zitejs 0.9.140 → 0.9.142

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.
Files changed (59) hide show
  1. package/dist/cjs/integrations/rules.d.ts +2 -0
  2. package/dist/cjs/integrations/rules.js +4 -0
  3. package/dist/cjs/start/describeBuilt.d.ts +3 -0
  4. package/dist/cjs/start/describeBuilt.js +106 -0
  5. package/dist/cjs/start/entries.d.ts +20 -0
  6. package/dist/cjs/start/entries.js +104 -0
  7. package/dist/cjs/start/problems.d.ts +19 -0
  8. package/dist/cjs/start/problems.js +11 -0
  9. package/dist/esm/integrations/rules.d.ts +2 -0
  10. package/dist/esm/integrations/rules.js +4 -0
  11. package/dist/esm/start/describe.d.ts +64 -0
  12. package/dist/esm/start/describe.js +176 -0
  13. package/dist/esm/start/describeBuilt.d.ts +3 -0
  14. package/dist/esm/start/describeBuilt.js +66 -0
  15. package/dist/esm/start/descriptions.d.ts +4 -0
  16. package/dist/esm/start/descriptions.js +47 -0
  17. package/dist/esm/start/entries.d.ts +20 -0
  18. package/dist/esm/start/entries.js +93 -0
  19. package/dist/esm/start/host.d.ts +3 -0
  20. package/dist/esm/start/host.js +4 -0
  21. package/dist/esm/start/index.d.ts +79 -0
  22. package/dist/esm/start/index.js +157 -0
  23. package/dist/esm/start/instance.d.ts +12 -0
  24. package/dist/esm/start/instance.js +73 -0
  25. package/dist/esm/start/problems.d.ts +19 -0
  26. package/dist/esm/start/problems.js +8 -0
  27. package/dist/esm/start/registration.d.ts +13 -0
  28. package/dist/esm/start/registration.js +21 -0
  29. package/dist/esm/start/router.d.ts +17 -0
  30. package/dist/esm/start/router.js +37 -0
  31. package/dist/esm/start/runLog.d.ts +5 -0
  32. package/dist/esm/start/runLog.js +18 -0
  33. package/dist/esm/start/scope.d.ts +15 -0
  34. package/dist/esm/start/scope.js +58 -0
  35. package/dist/esm/start/server.d.ts +51 -0
  36. package/dist/esm/start/server.js +215 -0
  37. package/dist/esm/start/types.d.ts +94 -0
  38. package/dist/esm/start/types.js +1 -0
  39. package/dist/esm/start/vite.d.ts +9 -0
  40. package/dist/esm/start/vite.js +144 -0
  41. package/dist/esm/start/worker.d.ts +11 -0
  42. package/dist/esm/start/worker.js +20 -0
  43. package/dist/esm/start/workflow.d.ts +50 -0
  44. package/dist/esm/start/workflow.js +13 -0
  45. package/dist/esm/start/workflow.test-d.d.ts +1 -0
  46. package/dist/esm/start/workflow.test-d.js +39 -0
  47. package/dist/esm/start/workflowModules.d.ts +1 -0
  48. package/dist/esm/start/workflowModules.js +3 -0
  49. package/dist/esm/start/workflowRun.d.ts +80 -0
  50. package/dist/esm/start/workflowRun.js +107 -0
  51. package/package.json +1 -1
  52. package/dist/cjs/meta/index.d.ts +0 -14
  53. package/dist/cjs/meta/index.js +0 -12
  54. package/dist/cjs/notifications/index.d.ts +0 -25
  55. package/dist/cjs/notifications/index.js +0 -12
  56. package/dist/esm/meta/index.d.ts +0 -14
  57. package/dist/esm/meta/index.js +0 -8
  58. package/dist/esm/notifications/index.d.ts +0 -25
  59. package/dist/esm/notifications/index.js +0 -8
