@zenithbuild/cli 0.7.4 → 0.7.7

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 (112) hide show
  1. package/README.md +5 -3
  2. package/dist/adapters/adapter-netlify.d.ts +1 -1
  3. package/dist/adapters/adapter-netlify.js +48 -14
  4. package/dist/adapters/adapter-static-export.d.ts +5 -0
  5. package/dist/adapters/adapter-static-export.js +115 -0
  6. package/dist/adapters/adapter-types.d.ts +3 -1
  7. package/dist/adapters/adapter-types.js +5 -2
  8. package/dist/adapters/adapter-vercel.d.ts +1 -1
  9. package/dist/adapters/adapter-vercel.js +67 -19
  10. package/dist/adapters/copy-hosted-page-runtime.d.ts +1 -0
  11. package/dist/adapters/copy-hosted-page-runtime.js +50 -0
  12. package/dist/adapters/resolve-adapter.js +4 -0
  13. package/dist/adapters/route-rules.d.ts +5 -0
  14. package/dist/adapters/route-rules.js +9 -0
  15. package/dist/adapters/validate-hosted-resource-routes.d.ts +1 -0
  16. package/dist/adapters/validate-hosted-resource-routes.js +13 -0
  17. package/dist/auth/route-auth.d.ts +6 -0
  18. package/dist/auth/route-auth.js +236 -0
  19. package/dist/build/compiler-runtime.d.ts +1 -1
  20. package/dist/build/compiler-runtime.js +8 -2
  21. package/dist/build/hoisted-code-transforms.d.ts +4 -1
  22. package/dist/build/hoisted-code-transforms.js +5 -3
  23. package/dist/build/page-ir-normalization.d.ts +1 -1
  24. package/dist/build/page-ir-normalization.js +33 -3
  25. package/dist/build/page-loop-state.js +1 -1
  26. package/dist/build/page-loop.js +46 -2
  27. package/dist/build/server-script.d.ts +2 -1
  28. package/dist/build/server-script.js +7 -3
  29. package/dist/build-output-manifest.d.ts +3 -2
  30. package/dist/build-output-manifest.js +3 -0
  31. package/dist/build.js +29 -17
  32. package/dist/dev-build-session/helpers.d.ts +29 -0
  33. package/dist/dev-build-session/helpers.js +223 -0
  34. package/dist/dev-build-session/session.d.ts +24 -0
  35. package/dist/dev-build-session/session.js +204 -0
  36. package/dist/dev-build-session/state.d.ts +37 -0
  37. package/dist/dev-build-session/state.js +17 -0
  38. package/dist/dev-build-session.d.ts +1 -24
  39. package/dist/dev-build-session.js +1 -434
  40. package/dist/dev-server/css-state.d.ts +7 -0
  41. package/dist/dev-server/css-state.js +92 -0
  42. package/dist/dev-server/not-found.d.ts +23 -0
  43. package/dist/dev-server/not-found.js +129 -0
  44. package/dist/dev-server/request-handler.d.ts +1 -0
  45. package/dist/dev-server/request-handler.js +376 -0
  46. package/dist/dev-server/route-check.d.ts +9 -0
  47. package/dist/dev-server/route-check.js +100 -0
  48. package/dist/dev-server/watcher.d.ts +5 -0
  49. package/dist/dev-server/watcher.js +216 -0
  50. package/dist/dev-server.js +136 -883
  51. package/dist/download-result.d.ts +14 -0
  52. package/dist/download-result.js +148 -0
  53. package/dist/images/payload.js +4 -0
  54. package/dist/images/service.d.ts +13 -1
  55. package/dist/images/service.js +45 -15
  56. package/dist/manifest.d.ts +15 -1
  57. package/dist/manifest.js +70 -6
  58. package/dist/preview/create-preview-server.d.ts +18 -0
  59. package/dist/preview/create-preview-server.js +71 -0
  60. package/dist/preview/manifest.d.ts +42 -0
  61. package/dist/preview/manifest.js +57 -0
  62. package/dist/preview/paths.d.ts +3 -0
  63. package/dist/preview/paths.js +38 -0
  64. package/dist/preview/payload.d.ts +6 -0
  65. package/dist/preview/payload.js +34 -0
  66. package/dist/preview/request-handler.d.ts +1 -0
  67. package/dist/preview/request-handler.js +300 -0
  68. package/dist/preview/server-runner.d.ts +49 -0
  69. package/dist/preview/server-runner.js +220 -0
  70. package/dist/preview/server-script-runner-template.d.ts +1 -0
  71. package/dist/preview/server-script-runner-template.js +425 -0
  72. package/dist/preview.d.ts +5 -104
  73. package/dist/preview.js +7 -993
  74. package/dist/request-body.d.ts +0 -1
  75. package/dist/request-body.js +0 -6
  76. package/dist/resource-manifest.d.ts +16 -0
  77. package/dist/resource-manifest.js +53 -0
  78. package/dist/resource-response.d.ts +49 -0
  79. package/dist/resource-response.js +160 -0
  80. package/dist/resource-route-module.d.ts +15 -0
  81. package/dist/resource-route-module.js +129 -0
  82. package/dist/route-check-support.js +1 -1
  83. package/dist/server-contract/constants.d.ts +5 -0
  84. package/dist/server-contract/constants.js +5 -0
  85. package/dist/server-contract/export-validation.d.ts +5 -0
  86. package/dist/server-contract/export-validation.js +59 -0
  87. package/dist/server-contract/json-serializable.d.ts +1 -0
  88. package/dist/server-contract/json-serializable.js +52 -0
  89. package/dist/server-contract/resolve.d.ts +15 -0
  90. package/dist/server-contract/resolve.js +271 -0
  91. package/dist/server-contract/result-helpers.d.ts +51 -0
  92. package/dist/server-contract/result-helpers.js +59 -0
  93. package/dist/server-contract/route-result-validation.d.ts +2 -0
  94. package/dist/server-contract/route-result-validation.js +73 -0
  95. package/dist/server-contract/stage.d.ts +6 -0
  96. package/dist/server-contract/stage.js +22 -0
  97. package/dist/server-contract.d.ts +6 -54
  98. package/dist/server-contract.js +9 -301
  99. package/dist/server-error.d.ts +1 -1
  100. package/dist/server-error.js +2 -0
  101. package/dist/server-middleware.d.ts +10 -0
  102. package/dist/server-middleware.js +30 -0
  103. package/dist/server-output.d.ts +2 -1
  104. package/dist/server-output.js +72 -12
  105. package/dist/server-runtime/node-server.js +59 -7
  106. package/dist/server-runtime/route-render.d.ts +25 -1
  107. package/dist/server-runtime/route-render.js +81 -29
  108. package/dist/server-script-composition.d.ts +4 -2
  109. package/dist/server-script-composition.js +6 -3
  110. package/dist/static-export-paths.d.ts +3 -0
  111. package/dist/static-export-paths.js +160 -0
  112. package/package.json +3 -3
