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.
- package/dist/cjs/start/entries.d.ts +6 -2
- package/dist/cjs/start/entries.js +13 -2
- package/dist/esm/start/describe.d.ts +8 -1
- package/dist/esm/start/describe.js +28 -1
- package/dist/esm/start/entries.d.ts +6 -2
- package/dist/esm/start/entries.js +13 -2
- package/dist/esm/start/index.d.ts +2 -2
- package/dist/esm/start/index.js +1 -1
- package/dist/esm/start/registration.d.ts +13 -0
- package/dist/esm/start/registration.js +21 -0
- package/dist/esm/start/server.d.ts +24 -10
- package/dist/esm/start/server.js +61 -10
- package/dist/esm/start/types.d.ts +12 -3
- package/dist/esm/start/vite.js +4 -3
- package/dist/esm/start/worker.d.ts +11 -0
- package/dist/esm/start/worker.js +20 -0
- package/dist/esm/start/workflow.d.ts +16 -2
- package/dist/esm/start/workflow.test-d.js +16 -1
- package/package.json +6 -1
|
@@ -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
|
-
|
|
5
|
-
|
|
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
|
-
/**
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
/**
|
|
7
|
-
|
|
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. */
|
package/dist/esm/start/index.js
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
}
|
|
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;
|
package/dist/esm/start/server.js
CHANGED
|
@@ -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
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
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
|
|
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
|
-
|
|
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"
|
|
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
|
};
|
package/dist/esm/start/vite.js
CHANGED
|
@@ -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:
|
|
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({
|
|
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.
|
|
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",
|