Skip to content

Custom worker

The plugin generates a worker from the adapters your app imports. Keep that default unless you need custom parsing, protocol handlers or runtime settings. You can supply your own worker file while keeping the plugin and client setup.

Setup What you configure
Generated worker The bundler plugin and createSpinetab()
Custom worker The same setup, plus spinetab.worker.ts listing your adapters and their options
Manual wiring Worker and local runtime factories passed to createSpinetab()

The generated worker constructs every adapter without options. Anything that takes a function, or a runtime setting, belongs in your own worker file:

Need In your worker file
WebSocket topic protocols websocketAdapter({ protocols })
Custom SSE decoders sseAdapter({ decoders })
Custom stream parsers streamAdapter({ parsers })
Socket.IO event routes socketIoAdapter({ routes })
A tRPC data transformer, such as superjson trpcWsAdapter({ transformer }) or trpcSseAdapter({ transformer })
Runtime limits defineWorker(adapters, { limits })
Diagnostics defineWorker(adapters, { diagnostics })

A tRPC link also needs transformer: true on the page; see tRPC. Each source page describes its worker customisation options.

Create src/spinetab.worker.ts and register the adapters you use:

src/spinetab.worker.ts
import { pollingAdapter } from "spinetab/polling/runtime";
import { sseAdapter } from "spinetab/sse/runtime";
import { defineWorker } from "spinetab/worker";
export default defineWorker(() => [pollingAdapter(), sseAdapter()]);

The plugin looks for spinetab.worker.ts in src/, in app/ and at the project root (.mts, .js and .mjs work too). When it finds one, it stops generating and uses yours. The plugin line and createSpinetab() stay as they are.

Anywhere else, name the file with the worker option, relative to the project root:

spinetab({ worker: "src/live/spinetab.worker.ts" });

Your file lists every adapter the app uses; the plugin no longer infers them. Restart the dev server after creating or deleting the file.

Provider credentials go only to the worker’s own origin. To allow another origin, list it in one place. For a generated worker, that is the plugin options:

spinetab({ credentialOrigins: ["https://api.example.com"] });

For your own worker file, it is defineWorker:

src/spinetab.worker.ts
import { graphqlWsAdapter } from "spinetab/graphql-ws/runtime";
import { defineWorker } from "spinetab/worker";
export default defineWorker(() => [graphqlWsAdapter()], {
credentialOrigins: ["https://api.example.com"],
});

Never both: a worker file with adapters or credentialOrigins in the plugin options fails the build. The list is deployment configuration, compiled into the worker. It takes effect for new SharedWorker instances; open tabs keep the old worker until they close. Credentials and scopes has the rules for entries.

Pass worker and local to createSpinetab and the client ignores the plugin’s wiring entirely. Wire it by hand has the recipe.

Build messages start with [spinetab] and include a code. Use the message’s file path, when present, to locate the problem.

Code What to do
no-adapters Import a source, set adapters, or remove the plugin if unused.
adapter-not-generated Add the missing adapter to adapters or your worker file.
missing-peer Install the peer package named in the message.
unknown-adapter Use an adapter name from the list below.
unknown-option, invalid-options Pass an options object containing worker, adapters or credentialOrigins; Next.js also accepts dir.
invalid-worker-option, worker-file-missing Point worker at an existing file relative to the project root.
invalid-dir-option, project-directory-unknown Run Next.js from its project directory, or set an absolute dir.
package-not-installed Install Spinetab in the application package.
scan-fallback Check the named source file, or set adapters explicitly.
invalid-credential-origin Use an exact HTTPS origin without a path, query or fragment; HTTP is allowed for loopback.
worker-file-with-options Move adapters and credentialOrigins from the plugin into your custom worker.
worker-file-conflict Keep one conventionally named worker file, or select one with worker.
worker-file-no-default Export defineWorker(...) as the worker file’s default export.
worker-file-not-wired Check the Next.js project directory and dir option.
restart-required, adapter-restart-required Restart the dev server and reload open tabs.
wiring-not-applied Check for aliases or plugins resolving Spinetab before its plugin.
worker-parser-disabled Restore the bundler’s module.parser.javascript.worker setting.

no-adapters warns in development and fails production builds. Use adapters: [] only if an empty worker is intentional. missing-peer can be a warning for inferred adapters; explicitly selected adapters require their peers.

For an app that imports spinetab/polling and spinetab/sse, the plugin generates a module like this:

// Generated by spinetab. Do not edit.
import { pollingAdapter } from "spinetab/polling/runtime";
import { sseAdapter } from "spinetab/sse/runtime";
import { defineWorker } from "spinetab/worker";
export default defineWorker(() => [pollingAdapter(), sseAdapter()]);

The same module runs as the SharedWorker and, in a tab that cannot share, as the lazy local runtime. Adapter inference explains how imports select adapters. To choose the set yourself, pass adapters:

spinetab({ adapters: ["polling", "sse"] });

The names are polling, sse, stream, websocket, graphql-ws, graphql-sse, socket-io, trpc-ws, trpc-sse and ai-sdk.