@@ -0,0 +1,271 @@
1
+ import { STAGED_SET_COOKIES_KEY } from './constants.js';
2
+ import { validateServerExports } from './export-validation.js';
3
+ import { assertJsonSerializable } from './json-serializable.js';
4
+ import { assertValidRouteResultShape, isRouteResultLike } from './route-result-validation.js';
5
+ import { allow, data, text } from './result-helpers.js';
6
+ import { invokeRouteStage } from './stage.js';
7
+ function buildActionState(result) {
8
+ if (!result || typeof result !== 'object') {
9
+ return null;
10
+ }
11
+ if (result.kind === 'data') {
12
+ return {
13
+ ok: true,
14
+ status: 200,
15
+ data: result.data
16
+ };
17
+ }
18
+ if (result.kind === 'invalid') {
19
+ return {
20
+ ok: false,
21
+ status: Number.isInteger(result.status) ? result.status : 400,
22
+ data: result.data
23
+ };
24
+ }
25
+ return null;
26
+ }
27
+ function buildResolvedEnvelope({ result, trace, status, ctx }) {
28
+ const envelope = { result, trace };
29
+ if (status !== undefined) {
30
+ envelope.status = status;
31
+ }
32
+ const setCookies = Array.isArray(ctx?.[STAGED_SET_COOKIES_KEY])
33
+ ? ctx[STAGED_SET_COOKIES_KEY].slice()
34
+ : [];
35
+ if (setCookies.length > 0) {
36
+ envelope.setCookies = setCookies;
37
+ }
38
+ return envelope;
39
+ }
40
+ export async function resolveRouteResult({ exports, ctx, filePath, guardOnly = false, routeKind = 'page' }) {
41
+ validateServerExports({ exports, filePath, routeKind });
42
+ if (routeKind === 'resource') {
43
+ return resolveResourceRouteResult({ exports, ctx, filePath, guardOnly });
44
+ }
45
+ const trace = {
46
+ guard: 'none',
47
+ action: 'none',
48
+ load: 'none'
49
+ };
50
+ let responseStatus = 200;
51
+ const requestMethod = String(ctx?.method || ctx?.request?.method || 'GET').toUpperCase();
52
+ const isActionRequest = !guardOnly && requestMethod === 'POST';
53
+ if (ctx && typeof ctx === 'object') {
54
+ ctx.action = null;
55
+ }
56
+ if ('guard' in exports) {
57
+ const guardRaw = await invokeRouteStage({
58
+ fn: exports.guard,
59
+ ctx,
60
+ where: `${filePath}: guard(ctx)`,
61
+ allowedKinds: new Set(['allow', 'redirect', 'deny'])
62
+ });
63
+ const guardResult = guardRaw == null ? allow() : guardRaw;
64
+ if (guardResult.kind === 'data') {
65
+ throw new Error(`[Zenith] ${filePath}: guard(ctx) returned data(payload) which is a critical invariant violation. guard() can only return allow(), redirect(), or deny(). Use load(ctx) for data injection.`);
66
+ }
67
+ assertValidRouteResultShape(guardResult, `${filePath}: guard(ctx) return`, new Set(['allow', 'redirect', 'deny']));
68
+ trace.guard = guardResult.kind;
69
+ if (guardResult.kind === 'redirect' || guardResult.kind === 'deny') {
70
+ return buildResolvedEnvelope({ result: guardResult, trace, ctx });
71
+ }
72
+ }
73
+ if (guardOnly) {
74
+ return buildResolvedEnvelope({ result: allow(), trace, ctx });
75
+ }
76
+ if (isActionRequest && 'action' in exports) {
77
+ const actionRaw = await invokeRouteStage({
78
+ fn: exports.action,
79
+ ctx,
80
+ where: `${filePath}: action(ctx)`,
81
+ allowedKinds: new Set(['data', 'invalid', 'redirect', 'deny'])
82
+ });
83
+ let actionResult = null;
84
+ if (isRouteResultLike(actionRaw)) {
85
+ actionResult = actionRaw;
86
+ assertValidRouteResultShape(actionResult, `${filePath}: action(ctx) return`, new Set(['data', 'invalid', 'redirect', 'deny']));
87
+ if (actionResult.kind === 'data' || actionResult.kind === 'invalid') {
88
+ assertJsonSerializable(actionResult.data, `${filePath}: action(ctx) return`);
89
+ }
90
+ }
91
+ else {
92
+ assertJsonSerializable(actionRaw, `${filePath}: action(ctx) return`);
93
+ actionResult = data(actionRaw);
94
+ }
95
+ trace.action = actionResult.kind;
96
+ if (actionResult.kind === 'redirect' || actionResult.kind === 'deny') {
97
+ return buildResolvedEnvelope({ result: actionResult, trace, ctx });
98
+ }
99
+ const actionState = buildActionState(actionResult);
100
+ if (ctx && typeof ctx === 'object') {
101
+ ctx.action = actionState;
102
+ }
103
+ if (actionState && actionState.ok === false) {
104
+ responseStatus = actionState.status;
105
+ }
106
+ }
107
+ let payload;
108
+ if ('load' in exports) {
109
+ const loadRaw = await invokeRouteStage({
110
+ fn: exports.load,
111
+ ctx,
112
+ where: `${filePath}: load(ctx)`,
113
+ allowedKinds: new Set(['data', 'redirect', 'deny'])
114
+ });
115
+ let loadResult = null;
116
+ if (isRouteResultLike(loadRaw)) {
117
+ loadResult = loadRaw;
118
+ assertValidRouteResultShape(loadResult, `${filePath}: load(ctx) return`, new Set(['data', 'redirect', 'deny']));
119
+ }
120
+ else {
121
+ assertJsonSerializable(loadRaw, `${filePath}: load(ctx) return`);
122
+ loadResult = data(loadRaw);
123
+ }
124
+ trace.load = loadResult.kind;
125
+ return buildResolvedEnvelope({
126
+ result: loadResult,
127
+ trace,
128
+ status: loadResult.kind === 'data' ? responseStatus : undefined,
129
+ ctx
130
+ });
131
+ }
132
+ if ('data' in exports) {
133
+ payload = exports.data;
134
+ assertJsonSerializable(payload, `${filePath}: data export`);
135
+ trace.load = 'data';
136
+ return buildResolvedEnvelope({ result: data(payload), trace, status: responseStatus, ctx });
137
+ }
138
+ // legacy fallback
139
+ if ('ssr_data' in exports) {
140
+ payload = exports.ssr_data;
141
+ assertJsonSerializable(payload, `${filePath}: ssr_data export`);
142
+ trace.load = 'data';
143
+ return buildResolvedEnvelope({ result: data(payload), trace, status: responseStatus, ctx });
144
+ }
145
+ if ('props' in exports) {
146
+ payload = exports.props;
147
+ assertJsonSerializable(payload, `${filePath}: props export`);
148
+ trace.load = 'data';
149
+ return buildResolvedEnvelope({ result: data(payload), trace, status: responseStatus, ctx });
150
+ }
151
+ if ('ssr' in exports) {
152
+ payload = exports.ssr;
153
+ assertJsonSerializable(payload, `${filePath}: ssr export`);
154
+ trace.load = 'data';
155
+ return buildResolvedEnvelope({ result: data(payload), trace, status: responseStatus, ctx });
156
+ }
157
+ if (isActionRequest && ctx?.action) {
158
+ trace.load = 'data';
159
+ return buildResolvedEnvelope({
160
+ result: data({ action: ctx.action }),
161
+ trace,
162
+ status: responseStatus,
163
+ ctx
164
+ });
165
+ }
166
+ return buildResolvedEnvelope({ result: data({}), trace, status: responseStatus, ctx });
167
+ }
168
+ async function resolveResourceRouteResult({ exports, ctx, filePath, guardOnly = false }) {
169
+ const trace = {
170
+ guard: 'none',
171
+ action: 'none',
172
+ load: 'none'
173
+ };
174
+ const requestMethod = String(ctx?.method || ctx?.request?.method || 'GET').toUpperCase();
175
+ if (ctx && typeof ctx === 'object') {
176
+ ctx.action = null;
177
+ }
178
+ if ('guard' in exports) {
179
+ const guardRaw = await invokeRouteStage({
180
+ fn: exports.guard,
181
+ ctx,
182
+ where: `${filePath}: guard(ctx)`,
183
+ allowedKinds: new Set(['allow', 'redirect', 'deny'])
184
+ });
185
+ const guardResult = guardRaw == null ? allow() : guardRaw;
186
+ assertValidRouteResultShape(guardResult, `${filePath}: guard(ctx) return`, new Set(['allow', 'redirect', 'deny']));
187
+ trace.guard = guardResult.kind;
188
+ if (guardResult.kind === 'redirect' || guardResult.kind === 'deny') {
189
+ return buildResolvedEnvelope({ result: guardResult, trace, ctx });
190
+ }
191
+ }
192
+ if (guardOnly) {
193
+ return buildResolvedEnvelope({ result: allow(), trace, ctx });
194
+ }
195
+ if (requestMethod === 'GET' || requestMethod === 'HEAD') {
196
+ if (!('load' in exports)) {
197
+ trace.load = 'text';
198
+ return buildResolvedEnvelope({ result: text('Method Not Allowed', 405), trace, status: 405, ctx });
199
+ }
200
+ const loadResult = await resolveResourceStage({
201
+ exports,
202
+ exportName: 'load',
203
+ ctx,
204
+ filePath,
205
+ trace,
206
+ traceKey: 'load'
207
+ });
208
+ return buildResolvedEnvelope({
209
+ result: loadResult,
210
+ trace,
211
+ status: loadResult.status,
212
+ ctx
213
+ });
214
+ }
215
+ if (requestMethod === 'POST') {
216
+ if (!('action' in exports)) {
217
+ trace.action = 'text';
218
+ return buildResolvedEnvelope({ result: text('Method Not Allowed', 405), trace, status: 405, ctx });
219
+ }
220
+ const actionResult = await resolveResourceStage({
221
+ exports,
222
+ exportName: 'action',
223
+ ctx,
224
+ filePath,
225
+ trace,
226
+ traceKey: 'action'
227
+ });
228
+ return buildResolvedEnvelope({
229
+ result: actionResult,
230
+ trace,
231
+ status: actionResult.status,
232
+ ctx
233
+ });
234
+ }
235
+ return buildResolvedEnvelope({
236
+ result: text('Method Not Allowed', 405),
237
+ trace,
238
+ status: 405,
239
+ ctx
240
+ });
241
+ }
242
+ async function resolveResourceStage({ exports, exportName, ctx, filePath, trace, traceKey }) {
243
+ const raw = await invokeRouteStage({
244
+ fn: exports[exportName],
245
+ ctx,
246
+ where: `${filePath}: ${exportName}(ctx)`,
247
+ allowedKinds: new Set(['json', 'text', 'download', 'redirect', 'deny', 'invalid', 'stream', 'sse'])
248
+ });
249
+ if (!isRouteResultLike(raw)) {
250
+ throw new Error(`[Zenith] ${filePath}: ${exportName}(ctx) on a resource route must return json(...), text(...), download(...), redirect(...), deny(...), invalid(...), stream(...), or sse(...).`);
251
+ }
252
+ assertValidRouteResultShape(raw, `${filePath}: ${exportName}(ctx) return`, new Set(['json', 'text', 'download', 'redirect', 'deny', 'invalid', 'stream', 'sse']));
253
+ if (raw.kind === 'json' || raw.kind === 'invalid') {
254
+ assertJsonSerializable(raw.data, `${filePath}: ${exportName}(ctx) return`);
255
+ }
256
+ trace[traceKey] = raw.kind;
257
+ return raw;
258
+ }
259
+ export async function resolveServerPayload({ exports, ctx, filePath }) {
260
+ const resolved = await resolveRouteResult({ exports, ctx, filePath });
261
+ if (!resolved || !resolved.result || typeof resolved.result !== 'object') {
262
+ return {};
263
+ }
264
+ if (resolved.result.kind === 'data') {
265
+ return resolved.result.data;
266
+ }
267
+ if (resolved.result.kind === 'allow') {
268
+ return {};
269
+ }
270
+ throw new Error(`[Zenith] ${filePath}: resolveServerPayload() expected data but received ${resolved.result.kind}. Use resolveRouteResult() for guard/load flows.`);
271
+ }
@@ -0,0 +1,51 @@
1
+ export function allow(): {
2
+ kind: string;
3
+ };
4
+ export function redirect(location: any, status?: number): {
5
+ kind: string;
6
+ location: string;
7
+ status: number;
8
+ };
9
+ export function deny(status?: number, message?: undefined): {
10
+ kind: string;
11
+ status: number;
12
+ message: undefined;
13
+ };
14
+ export function data(payload: any): {
15
+ kind: string;
16
+ data: any;
17
+ };
18
+ export function invalid(payload: any, status?: number): {
19
+ kind: string;
20
+ data: any;
21
+ status: number;
22
+ };
23
+ export function json(payload: any, status?: number): {
24
+ kind: string;
25
+ data: any;
26
+ status: number;
27
+ };
28
+ export function text(body: any, status?: number): {
29
+ kind: string;
30
+ body: string;
31
+ status: number;
32
+ };
33
+ export function download(body: any, options?: {}): {
34
+ kind: string;
35
+ body: any;
36
+ bodyEncoding: string;
37
+ bodySize: number;
38
+ filename: string;
39
+ contentType: string;
40
+ status: number;
41
+ };
42
+ export function stream(body: any, options?: {}): {
43
+ kind: string;
44
+ body: any;
45
+ status: any;
46
+ contentType: any;
47
+ };
48
+ export function sse(events: any): {
49
+ kind: string;
50
+ events: any;
51
+ };
@@ -0,0 +1,59 @@
1
+ import { createDownloadResult } from '../download-result.js';
2
+ export function allow() {
3
+ return { kind: 'allow' };
4
+ }
5
+ export function redirect(location, status = 302) {
6
+ return {
7
+ kind: 'redirect',
8
+ location: String(location || ''),
9
+ status: Number.isInteger(status) ? status : 302
10
+ };
11
+ }
12
+ export function deny(status = 403, message = undefined) {
13
+ return {
14
+ kind: 'deny',
15
+ status: Number.isInteger(status) ? status : 403,
16
+ message: typeof message === 'string' ? message : undefined
17
+ };
18
+ }
19
+ export function data(payload) {
20
+ return { kind: 'data', data: payload };
21
+ }
22
+ export function invalid(payload, status = 400) {
23
+ return {
24
+ kind: 'invalid',
25
+ data: payload,
26
+ status: Number.isInteger(status) ? status : 400
27
+ };
28
+ }
29
+ export function json(payload, status = 200) {
30
+ return {
31
+ kind: 'json',
32
+ data: payload,
33
+ status: Number.isInteger(status) ? status : 200
34
+ };
35
+ }
36
+ export function text(body, status = 200) {
37
+ return {
38
+ kind: 'text',
39
+ body: typeof body === 'string' ? body : String(body ?? ''),
40
+ status: Number.isInteger(status) ? status : 200
41
+ };
42
+ }
43
+ export function download(body, options = {}) {
44
+ return createDownloadResult(body, options);
45
+ }
46
+ export function stream(body, options = {}) {
47
+ return {
48
+ kind: 'stream',
49
+ body,
50
+ status: options?.status,
51
+ contentType: options?.contentType
52
+ };
53
+ }
54
+ export function sse(events) {
55
+ return {
56
+ kind: 'sse',
57
+ events
58
+ };
59
+ }
@@ -0,0 +1,2 @@
1
+ export function isRouteResultLike(value: any): boolean;
2
+ export function assertValidRouteResultShape(value: any, where: any, allowedKinds: any): void;
@@ -0,0 +1,73 @@
1
+ import { assertValidDownloadResult } from '../download-result.js';
2
+ import { ROUTE_RESULT_KINDS } from './constants.js';
3
+ export function isRouteResultLike(value) {
4
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
5
+ return false;
6
+ }
7
+ const kind = value.kind;
8
+ return typeof kind === 'string' && ROUTE_RESULT_KINDS.has(kind);
9
+ }
10
+ export function assertValidRouteResultShape(value, where, allowedKinds) {
11
+ if (!isRouteResultLike(value)) {
12
+ throw new Error(`[Zenith] ${where}: invalid route result. Expected object with kind.`);
13
+ }
14
+ const kind = value.kind;
15
+ if (!allowedKinds.has(kind)) {
16
+ throw new Error(`[Zenith] ${where}: kind "${kind}" is not allowed here (allowed: ${Array.from(allowedKinds).join(', ')}).`);
17
+ }
18
+ if (kind === 'redirect') {
19
+ if (typeof value.location !== 'string' || value.location.length === 0) {
20
+ throw new Error(`[Zenith] ${where}: redirect requires non-empty string location.`);
21
+ }
22
+ if (value.status !== undefined && (!Number.isInteger(value.status) || value.status < 300 || value.status > 399)) {
23
+ throw new Error(`[Zenith] ${where}: redirect status must be an integer 3xx.`);
24
+ }
25
+ }
26
+ if (kind === 'deny') {
27
+ if (!Number.isInteger(value.status) ||
28
+ (value.status !== 401 && value.status !== 403 && value.status !== 404)) {
29
+ throw new Error(`[Zenith] ${where}: deny status must be 401, 403, or 404.`);
30
+ }
31
+ if (value.message !== undefined && typeof value.message !== 'string') {
32
+ throw new Error(`[Zenith] ${where}: deny message must be a string when provided.`);
33
+ }
34
+ }
35
+ if (kind === 'invalid') {
36
+ if (!Number.isInteger(value.status) || (value.status !== 400 && value.status !== 422)) {
37
+ throw new Error(`[Zenith] ${where}: invalid status must be 400 or 422.`);
38
+ }
39
+ }
40
+ if (kind === 'json' || kind === 'text') {
41
+ if (!Number.isInteger(value.status) || value.status < 200 || value.status > 599 || (value.status >= 300 && value.status <= 399)) {
42
+ throw new Error(`[Zenith] ${where}: ${kind} status must be an integer between 200-599 and may not be 3xx.`);
43
+ }
44
+ if (kind === 'text' && typeof value.body !== 'string') {
45
+ throw new Error(`[Zenith] ${where}: text body must be a string.`);
46
+ }
47
+ }
48
+ if (kind === 'download') {
49
+ assertValidDownloadResult(value, where);
50
+ }
51
+ if (kind === 'stream') {
52
+ if (!isReadableStream(value.body) && !isAsyncIterable(value.body)) {
53
+ throw new Error(`[Zenith] ${where}: stream body must be a ReadableStream or AsyncIterable.`);
54
+ }
55
+ if (value.status !== undefined && (!Number.isInteger(value.status) || value.status < 200 || value.status > 599 || (value.status >= 300 && value.status <= 399))) {
56
+ throw new Error(`[Zenith] ${where}: stream status must be an integer between 200-599 and may not be 3xx.`);
57
+ }
58
+ if (value.contentType !== undefined && typeof value.contentType !== 'string') {
59
+ throw new Error(`[Zenith] ${where}: stream contentType must be a string.`);
60
+ }
61
+ }
62
+ if (kind === 'sse') {
63
+ if (!isAsyncIterable(value.events)) {
64
+ throw new Error(`[Zenith] ${where}: sse events must be an AsyncIterable.`);
65
+ }
66
+ }
67
+ }
68
+ function isReadableStream(v) {
69
+ return v && typeof v === 'object' && typeof v.getReader === 'function' && typeof v.cancel === 'function';
70
+ }
71
+ function isAsyncIterable(v) {
72
+ return v && typeof v === 'object' && typeof v[Symbol.asyncIterator] === 'function';
73
+ }
@@ -0,0 +1,6 @@
1
+ export function invokeRouteStage({ fn, ctx, where, allowedKinds }: {
2
+ fn: any;
3
+ ctx: any;
4
+ where: any;
5
+ allowedKinds: any;
6
+ }): Promise<any>;
@@ -0,0 +1,22 @@
1
+ import { AUTH_CONTROL_FLOW_FLAG } from './constants.js';
2
+ import { assertValidRouteResultShape } from './route-result-validation.js';
3
+ function unwrapAuthControlFlow(error, where, allowedKinds) {
4
+ if (!error || typeof error !== 'object' || error[AUTH_CONTROL_FLOW_FLAG] !== true) {
5
+ return null;
6
+ }
7
+ const result = error.result;
8
+ assertValidRouteResultShape(result, where, allowedKinds);
9
+ return result;
10
+ }
11
+ export async function invokeRouteStage({ fn, ctx, where, allowedKinds }) {
12
+ try {
13
+ return await fn(ctx);
14
+ }
15
+ catch (error) {
16
+ const authResult = unwrapAuthControlFlow(error, where, allowedKinds);
17
+ if (authResult) {
18
+ return authResult;
19
+ }
20
+ throw error;
21
+ }
22
+ }
@@ -1,54 +1,6 @@
1
- export function allow(): {
2
- kind: string;
3
- };
4
- export function redirect(location: any, status?: number): {
5
- kind: string;
6
- location: string;
7
- status: number;
8
- };
9
- export function deny(status?: number, message?: undefined): {
10
- kind: string;
11
- status: number;
12
- message: undefined;
13
- };
14
- export function data(payload: any): {
15
- kind: string;
16
- data: any;
17
- };
18
- export function invalid(payload: any, status?: number): {
19
- kind: string;
20
- data: any;
21
- status: number;
22
- };
23
- export function validateServerExports({ exports, filePath }: {
24
- exports: any;
25
- filePath: any;
26
- }): void;
27
- export function assertJsonSerializable(value: any, where?: string): void;
28
- export function resolveRouteResult({ exports, ctx, filePath, guardOnly }: {
29
- exports: any;
30
- ctx: any;
31
- filePath: any;
32
- guardOnly?: boolean | undefined;
33
- }): Promise<{
34
- result: any;
35
- trace: {
36
- guard: string;
37
- action: string;
38
- load: string;
39
- };
40
- status?: undefined;
41
- } | {
42
- result: any;
43
- trace: {
44
- guard: string;
45
- action: string;
46
- load: string;
47
- };
48
- status: number | undefined;
49
- }>;
50
- export function resolveServerPayload({ exports, ctx, filePath }: {
51
- exports: any;
52
- ctx: any;
53
- filePath: any;
54
- }): Promise<any>;
1
+ export { validateServerExports } from "./server-contract/export-validation.js";
2
+ export { assertJsonSerializable } from "./server-contract/json-serializable.js";
3
+ export { withMiddleware };
4
+ import { withMiddleware } from './server-middleware.js';
5
+ export { allow, redirect, deny, data, invalid, json, text, download, stream, sse } from "./server-contract/result-helpers.js";
6
+ export { resolveRouteResult, resolveServerPayload } from "./server-contract/resolve.js";