Named app fixtures
Browser tests can ask the real app process to seed committed records or perform small test operations.
Import the Node helpers from dyno/fixtures; keep factories and database connections in your app.
import { defineFixtureOperations, createFixtureController, createFixtureHttpHandler } from 'dyno/fixtures';
let controller = createFixtureController({ workerId, context: { db, factories }, operations: defineFixtureOperations({ 'seed.user': ({ factories }, input, { signal }) => factories.createUser(input, { signal }), }), pause: () => appRequests.pause(), reset: ({ db }, { signal }) => resetDatabase(db, { signal }), drain: (_context, { signal }) => stopAndJoinAppJobs({ signal }),});let handleFixtures = createFixtureHttpHandler(controller, { token: privateControlToken });Call handleFixtures(request, response) before delegating to your ordinary Node app handler.
It returns whether it handled the request. Keep control reachable outside the app runtime being paused.
An app with parsed endpoints can call controller.dispatch() directly; it needs no HTTP adapter.
Connect the worker’s lifecycle callbacks in Node setup:
import { connectFixtureControl } from 'dyno/fixtures';
let control = connectFixtureControl({ url: appOrigin, workerId, token: privateControlToken });return { beforeExecution: control.begin, drain: control.drain, teardown: () => stopOwnedAppResources(),};Register controller.close() before releasing its app dependencies. A reset requires pause() to
synchronously return an admission hold with idle({ signal }) and resume(). Construction acquires
that initial hold. Begin waits for idle, resets exclusively, then resumes. Resume commits preparation;
if the caller loses the response after that point, the worker is open and can still be drained by
execution identity. Drain closes admission and starts your app drain before waiting, so it can stop
producers or end streams. Keep the hold until
the next successful preparation. Failed or canceled reset/drain never reopens the worker.
Tests call await app.fixture('seed.user', { email: 'alice@example.test' }), then exercise the real app
through navigation and Interactor. Omitted input means null. Operations return JSON; reset may return
a JSON record of execution fixture values or undefined. Drain returns no browser data.
The default routes are POST /__dyno_fixtures__/begin, /call and /drain. Set the same base path on handler and control client to change them. Control uses a private
token; browser calls receive only their execution capability and same-origin call path. Calls never
retry or follow redirects. Keep tokens and returned records out of your own logs.
Input is limited to 64 KiB, result envelopes to 1 MiB, and JSON depth to 64. Values must be ordinary JSON: no undefined properties, functions, dates, custom serializers, sparse arrays or cycles. Callback errors have fixed public messages with their original cause available only in Node.
Aborting HTTP cannot undo committed writes or queued work. Operations must honor their signal; the controller retains their actual promises until settlement. Your app still owns jobs, service workers, database reset and shutdown. A delayed ordinary app request has no fixture execution envelope, so stopping its source remains app policy. Close is terminal; it uses its own cleanup signal and never resumes admission.