import { z } from "zod"; import { extensionDeclarationFields, interfaceBaseFields } from "../views/base"; /* -------------------------------------------------------------------------- */ /* Contract — what a service callback receives */ /* -------------------------------------------------------------------------- */ /** * Contract version stamped on every defined service. Lets the workbench host * and the generated worker artifact evolve the service contract without * breaking already-deployed services; bumped only on a breaking change. * @internal */ export const SERVICE_CONTRACT_VERSION = 1; /** * The service's own declaration, surfaced to the callback. * @public */ export interface ServiceInfo { readonly type: string; readonly name: string; } /** * Context every service callback receives when its worker starts. Mirrors how a * view component receives its `view` — the service receives its own `service`. * The OS handle a service acts through (e.g. dock badges) lands in a later * iteration; this iteration just runs the worker. * @public */ export interface ServiceContext { readonly service: ServiceInfo; } /** * A service callback. Runs once inside the worker on start; returns an optional * disposer the host calls before terminating the worker. * @public */ export type ServiceCallback = (context: ServiceContext) => void | (() => void); /** Fields every service declaration shares, whatever its type. */ const serviceBaseFields = extensionDeclarationFields("Service"); /* -------------------------------------------------------------------------- */ /* Worker — the only service type today */ /* -------------------------------------------------------------------------- */ /** * Declaration schema for a `worker` service — what a developer writes in * `unstable_defineApp({ services })`. Drives the build's worker-artifact * codegen. Service-specific fields are added here as the contract grows. */ const WorkerServiceSchema = z.object({ /** Discriminator — selects the worker artifact the build generates. */ type: z.literal("worker"), ...serviceBaseFields, }); /** * A `worker` interface — what the application service stores and returns. On * the type level a service is just an interface discriminated by * `interface_type`; the build turns a `{ type, name, src }` declaration into * this `interface_type: "worker"` record the workbench host runs. Shares the * interface shape (`id`/`deployment_id`/`name`/`entry_point`) with views, and * is folded into the {@link InterfaceSchema} union over in `views/interface`. */ export const WorkerInterfaceSchema = z.object({ ...interfaceBaseFields, /** Discriminator — matches the declaration's `type`. */ interface_type: z.literal("worker"), }); /* -------------------------------------------------------------------------- */ /* Service types — the registry of supported service kinds */ /* -------------------------------------------------------------------------- */ /** * Every supported service type. The first argument to `unstable_defineService`. * Add a service type by adding its schema above and registering it here + in * the declaration union below. * @public */ export type ServiceType = "worker"; /** The callback shape each service type defines, keyed by type. */ interface ServiceCallbacksByType { worker: ServiceCallback; } /** * A service declared on an app, discriminated by `type` — one variant per * service type, sharing `name`/`src`. Metadata only; built into a worker * artifact and persisted to the application service on deploy, never part of * the app manifest. * @internal */ export const ServiceDeclarationSchema = z.discriminatedUnion("type", [ WorkerServiceSchema, ]); /** * A service declared on an app (a `{ type, name, src }` variant). Carried on * `DefineAppInput`, persisted to the application service on deploy. * @public */ export type ServiceDeclaration = z.output; /* -------------------------------------------------------------------------- */ /* Interface records — the worker-typed subset of the interface model */ /* -------------------------------------------------------------------------- */ /** * A service an application runs — the worker-typed interface record, the same * shape the application service returns in its `interfaces` list (the * `interface_type: "worker"` member of the `Interface` union, counterpart of * `Panel`). Defined off {@link WorkerInterfaceSchema} here rather than extracted * from the union, so this module owns the worker shape without depending back on * `views/interface` (which folds this schema into the union). * @public */ export type Service = z.output; /** * A local application's service — a {@link Service} without the * service-assigned `id`/`deployment_id`, i.e. the worker-typed subset of the * `LocalInterface` union. * @public */ export type LocalService = Omit; /* -------------------------------------------------------------------------- */ /* Authoring — the `unstable_defineService` factory */ /* -------------------------------------------------------------------------- */ /** * The result of `unstable_defineService`: the author's callback, the service * type, and the internal contract version the worker artifact targets. * @public */ export interface DefinedService { readonly type: TType; /** @internal */ readonly version: typeof SERVICE_CONTRACT_VERSION; readonly run: ServiceCallbacksByType[TType]; } /** * Define a Sanity Workbench background service. The first argument narrows the * callback shape — `"worker"` runs the callback inside a Web Worker, where it * can emit dock-badge updates via `os.updateBadge(...)` and return a disposer. * * Identity at runtime: returns the callback tagged with its type and the * contract version, for the CLI build to generate a worker artifact from. Used * as the default export of a service's `src` file. * @public */ export function unstable_defineService( type: TType, run: ServiceCallbacksByType[TType], ): DefinedService { return { type, version: SERVICE_CONTRACT_VERSION, run }; }