@spacefast/common 0.0.12 → 0.0.13

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 (113) hide show
  1. package/dist/contracts/access.d.ts +386 -1201
  2. package/dist/contracts/access.js +240 -1057
  3. package/dist/contracts/activity.d.ts +1 -1
  4. package/dist/contracts/activity.js +6 -14
  5. package/dist/contracts/api-keys.d.ts +16 -0
  6. package/dist/contracts/api-keys.js +2 -0
  7. package/dist/contracts/archives.d.ts +1 -2
  8. package/dist/contracts/archives.js +4 -6
  9. package/dist/contracts/comments.d.ts +58 -85
  10. package/dist/contracts/comments.js +86 -67
  11. package/dist/contracts/countries.js +2 -7
  12. package/dist/contracts/data-source-routes.d.ts +20 -0
  13. package/dist/contracts/data-source-routes.js +15 -0
  14. package/dist/contracts/device-auth.d.ts +7 -3
  15. package/dist/contracts/enums.d.ts +2 -4
  16. package/dist/contracts/enums.js +3 -8
  17. package/dist/contracts/error-code-meta.d.ts +17 -20
  18. package/dist/contracts/error-code-meta.js +8 -5
  19. package/dist/contracts/error-codes.d.ts +1 -1
  20. package/dist/contracts/error-codes.js +4 -5
  21. package/dist/contracts/events.d.ts +13 -13
  22. package/dist/contracts/execution.d.ts +212 -0
  23. package/dist/contracts/execution.js +288 -0
  24. package/dist/contracts/feature-lifecycle.d.ts +21 -167
  25. package/dist/contracts/feature-lifecycle.js +27 -173
  26. package/dist/contracts/features.d.ts +13 -115
  27. package/dist/contracts/features.js +1 -31
  28. package/dist/contracts/functions.d.ts +729 -0
  29. package/dist/contracts/functions.js +485 -0
  30. package/dist/contracts/git.d.ts +1 -1
  31. package/dist/contracts/grants.d.ts +1158 -0
  32. package/dist/contracts/grants.js +696 -0
  33. package/dist/contracts/ids.d.ts +5 -3
  34. package/dist/contracts/ids.js +5 -3
  35. package/dist/contracts/mcp.d.ts +6 -6
  36. package/dist/contracts/oauth-resources.d.ts +5 -5
  37. package/dist/contracts/oauth-resources.js +1 -0
  38. package/dist/contracts/operations.d.ts +2 -2
  39. package/dist/contracts/pages.d.ts +6 -14
  40. package/dist/contracts/pages.js +6 -23
  41. package/dist/contracts/plan-policy.d.ts +1 -8
  42. package/dist/contracts/plan-policy.js +1 -4
  43. package/dist/contracts/platform.d.ts +78 -30
  44. package/dist/contracts/platform.js +59 -12
  45. package/dist/contracts/push-new.d.ts +26 -2
  46. package/dist/contracts/push-new.js +8 -0
  47. package/dist/contracts/quotas.d.ts +0 -1
  48. package/dist/contracts/quotas.js +0 -5
  49. package/dist/contracts/repository-connections.d.ts +1 -1
  50. package/dist/contracts/resources.d.ts +10 -66
  51. package/dist/contracts/resources.js +4 -10
  52. package/dist/contracts/runtime-api.d.ts +558 -265
  53. package/dist/contracts/runtime-api.js +178 -91
  54. package/dist/contracts/runtime-app.d.ts +138 -0
  55. package/dist/contracts/runtime-app.js +21 -0
  56. package/dist/contracts/sf-config-v1.d.ts +5 -0
  57. package/dist/contracts/sites.d.ts +5 -0
  58. package/dist/contracts/sites.js +12 -1
  59. package/dist/contracts/space-config.d.ts +46 -116
  60. package/dist/contracts/space-config.js +320 -24
  61. package/dist/contracts/spaces.d.ts +253 -158
  62. package/dist/contracts/spaces.js +68 -54
  63. package/dist/contracts/superadmin-emails.d.ts +2 -2
  64. package/dist/contracts/superadmin-spaces.d.ts +15 -130
  65. package/dist/contracts/superadmin-spaces.js +7 -0
  66. package/dist/contracts/tags.d.ts +23 -23
  67. package/dist/contracts/teams.d.ts +9 -9
  68. package/dist/contracts/transfers.d.ts +0 -1
  69. package/dist/contracts/zero.d.ts +195 -273
  70. package/dist/contracts/zero.js +78 -206
  71. package/dist/dashboard-paths/index.d.ts +2 -2
  72. package/dist/dashboard-paths/index.js +1 -0
  73. package/dist/docs/agent-prose.js +10 -8
  74. package/dist/docs/agent-setup.d.ts +2 -2
  75. package/dist/docs/agent-setup.js +11 -9
  76. package/dist/docs/agent-solutions.js +1 -1
  77. package/dist/docs/catalog.d.ts +28 -28
  78. package/dist/docs/catalog.js +29 -29
  79. package/dist/docs/error-docs.js +23 -27
  80. package/dist/utils/asset-fingerprint.d.ts +11 -0
  81. package/dist/utils/asset-fingerprint.js +46 -0
  82. package/dist/utils/auth-redirect.d.ts +1 -3
  83. package/dist/utils/auth-redirect.js +1 -21
  84. package/dist/utils/build-settings.d.ts +2 -0
  85. package/dist/utils/build-settings.js +61 -9
  86. package/dist/utils/credential-policy.d.ts +1 -1
  87. package/dist/utils/credential-policy.js +4 -0
  88. package/dist/utils/egress-policy.fixtures.json +1 -1
  89. package/dist/utils/grants.d.ts +53 -0
  90. package/dist/utils/grants.js +160 -0
  91. package/dist/utils/local-space-state.js +1 -0
  92. package/dist/utils/pages.d.ts +11 -5
  93. package/dist/utils/pages.js +19 -32
  94. package/dist/utils/publish-policy.d.ts +34 -4
  95. package/dist/utils/publish-policy.js +132 -61
  96. package/dist/utils/query-keys.d.ts +1 -6
  97. package/dist/utils/query-keys.js +1 -6
  98. package/dist/utils/space-config.d.ts +5 -1
  99. package/dist/utils/space-config.js +35 -0
  100. package/dist/utils/static-runtime-policy.d.ts +5 -0
  101. package/dist/utils/static-runtime-policy.js +54 -10
  102. package/dist/utils/upload-session.d.ts +3 -0
  103. package/dist/utils/upload-session.js +31 -0
  104. package/package.json +2 -1
  105. package/dist/utils/access-grant.d.ts +0 -14
  106. package/dist/utils/access-grant.js +0 -88
  107. package/dist/utils/access-match.d.ts +0 -46
  108. package/dist/utils/access-match.fixtures.json +0 -513
  109. package/dist/utils/access-match.js +0 -312
  110. package/dist/utils/access-verdict.fixtures.json +0 -421
  111. package/dist/utils/sf-config-v1.d.ts +0 -2
  112. package/dist/utils/sf-config-v1.fixtures.json +0 -9
  113. package/dist/utils/sf-config-v1.js +0 -551
