zitejs 0.9.142 → 0.9.144
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/integrations/rules.d.ts +9 -0
- package/dist/cjs/integrations/rules.js +22 -0
- package/dist/cjs/integrations/rules.test.js +1 -0
- package/dist/cjs/meta/index.d.ts +14 -0
- package/dist/cjs/meta/index.js +12 -0
- package/dist/cjs/notifications/index.d.ts +25 -0
- package/dist/cjs/notifications/index.js +12 -0
- package/dist/esm/integrations/rules.d.ts +9 -0
- package/dist/esm/integrations/rules.js +22 -0
- package/dist/esm/integrations/rules.test.js +1 -0
- package/dist/esm/meta/index.d.ts +14 -0
- package/dist/esm/meta/index.js +8 -0
- package/dist/esm/notifications/index.d.ts +25 -0
- package/dist/esm/notifications/index.js +8 -0
- package/package.json +1 -1
- package/dist/cjs/start/describeBuilt.d.ts +0 -3
- package/dist/cjs/start/describeBuilt.js +0 -106
- package/dist/cjs/start/entries.d.ts +0 -20
- package/dist/cjs/start/entries.js +0 -104
- package/dist/cjs/start/problems.d.ts +0 -19
- package/dist/cjs/start/problems.js +0 -11
- package/dist/esm/start/describe.d.ts +0 -64
- package/dist/esm/start/describe.js +0 -176
- package/dist/esm/start/describeBuilt.d.ts +0 -3
- package/dist/esm/start/describeBuilt.js +0 -66
- package/dist/esm/start/descriptions.d.ts +0 -4
- package/dist/esm/start/descriptions.js +0 -47
- package/dist/esm/start/entries.d.ts +0 -20
- package/dist/esm/start/entries.js +0 -93
- package/dist/esm/start/host.d.ts +0 -3
- package/dist/esm/start/host.js +0 -4
- package/dist/esm/start/index.d.ts +0 -79
- package/dist/esm/start/index.js +0 -157
- package/dist/esm/start/instance.d.ts +0 -12
- package/dist/esm/start/instance.js +0 -73
- package/dist/esm/start/problems.d.ts +0 -19
- package/dist/esm/start/problems.js +0 -8
- package/dist/esm/start/registration.d.ts +0 -13
- package/dist/esm/start/registration.js +0 -21
- package/dist/esm/start/router.d.ts +0 -17
- package/dist/esm/start/router.js +0 -37
- package/dist/esm/start/runLog.d.ts +0 -5
- package/dist/esm/start/runLog.js +0 -18
- package/dist/esm/start/scope.d.ts +0 -15
- package/dist/esm/start/scope.js +0 -58
- package/dist/esm/start/server.d.ts +0 -51
- package/dist/esm/start/server.js +0 -215
- package/dist/esm/start/types.d.ts +0 -94
- package/dist/esm/start/types.js +0 -1
- package/dist/esm/start/vite.d.ts +0 -9
- package/dist/esm/start/vite.js +0 -144
- package/dist/esm/start/worker.d.ts +0 -11
- package/dist/esm/start/worker.js +0 -20
- package/dist/esm/start/workflow.d.ts +0 -50
- package/dist/esm/start/workflow.js +0 -13
- package/dist/esm/start/workflow.test-d.d.ts +0 -1
- package/dist/esm/start/workflow.test-d.js +0 -39
- package/dist/esm/start/workflowModules.d.ts +0 -1
- package/dist/esm/start/workflowModules.js +0 -3
- package/dist/esm/start/workflowRun.d.ts +0 -80
- package/dist/esm/start/workflowRun.js +0 -107
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export type User = {
|
|
2
|
-
id: string;
|
|
3
|
-
email: string;
|
|
4
|
-
name: string;
|
|
5
|
-
roles: ReadonlyArray<string>;
|
|
6
|
-
};
|
|
7
|
-
/** One server function call, as the platform's run log shows it. Input and output are truncated JSON. */
|
|
8
|
-
export type RunRecord = {
|
|
9
|
-
fn: string;
|
|
10
|
-
name?: string;
|
|
11
|
-
nested: boolean;
|
|
12
|
-
startedAt: number;
|
|
13
|
-
durationMs: number;
|
|
14
|
-
input?: string;
|
|
15
|
-
output?: string;
|
|
16
|
-
error?: {
|
|
17
|
-
code: string;
|
|
18
|
-
message: string;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
export type AccessKind = "signedIn" | "anyone" | "webhook" | "workflow";
|
|
22
|
-
/** One data, integration or email call. Who is calling is the platform's to know, never the app's to say. */
|
|
23
|
-
export type PlatformCallInput = {
|
|
24
|
-
integrationId: string;
|
|
25
|
-
className: string;
|
|
26
|
-
methodName: string;
|
|
27
|
-
params: unknown;
|
|
28
|
-
/** The access decision the call ran under; the platform may narrow on it but never widens. */
|
|
29
|
-
access: AccessKind;
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* A call's answer. Errors come back as values: Workers RPC keeps a thrown
|
|
33
|
-
* error's message but drops its code.
|
|
34
|
-
*/
|
|
35
|
-
export type PlatformCallResult = {
|
|
36
|
-
value: unknown;
|
|
37
|
-
} | {
|
|
38
|
-
error: {
|
|
39
|
-
code: string;
|
|
40
|
-
message: string;
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
export type PlatformSession = {
|
|
44
|
-
call(call: PlatformCallInput): Promise<PlatformCallResult>;
|
|
45
|
-
/** As `PlatformBinding.runs`, on a session the request already holds. Hosts before 0.10.0-start.4 lack it. */
|
|
46
|
-
runs?(batch: {
|
|
47
|
-
runs: RunRecord[];
|
|
48
|
-
}): Promise<void>;
|
|
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
|
-
};
|
|
57
|
-
/** What the host hands each request: its platform, already scoped to the app, build, request and user. */
|
|
58
|
-
export type PlatformBinding = {
|
|
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>;
|
|
61
|
-
/** One batch per request, after its response has ended. */
|
|
62
|
-
runs(batch: {
|
|
63
|
-
runs: RunRecord[];
|
|
64
|
-
}): Promise<void>;
|
|
65
|
-
};
|
|
66
|
-
export type HostMode = "live" | "preview" | "validate";
|
|
67
|
-
export type HostInvoke = {
|
|
68
|
-
kind: "webhook" | "describe" | "workflow" | "register" | "warm";
|
|
69
|
-
name: string;
|
|
70
|
-
};
|
|
71
|
-
/** The per-request props the host dispatches with. Only the host can set them. */
|
|
72
|
-
export type HostProps = {
|
|
73
|
-
platform: PlatformBinding;
|
|
74
|
-
requestId: string;
|
|
75
|
-
user: User | null;
|
|
76
|
-
mode: HostMode;
|
|
77
|
-
invoke: HostInvoke | null;
|
|
78
|
-
};
|
|
79
|
-
/** What the host resolved for this request. Set by the generated server entry, never by the browser. */
|
|
80
|
-
export type ZiteRequestContext = {
|
|
81
|
-
requestId: string;
|
|
82
|
-
url: string;
|
|
83
|
-
user: User | null;
|
|
84
|
-
mode: HostMode;
|
|
85
|
-
invoke: HostInvoke | null;
|
|
86
|
-
/** Filled by run capture, flushed to the platform once the response ends. */
|
|
87
|
-
runs: RunRecord[];
|
|
88
|
-
platform?: PlatformBinding;
|
|
89
|
-
session?: Promise<PlatformSession>;
|
|
90
|
-
/** Set when the host renders a page it may cache. */
|
|
91
|
-
watch?: RenderWatch;
|
|
92
|
-
/** This request's router, so a page's status can follow the error it rendered. */
|
|
93
|
-
router?: unknown;
|
|
94
|
-
};
|
package/dist/esm/start/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/esm/start/vite.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { PluginOption } from "vite";
|
|
2
|
-
export type ZiteOptions = {
|
|
3
|
-
/** A server entry the caller owns, relative to the app root. By default zite() writes the platform's. */
|
|
4
|
-
serverEntry?: string;
|
|
5
|
-
/** Build for Cloudflare Workers, the target the platform deploys. Defaults to true; false builds for Node. */
|
|
6
|
-
cloudflare?: boolean;
|
|
7
|
-
};
|
|
8
|
-
/** The one plugin a Zite app's `vite.config.ts` uses. */
|
|
9
|
-
export declare function zite(options?: ZiteOptions): PluginOption[];
|
package/dist/esm/start/vite.js
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
|
|
3
|
-
import viteReact from "@vitejs/plugin-react";
|
|
4
|
-
import { ziteId } from "../vite/index.js";
|
|
5
|
-
import { factoryDescriptions } from "./descriptions.js";
|
|
6
|
-
import { hasAppStart, renderStartInstance, renderStartServerEntry, renderWorkflowModules, START_INSTANCE_ENTRY, START_SERVER_ENTRY, workflowFiles, writeIfChanged, } from "./entries.js";
|
|
7
|
-
// Must match the literals in ./server.ts, which the build rewrites.
|
|
8
|
-
const FN_IDS_TOKEN = "__ZITE_FN_IDS__";
|
|
9
|
-
const FN_DESCRIPTIONS_LITERAL = /(["'`])__ZITE_FN_DESCRIPTIONS__\1/;
|
|
10
|
-
// zitejs's empty registry, which the build replaces with the app's.
|
|
11
|
-
const WORKFLOW_REGISTRY = /[\\/]zitejs[\\/]dist[\\/]esm[\\/]start[\\/]workflowModules\.js$/;
|
|
12
|
-
// Platform-pinned Worker settings. The deploy uses its own fixed template, so
|
|
13
|
-
// these only have to match it; apps don't carry a wrangler config.
|
|
14
|
-
const WORKER_COMPATIBILITY_DATE = "2026-09-25";
|
|
15
|
-
const WORKER_COMPATIBILITY_FLAGS = ["nodejs_compat"];
|
|
16
|
-
// Resolved from the app at build time; zitejs doesn't depend on it.
|
|
17
|
-
const loadCloudflare = async (main) => {
|
|
18
|
-
const { cloudflare } = (await import("@cloudflare/vite-plugin"));
|
|
19
|
-
return cloudflare({
|
|
20
|
-
viteEnvironment: { name: "ssr" },
|
|
21
|
-
config: {
|
|
22
|
-
name: "zite-app",
|
|
23
|
-
main,
|
|
24
|
-
compatibility_date: WORKER_COMPATIBILITY_DATE,
|
|
25
|
-
compatibility_flags: WORKER_COMPATIBILITY_FLAGS,
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
|
-
/** The one plugin a Zite app's `vite.config.ts` uses. */
|
|
30
|
-
export function zite(options = {}) {
|
|
31
|
-
const serverEntry = options.serverEntry ?? START_SERVER_ENTRY;
|
|
32
|
-
// Platform-owned, so they're rewritten whenever they differ. Sandboxes run the
|
|
33
|
-
// image's zitejs for `generate`, which may predate Start; the plugin can't.
|
|
34
|
-
const appDir = process.cwd();
|
|
35
|
-
if (!options.serverEntry) {
|
|
36
|
-
writeIfChanged(appDir, START_SERVER_ENTRY, renderStartServerEntry(options.cloudflare === false ? "node" : "cloudflare"));
|
|
37
|
-
}
|
|
38
|
-
writeIfChanged(appDir, START_INSTANCE_ENTRY, renderStartInstance(hasAppStart(appDir)));
|
|
39
|
-
// Every server function TanStack's compiler found, from its own hook, by id.
|
|
40
|
-
const ids = new Map();
|
|
41
|
-
const compileZitejs = {
|
|
42
|
-
name: "zite:compile-zitejs",
|
|
43
|
-
// The factories must go through TanStack's compiler in both environments.
|
|
44
|
-
// The peer dependency gets zitejs crawled already; this keeps it true if an
|
|
45
|
-
// install drops that. A Node build bundles everything, so externalized
|
|
46
|
-
// libraries can't load the root's React 18 renderer.
|
|
47
|
-
config: (config) => {
|
|
48
|
-
const root = path.resolve(config.root ?? ".");
|
|
49
|
-
const src = `${path.join(root, "src")}/`;
|
|
50
|
-
return {
|
|
51
|
-
ssr: { noExternal: options.cloudflare === false ? true : ["zitejs"] },
|
|
52
|
-
optimizeDeps: { exclude: ["zitejs"] },
|
|
53
|
-
resolve: {
|
|
54
|
-
// `@/` as in classic apps and shadcn, `~/` as in TanStack's templates.
|
|
55
|
-
alias: [
|
|
56
|
-
{ find: /^@\//, replacement: src },
|
|
57
|
-
{ find: /^~\//, replacement: src },
|
|
58
|
-
// The workspace SDKs `zitejs generate` writes, as classic apps map them.
|
|
59
|
-
{ find: /^zitejs\/db$/, replacement: path.join(root, "../../.zite/db.ts") },
|
|
60
|
-
{ find: /^zitejs\/integrations$/, replacement: path.join(root, ".zite/integrations/index.ts") },
|
|
61
|
-
{ find: /^zitejs\/email$/, replacement: path.join(root, ".zite/integrations/email.ts") },
|
|
62
|
-
{ find: /^zitejs\/meta$/, replacement: path.join(root, ".zite/meta.ts") },
|
|
63
|
-
],
|
|
64
|
-
// Shared workspace libraries resolve from the root, where classic apps
|
|
65
|
-
// keep React 18; recharts 2 needs the app's react-is 19 or drops series.
|
|
66
|
-
dedupe: ["react-is"],
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
},
|
|
70
|
-
// Each new isolate loads the whole server bundle, so its size is cold-start
|
|
71
|
-
// time; Vite leaves SSR output unminified.
|
|
72
|
-
configEnvironment: (name, config) => name === "ssr" && options.cloudflare !== false && config.build?.minify === undefined ? { build: { minify: true } } : undefined,
|
|
73
|
-
};
|
|
74
|
-
// Each server function's `description` option, by name, for the editor.
|
|
75
|
-
const descriptions = new Map();
|
|
76
|
-
const srcDir = `${path.join(appDir, "src")}${path.sep}`;
|
|
77
|
-
const functionIds = {
|
|
78
|
-
name: "zite:function-ids",
|
|
79
|
-
apply: "build",
|
|
80
|
-
transform(code, id) {
|
|
81
|
-
if (this.environment?.config.consumer !== "server" || !id.startsWith(srcDir) || !code.includes("description"))
|
|
82
|
-
return;
|
|
83
|
-
try {
|
|
84
|
-
for (const [name, text] of factoryDescriptions(this.parse(code)))
|
|
85
|
-
descriptions.set(name, text);
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
// Display-only: an unreadable module just shows no descriptions.
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
generateBundle(_, bundle) {
|
|
92
|
-
const described = Object.fromEntries([...descriptions].filter(([name]) => ids.has(name)));
|
|
93
|
-
for (const chunk of Object.values(bundle)) {
|
|
94
|
-
if (chunk.type !== "chunk")
|
|
95
|
-
continue;
|
|
96
|
-
if (chunk.code.includes(FN_IDS_TOKEN))
|
|
97
|
-
chunk.code = chunk.code.replace(FN_IDS_TOKEN, [...ids.keys()].join(","));
|
|
98
|
-
chunk.code = chunk.code.replace(FN_DESCRIPTIONS_LITERAL, () => JSON.stringify(JSON.stringify(described)));
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
const workflowRegistry = {
|
|
103
|
-
name: "zite:workflow-registry",
|
|
104
|
-
load: (id) => (WORKFLOW_REGISTRY.test(id.split("?")[0]) ? renderWorkflowModules(appDir, workflowFiles(appDir)) : undefined),
|
|
105
|
-
};
|
|
106
|
-
return [
|
|
107
|
-
tanstackStart({
|
|
108
|
-
// TanStack resolves these from `src/`.
|
|
109
|
-
server: { entry: `../${serverEntry.replace(/\.ts$/, "")}` },
|
|
110
|
-
start: { entry: `../${START_INSTANCE_ENTRY.replace(/\.ts$/, "")}` },
|
|
111
|
-
serverFns: {
|
|
112
|
-
generateFunctionId: ({ filename, functionName }) => {
|
|
113
|
-
const id = functionName.replace(/_createServerFn_handler$/, "");
|
|
114
|
-
if (/(^|\/)node_modules\//.test(filename)) {
|
|
115
|
-
throw new Error(`[zite] ${filename} defines server function ${id}; dependencies can't ship server functions`);
|
|
116
|
-
}
|
|
117
|
-
const existing = ids.get(id);
|
|
118
|
-
if (existing && existing !== filename) {
|
|
119
|
-
throw new Error(`[zite] server function name ${id} is used in both ${existing} and ${filename}`);
|
|
120
|
-
}
|
|
121
|
-
ids.set(id, filename);
|
|
122
|
-
return id;
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
importProtection: {
|
|
126
|
-
behavior: "error",
|
|
127
|
-
client: {
|
|
128
|
-
// Appended to TanStack's own.
|
|
129
|
-
specifiers: ["zitejs/start/server", "zitejs/start/worker", "zitejs/start/describe", "zitejs/runtime"],
|
|
130
|
-
// Replaces TanStack's list, so its `**/*.server.*` is restated. File
|
|
131
|
-
// patterns also catch `zitejs/db` and `zitejs/integrations`, which the
|
|
132
|
-
// aliases above rewrite before specifiers are matched.
|
|
133
|
-
files: ["**/*.server.*", "**/.zite/db.ts", "**/.zite/integrations/**"],
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
}),
|
|
137
|
-
options.cloudflare !== false ? loadCloudflare(serverEntry) : null,
|
|
138
|
-
viteReact(),
|
|
139
|
-
ziteId(),
|
|
140
|
-
compileZitejs,
|
|
141
|
-
functionIds,
|
|
142
|
-
workflowRegistry,
|
|
143
|
-
];
|
|
144
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
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;
|
package/dist/esm/start/worker.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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,50 +0,0 @@
|
|
|
1
|
-
import type { ZiteTrigger, ZiteTriggerContext, ZiteTriggerSubscribe, ZiteTriggerUnsubscribe } from "../backend/index.js";
|
|
2
|
-
import type { ZiteSchedule } from "../schedules/index.js";
|
|
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;
|
|
18
|
-
export type WorkflowConfig = {
|
|
19
|
-
/** What this workflow does, in a sentence. Shown in the editor. */
|
|
20
|
-
description?: string;
|
|
21
|
-
} & ({
|
|
22
|
-
schedule: ZiteSchedule;
|
|
23
|
-
trigger?: never;
|
|
24
|
-
} | {
|
|
25
|
-
trigger: StartTrigger;
|
|
26
|
-
schedule?: never;
|
|
27
|
-
});
|
|
28
|
-
/** What a workflow's handler gets. A trigger adds `trigger`, typed by the kind it declares. */
|
|
29
|
-
export type WorkflowArgs<TConfig extends WorkflowConfig> = {
|
|
30
|
-
input: unknown;
|
|
31
|
-
} & (TConfig extends {
|
|
32
|
-
trigger: infer T;
|
|
33
|
-
} ? {
|
|
34
|
-
trigger: ZiteTriggerContext<T>;
|
|
35
|
-
} : unknown);
|
|
36
|
-
export type Workflow<TConfig extends WorkflowConfig = WorkflowConfig> = {
|
|
37
|
-
readonly [WORKFLOW]: true;
|
|
38
|
-
readonly config: TConfig;
|
|
39
|
-
readonly handler: (args: WorkflowArgs<TConfig>) => unknown;
|
|
40
|
-
};
|
|
41
|
-
/**
|
|
42
|
-
* Scheduled or triggered work, in a `*.server.ts` file:
|
|
43
|
-
* `export const nightly = createWorkflow({ schedule }).handler(async () => { ... })`.
|
|
44
|
-
* Only the platform runs it. It has no user and no URL, and reads data like a server function does.
|
|
45
|
-
*/
|
|
46
|
-
export declare function createWorkflow<const TConfig extends WorkflowConfig>(config: TConfig): {
|
|
47
|
-
handler: (fn: (args: WorkflowArgs<TConfig>) => unknown) => Workflow<TConfig>;
|
|
48
|
-
};
|
|
49
|
-
export declare const isWorkflow: (value: unknown) => value is Workflow;
|
|
50
|
-
export {};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Shared across copies of zitejs in one bundle, like the access scope.
|
|
2
|
-
const WORKFLOW = Symbol.for("zitejs:workflow");
|
|
3
|
-
/**
|
|
4
|
-
* Scheduled or triggered work, in a `*.server.ts` file:
|
|
5
|
-
* `export const nightly = createWorkflow({ schedule }).handler(async () => { ... })`.
|
|
6
|
-
* Only the platform runs it. It has no user and no URL, and reads data like a server function does.
|
|
7
|
-
*/
|
|
8
|
-
export function createWorkflow(config) {
|
|
9
|
-
return {
|
|
10
|
-
handler: (fn) => ({ [WORKFLOW]: true, config, handler: fn }),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
export const isWorkflow = (value) => typeof value === "object" && value !== null && value[WORKFLOW] === true;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { describe, expectTypeOf, it } from "vitest";
|
|
2
|
-
import { createWorkflow } from "./workflow.js";
|
|
3
|
-
describe("createWorkflow handler args", () => {
|
|
4
|
-
it("gives a schedule no trigger", () => {
|
|
5
|
-
createWorkflow({ schedule: { scheduleType: "oneTime", fireAt: "2026-10-10T03:00:00Z", timezone: "UTC" } }).handler((args) => {
|
|
6
|
-
expectTypeOf(args).toEqualTypeOf();
|
|
7
|
-
});
|
|
8
|
-
});
|
|
9
|
-
it("types the trigger by the kind it declares", () => {
|
|
10
|
-
createWorkflow({ trigger: { every: "5m" } }).handler(({ trigger }) => {
|
|
11
|
-
expectTypeOf(trigger).toEqualTypeOf();
|
|
12
|
-
});
|
|
13
|
-
createWorkflow({ trigger: { integration: "slack", events: ["message.channels"] } }).handler(({ trigger }) => {
|
|
14
|
-
expectTypeOf(trigger).toEqualTypeOf();
|
|
15
|
-
});
|
|
16
|
-
createWorkflow({
|
|
17
|
-
trigger: { integration: "asana", subscribe: async () => ({ externalId: "w1" }), unsubscribe: async () => { } },
|
|
18
|
-
}).handler(({ trigger }) => {
|
|
19
|
-
expectTypeOf(trigger).toEqualTypeOf();
|
|
20
|
-
});
|
|
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
|
-
});
|
|
35
|
-
it("takes a schedule or a trigger, not both", () => {
|
|
36
|
-
// @ts-expect-error a workflow runs on one or the other
|
|
37
|
-
createWorkflow({ schedule: { scheduleType: "oneTime", fireAt: "x", timezone: "UTC" }, trigger: { every: "5m" } });
|
|
38
|
-
});
|
|
39
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const workflowModules: Record<string, () => Promise<Record<string, unknown>>>;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { type Workflow } from "./workflow.js";
|
|
2
|
-
export type TriggerReport = {
|
|
3
|
-
handled: string[];
|
|
4
|
-
cursor?: string;
|
|
5
|
-
};
|
|
6
|
-
export type WorkflowRunResult = {
|
|
7
|
-
ok: true;
|
|
8
|
-
output: unknown;
|
|
9
|
-
trigger?: TriggerReport;
|
|
10
|
-
} | {
|
|
11
|
-
ok: false;
|
|
12
|
-
error: {
|
|
13
|
-
code: string;
|
|
14
|
-
message: string;
|
|
15
|
-
};
|
|
16
|
-
trigger?: TriggerReport;
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* Splits the platform's state out of the input. A poll or a provider delivery
|
|
20
|
-
* with a cursor gets `seen`, `handled` and `advance`. Handled ids are reported
|
|
21
|
-
* whether or not the run throws, so an item acted on is never acted on again;
|
|
22
|
-
* the staged cursor only on success. Only ids the incoming `seen` lacked are
|
|
23
|
-
* reported: billing reads an empty list as a run that found nothing new.
|
|
24
|
-
*/
|
|
25
|
-
export declare function triggerContext(workflow: Workflow, rawInput: unknown): {
|
|
26
|
-
input: unknown;
|
|
27
|
-
trigger?: undefined;
|
|
28
|
-
report?: undefined;
|
|
29
|
-
} | {
|
|
30
|
-
input: {
|
|
31
|
-
[x: string]: unknown;
|
|
32
|
-
};
|
|
33
|
-
trigger: {
|
|
34
|
-
provider: string;
|
|
35
|
-
deliveryId?: string | undefined;
|
|
36
|
-
headers: Record<string, unknown>;
|
|
37
|
-
} | {
|
|
38
|
-
headers?: undefined;
|
|
39
|
-
} | {
|
|
40
|
-
headers: {};
|
|
41
|
-
};
|
|
42
|
-
report?: undefined;
|
|
43
|
-
} | {
|
|
44
|
-
input: {
|
|
45
|
-
[x: string]: unknown;
|
|
46
|
-
};
|
|
47
|
-
trigger: {
|
|
48
|
-
provider: string;
|
|
49
|
-
deliveryId?: string | undefined;
|
|
50
|
-
headers: Record<string, unknown>;
|
|
51
|
-
cursor: string | null;
|
|
52
|
-
seen: (id: string) => boolean;
|
|
53
|
-
handled: (id: string) => void;
|
|
54
|
-
advance: (cursor: string) => void;
|
|
55
|
-
} | {
|
|
56
|
-
headers?: undefined;
|
|
57
|
-
cursor: string | null;
|
|
58
|
-
seen: (id: string) => boolean;
|
|
59
|
-
handled: (id: string) => void;
|
|
60
|
-
advance: (cursor: string) => void;
|
|
61
|
-
} | {
|
|
62
|
-
headers: {};
|
|
63
|
-
cursor: string | null;
|
|
64
|
-
seen: (id: string) => boolean;
|
|
65
|
-
handled: (id: string) => void;
|
|
66
|
-
advance: (cursor: string) => void;
|
|
67
|
-
};
|
|
68
|
-
report: (succeeded: boolean) => TriggerReport;
|
|
69
|
-
};
|
|
70
|
-
/** Runs the handler in a workflow access scope: no user, and data calls allowed. */
|
|
71
|
-
export declare function runWorkflow(workflow: Workflow, rawInput: unknown): Promise<WorkflowRunResult>;
|
|
72
|
-
export type LoadedWorkflow = {
|
|
73
|
-
id: string;
|
|
74
|
-
file: string;
|
|
75
|
-
workflow: Workflow;
|
|
76
|
-
};
|
|
77
|
-
/** Every exported workflow in the app, by export name. */
|
|
78
|
-
export declare function loadWorkflows(modules?: Record<string, () => Promise<Record<string, unknown>>>): Promise<LoadedWorkflow[]>;
|
|
79
|
-
/** The input as the run log keeps it: a trigger's `seen` list can hold thousands of ids. */
|
|
80
|
-
export declare const loggedInput: (rawInput: unknown) => unknown;
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
// Server-only: runs a workflow the host invoked. Takes the same platform input
|
|
2
|
-
// as classic triggered endpoints (`__cron`, `__poll`, `__webhook`), so the
|
|
3
|
-
// runner's schedules, polls and provider deliveries work unchanged.
|
|
4
|
-
import { errorOf } from "./runLog.js";
|
|
5
|
-
import { runInScope } from "./scope.js";
|
|
6
|
-
import { isWorkflow } from "./workflow.js";
|
|
7
|
-
import { workflowModules } from "./workflowModules.js";
|
|
8
|
-
const asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
9
|
-
const idOf = (id) => {
|
|
10
|
-
if (typeof id !== "string" && typeof id !== "number")
|
|
11
|
-
throw new TypeError(`trigger ids must be strings, got ${typeof id}`);
|
|
12
|
-
const value = String(id);
|
|
13
|
-
if (value.length === 0 || value.length > 512)
|
|
14
|
-
throw new TypeError("trigger ids must be 1 to 512 characters");
|
|
15
|
-
return value;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Splits the platform's state out of the input. A poll or a provider delivery
|
|
19
|
-
* with a cursor gets `seen`, `handled` and `advance`. Handled ids are reported
|
|
20
|
-
* whether or not the run throws, so an item acted on is never acted on again;
|
|
21
|
-
* the staged cursor only on success. Only ids the incoming `seen` lacked are
|
|
22
|
-
* reported: billing reads an empty list as a run that found nothing new.
|
|
23
|
-
*/
|
|
24
|
-
export function triggerContext(workflow, rawInput) {
|
|
25
|
-
const raw = asRecord(rawInput);
|
|
26
|
-
const { __cron, __poll, __webhook, ...input } = raw ?? {};
|
|
27
|
-
const declared = workflow.config.trigger;
|
|
28
|
-
if (!declared)
|
|
29
|
-
return { input: raw ? input : rawInput };
|
|
30
|
-
const poll = asRecord(__poll);
|
|
31
|
-
const webhook = asRecord(__webhook);
|
|
32
|
-
const delivery = typeof webhook?.provider === "string" ? webhook : null;
|
|
33
|
-
// A run with no trigger state, such as a manual one, still gets a trigger: an empty one.
|
|
34
|
-
const info = delivery
|
|
35
|
-
? {
|
|
36
|
-
headers: asRecord(delivery.headers) ?? {},
|
|
37
|
-
...(typeof delivery.deliveryId === "string" ? { deliveryId: delivery.deliveryId } : {}),
|
|
38
|
-
provider: delivery.provider,
|
|
39
|
-
}
|
|
40
|
-
: declared.every
|
|
41
|
-
? {}
|
|
42
|
-
: { headers: {} };
|
|
43
|
-
if (!poll && declared.events)
|
|
44
|
-
return { input, trigger: info };
|
|
45
|
-
const state = poll ?? delivery ?? {};
|
|
46
|
-
const known = new Set(Array.isArray(state.seen) ? state.seen.map(String) : []);
|
|
47
|
-
const handled = [];
|
|
48
|
-
let staged;
|
|
49
|
-
const trigger = {
|
|
50
|
-
cursor: typeof state.cursor === "string" ? state.cursor : null,
|
|
51
|
-
seen: (id) => known.has(String(id)),
|
|
52
|
-
handled: (id) => {
|
|
53
|
-
const value = idOf(id);
|
|
54
|
-
if (known.has(value))
|
|
55
|
-
return;
|
|
56
|
-
known.add(value);
|
|
57
|
-
handled.push(value);
|
|
58
|
-
},
|
|
59
|
-
advance: (cursor) => {
|
|
60
|
-
if (typeof cursor !== "string" || cursor.length > 4096) {
|
|
61
|
-
throw new TypeError("trigger.advance takes a string of at most 4096 characters");
|
|
62
|
-
}
|
|
63
|
-
staged = cursor;
|
|
64
|
-
},
|
|
65
|
-
...info,
|
|
66
|
-
};
|
|
67
|
-
const report = (succeeded) => ({
|
|
68
|
-
handled: handled.slice(-2000),
|
|
69
|
-
...(succeeded && staged !== undefined ? { cursor: staged } : {}),
|
|
70
|
-
});
|
|
71
|
-
return { input, trigger, report };
|
|
72
|
-
}
|
|
73
|
-
/** Runs the handler in a workflow access scope: no user, and data calls allowed. */
|
|
74
|
-
export async function runWorkflow(workflow, rawInput) {
|
|
75
|
-
const { input, trigger, report } = triggerContext(workflow, rawInput);
|
|
76
|
-
const args = { input, ...(trigger ? { trigger } : {}) };
|
|
77
|
-
try {
|
|
78
|
-
const output = await runInScope({ kind: "workflow", user: null }, () => workflow.handler(args));
|
|
79
|
-
return { ok: true, output, ...(report ? { trigger: report(true) } : {}) };
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
return { ok: false, error: errorOf(error), ...(report ? { trigger: report(false) } : {}) };
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/** Every exported workflow in the app, by export name. */
|
|
86
|
-
export async function loadWorkflows(modules = workflowModules) {
|
|
87
|
-
const found = [];
|
|
88
|
-
for (const [file, load] of Object.entries(modules)) {
|
|
89
|
-
for (const [id, value] of Object.entries(await load()))
|
|
90
|
-
if (isWorkflow(value))
|
|
91
|
-
found.push({ id, file, workflow: value });
|
|
92
|
-
}
|
|
93
|
-
return found;
|
|
94
|
-
}
|
|
95
|
-
/** The input as the run log keeps it: a trigger's `seen` list can hold thousands of ids. */
|
|
96
|
-
export const loggedInput = (rawInput) => {
|
|
97
|
-
const raw = asRecord(rawInput);
|
|
98
|
-
if (!raw)
|
|
99
|
-
return rawInput;
|
|
100
|
-
const out = { ...raw };
|
|
101
|
-
for (const key of ["__poll", "__webhook"]) {
|
|
102
|
-
const state = asRecord(raw[key]);
|
|
103
|
-
if (state && Array.isArray(state.seen))
|
|
104
|
-
out[key] = { ...state, seen: `[${state.seen.length} ids]` };
|
|
105
|
-
}
|
|
106
|
-
return out;
|
|
107
|
-
};
|