@analogjs/router 3.0.0-alpha.3 → 3.0.0-alpha.30
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/content/package.json +4 -0
- package/fesm2022/analogjs-router-content.mjs +63 -0
- package/fesm2022/analogjs-router-content.mjs.map +1 -0
- package/fesm2022/analogjs-router-server-actions.mjs +309 -1
- package/fesm2022/analogjs-router-server-actions.mjs.map +1 -0
- package/fesm2022/analogjs-router-server.mjs +2 -2
- package/fesm2022/analogjs-router-server.mjs.map +1 -0
- package/fesm2022/analogjs-router-tanstack-query-server.mjs +22 -0
- package/fesm2022/analogjs-router-tanstack-query-server.mjs.map +1 -0
- package/fesm2022/analogjs-router-tanstack-query.mjs +39 -0
- package/fesm2022/analogjs-router-tanstack-query.mjs.map +1 -0
- package/fesm2022/analogjs-router-tokens.mjs.map +1 -0
- package/fesm2022/analogjs-router.mjs +560 -62
- package/fesm2022/analogjs-router.mjs.map +1 -0
- package/fesm2022/debug.page.mjs +53 -31
- package/fesm2022/debug.page.mjs.map +1 -0
- package/fesm2022/provide-analog-query.mjs +23 -0
- package/fesm2022/provide-analog-query.mjs.map +1 -0
- package/fesm2022/route-files.mjs +362 -0
- package/fesm2022/route-files.mjs.map +1 -0
- package/fesm2022/routes.mjs +5 -278
- package/fesm2022/routes.mjs.map +1 -0
- package/package.json +71 -25
- package/tanstack-query/package.json +4 -0
- package/tanstack-query/server/package.json +4 -0
- package/types/content/src/index.d.ts +4 -0
- package/types/content/src/lib/debug/routes.d.ts +10 -0
- package/types/{src → content/src}/lib/markdown-helpers.d.ts +1 -1
- package/types/content/src/lib/routes.d.ts +8 -0
- package/types/content/src/lib/with-content-routes.d.ts +2 -0
- package/types/server/actions/src/define-action.d.ts +54 -0
- package/types/server/actions/src/define-api-route.d.ts +57 -0
- package/types/server/actions/src/define-page-load.d.ts +55 -0
- package/types/server/actions/src/define-server-route.d.ts +68 -0
- package/types/server/actions/src/index.d.ts +9 -1
- package/types/server/actions/src/parse-request-data.d.ts +9 -0
- package/types/server/actions/src/validate.d.ts +8 -0
- package/types/server/src/provide-server-context.d.ts +1 -1
- package/types/server/src/render.d.ts +1 -1
- package/types/server/src/server-component-render.d.ts +1 -1
- package/types/src/index.d.ts +16 -5
- package/types/src/lib/cache-key.d.ts +1 -1
- package/types/src/lib/cookie-interceptor.d.ts +1 -1
- package/types/src/lib/debug/debug.page.d.ts +4 -2
- package/types/src/lib/define-route.d.ts +6 -1
- package/types/src/lib/endpoints.d.ts +1 -1
- package/types/src/lib/experimental.d.ts +140 -0
- package/types/src/lib/form-action.directive.d.ts +12 -5
- package/types/src/lib/inject-load.d.ts +5 -2
- package/types/src/lib/inject-navigate.d.ts +23 -0
- package/types/src/lib/inject-route-context.d.ts +32 -0
- package/types/src/lib/inject-typed-params.d.ts +63 -0
- package/types/src/lib/json-ld.d.ts +32 -0
- package/types/src/lib/meta-tags.d.ts +3 -1
- package/types/src/lib/models.d.ts +3 -0
- package/types/src/lib/provide-file-router-base.d.ts +4 -0
- package/types/src/lib/provide-file-router.d.ts +2 -8
- package/types/src/lib/route-builder.d.ts +5 -0
- package/types/src/lib/route-files.d.ts +18 -0
- package/types/src/lib/route-path.d.ts +124 -0
- package/types/src/lib/route-types.d.ts +2 -1
- package/types/src/lib/routes.d.ts +2 -10
- package/types/src/lib/validation-errors.d.ts +7 -0
- package/types/tanstack-query/server/src/index.d.ts +1 -0
- package/types/tanstack-query/src/index.d.ts +2 -0
- package/types/tanstack-query/src/provide-analog-query.d.ts +4 -0
- package/types/tanstack-query/src/provide-server-analog-query.d.ts +2 -0
- package/types/tanstack-query/src/server-query.d.ts +16 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { API_PREFIX, injectAPIPrefix, injectBaseURL, injectInternalServerFetch, injectRequest } from "./analogjs-router-tokens.mjs";
|
|
2
|
+
import { a as updateMetaTagsOnRouteChange, i as injectRouteEndpointURL, n as ANALOG_ROUTE_FILES, o as updateJsonLdOnRouteChange, r as createRoutes$1, t as ANALOG_EXTRA_ROUTE_FILE_SOURCES } from "./route-files.mjs";
|
|
3
|
+
import { n as createRoutes, r as routes, t as injectDebugRoutes } from "./routes.mjs";
|
|
2
4
|
import { ActivatedRoute, ROUTES, Router, provideRouter } from "@angular/router";
|
|
3
5
|
import * as i0 from "@angular/core";
|
|
4
|
-
import { ChangeDetectionStrategy, Component, Directive,
|
|
6
|
+
import { ChangeDetectionStrategy, Component, Directive, InjectionToken, Injector, PLATFORM_ID, TransferState, effect, inject, input, isDevMode, makeEnvironmentProviders, makeStateKey, output, provideAppInitializer, signal } from "@angular/core";
|
|
5
7
|
import { HttpClient, HttpHeaders, HttpRequest, HttpResponse, ɵHTTP_ROOT_INTERCEPTOR_FNS } from "@angular/common/http";
|
|
6
|
-
import { catchError, from, map, of, throwError } from "rxjs";
|
|
7
|
-
import {
|
|
8
|
-
import { DomSanitizer } from "@angular/platform-browser";
|
|
9
|
-
import {
|
|
8
|
+
import { catchError, from, map, of, take, throwError } from "rxjs";
|
|
9
|
+
import { DOCUMENT, isPlatformServer } from "@angular/common";
|
|
10
|
+
import { DomSanitizer, Meta } from "@angular/platform-browser";
|
|
11
|
+
import { toSignal } from "@angular/core/rxjs-interop";
|
|
10
12
|
//#region packages/router/src/lib/define-route.ts
|
|
11
13
|
/**
|
|
12
14
|
* @deprecated Use `RouteMeta` type instead.
|
|
@@ -66,26 +68,38 @@ function cookieInterceptor(req, next, location = inject(PLATFORM_ID), serverRequ
|
|
|
66
68
|
} else return next(req);
|
|
67
69
|
}
|
|
68
70
|
//#endregion
|
|
69
|
-
//#region packages/router/src/lib/provide-file-router.ts
|
|
70
|
-
|
|
71
|
-
* Sets up providers for the Angular router, and registers
|
|
72
|
-
* file-based routes. Additional features can be provided
|
|
73
|
-
* to further configure the behavior of the router.
|
|
74
|
-
*
|
|
75
|
-
* @param features
|
|
76
|
-
* @returns Providers and features to configure the router with routes
|
|
77
|
-
*/
|
|
78
|
-
function provideFileRouter(...features) {
|
|
71
|
+
//#region packages/router/src/lib/provide-file-router-base.ts
|
|
72
|
+
function provideFileRouterWithRoutes(...features) {
|
|
79
73
|
const extraRoutesFeature = features.filter((feat) => feat.ɵkind >= 100);
|
|
80
74
|
const routerFeatures = features.filter((feat) => feat.ɵkind < 100);
|
|
81
75
|
return makeEnvironmentProviders([
|
|
82
76
|
extraRoutesFeature.map((erf) => erf.ɵproviders),
|
|
83
|
-
provideRouter(
|
|
77
|
+
provideRouter([], ...routerFeatures),
|
|
84
78
|
{
|
|
85
|
-
provide:
|
|
79
|
+
provide: ROUTES,
|
|
86
80
|
multi: true,
|
|
87
|
-
|
|
81
|
+
useFactory: () => {
|
|
82
|
+
const extraSources = inject(ANALOG_EXTRA_ROUTE_FILE_SOURCES, { optional: true }) ?? [];
|
|
83
|
+
if (extraSources.length === 0) return createRoutes$1(ANALOG_ROUTE_FILES, (_filename, fileLoader) => fileLoader);
|
|
84
|
+
const allFiles = { ...ANALOG_ROUTE_FILES };
|
|
85
|
+
const resolverMap = /* @__PURE__ */ new Map();
|
|
86
|
+
for (const source of extraSources) for (const [key, loader] of Object.entries(source.files)) {
|
|
87
|
+
allFiles[key] = loader;
|
|
88
|
+
resolverMap.set(key, source.resolveModule);
|
|
89
|
+
}
|
|
90
|
+
return createRoutes$1(allFiles, (filename, fileLoader) => {
|
|
91
|
+
const resolver = resolverMap.get(filename);
|
|
92
|
+
return resolver ? resolver(filename, fileLoader) : fileLoader;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
88
95
|
},
|
|
96
|
+
provideAppInitializer(() => {
|
|
97
|
+
const router = inject(Router);
|
|
98
|
+
const meta = inject(Meta);
|
|
99
|
+
const document = inject(DOCUMENT, { optional: true });
|
|
100
|
+
updateMetaTagsOnRouteChange(router, meta);
|
|
101
|
+
updateJsonLdOnRouteChange(router, document);
|
|
102
|
+
}),
|
|
89
103
|
{
|
|
90
104
|
provide: ɵHTTP_ROOT_INTERCEPTOR_FNS,
|
|
91
105
|
multi: true,
|
|
@@ -99,12 +113,6 @@ function provideFileRouter(...features) {
|
|
|
99
113
|
}
|
|
100
114
|
]);
|
|
101
115
|
}
|
|
102
|
-
/**
|
|
103
|
-
* Provides extra custom routes in addition to the routes
|
|
104
|
-
* discovered from the filesystem-based routing. These routes are
|
|
105
|
-
* inserted before the filesystem-based routes, and take priority in
|
|
106
|
-
* route matching.
|
|
107
|
-
*/
|
|
108
116
|
function withExtraRoutes(routes) {
|
|
109
117
|
return {
|
|
110
118
|
ɵkind: 100,
|
|
@@ -116,10 +124,32 @@ function withExtraRoutes(routes) {
|
|
|
116
124
|
};
|
|
117
125
|
}
|
|
118
126
|
//#endregion
|
|
127
|
+
//#region packages/router/src/lib/provide-file-router.ts
|
|
128
|
+
/**
|
|
129
|
+
* Sets up providers for the Angular router, and registers
|
|
130
|
+
* file-based routes. Additional features can be provided
|
|
131
|
+
* to further configure the behavior of the router.
|
|
132
|
+
*
|
|
133
|
+
* @param features
|
|
134
|
+
* @returns Providers and features to configure the router with routes
|
|
135
|
+
*/
|
|
136
|
+
function provideFileRouter(...features) {
|
|
137
|
+
return provideFileRouterWithRoutes(...features);
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
119
140
|
//#region packages/router/src/lib/inject-load.ts
|
|
141
|
+
function isResponse(value) {
|
|
142
|
+
return typeof value === "object" && value instanceof Response;
|
|
143
|
+
}
|
|
120
144
|
function injectLoad(options) {
|
|
121
145
|
return (options?.injector ?? inject(Injector)).get(ActivatedRoute).data.pipe(map((data) => data["load"]));
|
|
122
146
|
}
|
|
147
|
+
function injectLoadData(options) {
|
|
148
|
+
return injectLoad(options).pipe(map((result) => {
|
|
149
|
+
if (isResponse(result)) throw new Error("Expected page load data but received a response.");
|
|
150
|
+
return result;
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
123
153
|
//#endregion
|
|
124
154
|
//#region packages/router/src/lib/get-load-resolver.ts
|
|
125
155
|
/**
|
|
@@ -161,6 +191,22 @@ function generateHash(str) {
|
|
|
161
191
|
}
|
|
162
192
|
//#endregion
|
|
163
193
|
//#region packages/router/src/lib/request-context.ts
|
|
194
|
+
function mergeFetchParams(requestUrl, request) {
|
|
195
|
+
const merged = /* @__PURE__ */ new Map();
|
|
196
|
+
for (const key of requestUrl.searchParams.keys()) {
|
|
197
|
+
const values = requestUrl.searchParams.getAll(key);
|
|
198
|
+
if (values.length > 0) merged.set(key, values);
|
|
199
|
+
}
|
|
200
|
+
for (const key of request.params.keys()) {
|
|
201
|
+
const values = request.params.getAll(key);
|
|
202
|
+
if (values?.length) merged.set(key, values);
|
|
203
|
+
}
|
|
204
|
+
if (merged.size === 0) return;
|
|
205
|
+
return [...merged.entries()].reduce((params, [key, values]) => {
|
|
206
|
+
params[key] = values.length === 1 ? values[0] : values;
|
|
207
|
+
return params;
|
|
208
|
+
}, {});
|
|
209
|
+
}
|
|
164
210
|
/**
|
|
165
211
|
* Interceptor that is server-aware when making HttpClient requests.
|
|
166
212
|
* Server-side requests use the full URL
|
|
@@ -181,17 +227,19 @@ function requestContextInterceptor(req, next) {
|
|
|
181
227
|
const requestUrl = new URL(req.url, baseUrl);
|
|
182
228
|
const storeKey = makeStateKey(`analog_${makeCacheKey(req, new URL(requestUrl).pathname)}`);
|
|
183
229
|
const fetchUrl = requestUrl.pathname;
|
|
230
|
+
const fetchParams = mergeFetchParams(requestUrl, req);
|
|
184
231
|
const responseType = req.responseType === "arraybuffer" ? "arrayBuffer" : req.responseType;
|
|
185
232
|
return from(serverFetch.raw(fetchUrl, {
|
|
186
233
|
method: req.method,
|
|
187
234
|
body: req.body ? req.body : void 0,
|
|
188
|
-
params:
|
|
235
|
+
params: fetchParams,
|
|
189
236
|
responseType,
|
|
190
237
|
headers: req.headers.keys().reduce((hdrs, current) => {
|
|
191
|
-
|
|
238
|
+
const value = req.headers.get(current);
|
|
239
|
+
return value != null ? {
|
|
192
240
|
...hdrs,
|
|
193
|
-
[current]:
|
|
194
|
-
};
|
|
241
|
+
[current]: value
|
|
242
|
+
} : hdrs;
|
|
195
243
|
}, {})
|
|
196
244
|
}).then((res) => {
|
|
197
245
|
const cacheResponse = {
|
|
@@ -232,62 +280,83 @@ var FormAction = class FormAction {
|
|
|
232
280
|
this.state = output();
|
|
233
281
|
this.router = inject(Router);
|
|
234
282
|
this.route = inject(ActivatedRoute);
|
|
235
|
-
this.
|
|
283
|
+
this.currentState = signal("idle", ...[]);
|
|
284
|
+
/** Cached during construction (injection context) so inject() works. */
|
|
285
|
+
this._endpointUrl = this.route ? injectRouteEndpointURL(this.route.snapshot) : void 0;
|
|
236
286
|
}
|
|
237
287
|
submitted($event) {
|
|
238
288
|
$event.preventDefault();
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
289
|
+
const form = $event.target;
|
|
290
|
+
this._emitState("submitting");
|
|
291
|
+
const body = new FormData(form);
|
|
292
|
+
if (form.method.toUpperCase() === "GET") this._handleGet(body, this._getGetPath(form));
|
|
293
|
+
else this._handlePost(body, this._getPostPath(form), form.method);
|
|
243
294
|
}
|
|
244
295
|
_handleGet(body, path) {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this.router.navigate([url], {
|
|
250
|
-
queryParams: params,
|
|
251
|
-
onSameUrlNavigation: "reload"
|
|
296
|
+
const url = new URL(path, window.location.href);
|
|
297
|
+
const params = new URLSearchParams(url.search);
|
|
298
|
+
body.forEach((value, key) => {
|
|
299
|
+
params.append(key, value instanceof File ? value.name : value);
|
|
252
300
|
});
|
|
301
|
+
url.search = params.toString();
|
|
302
|
+
this._emitState("navigate");
|
|
303
|
+
this._navigateTo(url);
|
|
253
304
|
}
|
|
254
|
-
_handlePost(body, path,
|
|
305
|
+
_handlePost(body, path, method) {
|
|
255
306
|
fetch(path, {
|
|
256
|
-
method
|
|
307
|
+
method,
|
|
257
308
|
body
|
|
258
309
|
}).then((res) => {
|
|
259
310
|
if (res.ok) if (res.redirected) {
|
|
260
|
-
|
|
261
|
-
this.
|
|
262
|
-
this.router.navigate([redirectUrl]);
|
|
311
|
+
this._emitState("redirect");
|
|
312
|
+
this._navigateTo(new URL(res.url, window.location.href));
|
|
263
313
|
} else if (this._isJSON(res.headers.get("Content-type"))) res.json().then((result) => {
|
|
264
314
|
this.onSuccess.emit(result);
|
|
265
|
-
this.
|
|
315
|
+
this._emitState("success");
|
|
266
316
|
});
|
|
267
317
|
else res.text().then((result) => {
|
|
268
318
|
this.onSuccess.emit(result);
|
|
269
|
-
this.
|
|
319
|
+
this._emitState("success");
|
|
270
320
|
});
|
|
271
321
|
else if (res.headers.get("X-Analog-Errors")) res.json().then((errors) => {
|
|
272
322
|
this.onError.emit(errors);
|
|
273
|
-
this.
|
|
323
|
+
this._emitState("error");
|
|
274
324
|
});
|
|
275
|
-
else this.
|
|
325
|
+
else this._emitState("error");
|
|
276
326
|
}).catch((_) => {
|
|
277
|
-
this.
|
|
327
|
+
this._emitState("error");
|
|
278
328
|
});
|
|
279
329
|
}
|
|
280
|
-
|
|
281
|
-
|
|
330
|
+
_getExplicitAction(form) {
|
|
331
|
+
return this.action().trim() || form.getAttribute("action")?.trim() || void 0;
|
|
332
|
+
}
|
|
333
|
+
_getGetPath(form) {
|
|
334
|
+
return this._getExplicitAction(form) ?? this.router.url;
|
|
335
|
+
}
|
|
336
|
+
_getPostPath(form) {
|
|
337
|
+
const explicitAction = this._getExplicitAction(form);
|
|
338
|
+
if (explicitAction) return new URL(explicitAction, window.location.href).toString();
|
|
339
|
+
if (this._endpointUrl) return this._endpointUrl.pathname;
|
|
282
340
|
return `/api/_analog/pages${window.location.pathname}`;
|
|
283
341
|
}
|
|
342
|
+
_emitState(state) {
|
|
343
|
+
this.currentState.set(state);
|
|
344
|
+
this.state.emit(state);
|
|
345
|
+
}
|
|
346
|
+
_navigateTo(url) {
|
|
347
|
+
if (url.origin === window.location.origin) {
|
|
348
|
+
this.router.navigateByUrl(`${url.pathname}${url.search}${url.hash}`, { onSameUrlNavigation: "reload" });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
window.location.assign(url.toString());
|
|
352
|
+
}
|
|
284
353
|
_isJSON(contentType) {
|
|
285
354
|
return (contentType ? contentType.split(";") : [])[0] === "application/json";
|
|
286
355
|
}
|
|
287
356
|
static {
|
|
288
357
|
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
289
358
|
minVersion: "12.0.0",
|
|
290
|
-
version: "21.
|
|
359
|
+
version: "21.2.8",
|
|
291
360
|
ngImport: i0,
|
|
292
361
|
type: FormAction,
|
|
293
362
|
deps: [],
|
|
@@ -297,7 +366,7 @@ var FormAction = class FormAction {
|
|
|
297
366
|
static {
|
|
298
367
|
this.ɵdir = i0.ɵɵngDeclareDirective({
|
|
299
368
|
minVersion: "17.1.0",
|
|
300
|
-
version: "21.
|
|
369
|
+
version: "21.2.8",
|
|
301
370
|
type: FormAction,
|
|
302
371
|
isStandalone: true,
|
|
303
372
|
selector: "form[action],form[method]",
|
|
@@ -313,21 +382,31 @@ var FormAction = class FormAction {
|
|
|
313
382
|
onError: "onError",
|
|
314
383
|
state: "state"
|
|
315
384
|
},
|
|
316
|
-
host: {
|
|
385
|
+
host: {
|
|
386
|
+
listeners: { "submit": "submitted($event)" },
|
|
387
|
+
properties: {
|
|
388
|
+
"attr.data-state": "currentState()",
|
|
389
|
+
"attr.aria-busy": "currentState() === \"submitting\" ? \"true\" : null"
|
|
390
|
+
}
|
|
391
|
+
},
|
|
317
392
|
ngImport: i0
|
|
318
393
|
});
|
|
319
394
|
}
|
|
320
395
|
};
|
|
321
396
|
i0.ɵɵngDeclareClassMetadata({
|
|
322
397
|
minVersion: "12.0.0",
|
|
323
|
-
version: "21.
|
|
398
|
+
version: "21.2.8",
|
|
324
399
|
ngImport: i0,
|
|
325
400
|
type: FormAction,
|
|
326
401
|
decorators: [{
|
|
327
402
|
type: Directive,
|
|
328
403
|
args: [{
|
|
329
404
|
selector: "form[action],form[method]",
|
|
330
|
-
host: {
|
|
405
|
+
host: {
|
|
406
|
+
"(submit)": `submitted($event)`,
|
|
407
|
+
"[attr.data-state]": "currentState()",
|
|
408
|
+
"[attr.aria-busy]": "currentState() === \"submitting\" ? \"true\" : null"
|
|
409
|
+
},
|
|
331
410
|
standalone: true
|
|
332
411
|
}]
|
|
333
412
|
}],
|
|
@@ -434,7 +513,7 @@ var ServerOnly = class ServerOnly {
|
|
|
434
513
|
static {
|
|
435
514
|
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
436
515
|
minVersion: "12.0.0",
|
|
437
|
-
version: "21.
|
|
516
|
+
version: "21.2.8",
|
|
438
517
|
ngImport: i0,
|
|
439
518
|
type: ServerOnly,
|
|
440
519
|
deps: [],
|
|
@@ -444,7 +523,7 @@ var ServerOnly = class ServerOnly {
|
|
|
444
523
|
static {
|
|
445
524
|
this.ɵcmp = i0.ɵɵngDeclareComponent({
|
|
446
525
|
minVersion: "17.1.0",
|
|
447
|
-
version: "21.
|
|
526
|
+
version: "21.2.8",
|
|
448
527
|
type: ServerOnly,
|
|
449
528
|
isStandalone: true,
|
|
450
529
|
selector: "server-only,ServerOnly,Server",
|
|
@@ -474,7 +553,7 @@ var ServerOnly = class ServerOnly {
|
|
|
474
553
|
};
|
|
475
554
|
i0.ɵɵngDeclareClassMetadata({
|
|
476
555
|
minVersion: "12.0.0",
|
|
477
|
-
version: "21.
|
|
556
|
+
version: "21.2.8",
|
|
478
557
|
ngImport: i0,
|
|
479
558
|
type: ServerOnly,
|
|
480
559
|
decorators: [{
|
|
@@ -510,6 +589,425 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
510
589
|
}
|
|
511
590
|
});
|
|
512
591
|
//#endregion
|
|
513
|
-
|
|
592
|
+
//#region packages/router/src/lib/validation-errors.ts
|
|
593
|
+
function getPathSegmentKey(segment) {
|
|
594
|
+
return typeof segment === "object" ? segment.key : segment;
|
|
595
|
+
}
|
|
596
|
+
function issuePathToFieldName(path) {
|
|
597
|
+
return path.map((segment) => String(getPathSegmentKey(segment))).join(".");
|
|
598
|
+
}
|
|
599
|
+
function issuesToFieldErrors(issues) {
|
|
600
|
+
return issues.reduce((errors, issue) => {
|
|
601
|
+
if (!issue.path?.length) return errors;
|
|
602
|
+
const fieldName = issuePathToFieldName(issue.path);
|
|
603
|
+
errors[fieldName] ??= [];
|
|
604
|
+
errors[fieldName].push(issue.message);
|
|
605
|
+
return errors;
|
|
606
|
+
}, {});
|
|
607
|
+
}
|
|
608
|
+
function issuesToFormErrors(issues) {
|
|
609
|
+
return issues.filter((issue) => !issue.path?.length).map((issue) => issue.message);
|
|
610
|
+
}
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region packages/router/src/lib/route-path.ts
|
|
613
|
+
/**
|
|
614
|
+
* Typed route path utilities for Analog.
|
|
615
|
+
*
|
|
616
|
+
* This module provides:
|
|
617
|
+
* - The `AnalogRouteTable` base interface (augmented by generated code)
|
|
618
|
+
* - The `AnalogRoutePath` union type
|
|
619
|
+
* - The `routePath()` URL builder function
|
|
620
|
+
*
|
|
621
|
+
* No Angular dependencies — can be used in any context.
|
|
622
|
+
*/
|
|
623
|
+
/**
|
|
624
|
+
* Builds a typed route link object from a route path pattern and options.
|
|
625
|
+
*
|
|
626
|
+
* The returned object separates path, query params, and fragment for
|
|
627
|
+
* direct use with Angular's routerLink directive inputs.
|
|
628
|
+
*
|
|
629
|
+
* @example
|
|
630
|
+
* routePath('/about')
|
|
631
|
+
* // → { path: '/about', queryParams: null, fragment: undefined }
|
|
632
|
+
*
|
|
633
|
+
* routePath('/users/[id]', { params: { id: '42' } })
|
|
634
|
+
* // → { path: '/users/42', queryParams: null, fragment: undefined }
|
|
635
|
+
*
|
|
636
|
+
* routePath('/users/[id]', { params: { id: '42' }, query: { tab: 'settings' }, hash: 'bio' })
|
|
637
|
+
* // → { path: '/users/42', queryParams: { tab: 'settings' }, fragment: 'bio' }
|
|
638
|
+
*
|
|
639
|
+
* @example Template usage
|
|
640
|
+
* ```html
|
|
641
|
+
* @let link = routePath('/users/[id]', { params: { id: userId } });
|
|
642
|
+
* <a [routerLink]="link.path" [queryParams]="link.queryParams" [fragment]="link.fragment">
|
|
643
|
+
* ```
|
|
644
|
+
*/
|
|
645
|
+
function routePath(path, ...args) {
|
|
646
|
+
const options = args[0];
|
|
647
|
+
return buildRouteLink(path, options);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Internal: builds a `RouteLinkResult` from path and options.
|
|
651
|
+
* Exported for direct use in tests (avoids generic constraints).
|
|
652
|
+
*/
|
|
653
|
+
function buildRouteLink(path, options) {
|
|
654
|
+
const resolvedPath = buildPath(path, options?.params);
|
|
655
|
+
let queryParams = null;
|
|
656
|
+
if (options?.query) {
|
|
657
|
+
const filtered = {};
|
|
658
|
+
let hasEntries = false;
|
|
659
|
+
for (const [key, value] of Object.entries(options.query)) if (value !== void 0) {
|
|
660
|
+
filtered[key] = value;
|
|
661
|
+
hasEntries = true;
|
|
662
|
+
}
|
|
663
|
+
if (hasEntries) queryParams = filtered;
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
path: resolvedPath,
|
|
667
|
+
queryParams,
|
|
668
|
+
fragment: options?.hash
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Resolves param placeholders and normalises slashes.
|
|
673
|
+
* Returns only the path — no query string or hash.
|
|
674
|
+
*/
|
|
675
|
+
function buildPath(path, params) {
|
|
676
|
+
let url = path;
|
|
677
|
+
if (params) {
|
|
678
|
+
url = url.replace(/\[\[\.\.\.([^\]]+)\]\]/g, (_, name) => {
|
|
679
|
+
const value = params[name];
|
|
680
|
+
if (value == null) return "";
|
|
681
|
+
if (Array.isArray(value)) return value.map((v) => encodeURIComponent(v)).join("/");
|
|
682
|
+
return encodeURIComponent(String(value));
|
|
683
|
+
});
|
|
684
|
+
url = url.replace(/\[\.\.\.([^\]]+)\]/g, (_, name) => {
|
|
685
|
+
const value = params[name];
|
|
686
|
+
if (value == null) throw new Error(`Missing required catch-all param "${name}" for path "${path}"`);
|
|
687
|
+
if (Array.isArray(value)) {
|
|
688
|
+
if (value.length === 0) throw new Error(`Missing required catch-all param "${name}" for path "${path}"`);
|
|
689
|
+
return value.map((v) => encodeURIComponent(v)).join("/");
|
|
690
|
+
}
|
|
691
|
+
return encodeURIComponent(String(value));
|
|
692
|
+
});
|
|
693
|
+
url = url.replace(/\[([^\]]+)\]/g, (_, name) => {
|
|
694
|
+
const value = params[name];
|
|
695
|
+
if (value == null) throw new Error(`Missing required param "${name}" for path "${path}"`);
|
|
696
|
+
return encodeURIComponent(String(value));
|
|
697
|
+
});
|
|
698
|
+
} else {
|
|
699
|
+
url = url.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "");
|
|
700
|
+
url = url.replace(/\[\.\.\.([^\]]+)\]/g, "");
|
|
701
|
+
url = url.replace(/\[([^\]]+)\]/g, "");
|
|
702
|
+
}
|
|
703
|
+
url = url.replace(/\/+/g, "/");
|
|
704
|
+
if (url.length > 1 && url.endsWith("/")) url = url.slice(0, -1);
|
|
705
|
+
if (!url.startsWith("/")) url = "/" + url;
|
|
706
|
+
return url;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Internal URL builder. Separated from `routePath` so it can be
|
|
710
|
+
* used without generic constraints (e.g., in `injectNavigate`).
|
|
711
|
+
*/
|
|
712
|
+
function buildUrl(path, options) {
|
|
713
|
+
let url = buildPath(path, options?.params);
|
|
714
|
+
if (options?.query) {
|
|
715
|
+
const parts = [];
|
|
716
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
717
|
+
if (value === void 0) continue;
|
|
718
|
+
if (Array.isArray(value)) for (const v of value) parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(v)}`);
|
|
719
|
+
else parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
720
|
+
}
|
|
721
|
+
if (parts.length > 0) url += "?" + parts.join("&");
|
|
722
|
+
}
|
|
723
|
+
if (options?.hash) url += "#" + options.hash;
|
|
724
|
+
return url;
|
|
725
|
+
}
|
|
726
|
+
//#endregion
|
|
727
|
+
//#region packages/router/src/lib/inject-navigate.ts
|
|
728
|
+
function isRoutePathOptionsBase(value) {
|
|
729
|
+
return !!value && typeof value === "object" && ("params" in value || "query" in value || "hash" in value);
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Injects a typed navigate function.
|
|
733
|
+
*
|
|
734
|
+
* @example
|
|
735
|
+
* ```ts
|
|
736
|
+
* const navigate = injectNavigate();
|
|
737
|
+
*
|
|
738
|
+
* navigate('/users/[id]', { params: { id: '42' } }); // ✅
|
|
739
|
+
* navigate('/users/[id]', { params: { id: 42 } }); // ❌ type error
|
|
740
|
+
*
|
|
741
|
+
* // With navigation extras
|
|
742
|
+
* navigate('/users/[id]', { params: { id: '42' } }, { replaceUrl: true });
|
|
743
|
+
* ```
|
|
744
|
+
*/
|
|
745
|
+
function injectNavigate() {
|
|
746
|
+
const router = inject(Router);
|
|
747
|
+
const navigate = ((path, ...args) => {
|
|
748
|
+
let options;
|
|
749
|
+
let extras;
|
|
750
|
+
if (args.length > 1) {
|
|
751
|
+
options = args[0];
|
|
752
|
+
extras = args[1];
|
|
753
|
+
} else if (args.length === 1) if (isRoutePathOptionsBase(args[0])) options = args[0];
|
|
754
|
+
else extras = args[0];
|
|
755
|
+
const url = buildUrl(path, options);
|
|
756
|
+
return router.navigateByUrl(url, extras);
|
|
757
|
+
});
|
|
758
|
+
return navigate;
|
|
759
|
+
}
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region packages/router/src/lib/experimental.ts
|
|
762
|
+
/** @experimental */
|
|
763
|
+
var EXPERIMENTAL_TYPED_ROUTER = new InjectionToken("EXPERIMENTAL_TYPED_ROUTER");
|
|
764
|
+
/** @experimental */
|
|
765
|
+
var EXPERIMENTAL_ROUTE_CONTEXT = new InjectionToken("EXPERIMENTAL_ROUTE_CONTEXT");
|
|
766
|
+
/** @experimental */
|
|
767
|
+
var EXPERIMENTAL_LOADER_CACHE = new InjectionToken("EXPERIMENTAL_LOADER_CACHE");
|
|
768
|
+
/**
|
|
769
|
+
* Enables experimental typed router features.
|
|
770
|
+
*
|
|
771
|
+
* When active, `routePath()`, `injectNavigate()`, `injectParams()`,
|
|
772
|
+
* and `injectQuery()` will enforce route table constraints and
|
|
773
|
+
* optionally log warnings in strict mode.
|
|
774
|
+
*
|
|
775
|
+
* Inspired by TanStack Router's `Register` interface and strict type
|
|
776
|
+
* checking across the entire navigation surface.
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* ```ts
|
|
780
|
+
* provideFileRouter(
|
|
781
|
+
* withTypedRouter({ strictRouteParams: true }),
|
|
782
|
+
* )
|
|
783
|
+
* ```
|
|
784
|
+
*
|
|
785
|
+
* @experimental
|
|
786
|
+
*/
|
|
787
|
+
function withTypedRouter(options) {
|
|
788
|
+
return {
|
|
789
|
+
ɵkind: 102,
|
|
790
|
+
ɵproviders: [{
|
|
791
|
+
provide: EXPERIMENTAL_TYPED_ROUTER,
|
|
792
|
+
useValue: {
|
|
793
|
+
strictRouteParams: false,
|
|
794
|
+
...options
|
|
795
|
+
}
|
|
796
|
+
}]
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Provides root-level route context available to all route loaders
|
|
801
|
+
* and components via `injectRouteContext()`.
|
|
802
|
+
*
|
|
803
|
+
* Inspired by TanStack Router's `createRootRouteWithContext<T>()` where
|
|
804
|
+
* a typed context object is required at router creation and automatically
|
|
805
|
+
* available in every route's `beforeLoad` and `loader`.
|
|
806
|
+
*
|
|
807
|
+
* In Angular terms, this creates a DI token that server-side load
|
|
808
|
+
* functions and components can inject to access shared services
|
|
809
|
+
* without importing them individually.
|
|
810
|
+
*
|
|
811
|
+
* @example
|
|
812
|
+
* ```ts
|
|
813
|
+
* // app.config.ts
|
|
814
|
+
* provideFileRouter(
|
|
815
|
+
* withRouteContext({
|
|
816
|
+
* auth: inject(AuthService),
|
|
817
|
+
* db: inject(DatabaseService),
|
|
818
|
+
* }),
|
|
819
|
+
* )
|
|
820
|
+
*
|
|
821
|
+
* // In a component
|
|
822
|
+
* const ctx = injectRouteContext<{ auth: AuthService; db: DatabaseService }>();
|
|
823
|
+
* ```
|
|
824
|
+
*
|
|
825
|
+
* @experimental
|
|
826
|
+
*/
|
|
827
|
+
function withRouteContext(context) {
|
|
828
|
+
return {
|
|
829
|
+
ɵkind: 103,
|
|
830
|
+
ɵproviders: [{
|
|
831
|
+
provide: EXPERIMENTAL_ROUTE_CONTEXT,
|
|
832
|
+
useValue: context
|
|
833
|
+
}]
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Configures experimental loader caching behavior for server-loaded
|
|
838
|
+
* route data.
|
|
839
|
+
*
|
|
840
|
+
* Inspired by TanStack Router's built-in cache where `createRouter()`
|
|
841
|
+
* accepts `defaultStaleTime` and `defaultGcTime` to control when
|
|
842
|
+
* loaders re-execute and when cached data is discarded.
|
|
843
|
+
*
|
|
844
|
+
* @example
|
|
845
|
+
* ```ts
|
|
846
|
+
* provideFileRouter(
|
|
847
|
+
* withLoaderCaching({
|
|
848
|
+
* defaultStaleTime: 30_000, // 30s before re-fetch
|
|
849
|
+
* defaultGcTime: 300_000, // 5min cache retention
|
|
850
|
+
* defaultPendingMs: 200, // 200ms loading delay
|
|
851
|
+
* }),
|
|
852
|
+
* )
|
|
853
|
+
* ```
|
|
854
|
+
*
|
|
855
|
+
* @experimental
|
|
856
|
+
*/
|
|
857
|
+
function withLoaderCaching(options) {
|
|
858
|
+
return {
|
|
859
|
+
ɵkind: 104,
|
|
860
|
+
ɵproviders: [{
|
|
861
|
+
provide: EXPERIMENTAL_LOADER_CACHE,
|
|
862
|
+
useValue: {
|
|
863
|
+
defaultStaleTime: 0,
|
|
864
|
+
defaultGcTime: 3e5,
|
|
865
|
+
defaultPendingMs: 0,
|
|
866
|
+
...options
|
|
867
|
+
}
|
|
868
|
+
}]
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region packages/router/src/lib/inject-typed-params.ts
|
|
873
|
+
function extractRouteParams(routePath) {
|
|
874
|
+
const params = [];
|
|
875
|
+
for (const match of routePath.matchAll(/\[\[\.\.\.([^\]]+)\]\]/g)) params.push({
|
|
876
|
+
name: match[1],
|
|
877
|
+
type: "optionalCatchAll"
|
|
878
|
+
});
|
|
879
|
+
for (const match of routePath.matchAll(/(?<!\[)\[\.\.\.([^\]]+)\](?!\])/g)) params.push({
|
|
880
|
+
name: match[1],
|
|
881
|
+
type: "catchAll"
|
|
882
|
+
});
|
|
883
|
+
for (const match of routePath.matchAll(/(?<!\[)\[(?!\.)([^\]]+)\](?!\])/g)) params.push({
|
|
884
|
+
name: match[1],
|
|
885
|
+
type: "dynamic"
|
|
886
|
+
});
|
|
887
|
+
return params;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* When `strictRouteParams` is enabled, warns if expected params from the
|
|
891
|
+
* `_from` pattern are missing from the active `ActivatedRoute`.
|
|
892
|
+
*/
|
|
893
|
+
function assertRouteMatch(from, route, kind) {
|
|
894
|
+
const expectedParams = extractRouteParams(from).filter((param) => param.type === "dynamic" || param.type === "catchAll").map((param) => param.name);
|
|
895
|
+
if (expectedParams.length === 0) return;
|
|
896
|
+
route.params.pipe(take(1)).subscribe((params) => {
|
|
897
|
+
for (const name of expectedParams) if (!(name in params)) {
|
|
898
|
+
console.warn(`[Analog] ${kind}('${from}'): expected param "${name}" is not present in the active route's params. Ensure this hook is used inside a component rendered by '${from}'.`);
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Injects typed route params as a signal, constrained by the route table.
|
|
905
|
+
*
|
|
906
|
+
* Inspired by TanStack Router's `useParams({ from: '/users/$userId' })`
|
|
907
|
+
* pattern where the `from` parameter narrows the return type to only
|
|
908
|
+
* the params defined for that route.
|
|
909
|
+
*
|
|
910
|
+
* The `from` parameter is used purely for TypeScript type inference —
|
|
911
|
+
* at runtime, params are read from the current `ActivatedRoute`. This
|
|
912
|
+
* means it works correctly when used inside a component rendered by
|
|
913
|
+
* the specified route.
|
|
914
|
+
*
|
|
915
|
+
* When `withTypedRouter({ strictRouteParams: true })` is configured,
|
|
916
|
+
* a dev-mode assertion checks that the expected params from `from`
|
|
917
|
+
* exist in the active route and warns on mismatch.
|
|
918
|
+
*
|
|
919
|
+
* @example
|
|
920
|
+
* ```ts
|
|
921
|
+
* // In a component rendered at /users/[id]
|
|
922
|
+
* const params = injectParams('/users/[id]');
|
|
923
|
+
* // params() → { id: string }
|
|
924
|
+
*
|
|
925
|
+
* // With schema validation output types
|
|
926
|
+
* const params = injectParams('/products/[slug]');
|
|
927
|
+
* // params() → validated output type from routeParamsSchema
|
|
928
|
+
* ```
|
|
929
|
+
*
|
|
930
|
+
* @experimental
|
|
931
|
+
*/
|
|
932
|
+
function injectParams(_from, options) {
|
|
933
|
+
const injector = options?.injector;
|
|
934
|
+
const route = injector ? injector.get(ActivatedRoute) : inject(ActivatedRoute);
|
|
935
|
+
if (isDevMode()) {
|
|
936
|
+
if ((injector ? injector.get(EXPERIMENTAL_TYPED_ROUTER, null) : inject(EXPERIMENTAL_TYPED_ROUTER, { optional: true }))?.strictRouteParams) assertRouteMatch(_from, route, "injectParams");
|
|
937
|
+
}
|
|
938
|
+
return toSignal(route.params.pipe(map((params) => params)), { requireSync: true });
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Injects typed route query params as a signal, constrained by the
|
|
942
|
+
* route table.
|
|
943
|
+
*
|
|
944
|
+
* Inspired by TanStack Router's `useSearch({ from: '/issues' })` pattern
|
|
945
|
+
* where search params are validated and typed per-route via
|
|
946
|
+
* `validateSearch` schemas.
|
|
947
|
+
*
|
|
948
|
+
* In Analog, the typing comes from `routeQuerySchema` exports that are
|
|
949
|
+
* detected at build time and recorded in the generated route table.
|
|
950
|
+
*
|
|
951
|
+
* The `from` parameter is used purely for TypeScript type inference.
|
|
952
|
+
* When `withTypedRouter({ strictRouteParams: true })` is configured,
|
|
953
|
+
* a dev-mode assertion checks that the expected params from `from`
|
|
954
|
+
* exist in the active route and warns on mismatch.
|
|
955
|
+
*
|
|
956
|
+
* @example
|
|
957
|
+
* ```ts
|
|
958
|
+
* // In a component rendered at /issues
|
|
959
|
+
* // (where routeQuerySchema validates { page: number, status: string })
|
|
960
|
+
* const query = injectQuery('/issues');
|
|
961
|
+
* // query() → { page: number; status: string }
|
|
962
|
+
* ```
|
|
963
|
+
*
|
|
964
|
+
* @experimental
|
|
965
|
+
*/
|
|
966
|
+
function injectQuery(_from, options) {
|
|
967
|
+
const injector = options?.injector;
|
|
968
|
+
const route = injector ? injector.get(ActivatedRoute) : inject(ActivatedRoute);
|
|
969
|
+
if (isDevMode()) {
|
|
970
|
+
if ((injector ? injector.get(EXPERIMENTAL_TYPED_ROUTER, null) : inject(EXPERIMENTAL_TYPED_ROUTER, { optional: true }))?.strictRouteParams) assertRouteMatch(_from, route, "injectQuery");
|
|
971
|
+
}
|
|
972
|
+
return toSignal(route.queryParams.pipe(map((params) => params)), { requireSync: true });
|
|
973
|
+
}
|
|
974
|
+
//#endregion
|
|
975
|
+
//#region packages/router/src/lib/inject-route-context.ts
|
|
976
|
+
/**
|
|
977
|
+
* Injects the root route context provided via `withRouteContext()`.
|
|
978
|
+
*
|
|
979
|
+
* Inspired by TanStack Router's context inheritance where
|
|
980
|
+
* `createRootRouteWithContext<T>()` makes a typed context available
|
|
981
|
+
* to every route's `beforeLoad` and `loader` callbacks.
|
|
982
|
+
*
|
|
983
|
+
* In Angular, this uses DI under the hood — `withRouteContext(ctx)`
|
|
984
|
+
* provides the value, and `injectRouteContext<T>()` retrieves it
|
|
985
|
+
* with the expected type.
|
|
986
|
+
*
|
|
987
|
+
* @example
|
|
988
|
+
* ```ts
|
|
989
|
+
* // app.config.ts
|
|
990
|
+
* provideFileRouter(
|
|
991
|
+
* withRouteContext({
|
|
992
|
+
* auth: inject(AuthService),
|
|
993
|
+
* analytics: inject(AnalyticsService),
|
|
994
|
+
* }),
|
|
995
|
+
* )
|
|
996
|
+
*
|
|
997
|
+
* // any-page.page.ts
|
|
998
|
+
* const ctx = injectRouteContext<{
|
|
999
|
+
* auth: AuthService;
|
|
1000
|
+
* analytics: AnalyticsService;
|
|
1001
|
+
* }>();
|
|
1002
|
+
* ctx.analytics.trackPageView();
|
|
1003
|
+
* ```
|
|
1004
|
+
*
|
|
1005
|
+
* @experimental
|
|
1006
|
+
*/
|
|
1007
|
+
function injectRouteContext() {
|
|
1008
|
+
return inject(EXPERIMENTAL_ROUTE_CONTEXT);
|
|
1009
|
+
}
|
|
1010
|
+
//#endregion
|
|
1011
|
+
export { EXPERIMENTAL_LOADER_CACHE, EXPERIMENTAL_ROUTE_CONTEXT, EXPERIMENTAL_TYPED_ROUTER, FormAction, ServerOnly, createRoutes, defineRouteMeta, getLoadResolver, injectActivatedRoute, injectDebugRoutes, injectLoad, injectLoadData, injectNavigate, injectParams, injectQuery, injectRouteContext, injectRouteEndpointURL, injectRouter, issuePathToFieldName, issuesToFieldErrors, issuesToFormErrors, provideFileRouter, requestContextInterceptor, routePath, routes, withDebugRoutes, withExtraRoutes, withLoaderCaching, withRouteContext, withTypedRouter };
|
|
514
1012
|
|
|
515
1013
|
//# sourceMappingURL=analogjs-router.mjs.map
|