@analogjs/router 2.4.0-beta.9 → 3.0.0-alpha.10
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/fesm2022/analogjs-router-server-actions.mjs +327 -31
- package/fesm2022/analogjs-router-server.mjs +155 -176
- package/fesm2022/analogjs-router-tanstack-query.mjs +58 -0
- package/fesm2022/analogjs-router-tokens.mjs +17 -16
- package/fesm2022/analogjs-router.mjs +521 -812
- package/fesm2022/debug.page.mjs +121 -0
- package/fesm2022/routes.mjs +301 -0
- package/package.json +44 -26
- package/server/actions/package.json +4 -0
- package/server/package.json +4 -0
- package/tokens/package.json +4 -0
- package/types/server/actions/src/actions.d.ts +13 -0
- package/types/server/actions/src/define-action.d.ts +54 -0
- package/types/server/actions/src/define-page-load.d.ts +55 -0
- package/types/server/actions/src/define-server-route.d.ts +68 -0
- package/types/server/actions/src/index.d.ts +9 -0
- package/types/server/actions/src/parse-request-data.d.ts +9 -0
- package/types/server/actions/src/validate.d.ts +8 -0
- package/types/server/src/index.d.ts +4 -0
- package/types/server/src/provide-server-context.d.ts +11 -0
- package/types/server/src/render.d.ts +12 -0
- package/types/server/src/server-component-render.d.ts +4 -0
- package/types/server/src/tokens.d.ts +7 -0
- package/types/src/index.d.ts +16 -0
- package/types/src/lib/cache-key.d.ts +3 -0
- package/types/src/lib/constants.d.ts +2 -0
- package/types/src/lib/cookie-interceptor.d.ts +4 -0
- package/types/src/lib/debug/debug.page.d.ts +18 -0
- package/types/src/lib/debug/index.d.ts +10 -0
- package/types/src/lib/debug/routes.d.ts +10 -0
- package/types/src/lib/define-route.d.ts +46 -0
- package/types/src/lib/endpoints.d.ts +5 -0
- package/types/src/lib/form-action.directive.d.ts +25 -0
- package/types/src/lib/get-load-resolver.d.ts +8 -0
- package/types/src/lib/inject-load.d.ts +9 -0
- package/types/src/lib/inject-route-endpoint-url.d.ts +2 -0
- package/types/src/lib/markdown-helpers.d.ts +2 -0
- package/types/src/lib/meta-tags.d.ts +33 -0
- package/types/src/lib/models.d.ts +29 -0
- package/types/src/lib/provide-file-router.d.ts +18 -0
- package/types/src/lib/request-context.d.ts +13 -0
- package/types/src/lib/route-config.d.ts +2 -0
- package/types/src/lib/route-types.d.ts +12 -0
- package/types/src/lib/routes.d.ts +19 -0
- package/types/src/lib/server.component.d.ts +33 -0
- package/types/tanstack-query/src/index.d.ts +2 -0
- package/types/tanstack-query/src/provide-analog-query.d.ts +4 -0
- package/types/tanstack-query/src/provide-server-analog-query.d.ts +2 -0
- package/types/tanstack-query/src/server-query.d.ts +16 -0
- package/types/tokens/src/index.d.ts +23 -0
- package/fesm2022/analogjs-router-debug.page-jzggTA45.mjs +0 -91
- package/fesm2022/analogjs-router-debug.page-jzggTA45.mjs.map +0 -1
- package/fesm2022/analogjs-router-server-actions.mjs.map +0 -1
- package/fesm2022/analogjs-router-server.mjs.map +0 -1
- package/fesm2022/analogjs-router-tokens.mjs.map +0 -1
- package/fesm2022/analogjs-router.mjs.map +0 -1
- package/types/analogjs-router-server-actions.d.ts +0 -16
- package/types/analogjs-router-server.d.ts +0 -28
- package/types/analogjs-router-tokens.d.ts +0 -22
- package/types/analogjs-router.d.ts +0 -268
|
@@ -1,39 +1,335 @@
|
|
|
1
|
+
import { getRequestURL, readBody, readFormData, toRequest } from "nitro/h3";
|
|
2
|
+
//#region packages/router/server/actions/src/actions.ts
|
|
1
3
|
function fail(status, errors) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
},
|
|
7
|
-
});
|
|
4
|
+
return new Response(JSON.stringify(errors), {
|
|
5
|
+
status,
|
|
6
|
+
headers: { "X-Analog-Errors": "true" }
|
|
7
|
+
});
|
|
8
8
|
}
|
|
9
9
|
function json(data, config) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
...config,
|
|
15
|
-
});
|
|
10
|
+
return new Response(JSON.stringify(data), {
|
|
11
|
+
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
12
|
+
...config
|
|
13
|
+
});
|
|
16
14
|
}
|
|
17
15
|
function redirect(url, config = 302) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
if (typeof config === "number") return new Response(null, {
|
|
17
|
+
status: config,
|
|
18
|
+
headers: { Location: `${url}` }
|
|
19
|
+
});
|
|
20
|
+
return new Response(null, {
|
|
21
|
+
headers: { Location: `${url}` },
|
|
22
|
+
...config
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region packages/router/server/actions/src/parse-request-data.ts
|
|
27
|
+
function appendEntry(target, key, value) {
|
|
28
|
+
const existingValue = target[key];
|
|
29
|
+
if (existingValue === void 0) {
|
|
30
|
+
target[key] = value;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(existingValue)) {
|
|
34
|
+
existingValue.push(value);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
target[key] = [existingValue, value];
|
|
38
|
+
}
|
|
39
|
+
function getRequest(event) {
|
|
40
|
+
const maybeRequest = event.request;
|
|
41
|
+
if (maybeRequest) return maybeRequest;
|
|
42
|
+
return toRequest(event);
|
|
43
|
+
}
|
|
44
|
+
function getContentType(event) {
|
|
45
|
+
return getRequest(event).headers.get("content-type") ?? event.headers.get("content-type") ?? event.headers.get("Content-Type") ?? "";
|
|
46
|
+
}
|
|
47
|
+
function isJsonContentType(contentType) {
|
|
48
|
+
const mimeType = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
49
|
+
return mimeType === "application/json" || mimeType.endsWith("+json");
|
|
50
|
+
}
|
|
51
|
+
function isFormContentType(contentType) {
|
|
52
|
+
return contentType.includes("multipart/form-data") || contentType.includes("application/x-www-form-urlencoded");
|
|
53
|
+
}
|
|
54
|
+
function parseSearchParams(searchParams) {
|
|
55
|
+
const result = {};
|
|
56
|
+
searchParams.forEach((value, key) => {
|
|
57
|
+
appendEntry(result, key, value);
|
|
58
|
+
});
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
function parseFormData(formData) {
|
|
62
|
+
const result = {};
|
|
63
|
+
formData.forEach((value, key) => {
|
|
64
|
+
appendEntry(result, key, value);
|
|
65
|
+
});
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
async function parseRequestData(event) {
|
|
69
|
+
const request = getRequest(event);
|
|
70
|
+
const httpEvent = event;
|
|
71
|
+
const h3Event = event;
|
|
72
|
+
const method = event.method.toUpperCase();
|
|
73
|
+
if (method === "GET" || method === "HEAD") return parseSearchParams(new URL(request.url, "http://localhost").searchParams);
|
|
74
|
+
const contentType = getContentType(event);
|
|
75
|
+
if (isJsonContentType(contentType)) try {
|
|
76
|
+
return await readBody(httpEvent) ?? {};
|
|
77
|
+
} catch {
|
|
78
|
+
try {
|
|
79
|
+
return await request.json();
|
|
80
|
+
} catch {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (isFormContentType(contentType)) try {
|
|
85
|
+
return parseFormData(await readFormData(h3Event));
|
|
86
|
+
} catch {
|
|
87
|
+
if (typeof request.formData === "function") return parseFormData(await request.formData());
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
return await readBody(httpEvent) ?? {};
|
|
92
|
+
} catch {
|
|
93
|
+
try {
|
|
94
|
+
return await request.json();
|
|
95
|
+
} catch {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region packages/router/server/actions/src/validate.ts
|
|
102
|
+
/**
|
|
103
|
+
* Validates unknown input against a Standard Schema.
|
|
104
|
+
*
|
|
105
|
+
* Handles both sync and async `validate` implementations — the Standard
|
|
106
|
+
* Schema spec allows either return shape.
|
|
107
|
+
*/
|
|
108
|
+
async function validateWithSchema(schema, data) {
|
|
109
|
+
return schema["~standard"].validate(data);
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region packages/router/server/actions/src/define-action.ts
|
|
113
|
+
/**
|
|
114
|
+
* Creates a server action handler with Standard Schema input validation.
|
|
115
|
+
*
|
|
116
|
+
* Parses the request body (JSON or FormData) and validates it against the
|
|
117
|
+
* provided schema before invoking the handler. On validation failure,
|
|
118
|
+
* returns `fail(422, issues)` with `StandardSchemaV1.Issue[]`.
|
|
119
|
+
* Repeated form fields are preserved as arrays instead of being collapsed
|
|
120
|
+
* to the last value.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* import { defineAction, json } from '@analogjs/router/server/actions';
|
|
125
|
+
* import * as v from 'valibot';
|
|
126
|
+
*
|
|
127
|
+
* const Schema = v.object({
|
|
128
|
+
* email: v.pipe(v.string(), v.email()),
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* export const action = defineAction({
|
|
132
|
+
* schema: Schema,
|
|
133
|
+
* handler: async ({ data }) => {
|
|
134
|
+
* // data is typed as { email: string }
|
|
135
|
+
* return json({ ok: true });
|
|
136
|
+
* },
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
function defineAction(options) {
|
|
141
|
+
function getParams(params) {
|
|
142
|
+
return params ?? {};
|
|
143
|
+
}
|
|
144
|
+
return async (ctx) => {
|
|
145
|
+
const rawParams = getParams(ctx.params);
|
|
146
|
+
if (options.params) {
|
|
147
|
+
const paramsResult = await validateWithSchema(options.params, rawParams);
|
|
148
|
+
if (paramsResult.issues) return fail(422, paramsResult.issues);
|
|
149
|
+
return handleValidatedRequest(ctx, options, paramsResult.value);
|
|
150
|
+
}
|
|
151
|
+
return handleValidatedRequest(ctx, options, rawParams);
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async function handleValidatedRequest(ctx, options, params) {
|
|
155
|
+
const body = await parseRequestData(ctx.event);
|
|
156
|
+
let data = body;
|
|
157
|
+
if (options.schema) {
|
|
158
|
+
const result = await validateWithSchema(options.schema, body);
|
|
159
|
+
if (result.issues) return fail(422, result.issues);
|
|
160
|
+
data = result.value;
|
|
161
|
+
}
|
|
162
|
+
return options.handler({
|
|
163
|
+
data,
|
|
164
|
+
params,
|
|
165
|
+
req: ctx.req,
|
|
166
|
+
res: ctx.res,
|
|
167
|
+
fetch: ctx.fetch,
|
|
168
|
+
event: ctx.event
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region packages/router/server/actions/src/define-server-route.ts
|
|
173
|
+
function isDevEnvironment() {
|
|
174
|
+
return typeof process !== "undefined" && (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test");
|
|
175
|
+
}
|
|
176
|
+
function warnOnOutputIssues(issues) {
|
|
177
|
+
console.warn(`[analog] Server route output validation failed:\n` + issues.map((i) => {
|
|
178
|
+
const path = i.path ? ` at "${i.path.map((p) => typeof p === "object" ? p.key : p).join(".")}"` : "";
|
|
179
|
+
return ` - ${i.message}${path}`;
|
|
180
|
+
}).join("\n"));
|
|
181
|
+
}
|
|
182
|
+
function getRequestUrl(event) {
|
|
183
|
+
try {
|
|
184
|
+
return getRequestURL(event).href;
|
|
185
|
+
} catch {
|
|
186
|
+
return event.request.url;
|
|
187
|
+
}
|
|
32
188
|
}
|
|
33
|
-
|
|
34
189
|
/**
|
|
35
|
-
|
|
36
|
-
|
|
190
|
+
* Creates an h3-compatible event handler with Standard Schema validation.
|
|
191
|
+
*
|
|
192
|
+
* - `input` schema validates the request body (POST/PUT/PATCH) or query
|
|
193
|
+
* params (GET). Returns 422 with `StandardSchemaV1.Issue[]` on failure.
|
|
194
|
+
* - `output` schema validates the response in development only (stripped
|
|
195
|
+
* in production for zero overhead). Logs a warning on mismatch.
|
|
196
|
+
* - Plain return values are serialized with `json(...)`; raw `Response`
|
|
197
|
+
* objects are returned unchanged.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* import { defineServerRoute } from '@analogjs/router/server/actions';
|
|
202
|
+
* import * as v from 'valibot';
|
|
203
|
+
*
|
|
204
|
+
* const Input = v.object({
|
|
205
|
+
* name: v.pipe(v.string(), v.minLength(1)),
|
|
206
|
+
* email: v.pipe(v.string(), v.email()),
|
|
207
|
+
* });
|
|
208
|
+
* const Output = v.object({
|
|
209
|
+
* id: v.string(),
|
|
210
|
+
* name: v.string(),
|
|
211
|
+
* });
|
|
212
|
+
*
|
|
213
|
+
* export default defineServerRoute({
|
|
214
|
+
* input: Input,
|
|
215
|
+
* output: Output,
|
|
216
|
+
* handler: async ({ data }) => {
|
|
217
|
+
* const user = await db.users.create(data);
|
|
218
|
+
* return user;
|
|
219
|
+
* },
|
|
220
|
+
* });
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
function defineServerRoute(options) {
|
|
224
|
+
return (async (event) => {
|
|
225
|
+
const method = event.method.toUpperCase();
|
|
226
|
+
let data;
|
|
227
|
+
let query;
|
|
228
|
+
let body;
|
|
229
|
+
let params = event.context?.params ?? {};
|
|
230
|
+
if (options.params) {
|
|
231
|
+
const paramsResult = await validateWithSchema(options.params, params);
|
|
232
|
+
if (paramsResult.issues) return fail(422, paramsResult.issues);
|
|
233
|
+
params = paramsResult.value;
|
|
234
|
+
}
|
|
235
|
+
if (options.input) {
|
|
236
|
+
data = await parseRequestData(event);
|
|
237
|
+
const inputResult = await validateWithSchema(options.input, data);
|
|
238
|
+
if (inputResult.issues) return fail(422, inputResult.issues);
|
|
239
|
+
data = inputResult.value;
|
|
240
|
+
} else {
|
|
241
|
+
if (options.query) {
|
|
242
|
+
const url = new URL(getRequestUrl(event), "http://localhost");
|
|
243
|
+
const queryResult = await validateWithSchema(options.query, parseSearchParams(url.searchParams));
|
|
244
|
+
if (queryResult.issues) return fail(422, queryResult.issues);
|
|
245
|
+
query = queryResult.value;
|
|
246
|
+
}
|
|
247
|
+
if (options.body && method !== "GET" && method !== "HEAD") {
|
|
248
|
+
body = await parseRequestData(event);
|
|
249
|
+
const bodyResult = await validateWithSchema(options.body, body);
|
|
250
|
+
if (bodyResult.issues) return fail(422, bodyResult.issues);
|
|
251
|
+
body = bodyResult.value;
|
|
252
|
+
}
|
|
253
|
+
if (method === "GET" || method === "HEAD") data = query;
|
|
254
|
+
else if (body !== void 0) data = body;
|
|
255
|
+
else data = query;
|
|
256
|
+
}
|
|
257
|
+
const result = await options.handler({
|
|
258
|
+
data,
|
|
259
|
+
query,
|
|
260
|
+
body,
|
|
261
|
+
params,
|
|
262
|
+
event
|
|
263
|
+
});
|
|
264
|
+
if (result instanceof Response) return result;
|
|
265
|
+
if (options.output && isDevEnvironment()) {
|
|
266
|
+
const outputResult = await validateWithSchema(options.output, result);
|
|
267
|
+
if (outputResult.issues) warnOnOutputIssues(outputResult.issues);
|
|
268
|
+
}
|
|
269
|
+
return json(result);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region packages/router/server/actions/src/define-page-load.ts
|
|
274
|
+
/**
|
|
275
|
+
* Creates a typed page server load function with optional
|
|
276
|
+
* Standard Schema validation for route params and query.
|
|
277
|
+
*
|
|
278
|
+
* Follows the same validation patterns as `defineAction` and
|
|
279
|
+
* `defineServerRoute`: validates before invoking the handler,
|
|
280
|
+
* returns `fail(422, issues)` on validation failure.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```typescript
|
|
284
|
+
* // src/app/pages/users/[id].server.ts
|
|
285
|
+
* import { definePageLoad } from '@analogjs/router/server/actions';
|
|
286
|
+
* import * as v from 'valibot';
|
|
287
|
+
*
|
|
288
|
+
* export const routeParamsSchema = v.object({
|
|
289
|
+
* id: v.pipe(v.string(), v.regex(/^\d+$/)),
|
|
290
|
+
* });
|
|
291
|
+
*
|
|
292
|
+
* export const load = definePageLoad({
|
|
293
|
+
* params: routeParamsSchema,
|
|
294
|
+
* handler: async ({ params, fetch }) => {
|
|
295
|
+
* // params.id is typed as string (validated)
|
|
296
|
+
* const user = await fetch(`/api/users/${params.id}`);
|
|
297
|
+
* return user;
|
|
298
|
+
* },
|
|
299
|
+
* });
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
function definePageLoad(options) {
|
|
303
|
+
return async (ctx) => {
|
|
304
|
+
let params = ctx.params ?? {};
|
|
305
|
+
let requestUrl;
|
|
306
|
+
try {
|
|
307
|
+
requestUrl = getRequestURL(ctx.event).href;
|
|
308
|
+
} catch {
|
|
309
|
+
requestUrl = ctx.event.request.url;
|
|
310
|
+
}
|
|
311
|
+
let query = parseSearchParams(new URL(requestUrl, "http://localhost").searchParams);
|
|
312
|
+
if (options.params) {
|
|
313
|
+
const result = await validateWithSchema(options.params, params);
|
|
314
|
+
if (result.issues) return fail(422, result.issues);
|
|
315
|
+
params = result.value;
|
|
316
|
+
}
|
|
317
|
+
if (options.query) {
|
|
318
|
+
const result = await validateWithSchema(options.query, query);
|
|
319
|
+
if (result.issues) return fail(422, result.issues);
|
|
320
|
+
query = result.value;
|
|
321
|
+
}
|
|
322
|
+
return options.handler({
|
|
323
|
+
params,
|
|
324
|
+
query,
|
|
325
|
+
req: ctx.req,
|
|
326
|
+
res: ctx.res,
|
|
327
|
+
fetch: ctx.fetch,
|
|
328
|
+
event: ctx.event
|
|
329
|
+
});
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
333
|
+
export { defineAction, definePageLoad, defineServerRoute, fail, json, redirect, validateWithSchema };
|
|
37
334
|
|
|
38
|
-
|
|
39
|
-
//# sourceMappingURL=analogjs-router-server-actions.mjs.map
|
|
335
|
+
//# sourceMappingURL=analogjs-router-server-actions.mjs.map
|