@analogjs/router 3.0.0-alpha.5 → 3.0.0-alpha.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/content/package.json +4 -0
  2. package/fesm2022/analogjs-router-content.mjs +63 -0
  3. package/fesm2022/analogjs-router-content.mjs.map +1 -0
  4. package/fesm2022/analogjs-router-i18n.mjs +156 -0
  5. package/fesm2022/analogjs-router-i18n.mjs.map +1 -0
  6. package/fesm2022/analogjs-router-server-actions.mjs +309 -1
  7. package/fesm2022/analogjs-router-server-actions.mjs.map +1 -0
  8. package/fesm2022/analogjs-router-server.mjs +60 -3
  9. package/fesm2022/analogjs-router-server.mjs.map +1 -0
  10. package/fesm2022/analogjs-router-tanstack-query-server.mjs +22 -0
  11. package/fesm2022/analogjs-router-tanstack-query-server.mjs.map +1 -0
  12. package/fesm2022/analogjs-router-tanstack-query.mjs +39 -0
  13. package/fesm2022/analogjs-router-tanstack-query.mjs.map +1 -0
  14. package/fesm2022/analogjs-router-tokens.mjs +7 -2
  15. package/fesm2022/analogjs-router-tokens.mjs.map +1 -0
  16. package/fesm2022/analogjs-router.mjs +559 -61
  17. package/fesm2022/analogjs-router.mjs.map +1 -0
  18. package/fesm2022/debug.page.mjs +53 -31
  19. package/fesm2022/debug.page.mjs.map +1 -0
  20. package/fesm2022/provide-analog-query.mjs +23 -0
  21. package/fesm2022/provide-analog-query.mjs.map +1 -0
  22. package/fesm2022/route-files.mjs +361 -0
  23. package/fesm2022/route-files.mjs.map +1 -0
  24. package/fesm2022/routes.mjs +5 -278
  25. package/fesm2022/routes.mjs.map +1 -0
  26. package/i18n/package.json +4 -0
  27. package/package.json +76 -25
  28. package/tanstack-query/package.json +4 -0
  29. package/tanstack-query/server/package.json +4 -0
  30. package/types/content/src/index.d.ts +4 -0
  31. package/types/content/src/lib/debug/routes.d.ts +10 -0
  32. package/types/{src → content/src}/lib/markdown-helpers.d.ts +1 -1
  33. package/types/content/src/lib/routes.d.ts +8 -0
  34. package/types/content/src/lib/with-content-routes.d.ts +2 -0
  35. package/types/i18n/src/index.d.ts +1 -0
  36. package/types/i18n/src/provide-i18n.d.ts +92 -0
  37. package/types/server/actions/src/define-action.d.ts +54 -0
  38. package/types/server/actions/src/define-api-route.d.ts +57 -0
  39. package/types/server/actions/src/define-page-load.d.ts +55 -0
  40. package/types/server/actions/src/define-server-route.d.ts +68 -0
  41. package/types/server/actions/src/index.d.ts +9 -1
  42. package/types/server/actions/src/parse-request-data.d.ts +9 -0
  43. package/types/server/actions/src/validate.d.ts +8 -0
  44. package/types/server/src/provide-server-context.d.ts +15 -1
  45. package/types/server/src/render.d.ts +1 -1
  46. package/types/server/src/server-component-render.d.ts +1 -1
  47. package/types/src/index.d.ts +16 -5
  48. package/types/src/lib/cache-key.d.ts +1 -1
  49. package/types/src/lib/cookie-interceptor.d.ts +1 -1
  50. package/types/src/lib/debug/debug.page.d.ts +4 -2
  51. package/types/src/lib/define-route.d.ts +6 -1
  52. package/types/src/lib/endpoints.d.ts +1 -1
  53. package/types/src/lib/experimental.d.ts +140 -0
  54. package/types/src/lib/form-action.directive.d.ts +12 -5
  55. package/types/src/lib/inject-load.d.ts +5 -2
  56. package/types/src/lib/inject-navigate.d.ts +23 -0
  57. package/types/src/lib/inject-route-context.d.ts +32 -0
  58. package/types/src/lib/inject-typed-params.d.ts +63 -0
  59. package/types/src/lib/json-ld.d.ts +32 -0
  60. package/types/src/lib/meta-tags.d.ts +3 -1
  61. package/types/src/lib/models.d.ts +3 -0
  62. package/types/src/lib/provide-file-router-base.d.ts +4 -0
  63. package/types/src/lib/provide-file-router.d.ts +2 -8
  64. package/types/src/lib/route-builder.d.ts +5 -0
  65. package/types/src/lib/route-files.d.ts +18 -0
  66. package/types/src/lib/route-path.d.ts +124 -0
  67. package/types/src/lib/route-types.d.ts +2 -1
  68. package/types/src/lib/routes.d.ts +2 -10
  69. package/types/src/lib/validation-errors.d.ts +7 -0
  70. package/types/tanstack-query/server/src/index.d.ts +1 -0
  71. package/types/tanstack-query/src/index.d.ts +2 -0
  72. package/types/tanstack-query/src/provide-analog-query.d.ts +4 -0
  73. package/types/tanstack-query/src/provide-server-analog-query.d.ts +2 -0
  74. package/types/tanstack-query/src/server-query.d.ts +16 -0
  75. package/types/tokens/src/index.d.ts +2 -0