@@ -0,0 +1,212 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The platform execution layer: everything true of running tenant code on
4
+ * Spacefast regardless of which product is running it.
5
+ *
6
+ * Two products sit on top of this file, and they are genuinely different
7
+ * things rather than two configurations of one thing:
8
+ *
9
+ * - **Zero** (`./zero.js`) runs capsules — schema, queries, mutations and
10
+ * endpoints that share one parent-owned transaction — in the native runner
11
+ * on a dedicated wp.cloud site, next to that site's MySQL.
12
+ * - **Functions** (`./functions.js`) runs workers on Cloudflare Workers for
13
+ * Platforms, carrying npm and framework output that we could never sandbox
14
+ * as well ourselves.
15
+ *
16
+ * The only surface both can host is an endpoint that touches neither the
17
+ * database nor the caller's identity — see `endpointIsPortable`. That is the
18
+ * portable seam, and it is deliberately narrow: a capsule handler's semantics
19
+ * depend on the transaction wrapped around it, which does not exist on an
20
+ * isolate a continent away from the database.
21
+ */
22
+ /**
23
+ * What a live version runs. `static` is bytes on disk with no execution at
24
+ * all, and remains the overwhelming majority of traffic; the other two each
25
+ * name a product.
26
+ */
27
+ export declare const runtimeKindSchema: z.ZodEnum<{
28
+ zero: "zero";
29
+ functions: "functions";
30
+ static: "static";
31
+ }>;
32
+ export type RuntimeKind = z.infer<typeof runtimeKindSchema>;
33
+ export declare const EXECUTION_ENV_MAX_KEYS = 64;
34
+ export declare const EXECUTION_ENV_NAME_MAX_BYTES = 128;
35
+ export declare const EXECUTION_ENV_VALUE_MAX_BYTES: number;
36
+ export declare const EXECUTION_ENV_TOTAL_VALUE_MAX_BYTES: number;
37
+ /**
38
+ * Names the platform reserves for itself in the tenant environment. Tenant
39
+ * code cannot set these, so it cannot spoof the values the broker trusts.
40
+ */
41
+ export declare const EXECUTION_ENV_RESERVED_PREFIXES: readonly ["SPACEFAST_", "ZERO_"];
42
+ /** Paths the platform owns on every space, whatever the product. */
43
+ export declare const PLATFORM_ROUTE_PREFIX = "/__spacefast";
44
+ /**
45
+ * First path segments the platform claims. Matched as segment prefixes rather
46
+ * than whole segments because the runtime already reserves the whole family —
47
+ * `/__stattic_probe` is a live serving path, and `upload-policy.php` refuses
48
+ * any first segment starting with either of these. Encoding it the same way
49
+ * here keeps a capsule or worker from declaring a route the origin would
50
+ * intercept before it ever reached them.
51
+ */
52
+ export declare const PLATFORM_RESERVED_ROUTE_SEGMENT_PREFIXES: readonly ["__spacefast", "__stattic", "__span"];
53
+ export declare const executionSha256Schema: z.ZodString;
54
+ export declare const executionBase64Schema: z.ZodString;
55
+ export declare const executionIdentifierSchema: z.ZodString;
56
+ export declare const executionEnvNameSchema: z.ZodString;
57
+ /**
58
+ * Builds the route-path schema for a product. Structural rules — normalization,
59
+ * traversal, wildcards, the platform's own prefixes — are identical everywhere
60
+ * and live here. Each product passes the extra paths and prefixes its own app
61
+ * shell claims, because those differ: Zero's client router owns `/auth/*`,
62
+ * a Functions worker owns nothing beyond what the platform reserves.
63
+ */
64
+ export declare function appRoutePathSchema(options: {
65
+ subject: string;
66
+ reservedPaths?: readonly string[];
67
+ reservedPrefixes?: readonly string[];
68
+ }): z.ZodString;
69
+ export declare const httpMethodSchema: z.ZodEnum<{
70
+ POST: "POST";
71
+ PUT: "PUT";
72
+ GET: "GET";
73
+ HEAD: "HEAD";
74
+ PATCH: "PATCH";
75
+ DELETE: "DELETE";
76
+ OPTIONS: "OPTIONS";
77
+ }>;
78
+ export type HttpMethod = z.infer<typeof httpMethodSchema>;
79
+ /**
80
+ * What a handler is allowed to reach. Declared at build time from what the
81
+ * source actually imports, not from what the author claims, and printed in the
82
+ * publish receipt so placement is never a surprise.
83
+ */
84
+ export declare const executionCapabilitiesSchema: z.ZodDefault<z.ZodObject<{
85
+ db: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
86
+ fetch: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
87
+ auth: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
88
+ env: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
89
+ realtime: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
90
+ logging: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
91
+ }, z.core.$strip>>;
92
+ export type ExecutionCapabilities = z.infer<typeof executionCapabilitiesSchema>;
93
+ /**
94
+ * The portable seam, stated as one predicate.
95
+ *
96
+ * `db` and `auth` are the two capabilities that bind a handler to the local
97
+ * tier. `db` because Zero's store guarantees every handler runs inside one
98
+ * parent-owned transaction, which an isolate reaching the database over a
99
+ * relay cannot offer. `auth` because capsule identity is resolved against that
100
+ * same transaction's view. Everything else — fetch, env, realtime, logging —
101
+ * is brokered identically on both tiers and does not constrain placement.
102
+ *
103
+ * Nothing here decides placement automatically: the product a version deploys
104
+ * to is a property of what its author wrote, and this predicate only reports
105
+ * whether the code could also have run on the other tier.
106
+ */
107
+ export declare function endpointIsPortable(capabilities: ExecutionCapabilities): boolean;
108
+ /**
109
+ * A source-carrying handler, before it is compiled to whichever tier will run
110
+ * it. Both products publish these; only the capability set decides which of
111
+ * them could accept a given one.
112
+ */
113
+ export declare function executionEndpointSourceSchema(routePathSchema: z.ZodType<string>): z.ZodObject<{
114
+ method: z.ZodEnum<{
115
+ POST: "POST";
116
+ PUT: "PUT";
117
+ GET: "GET";
118
+ HEAD: "HEAD";
119
+ PATCH: "PATCH";
120
+ DELETE: "DELETE";
121
+ OPTIONS: "OPTIONS";
122
+ }>;
123
+ path: z.ZodType<string, unknown, z.core.$ZodTypeInternals<string, unknown>>;
124
+ endpointId: z.ZodString;
125
+ source: z.ZodString;
126
+ capabilities: z.ZodDefault<z.ZodObject<{
127
+ db: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
128
+ fetch: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
129
+ auth: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
130
+ env: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
131
+ realtime: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
132
+ logging: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
133
+ }, z.core.$strip>>;
134
+ }, z.core.$strict>;
135
+ /**
136
+ * Byte-carrying artifacts travel inline in the finalize payload rather than as
137
+ * a separate upload, so a version's code lands in the same atomic write as the
138
+ * rest of it. The storage root and ceiling differ per product, hence a factory.
139
+ */
140
+ export declare function executionBundleSchema(options: {
141
+ bundleRoot: string;
142
+ maxBytes: number;
143
+ }): z.ZodObject<{
144
+ path: z.ZodString;
145
+ sha256: z.ZodString;
146
+ size: z.ZodNumber;
147
+ contentBase64: z.ZodString;
148
+ }, z.core.$strict>;
149
+ export declare function executionStaticFileSchema(options: {
150
+ storageRoot: string;
151
+ maxBytes: number;
152
+ }): z.ZodObject<{
153
+ path: z.ZodString;
154
+ contentType: z.ZodString;
155
+ sha256: z.ZodString;
156
+ size: z.ZodNumber;
157
+ contentBase64: z.ZodString;
158
+ }, z.core.$strict>;
159
+ /**
160
+ * Whether a version's execution surface may be read by anyone or only by the
161
+ * space's own members. Both products expose an inspect route; the policy is
162
+ * the same question on each.
163
+ */
164
+ export declare const executionInspectPolicySchema: z.ZodEnum<{
165
+ public: "public";
166
+ private: "private";
167
+ }>;
168
+ export type ExecutionInspectPolicy = z.infer<typeof executionInspectPolicySchema>;
169
+ export declare const executionLogEntrySchema: z.ZodObject<{
170
+ id: z.ZodString;
171
+ timestamp: z.ZodString;
172
+ level: z.ZodEnum<{
173
+ info: "info";
174
+ error: "error";
175
+ debug: "debug";
176
+ warn: "warn";
177
+ }>;
178
+ requestId: z.ZodNullable<z.ZodString>;
179
+ versionId: z.ZodNullable<z.ZodString>;
180
+ handlerName: z.ZodNullable<z.ZodString>;
181
+ message: z.ZodString;
182
+ metadata: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
183
+ }, z.core.$strict>;
184
+ export type ExecutionLogEntry = z.infer<typeof executionLogEntrySchema>;
185
+ export declare const executionLogListQuerySchema: z.ZodObject<{
186
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
187
+ cursor: z.ZodOptional<z.ZodString>;
188
+ requestId: z.ZodOptional<z.ZodString>;
189
+ handlerName: z.ZodOptional<z.ZodString>;
190
+ }, z.core.$strict>;
191
+ export type ExecutionLogListQuery = z.infer<typeof executionLogListQuerySchema>;
192
+ export declare const executionLogListResponseSchema: z.ZodObject<{
193
+ data: z.ZodArray<z.ZodObject<{
194
+ id: z.ZodString;
195
+ timestamp: z.ZodString;
196
+ level: z.ZodEnum<{
197
+ info: "info";
198
+ error: "error";
199
+ debug: "debug";
200
+ warn: "warn";
201
+ }>;
202
+ requestId: z.ZodNullable<z.ZodString>;
203
+ versionId: z.ZodNullable<z.ZodString>;
204
+ handlerName: z.ZodNullable<z.ZodString>;
205
+ message: z.ZodString;
206
+ metadata: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
207
+ }, z.core.$strict>>;
208
+ pagination: z.ZodObject<{
209
+ nextCursor: z.ZodNullable<z.ZodString>;
210
+ }, z.core.$strict>;
211
+ }, z.core.$strict>;
212
+ export type ExecutionLogListResponse = z.infer<typeof executionLogListResponseSchema>;
@@ -0,0 +1,288 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The platform execution layer: everything true of running tenant code on
4
+ * Spacefast regardless of which product is running it.
5
+ *
6
+ * Two products sit on top of this file, and they are genuinely different
7
+ * things rather than two configurations of one thing:
8
+ *
9
+ * - **Zero** (`./zero.js`) runs capsules — schema, queries, mutations and
10
+ * endpoints that share one parent-owned transaction — in the native runner
11
+ * on a dedicated wp.cloud site, next to that site's MySQL.
12
+ * - **Functions** (`./functions.js`) runs workers on Cloudflare Workers for
13
+ * Platforms, carrying npm and framework output that we could never sandbox
14
+ * as well ourselves.
15
+ *
16
+ * The only surface both can host is an endpoint that touches neither the
17
+ * database nor the caller's identity — see `endpointIsPortable`. That is the
18
+ * portable seam, and it is deliberately narrow: a capsule handler's semantics
19
+ * depend on the transaction wrapped around it, which does not exist on an
20
+ * isolate a continent away from the database.
21
+ */
22
+ /**
23
+ * What a live version runs. `static` is bytes on disk with no execution at
24
+ * all, and remains the overwhelming majority of traffic; the other two each
25
+ * name a product.
26
+ */
27
+ export const runtimeKindSchema = z.enum(["static", "zero", "functions"]);
28
+ export const EXECUTION_ENV_MAX_KEYS = 64;
29
+ export const EXECUTION_ENV_NAME_MAX_BYTES = 128;
30
+ export const EXECUTION_ENV_VALUE_MAX_BYTES = 16 * 1024;
31
+ export const EXECUTION_ENV_TOTAL_VALUE_MAX_BYTES = 64 * 1024;
32
+ /**
33
+ * Names the platform reserves for itself in the tenant environment. Tenant
34
+ * code cannot set these, so it cannot spoof the values the broker trusts.
35
+ */
36
+ export const EXECUTION_ENV_RESERVED_PREFIXES = ["SPACEFAST_", "ZERO_"];
37
+ /** Paths the platform owns on every space, whatever the product. */
38
+ export const PLATFORM_ROUTE_PREFIX = "/__spacefast";
39
+ /**
40
+ * First path segments the platform claims. Matched as segment prefixes rather
41
+ * than whole segments because the runtime already reserves the whole family —
42
+ * `/__stattic_probe` is a live serving path, and `upload-policy.php` refuses
43
+ * any first segment starting with either of these. Encoding it the same way
44
+ * here keeps a capsule or worker from declaring a route the origin would
45
+ * intercept before it ever reached them.
46
+ */
47
+ export const PLATFORM_RESERVED_ROUTE_SEGMENT_PREFIXES = [
48
+ "__spacefast",
49
+ "__stattic",
50
+ "__span",
51
+ ];
52
+ export const executionSha256Schema = z.string().regex(/^sha256:[a-f0-9]{64}$/);
53
+ export const executionBase64Schema = z
54
+ .string()
55
+ .regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/);
56
+ export const executionIdentifierSchema = z
57
+ .string()
58
+ .min(1)
59
+ .max(128)
60
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/);
61
+ export const executionEnvNameSchema = z
62
+ .string()
63
+ .min(1)
64
+ .max(EXECUTION_ENV_NAME_MAX_BYTES)
65
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/)
66
+ .refine((name) => !EXECUTION_ENV_RESERVED_PREFIXES.some((prefix) => name.toUpperCase().startsWith(prefix)), { message: "Environment variable name uses a reserved prefix." });
67
+ /**
68
+ * Rejects lone surrogates. A path that survives JSON but not UTF-8 encoding
69
+ * round-trips differently through PHP, Rust and Cloudflare's router, so it is
70
+ * refused at the contract rather than resolved differently on each tier.
71
+ */
72
+ function hasWellFormedUnicode(value) {
73
+ for (let index = 0; index < value.length; index++) {
74
+ const codeUnit = value.charCodeAt(index);
75
+ if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
76
+ if (index + 1 >= value.length)
77
+ return false;
78
+ const next = value.charCodeAt(index + 1);
79
+ if (next < 0xdc00 || next > 0xdfff)
80
+ return false;
81
+ index++;
82
+ }
83
+ else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
84
+ return false;
85
+ }
86
+ }
87
+ return true;
88
+ }
89
+ function hasNoAsciiControlCharacters(value) {
90
+ for (let index = 0; index < value.length; index++) {
91
+ const codeUnit = value.charCodeAt(index);
92
+ if (codeUnit <= 0x1f || codeUnit === 0x7f)
93
+ return false;
94
+ }
95
+ return true;
96
+ }
97
+ /**
98
+ * Builds the route-path schema for a product. Structural rules — normalization,
99
+ * traversal, wildcards, the platform's own prefixes — are identical everywhere
100
+ * and live here. Each product passes the extra paths and prefixes its own app
101
+ * shell claims, because those differ: Zero's client router owns `/auth/*`,
102
+ * a Functions worker owns nothing beyond what the platform reserves.
103
+ */
104
+ export function appRoutePathSchema(options) {
105
+ const reservedPaths = options.reservedPaths ?? [];
106
+ const reservedPrefixes = options.reservedPrefixes ?? [];
107
+ return z
108
+ .string()
109
+ .min(1)
110
+ .max(2048)
111
+ .startsWith("/")
112
+ .refine(hasWellFormedUnicode, {
113
+ message: `${options.subject} must contain well-formed Unicode.`,
114
+ })
115
+ .refine((path) => path.normalize("NFC") === path, {
116
+ message: `${options.subject} must use NFC-normalized Unicode.`,
117
+ })
118
+ .refine(hasNoAsciiControlCharacters, {
119
+ message: `${options.subject} must not contain ASCII control characters.`,
120
+ })
121
+ .refine((path) => !path.includes("//") &&
122
+ !path.includes("\\") &&
123
+ !path.includes("*") &&
124
+ !path.includes("?") &&
125
+ !path.includes("#") &&
126
+ !path.includes("%") &&
127
+ // The root is the one path allowed to end in a slash. Zero reserves it
128
+ // for its app shell anyway, but a Functions worker serving a framework
129
+ // app needs to claim "/" as its index route.
130
+ (path === "/" || !path.endsWith("/")) &&
131
+ path
132
+ .split("/")
133
+ .every((segment, index, segments) => segment !== "." &&
134
+ segment !== ".." &&
135
+ segment !== ":" &&
136
+ (segment !== ":splat" || index === segments.length - 1)), {
137
+ message: `${options.subject} must be absolute app paths without encoded, empty, traversal, unnamed parameter, * wildcard, non-terminal splat, \\, ?, or # segments.`,
138
+ })
139
+ .refine((path) => !PLATFORM_RESERVED_ROUTE_SEGMENT_PREFIXES.some((segmentPrefix) => (path.split("/")[1] ?? "").startsWith(segmentPrefix)), { message: `${options.subject} cannot use a path the platform reserves.` })
140
+ .refine((path) => !reservedPaths.some((reservedPath) => path === reservedPath), {
141
+ message: `${options.subject} cannot use a reserved path.`,
142
+ })
143
+ .refine((path) => !reservedPrefixes.some((prefix) => path.startsWith(prefix)), {
144
+ message: `${options.subject} cannot use a reserved path prefix.`,
145
+ });
146
+ }
147
+ export const httpMethodSchema = z.enum([
148
+ "GET",
149
+ "HEAD",
150
+ "POST",
151
+ "PUT",
152
+ "PATCH",
153
+ "DELETE",
154
+ "OPTIONS",
155
+ ]);
156
+ /**
157
+ * What a handler is allowed to reach. Declared at build time from what the
158
+ * source actually imports, not from what the author claims, and printed in the
159
+ * publish receipt so placement is never a surprise.
160
+ */
161
+ export const executionCapabilitiesSchema = z
162
+ .object({
163
+ db: z.boolean().default(false),
164
+ fetch: z.boolean().default(false),
165
+ auth: z.boolean().default(false),
166
+ env: z.boolean().default(false),
167
+ realtime: z.boolean().default(false),
168
+ logging: z.boolean().default(false),
169
+ })
170
+ .partial()
171
+ .default({});
172
+ /**
173
+ * The portable seam, stated as one predicate.
174
+ *
175
+ * `db` and `auth` are the two capabilities that bind a handler to the local
176
+ * tier. `db` because Zero's store guarantees every handler runs inside one
177
+ * parent-owned transaction, which an isolate reaching the database over a
178
+ * relay cannot offer. `auth` because capsule identity is resolved against that
179
+ * same transaction's view. Everything else — fetch, env, realtime, logging —
180
+ * is brokered identically on both tiers and does not constrain placement.
181
+ *
182
+ * Nothing here decides placement automatically: the product a version deploys
183
+ * to is a property of what its author wrote, and this predicate only reports
184
+ * whether the code could also have run on the other tier.
185
+ */
186
+ export function endpointIsPortable(capabilities) {
187
+ return capabilities.db !== true && capabilities.auth !== true;
188
+ }
189
+ /**
190
+ * A source-carrying handler, before it is compiled to whichever tier will run
191
+ * it. Both products publish these; only the capability set decides which of
192
+ * them could accept a given one.
193
+ */
194
+ export function executionEndpointSourceSchema(routePathSchema) {
195
+ return z
196
+ .object({
197
+ method: httpMethodSchema,
198
+ path: routePathSchema,
199
+ endpointId: z.string().min(1).max(256),
200
+ source: z
201
+ .string()
202
+ .min(1)
203
+ .max(2 * 1024 * 1024),
204
+ capabilities: executionCapabilitiesSchema,
205
+ })
206
+ .strict();
207
+ }
208
+ /**
209
+ * Byte-carrying artifacts travel inline in the finalize payload rather than as
210
+ * a separate upload, so a version's code lands in the same atomic write as the
211
+ * rest of it. The storage root and ceiling differ per product, hence a factory.
212
+ */
213
+ export function executionBundleSchema(options) {
214
+ return z
215
+ .object({
216
+ path: z
217
+ .string()
218
+ .min(1)
219
+ .startsWith(`${options.bundleRoot}/`)
220
+ .refine((bundlePath) => !bundlePath.split("/").includes(".."), {
221
+ message: "Bundle paths cannot traverse outside the runtime bundle root.",
222
+ }),
223
+ sha256: executionSha256Schema,
224
+ size: z.number().int().nonnegative().max(options.maxBytes),
225
+ contentBase64: executionBase64Schema,
226
+ })
227
+ .strict();
228
+ }
229
+ export function executionStaticFileSchema(options) {
230
+ return z
231
+ .object({
232
+ path: z
233
+ .string()
234
+ .min(1)
235
+ .max(2048)
236
+ .refine((path) => !path.startsWith("/") && !path.split("/").includes(".."), {
237
+ message: "Static file paths must be relative and cannot traverse.",
238
+ })
239
+ .refine((path) => !path.startsWith(`${options.storageRoot}/`), {
240
+ message: `Static files cannot use ${options.storageRoot}.`,
241
+ })
242
+ .refine((path) => !path.startsWith("_") && !path.startsWith("."), {
243
+ message: "Static file paths cannot use private root prefixes.",
244
+ }),
245
+ contentType: z.string().min(1).max(255),
246
+ sha256: executionSha256Schema,
247
+ size: z.number().int().nonnegative().max(options.maxBytes),
248
+ contentBase64: executionBase64Schema,
249
+ })
250
+ .strict();
251
+ }
252
+ /**
253
+ * Whether a version's execution surface may be read by anyone or only by the
254
+ * space's own members. Both products expose an inspect route; the policy is
255
+ * the same question on each.
256
+ */
257
+ export const executionInspectPolicySchema = z.enum(["private", "public"]);
258
+ export const executionLogEntrySchema = z
259
+ .object({
260
+ id: z.string().min(1),
261
+ timestamp: z.string().datetime(),
262
+ level: z.enum(["debug", "info", "warn", "error"]),
263
+ requestId: z.string().min(1).nullable(),
264
+ versionId: z.string().min(1).nullable(),
265
+ /** Zero names the mutation; Functions names the handler. */
266
+ handlerName: z.string().min(1).nullable(),
267
+ message: z.string(),
268
+ metadata: z.record(z.string(), z.unknown()).default({}),
269
+ })
270
+ .strict();
271
+ export const executionLogListQuerySchema = z
272
+ .object({
273
+ limit: z.coerce.number().int().min(1).max(100).default(50),
274
+ cursor: z.string().min(1).optional(),
275
+ requestId: z.string().min(1).optional(),
276
+ handlerName: z.string().min(1).optional(),
277
+ })
278
+ .strict();
279
+ export const executionLogListResponseSchema = z
280
+ .object({
281
+ data: z.array(executionLogEntrySchema),
282
+ pagination: z
283
+ .object({
284
+ nextCursor: z.string().nullable(),
285
+ })
286
+ .strict(),
287
+ })
288
+ .strict();