zitejs 0.10.0-start.4 → 0.10.0-start.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,12 @@
1
1
  import type { SourceDir } from "../sourceRoots.js";
2
2
  export declare const START_SERVER_ENTRY = ".zite/server.ts";
3
3
  export declare const START_INSTANCE_ENTRY = ".zite/start.ts";
4
- /** The server entry every Start app builds with. Platform-owned: regenerated, never edited. */
5
- export declare const renderStartServerEntry: () => string;
4
+ export type StartTarget = "cloudflare" | "node";
5
+ /**
6
+ * The server entry every Start app builds with. Platform-owned: regenerated,
7
+ * never edited. On Workers it's an entrypoint the host can also call over RPC.
8
+ */
9
+ export declare const renderStartServerEntry: (target?: StartTarget) => string;
6
10
  /** The start instance: the platform's middleware, then the app's own `src/start.ts` when it has one. */
7
11
  export declare const renderStartInstance: (hasAppStart: boolean) => string;
8
12
  export declare const hasAppStart: (appDir: string) => boolean;
@@ -8,8 +8,19 @@ const node_path_1 = require("node:path");
8
8
  exports.START_SERVER_ENTRY = ".zite/server.ts";
9
9
  exports.START_INSTANCE_ENTRY = ".zite/start.ts";
10
10
  const APP_START_FILES = ["src/start.ts", "src/start.tsx"];
11
- /** The server entry every Start app builds with. Platform-owned: regenerated, never edited. */
12
- const renderStartServerEntry = () => `// Generated by zitejs for TanStack Start apps. The platform owns this file.
11
+ /**
12
+ * The server entry every Start app builds with. Platform-owned: regenerated,
13
+ * never edited. On Workers it's an entrypoint the host can also call over RPC.
14
+ */
15
+ const renderStartServerEntry = (target = "cloudflare") => target === "cloudflare"
16
+ ? `// Generated by zitejs for TanStack Start apps. The platform owns this file.
17
+ import handler from '@tanstack/react-start/server-entry';
18
+ import { createZiteWorker } from 'zitejs/start/worker';
19
+ import { getRouter } from '../src/router';
20
+
21
+ export default createZiteWorker({ handler, getRouter });
22
+ `
23
+ : `// Generated by zitejs for TanStack Start apps. The platform owns this file.
13
24
  import handler, { createServerEntry } from '@tanstack/react-start/server-entry';
14
25
  import { createZiteFetch } from 'zitejs/start/server';
15
26
  import { getRouter } from '../src/router';
@@ -1,3 +1,4 @@
1
+ import type { ZiteTrigger } from "../backend/index.js";
1
2
  import type { LoadedWorkflow } from "./workflowRun.js";
2
3
  import type { WorkflowConfig } from "./workflow.js";
3
4
  export type Access = {
@@ -26,7 +27,13 @@ export type DescribedWorkflow = {
26
27
  file: string;
27
28
  description?: string;
28
29
  schedule?: WorkflowConfig["schedule"];
29
- trigger?: WorkflowConfig["trigger"];
30
+ /** As declared, without a provider trigger's `subscribe` and `unsubscribe`. */
31
+ trigger?: ZiteTrigger;
32
+ /** A provider trigger's: whether it can be taken down, and a hash of both functions' code, so publish registers it again when they change. */
33
+ registration?: {
34
+ unsubscribe: boolean;
35
+ hash: string;
36
+ };
30
37
  problems: string[];
31
38
  };
32
39
  export type Description = {
@@ -123,6 +123,19 @@ export function describeRoutes(routesById) {
123
123
  }
124
124
  return out;
125
125
  }
126
+ // cyrb53: enough to tell when the code changed, which is all publish asks.
127
+ const sourceHash = (text) => {
128
+ let h1 = 0xdeadbeef;
129
+ let h2 = 0x41c6ce57;
130
+ for (let i = 0; i < text.length; i++) {
131
+ const ch = text.charCodeAt(i);
132
+ h1 = Math.imul(h1 ^ ch, 2654435761);
133
+ h2 = Math.imul(h2 ^ ch, 1597334677);
134
+ }
135
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
136
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
137
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, "0");
138
+ };
126
139
  // A workflow's id is its export name: what the host invokes it by and the editor shows.