@@ -0,0 +1,4 @@
1
+ {
2
+ "module": "../fesm2022/analogjs-router-content.mjs",
3
+ "typings": "../types/content/src/index.d.ts"
4
+ }
@@ -0,0 +1,63 @@
1
+ import { r as createRoutes, t as ANALOG_EXTRA_ROUTE_FILE_SOURCES } from "./route-files.mjs";
2
+ import { InjectionToken, inject } from "@angular/core";
3
+ import { ContentRenderer, MarkdownRouteComponent, parseRawContentFile } from "@analogjs/content";
4
+ //#region packages/router/content/src/lib/markdown-helpers.ts
5
+ var isNgZoneEnabled = typeof Zone !== "undefined" && !!Zone.root;
6
+ function toMarkdownModule(markdownFileFactory) {
7
+ return async () => {
8
+ const { content, attributes } = parseRawContentFile(await (isNgZoneEnabled ? Zone.root.run(markdownFileFactory) : markdownFileFactory()));
9
+ const { title, meta, jsonLd } = attributes;
10
+ return {
11
+ default: MarkdownRouteComponent,
12
+ routeMeta: {
13
+ data: { _analogContent: content },
14
+ title,
15
+ meta,
16
+ jsonLd,
17
+ resolve: { renderedAnalogContent: async () => {
18
+ const rendered = await inject(ContentRenderer).render(content);
19
+ return typeof rendered === "string" ? rendered : rendered.content;
20
+ } }
21
+ }
22
+ };
23
+ };
24
+ }
25
+ //#endregion
26
+ //#region packages/router/content/src/lib/routes.ts
27
+ /**
28
+ * This variable reference is replaced with a glob of all content routes.
29
+ */
30
+ var ANALOG_CONTENT_ROUTE_FILES = {};
31
+ function createContentRoutes(files, debug = false) {
32
+ return createRoutes(files, (filename, fileLoader) => filename.endsWith(".md") ? toMarkdownModule(fileLoader) : fileLoader, debug);
33
+ }
34
+ //#endregion
35
+ //#region packages/router/content/src/lib/with-content-routes.ts
36
+ function withContentRoutes() {
37
+ return {
38
+ ɵkind: 101,
39
+ ɵproviders: [{
40
+ provide: ANALOG_EXTRA_ROUTE_FILE_SOURCES,
41
+ multi: true,
42
+ useValue: {
43
+ files: ANALOG_CONTENT_ROUTE_FILES,
44
+ resolveModule: (filename, fileLoader) => filename.endsWith(".md") ? toMarkdownModule(fileLoader) : fileLoader
45
+ }
46
+ }]
47
+ };
48
+ }
49
+ //#endregion
50
+ //#region packages/router/content/src/lib/debug/routes.ts
51
+ var DEBUG_CONTENT_ROUTES = new InjectionToken("@analogjs/router/content debug routes", {
52
+ providedIn: "root",
53
+ factory() {
54
+ return createContentRoutes(ANALOG_CONTENT_ROUTE_FILES, true);
55
+ }
56
+ });
57
+ function injectDebugContentRoutes() {
58
+ return inject(DEBUG_CONTENT_ROUTES);
59
+ }
60
+ //#endregion
61
+ export { ANALOG_CONTENT_ROUTE_FILES, createContentRoutes, injectDebugContentRoutes, withContentRoutes };
62
+
63
+ //# sourceMappingURL=analogjs-router-content.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analogjs-router-content.mjs","names":[],"sources":["../../content/src/lib/markdown-helpers.ts","../../content/src/lib/routes.ts","../../content/src/lib/with-content-routes.ts","../../content/src/lib/debug/routes.ts"],"sourcesContent":["import { inject } from '@angular/core';\nimport {\n ContentRenderer,\n MarkdownRouteComponent,\n parseRawContentFile,\n} from '@analogjs/content';\n\nimport type { RouteExport } from '../../../src/lib/models';\n\ndeclare const Zone: any;\n\ntype RenderResult = string | { content: string };\ntype ContentRendererLike = {\n render: (content: string) => Promise<RenderResult>;\n};\n\nconst isNgZoneEnabled = typeof Zone !== 'undefined' && !!Zone.root;\n\nexport function toMarkdownModule(\n markdownFileFactory: () => Promise<string>,\n): () => Promise<RouteExport> {\n return async () => {\n const markdownFile = await (isNgZoneEnabled\n ? Zone.root.run(markdownFileFactory)\n : markdownFileFactory());\n\n const { content, attributes } = parseRawContentFile(markdownFile);\n const { title, meta, jsonLd } = attributes;\n\n return {\n default: MarkdownRouteComponent,\n routeMeta: {\n data: { _analogContent: content },\n title,\n meta,\n jsonLd,\n resolve: {\n renderedAnalogContent: async () => {\n const contentRenderer = inject<any>(\n ContentRenderer as any,\n ) as ContentRendererLike;\n const rendered = await contentRenderer.render(content);\n return typeof rendered === 'string' ? rendered : rendered.content;\n },\n },\n },\n };\n };\n}\n","import type { Route } from '@angular/router';\n\nimport type { RouteExport } from '../../../src/lib/models';\nimport { createRoutes as createBaseRoutes } from '../../../src/lib/route-builder';\nimport { toMarkdownModule } from './markdown-helpers';\n\n/**\n * This variable reference is replaced with a glob of all content routes.\n */\nexport const ANALOG_CONTENT_ROUTE_FILES = {};\n\nexport type Files = Record<string, () => Promise<RouteExport | string>>;\n\nexport function createContentRoutes(files: Files, debug = false): Route[] {\n return createBaseRoutes(\n files,\n (filename, fileLoader) =>\n filename.endsWith('.md')\n ? toMarkdownModule(fileLoader as () => Promise<string>)\n : (fileLoader as () => Promise<RouteExport>),\n debug,\n );\n}\n","import { type RouterFeatures } from '@angular/router';\n\nimport { ANALOG_CONTENT_ROUTE_FILES } from './routes';\nimport { toMarkdownModule } from './markdown-helpers';\nimport type { RouteExport } from '../../../src/lib/models';\nimport {\n ANALOG_EXTRA_ROUTE_FILE_SOURCES,\n type ExtraRouteFileSource,\n} from '../../../src/lib/route-files';\n\nexport function withContentRoutes(): RouterFeatures {\n return {\n ɵkind: 101 as number,\n ɵproviders: [\n {\n provide: ANALOG_EXTRA_ROUTE_FILE_SOURCES,\n multi: true,\n useValue: {\n files: ANALOG_CONTENT_ROUTE_FILES,\n resolveModule: (\n filename: string,\n fileLoader: () => Promise<unknown>,\n ) =>\n filename.endsWith('.md')\n ? toMarkdownModule(fileLoader as () => Promise<string>)\n : (fileLoader as () => Promise<RouteExport>),\n } satisfies ExtraRouteFileSource,\n },\n ],\n };\n}\n","import { inject, InjectionToken } from '@angular/core';\nimport { Route } from '@angular/router';\n\nimport { ANALOG_CONTENT_ROUTE_FILES, createContentRoutes } from '../routes';\n\nexport const DEBUG_CONTENT_ROUTES: InjectionToken<(Route & DebugRoute)[]> =\n new InjectionToken<(Route & DebugRoute)[]>(\n '@analogjs/router/content debug routes',\n {\n providedIn: 'root',\n factory() {\n const debugRoutes = createContentRoutes(\n ANALOG_CONTENT_ROUTE_FILES as Record<string, () => Promise<string>>,\n true,\n );\n\n return debugRoutes as (Route & DebugRoute)[];\n },\n },\n );\n\nexport type DebugRoute = {\n path: string;\n filename: string;\n isLayout: boolean;\n children?: DebugRoute[];\n};\n\nexport function injectDebugContentRoutes(): (Route & DebugRoute)[] {\n return inject(DEBUG_CONTENT_ROUTES);\n}\n"],"mappings":";;;;AAgBA,IAAM,kBAAkB,OAAO,SAAS,eAAe,CAAC,CAAC,KAAK;AAE9D,SAAgB,iBACd,qBAC4B;AAC5B,QAAO,YAAY;EAQjB,MAAO,EAAA,SAAA,eAAA,oBAPc,OAAO,kBAIpB,KAAS,KAAA,IAAA,oBAAe,GACxB,qBAAwB,EAEzB;EACL,MAAS,EAAA,OAAA,MAAA,WAAA;AACT,SAAW;GACD,SAAA;GACR,WAAA;IACA,MAAA,EAAA,gBAAA,SAAA;IACA;IAEE;IACQ;IAGA,SAAW,EACH,uBAAwB,YAAW;;AAIxD,YAAA,OAAA,aAAA,WAAA,WAAA,SAAA;;;;;;;;;;;ACrCL,IAAa,6BAA6B,EAAE;AAI5C,SAAgB,oBAAoB,OAAc,QAAQ,OAAgB;AACxE,QAAO,aACL,QACC,UAAU,eACT,SAAS,SAAS,MACd,GAAA,iBAAA,WAAA,GAAA,YAAA,MAAA;;;;ACRV,SAAgB,oBAAoC;AAClD,QAAO;EACL,OAAO;EACP,YACE,CACW;GACF,SAAA;GACG,OAAA;GACD,UAAA;IAEL,OAAA;wEAOL,iBAAA,WAAA,GAEJ;;;;;;;ACxBH,IAAa,uBACX,IAAI,eACF,yCACA;CACE,YAAY;CACZ,UAAU;AAMR,SALoB,oBAClB,4BAED,KAAA;;CAIJ,CACF;AASH,SAAgB,2BAAmD;AACjE,QAAO,OAAO,qBAAqB"}
@@ -0,0 +1,156 @@
1
+ import { InjectionToken, assertInInjectionContext, inject, makeEnvironmentProviders, provideAppInitializer } from "@angular/core";
2
+ import { LOCALE, REQUEST } from "@analogjs/router/tokens";
3
+ //#region packages/router/i18n/src/provide-i18n.ts
4
+ /**
5
+ * Injection token for the resolved i18n configuration.
6
+ * Provided by `provideI18n()` and consumed by `injectSwitchLocale()`.
7
+ * @internal
8
+ */
9
+ var I18N_CONFIG = new InjectionToken("@analogjs/router I18n Config");
10
+ /**
11
+ * Resolves the full i18n config by merging explicit values with
12
+ * build-time globals injected by the platform plugin.
13
+ */
14
+ function resolveI18nConfig(config) {
15
+ const defaultLocale = config.defaultLocale ?? (typeof ANALOG_I18N_DEFAULT_LOCALE !== "undefined" ? ANALOG_I18N_DEFAULT_LOCALE : void 0);
16
+ const locales = config.locales ?? (typeof ANALOG_I18N_LOCALES !== "undefined" ? ANALOG_I18N_LOCALES : void 0);
17
+ if (!defaultLocale || !locales) throw new Error("[@analogjs/router] provideI18n() requires defaultLocale and locales. Either pass them explicitly or configure i18n in the analog() plugin in vite.config.ts.");
18
+ return {
19
+ defaultLocale,
20
+ locales,
21
+ loader: config.loader
22
+ };
23
+ }
24
+ /**
25
+ * Provides runtime i18n support using Angular's $localize.
26
+ *
27
+ * This provider:
28
+ * 1. Detects the active locale from the URL or falls back to the default.
29
+ * 2. Makes the current locale available via the LOCALE injection token.
30
+ * 3. Loads translations for the active locale at startup using $localize.
31
+ *
32
+ * Works in both SSR and client-only modes. On the client, locale is detected
33
+ * from `window.location.pathname`. On the server, locale is detected from
34
+ * the request in `provideServerContext()` and provided at the platform level;
35
+ * this function does not shadow it.
36
+ *
37
+ * When the platform plugin is configured with `i18n` in `vite.config.ts`,
38
+ * `defaultLocale` and `locales` are injected automatically — only
39
+ * `loader` is required:
40
+ *
41
+ * ```typescript
42
+ * provideI18n({
43
+ * loader: (locale) => import(`./i18n/${locale}.json`),
44
+ * })
45
+ * ```
46
+ */
47
+ function provideI18n(config) {
48
+ const resolved = resolveI18nConfig(config);
49
+ const localeProviders = typeof window !== "undefined" ? [{
50
+ provide: LOCALE,
51
+ useValue: detectClientLocale(resolved)
52
+ }] : [];
53
+ return makeEnvironmentProviders([
54
+ {
55
+ provide: I18N_CONFIG,
56
+ useValue: resolved
57
+ },
58
+ ...localeProviders,
59
+ provideAppInitializer(async () => {
60
+ await initI18n(resolved, resolveActiveLocale(resolved));
61
+ })
62
+ ]);
63
+ }
64
+ /**
65
+ * Resolves the active locale, preferring the injected `LOCALE` token
66
+ * (which on the server reads from the platform-level provider set by
67
+ * `provideServerContext()`) and falling back to the request URL,
68
+ * `window.location.pathname`, or `defaultLocale`.
69
+ */
70
+ function resolveActiveLocale(config) {
71
+ const injected = inject(LOCALE, { optional: true });
72
+ if (injected && config.locales.includes(injected)) return injected;
73
+ const req = inject(REQUEST, { optional: true });
74
+ const first = (req?.originalUrl ?? req?.url ?? (typeof window !== "undefined" ? window.location.pathname : "/")).split("?")[0].split("/").filter(Boolean)[0];
75
+ if (first && config.locales.includes(first)) return first;
76
+ return config.defaultLocale;
77
+ }
78
+ function detectClientLocale(config) {
79
+ if (typeof window === "undefined") return config.defaultLocale;
80
+ const firstSegment = window.location.pathname.split("/").filter(Boolean)[0];
81
+ if (firstSegment && config.locales.includes(firstSegment)) return firstSegment;
82
+ return config.defaultLocale;
83
+ }
84
+ /**
85
+ * Loads translations for the given locale and registers them with $localize.
86
+ *
87
+ * Always clears any previously loaded translations first so that switching
88
+ * between locales in a single SSR process does not mix translation maps.
89
+ */
90
+ async function initI18n(config, locale) {
91
+ const activeLocale = locale ?? config.defaultLocale;
92
+ await clearTranslationsRuntime();
93
+ if (activeLocale === config.locales[0]) return;
94
+ const translations = await config.loader(activeLocale);
95
+ if (translations && Object.keys(translations).length > 0) await loadTranslationsRuntime(translations);
96
+ }
97
+ /**
98
+ * Loads translations into the global $localize translation map.
99
+ *
100
+ * Uses `@angular/localize`'s `loadTranslations` when available so that
101
+ * each translation string is parsed into the `{text, messageParts,
102
+ * placeholderNames}` shape that `$localize.translate` expects. Falls back
103
+ * to writing raw strings only as a last resort (in which case lookups
104
+ * will not succeed — the fallback exists to keep error messages useful
105
+ * for users who have not installed `@angular/localize`).
106
+ *
107
+ * Requires `@angular/localize/init` to be imported in the application
108
+ * entry point so that `globalThis.$localize` is defined.
109
+ */
110
+ async function loadTranslationsRuntime(translations) {
111
+ const $localize = globalThis.$localize;
112
+ if (!$localize) {
113
+ console.warn("[@analogjs/router] $localize is not available. Make sure to import @angular/localize/init in your application entry point.");
114
+ return;
115
+ }
116
+ try {
117
+ const { loadTranslations } = await import("@angular/localize");
118
+ loadTranslations(translations);
119
+ } catch {
120
+ console.warn("[@analogjs/router] Unable to import @angular/localize. Install it as a dependency to enable runtime translation loading.");
121
+ $localize.TRANSLATIONS ??= {};
122
+ for (const [id, message] of Object.entries(translations)) $localize.TRANSLATIONS[id] = message;
123
+ }
124
+ }
125
+ /** @internal — exported for tests; not re-exported from the package entry. */
126
+ async function clearTranslationsRuntime() {
127
+ const $localize = globalThis.$localize;
128
+ if (!$localize) return;
129
+ try {
130
+ const { clearTranslations } = await import("@angular/localize");
131
+ clearTranslations();
132
+ } catch {
133
+ $localize.translate = void 0;
134
+ $localize.TRANSLATIONS = {};
135
+ }
136
+ }
137
+ function injectSwitchLocale() {
138
+ assertInInjectionContext(injectSwitchLocale);
139
+ const config = inject(I18N_CONFIG);
140
+ return (targetLocale) => {
141
+ if (typeof window === "undefined") return;
142
+ const { pathname, search, hash } = window.location;
143
+ const newPath = replaceLocaleInPath(pathname, targetLocale, config.locales);
144
+ window.location.href = `${newPath}${search}${hash}`;
145
+ };
146
+ }
147
+ function replaceLocaleInPath(pathname, targetLocale, locales) {
148
+ const segments = pathname.split("/").filter(Boolean);
149
+ if (segments.length > 0 && locales.includes(segments[0])) segments[0] = targetLocale;
150
+ else segments.unshift(targetLocale);
151
+ return "/" + segments.join("/");
152
+ }
153
+ //#endregion
154
+ export { injectSwitchLocale, loadTranslationsRuntime, provideI18n };
155
+
156
+ //# sourceMappingURL=analogjs-router-i18n.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analogjs-router-i18n.mjs","names":[],"sources":["../../i18n/src/provide-i18n.ts"],"sourcesContent":["import {\n EnvironmentProviders,\n InjectionToken,\n Type,\n assertInInjectionContext,\n inject,\n makeEnvironmentProviders,\n provideAppInitializer,\n} from '@angular/core';\nimport { LOCALE, REQUEST, ServerRequest } from '@analogjs/router/tokens';\n\ndeclare const ANALOG_I18N_DEFAULT_LOCALE: string;\ndeclare const ANALOG_I18N_LOCALES: string[];\n\n/**\n * Configuration for runtime i18n support.\n *\n * `defaultLocale` and `locales` are optional when the platform plugin\n * is configured with `i18n` in `vite.config.ts` — the values are\n * injected as build-time globals automatically.\n */\nexport interface I18nConfig {\n /**\n * The default locale to use when no locale is detected.\n * If omitted, reads from the platform plugin's `i18n.defaultLocale`.\n */\n defaultLocale?: string;\n\n /**\n * List of supported locale identifiers.\n * If omitted, reads from the platform plugin's `i18n.locales`.\n */\n locales?: string[];\n\n /**\n * A function that returns translations for a given locale.\n * The returned record maps message IDs to translated strings.\n */\n loader: (\n locale: string,\n ) => Promise<Record<string, string>> | Record<string, string>;\n}\n\n/**\n * Fully resolved i18n config with all required fields.\n */\nexport type ResolvedI18nConfig = Required<I18nConfig>;\n\n/**\n * Injection token for the resolved i18n configuration.\n * Provided by `provideI18n()` and consumed by `injectSwitchLocale()`.\n * @internal\n */\nconst I18N_CONFIG = new InjectionToken<ResolvedI18nConfig>(\n '@analogjs/router I18n Config',\n);\n\n/**\n * Resolves the full i18n config by merging explicit values with\n * build-time globals injected by the platform plugin.\n */\nexport function resolveI18nConfig(config: I18nConfig): Required<I18nConfig> {\n const defaultLocale =\n config.defaultLocale ??\n (typeof ANALOG_I18N_DEFAULT_LOCALE !== 'undefined'\n ? ANALOG_I18N_DEFAULT_LOCALE\n : undefined);\n\n const locales =\n config.locales ??\n (typeof ANALOG_I18N_LOCALES !== 'undefined'\n ? ANALOG_I18N_LOCALES\n : undefined);\n\n if (!defaultLocale || !locales) {\n throw new Error(\n '[@analogjs/router] provideI18n() requires defaultLocale and locales. ' +\n 'Either pass them explicitly or configure i18n in the analog() plugin in vite.config.ts.',\n );\n }\n\n return { defaultLocale, locales, loader: config.loader };\n}\n\n/**\n * Provides runtime i18n support using Angular's $localize.\n *\n * This provider:\n * 1. Detects the active locale from the URL or falls back to the default.\n * 2. Makes the current locale available via the LOCALE injection token.\n * 3. Loads translations for the active locale at startup using $localize.\n *\n * Works in both SSR and client-only modes. On the client, locale is detected\n * from `window.location.pathname`. On the server, locale is detected from\n * the request in `provideServerContext()` and provided at the platform level;\n * this function does not shadow it.\n *\n * When the platform plugin is configured with `i18n` in `vite.config.ts`,\n * `defaultLocale` and `locales` are injected automatically — only\n * `loader` is required:\n *\n * ```typescript\n * provideI18n({\n * loader: (locale) => import(`./i18n/${locale}.json`),\n * })\n * ```\n */\nexport function provideI18n(config: I18nConfig): EnvironmentProviders {\n const resolved = resolveI18nConfig(config);\n\n // Only provide LOCALE at the environment level on the client. On the\n // server, the platform-level LOCALE set by `provideServerContext()` is\n // authoritative and must not be shadowed by an environment-level provider.\n const localeProviders =\n typeof window !== 'undefined'\n ? [{ provide: LOCALE, useValue: detectClientLocale(resolved) }]\n : [];\n\n return makeEnvironmentProviders([\n { provide: I18N_CONFIG, useValue: resolved },\n ...localeProviders,\n provideAppInitializer(async () => {\n const locale = resolveActiveLocale(resolved);\n await initI18n(resolved, locale);\n }),\n ]);\n}\n\n/**\n * Resolves the active locale, preferring the injected `LOCALE` token\n * (which on the server reads from the platform-level provider set by\n * `provideServerContext()`) and falling back to the request URL,\n * `window.location.pathname`, or `defaultLocale`.\n */\nfunction resolveActiveLocale(config: ResolvedI18nConfig): string {\n const injected = inject(LOCALE, { optional: true });\n if (injected && config.locales.includes(injected)) {\n return injected;\n }\n\n // Fallback: read the path directly from the request on the server or\n // from the browser URL on the client. This covers cases where a locale\n // prefix is present in the URL but no token provider set it explicitly.\n const req = inject(REQUEST, { optional: true }) as ServerRequest | null;\n const pathname =\n req?.originalUrl ??\n req?.url ??\n (typeof window !== 'undefined' ? window.location.pathname : '/');\n const first = pathname.split('?')[0].split('/').filter(Boolean)[0];\n if (first && config.locales.includes(first)) {\n return first;\n }\n\n return config.defaultLocale;\n}\n\nexport function detectClientLocale(config: ResolvedI18nConfig): string {\n if (typeof window === 'undefined') {\n return config.defaultLocale;\n }\n\n const pathname = window.location.pathname;\n const segments = pathname.split('/').filter(Boolean);\n const firstSegment = segments[0];\n\n if (firstSegment && config.locales.includes(firstSegment)) {\n return firstSegment;\n }\n\n return config.defaultLocale;\n}\n\n/**\n * Loads translations for the given locale and registers them with $localize.\n *\n * Always clears any previously loaded translations first so that switching\n * between locales in a single SSR process does not mix translation maps.\n */\nexport async function initI18n(\n config: ResolvedI18nConfig,\n locale?: string,\n): Promise<void> {\n const activeLocale = locale ?? config.defaultLocale;\n await clearTranslationsRuntime();\n\n // The source locale (first entry in `locales`) has its messages baked\n // directly into the template source, so there is nothing to load.\n if (activeLocale === config.locales[0]) {\n return;\n }\n\n const translations = await config.loader(activeLocale);\n if (translations && Object.keys(translations).length > 0) {\n await loadTranslationsRuntime(translations);\n }\n}\n\n/**\n * Loads translations into the global $localize translation map.\n *\n * Uses `@angular/localize`'s `loadTranslations` when available so that\n * each translation string is parsed into the `{text, messageParts,\n * placeholderNames}` shape that `$localize.translate` expects. Falls back\n * to writing raw strings only as a last resort (in which case lookups\n * will not succeed — the fallback exists to keep error messages useful\n * for users who have not installed `@angular/localize`).\n *\n * Requires `@angular/localize/init` to be imported in the application\n * entry point so that `globalThis.$localize` is defined.\n */\nexport async function loadTranslationsRuntime(\n translations: Record<string, string>,\n): Promise<void> {\n const $localize = (globalThis as any).$localize;\n if (!$localize) {\n console.warn(\n '[@analogjs/router] $localize is not available. ' +\n 'Make sure to import @angular/localize/init in your application entry point.',\n );\n return;\n }\n\n try {\n const localizePkg = '@angular/localize';\n const { loadTranslations } = (await import(\n /* @vite-ignore */ localizePkg\n )) as {\n loadTranslations: (t: Record<string, string>) => void;\n };\n loadTranslations(translations);\n } catch {\n console.warn(\n '[@analogjs/router] Unable to import @angular/localize. ' +\n 'Install it as a dependency to enable runtime translation loading.',\n );\n $localize.TRANSLATIONS ??= {};\n for (const [id, message] of Object.entries(translations)) {\n $localize.TRANSLATIONS[id] = message;\n }\n }\n}\n\n/** @internal — exported for tests; not re-exported from the package entry. */\nexport async function clearTranslationsRuntime(): Promise<void> {\n const $localize = (globalThis as any).$localize;\n if (!$localize) {\n return;\n }\n try {\n const localizePkg = '@angular/localize';\n const { clearTranslations } = (await import(\n /* @vite-ignore */ localizePkg\n )) as {\n clearTranslations: () => void;\n };\n clearTranslations();\n } catch {\n $localize.translate = undefined;\n $localize.TRANSLATIONS = {};\n }\n}\n\n// ---------------------------------------------------------------------------\n// Component definition registry\n// ---------------------------------------------------------------------------\n\nconst componentDefRegistry = new Set<any>();\n\n/** @internal */\nexport function ɵregisterI18nComponentDef(typeOrDef: Type<any> | any): void {\n if (!typeOrDef) return;\n const def = (typeOrDef as any).ɵcmp ?? typeOrDef;\n if (def && typeof def === 'object' && 'template' in def) {\n componentDefRegistry.add(def);\n }\n}\n\n/** @internal */\nexport function ɵresetI18nComponentDefCache(): void {\n for (const def of componentDefRegistry) {\n def.tView = null;\n }\n}\n\n/** @internal */\nexport function getI18nComponentDefRegistrySize(): number {\n return componentDefRegistry.size;\n}\n\n/** @internal */\nexport function clearI18nComponentDefRegistry(): void {\n componentDefRegistry.clear();\n}\n\nexport function injectSwitchLocale(): (targetLocale: string) => void {\n assertInInjectionContext(injectSwitchLocale);\n const config = inject(I18N_CONFIG);\n\n return (targetLocale: string) => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const { pathname, search, hash } = window.location;\n const newPath = replaceLocaleInPath(pathname, targetLocale, config.locales);\n window.location.href = `${newPath}${search}${hash}`;\n };\n}\n\nexport function replaceLocaleInPath(\n pathname: string,\n targetLocale: string,\n locales: string[],\n): string {\n const segments = pathname.split('/').filter(Boolean);\n\n if (segments.length > 0 && locales.includes(segments[0])) {\n segments[0] = targetLocale;\n } else {\n segments.unshift(targetLocale);\n }\n\n return '/' + segments.join('/');\n}\n"],"mappings":";;;;;;;;AAqDA,IAAM,cAAc,IAAI,eACtB,+BACD;;;;;AAMD,SAAgB,kBAAkB,QAA0C;CAC1E,MAAM,gBACJ,OAAO,kBAKH,OAAA,+BAEI,cAIL,6BACO,KAAA;oCAML,OAAA,wBAAA,cAAE,sBAAe,KAAA;AAAS,KAAA,CAAA,iBAAe,CAAA,QAAQ,OAAA,IAAA,MAAA,+JAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAkCtC,YAAA,QAAA;CAAQ,MAAA,WAAU,kBAAmB,OAAA;CAInD,MAAA,kBAAS,OAAA,WAAA,cAAa,CAAA;EAAU,SAAA;EAAA,UAAA,mBAAA,SAAA;EAAA,CAAA,GAAU,EAAA;AAC5C,QAAG,yBAAA;EACH;GAAA,SAAA;GAAsB,UAAY;GAAA;EAChC,GAAM;EACN,sBAAyB,YAAO;AAElC,SAAA,SAAA,UAAA,oBAAA,SAAA,CAAA;;;;;;;;;;AAWF,SAAI,oBAAmB,QAAQ;CAC7B,MAAO,WAAA,OAAA,QAAA,EAAA,UAAA,MAAA,CAAA;mDAMH,QAAM;;CAcZ,MAAI,SAJU,KAAA,eAAA,KAAA,QAGT,OAAS,WAAA,cAAmB,OAAoC,SAAA,WAAA,MAC/C,MAAA,IAAa,CAAA,GAAA,MAAA,IAAA,CAAA,OAAA,QAAA,CAAA;AACjC,KAAA,SAAc,OAAA,QAAA,SAAA,MAAA,CAAA,QAAA;AAIhB,QAAM,OAAW;;AAGjB,SAAI,mBAAuB,QAAQ;AACjC,KAAA,OAAO,WAAA,YAAA,QAAA,OAAA;;;;;;;;;;;AA0BT,eAAoB,SAAO,QAAK,QAAc;CAC5C,MAAM,eAAA,UAAwB,OAAA;;;;;;;;;;;;;;;;;;;;CAgCT,MAAA,YAAA,WAAA;;AAIrB,UAAA,KAAiB,6HACX;AACN;;AAKA,KAAK;4CACoB;;SAMtB;AACC,UAAA,KAAa,2HACH;AACd,YAAA,iBAAA,EAAA;2DAEE,WAAA,aAAA,MAAA;;;;;CAOF,MAAA,YAAmB,WAAA;MACb,UACN;;EASE,MAAA,EAAA,sBAAqC,MAAA,OAAA;AAGpC,qBAAS;SAER;AACF,YAAO,YAAe,KAAA;AACxB,YAAA,eAA6B,EAAA;;;SAyCzB,qBAA8B;AAEpC,0BAA2B,mBAAiB;CAC1C,MAAA,SAAc,OAAA,YAAA;SACT,iBAAA;AACL,MAAS,OAAQ,WAAA,YAAA"}
@@ -1,3 +1,4 @@
1
+ import { getRequestURL, readBody, readFormData, toRequest } from "nitro/h3";
1
2
  //#region packages/router/server/actions/src/actions.ts
2
3
  function fail(status, errors) {
3
4
  return new Response(JSON.stringify(errors), {
@@ -22,6 +23,313 @@ function redirect(url, config = 302) {
22
23
  });
23
24
  }
24
25
  //#endregion
25
- export { fail, json, redirect };
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 { defineAction, definePageLoad, defineServerRoute, fail, json, redirect, validateWithSchema };
26
334
 
27
335
  //# sourceMappingURL=analogjs-router-server-actions.mjs.map
@@ -0,0 +1 @@
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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"}