@analogjs/router 3.0.0-alpha.56 → 3.0.0-alpha.57
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 +1 -334
- package/fesm2022/analogjs-router-tanstack-query-server.mjs +64 -15
- package/fesm2022/analogjs-router-tanstack-query-server.mjs.map +1 -1
- package/fesm2022/provide-analog-query.mjs +51 -2
- package/fesm2022/provide-analog-query.mjs.map +1 -1
- package/fesm2022/src.mjs +335 -0
- package/fesm2022/src.mjs.map +1 -0
- package/package.json +4 -4
- package/types/tanstack-query/server/src/define-page-load-queries.d.ts +66 -0
- package/types/tanstack-query/server/src/index.d.ts +2 -0
- package/types/tanstack-query/src/constants.d.ts +9 -0
- package/fesm2022/analogjs-router-server-actions.mjs.map +0 -1
|
@@ -1,335 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region packages/router/server/actions/src/actions.ts
|
|
3
|
-
function fail(status, errors) {
|
|
4
|
-
return new Response(JSON.stringify(errors), {
|
|
5
|
-
status,
|
|
6
|
-
headers: { "X-Analog-Errors": "true" }
|
|
7
|
-
});
|
|
8
|
-
}
|
|
9
|
-
function json(data, config) {
|
|
10
|
-
return new Response(JSON.stringify(data), {
|
|
11
|
-
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
12
|
-
...config
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
function redirect(url, config = 302) {
|
|
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
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
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
|
|
1
|
+
import { a as fail, i as validateWithSchema, n as defineServerRoute, o as json, r as defineAction, s as redirect, t as definePageLoad } from "./src.mjs";
|
|
333
2
|
export { defineAction, definePageLoad, defineServerRoute, fail, json, redirect, validateWithSchema };
|
|
334
|
-
|
|
335
|
-
//# sourceMappingURL=analogjs-router-server-actions.mjs.map
|
|
@@ -1,22 +1,71 @@
|
|
|
1
|
-
import { t as ANALOG_QUERY_STATE_KEY } from "./provide-analog-query.mjs";
|
|
2
|
-
import {
|
|
1
|
+
import { r as ANALOG_QUERIES_KEY, t as ANALOG_QUERY_STATE_KEY } from "./provide-analog-query.mjs";
|
|
2
|
+
import { t as definePageLoad } from "./src.mjs";
|
|
3
|
+
import { ApplicationRef, ENVIRONMENT_INITIALIZER, TransferState, inject, makeEnvironmentProviders } from "@angular/core";
|
|
4
|
+
import { filter, skipWhile, take } from "rxjs/operators";
|
|
3
5
|
import { QueryClient, dehydrate } from "@tanstack/angular-query-experimental";
|
|
4
|
-
import { BEFORE_APP_SERIALIZED } from "@angular/platform-server";
|
|
5
6
|
//#region packages/router/tanstack-query/src/provide-server-analog-query.ts
|
|
6
|
-
var SERVER_ANALOG_QUERY_PROVIDER = {
|
|
7
|
-
provide: BEFORE_APP_SERIALIZED,
|
|
8
|
-
multi: true,
|
|
9
|
-
useFactory: (queryClient, transferState) => {
|
|
10
|
-
return () => {
|
|
11
|
-
transferState.set(ANALOG_QUERY_STATE_KEY, dehydrate(queryClient));
|
|
12
|
-
};
|
|
13
|
-
},
|
|
14
|
-
deps: [QueryClient, TransferState]
|
|
15
|
-
};
|
|
16
7
|
function provideServerAnalogQuery() {
|
|
17
|
-
return makeEnvironmentProviders([
|
|
8
|
+
return makeEnvironmentProviders([{
|
|
9
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
10
|
+
multi: true,
|
|
11
|
+
useValue() {
|
|
12
|
+
const appRef = inject(ApplicationRef);
|
|
13
|
+
const queryClient = inject(QueryClient);
|
|
14
|
+
const transferState = inject(TransferState);
|
|
15
|
+
const subscription = appRef.isStable.pipe(skipWhile((stable) => stable), filter((stable) => stable), take(1)).subscribe(() => {
|
|
16
|
+
transferState.set(ANALOG_QUERY_STATE_KEY, dehydrate(queryClient));
|
|
17
|
+
});
|
|
18
|
+
appRef.onDestroy(() => subscription.unsubscribe());
|
|
19
|
+
}
|
|
20
|
+
}]);
|
|
18
21
|
}
|
|
19
22
|
//#endregion
|
|
20
|
-
|
|
23
|
+
//#region packages/router/tanstack-query/server/src/define-page-load-queries.ts
|
|
24
|
+
/**
|
|
25
|
+
* Page load helper that prefetches TanStack Query queries inside the
|
|
26
|
+
* `.server.ts` handler and ships the dehydrated cache alongside any
|
|
27
|
+
* additional data. The router-side hydrator in `provideAnalogQuery()`
|
|
28
|
+
* merges the dehydrated payload into the active `QueryClient` on
|
|
29
|
+
* `ResolveEnd`, so components reading the same query options see a
|
|
30
|
+
* warm cache on first render.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* // src/app/pages/posts.server.ts
|
|
35
|
+
* import { definePageLoadQueries } from '@analogjs/router/tanstack-query/server';
|
|
36
|
+
* import { queryOptions } from '@tanstack/angular-query-experimental';
|
|
37
|
+
*
|
|
38
|
+
* export const postsQuery = queryOptions({
|
|
39
|
+
* queryKey: ['posts'],
|
|
40
|
+
* queryFn: async ({ signal }) =>
|
|
41
|
+
* fetch('https://api.example.com/posts', { signal }).then((r) => r.json()),
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* export const load = definePageLoadQueries({
|
|
45
|
+
* handler: async ({ client }) => {
|
|
46
|
+
* await client.prefetchQuery(postsQuery);
|
|
47
|
+
* },
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function definePageLoadQueries(options) {
|
|
52
|
+
return definePageLoad({
|
|
53
|
+
params: options.params,
|
|
54
|
+
query: options.query,
|
|
55
|
+
handler: async (ctx) => {
|
|
56
|
+
const client = options.client?.() ?? new QueryClient();
|
|
57
|
+
const data = await options.handler({
|
|
58
|
+
...ctx,
|
|
59
|
+
client
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
[ANALOG_QUERIES_KEY]: dehydrate(client),
|
|
63
|
+
data
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { ANALOG_QUERIES_KEY, definePageLoadQueries, provideServerAnalogQuery };
|
|
21
70
|
|
|
22
71
|
//# sourceMappingURL=analogjs-router-tanstack-query-server.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analogjs-router-tanstack-query-server.mjs","names":[],"sources":["../../tanstack-query/src/provide-server-analog-query.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"analogjs-router-tanstack-query-server.mjs","names":[],"sources":["../../tanstack-query/src/provide-server-analog-query.ts","../../tanstack-query/server/src/define-page-load-queries.ts"],"sourcesContent":["import {\n ApplicationRef,\n ENVIRONMENT_INITIALIZER,\n TransferState,\n inject,\n makeEnvironmentProviders,\n} from '@angular/core';\nimport type { EnvironmentProviders } from '@angular/core';\nimport { filter, skipWhile, take } from 'rxjs/operators';\nimport { QueryClient, dehydrate } from '@tanstack/angular-query-experimental';\n\nimport { ANALOG_QUERY_STATE_KEY } from './provide-analog-query';\n\nexport function provideServerAnalogQuery(): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: ENVIRONMENT_INITIALIZER,\n multi: true,\n useValue() {\n // Dehydrate the QueryClient into `TransferState` once the app\n // becomes stable POST-RENDER on the server. `renderApplication`\n // awaits `whenStable()` itself before invoking\n // `BEFORE_APP_SERIALIZED` (which is where Angular's\n // `TRANSFER_STATE_SERIALIZATION_PROVIDERS` reads the store and\n // writes the `ng-state` script). Settling our write at the same\n // point — but ahead of Angular's own subscriber — lets the\n // dehydrated cache land in `TransferState` before the serializer\n // snapshots it, so component-issued queries make it to the client\n // without depending on provider declaration order.\n //\n // `skipWhile(stable => stable)` waits past the initial \"no\n // pending tasks yet\" emission `BehaviorSubject` semantics give\n // us at subscribe time; the next stable transition is the\n // post-render one with all queries settled.\n const appRef = inject(ApplicationRef);\n const queryClient = inject(QueryClient);\n const transferState = inject(TransferState);\n\n const subscription = appRef.isStable\n .pipe(\n skipWhile((stable) => stable),\n filter((stable) => stable),\n take(1),\n )\n .subscribe(() => {\n transferState.set(ANALOG_QUERY_STATE_KEY, dehydrate(queryClient));\n });\n\n appRef.onDestroy(() => subscription.unsubscribe());\n },\n },\n ]);\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { QueryClient, dehydrate } from '@tanstack/angular-query-experimental';\nimport type { DehydratedState } from '@tanstack/angular-query-experimental';\nimport type { H3Event, H3EventContext } from 'nitro/h3';\nimport type { $Fetch } from 'nitro/types';\n\nimport {\n definePageLoad,\n type PageLoadContext,\n} from '../../../server/actions/src/index.js';\nimport { ANALOG_QUERIES_KEY } from '../../src/constants.js';\n\nexport { ANALOG_QUERIES_KEY } from '../../src/constants.js';\n\ntype NodeContext = NonNullable<H3Event['node']>;\ntype OptionalSchema = StandardSchemaV1 | undefined;\n\nexport interface PageLoadQueriesResult<TData> {\n __analogQueries: DehydratedState;\n data: TData;\n}\n\nexport interface DefinePageLoadQueriesOptions<\n TParamsSchema extends OptionalSchema,\n TQuerySchema extends OptionalSchema,\n TData,\n> {\n params?: TParamsSchema;\n query?: TQuerySchema;\n /**\n * Optional QueryClient factory. Defaults to `new QueryClient()`.\n * Override to set `defaultOptions` (e.g. `queries: { staleTime: Infinity }`).\n */\n client?: () => QueryClient;\n /**\n * Handler receives the standard PageLoadContext plus a per-request\n * QueryClient. Use `client.prefetchQuery` / `ensureQueryData` /\n * `prefetchInfiniteQuery` to warm the cache; the dehydrated client\n * is returned as `__analogQueries` on the load result. Anything you\n * return from the handler becomes `data` on the same result.\n */\n handler: (\n ctx: PageLoadContext<TParamsSchema, TQuerySchema> & { client: QueryClient },\n ) => Promise<TData> | TData;\n}\n\n/**\n * Page load helper that prefetches TanStack Query queries inside the\n * `.server.ts` handler and ships the dehydrated cache alongside any\n * additional data. The router-side hydrator in `provideAnalogQuery()`\n * merges the dehydrated payload into the active `QueryClient` on\n * `ResolveEnd`, so components reading the same query options see a\n * warm cache on first render.\n *\n * @example\n * ```ts\n * // src/app/pages/posts.server.ts\n * import { definePageLoadQueries } from '@analogjs/router/tanstack-query/server';\n * import { queryOptions } from '@tanstack/angular-query-experimental';\n *\n * export const postsQuery = queryOptions({\n * queryKey: ['posts'],\n * queryFn: async ({ signal }) =>\n * fetch('https://api.example.com/posts', { signal }).then((r) => r.json()),\n * });\n *\n * export const load = definePageLoadQueries({\n * handler: async ({ client }) => {\n * await client.prefetchQuery(postsQuery);\n * },\n * });\n * ```\n */\nexport function definePageLoadQueries<\n TParamsSchema extends OptionalSchema = undefined,\n TQuerySchema extends OptionalSchema = undefined,\n TData = void,\n>(\n options: DefinePageLoadQueriesOptions<TParamsSchema, TQuerySchema, TData>,\n): (ctx: {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n}) => Promise<PageLoadQueriesResult<TData> | Response> {\n return definePageLoad({\n params: options.params,\n query: options.query,\n handler: async (ctx): Promise<PageLoadQueriesResult<TData>> => {\n const client = options.client?.() ?? new QueryClient();\n const data = await options.handler({ ...ctx, client });\n return {\n [ANALOG_QUERIES_KEY]: dehydrate(client),\n data,\n };\n },\n });\n}\n"],"mappings":";;;;;;AAaA,SAAgB,2BAAiD;AAC/D,QAAO,yBACL,CACE;EACO,SAAA;EACP,OAAW;EAgBH,WAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACuCvB,SAAgB,sBAKd,SAOqD;AACrD,QAAO,eAAe;EACpB,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,SAAS,OAAO,QAA+C;GACvD,MAAS,SAAQ,QAAA,UAAkB,IAAA,IAAA,aAAa;GAChD,MAAO,OAAM,MAAQ,QAAQ,QAAA;IAAA,GAAA;IAAA;IAAA,CAAA;AAAK,UAAA;KAAK,qBAAA,UAAA,OAAA;IAAS;IAC/C;;EAEL,CAAA"}
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ResolveEnd, Router } from "@angular/router";
|
|
2
|
+
import { DestroyRef, ENVIRONMENT_INITIALIZER, PLATFORM_ID, TransferState, inject, makeEnvironmentProviders, makeStateKey } from "@angular/core";
|
|
3
|
+
import { isPlatformServer } from "@angular/common";
|
|
2
4
|
import { QueryClient, hydrate } from "@tanstack/angular-query-experimental";
|
|
5
|
+
//#region packages/router/tanstack-query/src/constants.ts
|
|
6
|
+
/**
|
|
7
|
+
* Route-data key under `ActivatedRoute.data['load']` that holds the
|
|
8
|
+
* `DehydratedState` produced by `definePageLoadQueries`. The Router-
|
|
9
|
+
* events hydrator in `provideAnalogQuery()` looks for this key on
|
|
10
|
+
* `ResolveEnd` and merges the dehydrated payload into the active
|
|
11
|
+
* `QueryClient`, so component-issued queries hit a warm cache on
|
|
12
|
+
* first render.
|
|
13
|
+
*/
|
|
14
|
+
var ANALOG_QUERIES_KEY = "__analogQueries";
|
|
15
|
+
//#endregion
|
|
3
16
|
//#region packages/router/tanstack-query/src/provide-analog-query.ts
|
|
4
17
|
var ANALOG_QUERY_STATE_KEY = makeStateKey("analog_query_state");
|
|
5
18
|
function provideAnalogQuery() {
|
|
@@ -15,9 +28,45 @@ function provideAnalogQuery() {
|
|
|
15
28
|
transferState.remove(ANALOG_QUERY_STATE_KEY);
|
|
16
29
|
}
|
|
17
30
|
}
|
|
31
|
+
}, {
|
|
32
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
33
|
+
multi: true,
|
|
34
|
+
useValue() {
|
|
35
|
+
const router = inject(Router, { optional: true });
|
|
36
|
+
if (!router) return;
|
|
37
|
+
const client = inject(QueryClient);
|
|
38
|
+
const destroyRef = inject(DestroyRef);
|
|
39
|
+
const transferState = isPlatformServer(inject(PLATFORM_ID)) ? inject(TransferState) : null;
|
|
40
|
+
const subscription = router.events.subscribe((event) => {
|
|
41
|
+
if (event instanceof ResolveEnd) mergeRouteSnapshot(event.state.root, client, transferState);
|
|
42
|
+
});
|
|
43
|
+
destroyRef.onDestroy(() => subscription.unsubscribe());
|
|
44
|
+
}
|
|
18
45
|
}]);
|
|
19
46
|
}
|
|
47
|
+
function mergeRouteSnapshot(snapshot, client, transferState) {
|
|
48
|
+
const load = snapshot.data?.["load"];
|
|
49
|
+
if (load && typeof load === "object" && "__analogQueries" in load) {
|
|
50
|
+
const dehydrated = load[ANALOG_QUERIES_KEY];
|
|
51
|
+
if (dehydrated) {
|
|
52
|
+
hydrate(client, dehydrated);
|
|
53
|
+
if (transferState) {
|
|
54
|
+
const existing = transferState.get(ANALOG_QUERY_STATE_KEY, null);
|
|
55
|
+
transferState.set(ANALOG_QUERY_STATE_KEY, existing ? mergeDehydrated(existing, dehydrated) : dehydrated);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const child of snapshot.children) mergeRouteSnapshot(child, client, transferState);
|
|
60
|
+
}
|
|
61
|
+
function mergeDehydrated(base, next) {
|
|
62
|
+
const queriesByHash = new Map(base.queries.map((query) => [query.queryHash, query]));
|
|
63
|
+
for (const query of next.queries) queriesByHash.set(query.queryHash, query);
|
|
64
|
+
return {
|
|
65
|
+
mutations: [...base.mutations, ...next.mutations],
|
|
66
|
+
queries: [...queriesByHash.values()]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
20
69
|
//#endregion
|
|
21
|
-
export { provideAnalogQuery as n, ANALOG_QUERY_STATE_KEY as t };
|
|
70
|
+
export { provideAnalogQuery as n, ANALOG_QUERIES_KEY as r, ANALOG_QUERY_STATE_KEY as t };
|
|
22
71
|
|
|
23
72
|
//# sourceMappingURL=provide-analog-query.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provide-analog-query.mjs","names":[],"sources":["../../tanstack-query/src/provide-analog-query.ts"],"sourcesContent":["import {\n ENVIRONMENT_INITIALIZER,\n TransferState,\n inject,\n makeEnvironmentProviders,\n makeStateKey,\n} from '@angular/core';\nimport type { EnvironmentProviders, StateKey } from '@angular/core';\nimport { QueryClient, hydrate } from '@tanstack/angular-query-experimental';\nimport type { DehydratedState } from '@tanstack/angular-query-experimental';\n\nexport const ANALOG_QUERY_STATE_KEY: StateKey<DehydratedState> =\n makeStateKey<DehydratedState>('analog_query_state');\n\nexport function provideAnalogQuery(): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: ENVIRONMENT_INITIALIZER,\n multi: true,\n useValue() {\n if (import.meta.env.SSR) {\n return;\n }\n\n const transferState = inject(TransferState);\n const client = inject(QueryClient);\n const dehydratedState = transferState.get<DehydratedState | null>(\n ANALOG_QUERY_STATE_KEY,\n null,\n );\n\n if (dehydratedState) {\n hydrate(client, dehydratedState);\n transferState.remove(ANALOG_QUERY_STATE_KEY);\n }\n },\n },\n ]);\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"provide-analog-query.mjs","names":[],"sources":["../../tanstack-query/src/constants.ts","../../tanstack-query/src/provide-analog-query.ts"],"sourcesContent":["/**\n * Route-data key under `ActivatedRoute.data['load']` that holds the\n * `DehydratedState` produced by `definePageLoadQueries`. The Router-\n * events hydrator in `provideAnalogQuery()` looks for this key on\n * `ResolveEnd` and merges the dehydrated payload into the active\n * `QueryClient`, so component-issued queries hit a warm cache on\n * first render.\n */\nexport const ANALOG_QUERIES_KEY: '__analogQueries' = '__analogQueries';\n","import {\n DestroyRef,\n ENVIRONMENT_INITIALIZER,\n PLATFORM_ID,\n TransferState,\n inject,\n makeEnvironmentProviders,\n makeStateKey,\n} from '@angular/core';\nimport type { EnvironmentProviders, StateKey } from '@angular/core';\nimport { isPlatformServer } from '@angular/common';\nimport { ResolveEnd, Router } from '@angular/router';\nimport type { ActivatedRouteSnapshot } from '@angular/router';\nimport { QueryClient, hydrate } from '@tanstack/angular-query-experimental';\nimport type { DehydratedState } from '@tanstack/angular-query-experimental';\n\nimport { ANALOG_QUERIES_KEY } from './constants.js';\n\nexport const ANALOG_QUERY_STATE_KEY: StateKey<DehydratedState> =\n makeStateKey<DehydratedState>('analog_query_state');\n\nexport function provideAnalogQuery(): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: ENVIRONMENT_INITIALIZER,\n multi: true,\n useValue() {\n if (import.meta.env.SSR) {\n return;\n }\n\n const transferState = inject(TransferState);\n const client = inject(QueryClient);\n const dehydratedState = transferState.get<DehydratedState | null>(\n ANALOG_QUERY_STATE_KEY,\n null,\n );\n\n if (dehydratedState) {\n hydrate(client, dehydratedState);\n transferState.remove(ANALOG_QUERY_STATE_KEY);\n }\n },\n },\n {\n provide: ENVIRONMENT_INITIALIZER,\n multi: true,\n useValue() {\n const router = inject(Router, { optional: true });\n if (!router) {\n return;\n }\n\n const client = inject(QueryClient);\n const destroyRef = inject(DestroyRef);\n // On the server, also mirror any dehydrated load payloads into\n // `TransferState` so Angular's own serializer can include them in\n // the `ng-state` script — we can't rely on\n // `provideServerAnalogQuery()`'s `BEFORE_APP_SERIALIZED` running\n // before Angular's `TRANSFER_STATE_SERIALIZATION_PROVIDERS`.\n const transferState = isPlatformServer(inject(PLATFORM_ID))\n ? inject(TransferState)\n : null;\n\n const subscription = router.events.subscribe((event) => {\n if (event instanceof ResolveEnd) {\n mergeRouteSnapshot(event.state.root, client, transferState);\n }\n });\n\n destroyRef.onDestroy(() => subscription.unsubscribe());\n },\n },\n ]);\n}\n\nfunction mergeRouteSnapshot(\n snapshot: ActivatedRouteSnapshot,\n client: QueryClient,\n transferState: TransferState | null,\n): void {\n const load = snapshot.data?.['load'];\n if (load && typeof load === 'object' && ANALOG_QUERIES_KEY in load) {\n const dehydrated = (load as Record<string, unknown>)[ANALOG_QUERIES_KEY] as\n | DehydratedState\n | undefined;\n if (dehydrated) {\n hydrate(client, dehydrated);\n if (transferState) {\n const existing = transferState.get<DehydratedState | null>(\n ANALOG_QUERY_STATE_KEY,\n null,\n );\n transferState.set(\n ANALOG_QUERY_STATE_KEY,\n existing ? mergeDehydrated(existing, dehydrated) : dehydrated,\n );\n }\n }\n }\n for (const child of snapshot.children) {\n mergeRouteSnapshot(child, client, transferState);\n }\n}\n\nfunction mergeDehydrated(\n base: DehydratedState,\n next: DehydratedState,\n): DehydratedState {\n // Last-writer-wins on duplicate `queryHash`: child route resolves run\n // after parent resolves, so the later entry is the fresher one and\n // matches `hydrate()`'s own newer-wins semantics for the QueryClient.\n const queriesByHash = new Map(\n base.queries.map((query) => [query.queryHash, query]),\n );\n for (const query of next.queries) {\n queriesByHash.set(query.queryHash, query);\n }\n return {\n mutations: [...base.mutations, ...next.mutations],\n queries: [...queriesByHash.values()],\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAQA,IAAa,qBAAwC;;;ACUrD,IAAa,yBACX,aAA8B,qBAAqB;AAErD,SAAgB,qBAA2C;AACzD,QAAO,yBACL,CACE;EACO,SAAA;EACP,OAAW;EACL,WAAgB;GAKd,MAAS,gBAAmB,OAAA,cAAA;GAC5B,MAAA,SAAkB,OAAA,YACtB;GAIE,MAAA,kBAAiB,cAAA,IAAA,wBAAA,KAAA;AACX,OAAQ,iBAAgB;AAClB,YAAO,QAAA,gBAAuB;;;;EAKhD,EACA;EACA,SAAW;EACH,OAAS;EACV,WAAQ;GACX,MAAA,SAAA,OAAA,QAAA,EAAA,UAAA,MAAA,CAAA;eAGa;GAOT,MAAA,SAAgB,OAAA,YAAwB;GAIxC,MAAA,aAAsB,OAAO,WAAW;iEASlD,OAAA,cAAA,GAAA;GAGK,MAAA,eACP,OACA,OACA,WACM,UAAA;AACO,QAAS,iBAAO,WACV,oBAAqB,MAAA,MAAA,MAAA,QAA4B,cAAA;KAIlD;AACN,cAAQ,gBAAW,aAAA,aAAA,CAAA;;EAEzB,CAIA,CAAA;;;;AAON,KAAK,QAAM,OAAS,SAAS,YAAA,qBAAU,MAAA;EACrC,MAAA,aAAmB,KAAO;;;AAIrB,OAAA,eAEP;IAKM,MAAA,WACJ,cAAa,IAAK,wBAAiB,KAAW;AAErC,kBAAc,IAAA,wBAAS,WAAA,gBAAA,UAAA,WAAA,GAAA,WAAA;;;;AAIhC,MAAA,MAAY,SAAQ,SAAW,SAC/B,oBAAa,OAAc,QAAQ,cAAA"}
|
package/fesm2022/src.mjs
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { getRequestURL, readBody, readFormData, toRequest } from "nitro/h3";
|
|
2
|
+
//#region packages/router/server/actions/src/actions.ts
|
|
3
|
+
function fail(status, errors) {
|
|
4
|
+
return new Response(JSON.stringify(errors), {
|
|
5
|
+
status,
|
|
6
|
+
headers: { "X-Analog-Errors": "true" }
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
function json(data, config) {
|
|
10
|
+
return new Response(JSON.stringify(data), {
|
|
11
|
+
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
12
|
+
...config
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function redirect(url, config = 302) {
|
|
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
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
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 { fail as a, validateWithSchema as i, defineServerRoute as n, json as o, defineAction as r, redirect as s, definePageLoad as t };
|
|
334
|
+
|
|
335
|
+
//# sourceMappingURL=src.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src.mjs","names":[],"sources":["../../server/actions/src/actions.ts","../../server/actions/src/parse-request-data.ts","../../server/actions/src/validate.ts","../../server/actions/src/define-action.ts","../../server/actions/src/define-server-route.ts","../../server/actions/src/define-page-load.ts"],"sourcesContent":["import type { H3Event, H3EventContext } from 'nitro/h3';\nimport type { $Fetch } from 'nitro/types';\nimport type { NodeContext } from '../../../src/lib/route-types.js';\n\nexport type PageServerAction = {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n};\n\nexport function fail<T = object>(status: number, errors: T): Response {\n return new Response(JSON.stringify(errors), {\n status,\n headers: {\n 'X-Analog-Errors': 'true',\n },\n });\n}\n\nexport function json<T = object>(data: T, config?: ResponseInit): Response {\n return new Response(JSON.stringify(data), {\n headers: {\n 'Content-Type': 'application/json; charset=utf-8',\n },\n ...config,\n });\n}\n\nexport function redirect(\n url: string,\n config: number | ResponseInit = 302,\n): Response {\n if (typeof config === 'number') {\n return new Response(null, {\n status: config,\n headers: {\n Location: `${url}`,\n },\n });\n }\n\n return new Response(null, {\n headers: {\n Location: `${url}`,\n },\n ...config,\n });\n}\n","import { readBody, readFormData, toRequest as h3ToRequest } from 'nitro/h3';\n\ntype RequestEntryValue = string | File;\ntype ParsedRequestValue = RequestEntryValue | RequestEntryValue[];\n\nfunction appendEntry(\n target: Record<string, ParsedRequestValue>,\n key: string,\n value: RequestEntryValue,\n) {\n const existingValue = target[key];\n\n if (existingValue === undefined) {\n target[key] = value;\n return;\n }\n\n if (Array.isArray(existingValue)) {\n existingValue.push(value);\n return;\n }\n\n target[key] = [existingValue, value];\n}\n\nfunction getRequest(event: { method: string; headers: Headers }): Request {\n const maybeRequest = (event as unknown as { request?: Request }).request;\n if (maybeRequest) {\n return maybeRequest;\n }\n return h3ToRequest(event as Parameters<typeof h3ToRequest>[0]);\n}\n\nfunction getContentType(event: { method: string; headers: Headers }): string {\n const request = getRequest(event);\n\n return (\n request.headers.get('content-type') ??\n event.headers.get('content-type') ??\n event.headers.get('Content-Type') ??\n ''\n );\n}\n\nfunction isJsonContentType(contentType: string): boolean {\n const mimeType = contentType.split(';', 1)[0]?.trim().toLowerCase() ?? '';\n return mimeType === 'application/json' || mimeType.endsWith('+json');\n}\n\nfunction isFormContentType(contentType: string): boolean {\n return (\n contentType.includes('multipart/form-data') ||\n contentType.includes('application/x-www-form-urlencoded')\n );\n}\n\nexport function parseSearchParams(\n searchParams: URLSearchParams,\n): Record<string, ParsedRequestValue> {\n const result: Record<string, ParsedRequestValue> = {};\n searchParams.forEach((value, key) => {\n appendEntry(result, key, value);\n });\n return result;\n}\n\nexport function parseFormData(\n formData: FormData,\n): Record<string, ParsedRequestValue> {\n const result: Record<string, ParsedRequestValue> = {};\n formData.forEach((value, key) => {\n appendEntry(result, key, value as RequestEntryValue);\n });\n return result;\n}\n\nexport async function parseRequestData(event: {\n method: string;\n headers: Headers;\n}): Promise<unknown> {\n const request = getRequest(event);\n const httpEvent = event as unknown as Parameters<typeof readBody>[0];\n const h3Event = event as unknown as Parameters<typeof readFormData>[0];\n const method = event.method.toUpperCase();\n\n if (method === 'GET' || method === 'HEAD') {\n const url = new URL(request.url, 'http://localhost');\n return parseSearchParams(url.searchParams);\n }\n\n const contentType = getContentType(event);\n\n if (isJsonContentType(contentType)) {\n try {\n return (await readBody(httpEvent)) ?? {};\n } catch {\n try {\n return await request.json();\n } catch {\n return {};\n }\n }\n }\n\n if (isFormContentType(contentType)) {\n try {\n return parseFormData(await readFormData(h3Event));\n } catch {\n if (typeof request.formData === 'function') {\n return parseFormData(await request.formData());\n }\n\n return {};\n }\n }\n\n try {\n return (await readBody(httpEvent)) ?? {};\n } catch {\n try {\n return await request.json();\n } catch {\n return {};\n }\n }\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n/**\n * Validates unknown input against a Standard Schema.\n *\n * Handles both sync and async `validate` implementations — the Standard\n * Schema spec allows either return shape.\n */\nexport async function validateWithSchema<T extends StandardSchemaV1>(\n schema: T,\n data: unknown,\n): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<T>>> {\n return schema['~standard'].validate(data);\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { H3Event, H3EventContext } from 'nitro/h3';\nimport type { $Fetch } from 'nitro/types';\nimport { fail } from './actions';\nimport { parseRequestData } from './parse-request-data';\nimport { validateWithSchema } from './validate';\n\ntype NodeContext = NonNullable<H3Event['node']>;\ntype OptionalSchema = StandardSchemaV1 | undefined;\ntype InferSchema<\n TSchema extends OptionalSchema,\n TFallback,\n> = TSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TSchema>\n : TFallback;\n\nexport interface DefineActionContext<\n TSchema extends OptionalSchema = undefined,\n TParamsSchema extends OptionalSchema = undefined,\n> {\n data: InferSchema<TSchema, Record<string, unknown>>;\n params: InferSchema<TParamsSchema, H3EventContext['params']>;\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n}\n\nexport interface DefineActionOptions<\n TSchema extends OptionalSchema = undefined,\n TParamsSchema extends OptionalSchema = undefined,\n> {\n schema?: TSchema;\n params?: TParamsSchema;\n handler: (\n context: DefineActionContext<TSchema, TParamsSchema>,\n ) => Promise<Response> | Response;\n}\n\n/**\n * Creates a server action handler with Standard Schema input validation.\n *\n * Parses the request body (JSON or FormData) and validates it against the\n * provided schema before invoking the handler. On validation failure,\n * returns `fail(422, issues)` with `StandardSchemaV1.Issue[]`.\n * Repeated form fields are preserved as arrays instead of being collapsed\n * to the last value.\n *\n * @example\n * ```typescript\n * import { defineAction, json } from '@analogjs/router/server/actions';\n * import * as v from 'valibot';\n *\n * const Schema = v.object({\n * email: v.pipe(v.string(), v.email()),\n * });\n *\n * export const action = defineAction({\n * schema: Schema,\n * handler: async ({ data }) => {\n * // data is typed as { email: string }\n * return json({ ok: true });\n * },\n * });\n * ```\n */\nexport function defineAction<\n TSchema extends OptionalSchema = undefined,\n TParamsSchema extends OptionalSchema = undefined,\n>(options: DefineActionOptions<TSchema, TParamsSchema>) {\n type Params = InferSchema<TParamsSchema, H3EventContext['params']>;\n\n function getParams(\n params: H3EventContext['params'],\n ): Params | Record<string, never> {\n return (params ?? {}) as Params | Record<string, never>;\n }\n\n return async (ctx: {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n }): Promise<Response> => {\n const rawParams = getParams(ctx.params);\n\n if (options.params) {\n const paramsResult = await validateWithSchema(options.params, rawParams);\n if (paramsResult.issues) {\n return fail(422, paramsResult.issues);\n }\n return handleValidatedRequest(ctx, options, paramsResult.value as Params);\n }\n\n return handleValidatedRequest(ctx, options, rawParams as Params);\n };\n}\n\nasync function handleValidatedRequest<\n TSchema extends OptionalSchema = undefined,\n TParamsSchema extends OptionalSchema = undefined,\n>(\n ctx: {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n },\n options: DefineActionOptions<TSchema, TParamsSchema>,\n params: InferSchema<TParamsSchema, H3EventContext['params']>,\n) {\n type Data = InferSchema<TSchema, Record<string, unknown>>;\n const body = await parseRequestData(ctx.event);\n\n let data: unknown = body;\n\n if (options.schema) {\n const result = await validateWithSchema(options.schema, body);\n if (result.issues) {\n return fail(422, result.issues);\n }\n data = result.value;\n }\n\n return options.handler({\n data: data as Data,\n params,\n req: ctx.req,\n res: ctx.res,\n fetch: ctx.fetch,\n event: ctx.event,\n });\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { H3Event } from 'nitro/h3';\nimport { getRequestURL } from 'nitro/h3';\nimport { fail, json } from './actions';\nimport { parseRequestData, parseSearchParams } from './parse-request-data';\nimport { validateWithSchema } from './validate';\n\nexport type DefineServerRouteResult = Response | unknown;\n\nexport interface ServerRouteHandler<\n TQuery = unknown,\n TBody = unknown,\n TResult = unknown,\n> {\n (event: H3Event): Promise<Response>;\n readonly _types: {\n readonly query: TQuery;\n readonly body: TBody;\n readonly result: TResult;\n };\n}\n\nexport type InferRouteQuery<T> =\n T extends ServerRouteHandler<infer Q, any, any> ? Q : never;\nexport type InferRouteBody<T> =\n T extends ServerRouteHandler<any, infer B, any> ? B : never;\nexport type InferRouteResult<T> =\n T extends ServerRouteHandler<any, any, infer R>\n ? Exclude<R, Response>\n : never;\n\ntype OptionalSchema = StandardSchemaV1 | undefined;\ntype InferSchema<\n TSchema extends OptionalSchema,\n TFallback = unknown,\n> = TSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TSchema>\n : TFallback;\ntype ResolveDataSchema<\n TInput extends OptionalSchema,\n TQuery extends OptionalSchema,\n TBody extends OptionalSchema,\n> = TInput extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TInput>\n : TQuery extends StandardSchemaV1\n ? TBody extends StandardSchemaV1\n ?\n | StandardSchemaV1.InferOutput<TQuery>\n | StandardSchemaV1.InferOutput<TBody>\n : StandardSchemaV1.InferOutput<TQuery>\n : TBody extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TBody>\n : unknown;\n\nexport interface DefineServerRouteContext<\n TInput extends StandardSchemaV1 | undefined = undefined,\n TQuery extends StandardSchemaV1 | undefined = undefined,\n TBody extends StandardSchemaV1 | undefined = undefined,\n TParams extends StandardSchemaV1 | undefined = undefined,\n> {\n data: ResolveDataSchema<TInput, TQuery, TBody>;\n query: InferSchema<TQuery, undefined>;\n body: InferSchema<TBody, undefined>;\n params: InferSchema<TParams, H3Event['context']['params']>;\n event: H3Event;\n}\n\nexport interface DefineServerRouteOptions<\n TInput extends StandardSchemaV1 | undefined = undefined,\n TOutput extends StandardSchemaV1 | undefined = undefined,\n TQuery extends StandardSchemaV1 | undefined = undefined,\n TBody extends StandardSchemaV1 | undefined = undefined,\n TParams extends StandardSchemaV1 | undefined = undefined,\n TResult extends DefineServerRouteResult = DefineServerRouteResult,\n> {\n input?: TInput;\n query?: TQuery;\n body?: TBody;\n params?: TParams;\n output?: TOutput;\n handler: (\n context: DefineServerRouteContext<TInput, TQuery, TBody, TParams>,\n ) => Promise<TResult> | TResult;\n}\n\nfunction isDevEnvironment() {\n return (\n typeof process !== 'undefined' &&\n (process.env['NODE_ENV'] === 'development' ||\n process.env['NODE_ENV'] === 'test')\n );\n}\n\nfunction warnOnOutputIssues(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n console.warn(\n `[analog] Server route output validation failed:\\n` +\n issues\n .map((i) => {\n const path = i.path\n ? ` at \"${i.path.map((p) => (typeof p === 'object' ? (p as { key: string }).key : p)).join('.')}\"`\n : '';\n return ` - ${i.message}${path}`;\n })\n .join('\\n'),\n );\n}\n\nfunction getRequestUrl(event: H3Event): string {\n try {\n return getRequestURL(event).href;\n } catch {\n return (event as H3Event & { request: Request }).request.url;\n }\n}\n\n/**\n * Creates an h3-compatible event handler with Standard Schema validation.\n *\n * - `input` schema validates the request body (POST/PUT/PATCH) or query\n * params (GET). Returns 422 with `StandardSchemaV1.Issue[]` on failure.\n * - `output` schema validates the response in development only (stripped\n * in production for zero overhead). Logs a warning on mismatch.\n * - Plain return values are serialized with `json(...)`; raw `Response`\n * objects are returned unchanged.\n *\n * @example\n * ```typescript\n * import { defineServerRoute } from '@analogjs/router/server/actions';\n * import * as v from 'valibot';\n *\n * const Input = v.object({\n * name: v.pipe(v.string(), v.minLength(1)),\n * email: v.pipe(v.string(), v.email()),\n * });\n * const Output = v.object({\n * id: v.string(),\n * name: v.string(),\n * });\n *\n * export default defineServerRoute({\n * input: Input,\n * output: Output,\n * handler: async ({ data }) => {\n * const user = await db.users.create(data);\n * return user;\n * },\n * });\n * ```\n */\nexport function defineServerRoute<\n TInput extends StandardSchemaV1 | undefined = undefined,\n TOutput extends StandardSchemaV1 | undefined = undefined,\n TQuery extends StandardSchemaV1 | undefined = undefined,\n TBody extends StandardSchemaV1 | undefined = undefined,\n TParams extends StandardSchemaV1 | undefined = undefined,\n TResult extends DefineServerRouteResult = DefineServerRouteResult,\n>(\n options: DefineServerRouteOptions<\n TInput,\n TOutput,\n TQuery,\n TBody,\n TParams,\n TResult\n >,\n): ServerRouteHandler<\n InferSchema<TQuery, undefined>,\n InferSchema<TBody, undefined>,\n TResult\n> {\n return (async (event: H3Event): Promise<Response> => {\n const method = event.method.toUpperCase();\n let data: unknown;\n let query: unknown;\n let body: unknown;\n let params: unknown = event.context?.params ?? {};\n\n if (options.params) {\n const paramsResult = await validateWithSchema(options.params, params);\n if (paramsResult.issues) {\n return fail(422, paramsResult.issues);\n }\n params = paramsResult.value;\n }\n\n if (options.input) {\n data = await parseRequestData(event);\n\n const inputResult = await validateWithSchema(options.input, data);\n if (inputResult.issues) {\n return fail(422, inputResult.issues);\n }\n data = inputResult.value;\n } else {\n if (options.query) {\n const url = new URL(getRequestUrl(event), 'http://localhost');\n const queryResult = await validateWithSchema(\n options.query,\n parseSearchParams(url.searchParams),\n );\n if (queryResult.issues) {\n return fail(422, queryResult.issues);\n }\n query = queryResult.value;\n }\n\n if (options.body && method !== 'GET' && method !== 'HEAD') {\n body = await parseRequestData(event);\n const bodyResult = await validateWithSchema(options.body, body);\n if (bodyResult.issues) {\n return fail(422, bodyResult.issues);\n }\n body = bodyResult.value;\n }\n\n if (method === 'GET' || method === 'HEAD') {\n data = query;\n } else if (body !== undefined) {\n data = body;\n } else {\n data = query;\n }\n }\n\n const result = await options.handler({\n data: data as ResolveDataSchema<TInput, TQuery, TBody>,\n query: query as InferSchema<TQuery, undefined>,\n body: body as InferSchema<TBody, undefined>,\n params: params as InferSchema<TParams, H3Event['context']['params']>,\n event,\n });\n\n if (result instanceof Response) {\n return result;\n }\n\n if (options.output && isDevEnvironment()) {\n const outputResult = await validateWithSchema(options.output, result);\n if (outputResult.issues) {\n warnOnOutputIssues(outputResult.issues);\n }\n }\n\n return json(result);\n }) as ServerRouteHandler<\n InferSchema<TQuery, undefined>,\n InferSchema<TBody, undefined>,\n TResult\n >;\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { H3Event, H3EventContext } from 'nitro/h3';\nimport { getRequestURL } from 'nitro/h3';\nimport type { $Fetch } from 'nitro/types';\nimport { fail } from './actions';\nimport { parseSearchParams } from './parse-request-data';\nimport { validateWithSchema } from './validate';\n\ntype NodeContext = NonNullable<H3Event['node']>;\ntype OptionalSchema = StandardSchemaV1 | undefined;\ntype InferSchema<\n TSchema extends OptionalSchema,\n TFallback,\n> = TSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TSchema>\n : TFallback;\n\nexport interface PageLoadContext<\n TParamsSchema extends OptionalSchema = undefined,\n TQuerySchema extends OptionalSchema = undefined,\n> {\n params: InferSchema<TParamsSchema, H3EventContext['params']>;\n query: InferSchema<\n TQuerySchema,\n Record<string, string | string[] | undefined>\n >;\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n}\n\nexport interface DefinePageLoadOptions<\n TParamsSchema extends OptionalSchema = undefined,\n TQuerySchema extends OptionalSchema = undefined,\n TResult = unknown,\n> {\n params?: TParamsSchema;\n query?: TQuerySchema;\n handler: (\n context: PageLoadContext<TParamsSchema, TQuerySchema>,\n ) => Promise<TResult> | TResult;\n}\n\n/**\n * Creates a typed page server load function with optional\n * Standard Schema validation for route params and query.\n *\n * Follows the same validation patterns as `defineAction` and\n * `defineServerRoute`: validates before invoking the handler,\n * returns `fail(422, issues)` on validation failure.\n *\n * @example\n * ```typescript\n * // src/app/pages/users/[id].server.ts\n * import { definePageLoad } from '@analogjs/router/server/actions';\n * import * as v from 'valibot';\n *\n * export const routeParamsSchema = v.object({\n * id: v.pipe(v.string(), v.regex(/^\\d+$/)),\n * });\n *\n * export const load = definePageLoad({\n * params: routeParamsSchema,\n * handler: async ({ params, fetch }) => {\n * // params.id is typed as string (validated)\n * const user = await fetch(`/api/users/${params.id}`);\n * return user;\n * },\n * });\n * ```\n */\nexport function definePageLoad<\n TParamsSchema extends OptionalSchema = undefined,\n TQuerySchema extends OptionalSchema = undefined,\n TResult = unknown,\n>(\n options: DefinePageLoadOptions<TParamsSchema, TQuerySchema, TResult>,\n): (ctx: {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n}) => Promise<TResult | Response> {\n type Params = InferSchema<TParamsSchema, H3EventContext['params']>;\n type Query = InferSchema<\n TQuerySchema,\n Record<string, string | string[] | undefined>\n >;\n\n return async (ctx: {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n }) => {\n let params: unknown = ctx.params ?? {};\n\n let requestUrl: string;\n try {\n requestUrl = getRequestURL(ctx.event).href;\n } catch {\n requestUrl = (ctx.event as H3Event & { request: Request }).request.url;\n }\n const url = new URL(requestUrl, 'http://localhost');\n let query: unknown = parseSearchParams(url.searchParams);\n\n // Validate params\n if (options.params) {\n const result = await validateWithSchema(options.params, params);\n if (result.issues) {\n return fail(422, result.issues);\n }\n params = result.value;\n }\n\n // Validate query\n if (options.query) {\n const result = await validateWithSchema(options.query, query);\n if (result.issues) {\n return fail(422, result.issues);\n }\n query = result.value;\n }\n\n return options.handler({\n params: params as Params,\n query: query as Query,\n req: ctx.req,\n res: ctx.res,\n fetch: ctx.fetch,\n event: ctx.event,\n });\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@analogjs/router",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.57",
|
|
4
4
|
"description": "Filesystem-based routing for Angular",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Brandon Roberts <robertsbt@gmail.com>",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"url": "https://github.com/sponsors/brandonroberts"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@analogjs/content": "3.0.0-alpha.
|
|
72
|
+
"@analogjs/content": "3.0.0-alpha.57",
|
|
73
73
|
"@standard-schema/spec": "^1.1.0",
|
|
74
74
|
"@angular/core": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
|
|
75
75
|
"@angular/platform-server": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
"tslib": "^2.3.0"
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@analogjs/vite-plugin-angular": "3.0.0-alpha.
|
|
104
|
-
"@analogjs/vitest-angular": "3.0.0-alpha.
|
|
103
|
+
"@analogjs/vite-plugin-angular": "3.0.0-alpha.57",
|
|
104
|
+
"@analogjs/vitest-angular": "3.0.0-alpha.57"
|
|
105
105
|
},
|
|
106
106
|
"ng-update": {
|
|
107
107
|
"packageGroup": [
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import { QueryClient } from '@tanstack/angular-query-experimental';
|
|
3
|
+
import type { DehydratedState } from '@tanstack/angular-query-experimental';
|
|
4
|
+
import type { H3Event, H3EventContext } from 'nitro/h3';
|
|
5
|
+
import type { $Fetch } from 'nitro/types';
|
|
6
|
+
import { type PageLoadContext } from '../../../server/actions/src/index.js';
|
|
7
|
+
export { ANALOG_QUERIES_KEY } from '../../src/constants.js';
|
|
8
|
+
type NodeContext = NonNullable<H3Event['node']>;
|
|
9
|
+
type OptionalSchema = StandardSchemaV1 | undefined;
|
|
10
|
+
export interface PageLoadQueriesResult<TData> {
|
|
11
|
+
__analogQueries: DehydratedState;
|
|
12
|
+
data: TData;
|
|
13
|
+
}
|
|
14
|
+
export interface DefinePageLoadQueriesOptions<TParamsSchema extends OptionalSchema, TQuerySchema extends OptionalSchema, TData> {
|
|
15
|
+
params?: TParamsSchema;
|
|
16
|
+
query?: TQuerySchema;
|
|
17
|
+
/**
|
|
18
|
+
* Optional QueryClient factory. Defaults to `new QueryClient()`.
|
|
19
|
+
* Override to set `defaultOptions` (e.g. `queries: { staleTime: Infinity }`).
|
|
20
|
+
*/
|
|
21
|
+
client?: () => QueryClient;
|
|
22
|
+
/**
|
|
23
|
+
* Handler receives the standard PageLoadContext plus a per-request
|
|
24
|
+
* QueryClient. Use `client.prefetchQuery` / `ensureQueryData` /
|
|
25
|
+
* `prefetchInfiniteQuery` to warm the cache; the dehydrated client
|
|
26
|
+
* is returned as `__analogQueries` on the load result. Anything you
|
|
27
|
+
* return from the handler becomes `data` on the same result.
|
|
28
|
+
*/
|
|
29
|
+
handler: (ctx: PageLoadContext<TParamsSchema, TQuerySchema> & {
|
|
30
|
+
client: QueryClient;
|
|
31
|
+
}) => Promise<TData> | TData;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Page load helper that prefetches TanStack Query queries inside the
|
|
35
|
+
* `.server.ts` handler and ships the dehydrated cache alongside any
|
|
36
|
+
* additional data. The router-side hydrator in `provideAnalogQuery()`
|
|
37
|
+
* merges the dehydrated payload into the active `QueryClient` on
|
|
38
|
+
* `ResolveEnd`, so components reading the same query options see a
|
|
39
|
+
* warm cache on first render.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* // src/app/pages/posts.server.ts
|
|
44
|
+
* import { definePageLoadQueries } from '@analogjs/router/tanstack-query/server';
|
|
45
|
+
* import { queryOptions } from '@tanstack/angular-query-experimental';
|
|
46
|
+
*
|
|
47
|
+
* export const postsQuery = queryOptions({
|
|
48
|
+
* queryKey: ['posts'],
|
|
49
|
+
* queryFn: async ({ signal }) =>
|
|
50
|
+
* fetch('https://api.example.com/posts', { signal }).then((r) => r.json()),
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* export const load = definePageLoadQueries({
|
|
54
|
+
* handler: async ({ client }) => {
|
|
55
|
+
* await client.prefetchQuery(postsQuery);
|
|
56
|
+
* },
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function definePageLoadQueries<TParamsSchema extends OptionalSchema = undefined, TQuerySchema extends OptionalSchema = undefined, TData = void>(options: DefinePageLoadQueriesOptions<TParamsSchema, TQuerySchema, TData>): (ctx: {
|
|
61
|
+
params: H3EventContext['params'];
|
|
62
|
+
req: NodeContext['req'];
|
|
63
|
+
res: NonNullable<NodeContext['res']>;
|
|
64
|
+
fetch: $Fetch;
|
|
65
|
+
event: H3Event;
|
|
66
|
+
}) => Promise<PageLoadQueriesResult<TData> | Response>;
|
|
@@ -1 +1,3 @@
|
|
|
1
1
|
export { provideServerAnalogQuery } from '../../src/provide-server-analog-query';
|
|
2
|
+
export { definePageLoadQueries, ANALOG_QUERIES_KEY, } from './define-page-load-queries';
|
|
3
|
+
export type { PageLoadQueriesResult, DefinePageLoadQueriesOptions, } from './define-page-load-queries';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-data key under `ActivatedRoute.data['load']` that holds the
|
|
3
|
+
* `DehydratedState` produced by `definePageLoadQueries`. The Router-
|
|
4
|
+
* events hydrator in `provideAnalogQuery()` looks for this key on
|
|
5
|
+
* `ResolveEnd` and merges the dehydrated payload into the active
|
|
6
|
+
* `QueryClient`, so component-issued queries hit a warm cache on
|
|
7
|
+
* first render.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ANALOG_QUERIES_KEY: '__analogQueries';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analogjs-router-server-actions.mjs","names":[],"sources":["../../server/actions/src/actions.ts","../../server/actions/src/parse-request-data.ts","../../server/actions/src/validate.ts","../../server/actions/src/define-action.ts","../../server/actions/src/define-server-route.ts","../../server/actions/src/define-page-load.ts"],"sourcesContent":["import type { H3Event, H3EventContext } from 'nitro/h3';\nimport type { $Fetch } from 'nitro/types';\nimport type { NodeContext } from '../../../src/lib/route-types.js';\n\nexport type PageServerAction = {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n};\n\nexport function fail<T = object>(status: number, errors: T): Response {\n return new Response(JSON.stringify(errors), {\n status,\n headers: {\n 'X-Analog-Errors': 'true',\n },\n });\n}\n\nexport function json<T = object>(data: T, config?: ResponseInit): Response {\n return new Response(JSON.stringify(data), {\n headers: {\n 'Content-Type': 'application/json; charset=utf-8',\n },\n ...config,\n });\n}\n\nexport function redirect(\n url: string,\n config: number | ResponseInit = 302,\n): Response {\n if (typeof config === 'number') {\n return new Response(null, {\n status: config,\n headers: {\n Location: `${url}`,\n },\n });\n }\n\n return new Response(null, {\n headers: {\n Location: `${url}`,\n },\n ...config,\n });\n}\n","import { readBody, readFormData, toRequest as h3ToRequest } from 'nitro/h3';\n\ntype RequestEntryValue = string | File;\ntype ParsedRequestValue = RequestEntryValue | RequestEntryValue[];\n\nfunction appendEntry(\n target: Record<string, ParsedRequestValue>,\n key: string,\n value: RequestEntryValue,\n) {\n const existingValue = target[key];\n\n if (existingValue === undefined) {\n target[key] = value;\n return;\n }\n\n if (Array.isArray(existingValue)) {\n existingValue.push(value);\n return;\n }\n\n target[key] = [existingValue, value];\n}\n\nfunction getRequest(event: { method: string; headers: Headers }): Request {\n const maybeRequest = (event as unknown as { request?: Request }).request;\n if (maybeRequest) {\n return maybeRequest;\n }\n return h3ToRequest(event as Parameters<typeof h3ToRequest>[0]);\n}\n\nfunction getContentType(event: { method: string; headers: Headers }): string {\n const request = getRequest(event);\n\n return (\n request.headers.get('content-type') ??\n event.headers.get('content-type') ??\n event.headers.get('Content-Type') ??\n ''\n );\n}\n\nfunction isJsonContentType(contentType: string): boolean {\n const mimeType = contentType.split(';', 1)[0]?.trim().toLowerCase() ?? '';\n return mimeType === 'application/json' || mimeType.endsWith('+json');\n}\n\nfunction isFormContentType(contentType: string): boolean {\n return (\n contentType.includes('multipart/form-data') ||\n contentType.includes('application/x-www-form-urlencoded')\n );\n}\n\nexport function parseSearchParams(\n searchParams: URLSearchParams,\n): Record<string, ParsedRequestValue> {\n const result: Record<string, ParsedRequestValue> = {};\n searchParams.forEach((value, key) => {\n appendEntry(result, key, value);\n });\n return result;\n}\n\nexport function parseFormData(\n formData: FormData,\n): Record<string, ParsedRequestValue> {\n const result: Record<string, ParsedRequestValue> = {};\n formData.forEach((value, key) => {\n appendEntry(result, key, value as RequestEntryValue);\n });\n return result;\n}\n\nexport async function parseRequestData(event: {\n method: string;\n headers: Headers;\n}): Promise<unknown> {\n const request = getRequest(event);\n const httpEvent = event as unknown as Parameters<typeof readBody>[0];\n const h3Event = event as unknown as Parameters<typeof readFormData>[0];\n const method = event.method.toUpperCase();\n\n if (method === 'GET' || method === 'HEAD') {\n const url = new URL(request.url, 'http://localhost');\n return parseSearchParams(url.searchParams);\n }\n\n const contentType = getContentType(event);\n\n if (isJsonContentType(contentType)) {\n try {\n return (await readBody(httpEvent)) ?? {};\n } catch {\n try {\n return await request.json();\n } catch {\n return {};\n }\n }\n }\n\n if (isFormContentType(contentType)) {\n try {\n return parseFormData(await readFormData(h3Event));\n } catch {\n if (typeof request.formData === 'function') {\n return parseFormData(await request.formData());\n }\n\n return {};\n }\n }\n\n try {\n return (await readBody(httpEvent)) ?? {};\n } catch {\n try {\n return await request.json();\n } catch {\n return {};\n }\n }\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n/**\n * Validates unknown input against a Standard Schema.\n *\n * Handles both sync and async `validate` implementations — the Standard\n * Schema spec allows either return shape.\n */\nexport async function validateWithSchema<T extends StandardSchemaV1>(\n schema: T,\n data: unknown,\n): Promise<StandardSchemaV1.Result<StandardSchemaV1.InferOutput<T>>> {\n return schema['~standard'].validate(data);\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { H3Event, H3EventContext } from 'nitro/h3';\nimport type { $Fetch } from 'nitro/types';\nimport { fail } from './actions';\nimport { parseRequestData } from './parse-request-data';\nimport { validateWithSchema } from './validate';\n\ntype NodeContext = NonNullable<H3Event['node']>;\ntype OptionalSchema = StandardSchemaV1 | undefined;\ntype InferSchema<\n TSchema extends OptionalSchema,\n TFallback,\n> = TSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TSchema>\n : TFallback;\n\nexport interface DefineActionContext<\n TSchema extends OptionalSchema = undefined,\n TParamsSchema extends OptionalSchema = undefined,\n> {\n data: InferSchema<TSchema, Record<string, unknown>>;\n params: InferSchema<TParamsSchema, H3EventContext['params']>;\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n}\n\nexport interface DefineActionOptions<\n TSchema extends OptionalSchema = undefined,\n TParamsSchema extends OptionalSchema = undefined,\n> {\n schema?: TSchema;\n params?: TParamsSchema;\n handler: (\n context: DefineActionContext<TSchema, TParamsSchema>,\n ) => Promise<Response> | Response;\n}\n\n/**\n * Creates a server action handler with Standard Schema input validation.\n *\n * Parses the request body (JSON or FormData) and validates it against the\n * provided schema before invoking the handler. On validation failure,\n * returns `fail(422, issues)` with `StandardSchemaV1.Issue[]`.\n * Repeated form fields are preserved as arrays instead of being collapsed\n * to the last value.\n *\n * @example\n * ```typescript\n * import { defineAction, json } from '@analogjs/router/server/actions';\n * import * as v from 'valibot';\n *\n * const Schema = v.object({\n * email: v.pipe(v.string(), v.email()),\n * });\n *\n * export const action = defineAction({\n * schema: Schema,\n * handler: async ({ data }) => {\n * // data is typed as { email: string }\n * return json({ ok: true });\n * },\n * });\n * ```\n */\nexport function defineAction<\n TSchema extends OptionalSchema = undefined,\n TParamsSchema extends OptionalSchema = undefined,\n>(options: DefineActionOptions<TSchema, TParamsSchema>) {\n type Params = InferSchema<TParamsSchema, H3EventContext['params']>;\n\n function getParams(\n params: H3EventContext['params'],\n ): Params | Record<string, never> {\n return (params ?? {}) as Params | Record<string, never>;\n }\n\n return async (ctx: {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n }): Promise<Response> => {\n const rawParams = getParams(ctx.params);\n\n if (options.params) {\n const paramsResult = await validateWithSchema(options.params, rawParams);\n if (paramsResult.issues) {\n return fail(422, paramsResult.issues);\n }\n return handleValidatedRequest(ctx, options, paramsResult.value as Params);\n }\n\n return handleValidatedRequest(ctx, options, rawParams as Params);\n };\n}\n\nasync function handleValidatedRequest<\n TSchema extends OptionalSchema = undefined,\n TParamsSchema extends OptionalSchema = undefined,\n>(\n ctx: {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n },\n options: DefineActionOptions<TSchema, TParamsSchema>,\n params: InferSchema<TParamsSchema, H3EventContext['params']>,\n) {\n type Data = InferSchema<TSchema, Record<string, unknown>>;\n const body = await parseRequestData(ctx.event);\n\n let data: unknown = body;\n\n if (options.schema) {\n const result = await validateWithSchema(options.schema, body);\n if (result.issues) {\n return fail(422, result.issues);\n }\n data = result.value;\n }\n\n return options.handler({\n data: data as Data,\n params,\n req: ctx.req,\n res: ctx.res,\n fetch: ctx.fetch,\n event: ctx.event,\n });\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { H3Event } from 'nitro/h3';\nimport { getRequestURL } from 'nitro/h3';\nimport { fail, json } from './actions';\nimport { parseRequestData, parseSearchParams } from './parse-request-data';\nimport { validateWithSchema } from './validate';\n\nexport type DefineServerRouteResult = Response | unknown;\n\nexport interface ServerRouteHandler<\n TQuery = unknown,\n TBody = unknown,\n TResult = unknown,\n> {\n (event: H3Event): Promise<Response>;\n readonly _types: {\n readonly query: TQuery;\n readonly body: TBody;\n readonly result: TResult;\n };\n}\n\nexport type InferRouteQuery<T> =\n T extends ServerRouteHandler<infer Q, any, any> ? Q : never;\nexport type InferRouteBody<T> =\n T extends ServerRouteHandler<any, infer B, any> ? B : never;\nexport type InferRouteResult<T> =\n T extends ServerRouteHandler<any, any, infer R>\n ? Exclude<R, Response>\n : never;\n\ntype OptionalSchema = StandardSchemaV1 | undefined;\ntype InferSchema<\n TSchema extends OptionalSchema,\n TFallback = unknown,\n> = TSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TSchema>\n : TFallback;\ntype ResolveDataSchema<\n TInput extends OptionalSchema,\n TQuery extends OptionalSchema,\n TBody extends OptionalSchema,\n> = TInput extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TInput>\n : TQuery extends StandardSchemaV1\n ? TBody extends StandardSchemaV1\n ?\n | StandardSchemaV1.InferOutput<TQuery>\n | StandardSchemaV1.InferOutput<TBody>\n : StandardSchemaV1.InferOutput<TQuery>\n : TBody extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TBody>\n : unknown;\n\nexport interface DefineServerRouteContext<\n TInput extends StandardSchemaV1 | undefined = undefined,\n TQuery extends StandardSchemaV1 | undefined = undefined,\n TBody extends StandardSchemaV1 | undefined = undefined,\n TParams extends StandardSchemaV1 | undefined = undefined,\n> {\n data: ResolveDataSchema<TInput, TQuery, TBody>;\n query: InferSchema<TQuery, undefined>;\n body: InferSchema<TBody, undefined>;\n params: InferSchema<TParams, H3Event['context']['params']>;\n event: H3Event;\n}\n\nexport interface DefineServerRouteOptions<\n TInput extends StandardSchemaV1 | undefined = undefined,\n TOutput extends StandardSchemaV1 | undefined = undefined,\n TQuery extends StandardSchemaV1 | undefined = undefined,\n TBody extends StandardSchemaV1 | undefined = undefined,\n TParams extends StandardSchemaV1 | undefined = undefined,\n TResult extends DefineServerRouteResult = DefineServerRouteResult,\n> {\n input?: TInput;\n query?: TQuery;\n body?: TBody;\n params?: TParams;\n output?: TOutput;\n handler: (\n context: DefineServerRouteContext<TInput, TQuery, TBody, TParams>,\n ) => Promise<TResult> | TResult;\n}\n\nfunction isDevEnvironment() {\n return (\n typeof process !== 'undefined' &&\n (process.env['NODE_ENV'] === 'development' ||\n process.env['NODE_ENV'] === 'test')\n );\n}\n\nfunction warnOnOutputIssues(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n console.warn(\n `[analog] Server route output validation failed:\\n` +\n issues\n .map((i) => {\n const path = i.path\n ? ` at \"${i.path.map((p) => (typeof p === 'object' ? (p as { key: string }).key : p)).join('.')}\"`\n : '';\n return ` - ${i.message}${path}`;\n })\n .join('\\n'),\n );\n}\n\nfunction getRequestUrl(event: H3Event): string {\n try {\n return getRequestURL(event).href;\n } catch {\n return (event as H3Event & { request: Request }).request.url;\n }\n}\n\n/**\n * Creates an h3-compatible event handler with Standard Schema validation.\n *\n * - `input` schema validates the request body (POST/PUT/PATCH) or query\n * params (GET). Returns 422 with `StandardSchemaV1.Issue[]` on failure.\n * - `output` schema validates the response in development only (stripped\n * in production for zero overhead). Logs a warning on mismatch.\n * - Plain return values are serialized with `json(...)`; raw `Response`\n * objects are returned unchanged.\n *\n * @example\n * ```typescript\n * import { defineServerRoute } from '@analogjs/router/server/actions';\n * import * as v from 'valibot';\n *\n * const Input = v.object({\n * name: v.pipe(v.string(), v.minLength(1)),\n * email: v.pipe(v.string(), v.email()),\n * });\n * const Output = v.object({\n * id: v.string(),\n * name: v.string(),\n * });\n *\n * export default defineServerRoute({\n * input: Input,\n * output: Output,\n * handler: async ({ data }) => {\n * const user = await db.users.create(data);\n * return user;\n * },\n * });\n * ```\n */\nexport function defineServerRoute<\n TInput extends StandardSchemaV1 | undefined = undefined,\n TOutput extends StandardSchemaV1 | undefined = undefined,\n TQuery extends StandardSchemaV1 | undefined = undefined,\n TBody extends StandardSchemaV1 | undefined = undefined,\n TParams extends StandardSchemaV1 | undefined = undefined,\n TResult extends DefineServerRouteResult = DefineServerRouteResult,\n>(\n options: DefineServerRouteOptions<\n TInput,\n TOutput,\n TQuery,\n TBody,\n TParams,\n TResult\n >,\n): ServerRouteHandler<\n InferSchema<TQuery, undefined>,\n InferSchema<TBody, undefined>,\n TResult\n> {\n return (async (event: H3Event): Promise<Response> => {\n const method = event.method.toUpperCase();\n let data: unknown;\n let query: unknown;\n let body: unknown;\n let params: unknown = event.context?.params ?? {};\n\n if (options.params) {\n const paramsResult = await validateWithSchema(options.params, params);\n if (paramsResult.issues) {\n return fail(422, paramsResult.issues);\n }\n params = paramsResult.value;\n }\n\n if (options.input) {\n data = await parseRequestData(event);\n\n const inputResult = await validateWithSchema(options.input, data);\n if (inputResult.issues) {\n return fail(422, inputResult.issues);\n }\n data = inputResult.value;\n } else {\n if (options.query) {\n const url = new URL(getRequestUrl(event), 'http://localhost');\n const queryResult = await validateWithSchema(\n options.query,\n parseSearchParams(url.searchParams),\n );\n if (queryResult.issues) {\n return fail(422, queryResult.issues);\n }\n query = queryResult.value;\n }\n\n if (options.body && method !== 'GET' && method !== 'HEAD') {\n body = await parseRequestData(event);\n const bodyResult = await validateWithSchema(options.body, body);\n if (bodyResult.issues) {\n return fail(422, bodyResult.issues);\n }\n body = bodyResult.value;\n }\n\n if (method === 'GET' || method === 'HEAD') {\n data = query;\n } else if (body !== undefined) {\n data = body;\n } else {\n data = query;\n }\n }\n\n const result = await options.handler({\n data: data as ResolveDataSchema<TInput, TQuery, TBody>,\n query: query as InferSchema<TQuery, undefined>,\n body: body as InferSchema<TBody, undefined>,\n params: params as InferSchema<TParams, H3Event['context']['params']>,\n event,\n });\n\n if (result instanceof Response) {\n return result;\n }\n\n if (options.output && isDevEnvironment()) {\n const outputResult = await validateWithSchema(options.output, result);\n if (outputResult.issues) {\n warnOnOutputIssues(outputResult.issues);\n }\n }\n\n return json(result);\n }) as ServerRouteHandler<\n InferSchema<TQuery, undefined>,\n InferSchema<TBody, undefined>,\n TResult\n >;\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { H3Event, H3EventContext } from 'nitro/h3';\nimport { getRequestURL } from 'nitro/h3';\nimport type { $Fetch } from 'nitro/types';\nimport { fail } from './actions';\nimport { parseSearchParams } from './parse-request-data';\nimport { validateWithSchema } from './validate';\n\ntype NodeContext = NonNullable<H3Event['node']>;\ntype OptionalSchema = StandardSchemaV1 | undefined;\ntype InferSchema<\n TSchema extends OptionalSchema,\n TFallback,\n> = TSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TSchema>\n : TFallback;\n\nexport interface PageLoadContext<\n TParamsSchema extends OptionalSchema = undefined,\n TQuerySchema extends OptionalSchema = undefined,\n> {\n params: InferSchema<TParamsSchema, H3EventContext['params']>;\n query: InferSchema<\n TQuerySchema,\n Record<string, string | string[] | undefined>\n >;\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n}\n\nexport interface DefinePageLoadOptions<\n TParamsSchema extends OptionalSchema = undefined,\n TQuerySchema extends OptionalSchema = undefined,\n TResult = unknown,\n> {\n params?: TParamsSchema;\n query?: TQuerySchema;\n handler: (\n context: PageLoadContext<TParamsSchema, TQuerySchema>,\n ) => Promise<TResult> | TResult;\n}\n\n/**\n * Creates a typed page server load function with optional\n * Standard Schema validation for route params and query.\n *\n * Follows the same validation patterns as `defineAction` and\n * `defineServerRoute`: validates before invoking the handler,\n * returns `fail(422, issues)` on validation failure.\n *\n * @example\n * ```typescript\n * // src/app/pages/users/[id].server.ts\n * import { definePageLoad } from '@analogjs/router/server/actions';\n * import * as v from 'valibot';\n *\n * export const routeParamsSchema = v.object({\n * id: v.pipe(v.string(), v.regex(/^\\d+$/)),\n * });\n *\n * export const load = definePageLoad({\n * params: routeParamsSchema,\n * handler: async ({ params, fetch }) => {\n * // params.id is typed as string (validated)\n * const user = await fetch(`/api/users/${params.id}`);\n * return user;\n * },\n * });\n * ```\n */\nexport function definePageLoad<\n TParamsSchema extends OptionalSchema = undefined,\n TQuerySchema extends OptionalSchema = undefined,\n TResult = unknown,\n>(\n options: DefinePageLoadOptions<TParamsSchema, TQuerySchema, TResult>,\n): (ctx: {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n}) => Promise<TResult | Response> {\n type Params = InferSchema<TParamsSchema, H3EventContext['params']>;\n type Query = InferSchema<\n TQuerySchema,\n Record<string, string | string[] | undefined>\n >;\n\n return async (ctx: {\n params: H3EventContext['params'];\n req: NodeContext['req'];\n res: NonNullable<NodeContext['res']>;\n fetch: $Fetch;\n event: H3Event;\n }) => {\n let params: unknown = ctx.params ?? {};\n\n let requestUrl: string;\n try {\n requestUrl = getRequestURL(ctx.event).href;\n } catch {\n requestUrl = (ctx.event as H3Event & { request: Request }).request.url;\n }\n const url = new URL(requestUrl, 'http://localhost');\n let query: unknown = parseSearchParams(url.searchParams);\n\n // Validate params\n if (options.params) {\n const result = await validateWithSchema(options.params, params);\n if (result.issues) {\n return fail(422, result.issues);\n }\n params = result.value;\n }\n\n // Validate query\n if (options.query) {\n const result = await validateWithSchema(options.query, query);\n if (result.issues) {\n return fail(422, result.issues);\n }\n query = result.value;\n }\n\n return options.handler({\n params: params as Params,\n query: query as Query,\n req: ctx.req,\n res: ctx.res,\n fetch: ctx.fetch,\n event: ctx.event,\n });\n };\n}\n"],"mappings":"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"}
|