@@ -0,0 +1,47 @@
1
+ const stringValue = (node) => {
2
+ if (node?.type === "Literal" && typeof node.value === "string")
3
+ return node.value;
4
+ const quasis = node?.quasis;
5
+ if (node?.type === "TemplateLiteral" && node.expressions.length === 0)
6
+ return quasis?.[0]?.value.cooked;
7
+ return undefined;
8
+ };
9
+ const descriptionProperty = (node) => {
10
+ for (const property of node.properties) {
11
+ const key = property.key;
12
+ const named = key?.type === "Identifier" ? key.name : key?.type === "Literal" ? key.value : undefined;
13
+ if (property.type === "Property" && named === "description")
14
+ return stringValue(property.value);
15
+ }
16
+ return undefined;
17
+ };
18
+ // Walks a builder chain, `signedInFn({ description }).validator(…).handler(…)`, to an object option.
19
+ const descriptionIn = (node) => {
20
+ if (node?.type === "CallExpression") {
21
+ for (const arg of node.arguments) {
22
+ const found = arg.type === "ObjectExpression" ? descriptionProperty(arg) : undefined;
23
+ if (found !== undefined)
24
+ return found;
25
+ }
26
+ return descriptionIn(node.callee);
27
+ }
28
+ if (node?.type === "MemberExpression")
29
+ return descriptionIn(node.object);
30
+ return undefined;
31
+ };
32
+ /** `name -> description` for each top-level const built with a description option. */
33
+ export function factoryDescriptions(program) {
34
+ const found = new Map();
35
+ for (const statement of program.body) {
36
+ const declaration = (statement.type === "ExportNamedDeclaration" ? statement.declaration : statement);
37
+ if (declaration?.type !== "VariableDeclaration")
38
+ continue;
39
+ for (const declarator of declaration.declarations) {
40
+ const id = declarator.id;
41
+ const description = descriptionIn(declarator.init);
42
+ if (id.type === "Identifier" && description)
43
+ found.set(id.name, description);
44
+ }
45
+ }
46
+ return found;
47
+ }
@@ -0,0 +1,20 @@
1
+ import type { SourceDir } from "../sourceRoots.js";
2
+ export declare const START_SERVER_ENTRY = ".zite/server.ts";
3
+ export declare const START_INSTANCE_ENTRY = ".zite/start.ts";
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;
10
+ /** The start instance: the platform's middleware, then the app's own `src/start.ts` when it has one. */
11
+ export declare const renderStartInstance: (hasAppStart: boolean) => string;
12
+ export declare const hasAppStart: (appDir: string) => boolean;
13
+ /** Writes `file` under `appDir` when its content differs. Returns whether it wrote. */
14
+ export declare const writeIfChanged: (appDir: string, file: string, content: string) => boolean;
15
+ export declare const isStartAppDir: (appPath: string) => boolean;
16
+ export declare function regenerateStartEntries(app: SourceDir): void;
17
+ /** App modules that mention createWorkflow: what the workflow registry imports. Describe decides what each one really exports. */
18
+ export declare function workflowFiles(appDir: string): string[];
19
+ /** The registry zite() builds in place of zitejs's empty one. */
20
+ export declare const renderWorkflowModules: (appDir: string, files: string[]) => string;
@@ -0,0 +1,93 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
3
+ export const START_SERVER_ENTRY = ".zite/server.ts";
4
+ export const START_INSTANCE_ENTRY = ".zite/start.ts";
5
+ const APP_START_FILES = ["src/start.ts", "src/start.tsx"];
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.
19
+ import handler, { createServerEntry } from '@tanstack/react-start/server-entry';
20
+ import { createZiteFetch } from 'zitejs/start/server';
21
+ import { getRouter } from '../src/router';
22
+
23
+ export default createServerEntry({ fetch: createZiteFetch({ handler, getRouter }) });
24
+ `;
25
+ /** The start instance: the platform's middleware, then the app's own `src/start.ts` when it has one. */
26
+ export const renderStartInstance = (hasAppStart) => hasAppStart
27
+ ? `// Generated by zitejs for TanStack Start apps. The platform owns this file.
28
+ import { createZiteStart } from 'zitejs/start/instance';
29
+ import { startInstance as app } from '../src/start';
30
+
31
+ export const startInstance = createZiteStart(app);
32
+ `
33
+ : `// Generated by zitejs for TanStack Start apps. The platform owns this file.
34
+ import { createZiteStart } from 'zitejs/start/instance';
35
+
36
+ export const startInstance = createZiteStart();
37
+ `;
38
+ export const hasAppStart = (appDir) => APP_START_FILES.some((file) => existsSync(join(appDir, file)));
39
+ /** Writes `file` under `appDir` when its content differs. Returns whether it wrote. */
40
+ export const writeIfChanged = (appDir, file, content) => {
41
+ const target = join(appDir, file);
42
+ if (existsSync(target) && readFileSync(target, "utf-8") === content)
43
+ return false;
44
+ mkdirSync(dirname(target), { recursive: true });
45
+ writeFileSync(target, content);
46
+ return true;
47
+ };
48
+ export const isStartAppDir = (appPath) => {
49
+ try {
50
+ return JSON.parse(readFileSync(join(appPath, "zite.config.json"), "utf-8"))?.runtime === "start";
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ };
56
+ const isStartApp = (app) => isStartAppDir(app.path);
57
+ export function regenerateStartEntries(app) {
58
+ if (!isStartApp(app))
59
+ return;
60
+ const entries = [
61
+ [START_SERVER_ENTRY, renderStartServerEntry()],
62
+ [START_INSTANCE_ENTRY, renderStartInstance(hasAppStart(app.path))],
63
+ ];
64
+ for (const [file, content] of entries) {
65
+ if (writeIfChanged(app.path, file, content))
66
+ console.log(`Wrote ${join(app.path, file)}`);
67
+ }
68
+ }
69
+ const SOURCE_FILE = /\.[mc]?[jt]sx?$/;
70
+ const NOT_APP_CODE = /(\.(test|spec)\.[^.]+|\.d\.ts|routeTree\.gen\.ts)$/;
71
+ /** App modules that mention createWorkflow: what the workflow registry imports. Describe decides what each one really exports. */
72
+ export function workflowFiles(appDir) {
73
+ const found = [];
74
+ const walk = (dir) => {
75
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
76
+ const full = join(dir, entry.name);
77
+ if (entry.isDirectory()) {
78
+ if (entry.name !== "node_modules")
79
+ walk(full);
80
+ }
81
+ else if (SOURCE_FILE.test(entry.name) && !NOT_APP_CODE.test(entry.name) && readFileSync(full, "utf-8").includes("createWorkflow")) {
82
+ found.push(relative(appDir, full).split("\\").join("/"));
83
+ }
84
+ }
85
+ };
86
+ if (existsSync(join(appDir, "src")))
87
+ walk(join(appDir, "src"));
88
+ return found.sort();
89
+ }
90
+ /** The registry zite() builds in place of zitejs's empty one. */
91
+ export const renderWorkflowModules = (appDir, files) => `export const workflowModules = {\n${files
92
+ .map((file) => ` ${JSON.stringify(file)}: () => import(${JSON.stringify(join(appDir, file))}),`)
93
+ .join("\n")}\n};\n`;
@@ -0,0 +1,3 @@
1
+ import type { ZiteRequestContext } from "./types.js";
2
+ export declare const HOST_STORE_KEY: unique symbol;
3
+ export declare const currentHost: () => ZiteRequestContext | undefined;
@@ -0,0 +1,4 @@
1
+ // Isomorphic, so it can't import the server-only store: it reads it through the
2
+ // global key ./scope.ts registers it under. In the browser there is none.
3
+ export const HOST_STORE_KEY = Symbol.for("zitejs:host-context");
4
+ export const currentHost = () => globalThis[HOST_STORE_KEY]?.getStore();
@@ -0,0 +1,79 @@
1
+ import type { User } from "./types.js";
2
+ export type { User, ZiteRequestContext } from "./types.js";
3
+ export { createZiteRouter, type ZiteRouterContext } from "./router.js";
4
+ export { createWorkflow, type StartProviderTrigger, type StartTrigger, type Workflow, type WorkflowArgs, type WorkflowConfig, } from "./workflow.js";
5
+ export type { ZiteSchedule } from "../schedules/index.js";
6
+ export type { ZiteTrigger, ZiteTriggerSubscribe, ZiteTriggerSubscribeInput, ZiteTriggerSubscribeResult, ZiteTriggerUnsubscribe, ZiteTriggerUnsubscribeInput, } from "../backend/index.js";
7
+ declare module "@tanstack/start-client-core" {
8
+ interface ServerFnOptions {
9
+ /** What this function does, in a sentence. Shown in the editor. */
10
+ description?: string;
11
+ }
12
+ }
13
+ declare const STATUS: {
14
+ readonly BAD_REQUEST: 400;
15
+ readonly INVALID_INPUT: 400;
16
+ readonly UNAUTHORIZED: 401;
17
+ readonly FORBIDDEN: 403;
18
+ readonly NOT_FOUND: 404;
19
+ readonly CONFLICT: 409;
20
+ readonly RATE_LIMITED: 429;
21
+ readonly INTERNAL_ERROR: 500;
22
+ };
23
+ export type ZiteErrorCode = keyof typeof STATUS;
24
+ /** @internal The HTTP status each code answers with. */
25
+ export declare const ZITE_ERROR_STATUS: Readonly<Record<ZiteErrorCode, number>>;
26
+ /** Fail with a status: `throw new ZiteError('FORBIDDEN', 'Only the owner can edit this')`. */
27
+ export declare class ZiteError extends Error {
28
+ readonly code: ZiteErrorCode;
29
+ constructor(code: ZiteErrorCode, message?: string);
30
+ }
31
+ export declare const hasRole: (user: User, role: string) => boolean;
32
+ /** The message a server function fails with when it needs a session; the platform's client middleware sends the user to sign in. */
33
+ export declare const SIGN_IN_REQUIRED = "ZITE_SIGN_IN_REQUIRED";
34
+ export declare const isSignInRequired: (error: unknown) => boolean;
35
+ /**
36
+ * The platform's sign-in page, returning to `redirectTo` (default: the current
37
+ * page). For guards: `throw redirect({ href: loginHref(), reloadDocument: true })`.
38
+ */
39
+ export declare function loginHref(redirectTo?: string): string;
40
+ /** What a zitejs middleware means, for describe. Keyed by identity, so composing or re-exporting keeps it. */
41
+ export type ZiteMiddlewareKind = {
42
+ kind: "signedIn" | "anyone" | "factory";
43
+ } | {
44
+ kind: "webhook";
45
+ paused?: true;
46
+ } | {
47
+ kind: "role";
48
+ role: string;
49
+ };
50
+ /** @internal */
51
+ export declare const ziteMiddlewareKind: (middleware: unknown) => ZiteMiddlewareKind | undefined;
52
+ /** Only signed-in users. Handlers get `context.user: User`. */
53
+ export declare const signedIn: import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, {
54
+ user: User;
55
+ }>;
56
+ /** Anyone, signed in or not. Handlers get `context.user: User | null`. */
57
+ export declare const anyone: import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, {
58
+ user: User | null;
59
+ }>;
60
+ /**
61
+ * Webhooks delivered by the platform, which already checked the URL token.
62
+ * `webhook.paused` keeps the URL but runs nothing: the platform answers the
63
+ * provider 200 and drops the delivery until it's switched back.
64
+ */
65
+ export declare const webhook: import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined> & {
66
+ paused: import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
67
+ };
68
+ /** Signed-in users with this role. */
69
+ export declare const role: (name: string) => import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, readonly [import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, {
70
+ user: User;
71
+ }>], undefined>;
72
+ declare const compiled: import("@tanstack/react-start").FunctionMiddlewareAfterClient<{}, unknown, undefined, undefined, undefined>;
73
+ declare const builder: import("@tanstack/react-start").ServerFnBuilder<import("@tanstack/react-start").Register, import("@tanstack/react-start").Method, true>;
74
+ type SignedInFn = ReturnType<typeof builder.middleware<readonly [typeof compiled, typeof signedIn]>>;
75
+ type PublicFn = ReturnType<typeof builder.middleware<readonly [typeof compiled, typeof anyone]>>;
76
+ /** A server function only signed-in users can call. */
77
+ export declare const signedInFn: SignedInFn;
78
+ /** A server function anyone can call (external apps only). */
79
+ export declare const publicFn: PublicFn;
@@ -0,0 +1,157 @@
1
+ import { createMiddleware, createServerFn } from "@tanstack/react-start";
2
+ import { setResponseStatus } from "@tanstack/react-start/server";
3
+ import { USAGE_TOKEN_QUERY_PARAM } from "../auth/constants.js";
4
+ import { currentHost } from "./host.js";
5
+ import { getHost, inScope, runInScope } from "./scope.js";
6
+ export { createZiteRouter } from "./router.js";
7
+ export { createWorkflow, } from "./workflow.js";
8
+ const STATUS = {
9
+ BAD_REQUEST: 400,
10
+ INVALID_INPUT: 400,
11
+ UNAUTHORIZED: 401,
12
+ FORBIDDEN: 403,
13
+ NOT_FOUND: 404,
14
+ CONFLICT: 409,
15
+ RATE_LIMITED: 429,
16
+ INTERNAL_ERROR: 500,
17
+ };
18
+ /** @internal The HTTP status each code answers with. */
19
+ export const ZITE_ERROR_STATUS = STATUS;
20
+ /** Fail with a status: `throw new ZiteError('FORBIDDEN', 'Only the owner can edit this')`. */
21
+ export class ZiteError extends Error {
22
+ code;
23
+ constructor(code, message) {
24
+ super(message ?? code);
25
+ this.name = "ZiteError";
26
+ this.code = code;
27
+ }
28
+ }
29
+ export const hasRole = (user, role) => user.roles.includes(role);
30
+ /** The message a server function fails with when it needs a session; the platform's client middleware sends the user to sign in. */
31
+ export const SIGN_IN_REQUIRED = "ZITE_SIGN_IN_REQUIRED";
32
+ export const isSignInRequired = (error) => error instanceof Error && error.message === SIGN_IN_REQUIRED;
33
+ /**
34
+ * The platform's sign-in page, returning to `redirectTo` (default: the current
35
+ * page). For guards: `throw redirect({ href: loginHref(), reloadDocument: true })`.
36
+ */
37
+ export function loginHref(redirectTo) {
38
+ const current = typeof window !== "undefined" ? window.location.href : currentHost()?.url;
39
+ // Absolute on purpose: better-auth drops a relative callback with an encoded
40
+ // space, fragment or non-ASCII path, and never checks an absolute one.
41
+ const target = current ? new URL(redirectTo ?? current, current) : undefined;
42
+ const params = new URLSearchParams();
43
+ if (target && !(target.pathname === "/" && !target.search && !target.hash))
44
+ params.set("redirectUrl", target.toString());
45
+ else if (!target && redirectTo)
46
+ params.set("redirectUrl", redirectTo);
47
+ // The editor preview's sign-in page needs the preview's token.
48
+ const usageToken = current ? new URL(current).searchParams.get(USAGE_TOKEN_QUERY_PARAM) : null;
49
+ if (usageToken)
50
+ params.set(USAGE_TOKEN_QUERY_PARAM, usageToken);
51
+ const query = params.toString();
52
+ return query ? `/auth/login?${query}` : "/auth/login";
53
+ }
54
+ const kinds = new WeakMap();
55
+ const tag = (middleware, kind) => {
56
+ kinds.set(middleware, kind);
57
+ return middleware;
58
+ };
59
+ /** @internal */
60
+ export const ziteMiddlewareKind = (middleware) => typeof middleware === "object" && middleware !== null ? kinds.get(middleware) : undefined;
61
+ // Identity comes from server-only storage, never TanStack's context, which
62
+ // merges whatever context the browser sends.
63
+ const host = () => {
64
+ const current = getHost();
65
+ if (!current)
66
+ throw new ZiteError("UNAUTHORIZED", "Missing platform context");
67
+ return current;
68
+ };
69
+ // TanStack fails a validator with a plain Error whose message is the issues as JSON.
70
+ const validationIssues = (error) => {
71
+ if (!(error instanceof Error) || error.constructor !== Error)
72
+ return undefined;
73
+ try {
74
+ const issues = JSON.parse(error.message);
75
+ return Array.isArray(issues) && issues.length > 0 && issues.every((issue) => typeof issue?.message === "string")
76
+ ? issues
77
+ : undefined;
78
+ }
79
+ catch {
80
+ return undefined;
81
+ }
82
+ };
83
+ const invalidInput = (issues) => {
84
+ if (getHost()?.mode === "live")
85
+ return new ZiteError("INVALID_INPUT", "Invalid input");
86
+ const describe = ({ message, path = [] }) => {
87
+ const at = path.map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
88
+ return at ? `${at}: ${message}` : message;
89
+ };
90
+ return new ZiteError("INVALID_INPUT", issues.map(describe).join("; "));
91
+ };
92
+ // Server functions report a status through TanStack's error envelope; server
93
+ // routes need a Response. A nested call's failure is the caller's to handle.
94
+ const withStatus = async (isServerFn, fn) => {
95
+ const nested = inScope();
96
+ try {
97
+ return await fn();
98
+ }
99
+ catch (caught) {
100
+ const issues = validationIssues(caught);
101
+ const error = issues ? invalidInput(issues) : caught;
102
+ if (!(error instanceof ZiteError))
103
+ throw error;
104
+ if (!isServerFn)
105
+ return new Response(error.message, { status: STATUS[error.code] });
106
+ if (!nested)
107
+ setResponseStatus(STATUS[error.code]);
108
+ throw error;
109
+ }
110
+ };
111
+ /** Only signed-in users. Handlers get `context.user: User`. */
112
+ export const signedIn = tag(createMiddleware().server(({ next, serverFnMeta }) => withStatus(!!serverFnMeta, async () => {
113
+ const { user } = host();
114
+ if (!user)
115
+ throw new ZiteError("UNAUTHORIZED", SIGN_IN_REQUIRED);
116
+ return runInScope({ kind: "signedIn", user }, () => next({ context: { user } }));
117
+ })), { kind: "signedIn" });
118
+ /** Anyone, signed in or not. Handlers get `context.user: User | null`. */
119
+ export const anyone = tag(createMiddleware().server(({ next, serverFnMeta }) => withStatus(!!serverFnMeta, async () => {
120
+ const { user } = host();
121
+ return runInScope({ kind: "anyone", user }, () => next({ context: { user } }));
122
+ })), { kind: "anyone" });
123
+ const webhookMiddleware = (paused) => tag(createMiddleware().server(({ next }) => {
124
+ if (host().invoke?.kind !== "webhook")
125
+ return new Response("Not found", { status: 404 });
126
+ return runInScope({ kind: "webhook", user: null }, () => next());
127
+ }), paused ? { kind: "webhook", paused: true } : { kind: "webhook" });
128
+ /**
129
+ * Webhooks delivered by the platform, which already checked the URL token.
130
+ * `webhook.paused` keeps the URL but runs nothing: the platform answers the
131
+ * provider 200 and drops the delivery until it's switched back.
132
+ */
133
+ export const webhook = Object.assign(webhookMiddleware(false), { paused: webhookMiddleware(true) });
134
+ /** Signed-in users with this role. */
135
+ export const role = (name) => tag(createMiddleware()
136
+ .middleware([signedIn])
137
+ .server(({ next, context, serverFnMeta }) => withStatus(!!serverFnMeta, async () => {
138
+ if (!hasRole(context.user, name))
139
+ throw new ZiteError("FORBIDDEN", `Requires the ${name} role`);
140
+ return next();
141
+ })), { kind: "role", role: name });
142
+ // TanStack's compiler tags every server function it compiles with an id. A
143
+ // builder it didn't compile (an object property, one returned from a helper)
144
+ // has none and would run its handler wherever it was called, so it fails here.
145
+ const compiled = tag(createMiddleware({ type: "function" }).client(({ next, serverFnMeta }) => {
146
+ if (!serverFnMeta?.id) {
147
+ throw new Error("This server function was not compiled by TanStack Start. Assign signedInFn(...)/publicFn(...)" +
148
+ ".handler(...) directly to its own top-level const, not an object property or a helper's return value.");
149
+ }
150
+ return next();
151
+ }), { kind: "factory" });
152
+ // TanStack doesn't export its builder-after-middleware type, so name it through the public builder.
153
+ const builder = createServerFn();
154
+ /** A server function only signed-in users can call. */
155
+ export const signedInFn = createServerFn().middleware([compiled, signedIn]);
156
+ /** A server function anyone can call (external apps only). */
157
+ export const publicFn = createServerFn().middleware([compiled, anyone]);
@@ -0,0 +1,12 @@
1
+ import { type AnySerializationAdapter } from "@tanstack/react-router";
2
+ import type { SSROption } from "@tanstack/router-core";
3
+ import type { AnyFunctionMiddleware, AnyRequestMiddleware, StartInstance } from "@tanstack/start-client-core";
4
+ import { ZiteError } from "./index.js";
5
+ /**
6
+ * The start instance every Start app builds with: the platform's middleware and
7
+ * adapters first, then whatever the app's own `src/start.ts` sets.
8
+ */
9
+ export declare function createZiteStart<const TAdapters extends ReadonlyArray<AnySerializationAdapter> = [], TDefaultSsr extends SSROption = SSROption, const TRequest extends ReadonlyArray<AnyRequestMiddleware> = [], const TFunction extends ReadonlyArray<AnyFunctionMiddleware> = []>(app?: StartInstance<TAdapters, TDefaultSsr, TRequest, TFunction>): StartInstance<readonly [import("@tanstack/react-router").SerializationAdapter<ZiteError, {
10
+ code: "BAD_REQUEST" | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "RATE_LIMITED" | "INTERNAL_ERROR" | "INVALID_INPUT";
11
+ message: string;
12
+ }, never>, ...TAdapters], TDefaultSsr, readonly [import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>, ...TRequest], readonly [import("@tanstack/react-start").FunctionMiddlewareAfterClient<{}, unknown, undefined, undefined, undefined>, import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, undefined, undefined, undefined>, ...TFunction]>;
@@ -0,0 +1,73 @@
1
+ // For the generated `.zite/start.ts` only. Apps put their own options in
2
+ // `src/start.ts`, which the generated instance composes after the platform's.
3
+ import { createCsrfMiddleware, createMiddleware, createStart } from "@tanstack/react-start";
4
+ import { createSerializationAdapter, redirect } from "@tanstack/react-router";
5
+ import { isSignInRequired, loginHref, ZiteError } from "./index.js";
6
+ import { errorOf, preview } from "./runLog.js";
7
+ import { getHost, inScope } from "./scope.js";
8
+ // TanStack drops its default CSRF check once an app has a start instance.
9
+ const csrf = createCsrfMiddleware({ filter: (ctx) => ctx.handlerType === "serverFn" });
10
+ // The host answers a signed-out call with a plain 401, the app with a ZiteError;
11
+ // either way the browser goes to sign in rather than showing an error.
12
+ const signInOn401 = createMiddleware({ type: "function" }).client(async ({ next }) => {
13
+ try {
14
+ return await next();
15
+ }
16
+ catch (error) {
17
+ if (!isSignInRequired(error))
18
+ throw error;
19
+ const href = loginHref();
20
+ if (typeof window !== "undefined")
21
+ window.location.assign(href);
22
+ throw redirect({ href, reloadDocument: true });
23
+ }
24
+ });
25
+ // Records each call for the run log. The host's own record of the request is the
26
+ // floor; this adds what only the app sees: decoded input, output and errors.
27
+ const runCapture = createMiddleware({ type: "function" }).server(async ({ next, data, serverFnMeta }) => {
28
+ const runs = getHost()?.runs;
29
+ const startedAt = Date.now();
30
+ const record = (outcome) => runs?.push({
31
+ fn: serverFnMeta.id,
32
+ name: serverFnMeta.name,
33
+ nested: inScope(),
34
+ startedAt,
35
+ durationMs: Date.now() - startedAt,
36
+ input: preview(data),
37
+ ...("error" in outcome ? { error: errorOf(outcome.error) } : { output: preview(outcome.output) }),
38
+ });
39
+ try {
40
+ const out = await next();
41
+ const failed = out.error;
42
+ record(failed === undefined ? { output: out.result } : { error: failed });
43
+ return out;
44
+ }
45
+ catch (error) {
46
+ record({ error });
47
+ throw error;
48
+ }
49
+ });
50
+ /** Carries a ZiteError to the browser as itself, so `instanceof ZiteError` and `.code` work there. */
51
+ const ziteErrorAdapter = createSerializationAdapter({
52
+ key: "zite-error",
53
+ test: (value) => value instanceof ZiteError,
54
+ toSerializable: (error) => ({ code: error.code, message: error.message }),
55
+ fromSerializable: ({ code, message }) => new ZiteError(code, message),
56
+ });
57
+ /**
58
+ * The start instance every Start app builds with: the platform's middleware and
59
+ * adapters first, then whatever the app's own `src/start.ts` sets.
60
+ */
61
+ export function createZiteStart(app) {
62
+ return createStart(async () => {
63
+ const own = app ? await app.getOptions() : undefined;
64
+ // Spreading the app's optional arrays loses their tuple types, which the
65
+ // route tree registers for typed context and serialization.
66
+ return {
67
+ ...own,
68
+ requestMiddleware: [csrf, ...(own?.requestMiddleware ?? [])],
69
+ functionMiddleware: [signInOn401, runCapture, ...(own?.functionMiddleware ?? [])],
70
+ serializationAdapters: [ziteErrorAdapter, ...(own?.serializationAdapters ?? [])],
71
+ };
72
+ });
73
+ }
@@ -0,0 +1,19 @@
1
+ type WithProblems = {
2
+ problems: string[];
3
+ };
4
+ type Described = {
5
+ functions: Array<WithProblems & {
6
+ id: string;
7
+ file?: string;
8
+ }>;
9
+ routes: Array<WithProblems & {
10
+ path: string;
11
+ }>;
12
+ workflows?: Array<WithProblems & {
13
+ id: string;
14
+ file: string;
15
+ }>;
16
+ };
17
+ /** Every problem in a description, one line each, named by its rule. Any of them fails the build. */
18
+ export declare function descriptionProblems(description: Described): string[];
19
+ export {};
@@ -0,0 +1,8 @@
1
+ /** Every problem in a description, one line each, named by its rule. Any of them fails the build. */
2
+ export function descriptionProblems(description) {
3
+ return [
4
+ ...description.functions.flatMap((f) => f.problems.map((p) => `function ${f.id}${f.file ? ` (${f.file})` : ""}: ${p}`)),
5
+ ...description.routes.flatMap((r) => r.problems.map((p) => `route ${r.path}: ${p}`)),
6
+ ...(description.workflows ?? []).flatMap((w) => w.problems.map((p) => `workflow ${w.id} (${w.file}): ${p}`)),
7
+ ];
8
+ }
@@ -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
+ }
@@ -0,0 +1,17 @@
1
+ import { type AnyRoute, type RouterConstructorOptions, type RouterHistory } from "@tanstack/react-router";
2
+ import type { User } from "./types.js";
3
+ /** The router context every Zite app's root route declares: `createRootRouteWithContext<ZiteRouterContext>()`. */
4
+ export type ZiteRouterContext = {
5
+ user: User | null;
6
+ };
7
+ type Dehydrated = {
8
+ user: User | null;
9
+ };
10
+ type ZiteRouterOptions<TRouteTree extends AnyRoute> = Omit<RouterConstructorOptions<TRouteTree, "never", false, RouterHistory, Dehydrated>, "context" | "dehydrate" | "hydrate">;
11
+ /**
12
+ * The router for `src/router.tsx`. The signed-in user is in `context.user`: on
13
+ * the server from the platform, in the browser from the page it rendered, so
14
+ * no request asks for it again.
15
+ */
16
+ export declare function createZiteRouter<TRouteTree extends AnyRoute>(options: ZiteRouterOptions<TRouteTree>): import("@tanstack/router-core").RouterCore<TRouteTree, "never", false, RouterHistory, Dehydrated>;
17
+ export {};
@@ -0,0 +1,37 @@
1
+ import { createElement as h } from "react";
2
+ import { createRouter, Link, useRouter, } from "@tanstack/react-router";
3
+ import { currentHost } from "./host.js";
4
+ const box = { maxWidth: 480, margin: "15vh auto", padding: 24, fontFamily: "system-ui, sans-serif", textAlign: "center" };
5
+ function ZiteErrorComponent({ error }) {
6
+ const router = useRouter();
7
+ const message = error instanceof Error && error.name === "ZiteError" ? error.message : "Something went wrong.";
8
+ return h("div", { role: "alert", style: box }, h("p", { style: { fontWeight: 600 } }, message), h("button", { type: "button", onClick: () => router.invalidate() }, "Try again"));
9
+ }
10
+ function ZiteNotFound() {
11
+ return h("div", { style: box }, h("p", { style: { fontWeight: 600 } }, "Page not found"), h(Link, { to: "/" }, "Go home"));
12
+ }
13
+ /**
14
+ * The router for `src/router.tsx`. The signed-in user is in `context.user`: on
15
+ * the server from the platform, in the browser from the page it rendered, so
16
+ * no request asks for it again.
17
+ */
18
+ export function createZiteRouter(options) {
19
+ const context = { user: currentHost()?.user ?? null };
20
+ const router = createRouter({
21
+ defaultPreload: "intent",
22
+ defaultPreloadStaleTime: 0,
23
+ scrollRestoration: true,
24
+ defaultErrorComponent: ZiteErrorComponent,
25
+ defaultNotFoundComponent: ZiteNotFound,
26
+ ...options,
27
+ context,
28
+ dehydrate: () => ({ user: context.user }),
29
+ hydrate: (dehydrated) => {
30
+ context.user = dehydrated.user;
31
+ },
32
+ });
33
+ const host = currentHost();
34
+ if (host)
35
+ host.router = router;
36
+ return router;
37
+ }
@@ -0,0 +1,5 @@
1
+ export declare const preview: (value: unknown) => string | undefined;
2
+ export declare const errorOf: (error: unknown) => {
3
+ code: string;
4
+ message: string;
5
+ };
@@ -0,0 +1,18 @@
1
+ import { ZiteError } from "./index.js";
2
+ const PREVIEW_LIMIT = 8_000;
3
+ export const preview = (value) => {
4
+ if (value === undefined)
5
+ return undefined;
6
+ if (typeof value === "object" && value !== null && Symbol.asyncIterator in value)
7
+ return "[stream]";
8
+ try {
9
+ const json = JSON.stringify(value);
10
+ return json && json.length > PREVIEW_LIMIT ? `${json.slice(0, PREVIEW_LIMIT)}…` : json;
11
+ }
12
+ catch {
13
+ return "[unserializable]";
14
+ }
15
+ };
16
+ export const errorOf = (error) => error instanceof ZiteError
17
+ ? { code: error.code, message: error.message }
18
+ : { code: "INTERNAL_ERROR", message: error instanceof Error ? error.message : String(error) };