127
140
  export function describeWorkflows(workflows) {
128
141
  return workflows.map(({ id, file, workflow }) => {
@@ -133,7 +146,21 @@ export function describeWorkflows(workflows) {
133
146
  if (elsewhere)
134
147
  problems.push(`duplicate-workflow: ${id} is also exported from ${elsewhere.file}`);
135
148
  const { description, schedule, trigger } = workflow.config;
136
- return { id, file, description, ...(schedule ? { schedule } : { trigger }), problems };
149
+ if (schedule)
150
+ return { id, file, description, schedule, problems };
151
+ if (!trigger || trigger.every !== undefined || trigger.events !== undefined) {
152
+ return { id, file, description, trigger, problems };
153
+ }
154
+ const { subscribe, unsubscribe, ...declared } = trigger;
155
+ if (typeof subscribe !== "function") {
156
+ problems.push("workflow-trigger-unregistered: a provider trigger needs `subscribe` to register it with the provider");
157
+ return { id, file, description, trigger: declared, problems };
158
+ }
159
+ const registration = {
160
+ unsubscribe: typeof unsubscribe === "function",
161
+ hash: sourceHash(`${String(subscribe)}\n${typeof unsubscribe === "function" ? String(unsubscribe) : ""}`),
162
+ };
163
+ return { id, file, description, trigger: declared, registration, problems };
137
164
  });
138
165
  }
139
166
  export async function describeApp(opts) {
@@ -1,8 +1,12 @@
1
1
  import type { SourceDir } from "../sourceRoots.js";
2
2
  export declare const START_SERVER_ENTRY = ".zite/server.ts";
3
3
  export declare const START_INSTANCE_ENTRY = ".zite/start.ts";
4
- /** The server entry every Start app builds with. Platform-owned: regenerated, never edited. */
5
- export declare const renderStartServerEntry: () => string;
4
+ export type StartTarget = "cloudflare" | "node";
5
+ /**
6
+ * The server entry every Start app builds with. Platform-owned: regenerated,
7
+ * never edited. On Workers it's an entrypoint the host can also call over RPC.
8
+ */
9
+ export declare const renderStartServerEntry: (target?: StartTarget) => string;
6
10
  /** The start instance: the platform's middleware, then the app's own `src/start.ts` when it has one. */
7
11
  export declare const renderStartInstance: (hasAppStart: boolean) => string;
8
12
  export declare const hasAppStart: (appDir: string) => boolean;
@@ -3,8 +3,19 @@ import { dirname, join, relative } from "node:path";
3
3
  export const START_SERVER_ENTRY = ".zite/server.ts";
4
4
  export const START_INSTANCE_ENTRY = ".zite/start.ts";
5
5
  const APP_START_FILES = ["src/start.ts", "src/start.tsx"];
6
- /** The server entry every Start app builds with. Platform-owned: regenerated, never edited. */
7
- export const renderStartServerEntry = () => `// Generated by zitejs for TanStack Start apps. The platform owns this file.
6
+ /**
7
+ * The server entry every Start app builds with. Platform-owned: regenerated,
8
+ * never edited. On Workers it's an entrypoint the host can also call over RPC.
9
+ */
10
+ export const renderStartServerEntry = (target = "cloudflare") => target === "cloudflare"
11
+ ? `// Generated by zitejs for TanStack Start apps. The platform owns this file.
12
+ import handler from '@tanstack/react-start/server-entry';
13
+ import { createZiteWorker } from 'zitejs/start/worker';
14
+ import { getRouter } from '../src/router';
15
+
16
+ export default createZiteWorker({ handler, getRouter });
17
+ `
18
+ : `// Generated by zitejs for TanStack Start apps. The platform owns this file.
8
19
  import handler, { createServerEntry } from '@tanstack/react-start/server-entry';
9
20
  import { createZiteFetch } from 'zitejs/start/server';
10
21
  import { getRouter } from '../src/router';
@@ -1,9 +1,9 @@
1
1
  import type { User } from "./types.js";
2
2
  export type { User, ZiteRequestContext } from "./types.js";
3
3
  export { createZiteRouter, type ZiteRouterContext } from "./router.js";
4
- export { createWorkflow, type Workflow, type WorkflowArgs, type WorkflowConfig } from "./workflow.js";
4
+ export { createWorkflow, type StartProviderTrigger, type StartTrigger, type Workflow, type WorkflowArgs, type WorkflowConfig, } from "./workflow.js";
5
5
  export type { ZiteSchedule } from "../schedules/index.js";
6
- export type { ZiteTrigger } from "../backend/index.js";
6
+ export type { ZiteTrigger, ZiteTriggerSubscribe, ZiteTriggerSubscribeInput, ZiteTriggerSubscribeResult, ZiteTriggerUnsubscribe, ZiteTriggerUnsubscribeInput, } from "../backend/index.js";
7
7
  declare module "@tanstack/start-client-core" {
8
8
  interface ServerFnOptions {
9
9
  /** What this function does, in a sentence. Shown in the editor. */
@@ -4,7 +4,7 @@ import { USAGE_TOKEN_QUERY_PARAM } from "../auth/constants.js";
4
4
  import { currentHost } from "./host.js";
5
5
  import { getHost, inScope, runInScope } from "./scope.js";
6
6
  export { createZiteRouter } from "./router.js";
7
- export { createWorkflow } from "./workflow.js";
7
+ export { createWorkflow, } from "./workflow.js";
8
8
  const STATUS = {
9
9
  BAD_REQUEST: 400,
10
10
  INVALID_INPUT: 400,
@@ -0,0 +1,13 @@
1
+ import type { LoadedWorkflow } from "./workflowRun.js";
2
+ export type RegistrationResult = {
3
+ ok: true;
4
+ output: unknown;
5
+ } | {
6
+ ok: false;
7
+ error: {
8
+ code: string;
9
+ message: string;
10
+ };
11
+ };
12
+ /** Undefined when the workflow has no such function. */
13
+ export declare function runRegistration(workflows: LoadedWorkflow[], name: string, op: unknown, input: unknown): Promise<RegistrationResult | undefined>;
@@ -0,0 +1,21 @@
1
+ // Server-only: a provider trigger's `subscribe` or `unsubscribe`, which the host
2
+ // runs at publish. Not a run: nothing is logged, since what `subscribe` returns
3
+ // carries the provider's signing secret.
4
+ import { errorOf } from "./runLog.js";
5
+ import { runInScope } from "./scope.js";
6
+ /** Undefined when the workflow has no such function. */
7
+ export async function runRegistration(workflows, name, op, input) {
8
+ const trigger = workflows.find((w) => w.id === name)?.workflow.config.trigger;
9
+ if (!trigger || !("subscribe" in trigger))
10
+ return undefined;
11
+ const fn = op === "subscribe" ? trigger.subscribe : op === "unsubscribe" ? trigger.unsubscribe : undefined;
12
+ if (typeof fn !== "function")
13
+ return undefined;
14
+ try {
15
+ const output = await runInScope({ kind: "workflow", user: null }, () => fn(input));
16
+ return { ok: true, output: output ?? null };
17
+ }
18
+ catch (error) {
19
+ return { ok: false, error: errorOf(error) };
20
+ }
21
+ }
@@ -1,7 +1,7 @@
1
- import type { HostProps, PlatformBinding, ZiteRequestContext } from "./types.js";
1
+ import type { HostProps, PlatformBinding, RenderWatch, ZiteRequestContext } from "./types.js";
2
2
  export { runWithHost } from "./scope.js";
3
3
  export { describeApp } from "./describe.js";
4
- export type { HostProps, PlatformBinding, PlatformCallInput, PlatformCallResult, PlatformSession, RunRecord } from "./types.js";
4
+ export type { HostProps, PlatformBinding, PlatformCallInput, PlatformCallResult, PlatformSession, RenderWatch, RunRecord } from "./types.js";
5
5
  /**
6
6
  * Routes every zitejs data, integration and email call to this request's
7
7
  * platform, on one session, and only inside an access scope. The access kind
@@ -22,16 +22,30 @@ export declare const builtFunctionIds: () => string[];
22
22
  /** Each server function's `description` option, written in by `zite()` at build. */
23
23
  export declare const builtFunctionDescriptions: () => Record<string, string>;
24
24
  type Fetch = (request: Request, ...rest: unknown[]) => Promise<Response> | Response;
25
- /**
26
- * The fetch handler of a Start app's server entry. A request the host
27
- * dispatched carries its identity and platform in props; anything else (describe
28
- * under `vite preview`, tests) reads `x-zite-*` headers and uses `platform`
29
- * if one is given.
30
- */
31
- export declare function createZiteFetch(options: {
25
+ type WorkerContext = {
26
+ waitUntil(promise: Promise<unknown>): void;
27
+ };
28
+ export type ZiteServerOptions = {
32
29
  handler: {
33
30
  fetch: Fetch;
34
31
  };
35
32
  getRouter: () => unknown;
36
33
  platform?: PlatformBinding;
37
- }): Fetch;
34
+ };
35
+ type HostedContext = WorkerContext & {
36
+ props?: Partial<HostProps>;
37
+ };
38
+ export type ZiteHandlers = {
39
+ fetch: Fetch;
40
+ /** A page the host may cache: as `fetch`, with every read and outbound fetch reported to `watch`. */
41
+ render(request: Request, ctx: HostedContext, watch: RenderWatch): Promise<Response>;
42
+ };
43
+ /**
44
+ * The handlers of a Start app's server entry. A request the host dispatched
45
+ * carries its identity and platform in props; anything else (describe under
46
+ * `vite preview`, tests) reads `x-zite-*` headers and uses `platform` if one
47
+ * is given.
48
+ */
49
+ export declare function createZiteHandlers(options: ZiteServerOptions): ZiteHandlers;
50
+ /** The fetch handler alone, for entries that take no RPC. */
51
+ export declare function createZiteFetch(options: ZiteServerOptions): Fetch;
@@ -1,5 +1,6 @@
1
1
  import { ZiteError, ZITE_ERROR_STATUS } from "./index.js";
2
2
  import { describeApp } from "./describe.js";
3
+ import { runRegistration } from "./registration.js";
3
4
  import { preview } from "./runLog.js";
4
5
  import { getHost, requireScope, runWithHost } from "./scope.js";
5
6
  import { loadWorkflows, loggedInput, runWorkflow } from "./workflowRun.js";
@@ -18,7 +19,7 @@ export function installPlatformTransport() {
18
19
  // Fails closed: no platform means no platform access, never a fallback.
19
20
  if (!host?.platform)
20
21
  throw new Error("The Zite platform binding is missing");
21
- host.session ??= Promise.resolve(host.platform.session());
22
+ host.session ??= Promise.resolve(host.watch ? host.platform.session(host.watch) : host.platform.session());
22
23
  const result = await (await host.session).call({ integrationId, className, methodName, params, access: scope.kind });
23
24
  if ("value" in result)
24
25
  return result.value;
@@ -26,9 +27,33 @@ export function installPlatformTransport() {
26
27
  throw new ZiteError(code in ZITE_ERROR_STATUS ? code : "INTERNAL_ERROR", message);
27
28
  };
28
29
  globalThis.__wrapSdkCall = sdkCall;
30
+ watchOutboundFetches();
29
31
  }
32
+ let fetchesWatched = false;
33
+ const fetchedHost = (input) => {
34
+ try {
35
+ return new URL(input instanceof Request ? input.url : input).host;
36
+ }
37
+ catch {
38
+ return "?";
39
+ }
40
+ };
41
+ // A page that fetched depends on more than the reads the host saw, so the host
42
+ // must hear of it before the fetch goes out. Next patches fetch for the same reason.
43
+ const watchOutboundFetches = () => {
44
+ if (fetchesWatched)
45
+ return;
46
+ fetchesWatched = true;
47
+ const nativeFetch = globalThis.fetch.bind(globalThis);
48
+ globalThis.fetch = async (input, init) => {
49
+ const watch = getHost()?.watch;
50
+ if (watch)
51
+ await watch.uncacheable(`fetch ${fetchedHost(input)}`);
52
+ return nativeFetch(input, init);
53
+ };
54
+ };
30
55
  const decodeUser = (value) => JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0))));
31
- const INVOKE_KINDS = new Set(["webhook", "describe", "workflow", "warm"]);
56
+ const INVOKE_KINDS = new Set(["webhook", "describe", "workflow", "register", "warm"]);
32
57
  /** What the host dispatched this request with. Headers are ignored: props are the only thing it alone can set. */
33
58
  export function hostContextFromProps(request, props) {
34
59
  return {
@@ -105,6 +130,19 @@ async function invokeWorkflow(request, host, workflows) {
105
130
  });
106
131
  return Response.json(result, { status: result.ok ? 200 : 500 });
107
132
  }
133
+ // The host sends `{ op, input }`: `subscribe` with the platform's notification
134
+ // URL and secret, or `unsubscribe` with what `subscribe` returned.
135
+ async function invokeRegistration(request, host, workflows) {
136
+ if (request.method !== "POST" || new URL(request.url).pathname !== INVOKE_PATH)
137
+ return new Response("Not found", { status: 404 });
138
+ const name = host.invoke?.name ?? "";
139
+ const body = (await request.json().catch(() => null));
140
+ const result = await runRegistration(workflows, name, body?.op, body?.input);
141
+ if (!result) {
142
+ return Response.json({ ok: false, error: { code: "NOT_FOUND", message: `${name} has no ${String(body?.op)}` } }, { status: 404 });
143
+ }
144
+ return Response.json(result, { status: result.ok ? 200 : 500 });
145
+ }
108
146
  const isZiteError = (error) => error instanceof Error && error.name === "ZiteError" && error.code in ZITE_ERROR_STATUS;
109
147
  // TanStack renders any loader error as a 500. A page refused with a ZiteError
110
148
  // (a missing role, a record that isn't there) answers with that error's status.
@@ -114,21 +152,22 @@ const pageStatus = (host) => {
114
152
  return isZiteError(failed) ? ZITE_ERROR_STATUS[failed.code] : undefined;
115
153
  };
116
154
  /**
117
- * The fetch handler of a Start app's server entry. A request the host
118
- * dispatched carries its identity and platform in props; anything else (describe
119
- * under `vite preview`, tests) reads `x-zite-*` headers and uses `platform`
120
- * if one is given.
155
+ * The handlers of a Start app's server entry. A request the host dispatched
156
+ * carries its identity and platform in props; anything else (describe under
157
+ * `vite preview`, tests) reads `x-zite-*` headers and uses `platform` if one
158
+ * is given.
121
159
  */
122
- export function createZiteFetch(options) {
160
+ export function createZiteHandlers(options) {
123
161
  installPlatformTransport();
124
162
  // Loaded on first use, so a page request never pays for importing workflow modules.
125
163
  let workflows;
126
164
  const loadedWorkflows = () => (workflows ??= loadWorkflows());
127
- return async (request, ...rest) => {
128
- const ctx = rest[1];
165
+ const serve = async (request, ctx, watch) => {
129
166
  const host = ctx?.props?.platform
130
167
  ? hostContextFromProps(request, ctx.props)
131
168
  : hostContextFromRequest(request, options.platform);
169
+ if (watch)
170
+ host.watch = watch;
132
171
  // Starting this isolate was the point: the platform warms a build before its
133
172
  // first visitors, and no app code runs.
134
173
  if (host.invoke?.kind === "warm")
@@ -142,7 +181,11 @@ export function createZiteFetch(options) {
142
181
  workflows: await loadedWorkflows(),
143
182
  }));
144
183
  }
145
- let response = await runWithHost(host, async () => host.invoke?.kind === "workflow" ? invokeWorkflow(request, host, await loadedWorkflows()) : options.handler.fetch(request));
184
+ let response = await runWithHost(host, async () => host.invoke?.kind === "workflow"
185
+ ? invokeWorkflow(request, host, await loadedWorkflows())
186
+ : host.invoke?.kind === "register"
187
+ ? invokeRegistration(request, host, await loadedWorkflows())
188
+ : options.handler.fetch(request));
146
189
  const status = response.status === 500 ? pageStatus(host) : undefined;
147
190
  if (status)
148
191
  response = new Response(response.body, { status, headers: response.headers });
@@ -161,4 +204,12 @@ export function createZiteFetch(options) {
161
204
  ctx?.waitUntil(flushed);
162
205
  });
163
206
  };
207
+ return {
208
+ fetch: (request, ...rest) => serve(request, rest[1]),
209
+ render: (request, ctx, watch) => serve(request, ctx, watch),
210
+ };
211
+ }
212
+ /** The fetch handler alone, for entries that take no RPC. */
213
+ export function createZiteFetch(options) {
214
+ return createZiteHandlers(options).fetch;
164
215
  }
@@ -47,10 +47,17 @@ export type PlatformSession = {
47
47
  runs: RunRecord[];
48
48
  }): Promise<void>;
49
49
  };
50
+ /**
51
+ * The host's ear on a page render it may cache, from `render()`. Reports can
52
+ * only make the page less cacheable, so an app gains nothing by sending them.
53
+ */
54
+ export type RenderWatch = {
55
+ uncacheable(reason: string): Promise<void>;
56
+ };
50
57
  /** What the host hands each request: its platform, already scoped to the app, build, request and user. */
51
58
  export type PlatformBinding = {
52
- /** Every platform call of one request goes through one session. */
53
- session(): PlatformSession | Promise<PlatformSession>;
59
+ /** Every platform call of one request goes through one session. A render's session reports what it read to `watch`. */
60
+ session(watch?: RenderWatch): PlatformSession | Promise<PlatformSession>;
54
61
  /** One batch per request, after its response has ended. */
55
62
  runs(batch: {
56
63
  runs: RunRecord[];
@@ -58,7 +65,7 @@ export type PlatformBinding = {
58
65
  };
59
66
  export type HostMode = "live" | "preview" | "validate";
60
67
  export type HostInvoke = {
61
- kind: "webhook" | "describe" | "workflow" | "warm";
68
+ kind: "webhook" | "describe" | "workflow" | "register" | "warm";
62
69
  name: string;
63
70
  };
64
71
  /** The per-request props the host dispatches with. Only the host can set them. */
@@ -80,6 +87,8 @@ export type ZiteRequestContext = {
80
87
  runs: RunRecord[];
81
88
  platform?: PlatformBinding;
82
89
  session?: Promise<PlatformSession>;
90
+ /** Set when the host renders a page it may cache. */
91
+ watch?: RenderWatch;
83
92
  /** This request's router, so a page's status can follow the error it rendered. */
84
93
  router?: unknown;
85
94
  };
@@ -32,8 +32,9 @@ export function zite(options = {}) {
32
32
  // Platform-owned, so they're rewritten whenever they differ. Sandboxes run the
33
33
  // image's zitejs for `generate`, which may predate Start; the plugin can't.
34
34
  const appDir = process.cwd();
35
- if (!options.serverEntry)
36
- writeIfChanged(appDir, START_SERVER_ENTRY, renderStartServerEntry());
35
+ if (!options.serverEntry) {
36
+ writeIfChanged(appDir, START_SERVER_ENTRY, renderStartServerEntry(options.cloudflare === false ? "node" : "cloudflare"));
37
+ }
37
38
  writeIfChanged(appDir, START_INSTANCE_ENTRY, renderStartInstance(hasAppStart(appDir)));
38
39
  // Every server function TanStack's compiler found, from its own hook, by id.
39
40
  const ids = new Map();
@@ -125,7 +126,7 @@ export function zite(options = {}) {
125
126
  behavior: "error",
126
127
  client: {
127
128
  // Appended to TanStack's own.
128
- specifiers: ["zitejs/start/server", "zitejs/start/describe", "zitejs/runtime"],
129
+ specifiers: ["zitejs/start/server", "zitejs/start/worker", "zitejs/start/describe", "zitejs/runtime"],
129
130
  // Replaces TanStack's list, so its `**/*.server.*` is restated. File
130
131
  // patterns also catch `zitejs/db` and `zitejs/integrations`, which the
131
132
  // aliases above rewrite before specifiers are matched.
@@ -0,0 +1,11 @@
1
+ import { type ZiteServerOptions } from "./server.js";
2
+ import type { RenderWatch } from "./types.js";
3
+ export type ZiteWorker = new (ctx: unknown, env: unknown) => {
4
+ fetch(request: Request): Promise<Response>;
5
+ render(request: Request, watch: RenderWatch): Promise<Response>;
6
+ };
7
+ /**
8
+ * A Start app's Worker. The host sends most requests to `fetch`, and a page it
9
+ * may cache to `render` over RPC, with a watch for what the page depends on.
10
+ */
11
+ export declare function createZiteWorker(options: ZiteServerOptions): ZiteWorker;
@@ -0,0 +1,20 @@
1
+ // For the generated server entry of a Workers build only. Apps never import this.
2
+ import { WorkerEntrypoint } from "cloudflare:workers";
3
+ import { createZiteHandlers } from "./server.js";
4
+ /**
5
+ * A Start app's Worker. The host sends most requests to `fetch`, and a page it
6
+ * may cache to `render` over RPC, with a watch for what the page depends on.
7
+ */
8
+ export function createZiteWorker(options) {
9
+ const handlers = createZiteHandlers(options);
10
+ return class extends WorkerEntrypoint {
11
+ async fetch(request) {
12
+ return handlers.fetch(request, this.env, this.ctx);
13
+ }
14
+ // An argument's stubs are disposed when the call returns, and a streamed
15
+ // page can still read after that.
16
+ async render(request, watch) {
17
+ return handlers.render(request, this.ctx, watch.dup?.() ?? watch);
18
+ }
19
+ };
20
+ }
@@ -1,6 +1,20 @@
1
- import type { ZiteTrigger, ZiteTriggerContext } from "../backend/index.js";
1
+ import type { ZiteTrigger, ZiteTriggerContext, ZiteTriggerSubscribe, ZiteTriggerUnsubscribe } from "../backend/index.js";
2
2
  import type { ZiteSchedule } from "../schedules/index.js";
3
3
  declare const WORKFLOW: unique symbol;
4
+ type ProviderTriggerBase = Extract<ZiteTrigger, {
5
+ every?: never;
6
+ events?: never;
7
+ }>;
8
+ /**
9
+ * A provider trigger, registered with the provider by its `subscribe` when the
10
+ * app is published and taken down by its `unsubscribe` when it no longer is.
11
+ * Both run on the platform with only the trigger's own connection.
12
+ */
13
+ export type StartProviderTrigger = ProviderTriggerBase & {
14
+ subscribe: ZiteTriggerSubscribe;
15
+ unsubscribe?: ZiteTriggerUnsubscribe;
16
+ };
17
+ export type StartTrigger = Exclude<ZiteTrigger, ProviderTriggerBase> | StartProviderTrigger;
4
18
  export type WorkflowConfig = {
5
19
  /** What this workflow does, in a sentence. Shown in the editor. */
6
20
  description?: string;
@@ -8,7 +22,7 @@ export type WorkflowConfig = {
8
22
  schedule: ZiteSchedule;
9
23
  trigger?: never;
10
24
  } | {
11
- trigger: ZiteTrigger;
25
+ trigger: StartTrigger;
12
26
  schedule?: never;
13
27
  });
14
28
  /** What a workflow's handler gets. A trigger adds `trigger`, typed by the kind it declares. */
@@ -13,10 +13,25 @@ describe("createWorkflow handler args", () => {
13
13
  createWorkflow({ trigger: { integration: "slack", events: ["message.channels"] } }).handler(({ trigger }) => {
14
14
  expectTypeOf(trigger).toEqualTypeOf();
15
15
  });
16
- createWorkflow({ trigger: { integration: "asana" } }).handler(({ trigger }) => {
16
+ createWorkflow({
17
+ trigger: { integration: "asana", subscribe: async () => ({ externalId: "w1" }), unsubscribe: async () => { } },
18
+ }).handler(({ trigger }) => {
17
19
  expectTypeOf(trigger).toEqualTypeOf();
18
20
  });
19
21
  });
22
+ it("registers a provider trigger with subscribe", () => {
23
+ // @ts-expect-error nothing would register it with the provider
24
+ createWorkflow({ trigger: { integration: "asana" } });
25
+ createWorkflow({
26
+ trigger: {
27
+ integration: "github",
28
+ subscribe: (input) => {
29
+ expectTypeOf(input.notificationUrl).toEqualTypeOf();
30
+ return { externalId: "1", secret: input.secret };
31
+ },
32
+ },
33
+ });
34
+ });
20
35
  it("takes a schedule or a trigger, not both", () => {
21
36
  // @ts-expect-error a workflow runs on one or the other
22
37
  createWorkflow({ schedule: { scheduleType: "oneTime", fireAt: "x", timezone: "UTC" }, trigger: { every: "5m" } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zitejs",
3
- "version": "0.10.0-start.4",
3
+ "version": "0.10.0-start.6",
4
4
  "description": "The Zite framework — build apps on Zite Database",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -101,6 +101,11 @@
101
101
  "import": "./dist/esm/start/server.js",
102
102
  "default": "./dist/esm/start/server.js"
103
103
  },
104
+ "./start/worker": {
105
+ "types": "./dist/esm/start/worker.d.ts",
106
+ "import": "./dist/esm/start/worker.js",
107
+ "default": "./dist/esm/start/worker.js"
108
+ },
104
109
  "./start/instance": {
105
110
  "types": "./dist/esm/start/instance.d.ts",
106
111
  "import": "./dist/esm/start/instance.js",