@kosmojs/dev 0.4.2 → 0.4.4

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/pkg/index.js CHANGED
@@ -1,6 +1,588 @@
1
- import{t as e}from"./assets/pkg-Bkag325q.js";import{join as t,resolve as n}from"node:path";import{styleText as r}from"node:util";import{DEFAULT_APIBASE as i,RequestBodyTargets as a,RequestValidationTargets as o,createRouteResolver as s,createTemplateResolver as c,defaults as l}from"@kosmojs/core";import{createH3Pattern as u,createHonoPattern as d,createPathPattern as f,createWatchedApiRouteEntriesFilter as p,createWatchedPageRouteEntriesFilter as m,defineGenerator as h,defineGeneratorFactory as g,mergeConfigs as _,nestedRoutesFactory as v,pathResolver as y,pathTokensFactory as b,renderFactory as x,renderToFile as ee,sortRoutes as S,spinnerFactory as te,vitePlugins as C}from"@kosmojs/lib";import{routeRenderHelpers as w}from"@kosmojs/core/generators";import T from"crc/crc32";import ne from"@mdx-js/rollup";import{build as E,createFilter as re}from"vite";import ie from"yaml";import{parse as ae}from"path-to-regexp";import oe from"typebox";import se from"@vitejs/plugin-react";import D from"vite-plugin-solid";import{access as ce,constants as le,cp as O,mkdir as ue,rm as k,writeFile as de}from"node:fs/promises";import{svelte as fe}from"@sveltejs/vite-plugin-svelte";import pe from"@vitejs/plugin-vue";var A=`export * from "./transport";
2
- `,j=`export const transport = undefined;
3
- `,M=`{{#each routes}}
1
+ import{dirname as e,join as t,posix as n,resolve as r}from"node:path";import{styleText as i}from"node:util";import{DEFAULT_APIBASE as a,RequestBodyTargets as o,RequestValidationTargets as s,createRouteResolver as c,createTemplateResolver as l,defaults as u}from"@kosmojs/core";import{collectVirtualModules as d,createH3Pattern as f,createHonoPattern as p,createPathPattern as m,createWatchedApiRouteEntriesFilter as h,createWatchedPageRouteEntriesFilter as g,defineGenerator as _,defineGeneratorFactory as v,mergeConfigs as y,nestedRoutesFactory as b,pathExists as x,pathResolver as S,pathTokensFactory as C,renderFactory as w,renderToFile as T,sortRoutes as E,sortRoutesForResolution as D,spinnerFactory as ee,vitePlugins as O}from"@kosmojs/lib";import k from"semver";import{routeRenderHelpers as A}from"@kosmojs/core/generators";import{HTTPMethods as te}from"@kosmojs/core/fetch";import j from"crc/crc32";import ne from"@mdx-js/rollup";import{build as M,createFilter as re}from"vite";import ie from"yaml";import{parse as ae}from"path-to-regexp";import oe from"typebox";import se from"@vitejs/plugin-react";import N from"vite-plugin-solid";import{access as ce,constants as le,cp as P,mkdir as ue,rm as F,writeFile as de}from"node:fs/promises";import{svelte as fe}from"@sveltejs/vite-plugin-svelte";import pe from"@vitejs/plugin-vue";var me={type:`module`,private:!0,name:`@kosmojs/core-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,semver:`^7.8.5`},devDependencies:{"@types/semver":`^7.8.0`,"path-to-regexp":`^8.4.2`}},he=`export const base = "{{config.base}}";
2
+ export const apiBase = "{{config.apiBase}}";
3
+ `,I=`import type { StaticParams } from "./types";
4
+
5
+ export * from "./config";
6
+ export * from "./routes";
7
+ export * from "./types";
8
+
9
+ export const defineStaticParams = <T extends keyof StaticParams>(
10
+ variants: Array<StaticParams[T]>,
11
+ ) => {
12
+ return variants
13
+ };
14
+ `,L=`{{> routeMapperPartial}}
15
+
16
+ import { base, apiBase } from "./config";
17
+
18
+ // used by backend generators and fetch clients
19
+ export const apiRouteMap = {
20
+ {{#each apiRoutes}}
21
+ "{{name}}": apiRouteMapper<[{{serializeParamsTupleElements .}}]>(
22
+ join(base, apiBase),
23
+ {{serializeApiRoute .}},
24
+ ),
25
+ {{/each}}
26
+ };
27
+
28
+ // used by frontend generators on Link component
29
+ export const pageRouteMap = {
30
+ {{#each pageRoutes}}
31
+ "{{name}}": pageRouteMapper<[{{serializeParamsTupleElements .}}]>(
32
+ base,
33
+ {{serializePageRoute .}},
34
+ ),
35
+ {{/each}}
36
+ };
37
+ `,R=`import { compile, match } from "path-to-regexp";
38
+
39
+ import {
40
+ type ApiRouteSerialized,
41
+ type PageRouteSerialized,
42
+ stringifySearchParams,
43
+ type ValidationTarget,
44
+ } from "@kosmojs/core";
45
+ import { createHost, join } from "@kosmojs/core/fetch";
46
+ import type { RoutePathMethods } from "@kosmojs/core/generators";
47
+
48
+ type NormalizeParams = (path: string) => Record<string, unknown>;
49
+
50
+ type NormalizeSearchParams = (
51
+ searchParams: Record<string, unknown>,
52
+ method: string,
53
+ ) => Record<string, unknown>;
54
+
55
+ type PayloadResolver = <T>(
56
+ payload: Record<ValidationTarget, T> | undefined,
57
+ target: ValidationTarget,
58
+ method: string,
59
+ ) => T | Record<string, unknown> | undefined;
60
+
61
+ export const apiRouteMapper = <ParamsT extends readonly unknown[]>(
62
+ base: string,
63
+ {
64
+ name,
65
+ pathPattern,
66
+ params,
67
+ numericProperties,
68
+ booleanProperties,
69
+ }: ApiRouteSerialized,
70
+ ): RoutePathMethods<ParamsT> & {
71
+ normalizeParams: NormalizeParams;
72
+ normalizeSearchParams: NormalizeSearchParams;
73
+ payloadResolver: PayloadResolver;
74
+ } => {
75
+ const toPath = compile(pathPattern);
76
+ const pathMatcher = match(join(base, pathPattern));
77
+
78
+ const maybeNumber = (val: unknown) => {
79
+ if (val === undefined || val === null) {
80
+ return val;
81
+ }
82
+ const n = Number(val);
83
+ return Number.isFinite(n) ? n : val;
84
+ };
85
+
86
+ const maybeBoolean = (val: unknown) => {
87
+ return [true, false, "true", "false"].includes(val as never) //
88
+ ? JSON.parse(val as never)
89
+ : val;
90
+ };
91
+
92
+ const resolveParam = (path: string, param: (typeof params)[number]) => {
93
+ try {
94
+ const match = pathMatcher(path);
95
+ return match ? match.params[param] : undefined;
96
+ } catch (e) {
97
+ return undefined;
98
+ }
99
+ };
100
+
101
+ const normalizeSearchParams: NormalizeSearchParams = (
102
+ searchParams,
103
+ method,
104
+ ) => {
105
+ return Object.fromEntries(
106
+ Object.entries(searchParams).map(([k, v]) => {
107
+ if (numericProperties.query?.[method]?.includes(k)) {
108
+ return [
109
+ k,
110
+ Array.isArray(v) ? v.map((e) => maybeNumber(e)) : maybeNumber(v),
111
+ ];
112
+ }
113
+ if (booleanProperties.query?.[method]?.includes(k)) {
114
+ return [
115
+ k,
116
+ Array.isArray(v) ? v.map((e) => maybeBoolean(e)) : maybeBoolean(v),
117
+ ];
118
+ }
119
+ return [k, v];
120
+ }),
121
+ );
122
+ };
123
+
124
+ const normalizeParams: NormalizeParams = (path) => {
125
+ return params.reduce((map: Record<string, unknown>, param) => {
126
+ const value = resolveParam(path, param);
127
+ if (Array.isArray(value)) {
128
+ map[param] = numericProperties.params.includes(param)
129
+ ? value.map((e) => maybeNumber(e))
130
+ : value;
131
+ } else if (value) {
132
+ map[param] = numericProperties.params.includes(param)
133
+ ? maybeNumber(value)
134
+ : value;
135
+ }
136
+ return map;
137
+ }, {});
138
+ };
139
+
140
+ const paramsMapper: RoutePathMethods<ParamsT>["paramsMapper"] = (
141
+ input,
142
+ opt,
143
+ ) => {
144
+ return params.reduce<Record<string, unknown>>((map, name, i) => {
145
+ const coerceNumbers = opt?.coerceNumbers
146
+ ? numericProperties.params.includes(name)
147
+ : false;
148
+ if (Array.isArray(input[i])) {
149
+ map[name] = coerceNumbers
150
+ ? input[i].map((v) => maybeNumber(v))
151
+ : input[i].map(String);
152
+ } else if (input[i] !== undefined) {
153
+ map[name] = coerceNumbers ? maybeNumber(input[i]) : String(input[i]);
154
+ }
155
+ return map;
156
+ }, {});
157
+ };
158
+
159
+ const parametrize: RoutePathMethods<ParamsT>["parametrize"] = (params) => {
160
+ try {
161
+ return toPath(paramsMapper(params) as never);
162
+ } catch (error) {
163
+ console.error(\`❗ERROR: Failed building path for \${name}\`);
164
+ throw error;
165
+ }
166
+ };
167
+
168
+ const path: RoutePathMethods<ParamsT>["path"] = (params, query, opt) => {
169
+ const path = join(
170
+ opt?.prefix === false
171
+ ? "/"
172
+ : typeof opt?.prefix === "string"
173
+ ? opt.prefix
174
+ : base,
175
+ parametrize(params),
176
+ );
177
+ return query //
178
+ ? [path, stringifySearchParams(query)].join("?")
179
+ : path;
180
+ };
181
+
182
+ const href: RoutePathMethods<ParamsT>["href"] = (
183
+ host,
184
+ params,
185
+ query,
186
+ opt,
187
+ ) => {
188
+ return createHost(host) + path(params, query, opt);
189
+ };
190
+
191
+ const payloadResolver: PayloadResolver = (payload, target, method) => {
192
+ const data = payload?.[target];
193
+
194
+ if (target === "query") {
195
+ return Object.fromEntries(
196
+ Object.entries({ ...data }).map(([k, v]) => {
197
+ return [
198
+ k,
199
+ numericProperties.query[method]?.includes(k)
200
+ ? Array.isArray(v)
201
+ ? v.map((v) => maybeNumber(v))
202
+ : maybeNumber(v)
203
+ : v,
204
+ ];
205
+ }),
206
+ );
207
+ }
208
+
209
+ if (data instanceof FormData) {
210
+ return [...data].reduce<
211
+ Record<string, FormDataEntryValue | Array<FormDataEntryValue>>
212
+ >((map, [key, val]) => {
213
+ if (key in map) {
214
+ map[key] = [map[key]].flat().concat(val);
215
+ } else {
216
+ map[key] = val;
217
+ }
218
+ return map;
219
+ }, {});
220
+ }
221
+
222
+ return data;
223
+ };
224
+
225
+ return {
226
+ normalizeParams,
227
+ normalizeSearchParams,
228
+ payloadResolver,
229
+ paramsMapper,
230
+ parametrize,
231
+ path,
232
+ href,
233
+ };
234
+ };
235
+
236
+ export const pageRouteMapper = <ParamsT extends readonly unknown[]>(
237
+ base: string,
238
+ route: PageRouteSerialized,
239
+ ): RoutePathMethods<ParamsT> => {
240
+ const toPath = compile(route.pathPattern);
241
+
242
+ const paramsMapper: RoutePathMethods<ParamsT>["paramsMapper"] = (params) => {
243
+ return route.params.reduce<Record<string, unknown>>((map, name, i) => {
244
+ if (Array.isArray(params[i])) {
245
+ map[name] = params[i].map(String);
246
+ } else if (params[i] !== undefined) {
247
+ map[name] = String(params[i]);
248
+ }
249
+ return map;
250
+ }, {});
251
+ };
252
+
253
+ const parametrize: RoutePathMethods<ParamsT>["parametrize"] = (params) => {
254
+ try {
255
+ return toPath(paramsMapper(params) as never);
256
+ } catch (error) {
257
+ console.error(\`❗ERROR: Failed building path for \${route.name}\`);
258
+ throw error;
259
+ }
260
+ };
261
+
262
+ const path: RoutePathMethods<ParamsT>["path"] = (params, query, opt) => {
263
+ const path = join(
264
+ opt?.prefix === false
265
+ ? "/"
266
+ : typeof opt?.prefix === "string"
267
+ ? opt.prefix
268
+ : base,
269
+ parametrize(params),
270
+ );
271
+ return query //
272
+ ? [path, stringifySearchParams(query)].join("?")
273
+ : path;
274
+ };
275
+
276
+ const href: RoutePathMethods<ParamsT>["href"] = (
277
+ host,
278
+ params,
279
+ query,
280
+ opt,
281
+ ) => {
282
+ return createHost(host) + path(params, query, opt);
283
+ };
284
+
285
+ return {
286
+ paramsMapper,
287
+ parametrize,
288
+ path,
289
+ href,
290
+ };
291
+ };
292
+ `,ge=`import { AsyncLocalStorage } from "node:async_hooks";
293
+
294
+ export type RequestContext = {
295
+ headers?: HeadersInit;
296
+ tsqClient?: unknown;
297
+ error?: unknown;
298
+ };
299
+
300
+ /**
301
+ * Request-scoped context store.
302
+ * */
303
+ export const store = new AsyncLocalStorage<RequestContext>();
304
+
305
+ /**
306
+ * Origin used to absolutize the relative URLs the client produces.
307
+ * */
308
+ export const ssrOrigin = "http://ssr.local";
309
+
310
+ export const redirectCodes = [
311
+ // Moved Permanently
312
+ 301,
313
+ // Found (temporary)
314
+ 302,
315
+ // See Other (redirect after POST)
316
+ 303,
317
+ // Temporary Redirect (preserves method)
318
+ 307,
319
+ // Permanent Redirect (preserves method)
320
+ 308,
321
+ ];
322
+ `,_e=`export type Override<A, B> = Omit<A, keyof B> & B;
323
+
324
+ export type StaticParams = {
325
+ {{#each pageRoutes}}
326
+ "{{name}}": [ {{serializeParamsTupleElements .}} ];
327
+ {{/each}}
328
+ };
329
+
330
+ {{#if pageRoutes.length}}
331
+ export type LinkProps =
332
+ {{#each pageRoutes}}
333
+ | [ "{{name}}", {{serializeParamsTupleElements .}} ]
334
+ {{/each}};
335
+ {{else}}
336
+ export type LinkProps = never;
337
+ {{/if}}
338
+ `,ve=`declare module "virtual:kosmo/env" {
339
+ export const command: "serve" | "build";
340
+ }
341
+
342
+ declare module "virtual:kosmo/backend-app" {
343
+ import type { FetchApp, NodeApp } from "@kosmojs/core";
344
+ const backend: FetchApp | NodeApp | undefined;
345
+ export default backend;
346
+ }
347
+
348
+ declare module "virtual:kosmo/fetch-transport" {
349
+ import type { Transport } from "@kosmojs/core/fetch";
350
+ /**
351
+ * Undefined on the client, where fetch clients fall back to global fetch.
352
+ * On the SSR bundle it dispatches straight into the backend app, in process.
353
+ * Supplied by the \`kosmo:virtualModules\` Vite plugin - there is no file.
354
+ * */
355
+ export const transport: Transport | undefined;
356
+ }
357
+
358
+ /**
359
+ * Enhances base TypeScript types with JSON Schema validation constraints.
360
+ * Allows declaring refined types that carry validation metadata for runtime
361
+ * schema validation while maintaining full TypeScript type safety.
362
+ *
363
+ * Useful for generating validation schemas and ensuring
364
+ * data conforms to specific business rules beyond basic type checking.
365
+ * */
366
+ declare type VRefine<
367
+ T extends unknown[] | number | string | object,
368
+ _ extends T extends unknown[]
369
+ ? TArrayOptions
370
+ : T extends number
371
+ ? TNumberOptions
372
+ : T extends string
373
+ ? TStringOptions
374
+ : TObjectOptions,
375
+ > = T;
376
+
377
+ /**
378
+ * Type definitions inspired by and gently adapted from TypeBox.
379
+ * Original TypeBox created by sinclairzx81: https://github.com/sinclairzx81/typebox
380
+ * TypeBox is licensed under MIT: https://github.com/sinclairzx81/typebox/blob/main/license
381
+ *
382
+ * These types provide JSON Schema compatible type refinements for TypeScript.
383
+ * */
384
+ interface TSchema {}
385
+
386
+ // ------------------------------------------------------------------
387
+ // ObjectOptions
388
+ // ------------------------------------------------------------------
389
+ interface TObjectOptions {
390
+ /**
391
+ * Defines whether additional properties are allowed beyond those explicitly defined in \`properties\`.
392
+ */
393
+ additionalProperties?: TSchema | boolean;
394
+ /**
395
+ * The minimum number of properties required in the object.
396
+ */
397
+ minProperties?: number;
398
+ /**
399
+ * The maximum number of properties allowed in the object.
400
+ */
401
+ maxProperties?: number;
402
+ /**
403
+ * Defines conditional requirements for properties.
404
+ */
405
+ dependencies?: Record<string, boolean | TSchema | string[]>;
406
+ /**
407
+ * Specifies properties that *must* be present if a given property is present.
408
+ */
409
+ dependentRequired?: Record<string, string[]>;
410
+ /**
411
+ * Defines schemas that apply if a specific property is present.
412
+ */
413
+ dependentSchemas?: Record<string, TSchema>;
414
+ /**
415
+ * Maps regular expressions to schemas properties matching a pattern must validate against the schema.
416
+ */
417
+ patternProperties?: Record<string, TSchema>;
418
+ /**
419
+ * A schema that all property names within the object must validate against.
420
+ */
421
+ propertyNames?: TSchema;
422
+ }
423
+
424
+ // ------------------------------------------------------------------
425
+ // ArrayOptions
426
+ // ------------------------------------------------------------------
427
+ interface TArrayOptions {
428
+ /**
429
+ * The minimum number of items allowed in the array.
430
+ */
431
+ minItems?: number;
432
+ /**
433
+ * The maximum number of items allowed in the array.
434
+ */
435
+ maxItems?: number;
436
+ /**
437
+ * A schema that at least one item in the array must validate against.
438
+ */
439
+ contains?: TSchema;
440
+ /**
441
+ * The minimum number of array items that must validate against the \`contains\` schema.
442
+ */
443
+ minContains?: number;
444
+ /**
445
+ * The maximum number of array items that may validate against the \`contains\` schema.
446
+ */
447
+ maxContains?: number;
448
+ /**
449
+ * An array of schemas, where each schema in \`prefixItems\` validates against items at corresponding positions from the beginning of the array.
450
+ */
451
+ prefixItems?: TSchema[];
452
+ /**
453
+ * If \`true\`, all items in the array must be unique.
454
+ */
455
+ uniqueItems?: boolean;
456
+ }
457
+
458
+ // ------------------------------------------------------------------
459
+ // NumberOptions
460
+ // ------------------------------------------------------------------
461
+ interface TNumberOptions {
462
+ /**
463
+ * Specifies an exclusive upper limit for the number (number must be less than this value).
464
+ */
465
+ exclusiveMaximum?: number | bigint;
466
+ /**
467
+ * Specifies an exclusive lower limit for the number (number must be greater than this value).
468
+ */
469
+ exclusiveMinimum?: number | bigint;
470
+ /**
471
+ * Specifies an inclusive upper limit for the number (number must be less than or equal to this value).
472
+ */
473
+ maximum?: number | bigint;
474
+ /**
475
+ * Specifies an inclusive lower limit for the number (number must be greater than or equal to this value).
476
+ */
477
+ minimum?: number | bigint;
478
+ /**
479
+ * Specifies that the number must be a multiple of this value.
480
+ */
481
+ multipleOf?: number | bigint;
482
+ }
483
+
484
+ // ------------------------------------------------------------------
485
+ // StringOptions
486
+ // ------------------------------------------------------------------
487
+ type TFormat =
488
+ | "date-time"
489
+ | "date"
490
+ | "duration"
491
+ | "email"
492
+ | "hostname"
493
+ | "idn-email"
494
+ | "idn-hostname"
495
+ | "ipv4"
496
+ | "ipv6"
497
+ | "iri-reference"
498
+ | "iri"
499
+ | "json-pointer-uri-fragment"
500
+ | "json-pointer"
501
+ | "json-string"
502
+ | "regex"
503
+ | "relative-json-pointer"
504
+ | "time"
505
+ | "uri-reference"
506
+ | "uri-template"
507
+ | "url"
508
+ | "uuid";
509
+
510
+ interface TStringOptions {
511
+ /**
512
+ * Specifies the expected string format.
513
+ *
514
+ * Common values include:
515
+ * - \`base64\` – Base64-encoded string.
516
+ * - \`date-time\` – [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date-time format.
517
+ * - \`date\` – [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date (YYYY-MM-DD).
518
+ * - \`duration\` – [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) duration format.
519
+ * - \`email\` – RFC 5321/5322 compliant email address.
520
+ * - \`hostname\` – RFC 1034/1035 compliant host name.
521
+ * - \`idn-email\` – Internationalized email address.
522
+ * - \`idn-hostname\` – Internationalized host name.
523
+ * - \`ipv4\` – IPv4 address.
524
+ * - \`ipv6\` – IPv6 address.
525
+ * - \`iri\` / \`iri-reference\` – Internationalized Resource Identifier.
526
+ * - \`json-pointer\` / \`json-pointer-uri-fragment\` – JSON Pointer format.
527
+ * - \`json-string\` – String containing valid JSON.
528
+ * - \`regex\` – Regular expression syntax.
529
+ * - \`relative-json-pointer\` – Relative JSON Pointer format.
530
+ * - \`time\` – [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) time (HH:MM:SS).
531
+ * - \`uri-reference\` / \`uri-template\` – URI reference or template.
532
+ * - \`url\` – Web URL format.
533
+ * - \`uuid\` – RFC 4122 UUID string.
534
+ *
535
+ * May also be a custom format string.
536
+ */
537
+ format?: TFormat;
538
+ /**
539
+ * Specifies the minimum number of characters allowed in the string.
540
+ * Must be a non-negative integer.
541
+ */
542
+ minLength?: number;
543
+ /**
544
+ * Specifies the maximum number of characters allowed in the string.
545
+ * Must be a non-negative integer.
546
+ */
547
+ maxLength?: number;
548
+ /**
549
+ * Specifies a regular expression pattern that the string value must match.
550
+ * Can be provided as a string (ECMA-262 regex syntax) or a \`RegExp\` object.
551
+ */
552
+ pattern?: string | RegExp;
553
+ }
554
+ `,ye=`# Ignore all files
555
+ *
556
+
557
+ # But don't ignore directories (so Git can traverse them)
558
+ !*/
559
+
560
+ # And don't ignore these files at any depth
561
+ !cache.json
562
+ !types.ts
563
+ `,be=`export declare global {
564
+ interface Window {
565
+ __KOSMO_HYDRATION_BOOL__: boolean;
566
+ __KOSMO_HYDRATION_DATA__: Record<string, unknown> | undefined;
567
+ }
568
+ }
569
+ `,xe=`<!doctype html>
570
+ <html lang="en">
571
+ <head>
572
+ <meta charset="UTF-8" />
573
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
574
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
575
+ </head>
576
+ <body>
577
+ <div id="app"><!--app-html--></div>
578
+ <script type="module" src="/{{ entryDir }}/client.ts"><\/script>
579
+ </body>
580
+ </html>
581
+ `,Se=`// stub schemas, specialized generators supposed to overwrite this file
582
+ import type { ValidationSchemas } from "@kosmojs/core";
583
+ export type { ValidationSchemas };
584
+ export const validationSchemas: ValidationSchemas = {};
585
+ `,z=({dependencies:e,devDependencies:t},n)=>{let r="${configDir}",i=Object.keys({...e,...t}),a={types:[`vite/client`,...[`node`,`deno`,`bun`].flatMap(e=>i.includes(`@types/${e}`)?[`@types/${e}`]:[])],moduleResolution:`bundler`,module:`ESNext`,target:`ESNext`,strict:!0,exactOptionalPropertyTypes:!0,noImplicitAny:!0,noImplicitThis:!0,noImplicitOverride:!0,noImplicitReturns:!0,noUnusedLocals:!1,noUnusedParameters:!1,allowArbitraryExtensions:!0,allowImportingTsExtensions:!0,allowUnreachableCode:!1,allowUnusedLabels:!1,useUnknownInCatchVariables:!0,noFallthroughCasesInSwitch:!0,noUncheckedSideEffectImports:!0,resolveJsonModule:!0,esModuleInterop:!0,verbatimModuleSyntax:!0,skipLibCheck:!0,noEmit:!0};return n?{include:[`${r}/`,`${r}/../../${u.libDir}/${n}/`,`${r}/../../**/*.d.ts`],compilerOptions:{...a,types:[...a.types],paths:{[`${u.appPrefix}/*`]:[`${r}/../../*`],[`${u.srcPrefix}/*`]:[`${r}/*`],[`${u.libPrefix}/*`]:[`${r}/../../${u.libDir}/${n}/*`]}}}:{include:[`${r}/`],exclude:[`${r}/${u.srcDir}/`],compilerOptions:{...a,paths:{[`${u.appPrefix}/*`]:[`${r}/*`]}}}},Ce=v(t=>{let{createPath:n,createImportHelpers:a}=S(t),{generators:o}=t.config,s=async()=>{let{dependencies:e={},devDependencies:a={}}=await import(r(t.root,`package.json`),{with:{type:`json`}}).then(e=>e.default);{let t=[],n=[],r=o.flatMap(e=>[`dependencies`,`devDependencies`].flatMap(t=>e[t]?Object.entries(typeof e[t]==`function`?e[t](e.options):e[t]).flatMap(([e,n])=>{let r=k.minVersion(n)?.version;return r?[[e,r,t]]:[]}):[]));for(let[i,o,s]of r){let r=e[i]||a[i],c=r?k.minVersion(r)?.version:void 0;!r||!c?t.push([i,o,s]):k.lt(c,o)&&n.push([i,o,s])}if(t.length){console.error(i([`red`,`italic`],`There are ${t.length} missing dependencies, please consider installing them.`));for(let e of[`dependencies`,`devDependencies`]){let n=t.filter(t=>t[2]===e);n.length&&console.error(`${e}: ${i([`blue`],n.map(([e])=>e).join(` `))}`)}}n.length&&(console.error(i([`yellow`,`italic`],`There are ${n.length} outdated dependencies, please consider updating them:`)),console.error(n.map(([e])=>e).join(` `)),console.error())}{await T(r(t.root,`tsconfig.json`),JSON.stringify({extends:`./${u.libDir}/tsconfig.json`},void 0,2),{},{overwrite:!1}),await T(n.lib(`../tsconfig.json`),JSON.stringify(z({dependencies:e,devDependencies:a}),void 0,2),{}),await T(n.src(`tsconfig.json`),JSON.stringify({extends:`../../${u.libDir}/${t.name}/tsconfig.json`},void 0,2),{},{overwrite:!1});let i=z({dependencies:e,devDependencies:a},t.name),s={},c=new Set(i.compilerOptions.types||[]);for(let{meta:e}of o){e.jsx&&(s.jsx=e.jsx),e.jsxImportSource&&(s.jsxImportSource=e.jsxImportSource);for(let t of e.types||[])c.add(t)}await T(n.lib(`tsconfig.json`),JSON.stringify({...i,compilerOptions:{...i.compilerOptions,...s,types:[...c.values()]}},void 0,2),{})}for(let[e,t]of[[`env.d.ts`,ve],[`global.d.ts`,be]])await T(n.lib(`../${e}`),t,{});await T(n.lib(`../.gitignore`),ye,{},{overwrite:!1}),o.some(e=>e.meta.slot===`frontend`)&&await T(n.src(`index.html`),xe,{entryDir:u.entryDir},{overwrite:e=>!e?.trim()})},c=async r=>{let{renderToFile:i}=w({helpers:{...a({origin:`lib`}),...A()},partials:{routeMapperPartial:R}}),o=r.flatMap(({kind:e,entry:t})=>e===`apiRoute`?[t]:[]),s=r.flatMap(({kind:e,entry:t})=>e===`pageRoute`?[t]:[]);await i(n.libCore(`routes.ts`),L,{apiRoutes:o,pageRoutes:s});for(let[e,r]of[[`config.ts`,he],[`types.ts`,_e],[`ssr.ts`,ge],[`index.ts`,I]])await i(n.libCore(e),r,{...t,apiRoutes:o,pageRoutes:s});for(let{kind:t,entry:a}of r)t===`apiRoute`&&await i(n.libApi(e(a.file),`schemas.ts`),Se,{route:a},{overwrite:!1})};return{start:s,watch:c,build:c,virtualModules(){let{createImport:e}=S(t);return[{specifier:`virtual:kosmo/backend-app`,csr:`export default undefined;`,ssr:o.some(e=>e.meta.slot===`backend`)?`export { default } from "${e.api([`app`],{origin:`lib`})}";`:`export default undefined;`}]}}}),B=_({meta:{name:`Core`},dependencies:{"path-to-regexp":me.devDependencies[`path-to-regexp`]},factory:Ce}),we={type:`module`,private:!0,name:`@kosmojs/fetch-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/fetch-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`},devDependencies:{"light-my-request":`^6.6.0`}},Te=`{{#each routes}}
4
586
  import {{id}} from "{{ createImport 'libApi' name 'fetch' }}";
5
587
  {{/each}}
6
588
 
@@ -18,10 +600,10 @@ export default {
18
600
  {{/each}}
19
601
  {{/each}}
20
602
  }
21
- `,N=`import fetchFactory, { join } from "@kosmojs/core/fetch";
603
+ `,Ee=`import fetchFactory, { join } from "@kosmojs/core/fetch";
22
604
 
23
605
  import { base, apiBase, apiRouteMap } from "{{ createImport 'libCore' }}";
24
- import { transport } from "{{ createImport 'lib' '@fetch' }}";
606
+ import { transport } from "virtual:kosmo/fetch-transport";
25
607
 
26
608
  import {
27
609
  type MaybeWrapped,
@@ -73,9 +655,7 @@ export const {{method}} = (
73
655
  _params{{#if ../route.optionalParams}}?{{/if}}: MaybeWrapped<ParamsT>,
74
656
  _payload?: {
75
657
  {{#each payloadTypes}}
76
- {{target}}: {{id}} extends { {{method}}: unknown }
77
- ? MaybeWrapped<{{id}}["{{method}}"]>
78
- : unknown,
658
+ {{target}}: MaybeWrapped<{{id}}>,
79
659
  {{/each}}
80
660
  },
81
661
  opt?: {
@@ -104,27 +684,229 @@ export const {{method}} = (
104
684
  payloadResolver(payload as never, "{{target}}", "{{../method}}"),
105
685
  );
106
686
  }
107
- {{/each}}
687
+ {{/each}}
688
+ }
689
+ return fetchApi.{{method}}(parametrize(params as never), payload as never);
690
+ };
691
+ {{/each}}
692
+
693
+ export default {
694
+ {{#each routeMethods}}
695
+ {{method}},
696
+ {{/each}}
697
+ path,
698
+ href,
699
+ validationSchemas,
700
+ };
701
+ `,De=`import backend from "virtual:kosmo/backend-app";
702
+
703
+ import {
704
+ redirectCodes,
705
+ ssrOrigin,
706
+ store,
707
+ } from "{{ createImport 'libCore' 'ssr' }}";
708
+
709
+ /**
710
+ * Maximum redirect hops, mirroring the fetch spec limit.
711
+ * */
712
+ export const maxRedirects = 5;
713
+
714
+ /**
715
+ * HeadersProvider for createTransport.
716
+ * */
717
+ const headersProvider = () => {
718
+ return store.getStore()?.headers;
719
+ };
720
+
721
+ const createDispatch = (app) => {
722
+ return typeof app.fetch === "function"
723
+ ? app.fetch
724
+ : async (request) => {
725
+ const { inject } = await import("light-my-request");
726
+
727
+ /**
728
+ * Node dispatch: serializes the web Request into light-my-request's
729
+ * injection format and lifts the injected response back into a web Response.
730
+ * */
731
+ const url = new URL(request.url);
732
+
733
+ const payload = ["GET", "HEAD"].includes(request.method)
734
+ ? undefined
735
+ : Buffer.from(await request.arrayBuffer());
736
+
737
+ const result = await inject(app.callback(), {
738
+ method: request.method,
739
+ url: url.pathname + url.search,
740
+ headers: Object.fromEntries(request.headers),
741
+ ...(payload?.length ? { payload } : {}),
742
+ });
743
+
744
+ const headers = new Headers();
745
+
746
+ for (const [key, value] of Object.entries(result.headers)) {
747
+ for (const entry of Array.isArray(value) ? value : [value]) {
748
+ if (entry !== undefined) {
749
+ headers.append(key, String(entry));
750
+ }
751
+ }
752
+ }
753
+
754
+ /**
755
+ * 204/304 responses must not carry a body per the Response
756
+ * constructor contract.
757
+ * */
758
+ const body = [204, 304].includes(result.statusCode)
759
+ ? null
760
+ : new Uint8Array(result.rawPayload);
761
+
762
+ return new Response(body, {
763
+ status: result.statusCode,
764
+ statusText: result.statusMessage,
765
+ headers,
766
+ });
767
+ };
768
+ };
769
+
770
+ const createTransport = (app) => {
771
+ const dispatch = createDispatch(app);
772
+
773
+ /**
774
+ * Build a fetch-compatible transport that dispatches requests
775
+ * directly into the given app - no sockets, no interception.
776
+ * Redirects are followed in-process, including the 303 and 301/302 method rewrite to GET.
777
+ * */
778
+ return async (input, init) => {
779
+ /**
780
+ * Request-scoped headers act as defaults: anything set explicitly
781
+ * on the call itself wins over forwarded values.
782
+ * */
783
+ const headers = new Headers(init?.headers);
784
+
785
+ // When the body is FormData, the Request constructor sets a multipart
786
+ // Content-Type with a fresh boundary. A forwarded Content-Type default would
787
+ // override that boundary and desync it from the serialized body, so never
788
+ // forward Content-Type for FormData bodies.
789
+ const isFormBody = init?.body instanceof FormData;
790
+
791
+ for (const [key, value] of new Headers(headersProvider() || undefined)) {
792
+ if (isFormBody && key.toLowerCase() === "content-type") {
793
+ continue;
794
+ }
795
+ if (!headers.has(key)) {
796
+ headers.set(key, value);
797
+ }
798
+ }
799
+
800
+ let request = new Request(new URL(String(input), ssrOrigin), {
801
+ ...init,
802
+ headers,
803
+ });
804
+
805
+ /**
806
+ * Bodies are buffered once so they can be replayed across
807
+ * 307/308 hops; the client only ever sends strings, FormData
808
+ * and buffer-ish payloads, so this is safe and cheap.
809
+ * */
810
+ const body = ["GET", "HEAD"].includes(request.method)
811
+ ? undefined
812
+ : await request.arrayBuffer();
813
+
814
+ for (let hop = 0; ; hop++) {
815
+ if (hop === maxRedirects) {
816
+ throw new TypeError("Failed to fetch: too many redirects");
817
+ }
818
+
819
+ const response = await dispatch(
820
+ body === undefined || request.method === "GET"
821
+ ? new Request(request, { body: null })
822
+ : new Request(request, { body }),
823
+ );
824
+
825
+ const location = response.headers.get("location");
826
+
827
+ if (!location || !redirectCodes.includes(response.status)) {
828
+ return response;
829
+ }
830
+
831
+ const method =
832
+ response.status === 303 ||
833
+ ([301, 302].includes(response.status) && request.method === "POST")
834
+ ? "GET"
835
+ : request.method;
836
+
837
+ request = new Request(new URL(location, request.url), {
838
+ method,
839
+ headers: request.headers,
840
+ });
841
+ }
842
+ };
843
+ };
844
+
845
+ const ssrTransport = backend ? createTransport(backend) : undefined;
846
+
847
+ export const transport = ssrTransport
848
+ ? async (input, init) => {
849
+ try {
850
+ const response = await ssrTransport(input, init);
851
+ if (response?.ok) {
852
+ return response;
853
+ }
854
+ // the rethrow here needed cause ssrTransport does not throw on non-2xx responses
855
+ throw new SSRFetchError([
856
+ input,
857
+ response,
858
+ typeof response?.text === "function"
859
+ ? await response.text()
860
+ : response?.statusText,
861
+ ]);
862
+ } catch (error) {
863
+ /**
864
+ * Capture the fetch error at the transport level and stash it on the request store.
865
+ * Some frameworks - Solid notably - swallow a rejecting loader and still emit a partial render tree.
866
+ * Storing the error here keeps it observable regardless of how the framework handles the loader rejection.
867
+ * */
868
+ const storage = store.getStore();
869
+ if (storage) {
870
+ storage.error = error;
871
+ }
872
+ throw error;
873
+ }
874
+ }
875
+ : undefined; // let fetch clients pick the transport
876
+
877
+ class SSRFetchError extends Error {
878
+ constructor([input, response, message]) {
879
+ const pathname = pathnameOf(input);
880
+ const status = response.status ?? "unknown";
881
+ super(\`\${pathname}: \${status} [ \${message} ]\`.trim());
882
+ this.name = "SSRFetchError";
108
883
  }
109
- return fetchApi.{{method}}(parametrize(params as never), payload as never);
110
- };
111
- {{/each}}
884
+ }
112
885
 
113
- export default {
114
- {{#each routeMethods}}
115
- {{method}},
116
- {{/each}}
117
- path,
118
- href,
119
- validationSchemas,
886
+ const pathnameOf = (input) => {
887
+ try {
888
+ if (typeof input === "string") {
889
+ return new URL(input, "http://x").pathname;
890
+ }
891
+ if (input instanceof URL) {
892
+ return input.pathname;
893
+ }
894
+ if (input instanceof Request) {
895
+ return new URL(input.url).pathname;
896
+ }
897
+ } catch {}
898
+ return String(input);
120
899
  };
121
- `,P=`export type MaybeWrapped<T> = T;
900
+ `,Oe=`export type MaybeWrapped<T> = T;
122
901
  export const unwrap = <T>(data: T) => data;
123
- `,F=g(e=>{let{createPath:t,createImportHelpers:n}=y(e),{renderToFile:r}=x({helpers:{...n({origin:`lib`}),...w()}}),i=async(e,n)=>{let i=e.flatMap(({kind:e,entry:t})=>e===`apiRoute`?[t]:[]).sort(S);await r(t.lib(`fetch.ts`),M,{routes:i});for(let{kind:e,entry:i}of n)if(e===`apiRoute`){let e=[];for(let t of i.validationDefinitions)if(t.target===`response`)for(let{id:n,body:r,resolvedType:i}of t.variants)r&&e.push({id:n,target:t.target,method:t.method,resolvedType:i});else{let{id:n,resolvedType:r}=t.schema;e.push({id:n,target:t.target,method:t.method,resolvedType:r})}let n=i.methods.map(t=>({method:t,payloadTypes:e.filter(e=>e.method===t&&![`headers`,`cookies`,`response`].includes(e.target)),responseType:e.find(e=>e.target===`response`&&e.method===t)})),a=Object.values(e.reduce((e,{id:t,target:n,method:r,resolvedType:i})=>(n===`response`&&(e[r]||(e[r]={method:r,types:[]}),e[r].types.push({id:t,target:n,method:r,resolvedType:i})),e),{}));await r(t.libApi(i.name,`fetch.ts`),N,{route:i,validationTypes:e,routeMethods:n,responseTypes:a})}};return{async start(){for(let[e,n]of[[`unwrap.ts`,P],[`@fetch/transport.ts`,j],[`@fetch/index.ts`,A]])await r(t.lib(e),n,{})},async watch(e,t){await i(e,e.filter(p(t,[`create`,`update`])))},async build(e){await i(e,e)},async ssrBuild(){for(let[e,n]of[[`@fetch/transport.ts`,`export { transport } from "${l.libPrefix}/@ssr/fetch";`]])await r(t.lib(e),n,{})}}}),I=h({meta:{name:`Fetch`,slot:`fetch`},factory:F}),L={type:`module`,private:!0,name:`@kosmojs/h3-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/h3-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,crc:`^4.3.2`},devDependencies:{h3:`2.0.1-rc.29`,vite:`^8.2.2`}},R=`import { H3, type Middleware } from "h3";
902
+ `,ke=v(e=>{let{createPath:t,createImportHelpers:n}=S(e),{render:r,renderToFile:i}=w({helpers:{...n({origin:`lib`}),...A()}}),a=async(e,n)=>{let r=e.flatMap(({kind:e,entry:t})=>e===`apiRoute`?[t]:[]).sort(E);await i(t.lib(`fetch.ts`),Te,{routes:r});for(let{kind:e,entry:r}of n)if(e===`apiRoute`){let e=[];for(let t of r.validationDefinitions)if(t.target===`response`)for(let{id:n,status:r,body:i,resolvedType:a}of t.variants)!i||Math.floor(r/100)!==2||e.push({id:n,target:t.target,method:t.method,resolvedType:a});else{let{id:n,resolvedType:r}=t.schema;e.push({id:n,target:t.target,method:t.method,resolvedType:r})}let n=Object.keys(te),a=r.methods.flatMap(t=>n.includes(t)?[{method:t,payloadTypes:e.filter(e=>e.method===t&&![`headers`,`cookies`,`response`].includes(e.target)),responseType:e.find(e=>e.target===`response`&&e.method===t)}]:[]),o=Object.values(e.reduce((e,{id:t,target:n,method:r,resolvedType:i})=>(n===`response`&&(e[r]||(e[r]={method:r,types:[]}),e[r].types.push({id:t,target:n,method:r,resolvedType:i})),e),{}));await i(t.libApi(r.name,`fetch.ts`),Ee,{route:r,validationTypes:e,routeMethods:a,responseTypes:o})}};return{async start(){for(let[e,n]of[[`unwrap.ts`,Oe]])await i(t.lib(e),n,{})},async watch(e,t){await a(e,e.filter(h(t,[`create`,`update`])))},async build(e){await a(e,e)},virtualModules(){return[{specifier:`virtual:kosmo/fetch-transport`,csr:`export const transport = undefined;`,ssr:r(De,{})}]}}}),Ae=_({meta:{name:`Fetch`,slot:`fetch`},dependencies:{"light-my-request":we.devDependencies[`light-my-request`]},factory:ke}),je={type:`module`,private:!0,name:`@kosmojs/h3-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/h3-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,crc:`^4.3.2`},devDependencies:{h3:`2.0.1-rc.29`,vite:`^8.2.2`}},Me=`import { H3, type Middleware } from "h3";
124
903
 
125
904
  import type { Route, RouteDebugOption } from "@kosmojs/core/api";
126
905
 
127
- export type App = H3;
906
+ /**
907
+ * The interface is a nameable symbol of this module, emit stops here and never chases through to the class.
908
+ * */
909
+ export interface App extends H3 {}
128
910
 
129
911
  export type AppOptions = ConstructorParameters<typeof H3>[0] & {
130
912
  debug?: RouteDebugOption;
@@ -168,23 +950,65 @@ export function appFactory(
168
950
  } else if (debug) {
169
951
  console.log(route.debug[typeof debug === "string" ? debug : "full"]);
170
952
  }
171
- for (const method of route.methods) {
172
- // last middleware is the handler
173
- const handler = route.middleware.at(-1);
174
- if (handler) {
175
- app.on(method, route.path, handler as never, {
953
+
954
+ // last middleware is the handler
955
+ const handler = route.middleware.at(-1);
956
+
957
+ if (handler) {
958
+ app.on(route.method, route.path, handler as never, {
959
+ middleware: route.middleware.slice(0, -1),
960
+ });
961
+ if (
962
+ route.method === "GET" &&
963
+ !routes.some((e) => e.path === route.path && e.method === "HEAD")
964
+ ) {
965
+ /**
966
+ * Register HEAD against the sibling GET handler,
967
+ * matching the HEAD-via-GET dispatch hono and koa provide natively.
968
+ * */
969
+ app.on("HEAD", route.path, handler as never, {
176
970
  middleware: route.middleware.slice(0, -1),
177
971
  });
178
972
  }
179
973
  }
180
974
  }
181
975
 
976
+ const routesByPath = routes.reduce<Record<string, Array<Route<Middleware>>>>(
977
+ (map, route) => {
978
+ if (!map[route.path]) {
979
+ map[route.path] = [];
980
+ }
981
+ map[route.path].push(route);
982
+ return map;
983
+ },
984
+ {},
985
+ );
986
+
987
+ for (const [path, routes] of Object.entries(routesByPath)) {
988
+ const methods = routes.map((e) => e.method);
989
+
990
+ app.all(path, (event) => {
991
+ const allowedMethods = new Set([...methods, "OPTIONS"]);
992
+
993
+ if (methods.includes("GET")) {
994
+ allowedMethods.add("HEAD");
995
+ }
996
+
997
+ const status = event.req.method === "OPTIONS" ? 204 : 405;
998
+
999
+ return new Response(undefined, {
1000
+ status,
1001
+ headers: { Allow: [...allowedMethods].join(", ") },
1002
+ });
1003
+ });
1004
+ }
1005
+
182
1006
  return app;
183
1007
  }
184
- `,z=`import type { DevSetup } from "@kosmojs/core/api";
1008
+ `,Ne=`import type { DevSetup } from "@kosmojs/core/api";
185
1009
 
186
1010
  export const devSetup = (setup: DevSetup) => setup;
187
- `,B=`import { type H3Event, HTTPError } from "h3";
1011
+ `,Pe=`import { type H3Event, HTTPError } from "h3";
188
1012
 
189
1013
  import { ValidationError } from "@kosmojs/core/errors";
190
1014
 
@@ -208,7 +1032,17 @@ export const errorHandlerFactory: ErrorHandlerFactory = (handler) => {
208
1032
  );
209
1033
  };
210
1034
  };
211
- `,me=`import { type H3Event, readBody } from "h3";
1035
+ `,Fe=`import { createListener } from "./server";
1036
+
1037
+ import app from "{{ createImport 'api' 'app' }}";
1038
+
1039
+ /**
1040
+ * Entry for the \`dist/<folder>/api/listener.js\` bundle.
1041
+ * Exposes this folder's API as a plain node:http listener, mounted by \`dist/run.js\`
1042
+ * next to the other folders. Nothing here listens on a port - \`server.ts\` does that.
1043
+ * */
1044
+ export default createListener(app);
1045
+ `,Ie=`import { type H3Event, readBody } from "h3";
212
1046
 
213
1047
  import {
214
1048
  parseCookies,
@@ -248,7 +1082,8 @@ export const bodyparsers: {
248
1082
  return event.req.text();
249
1083
  },
250
1084
  };
251
- `,he=`import type { Middleware } from "h3";
1085
+ `,Le=`import { command } from "virtual:kosmo/env";
1086
+ import type { Middleware } from "h3";
252
1087
 
253
1088
  import type {
254
1089
  RequestBodyTarget,
@@ -333,7 +1168,9 @@ export const createRouteMiddleware: CreateRouteMiddleware<
333
1168
  target === "query"
334
1169
  ? normalizeSearchParams(
335
1170
  parser(event),
336
- event.req.method as never,
1171
+ event.req.method === "HEAD"
1172
+ ? "GET"
1173
+ : event.req.method,
337
1174
  )
338
1175
  : parser(event),
339
1176
  );
@@ -411,7 +1248,10 @@ export const createRouteMiddleware: CreateRouteMiddleware<
411
1248
  * */
412
1249
  use(
413
1250
  async function useValidateResponse(event, next) {
414
- const variants = validationSchemas.response?.[event.req.method] || [];
1251
+ const variants =
1252
+ validationSchemas.response?.[
1253
+ event.req.method === "HEAD" ? "GET" : event.req.method
1254
+ ] || [];
415
1255
 
416
1256
  if (!Array.isArray(variants) || !variants.length) {
417
1257
  return next();
@@ -420,13 +1260,13 @@ export const createRouteMiddleware: CreateRouteMiddleware<
420
1260
  // options are same for all variants
421
1261
  const { runtimeValidation, customErrors } = variants[0];
422
1262
 
423
- if (KOSMO_PRODUCTION_BUILD) {
424
- // skip if undefined or explicitly set to false
1263
+ if (command === "build") {
1264
+ // production build - skip if undefined or explicitly set to false
425
1265
  if (runtimeValidation === undefined || runtimeValidation === false) {
426
1266
  return next();
427
1267
  }
428
1268
  } else {
429
- // skip only if explicitly set to false
1269
+ // dev mode - skip only if explicitly set to false
430
1270
  if (runtimeValidation === false) {
431
1271
  return next();
432
1272
  }
@@ -460,6 +1300,11 @@ export const createRouteMiddleware: CreateRouteMiddleware<
460
1300
  : "application/json"),
461
1301
  };
462
1302
 
1303
+ // validate only 2xx responses
1304
+ if (Math.floor(response.status / 100) !== 2) {
1305
+ return;
1306
+ }
1307
+
463
1308
  // Validate body only for JSON variants
464
1309
  if (variants.some((e) => e.contentType?.includes("json"))) {
465
1310
  response.body = rawResponse
@@ -598,7 +1443,9 @@ export const createRouteMiddleware: CreateRouteMiddleware<
598
1443
  use(
599
1444
  async (event, next) => {
600
1445
  const schema = {
601
- ...validationSchemas[target]?.[event.req.method],
1446
+ ...validationSchemas[target]?.[
1447
+ event.req.method === "HEAD" ? "GET" : event.req.method
1448
+ ],
602
1449
  };
603
1450
  if (schema.validate && schema.runtimeValidation !== false) {
604
1451
  schema.validate(await loadData(event as never));
@@ -628,7 +1475,7 @@ export const routes = createRoutes<ParameterizedMiddleware, Middleware>(
628
1475
  createRouteMiddleware,
629
1476
  },
630
1477
  );
631
- `,ge=`import { join } from "node:path";
1478
+ `,Re=`import { join } from "node:path";
632
1479
 
633
1480
  import type { RouteSource } from "@kosmojs/core/api";
634
1481
 
@@ -680,12 +1527,25 @@ export const routeSources: Array<RouteSource<never>> = [
680
1527
  },
681
1528
  {{/each}}
682
1529
  ];
683
- `,_e=`import { parseArgs, styleText } from "node:util";
1530
+ `,ze=`import type { IncomingMessage, ServerResponse } from "node:http";
1531
+ import { parseArgs, styleText } from "node:util";
684
1532
 
685
1533
  import { serve as h3serve } from "h3";
1534
+ import { toNodeHandler } from "h3/node";
686
1535
 
687
1536
  import type { App } from "./app";
688
1537
 
1538
+ export type NodeListener = (req: IncomingMessage, res: ServerResponse) => void;
1539
+
1540
+ /**
1541
+ * Wrap the app into a node:http request listener.
1542
+ * Used by dist/run.js to mount this folder's API next to other folders;
1543
+ * the standalone server (\`serve\`) binds the app through h3's own adapter instead.
1544
+ * */
1545
+ export const createListener = <T extends App>(app: T): NodeListener => {
1546
+ return toNodeHandler(app);
1547
+ };
1548
+
689
1549
  type Handles = {
690
1550
  port?: number | undefined;
691
1551
  onListen?: () => Promise<void>;
@@ -728,7 +1588,7 @@ export const serve = async <T extends App>(app: T, opt?: Handles) => {
728
1588
 
729
1589
  return server as never;
730
1590
  };
731
- `,ve=`import type { H3Event, H3EventContext } from "h3";
1591
+ `,Be=`import type { H3Event, H3EventContext } from "h3";
732
1592
 
733
1593
  import type { ValidationDefmap, ValidationOptmap } from "@kosmojs/core";
734
1594
  import {
@@ -875,22 +1735,22 @@ export const defineRoute: <
875
1735
  use: use as never,
876
1736
  });
877
1737
  };
878
- `,ye=`export * from "./@api/app";
1738
+ `,Ve=`export * from "./@api/app";
879
1739
  export { appFactory as default } from "./@api/app";
880
1740
  export * from "./@api/dev";
881
1741
  export * from "./@api/errors";
882
1742
  export * from "./@api/router";
883
1743
  export * from "./@api/routes";
884
1744
  export * from "./@api/server";
885
- `,be=`import { onError } from "h3";
1745
+ `,He=`import { onError } from "h3";
886
1746
 
887
- import appFactory, { routes, type App } from "{{ createImport 'lib' 'api:factory' }}";
1747
+ import appFactory, { routes } from "{{ createImport 'lib' 'api:factory' }}";
888
1748
  import defaultErrorHandler from "./errors";
889
1749
 
890
1750
  export default appFactory(routes, ({ app }) => {
891
1751
  app.use(onError(defaultErrorHandler));
892
- }) as App;
893
- `,xe=`import { toNodeHandler } from "h3/node";
1752
+ });
1753
+ `,Ue=`import { toNodeHandler } from "h3/node";
894
1754
 
895
1755
  import app from "./app";
896
1756
 
@@ -910,10 +1770,10 @@ process.on("unhandledRejection", (reason) => {
910
1770
  console.error("Reason:", reason);
911
1771
  process.exit(1);
912
1772
  });
913
- `,Se=`export declare module "{{ createImport 'libApi' }}" {
1773
+ `,We=`export declare module "{{ createImport 'libApi' }}" {
914
1774
  interface DefaultContext {}
915
1775
  }
916
- `,Ce=`import { ValidationError } from "@kosmojs/core/errors";
1776
+ `,Ge=`import { ValidationError } from "@kosmojs/core/errors";
917
1777
  import { HTTPError } from "h3";
918
1778
 
919
1779
  import { errorHandlerFactory } from "{{ createImport 'lib' 'api:factory' }}";
@@ -939,14 +1799,14 @@ export default errorHandlerFactory(async (error, event) => {
939
1799
  headers: { "Content-Type": "text/plain" },
940
1800
  });
941
1801
  });
942
- `,we=`import { defineRoute } from "{{ createImport 'libApi' }}";
1802
+ `,Ke=`import { defineRoute } from "{{ createImport 'libApi' }}";
943
1803
 
944
1804
  export default defineRoute<"{{route.name}}">(({ GET }) => [
945
1805
  GET(async (event) => {
946
1806
  return "Automatically generated route";
947
1807
  }),
948
1808
  ]);
949
- `,Te=`import { use } from "{{ createImport 'libApi' }}";
1809
+ `,qe=`import { use } from "{{ createImport 'libApi' }}";
950
1810
 
951
1811
  export type UseT = {};
952
1812
 
@@ -955,11 +1815,11 @@ export default [
955
1815
  return next();
956
1816
  }),
957
1817
  ];
958
- `,Ee=`import { serve } from "{{ createImport 'lib' 'api:factory' }}";
1818
+ `,Je=`import { serve } from "{{ createImport 'lib' 'api:factory' }}";
959
1819
  import app from "./app";
960
1820
 
961
1821
  await serve(app);
962
- `,De=`import { use } from "{{ createImport 'libApi' }}";
1822
+ `,Ye=`import { use } from "{{ createImport 'libApi' }}";
963
1823
 
964
1824
  /**
965
1825
  * Define global middleware applied to all routes.
@@ -970,7 +1830,7 @@ export default [
970
1830
  return next();
971
1831
  }),
972
1832
  ];
973
- `,Oe=g((e,n)=>{let{createPath:r,createImportHelpers:i}=y(e),a=e=>e.length===0?`{}`:e.length===1?e[0]:`Override<${e[0]}, ${a(e.slice(1))}>`,{renderToFile:o}=x({helpers:{...i({origin:`lib`}),...w(),paramsDefaults({params:e}){return`[${e.schema.map(()=>`unknown?`).join(`, `)}]`},paramsMappings({params:e}){return`[${e.schema.map(({name:e,kind:t})=>`["${e}", unknown, ${t===`required`?`true`:`false`}]`).join(`, `)}]`},cascadingState({cascadingMiddleware:e}){return a(e.map(({id:e})=>`UseT${e}`))}}}),{renderToFile:s}=x({helpers:i({origin:`src`})}),l=e=>e?.trim().length===0,d=c(n?.templates,we),f=async e=>{for(let{kind:t,entry:n}of e)t===`apiRoute`?await s(r.api(n.file),d(n.name,n),{route:n},{overwrite:l}):t===`apiUse`&&await s(r.api(n.file),Te,{},{overwrite:l})},m=async e=>{let i=e.flatMap(({kind:e,entry:t})=>e===`apiUse`?[t]:[]),a=e.flatMap(({kind:e,entry:r})=>{if(e!==`apiRoute`)return[];let a=r.name.split(`/`).reduce((e,n)=>{let r=e[e.length-1];return e.push(r?t(r,n):n),e},[]),o={...r,path:r.h3Pattern,basename:r.name,cascadingMiddleware:i.flatMap(e=>a.some(t=>e.name===t)?[e]:[])};return[o,...Object.entries({...n?.alias}).flatMap(([e,t])=>{let n=b(e);return t===r.name?[{...o,name:e,basename:r.name,id:`${o.id}_${T(e)}`,alias:u(n),pathTokens:n}]:[]})]}).sort(S);for(let[e,t]of[[`@api/routes.ts`,ge]])await o(r.lib(e),t,{routes:a,cascadingMiddleware:i})};return{config({command:e}){return{define:{KOSMO_PRODUCTION_BUILD:e===`build`?`true`:`false`}}},async start(){for(let[e,t]of[[`api.ts`,ve],[`api:factory.ts`,ye],[`@api/app.ts`,R],[`@api/parsers.ts`,me],[`@api/dev.ts`,z],[`@api/errors.ts`,B],[`@api/router.ts`,he],[`@api/server.ts`,_e]])await o(r.lib(e),t,{});for(let[e,t]of[[`app.ts`,be],[`dev.ts`,xe],[`errors.ts`,Ce],[`server.ts`,Ee],[`use.ts`,De],[`env.d.ts`,Se]])await s(r.api(e),t,{},{overwrite:l})},async watch(e,t){await f(e.filter(p(t,[`create`]))),await m(e)},async build(e){await f(e),await m(e)}}}),ke=h({meta:{name:`H3`,slot:`backend`},dependencies:{h3:L.devDependencies.h3},factory:Oe}),V={type:`module`,private:!0,name:`@kosmojs/hono-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/hono-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,crc:`^4.3.2`},devDependencies:{"@hono/node-server":`^2.1.1`,hono:`^4.13.3`,vite:`^8.2.2`}},Ae=`import { Hono, type MiddlewareHandler } from "hono";
1833
+ `,Xe=v((e,n)=>{let{createPath:r,createImportHelpers:i}=S(e),a=e=>e.length===0?`{}`:e.length===1?e[0]:`Override<${e[0]}, ${a(e.slice(1))}>`,{renderToFile:o}=w({helpers:{...i({origin:`lib`}),...A(),paramsDefaults({params:e}){return`[${e.schema.map(()=>`unknown?`).join(`, `)}]`},paramsMappings({params:e}){return`[${e.schema.map(({name:e,kind:t})=>`["${e}", unknown, ${t===`required`?`true`:`false`}]`).join(`, `)}]`},cascadingState({cascadingMiddleware:e}){return a(e.map(({id:e})=>`UseT${e}`))}}}),{renderToFile:s}=w({helpers:i({origin:`src`})}),c=e=>e?.trim().length===0,u=l(n?.templates,Ke),d=async e=>{for(let{kind:t,entry:n}of e)t===`apiRoute`?await s(r.api(n.file),u(n.name,n),{route:n},{overwrite:c}):t===`apiUse`&&await s(r.api(n.file),qe,{},{overwrite:c})},p=async e=>{let i=e.flatMap(({kind:e,entry:t})=>e===`apiUse`?[t]:[]),a=e.flatMap(({kind:e,entry:r})=>{if(e!==`apiRoute`)return[];let a=r.name.split(`/`).reduce((e,n)=>{let r=e[e.length-1];return e.push(r?t(r,n):n),e},[]),o={...r,path:r.h3Pattern,basename:r.name,cascadingMiddleware:i.flatMap(e=>a.some(t=>e.name===t)?[e]:[])};return[o,...Object.entries({...n?.alias}).flatMap(([e,t])=>{let n=C(e);return t===r.name?[{...o,name:e,basename:r.name,id:`${o.id}_${j(e)}`,alias:f(n),pathTokens:n}]:[]})]}).sort(E);for(let[e,t]of[[`@api/routes.ts`,Re]])await o(r.lib(e),t,{routes:a,cascadingMiddleware:i})};return{async start(){for(let[e,t]of[[`api.ts`,Be],[`api:factory.ts`,Ve],[`@api/app.ts`,Me],[`@api/parsers.ts`,Ie],[`@api/dev.ts`,Ne],[`@api/errors.ts`,Pe],[`@api/listener.ts`,Fe],[`@api/router.ts`,Le],[`@api/server.ts`,ze]])await o(r.lib(e),t,{});for(let[e,t]of[[`app.ts`,He],[`dev.ts`,Ue],[`errors.ts`,Ge],[`server.ts`,Je],[`use.ts`,Ye],[`env.d.ts`,We]])await s(r.api(e),t,{},{overwrite:c})},async watch(e,t){await d(e.filter(h(t,[`create`]))),await p(e)},async build(e){await d(e),await p(e)}}}),Ze=_({meta:{name:`H3`,slot:`backend`},dependencies:{h3:je.devDependencies.h3},factory:Xe}),V={type:`module`,private:!0,name:`@kosmojs/hono-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/hono-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,crc:`^4.3.2`},devDependencies:{"@hono/node-server":`^2.1.1`,hono:`^4.13.3`,vite:`^8.2.2`}},Qe=`import { Hono, type MiddlewareHandler } from "hono";
974
1834
  import type { Router } from "hono/router";
975
1835
  import { RegExpRouter } from "hono/router/reg-exp-router";
976
1836
  import { SmartRouter } from "hono/router/smart-router";
@@ -1037,15 +1897,45 @@ export function appFactory(
1037
1897
  } else if (debug) {
1038
1898
  console.log(route.debug[typeof debug === "string" ? debug : "full"]);
1039
1899
  }
1040
- app.on(route.methods, [route.path], ...route.middleware);
1900
+
1901
+ app.on(route.method, [route.path], ...route.middleware);
1902
+ }
1903
+
1904
+ const routesByPath = routes.reduce<
1905
+ Record<string, Array<Route<MiddlewareHandler>>>
1906
+ >((map, route) => {
1907
+ if (!map[route.path]) {
1908
+ map[route.path] = [];
1909
+ }
1910
+ map[route.path].push(route);
1911
+ return map;
1912
+ }, {});
1913
+
1914
+ for (const [path, routes] of Object.entries(routesByPath)) {
1915
+ const methods = routes.map((e) => e.method);
1916
+
1917
+ app.all(path, (ctx) => {
1918
+ const allowedMethods = new Set([...methods, "OPTIONS"]);
1919
+
1920
+ if (methods.includes("GET")) {
1921
+ allowedMethods.add("HEAD");
1922
+ }
1923
+
1924
+ const status = ctx.req.method === "OPTIONS" ? 204 : 405;
1925
+
1926
+ return new Response(undefined, {
1927
+ status,
1928
+ headers: { Allow: [...allowedMethods].join(", ") },
1929
+ });
1930
+ });
1041
1931
  }
1042
1932
 
1043
1933
  return app as never;
1044
1934
  }
1045
- `,je=`import type { DevSetup } from "@kosmojs/core/api";
1935
+ `,$e=`import type { DevSetup } from "@kosmojs/core/api";
1046
1936
 
1047
1937
  export const devSetup = (setup: DevSetup) => setup;
1048
- `,Me=`import type { Context } from "hono";
1938
+ `,et=`import type { Context } from "hono";
1049
1939
 
1050
1940
  import type { AppEnv } from "./app";
1051
1941
 
@@ -1059,7 +1949,17 @@ export type ErrorHandlerFactory = (handler: ErrorHandler) => ErrorHandler;
1059
1949
  export const errorHandlerFactory: ErrorHandlerFactory = (handler) => {
1060
1950
  return handler;
1061
1951
  };
1062
- `,Ne=`import type { Context } from "hono";
1952
+ `,tt=`import { createListener } from "./server";
1953
+
1954
+ import app from "{{ createImport 'api' 'app' }}";
1955
+
1956
+ /**
1957
+ * Entry for the \`dist/<folder>/api/listener.js\` bundle.
1958
+ * Exposes this folder's API as a plain node:http listener, mounted by \`dist/run.js\`
1959
+ * next to the other folders. Nothing here listens on a port - \`server.ts\` does that.
1960
+ * */
1961
+ export default createListener(app);
1962
+ `,nt=`import type { Context } from "hono";
1063
1963
 
1064
1964
  import {
1065
1965
  parseCookies,
@@ -1108,7 +2008,8 @@ export const bodyparsers: {
1108
2008
  return ctx.req[as]();
1109
2009
  },
1110
2010
  };
1111
- `,Pe=`import type { MiddlewareHandler } from "hono";
2011
+ `,rt=`import { command } from "virtual:kosmo/env";
2012
+ import type { MiddlewareHandler } from "hono";
1112
2013
 
1113
2014
  import type {
1114
2015
  RequestBodyTarget,
@@ -1194,7 +2095,7 @@ export const createRouteMiddleware: CreateRouteMiddleware<
1194
2095
  target === "query"
1195
2096
  ? normalizeSearchParams(
1196
2097
  parser(ctx),
1197
- ctx.req.method as never,
2098
+ ctx.req.method === "HEAD" ? "GET" : ctx.req.method,
1198
2099
  )
1199
2100
  : parser(ctx),
1200
2101
  );
@@ -1274,7 +2175,10 @@ export const createRouteMiddleware: CreateRouteMiddleware<
1274
2175
  * */
1275
2176
  use(
1276
2177
  async function useValidateResponse(ctx, next) {
1277
- const variants = validationSchemas.response?.[ctx.req.method] || [];
2178
+ const variants =
2179
+ validationSchemas.response?.[
2180
+ ctx.req.method === "HEAD" ? "GET" : ctx.req.method
2181
+ ] || [];
1278
2182
 
1279
2183
  if (!Array.isArray(variants) || !variants.length) {
1280
2184
  return next();
@@ -1283,13 +2187,13 @@ export const createRouteMiddleware: CreateRouteMiddleware<
1283
2187
  // options are same for all variants
1284
2188
  const { runtimeValidation, customErrors } = variants[0];
1285
2189
 
1286
- if (KOSMO_PRODUCTION_BUILD) {
1287
- // skip if undefined or explicitly set to false
2190
+ if (command === "build") {
2191
+ // production build - skip if undefined or explicitly set to false
1288
2192
  if (runtimeValidation === undefined || runtimeValidation === false) {
1289
2193
  return next();
1290
2194
  }
1291
2195
  } else {
1292
- // skip only if explicitly set to false
2196
+ // dev mode - skip only if explicitly set to false
1293
2197
  if (runtimeValidation === false) {
1294
2198
  return next();
1295
2199
  }
@@ -1307,10 +2211,17 @@ export const createRouteMiddleware: CreateRouteMiddleware<
1307
2211
  contentType: ctx.res.headers.get("Content-Type"),
1308
2212
  };
1309
2213
 
2214
+ // validate only 2xx responses
2215
+ if (Math.floor(response.status / 100) !== 2) {
2216
+ return;
2217
+ }
2218
+
1310
2219
  // Validate body only for JSON variants
1311
2220
  if (variants.some((e) => e.contentType?.includes("json"))) {
1312
- const cloned = ctx.res.clone();
1313
- response.body = await cloned.json();
2221
+ response.body = await ctx.res
2222
+ .clone()
2223
+ .json()
2224
+ .catch(() => undefined);
1314
2225
  }
1315
2226
 
1316
2227
  /**
@@ -1445,7 +2356,9 @@ export const createRouteMiddleware: CreateRouteMiddleware<
1445
2356
  use(
1446
2357
  async (ctx, next) => {
1447
2358
  const schema = {
1448
- ...validationSchemas[target]?.[ctx.req.method],
2359
+ ...validationSchemas[target]?.[
2360
+ ctx.req.method === "HEAD" ? "GET" : ctx.req.method
2361
+ ],
1449
2362
  };
1450
2363
  if (schema.validate && schema.runtimeValidation !== false) {
1451
2364
  schema.validate(await loadData(ctx as never));
@@ -1475,7 +2388,7 @@ export const routes = createRoutes<ParameterizedMiddleware, MiddlewareHandler>(
1475
2388
  createRouteMiddleware,
1476
2389
  },
1477
2390
  );
1478
- `,Fe=`import { join } from "node:path";
2391
+ `,it=`import { join } from "node:path";
1479
2392
 
1480
2393
  import type { RouteSource } from "@kosmojs/core/api";
1481
2394
 
@@ -1527,13 +2440,25 @@ export const routeSources: Array<RouteSource<never>> = [
1527
2440
  },
1528
2441
  {{/each}}
1529
2442
  ];
1530
- `,Ie=`import { chmod, unlink } from "node:fs/promises";
2443
+ `,at=`import { chmod, unlink } from "node:fs/promises";
2444
+ import type { IncomingMessage, ServerResponse } from "node:http";
1531
2445
  import { parseArgs, styleText } from "node:util";
1532
2446
 
1533
- import { createAdaptorServer } from "@hono/node-server";
2447
+ import { createAdaptorServer, getRequestListener } from "@hono/node-server";
1534
2448
 
1535
2449
  import type { App } from "./app";
1536
2450
 
2451
+ export type NodeListener = (req: IncomingMessage, res: ServerResponse) => void;
2452
+
2453
+ /**
2454
+ * Wrap the app into a node:http request listener.
2455
+ * Used by dist/run.js to mount this folder's API next to other folders;
2456
+ * the standalone server (\`serve\`) binds the app through the runtime's native adapter instead.
2457
+ * */
2458
+ export const createListener = <T extends App>(app: T): NodeListener => {
2459
+ return getRequestListener(app.fetch);
2460
+ };
2461
+
1537
2462
  type Handles = {
1538
2463
  port?: number | undefined;
1539
2464
  sock?: string | undefined;
@@ -1612,7 +2537,7 @@ export const serve = async <T extends App>(app: T, opt?: Handles) => {
1612
2537
 
1613
2538
  return server as never;
1614
2539
  };
1615
- `,Le=`import type { Context, Next } from "hono";
2540
+ `,ot=`import type { Context, Next } from "hono";
1616
2541
 
1617
2542
  import type { ValidationDefmap, ValidationOptmap } from "@kosmojs/core";
1618
2543
  import {
@@ -1795,20 +2720,20 @@ export const defineRoute: <
1795
2720
  use: use as never,
1796
2721
  });
1797
2722
  };
1798
- `,Re=`export * from "./@api/app";
2723
+ `,st=`export * from "./@api/app";
1799
2724
  export { appFactory as default } from "./@api/app";
1800
2725
  export * from "./@api/dev";
1801
2726
  export * from "./@api/errors";
1802
2727
  export * from "./@api/router";
1803
2728
  export * from "./@api/routes";
1804
2729
  export * from "./@api/server";
1805
- `,ze=`import appFactory, { routes } from "{{ createImport 'lib' 'api:factory' }}";
2730
+ `,ct=`import appFactory, { routes } from "{{ createImport 'lib' 'api:factory' }}";
1806
2731
  import defaultErrorHandler from "./errors";
1807
2732
 
1808
2733
  export default appFactory(routes, ({ app }) => {
1809
2734
  app.onError(defaultErrorHandler);
1810
2735
  })
1811
- `,Be=`import { getRequestListener } from "@hono/node-server";
2736
+ `,lt=`import { getRequestListener } from "@hono/node-server";
1812
2737
 
1813
2738
  import app from "./app";
1814
2739
 
@@ -1829,11 +2754,11 @@ process.on("unhandledRejection", (reason) => {
1829
2754
  process.exit(1);
1830
2755
  });
1831
2756
 
1832
- `,Ve=`export declare module "{{ createImport 'libApi' }}" {
2757
+ `,ut=`export declare module "{{ createImport 'libApi' }}" {
1833
2758
  interface DefaultVariables {}
1834
2759
  interface DefaultBindings {}
1835
2760
  }
1836
- `,He=`import { accepts } from "hono/accepts";
2761
+ `,dt=`import { accepts } from "hono/accepts";
1837
2762
  import { HTTPException } from "hono/http-exception";
1838
2763
 
1839
2764
  import { ValidationError, HTTPError } from "@kosmojs/core/errors";
@@ -1865,7 +2790,7 @@ export default errorHandlerFactory(async (error, ctx) => {
1865
2790
  ? ctx.json({ error: message }, status)
1866
2791
  : ctx.text(message, status);
1867
2792
  });
1868
- `,Ue=`import { defineRoute } from "{{ createImport 'libApi' }}";
2793
+ `,ft=`import { defineRoute } from "{{ createImport 'libApi' }}";
1869
2794
 
1870
2795
  export default defineRoute<"{{route.name}}">(({ GET }) => [
1871
2796
  GET(async (ctx) => {
@@ -1874,7 +2799,7 @@ export default defineRoute<"{{route.name}}">(({ GET }) => [
1874
2799
  return ctx.text("Automatically generated route");
1875
2800
  }),
1876
2801
  ]);
1877
- `,We=`import { use } from "{{ createImport 'libApi' }}";
2802
+ `,pt=`import { use } from "{{ createImport 'libApi' }}";
1878
2803
 
1879
2804
  export type UseT = {};
1880
2805
 
@@ -1885,11 +2810,11 @@ export default [
1885
2810
  return next();
1886
2811
  }),
1887
2812
  ];
1888
- `,Ge=`import { serve } from "{{ createImport 'lib' 'api:factory' }}";
2813
+ `,mt=`import { serve } from "{{ createImport 'lib' 'api:factory' }}";
1889
2814
  import app from "./app";
1890
2815
 
1891
2816
  await serve(app);
1892
- `,Ke=`import { use } from "{{ createImport 'libApi' }}";
2817
+ `,ht=`import { use } from "{{ createImport 'libApi' }}";
1893
2818
 
1894
2819
  /**
1895
2820
  * Define global middleware applied to all routes.
@@ -1900,7 +2825,9 @@ export default [
1900
2825
  return next();
1901
2826
  }),
1902
2827
  ];
1903
- `,qe=g((e,n)=>{let{createPath:r,createImportHelpers:i}=y(e),a=e=>e.length===0?`{}`:e.length===1?e[0]:`Override<${e[0]}, ${a(e.slice(1))}>`,{renderToFile:o}=x({helpers:{...i({origin:`lib`}),...w(),paramsDefaults({params:e}){return`[${e.schema.map(()=>`unknown?`).join(`, `)}]`},paramsMappings({params:e}){return`[${e.schema.map(({name:e,kind:t})=>`["${e}", unknown, ${t===`required`?`true`:`false`}]`).join(`, `)}]`},cascadingState({cascadingMiddleware:e}){return a(e.map(({id:e})=>`UseT${e}`))}}}),{renderToFile:s}=x({helpers:i({origin:`src`})}),l=e=>e?.trim().length===0,u=c(n?.templates,Ue),f=async e=>{for(let{kind:t,entry:n}of e)t===`apiRoute`?await s(r.api(n.file),u(n.name,n),{route:n},{overwrite:l}):t===`apiUse`&&await s(r.api(n.file),We,{},{overwrite:l})},m=async e=>{let i=e.flatMap(({kind:e,entry:t})=>e===`apiUse`?[t]:[]),a=e.flatMap(({kind:e,entry:r})=>{if(e!==`apiRoute`)return[];let a=r.name.split(`/`).reduce((e,n)=>{let r=e[e.length-1];return e.push(r?t(r,n):n),e},[]),o={...r,path:r.honoPattern,basename:r.name,cascadingMiddleware:i.flatMap(e=>a.some(t=>e.name===t)?[e]:[])};return[o,...Object.entries({...n?.alias}).flatMap(([e,t])=>{let n=b(e);return t===r.name?[{...o,name:e,basename:r.name,id:`${o.id}_${T(e)}`,alias:d(n),pathTokens:n}]:[]})]}).sort(S);for(let[e,t]of[[`@api/routes.ts`,Fe]])await o(r.lib(e),t,{routes:a,cascadingMiddleware:i})};return{config({command:e}){return{define:{KOSMO_PRODUCTION_BUILD:e===`build`?`true`:`false`}}},async start(){for(let[e,t]of[[`api.ts`,Le],[`api:factory.ts`,Re],[`@api/app.ts`,Ae],[`@api/parsers.ts`,Ne],[`@api/dev.ts`,je],[`@api/errors.ts`,Me],[`@api/router.ts`,Pe],[`@api/server.ts`,Ie]])await o(r.lib(e),t,{});for(let[e,t]of[[`app.ts`,ze],[`dev.ts`,Be],[`errors.ts`,He],[`server.ts`,Ge],[`use.ts`,Ke],[`env.d.ts`,Ve]])await s(r.api(e),t,{},{overwrite:l})},async watch(e,t){await f(e.filter(p(t,[`create`]))),await m(e)},async build(e){await f(e),await m(e)}}}),Je=h({meta:{name:`Hono`,slot:`backend`},dependencies:{hono:V.devDependencies.hono,"@hono/node-server":V.devDependencies[`@hono/node-server`]},factory:qe}),H={type:`module`,private:!0,name:`@kosmojs/koa-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`},"./lib":{types:`./pkg/lib.d.ts`,default:`./pkg/lib.js`}},scripts:{build:`wsbuild src/index.ts src/lib.ts`,test:`vitest --root ../.. --project generators/koa-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,crc:`^4.3.2`},devDependencies:{"@koa/router":`^15.7.0`,"@types/formidable":`^3.5.1`,"@types/koa":`^3.0.3`,"@types/koa-compose":`^3.2.9`,formidable:`^3.5.4`,koa:`^3.2.1`,"koa-compose":`^4.1.0`,"light-my-request":`^6.6.0`,"raw-body":`^4.0.0`,vite:`^8.2.2`}},Ye=`import Router, { type RouterMiddleware } from "@koa/router";
2828
+ `,gt=v((e,n)=>{let{createPath:r,createImportHelpers:i}=S(e),a=e=>e.length===0?`{}`:e.length===1?e[0]:`Override<${e[0]}, ${a(e.slice(1))}>`,{renderToFile:o}=w({helpers:{...i({origin:`lib`}),...A(),paramsDefaults({params:e}){return`[${e.schema.map(()=>`unknown?`).join(`, `)}]`},paramsMappings({params:e}){return`[${e.schema.map(({name:e,kind:t})=>`["${e}", unknown, ${t===`required`?`true`:`false`}]`).join(`, `)}]`},cascadingState({cascadingMiddleware:e}){return a(e.map(({id:e})=>`UseT${e}`))}}}),{renderToFile:s}=w({helpers:i({origin:`src`})}),c=e=>e?.trim().length===0,u=l(n?.templates,ft),d=async e=>{for(let{kind:t,entry:n}of e)t===`apiRoute`?await s(r.api(n.file),u(n.name,n),{route:n},{overwrite:c}):t===`apiUse`&&await s(r.api(n.file),pt,{},{overwrite:c})},f=async e=>{let i=e.flatMap(({kind:e,entry:t})=>e===`apiUse`?[t]:[]),a=e.flatMap(({kind:e,entry:r})=>{if(e!==`apiRoute`)return[];let a=r.name.split(`/`).reduce((e,n)=>{let r=e[e.length-1];return e.push(r?t(r,n):n),e},[]),o={...r,path:r.honoPattern,basename:r.name,cascadingMiddleware:i.flatMap(e=>a.some(t=>e.name===t)?[e]:[])};return[o,...Object.entries({...n?.alias}).flatMap(([e,t])=>{let n=C(e);return t===r.name?[{...o,name:e,basename:r.name,id:`${o.id}_${j(e)}`,alias:p(n),pathTokens:n}]:[]})]}).sort(E);for(let[e,t]of[[`@api/routes.ts`,it]])await o(r.lib(e),t,{routes:a,cascadingMiddleware:i})};return{async start(){for(let[e,t]of[[`api.ts`,ot],[`api:factory.ts`,st],[`@api/app.ts`,Qe],[`@api/parsers.ts`,nt],[`@api/dev.ts`,$e],[`@api/errors.ts`,et],[`@api/listener.ts`,tt],[`@api/router.ts`,rt],[`@api/server.ts`,at]])await o(r.lib(e),t,{});for(let[e,t]of[[`app.ts`,ct],[`dev.ts`,lt],[`errors.ts`,dt],[`server.ts`,mt],[`use.ts`,ht],[`env.d.ts`,ut]])await s(r.api(e),t,{},{overwrite:c})},async watch(e,t){await d(e.filter(h(t,[`create`]))),await f(e)},async build(e){await d(e),await f(e)}}}),_t=_({meta:{name:`Hono`,slot:`backend`},dependencies:{hono:V.devDependencies.hono,"@hono/node-server":V.devDependencies[`@hono/node-server`]},factory:gt}),H={type:`module`,private:!0,name:`@kosmojs/koa-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`},"./lib":{types:`./pkg/lib.d.ts`,default:`./pkg/lib.js`}},scripts:{build:`wsbuild src/index.ts src/lib.ts`,test:`vitest --root ../.. --project generators/koa-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,crc:`^4.3.2`},devDependencies:{"@koa/router":`^15.7.0`,"@types/formidable":`^3.5.1`,"@types/koa":`^3.0.3`,"@types/koa-compose":`^3.2.9`,formidable:`^3.5.4`,koa:`^3.2.1`,"koa-compose":`^4.1.0`,"light-my-request":`^6.6.0`,"raw-body":`^4.0.0`,vite:`^8.2.2`}},vt=`import { styleText } from "node:util";
2829
+
2830
+ import Router, { type RouterMiddleware } from "@koa/router";
1904
2831
  import Koa from "koa";
1905
2832
 
1906
2833
  import type { Route, RouteDebugOption } from "@kosmojs/core/api";
@@ -1911,7 +2838,7 @@ export type App = Koa<DefaultState, DefaultContext>;
1911
2838
 
1912
2839
  export type AppOptions = ConstructorParameters<
1913
2840
  typeof Koa<DefaultState, DefaultContext>
1914
- >[0] & { router?: Router; debug?: RouteDebugOption };
2841
+ >[0] & { debug?: RouteDebugOption };
1915
2842
 
1916
2843
  export function appFactory(
1917
2844
  routes: Array<Route<RouterMiddleware>>,
@@ -1935,51 +2862,98 @@ export function appFactory(
1935
2862
  ): App {
1936
2863
  const [options, fn] = typeof rest[0] === "function" ? [{}, rest[0]] : rest;
1937
2864
 
1938
- const {
1939
- router = new Router(),
1940
- debug = undefined,
1941
- ...appOptions
1942
- } = {
2865
+ const { debug = undefined, ...appOptions } = {
1943
2866
  ...(options ? { ...options } : {}),
1944
2867
  };
1945
2868
 
2869
+ const app = new Koa(appOptions);
2870
+ const router = new Router();
2871
+
1946
2872
  for (const route of routes) {
1947
2873
  if (typeof debug === "function") {
1948
2874
  (debug as Function)(route.debug, route);
1949
2875
  } else if (debug) {
1950
2876
  console.log(route.debug[typeof debug === "string" ? debug : "full"]);
1951
2877
  }
1952
- router.register(route.path, route.methods, route.middleware, route);
2878
+ router.register(route.path, [route.method], route.middleware, route);
1953
2879
  }
1954
2880
 
1955
- const app = new Koa(appOptions);
2881
+ const routesByPath = routes.reduce<
2882
+ Record<string, Array<Route<RouterMiddleware>>>
2883
+ >((map, route) => {
2884
+ if (!map[route.path]) {
2885
+ map[route.path] = [];
2886
+ }
2887
+ map[route.path].push(route);
2888
+ return map;
2889
+ }, {});
2890
+
2891
+ for (const [path, routes] of Object.entries(routesByPath)) {
2892
+ const methods = routes.map((e) => e.method);
2893
+
2894
+ if (
2895
+ methods.includes("GET") &&
2896
+ methods.indexOf("HEAD") > methods.indexOf("GET")
2897
+ ) {
2898
+ console.warn("----");
2899
+ console.warn(
2900
+ \`\${styleText("yellow", "WARN")}: HEAD handler for \${styleText("blue", routes[0].name)} route is unreachable - \${styleText("magenta", "define HEAD before GET")}\`,
2901
+ );
2902
+ console.warn("----");
2903
+ }
2904
+
2905
+ router.all(path, (ctx) => {
2906
+ const allowedMethods = new Set([...methods, "OPTIONS"]);
2907
+
2908
+ if (methods.includes("GET")) {
2909
+ allowedMethods.add("HEAD");
2910
+ }
2911
+
2912
+ ctx.status = ctx.method === "OPTIONS" ? 204 : 405;
2913
+
2914
+ ctx.set("Allow", [...allowedMethods].join(", "));
2915
+ });
2916
+ }
1956
2917
 
1957
2918
  if (typeof fn === "function") {
1958
2919
  fn({ app, router });
1959
2920
  }
1960
2921
 
2922
+ // NOTE: Routes should be added last, after any middleware
2923
+ app.use(router.routes());
2924
+
1961
2925
  return app;
1962
2926
  }
1963
- `,Xe=`import type { DevSetup } from "@kosmojs/core/api";
2927
+ `,yt=`import type { DevSetup } from "@kosmojs/core/api";
1964
2928
 
1965
2929
  export const devSetup = (setup: DevSetup) => setup;
1966
- `,Ze=`import type {
2930
+ `,bt=`import type {
1967
2931
  DefaultContext,
1968
2932
  DefaultState,
1969
2933
  ParameterizedContext,
1970
2934
  } from "../api";
1971
2935
 
1972
2936
  type ErrorHandler = (
1973
- error: any,
1974
2937
  ctx: ParameterizedContext<unknown, DefaultState, DefaultContext>,
1975
- ) => Promise<void> | void;
2938
+ next: Function,
2939
+ ) => Promise<void>;
1976
2940
 
1977
2941
  export type ErrorHandlerFactory = (handler: ErrorHandler) => ErrorHandler;
1978
2942
 
1979
2943
  export const errorHandlerFactory: ErrorHandlerFactory = (handler) => {
1980
2944
  return handler;
1981
2945
  };
1982
- `,Qe=`import zlib from "node:zlib";
2946
+ `,xt=`import { createListener } from "./server";
2947
+
2948
+ import app from "{{ createImport 'api' 'app' }}";
2949
+
2950
+ /**
2951
+ * Entry for the \`dist/<folder>/api/listener.js\` bundle.
2952
+ * Exposes this folder's API as a plain node:http listener, mounted by \`dist/run.js\`
2953
+ * next to the other folders. Nothing here listens on a port - \`server.ts\` does that.
2954
+ * */
2955
+ export default createListener(app);
2956
+ `,St=`import zlib from "node:zlib";
1983
2957
 
1984
2958
  import type { RouterContext } from "@koa/router";
1985
2959
  import Formidable, { type Options as FormidableOptions } from "formidable";
@@ -2192,7 +3166,8 @@ export const bodyparsers: {
2192
3166
  return rawParser(stream, rawParserOptions);
2193
3167
  },
2194
3168
  };
2195
- `,$e=`import type { RouterMiddleware } from "@koa/router";
3169
+ `,Ct=`import { command } from "virtual:kosmo/env";
3170
+ import type { RouterMiddleware } from "@koa/router";
2196
3171
 
2197
3172
  import type {
2198
3173
  RequestBodyTarget,
@@ -2276,7 +3251,10 @@ export const createRouteMiddleware: CreateRouteMiddleware<
2276
3251
  ctx[StateKey].set(
2277
3252
  target,
2278
3253
  target === "query"
2279
- ? normalizeSearchParams(parser(ctx), ctx.method as never)
3254
+ ? normalizeSearchParams(
3255
+ parser(ctx),
3256
+ ctx.method === "HEAD" ? "GET" : ctx.method,
3257
+ )
2280
3258
  : parser(ctx),
2281
3259
  );
2282
3260
  }
@@ -2355,7 +3333,10 @@ export const createRouteMiddleware: CreateRouteMiddleware<
2355
3333
  * */
2356
3334
  use(
2357
3335
  async function useValidateResponse(ctx, next) {
2358
- const variants = validationSchemas.response?.[ctx.method] || [];
3336
+ const variants =
3337
+ validationSchemas.response?.[
3338
+ ctx.method === "HEAD" ? "GET" : ctx.method
3339
+ ] || [];
2359
3340
 
2360
3341
  if (!Array.isArray(variants) || !variants.length) {
2361
3342
  return next();
@@ -2364,13 +3345,13 @@ export const createRouteMiddleware: CreateRouteMiddleware<
2364
3345
  // options are same for all variants
2365
3346
  const { runtimeValidation, customErrors } = variants[0];
2366
3347
 
2367
- if (KOSMO_PRODUCTION_BUILD) {
2368
- // skip if undefined or explicitly set to false
3348
+ if (command === "build") {
3349
+ // production build - skip if undefined or explicitly set to false
2369
3350
  if (runtimeValidation === undefined || runtimeValidation === false) {
2370
3351
  return next();
2371
3352
  }
2372
3353
  } else {
2373
- // skip only if explicitly set to false
3354
+ // dev mode - skip only if explicitly set to false
2374
3355
  if (runtimeValidation === false) {
2375
3356
  return next();
2376
3357
  }
@@ -2389,6 +3370,11 @@ export const createRouteMiddleware: CreateRouteMiddleware<
2389
3370
  contentType: ctx.type,
2390
3371
  };
2391
3372
 
3373
+ // validate only 2xx responses
3374
+ if (Math.floor(response.status / 100) !== 2) {
3375
+ return;
3376
+ }
3377
+
2392
3378
  // Validate body only for JSON variants
2393
3379
  if (variants.some((e) => e.contentType?.includes("json"))) {
2394
3380
  response.body = ctx.body;
@@ -2527,7 +3513,9 @@ export const createRouteMiddleware: CreateRouteMiddleware<
2527
3513
  use(
2528
3514
  async (ctx, next) => {
2529
3515
  const schema = {
2530
- ...validationSchemas[target]?.[ctx.method],
3516
+ ...validationSchemas[target]?.[
3517
+ ctx.method === "HEAD" ? "GET" : ctx.method
3518
+ ],
2531
3519
  };
2532
3520
  if (schema.validate && schema.runtimeValidation !== false) {
2533
3521
  schema.validate(await loadData(ctx as never));
@@ -2557,7 +3545,7 @@ export const routes = createRoutes<ParameterizedMiddleware, RouterMiddleware>(
2557
3545
  createRouteMiddleware,
2558
3546
  },
2559
3547
  );
2560
- `,et=`import { join } from "node:path";
3548
+ `,wt=`import { join } from "node:path";
2561
3549
 
2562
3550
  import type { RouteSource } from "@kosmojs/core/api";
2563
3551
 
@@ -2609,11 +3597,23 @@ export const routeSources: Array<RouteSource<never>> = [
2609
3597
  },
2610
3598
  {{/each}}
2611
3599
  ];
2612
- `,tt=`import { chmod, unlink } from "node:fs/promises";
3600
+ `,Tt=`import { chmod, unlink } from "node:fs/promises";
3601
+ import type { IncomingMessage, ServerResponse } from "node:http";
2613
3602
  import { parseArgs, styleText } from "node:util";
2614
3603
 
2615
3604
  import type { App } from "./app";
2616
3605
 
3606
+ export type NodeListener = (req: IncomingMessage, res: ServerResponse) => void;
3607
+
3608
+ /**
3609
+ * Wrap the app into a node:http request listener.
3610
+ * Used by dist/run.js to mount this folder's API next to other folders;
3611
+ * the standalone server (\`serve\`) calls app.listen() directly instead.
3612
+ * */
3613
+ export const createListener = <T extends App>(app: T): NodeListener => {
3614
+ return app.callback();
3615
+ };
3616
+
2617
3617
  type Handles = {
2618
3618
  port?: number | undefined;
2619
3619
  sock?: string | undefined;
@@ -2672,7 +3672,7 @@ export const serve = async <T extends App>(app: T, opt?: Handles) => {
2672
3672
  const server = app.listen(port || sock, onListen);
2673
3673
  return server as never;
2674
3674
  };
2675
- `,nt=`import type { RouterContext } from "@koa/router";
3675
+ `,Et=`import type { RouterContext } from "@koa/router";
2676
3676
  import type { Next } from "koa";
2677
3677
 
2678
3678
  import type { ValidationDefmap, ValidationOptmap } from "@kosmojs/core";
@@ -2834,25 +3834,20 @@ export const defineRoute: <
2834
3834
  use: use as never,
2835
3835
  });
2836
3836
  };
2837
- `,rt=`export * from "./@api/app";
3837
+ `,Dt=`export * from "./@api/app";
2838
3838
  export { appFactory as default } from "./@api/app";
2839
3839
  export * from "./@api/dev";
2840
3840
  export * from "./@api/errors";
2841
3841
  export * from "./@api/router";
2842
3842
  export * from "./@api/routes";
2843
3843
  export * from "./@api/server";
2844
- `,it=`import appFactory, { routes } from "{{ createImport 'lib' 'api:factory' }}";
3844
+ `,Ot=`import appFactory, { routes } from "{{ createImport 'lib' 'api:factory' }}";
2845
3845
  import defaultErrorHandler from "./errors";
2846
3846
 
2847
- export default appFactory(routes, ({ app, router }) => {
2848
-
2849
- app.on("error", defaultErrorHandler);
2850
-
2851
- // NOTE: Routes should be added last, after any middleware
2852
- app.use(router.routes());
2853
-
2854
- });
2855
- `,at=`import app from "./app";
3847
+ export default appFactory(routes, ({ app }) => {
3848
+ app.use(defaultErrorHandler);
3849
+ })
3850
+ `,kt=`import app from "./app";
2856
3851
 
2857
3852
  import { devSetup } from "{{ createImport 'lib' 'api:factory' }}";
2858
3853
 
@@ -2870,39 +3865,43 @@ process.on("unhandledRejection", (reason) => {
2870
3865
  console.error("Reason:", reason);
2871
3866
  process.exit(1);
2872
3867
  });
2873
- `,ot=`export declare module "{{ createImport 'libApi' }}" {
3868
+ `,At=`export declare module "{{ createImport 'libApi' }}" {
2874
3869
  interface DefaultState {}
2875
3870
  interface DefaultContext {}
2876
3871
  }
2877
- `,st=`import { HTTPError, ValidationError } from "@kosmojs/core/errors";
3872
+ `,jt=`import { HTTPError, ValidationError } from "@kosmojs/core/errors";
2878
3873
 
2879
3874
  import { errorHandlerFactory } from "{{ createImport 'lib' 'api:factory' }}";
2880
3875
 
2881
- export default errorHandlerFactory(async (error, ctx) => {
2882
- const [status, message] = Array.isArray(error)
2883
- ? error
2884
- : error instanceof HTTPError
2885
- ? [error.status, error.message]
2886
- : error instanceof ValidationError
2887
- ? [400, \`\${error.target}: \${error.errorMessage}\`]
2888
- : [error.statusCode || 500, error.message];
2889
-
2890
- ctx.status = status;
2891
-
2892
- if (ctx.accepts("json")) {
2893
- ctx.body = { error: message };
2894
- } else {
2895
- ctx.body = message;
3876
+ export default errorHandlerFactory(async (ctx, next) => {
3877
+ try {
3878
+ await next();
3879
+ } catch (error: any) {
3880
+ const [status, message] = Array.isArray(error)
3881
+ ? error
3882
+ : error instanceof HTTPError
3883
+ ? [error.status, error.message]
3884
+ : error instanceof ValidationError
3885
+ ? [400, \`\${error.target}: \${error.errorMessage}\`]
3886
+ : [error.statusCode || 500, error.message];
3887
+
3888
+ ctx.status = status;
3889
+
3890
+ if (ctx.accepts("json")) {
3891
+ ctx.body = { error: message };
3892
+ } else {
3893
+ ctx.body = message;
3894
+ }
2896
3895
  }
2897
3896
  });
2898
- `,ct=`import { defineRoute } from "{{ createImport 'libApi' }}";
3897
+ `,Mt=`import { defineRoute } from "{{ createImport 'libApi' }}";
2899
3898
 
2900
3899
  export default defineRoute<"{{route.name}}">(({ GET }) => [
2901
3900
  GET(async (ctx) => {
2902
3901
  ctx.body = "Automatically generated route";
2903
3902
  }),
2904
3903
  ]);
2905
- `,lt=`import { use } from "{{ createImport 'libApi' }}";
3904
+ `,Nt=`import { use } from "{{ createImport 'libApi' }}";
2906
3905
 
2907
3906
  export type UseT = {};
2908
3907
 
@@ -2913,11 +3912,11 @@ export default [
2913
3912
  return next();
2914
3913
  }),
2915
3914
  ];
2916
- `,ut=`import { serve } from "{{ createImport 'lib' 'api:factory' }}";
3915
+ `,Pt=`import { serve } from "{{ createImport 'lib' 'api:factory' }}";
2917
3916
  import app from "./app";
2918
3917
 
2919
3918
  await serve(app);
2920
- `,dt=`import { use } from "{{ createImport 'libApi' }}";
3919
+ `,Ft=`import { use } from "{{ createImport 'libApi' }}";
2921
3920
 
2922
3921
  /**
2923
3922
  * Define global middleware applied to all routes.
@@ -2928,17 +3927,17 @@ export default [
2928
3927
  return next();
2929
3928
  }),
2930
3929
  ];
2931
- `,ft=g((e,n)=>{let{createPath:r,createImportHelpers:i}=y(e),a=e=>e.length===0?`{}`:e.length===1?e[0]:`Override<${e[0]}, ${a(e.slice(1))}>`,{renderToFile:o}=x({helpers:{...i({origin:`lib`}),...w(),paramsDefaults({params:e}){return`[${e.schema.map(()=>`unknown?`).join(`, `)}]`},paramsMappings({params:e}){return`[${e.schema.map(({name:e,kind:t})=>`["${e}", unknown, ${t===`required`?`true`:`false`}]`).join(`, `)}]`},cascadingState({cascadingMiddleware:e}){return a(e.map(({id:e})=>`UseT${e}`))}}}),{renderToFile:s}=x({helpers:i({origin:`src`})}),l=e=>e?.trim().length===0,u=c(n?.templates,ct),d=async e=>{for(let{kind:t,entry:n}of e)t===`apiRoute`?await s(r.api(n.file),u(n.name,n),{route:n},{overwrite:l}):t===`apiUse`&&await s(r.api(n.file),lt,{},{overwrite:l})},m=async e=>{let i=e.flatMap(({kind:e,entry:t})=>e===`apiUse`?[t]:[]),a=e.flatMap(({kind:e,entry:r})=>{if(e!==`apiRoute`)return[];let a=r.name.split(`/`).reduce((e,n)=>{let r=e[e.length-1];return e.push(r?t(r,n):n),e},[]),o={...r,basename:r.name,path:r.pathPattern,cascadingMiddleware:i.flatMap(e=>a.some(t=>e.name===t)?[e]:[])};return[o,...Object.entries({...n?.alias}).flatMap(([e,t])=>{let n=b(e);return t===r.name?[{...o,name:e,basename:r.name,id:`${o.id}_${T(e)}`,alias:f(n),pathTokens:n}]:[]})]}).sort(S);for(let[e,t]of[[`@api/routes.ts`,et]])await o(r.lib(e),t,{routes:a,cascadingMiddleware:i})};return{config({command:e}){return{define:{KOSMO_PRODUCTION_BUILD:e===`build`?`true`:`false`}}},async start(){for(let[e,t]of[[`api.ts`,nt],[`api:factory.ts`,rt],[`@api/app.ts`,Ye],[`@api/dev.ts`,Xe],[`@api/errors.ts`,Ze],[`@api/parsers.ts`,Qe],[`@api/router.ts`,$e],[`@api/server.ts`,tt]])await o(r.lib(e),t,{});for(let[e,t]of[[`app.ts`,it],[`dev.ts`,at],[`errors.ts`,st],[`server.ts`,ut],[`use.ts`,dt],[`env.d.ts`,ot]])await s(r.api(e),t,{},{overwrite:l})},async watch(e,t){await d(e.filter(p(t,[`create`]))),await m(e)},async build(e){await d(e),await m(e)}}}),pt=h({meta:{name:`Koa`,slot:`backend`,types:[`@types/koa`]},dependencies:{koa:H.devDependencies.koa,"@koa/router":H.devDependencies[`@koa/router`],formidable:H.devDependencies.formidable,"raw-body":H.devDependencies[`raw-body`]},devDependencies:{"@types/koa":H.devDependencies[`@types/koa`],"@types/formidable":H.devDependencies[`@types/formidable`]},factory:ft}),U={type:`module`,private:!0,name:`@kosmojs/mdx-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"@mdx-js/rollup":`^3.1.1`,vite:`^8.2.2`},devDependencies:{"@mdx-js/mdx":`^3.1.1`,"@mdx-js/preact":`^3.1.1`,"path-to-regexp":`^8.4.2`,preact:`^10.29.8`,"preact-render-to-string":`^6.7.0`,"remark-frontmatter":`^5.0.0`,"remark-mdx-frontmatter":`^5.2.0`}},mt=()=>{let e=[`🎉 Well done! You just created a new MDX page.`,`🚀 Success! A fresh MDX page is ready to roll.`,`🌟 Nice work! Another MDX page added to your site.`,`🧩 All set! A new MDX page has been scaffolded.`,`🔧 Scaffold complete! Your new MDX page is in place.`,`✅ Built! Your MDX page is scaffolded and ready.`,`✨ Fantastic! Your new MDX page is good to go.`,`🎯 Nailed it! A brand new MDX page just landed.`,`💫 Awesome! Another MDX page joins the party.`,`⚡ Lightning fast! A new MDX page created successfully.`];return e[Math.floor(Math.random()*e.length)]},ht=(e,t,n)=>{let{remarkPlugins:r=[],rehypePlugins:i=[]}={...n},a=()=>{let t=[`${l.srcDir}/${e.name}/${l.entryDir}/client.ts`].map(e=>re(e)),n=e=>t.some(t=>t(e));return{name:`kosmo:mdx[hmr]`,enforce:`post`,transform(e,t){if(!(!n(t)||e.includes(`import.meta.hot.accept`)))return{code:[e,`
3930
+ `,It=v((e,n)=>{let{createPath:r,createImportHelpers:i}=S(e),a=e=>e.length===0?`{}`:e.length===1?e[0]:`Override<${e[0]}, ${a(e.slice(1))}>`,{renderToFile:o}=w({helpers:{...i({origin:`lib`}),...A(),paramsDefaults({params:e}){return`[${e.schema.map(()=>`unknown?`).join(`, `)}]`},paramsMappings({params:e}){return`[${e.schema.map(({name:e,kind:t})=>`["${e}", unknown, ${t===`required`?`true`:`false`}]`).join(`, `)}]`},cascadingState({cascadingMiddleware:e}){return a(e.map(({id:e})=>`UseT${e}`))}}}),{renderToFile:s}=w({helpers:i({origin:`src`})}),c=e=>e?.trim().length===0,u=l(n?.templates,Mt),d=async e=>{for(let{kind:t,entry:n}of e)t===`apiRoute`?await s(r.api(n.file),u(n.name,n),{route:n},{overwrite:c}):t===`apiUse`&&await s(r.api(n.file),Nt,{},{overwrite:c})},f=async e=>{let i=e.flatMap(({kind:e,entry:t})=>e===`apiUse`?[t]:[]),a=e.flatMap(({kind:e,entry:r})=>{if(e!==`apiRoute`)return[];let a=r.name.split(`/`).reduce((e,n)=>{let r=e[e.length-1];return e.push(r?t(r,n):n),e},[]),o={...r,basename:r.name,path:r.pathPattern,cascadingMiddleware:i.flatMap(e=>a.some(t=>e.name===t)?[e]:[])};return[o,...Object.entries({...n?.alias}).flatMap(([e,t])=>{let n=C(e);return t===r.name?[{...o,name:e,basename:r.name,id:`${o.id}_${j(e)}`,alias:m(n),pathTokens:n}]:[]})]}).sort(E);for(let[e,t]of[[`@api/routes.ts`,wt]])await o(r.lib(e),t,{routes:a,cascadingMiddleware:i})};return{async start(){for(let[e,t]of[[`api.ts`,Et],[`api:factory.ts`,Dt],[`@api/app.ts`,vt],[`@api/dev.ts`,yt],[`@api/errors.ts`,bt],[`@api/listener.ts`,xt],[`@api/parsers.ts`,St],[`@api/router.ts`,Ct],[`@api/server.ts`,Tt]])await o(r.lib(e),t,{});for(let[e,t]of[[`app.ts`,Ot],[`dev.ts`,kt],[`errors.ts`,jt],[`server.ts`,Pt],[`use.ts`,Ft],[`env.d.ts`,At]])await s(r.api(e),t,{},{overwrite:c})},async watch(e,t){await d(e.filter(h(t,[`create`]))),await f(e)},async build(e){await d(e),await f(e)}}}),Lt=_({meta:{name:`Koa`,slot:`backend`,types:[`@types/koa`]},dependencies:{koa:H.devDependencies.koa,"@koa/router":H.devDependencies[`@koa/router`],formidable:H.devDependencies.formidable,"raw-body":H.devDependencies[`raw-body`]},devDependencies:{"@types/koa":H.devDependencies[`@types/koa`],"@types/formidable":H.devDependencies[`@types/formidable`]},factory:It}),U={type:`module`,private:!0,name:`@kosmojs/mdx-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"@mdx-js/rollup":`^3.1.1`,vite:`^8.2.2`},devDependencies:{"@mdx-js/mdx":`^3.1.1`,"@mdx-js/preact":`^3.1.1`,"path-to-regexp":`^8.4.2`,preact:`^10.29.8`,"preact-render-to-string":`^6.7.0`,"remark-frontmatter":`^5.0.0`,"remark-mdx-frontmatter":`^5.2.0`}},Rt=()=>{let e=[`🎉 Well done! You just created a new MDX page.`,`🚀 Success! A fresh MDX page is ready to roll.`,`🌟 Nice work! Another MDX page added to your site.`,`🧩 All set! A new MDX page has been scaffolded.`,`🔧 Scaffold complete! Your new MDX page is in place.`,`✅ Built! Your MDX page is scaffolded and ready.`,`✨ Fantastic! Your new MDX page is good to go.`,`🎯 Nailed it! A brand new MDX page just landed.`,`💫 Awesome! Another MDX page joins the party.`,`⚡ Lightning fast! A new MDX page created successfully.`];return e[Math.floor(Math.random()*e.length)]},zt=(e,t,n)=>{let{remarkPlugins:r=[],rehypePlugins:i=[]}={...n},a=()=>{let t=[`${u.srcDir}/${e.name}/${u.entryDir}/client.ts`].map(e=>re(e)),n=e=>t.some(t=>t(e));return{name:`kosmo:mdx[hmr]`,enforce:`post`,transform(e,t){if(!(!n(t)||e.includes(`import.meta.hot.accept`)))return{code:[e,`
2932
3931
  if (import.meta.hot) {
2933
3932
  import.meta.hot.accept(() => {});
2934
3933
  }
2935
3934
  `].join(`
2936
- `)}}}},o=[ne({jsxImportSource:`preact`,providerImportSource:`@mdx-js/preact`,remarkPlugins:r,rehypePlugins:i})];return t===`serve`&&o.push(a()),o},gt=`import type { FunctionComponent } from "preact";
3935
+ `)}}}},o=[ne({jsxImportSource:`preact`,providerImportSource:`@mdx-js/preact`,remarkPlugins:r,rehypePlugins:i})];return t===`serve`&&o.push(a()),o},Bt=`import type { FunctionComponent } from "preact";
2937
3936
 
2938
3937
  export const AppProvider: FunctionComponent = (props) => {
2939
3938
  return props.children;
2940
3939
  };
2941
- `,_t=`import { render, hydrate as hydrateOrig } from "preact";
3940
+ `,Vt=`import { render, hydrate as hydrateOrig } from "preact";
2942
3941
  import type { RouterFactoryReturn } from "@kosmojs/core";
2943
3942
  import { clientRenderFactory } from "@kosmojs/core/generators";
2944
3943
 
@@ -2979,7 +3978,7 @@ export const mount = async (
2979
3978
  }
2980
3979
 
2981
3980
  export default clientRenderFactory();
2982
- `,vt=`import { renderToString as renderToStringOrig } from "preact-render-to-string";
3981
+ `,Ht=`import { renderToString as renderToStringOrig } from "preact-render-to-string";
2983
3982
 
2984
3983
  import type {
2985
3984
  RenderToStringWrapper,
@@ -3053,7 +4052,7 @@ export const renderToString: RenderToStringWrapper<
3053
4052
  }
3054
4053
 
3055
4054
  export default serverRenderFactory<false>();
3056
- `,yt=`declare module "*.mdx" {
4055
+ `,Ut=`declare module "*.mdx" {
3057
4056
  import type { ComponentType } from "preact";
3058
4057
  export const frontmatter: Record<string, unknown>;
3059
4058
  const component: ComponentType;
@@ -3066,7 +4065,7 @@ declare module "*.md" {
3066
4065
  const component: ComponentType;
3067
4066
  export default component;
3068
4067
  }
3069
- `,bt=`import { MDXProvider } from "@mdx-js/preact";
4068
+ `,Wt=`import { MDXProvider } from "@mdx-js/preact";
3070
4069
  import { match, pathToRegexp } from "path-to-regexp";
3071
4070
  import { type ComponentType, createContext, h, type VNode } from "preact";
3072
4071
 
@@ -3077,7 +4076,6 @@ import { base } from "{{ createImport 'libCore' }}";
3077
4076
 
3078
4077
  export type RawRoute = {
3079
4078
  name: string;
3080
- pathSegments: number | undefined;
3081
4079
  regexp: RegExp;
3082
4080
  extractParams: (path: string) => Route["params"];
3083
4081
  loader: () => Promise<RouteModule>;
@@ -3145,23 +4143,20 @@ export const createRouter = (
3145
4143
  return {
3146
4144
  async resolve(url: URL = new URL(window.location.href)) {
3147
4145
  const searchParams = parseSearchParams(url);
3148
- const urlSegments = url.pathname.split("/").filter(Boolean).length;
3149
-
3150
- // 1: use lightweight \`RegExp.test()\` on linear scan - no capture allocation
3151
- const matchedRoutes = routes.filter(({ regexp }) => {
3152
- return regexp.test(url.pathname);
3153
- });
3154
4146
 
4147
+ // The routes array is generated pre-sorted by specificity
4148
+ // (static beats required beats optional beats splat, token by token),
4149
+ // the same ordering the SSR server registers routes in -
4150
+ // so the first pattern that matches IS the most specific one,
4151
+ // and CSR resolution stays consistent with SSR.
4152
+ // A route with optional parameters matches a range of segment counts,
4153
+ // which is why no segment-count heuristic can disambiguate here.
4154
+ // Lightweight \`RegExp.test()\` on linear scan - no capture allocation.
3155
4155
  const matchedRoute =
3156
- matchedRoutes.length > 1
3157
- ? matchedRoutes.find(({ pathSegments }) => {
3158
- return pathSegments === undefined || pathSegments === urlSegments;
3159
- }) || catchallRoute
3160
- : matchedRoutes.length === 1
3161
- ? matchedRoutes[0]
3162
- : catchallRoute;
3163
-
3164
- // 2: capture params only on matched route
4156
+ routes.find(({ regexp }) => {
4157
+ return regexp.test(url.pathname);
4158
+ }) || catchallRoute;
4159
+
3165
4160
  const params = matchedRoute
3166
4161
  ? matchedRoute.extractParams(url.pathname)
3167
4162
  : {};
@@ -3255,11 +4250,6 @@ export const createRoute = (
3255
4250
  return {
3256
4251
  name,
3257
4252
  regexp,
3258
- // count segments of the same base-joined path the regexp matches against;
3259
- // resolve() compares this against the full url pathname's segment count
3260
- pathSegments: name.includes("...")
3261
- ? undefined
3262
- : path.split("/").filter(Boolean).length,
3263
4253
  extractParams: (path) => {
3264
4254
  const match = matcher(path);
3265
4255
  return match ? match.params : {};
@@ -3268,7 +4258,7 @@ export const createRoute = (
3268
4258
  layouts,
3269
4259
  };
3270
4260
  };
3271
- `,xt=`/* @jsxImportSource preact */
4261
+ `,Gt=`/* @jsxImportSource preact */
3272
4262
 
3273
4263
  import styles from "./styles.module.css";
3274
4264
 
@@ -3309,7 +4299,7 @@ export default function PageSample(props: {
3309
4299
  </div>
3310
4300
  );
3311
4301
  }
3312
- `,St=`/* @jsxImportSource preact */
4302
+ `,Kt=`/* @jsxImportSource preact */
3313
4303
 
3314
4304
  import styles from "./styles.module.css";
3315
4305
 
@@ -3361,7 +4351,7 @@ export default function PageSample(props: {
3361
4351
  </div>
3362
4352
  );
3363
4353
  }
3364
- `,Ct=`* {
4354
+ `,qt=`* {
3365
4355
  margin: 0;
3366
4356
  padding: 0;
3367
4357
  box-sizing: border-box;
@@ -3496,7 +4486,7 @@ export default function PageSample(props: {
3496
4486
  align-items: center;
3497
4487
  gap: 0.25rem;
3498
4488
  }
3499
- `,wt=`/* @jsxImportSource preact */
4489
+ `,Jt=`/* @jsxImportSource preact */
3500
4490
 
3501
4491
  import styles from "./styles.module.css";
3502
4492
 
@@ -3562,7 +4552,7 @@ export default function WelcomePage() {
3562
4552
  </div>
3563
4553
  );
3564
4554
  }
3565
- `,Tt=`export type ParamsMap = {
4555
+ `,Yt=`export type ParamsMap = {
3566
4556
  {{#each pageRoutes}}"{{name}}": {{serializeParamsLiteral .}};
3567
4557
  {{/each}}
3568
4558
  };
@@ -3571,7 +4561,7 @@ export const paramNames = {
3571
4561
  {{#each pageRoutes}}"{{name}}": [ {{#each params.schema}}"{{name}}", {{/each}}],
3572
4562
  {{/each}}
3573
4563
  } as const;
3574
- `,Et=`import type { ComponentType } from "preact";
4564
+ `,Xt=`import type { ComponentType } from "preact";
3575
4565
 
3576
4566
  import type { RouterFactoryReturn } from "@kosmojs/core";
3577
4567
  import { createRouterFactory } from "@kosmojs/core/generators";
@@ -3585,107 +4575,37 @@ import {
3585
4575
 
3586
4576
  export const createRouters = (
3587
4577
  routes: Array<RawRoute>,
3588
- {
3589
- app,
3590
- components,
3591
- }: {
3592
- app: ComponentType;
3593
- components: Record<string, ComponentType<never>>;
3594
- },
3595
- ): {
3596
- clientRouter: () => RouterFactoryReturn<Promise<RouteComponent>>;
3597
- serverRouter: (
3598
- url: URL,
3599
- ) => RouterFactoryReturn<Promise<RouteComponent>, { route: Route }>;
3600
- } => {
3601
- const router = createRouter(routes, app, { components });
3602
-
3603
- return {
3604
- clientRouter() {
3605
- return router.resolve();
3606
- },
3607
- serverRouter(url) {
3608
- return router.resolve(url);
3609
- },
3610
- };
3611
- };
3612
-
3613
- export default createRouterFactory<
3614
- RawRoute,
3615
- Promise<RouteComponent>,
3616
- { server: { route: Route } }
3617
- >();
3618
- `,Dt=`import { join } from "node:path";
3619
-
3620
- import { compile } from "path-to-regexp";
3621
-
3622
- import type { PageRoute } from "@kosmojs/core";
3623
-
3624
- import routes from "{{ createImport 'lib' 'ssg:routes' }}";
3625
- import { base } from "{{ createImport 'libCore' }}";
3626
-
3627
- const paramsMapper = (params: PageRoute["params"], value: Array<unknown>) => {
3628
- return params.schema.reduce<Record<string, unknown>>(
3629
- (map, { name, kind }, i) => {
3630
- if (kind === "splat") {
3631
- if (Array.isArray(value[i]) && value[i].length) {
3632
- map[name] = value[i].map(String);
3633
- }
3634
- } else if (value[i] !== undefined) {
3635
- map[name] = String(value[i]);
3636
- }
3637
- return map;
3638
- },
3639
- {},
3640
- );
3641
- };
3642
-
3643
- export default Object.entries(routes)
3644
- .flatMap(([name, { pathPattern, params, frontmatter }]) => {
3645
- if (!params.schema.length || Array.isArray(frontmatter?.staticParams)) {
3646
- if (params.schema.length) {
3647
- const toPath = compile(pathPattern);
3648
- return Array.from(frontmatter?.staticParams || []).flatMap((entry) => {
3649
- try {
3650
- return [toPath(paramsMapper(params, entry) as never)];
3651
- } catch (error: any) {
3652
- console.error(\`❗SSG: Failed building path for \${name}\`);
3653
- console.error(error);
3654
- return [];
3655
- }
3656
- });
3657
- }
3658
- // static route
3659
- return [pathPattern.replace(/^index\\/?/, "")];
3660
- }
3661
- return [];
3662
- })
3663
- .map((path) => join(base, path));
3664
- `,Ot=`import type { PageRoute } from "@kosmojs/core";
3665
-
3666
- {{#each pageRoutes}}
3667
- import * as {{id}} from "{{ createImport 'pages' file }}";
3668
- {{/each}}
3669
-
3670
- const routeMap: Record<
3671
- string,
3672
- {
3673
- frontmatter?: { staticParams?: Array<Array<string | Array<string>>> };
3674
- pathPattern: string;
3675
- params: PageRoute["params"];
3676
- }
3677
- > = {
3678
- {{#each pageRoutes}}
3679
- "{{name}}": {
3680
- frontmatter: {{id}}.frontmatter,
3681
- pathPattern: "{{pathPattern}}",
3682
- params: {{serializeParams .}},
4578
+ {
4579
+ app,
4580
+ components,
4581
+ }: {
4582
+ app: ComponentType;
4583
+ components: Record<string, ComponentType<never>>;
3683
4584
  },
3684
- {{/each}}
3685
- }
4585
+ ): {
4586
+ clientRouter: () => RouterFactoryReturn<Promise<RouteComponent>>;
4587
+ serverRouter: (
4588
+ url: URL,
4589
+ ) => RouterFactoryReturn<Promise<RouteComponent>, { route: Route }>;
4590
+ } => {
4591
+ const router = createRouter(routes, app, { components });
3686
4592
 
3687
- export default routeMap;
3688
- `,kt=`import { useContext } from "preact/hooks";
4593
+ return {
4594
+ clientRouter() {
4595
+ return router.resolve();
4596
+ },
4597
+ serverRouter(url) {
4598
+ return router.resolve(url);
4599
+ },
4600
+ };
4601
+ };
4602
+
4603
+ export default createRouterFactory<
4604
+ RawRoute,
4605
+ Promise<RouteComponent>,
4606
+ { server: { route: Route } }
4607
+ >();
4608
+ `,Zt=`import { useContext } from "preact/hooks";
3689
4609
 
3690
4610
  import { RouterContext } from "./mdx";
3691
4611
 
@@ -3731,10 +4651,10 @@ export const useFrontmatter = <
3731
4651
  >(): T => {
3732
4652
  return useRoute().frontmatter as T;
3733
4653
  };
3734
- `,At=`import { AppProvider } from "{{ createImport 'lib' 'app' }}";
4654
+ `,Qt=`import { AppProvider } from "{{ createImport 'lib' 'app' }}";
3735
4655
 
3736
4656
  <AppProvider>{props.children}</AppProvider>
3737
- `,jt=`import { h, type JSX } from "preact";
4657
+ `,$t=`import { h, type JSX } from "preact";
3738
4658
 
3739
4659
  import { pageRouteMap, type LinkProps } from "{{ createImport 'libCore' }}";
3740
4660
 
@@ -3751,7 +4671,7 @@ export default function Link(
3751
4671
 
3752
4672
  return h("a", { ...restProps, href }, children);
3753
4673
  }
3754
- `,Mt=`/**
4674
+ `,en=`/**
3755
4675
  * MDX component overrides.
3756
4676
  *
3757
4677
  * Every standard markdown element (headings, links, code blocks, etc.)
@@ -3774,7 +4694,7 @@ export const components = {
3774
4694
  declare global {
3775
4695
  type MDXProvidedComponents = typeof components;
3776
4696
  }
3777
- `,Nt=`import renderFactory, {
4697
+ `,tn=`import renderFactory, {
3778
4698
  createRoutes,
3779
4699
  hydrate,
3780
4700
  mount,
@@ -3801,7 +4721,7 @@ if (root) {
3801
4721
  } else {
3802
4722
  console.error("❌ Root element not found!");
3803
4723
  }
3804
- `,Pt=`import renderFactory, {
4724
+ `,nn=`import renderFactory, {
3805
4725
  createRoutes,
3806
4726
  renderToString,
3807
4727
  // no renderToStream on MDX folders
@@ -3822,25 +4742,13 @@ export default renderFactory(() => {
3822
4742
  },
3823
4743
  };
3824
4744
  });
3825
- `,Ft=`<!doctype html>
3826
- <html lang="en">
3827
- <head>
3828
- <meta charset="UTF-8" />
3829
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
3830
- <!--app-head-->
3831
- </head>
3832
- <body>
3833
- <div id="app"><!--app-html--></div>
3834
- <script type="module" src="/{{ entryDir }}/client.ts"><\/script>
3835
- </body>
3836
- </html>
3837
- `,It=`import PageSample from "{{ createImport 'lib' 'pageSamples/404.tsx' }}";
4745
+ `,rn=`import PageSample from "{{ createImport 'lib' 'pageSamples/404.tsx' }}";
3838
4746
 
3839
4747
  export default function Page() {
3840
4748
  return <PageSample />;
3841
4749
  }
3842
- `,Lt=`{props.children}
3843
- `,Rt=`---
4750
+ `,an=`{props.children}
4751
+ `,on=`---
3844
4752
  title: "{{title}}"
3845
4753
  ---
3846
4754
 
@@ -3857,7 +4765,7 @@ export const pathMap = {
3857
4765
  routeName="{{route.name}}"
3858
4766
  pathMap={pathMap}
3859
4767
  />
3860
- `,zt=`---
4768
+ `,sn=`---
3861
4769
  title: Welcome to KosmoJS
3862
4770
  description: Content-first development with MDX and Vite
3863
4771
  ---
@@ -3865,7 +4773,7 @@ description: Content-first development with MDX and Vite
3865
4773
  import WelcomePage from "{{ createImport 'lib' 'pageSamples/welcome.tsx' }}"
3866
4774
 
3867
4775
  <WelcomePage />
3868
- `,Bt=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
4776
+ `,cn=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
3869
4777
 
3870
4778
  import app from "./app.mdx";
3871
4779
  import { components } from "./components/mdx"
@@ -3881,12 +4789,12 @@ export default routerFactory((routes) => {
3881
4789
  },
3882
4790
  };
3883
4791
  });
3884
- `,Vt=g((e,t)=>{let{createPath:n,createImportHelpers:r}=y(e),{renderToFile:i}=x({helpers:{...r({origin:`lib`}),...w(),serializeParams(e){return JSON.stringify(e.params)}}}),{renderToFile:a}=x({helpers:r({origin:`src`})}),o=e=>!e?.trim().length,s=c(t?.templates,Rt),u=async e=>{for(let{kind:t,entry:r}of e)t===`pageRoute`?await a(n.pages(r.file),r.name===`index`?zt:s(r.name,r),{route:r,title:r.name.replace(/\{([^}]+)\}/g,`$1`),message:mt()},{overwrite:o}):t===`pageLayout`&&await a(n.pages(r.file),Lt,{route:r},{overwrite:o})},d=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageLayout`?[t]:[]),r=e.flatMap(({kind:e,entry:n})=>{if(e===`pageRoute`){let{name:e,file:r}=n;return[{...n,layouts:t.flatMap(t=>t.name===e||r.startsWith(`${t.name}/`)?[t]:[]).sort(S)}]}return[]}).sort(S);for(let[e,a]of[[`client.ts`,_t],[`server.ts`,vt]])await i(n.libEntry(e),a,{pageRoutes:r,layouts:t});for(let[e,t]of[[`params.ts`,Tt],[`router.ts`,Et],[`ssg:routes.ts`,Ot]])await i(n.lib(e),t,{pageRoutes:r})};return{config({command:n}){return{oxc:{jsx:{runtime:`automatic`,importSource:`preact`}},plugins:ht(e,n,t)}},async start(){for(let[e,t]of[[`env.d.ts`,yt],[`app.ts`,gt],[`mdx.ts`,bt],[`use.ts`,kt],[`ssg.ts`,Dt],[`pageSamples/styles.module.css`,Ct],[`pageSamples/welcome.tsx`,wt],[`pageSamples/page.tsx`,St],[`pageSamples/404.tsx`,xt]])await i(n.lib(e),t,{});for(let[e,t]of[[`pages/404.mdx`,It],[`components/Link.tsx`,jt],[`components/mdx.ts`,Mt],[`app.mdx`,At],[`router.ts`,Bt]])await a(n.src(e),t,{entryDir:l.entryDir},{overwrite:o});await a(n.src(`index.html`),Ft,{entryDir:l.entryDir},{overwrite:e=>!e?.trim().length||!e.replace(/<!--[\s\S]*?-->/g,``).trim().length});for(let[e,t]of[[`client.ts`,Nt],[`server.ts`,Pt]])await a(n.entry(e),t,{},{overwrite:o})},async watch(e,t){await u(e.filter(m(t,[`create`]))),await d(e)},async build(e){await u(e),await d(e)}}}),Ht=h({meta:{name:`MDX`,jsx:`react-jsx`,jsxImportSource:`preact`},dependencies:{"path-to-regexp":U.devDependencies[`path-to-regexp`]},devDependencies:{preact:U.devDependencies.preact,"preact-render-to-string":U.devDependencies[`preact-render-to-string`],"@mdx-js/preact":U.devDependencies[`@mdx-js/preact`],"remark-frontmatter":U.devDependencies[`remark-frontmatter`],"remark-mdx-frontmatter":U.devDependencies[`remark-mdx-frontmatter`]},factory:Vt}),Ut={json:`application/json`,form:[`application/x-www-form-urlencoded`,`multipart/form-data`],raw:void 0},Wt=()=>{let e=e=>[`Buffer`,`ArrayBuffer`,`Blob`].includes(e)?{type:`string`,format:`binary`}:oe.Script(e),n=(e,t,n)=>{let r=n?`${t}_${n.replace(/[^\w.-]/g,`_`)}${T(n)}`:t;return`${e.id}_${r}`},r=(e,t,r,i)=>[`#`,`components`,e,n(t,r,i)].join(`/`),i=e=>e.split(`/`).reduce((e,t)=>e+ +!t.includes(`{`),0),o=e=>{if(e.name===`index`)return[`/`];let{tokens:n}=ae(e.pathPattern),r=e=>e.flatMap(e=>{switch(e.type){case`param`:return[e.name];case`wildcard`:return[e.name];case`group`:return r(e.tokens);default:return[]}}),a=(e,t)=>{if(!e.length)return[t];let[n,...i]=e;switch(n.type){case`text`:return a(i,{path:`${t.path}${n.value}`,params:t.params});case`param`:return a(i,{path:`${t.path}{${n.name}}`,params:[...t.params,n.name]});case`wildcard`:return a(i,{path:`${t.path}{${n.name}*}`,params:[...t.params,n.name]});case`group`:{let e=a(i,t),o=a([...n.tokens,...i],t),s=r(n.tokens),c=o.filter(e=>s.some(t=>e.params.includes(t)));return[...e,...c]}}},o=a(n,{path:``,params:[]}),s=e.params.schema.reduce((e,{name:t},n)=>(e[n]=t,e),{});return o.reduce((e,n)=>{let r=t(`/`,n.path);return e.includes(r)||n.params.every((e,t)=>e===s[t])&&e.push(r),e},[]).sort((e,t)=>i(t)-i(e))},s=(e,t)=>{let n=e.params.resolvedType?.properties?.flatMap(n=>RegExp(`\\{${n.name}\\*?\\}`).test(t)?[{$ref:r(`parameters`,e,e.params.id,n.name)}]:[]);return n?.length?n:void 0},c=(e,t)=>{let n=e.validationDefinitions.find(e=>e.method===t&&e.target===`response`);return Array.isArray(n?.variants)?n.variants.reduce((t,{id:n,status:i,contentType:a,body:o})=>(t[i]=Gt(i,a)||{description:`Success`,content:{[a||`application/json`]:{schema:o?{$ref:r(`schemas`,e,n)}:{type:`object`}}}},t),{}):{200:{description:`Success - TODO: Add response schema`,content:{"text/plain":{schema:{type:`string`}}}}}},l=t=>{let{parameters:r,schemas:i}={parameters:{},schemas:{}};for(let r of t.validationDefinitions)if(r.target===`response`)for(let{id:a,resolvedType:o}of r.variants)o?.typeboxSchema&&(i[n(t,a)]=e(o.typeboxSchema));else if(r.target===`query`){let{id:a,resolvedType:o}=r.schema;for(let r of o?.properties||[])if(r?.typeboxSchema){let o=n(t,a,r.name);i[o]=e(r.typeboxSchema)}}else{let{id:a,resolvedType:o}=r.schema;o?.typeboxSchema&&(i[n(t,a)]=e(o.typeboxSchema))}if(t.params.resolvedType)for(let i of t.params.resolvedType.properties||[])i?.typeboxSchema&&(r[n(t,t.params.id,i.name)]={name:i.name,in:`path`,required:!0,schema:e(i.typeboxSchema)});return{parameters:r,schemas:i}},u=e=>{let t={},n=e.validationDefinitions.flatMap(e=>e.target===`response`?[]:e.schema.resolvedType?[e]:[]);for(let i of o(e))for(let o of e.methods){let l={responses:c(e,o)},u=s(e,i);u&&(l.parameters=u);let d=n.find(e=>e.method===o&&e.target===`query`);if(d?.schema)for(let t of d.schema.resolvedType?.properties||[])l.parameters||=[],l.parameters.push({name:t.name,in:`query`,required:!t.optional,schema:{$ref:r(`schemas`,e,d.schema.id,t.name)}});let f=n.filter(e=>e.method===o&&Object.keys(a).includes(e.target));f.length&&(l.requestBody={required:!0,content:f.reduce((t,n)=>{let{contentType:i=Ut[n.target]}=n;if(i){let a={$ref:r(`schemas`,e,n.schema.id)};for(let e of[i].flat())t[e]={schema:a}}return t},{})}),t[i]||(t[i]={}),t[i][o.toLowerCase()]=l}return t};return{generateComponentId:n,generateComponentPath:r,generatePathVariations:o,generateOpenAPISchema:e=>{let t=new Map;for(let n of e)t.set(n.name,o(n));let{components:n,paths:r}=e.sort(S).flatMap(n=>{let r=t.get(n.name)??[];return r.length>0&&e.some(e=>{if(e.name===n.name)return!1;let i=t.get(e.name)??[],a=new Set(i);return r.every(e=>a.has(e))})?[]:[n]}).reduce((e,t)=>{let n=u(t),{parameters:r,schemas:i}=l(t);return{paths:{...e.paths,...n},components:{parameters:{...e.components.parameters,...r},schemas:{...e.components.schemas,...i}}}},{paths:{},components:{parameters:{},schemas:{}}});return{paths:r,components:n}}}},Gt=(e,t)=>{let n={type:`string`,format:`uri`,...t?{enum:[t]}:{}};return{301:{description:`Moved Permanently`,headers:{Location:{description:`New permanent location`,schema:n}}},302:{description:`Found`,headers:{Location:{description:`Temporary location`,schema:n}}},303:{description:`See Other`,headers:{Location:{description:`Location to GET after POST/PUT/DELETE`,schema:n}}},307:{description:`Temporary Redirect`,headers:{Location:{description:`Temporary location (preserves request method)`,schema:n}}},308:{description:`Permanent Redirect`,headers:{Location:{description:`New permanent location (preserves request method)`,schema:n}}}}[e]},Kt=g((e,t)=>{let{outfile:n=``,...r}={...t},{createPath:i}=y(e),{generateOpenAPISchema:a}=Wt(),o=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`apiRoute`?[t]:[]),{paths:o,components:s}=a(t),c={...JSON.parse(JSON.stringify(r)),paths:o,components:s},l=/ya?ml/.test(n)?ie.stringify(c):JSON.stringify(c,null,2);await ee(i.src(n),l,{})};return{async watch(e){await o(e)},async build(e){await o(e)}}}),qt=h({meta:{name:`OpenAPI`,resolveTypes:!0},factory:Kt}),W={type:`module`,private:!0,name:`@kosmojs/react-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/react-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"@vitejs/plugin-react":`^6.1.0`},devDependencies:{"@tanstack/react-query":`^5.102.2`,"@types/react":`^19.2.18`,"@types/react-dom":`^19.2.5`,"path-to-regexp":`^8.4.2`,react:`^19.2.8`,"react-dom":`^19.2.8`,"react-router":`^8.3.0`}},Jt=e=>{let t=e.map(e=>e.orig).join(`/`),n=e=>e.kind===`splat`?`*`:e.kind===`optional`?`:${e.name}?`:`:${e.name}`;return e.flatMap(e=>e.kind===`static`?[e.parts[0].value]:e.kind===`param`?[n(e.parts[0])]:(/\.\w+$/.test(e.orig)||(console.warn(`❗${r([`red`,`bold`],`WARN`)}: React Router v7 only supports dot-suffix mixed segments (e.g. :param.html).`),console.warn(` ${r([`magenta`],e.orig)} in ${r([`blue`],t)} route won't match as expected.`),console.warn()),[e.parts.map(e=>e.type===`static`?e.value:n(e)).join(``)])).join(`/`)},Yt=()=>{let e=e=>e?.kind===`param`&&e.parts[0]?.kind===`splat`,t=n=>n.flatMap(({index:n,layout:r,children:i})=>{let{name:a,pathTokens:o}={...n,...r};if(!o)return[];let s=`${a}:layout`,c=Jt(o),l=o.at(-1);return e(l)?n&&r?[{name:s,path:c,component:r.id,children:[{name:a,path:`*`,component:n.id}]}]:n?[{path:c,children:[{name:a,path:`*`,component:n.id},...t(i)]}]:r?[{name:s,path:c,component:r.id,children:t(i)}]:[]:n&&r?[{name:s,path:c,component:r.id,children:[{name:a,index:!0,component:n.id},...t(i)]}]:n?[{path:c,children:[{name:a,index:!0,component:n.id},...t(i)]}]:r?[{name:s,path:c,component:r.id,children:t(i)}]:[]});return t},Xt=()=>{let e=[`🎉 Well done! You just created a new React route.`,`🚀 Success! A fresh React route is ready to roll.`,`🌟 Nice work! Another React route added to your app.`,`⚡ Quick and easy! Your new React route is good to go.`,`🥳 Congrats! Your app just leveled up with a new React route.`,`🧩 All set! A new React route has been scaffolded.`,`🔧 Scaffold complete! Your new React route is in place.`,`✨ Fantastic! Your new React route is ready.`,`🎯 Nailed it! A brand new React route just landed.`,`💫 Awesome! Another React route joins the lineup.`];return e[Math.floor(Math.random()*e.length)]},Zt=`import type { ReactNode } from "react";
4792
+ `,ln=v((e,t)=>{let{createPath:n,createImportHelpers:r}=S(e),{renderToFile:i}=w({helpers:{...r({origin:`lib`}),...A(),serializeParams(e){return JSON.stringify(e.params)}}}),{renderToFile:a}=w({helpers:r({origin:`src`})}),o=e=>!e?.trim().length,s=l(t?.templates,on),c=async e=>{for(let{kind:t,entry:r}of e)t===`pageRoute`?await a(n.pages(r.file),r.name===`index`?sn:s(r.name,r),{route:r,title:r.name.replace(/\{([^}]+)\}/g,`$1`),message:Rt()},{overwrite:o}):t===`pageLayout`&&await a(n.pages(r.file),an,{route:r},{overwrite:o})},d=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageLayout`?[t]:[]),r=e.flatMap(({kind:e,entry:n})=>{if(e===`pageRoute`){let{name:e,file:r}=n;return[{...n,layouts:t.flatMap(t=>t.name===e||r.startsWith(`${t.name}/`)?[t]:[]).sort(E)}]}return[]}).sort(D);for(let[e,a]of[[`client.ts`,Vt],[`server.ts`,Ht]])await i(n.libEntry(e),a,{pageRoutes:r,layouts:t});for(let[e,t]of[[`params.ts`,Yt],[`router.ts`,Xt]])await i(n.lib(e),t,{pageRoutes:r})};return{config({command:n}){return{oxc:{jsx:{runtime:`automatic`,importSource:`preact`}},plugins:zt(e,n,t)}},async start(){for(let[e,t]of[[`env.d.ts`,Ut],[`app.ts`,Bt],[`mdx.ts`,Wt],[`use.ts`,Zt],[`pageSamples/styles.module.css`,qt],[`pageSamples/welcome.tsx`,Jt],[`pageSamples/page.tsx`,Kt],[`pageSamples/404.tsx`,Gt]])await i(n.lib(e),t,{});for(let[e,t]of[[`pages/404.mdx`,rn],[`components/Link.tsx`,$t],[`components/mdx.ts`,en],[`app.mdx`,Qt],[`router.ts`,cn]])await a(n.src(e),t,{entryDir:u.entryDir},{overwrite:o});for(let[e,t]of[[`client.ts`,tn],[`server.ts`,nn]])await a(n.entry(e),t,{},{overwrite:o})},async watch(e,t){await c(e.filter(g(t,[`create`]))),await d(e)},async build(e){await c(e),await d(e)}}}),un=_({meta:{name:`MDX`,slot:`frontend`,jsx:`react-jsx`,jsxImportSource:`preact`},dependencies:{"path-to-regexp":U.devDependencies[`path-to-regexp`]},devDependencies:{preact:U.devDependencies.preact,"preact-render-to-string":U.devDependencies[`preact-render-to-string`],"@mdx-js/preact":U.devDependencies[`@mdx-js/preact`],"remark-frontmatter":U.devDependencies[`remark-frontmatter`],"remark-mdx-frontmatter":U.devDependencies[`remark-mdx-frontmatter`]},factory:ln}),dn={json:`application/json`,form:[`application/x-www-form-urlencoded`,`multipart/form-data`],raw:void 0},fn=()=>{let e=e=>[`Buffer`,`ArrayBuffer`,`Blob`].includes(e)?{type:`string`,format:`binary`}:oe.Script(e),n=(e,t,n)=>{let r=n?`${t}_${n.replace(/[^\w.-]/g,`_`)}${j(n)}`:t;return`${e.id}_${r}`},r=(e,t,r,i)=>[`#`,`components`,e,n(t,r,i)].join(`/`),i=e=>e.split(`/`).reduce((e,t)=>e+ +!t.includes(`{`),0),a=e=>{if(e.name===`index`)return[`/`];let{tokens:n}=ae(e.pathPattern),r=e=>e.flatMap(e=>{switch(e.type){case`param`:return[e.name];case`wildcard`:return[e.name];case`group`:return r(e.tokens);default:return[]}}),a=(e,t)=>{if(!e.length)return[t];let[n,...i]=e;switch(n.type){case`text`:return a(i,{path:`${t.path}${n.value}`,params:t.params});case`param`:return a(i,{path:`${t.path}{${n.name}}`,params:[...t.params,n.name]});case`wildcard`:return a(i,{path:`${t.path}{${n.name}*}`,params:[...t.params,n.name]});case`group`:{let e=a(i,t),o=a([...n.tokens,...i],t),s=r(n.tokens),c=o.filter(e=>s.some(t=>e.params.includes(t)));return[...e,...c]}}},o=a(n,{path:``,params:[]}),s=e.params.schema.reduce((e,{name:t},n)=>(e[n]=t,e),{});return o.reduce((e,n)=>{let r=t(`/`,n.path);return e.includes(r)||n.params.every((e,t)=>e===s[t])&&e.push(r),e},[]).sort((e,t)=>i(t)-i(e))},s=(e,t)=>{let n=e.params.resolvedType?.properties?.flatMap(n=>RegExp(`\\{${n.name}\\*?\\}`).test(t)?[{$ref:r(`parameters`,e,e.params.id,n.name)}]:[]);return n?.length?n:void 0},c=(e,t)=>{let n=e.validationDefinitions.find(e=>e.method===t&&e.target===`response`);return Array.isArray(n?.variants)?n.variants.reduce((t,{id:n,status:i,contentType:a,body:o})=>(t[i]=pn(i,a)||{description:`Success`,content:{[a||`application/json`]:{schema:o?{$ref:r(`schemas`,e,n)}:{type:`object`}}}},t),{}):{200:{description:`Success - TODO: Add response schema`,content:{"text/plain":{schema:{type:`string`}}}}}},l=t=>{let{parameters:r,schemas:i}={parameters:{},schemas:{}};for(let r of t.validationDefinitions)if(r.target===`response`)for(let{id:a,resolvedType:o}of r.variants)o?.typeboxSchema&&(i[n(t,a)]=e(o.typeboxSchema));else if(r.target===`query`){let{id:a,resolvedType:o}=r.schema;for(let r of o?.properties||[])if(r?.typeboxSchema){let o=n(t,a,r.name);i[o]=e(r.typeboxSchema)}}else{let{id:a,resolvedType:o}=r.schema;o?.typeboxSchema&&(i[n(t,a)]=e(o.typeboxSchema))}if(t.params.resolvedType)for(let i of t.params.resolvedType.properties||[])i?.typeboxSchema&&(r[n(t,t.params.id,i.name)]={name:i.name,in:`path`,required:!0,schema:e(i.typeboxSchema)});return{parameters:r,schemas:i}},u=e=>{let t={},n=e.validationDefinitions.flatMap(e=>e.target===`response`?[]:e.schema.resolvedType?[e]:[]);for(let i of a(e))for(let a of e.methods){let l={responses:c(e,a)},u=s(e,i);u&&(l.parameters=u);let d=n.find(e=>e.method===a&&e.target===`query`);if(d?.schema)for(let t of d.schema.resolvedType?.properties||[])l.parameters||=[],l.parameters.push({name:t.name,in:`query`,required:!t.optional,schema:{$ref:r(`schemas`,e,d.schema.id,t.name)}});let f=n.filter(e=>e.method===a&&Object.keys(o).includes(e.target));f.length&&(l.requestBody={required:!0,content:f.reduce((t,n)=>{let{contentType:i=dn[n.target]}=n;if(i){let a={$ref:r(`schemas`,e,n.schema.id)};for(let e of[i].flat())t[e]={schema:a}}return t},{})}),t[i]||(t[i]={}),t[i][a.toLowerCase()]=l}return t};return{generateComponentId:n,generateComponentPath:r,generatePathVariations:a,generateOpenAPISchema:e=>{let t=new Map;for(let n of e)t.set(n.name,a(n));let{components:n,paths:r}=e.sort(E).flatMap(n=>{let r=t.get(n.name)??[];return r.length>0&&e.some(e=>{if(e.name===n.name)return!1;let i=t.get(e.name)??[],a=new Set(i);return r.every(e=>a.has(e))})?[]:[n]}).reduce((e,t)=>{let n=u(t),{parameters:r,schemas:i}=l(t);return{paths:{...e.paths,...n},components:{parameters:{...e.components.parameters,...r},schemas:{...e.components.schemas,...i}}}},{paths:{},components:{parameters:{},schemas:{}}});return{paths:r,components:n}}}},pn=(e,t)=>{let n={type:`string`,format:`uri`,...t?{enum:[t]}:{}};return{301:{description:`Moved Permanently`,headers:{Location:{description:`New permanent location`,schema:n}}},302:{description:`Found`,headers:{Location:{description:`Temporary location`,schema:n}}},303:{description:`See Other`,headers:{Location:{description:`Location to GET after POST/PUT/DELETE`,schema:n}}},307:{description:`Temporary Redirect`,headers:{Location:{description:`Temporary location (preserves request method)`,schema:n}}},308:{description:`Permanent Redirect`,headers:{Location:{description:`New permanent location (preserves request method)`,schema:n}}}}[e]},mn=v((e,t)=>{let{outfile:n=``,...r}={...t},{createPath:i}=S(e),{generateOpenAPISchema:a}=fn(),o=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`apiRoute`?[t]:[]),{paths:o,components:s}=a(t),c={...JSON.parse(JSON.stringify(r)),paths:o,components:s},l=/ya?ml/.test(n)?ie.stringify(c):JSON.stringify(c,null,2);await T(i.src(n),l,{})};return{async watch(e){await o(e)},async build(e){await o(e)}}}),hn=_({meta:{name:`OpenAPI`,resolveTypes:!0},factory:mn}),W={type:`module`,private:!0,name:`@kosmojs/react-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/react-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"@vitejs/plugin-react":`^6.1.0`},devDependencies:{"@tanstack/react-query":`^5.102.2`,"@types/react":`^19.2.18`,"@types/react-dom":`^19.2.5`,react:`^19.2.8`,"react-dom":`^19.2.8`,"react-router":`^8.3.0`}},gn=e=>{let t=e.map(e=>e.orig).join(`/`),n=e=>e.kind===`splat`?`*`:e.kind===`optional`?`:${e.name}?`:`:${e.name}`;return e.flatMap(e=>e.kind===`static`?[e.parts[0].value]:e.kind===`param`?[n(e.parts[0])]:(/\.\w+$/.test(e.orig)||(console.warn(`❗${i([`red`,`bold`],`WARN`)}: React Router v7 only supports dot-suffix mixed segments (e.g. :param.html).`),console.warn(` ${i([`magenta`],e.orig)} in ${i([`blue`],t)} route won't match as expected.`),console.warn()),[e.parts.map(e=>e.type===`static`?e.value:n(e)).join(``)])).join(`/`)},_n=()=>{let e=e=>e?.kind===`param`&&e.parts[0]?.kind===`splat`,t=n=>n.flatMap(({index:n,layout:r,children:i})=>{let{name:a,pathTokens:o}={...n,...r};if(!o)return[];let s=`${a}:layout`,c=gn(o),l=o.at(-1);return e(l)?n&&r?[{name:s,path:c,component:r.id,children:[{name:a,path:`*`,component:n.id}]}]:n?[{path:c,children:[{name:a,path:`*`,component:n.id},...t(i)]}]:r?[{name:s,path:c,component:r.id,children:t(i)}]:[]:n&&r?[{name:s,path:c,component:r.id,children:[{name:a,index:!0,component:n.id},...t(i)]}]:n?[{path:c,children:[{name:a,index:!0,component:n.id},...t(i)]}]:r?[{name:s,path:c,component:r.id,children:t(i)}]:[]});return t},vn=()=>{let e=[`🎉 Well done! You just created a new React route.`,`🚀 Success! A fresh React route is ready to roll.`,`🌟 Nice work! Another React route added to your app.`,`⚡ Quick and easy! Your new React route is good to go.`,`🥳 Congrats! Your app just leveled up with a new React route.`,`🧩 All set! A new React route has been scaffolded.`,`🔧 Scaffold complete! Your new React route is in place.`,`✨ Fantastic! Your new React route is ready.`,`🎯 Nailed it! A brand new React route just landed.`,`💫 Awesome! Another React route joins the lineup.`];return e[Math.floor(Math.random()*e.length)]},yn=`import type { ReactNode } from "react";
3885
4793
 
3886
4794
  export const AppProvider = ({ children }: { children: ReactNode }) => {
3887
4795
  return children;
3888
4796
  }
3889
- `,Qt=`import { type QueryClient, QueryClientProvider } from "@tanstack/react-query";
4797
+ `,bn=`import { type QueryClient, QueryClientProvider } from "@tanstack/react-query";
3890
4798
  import type { ReactNode } from "react";
3891
4799
 
3892
4800
  import { getQueryClient } from "./query";
@@ -3905,7 +4813,7 @@ export const AppProvider = ({
3905
4813
  </QueryClientProvider>
3906
4814
  );
3907
4815
  }
3908
- `,$t=`import { lazy, type JSX } from "react";
4816
+ `,xn=`import { lazy, type JSX } from "react";
3909
4817
 
3910
4818
  import {
3911
4819
  createRoot,
@@ -3959,7 +4867,7 @@ export const mount = async (
3959
4867
  }
3960
4868
 
3961
4869
  export default clientRenderFactory();
3962
- `,en=`{
4870
+ `,Sn=`{
3963
4871
  {{#if name}}
3964
4872
  id: "{{name}}",
3965
4873
  {{/if}}
@@ -3977,7 +4885,7 @@ export default clientRenderFactory();
3977
4885
  children: [ {{#each children}}{{> routePartial}}, {{/each}}],
3978
4886
  {{/if}}
3979
4887
  }
3980
- `,tn=`import type { JSX } from "react";
4888
+ `,Cn=`import type { JSX } from "react";
3981
4889
 
3982
4890
  import {
3983
4891
  renderToString as renderToStringOrig,
@@ -4043,7 +4951,12 @@ export const renderToStream: RenderToStreamWrapper<
4043
4951
  };
4044
4952
 
4045
4953
  export default serverRenderFactory();
4046
- `,nn=`/* @jsxImportSource react */
4954
+ `,wn=`declare module "virtual:kosmo/tsq-client" {
4955
+ import type { QueryClient, QueryClientConfig } from "@tanstack/react-query";
4956
+ export const createQueryClient: (options?: QueryClientConfig) => QueryClient;
4957
+ export const getQueryClient: () => QueryClient;
4958
+ }
4959
+ `,Tn=`/* @jsxImportSource react */
4047
4960
 
4048
4961
  import styles from "./styles.module.css";
4049
4962
 
@@ -4084,7 +4997,7 @@ export default function PageSample(props: {
4084
4997
  </div>
4085
4998
  );
4086
4999
  }
4087
- `,rn=`/* @jsxImportSource react */
5000
+ `,En=`/* @jsxImportSource react */
4088
5001
 
4089
5002
  import styles from "./styles.module.css";
4090
5003
 
@@ -4136,7 +5049,7 @@ export default function PageSample(props: {
4136
5049
  </div>
4137
5050
  );
4138
5051
  }
4139
- `,an=`* {
5052
+ `,Dn=`* {
4140
5053
  margin: 0;
4141
5054
  padding: 0;
4142
5055
  box-sizing: border-box;
@@ -4271,7 +5184,7 @@ export default function PageSample(props: {
4271
5184
  align-items: center;
4272
5185
  gap: 0.25rem;
4273
5186
  }
4274
- `,on=`/* @jsxImportSource react */
5187
+ `,On=`/* @jsxImportSource react */
4275
5188
 
4276
5189
  import styles from "./styles.module.css";
4277
5190
 
@@ -4337,26 +5250,27 @@ export default function WelcomePage() {
4337
5250
  </div>
4338
5251
  );
4339
5252
  }
4340
- `,sn=`import { QueryClient, type QueryClientConfig } from "@tanstack/react-query";
5253
+ `,kn=`export * from "virtual:kosmo/tsq-client";
5254
+ `,An=`import { QueryClient } from "@tanstack/react-query";
4341
5255
 
4342
- let client: QueryClient | undefined;
5256
+ let client = undefined;
4343
5257
 
4344
- export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
5258
+ export const createQueryClient = (options) => {
4345
5259
  client = new QueryClient(options);
4346
5260
  return client;
4347
5261
  };
4348
5262
 
4349
- export const getQueryClient = (): QueryClient => {
5263
+ export const getQueryClient = () => {
4350
5264
  if (!client) {
4351
5265
  client = new QueryClient();
4352
5266
  }
4353
5267
  return client;
4354
5268
  };
4355
- `,cn=`import { QueryClient, type QueryClientConfig } from "@tanstack/react-query";
5269
+ `,jn=`import { QueryClient } from "@tanstack/react-query";
4356
5270
 
4357
- import { store } from "{{ createImport 'lib' '@ssr/base' }}";
5271
+ import { store } from "{{ createImport 'libCore' 'ssr' }}";
4358
5272
 
4359
- export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
5273
+ export const createQueryClient = (options) => {
4360
5274
  const client = new QueryClient(options);
4361
5275
  const ctx = store?.getStore();
4362
5276
  if (ctx) {
@@ -4365,7 +5279,7 @@ export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
4365
5279
  return client;
4366
5280
  };
4367
5281
 
4368
- export const getQueryClient = (): QueryClient => {
5282
+ export const getQueryClient = () => {
4369
5283
  const ctx = store?.getStore();
4370
5284
  if (!ctx) {
4371
5285
  throw new Error("getQueryClient(): called outside an SSR request scope");
@@ -4373,9 +5287,9 @@ export const getQueryClient = (): QueryClient => {
4373
5287
  if (!ctx.tsqClient) {
4374
5288
  ctx.tsqClient = new QueryClient();
4375
5289
  }
4376
- return ctx.tsqClient as QueryClient;
5290
+ return ctx.tsqClient;
4377
5291
  };
4378
- `,ln=`export type ComponentLoader = () => Promise<{
5292
+ `,Mn=`export type ComponentLoader = () => Promise<{
4379
5293
  loader?: (arg: unknown) => Promise<unknown>;
4380
5294
  }>;
4381
5295
 
@@ -4395,7 +5309,7 @@ export const loaderFactory = (opt?: { withPreload?: boolean }) => {
4395
5309
  return opt?.withPreload ? { loader } : {};
4396
5310
  };
4397
5311
  };
4398
- `,un=`import type { JSX, ComponentType } from "react";
5312
+ `,Nn=`import type { JSX, ComponentType } from "react";
4399
5313
 
4400
5314
  import {
4401
5315
  type RouteObject,
@@ -4453,7 +5367,7 @@ export const createRouters = (
4453
5367
  }
4454
5368
 
4455
5369
  export default createRouterFactory<RouteObject, Promise<JSX.Element>>();
4456
- `,dn=`import { Outlet } from "react-router";
5370
+ `,Pn=`import { Outlet } from "react-router";
4457
5371
  import { AppProvider } from "{{ createImport 'lib' 'app' }}";
4458
5372
 
4459
5373
  export default function App() {
@@ -4463,7 +5377,7 @@ export default function App() {
4463
5377
  </AppProvider>
4464
5378
  );
4465
5379
  }
4466
- `,fn=`import {
5380
+ `,Fn=`import {
4467
5381
  type LinkProps as RouterLinkProps,
4468
5382
  Link as RouterLink,
4469
5383
  } from "react-router";
@@ -4491,7 +5405,7 @@ export default function Link(
4491
5405
  </RouterLink>
4492
5406
  );
4493
5407
  }
4494
- `,pn=`import renderFactory, {
5408
+ `,In=`import renderFactory, {
4495
5409
  createRoutes,
4496
5410
  hydrate,
4497
5411
  mount,
@@ -4518,7 +5432,7 @@ if (root) {
4518
5432
  } else {
4519
5433
  console.error("❌ Root element not found!");
4520
5434
  }
4521
- `,mn=`import renderFactory, {
5435
+ `,Ln=`import renderFactory, {
4522
5436
  createRoutes,
4523
5437
  renderToStream,
4524
5438
  renderToString,
@@ -4545,29 +5459,17 @@ export default renderFactory(() => {
4545
5459
  },
4546
5460
  };
4547
5461
  });
4548
- `,hn=`<!doctype html>
4549
- <html lang="en">
4550
- <head>
4551
- <meta charset="UTF-8" />
4552
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
4553
- <!--app-head-->
4554
- </head>
4555
- <body>
4556
- <div id="app"><!--app-html--></div>
4557
- <script type="module" src="/{{ entryDir }}/client.ts"><\/script>
4558
- </body>
4559
- </html>
4560
- `,gn=`import PageSample from "{{ createImport 'lib' 'pageSamples/404.tsx' }}";
5462
+ `,Rn=`import PageSample from "{{ createImport 'lib' 'pageSamples/404.tsx' }}";
4561
5463
 
4562
5464
  export default function Page() {
4563
5465
  return <PageSample />;
4564
5466
  }
4565
- `,_n=`import { Outlet } from "react-router";
5467
+ `,zn=`import { Outlet } from "react-router";
4566
5468
 
4567
5469
  export default function Layout() {
4568
5470
  return <Outlet />;
4569
5471
  }
4570
- `,vn=`import PageSample from "{{ createImport 'lib' 'pageSamples/page.tsx' }}";
5472
+ `,Bn=`import PageSample from "{{ createImport 'lib' 'pageSamples/page.tsx' }}";
4571
5473
 
4572
5474
  export default function Page() {
4573
5475
  return PageSample({
@@ -4580,8 +5482,8 @@ export default function Page() {
4580
5482
  },
4581
5483
  });
4582
5484
  }
4583
- `,yn=`export { default } from "{{ createImport 'lib' 'pageSamples/welcome.tsx' }}";
4584
- `,bn=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
5485
+ `,Vn=`export { default } from "{{ createImport 'lib' 'pageSamples/welcome.tsx' }}";
5486
+ `,Hn=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
4585
5487
 
4586
5488
  import app from "./app";
4587
5489
 
@@ -4596,12 +5498,12 @@ export default routerFactory((routes) => {
4596
5498
  },
4597
5499
  };
4598
5500
  });
4599
- `,xn=g((e,t)=>{let{createPath:n,createImportHelpers:r}=y(e),{renderToFile:i}=x({helpers:{...r({origin:`lib`}),...w()},partials:{routePartial:en}}),{renderToFile:a}=x({helpers:r({origin:`src`})}),o=Yt(),s=e=>!e?.trim().length,u=c(t?.templates,vn),d=async e=>{for(let{kind:t,entry:r}of e)t===`pageRoute`?await a(n.pages(r.file),r.name===`index`?yn:u(r.name,r),{route:r,message:Xt()},{overwrite:s}):t===`pageLayout`&&await a(n.pages(r.file),_n,{route:r},{overwrite:s})},f=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`?[t]:[]).sort(S),r=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`||e===`pageLayout`?[t]:[]),a=o(v(r));for(let[e,t]of[[`client.ts`,$t],[`server.ts`,tn]])await i(n.libEntry(e),t,{pageEntries:r,nestedRoutes:a});await i(n.lib(`router.tsx`),un,{entries:e,indexRoutes:t})};return{config(){let{templates:e,...n}={...t};return{plugins:[se(n)]}},async start(){for(let[e,r]of[[`env.d.ts`,``],[`react.ts`,ln],[`pageSamples/styles.module.css`,an],[`pageSamples/welcome.tsx`,on],[`pageSamples/page.tsx`,rn],[`pageSamples/404.tsx`,nn],...t?.tanstack?.query?[[`app.tsx`,Qt],[`query.ts`,sn]]:[[`app.tsx`,Zt],[`query.ts`,`/** tanstack query disabled */`]]])await i(n.lib(e),r,{});for(let[e,t]of[[`pages/404.tsx`,gn],[`components/Link.tsx`,fn],[`app.tsx`,dn],[`router.ts`,bn]])await a(n.src(e),t,{entryDir:l.entryDir},{overwrite:s});await a(n.src(`index.html`),hn,{entryDir:l.entryDir},{overwrite:e=>!e?.trim().length||!e.replace(/<!--[\s\S]*?-->/g,``).trim().length});for(let[e,t]of[[`client.ts`,pn],[`server.ts`,mn]])await a(n.entry(e),t,{},{overwrite:s})},async watch(e,t){await d(e.filter(m(t,[`create`]))),await f(e)},async build(e){await d(e),await f(e)},async ssrBuild(){await i(n.lib(`query.ts`),t?.tanstack?.query?cn:`/** tanstack query disabled */`,{ssrBundle:!0})}}}),Sn=h({meta:{name:`React`,jsx:`preserve`,jsxImportSource:`react`},dependencies(e){return{react:W.devDependencies.react,"react-router":W.devDependencies[`react-router`],"path-to-regexp":W.devDependencies[`path-to-regexp`],...e?.tanstack?.query?{"@tanstack/react-query":W.devDependencies[`@tanstack/react-query`]}:{}}},devDependencies:{"@types/react":W.devDependencies[`@types/react`],"@types/react-dom":W.devDependencies[`@types/react-dom`],"react-dom":W.devDependencies[`react-dom`]},factory:xn}),G={type:`module`,private:!0,name:`@kosmojs/solid-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/solid-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"vite-plugin-solid":`^2.11.14`},devDependencies:{"@solidjs/router":`^1.0.0`,"@tanstack/solid-query":`^5.102.2`,"path-to-regexp":`^8.4.2`,"solid-js":`^1.9.15`}},Cn=()=>{let e=e=>e?.kind===`param`&&e.parts[0]?.kind===`splat`,t=t=>t?.kind===`param`?t.parts[0]?.kind===`optional`||e(t):!1,n=r=>r.flatMap(({index:r,layout:i,children:a})=>{let{pathTokens:o}={...r,...i},s=K(o.map((e,n)=>e.kind===`param`&&(t(o[n+1])||a.some(e=>e.index?.pathTokens.some(t)))?{...e,parts:[{...e.parts[0],kind:`required`}]}:e)),c=o.at(-1);if(e(c)){let e=K([c]);return r&&i?[{path:s,component:i.id,children:[{path:e,component:r.id}]}]:r?[{path:s,children:[{path:e,component:r.id},...n(a)]}]:i?[{path:s,component:i.id,children:n(a)}]:[]}return r&&i?[{path:s,component:i.id,children:[{path:`/`,component:r.id},...n(a)]}]:r?[{path:s,children:[{path:`/`,component:r.id},...n(a)]}]:i?[{path:s,component:i.id,children:n(a)}]:[]});return n},K=e=>{let t=e.map(e=>e.orig).join(`/`),n=e=>e.kind===`splat`?`*${e.name}`:e.kind===`optional`?`:${e.name}?`:`:${e.name}`;return e.flatMap(e=>e.kind===`static`?[e.parts[0].value]:e.kind===`param`?[n(e.parts[0])]:(e.parts.length&&(console.warn(`❗${r([`red`,`bold`],`WARN`)}: At the moment Solid Router does not support mixed path segments.`),console.warn(` ${r([`magenta`],e.orig)} segment in ${r([`blue`],t)} route won't match as expected.`),console.warn()),[e.parts.map(e=>e.type===`static`?e.value:n(e)).join(``)])).join(`/`)},wn=()=>{let e=[`🎉 Well done! You just created a new Solid route.`,`🚀 Success! A fresh Solid route is ready to roll.`,`🌟 Nice work! Another Solid route added to your app.`,`🧩 All set! A new Solid route has been scaffolded.`,`🔧 Scaffold complete! Your new Solid route is in place.`,`✅ Built! Your Solid route is scaffolded and ready.`,`✨ Fantastic! Your new Solid route is good to go.`,`🎯 Nailed it! A brand new Solid route just landed.`,`💫 Awesome! Another Solid route joins the party.`,`⚡ Lightning fast! A new Solid route created successfully.`];return e[Math.floor(Math.random()*e.length)]},Tn=`import type { ParentComponent } from "solid-js";
5501
+ `,Un=v((e,t)=>{let{createPath:n,createImportHelpers:r}=S(e),{render:i,renderToFile:a}=w({helpers:{...r({origin:`lib`}),...A()},partials:{routePartial:Sn}}),{renderToFile:o}=w({helpers:r({origin:`src`})}),s=_n(),c=e=>!e?.trim().length,d=l(t?.templates,Bn),f=async e=>{for(let{kind:t,entry:r}of e)t===`pageRoute`?await o(n.pages(r.file),r.name===`index`?Vn:d(r.name,r),{route:r,message:vn()},{overwrite:c}):t===`pageLayout`&&await o(n.pages(r.file),zn,{route:r},{overwrite:c})},p=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`?[t]:[]).sort(E),r=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`||e===`pageLayout`?[t]:[]),i=s(b(r));for(let[e,t]of[[`client.ts`,xn],[`server.ts`,Cn]])await a(n.libEntry(e),t,{pageEntries:r,nestedRoutes:i});await a(n.lib(`router.tsx`),Nn,{entries:e,indexRoutes:t})};return{config(){let{templates:e,...n}={...t};return{plugins:[se(n)]}},async start(){for(let[e,r]of[[`env.d.ts`,wn],[`react.ts`,Mn],[`pageSamples/styles.module.css`,Dn],[`pageSamples/welcome.tsx`,On],[`pageSamples/page.tsx`,En],[`pageSamples/404.tsx`,Tn],...t?.tanstack?.query?[[`app.tsx`,bn],[`query.ts`,kn]]:[[`app.tsx`,yn],[`query.ts`,`/** tanstack query disabled */`]]])await a(n.lib(e),r,{});for(let[e,t]of[[`pages/404.tsx`,Rn],[`components/Link.tsx`,Fn],[`app.tsx`,Pn],[`router.ts`,Hn]])await o(n.src(e),t,{entryDir:u.entryDir},{overwrite:c});for(let[e,t]of[[`client.ts`,In],[`server.ts`,Ln]])await o(n.entry(e),t,{},{overwrite:c})},async watch(e,t){await f(e.filter(g(t,[`create`]))),await p(e)},async build(e){await f(e),await p(e)},virtualModules(){return t?.tanstack?.query?[{specifier:`virtual:kosmo/tsq-client`,csr:i(An,{}),ssr:i(jn,{})}]:[]}}}),Wn=_({meta:{name:`React`,slot:`frontend`,jsx:`preserve`,jsxImportSource:`react`},dependencies(e){return{react:W.devDependencies.react,"react-router":W.devDependencies[`react-router`],...e?.tanstack?.query?{"@tanstack/react-query":W.devDependencies[`@tanstack/react-query`]}:{}}},devDependencies:{"@types/react":W.devDependencies[`@types/react`],"@types/react-dom":W.devDependencies[`@types/react-dom`],"react-dom":W.devDependencies[`react-dom`]},factory:Un}),G={type:`module`,private:!0,name:`@kosmojs/solid-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/solid-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"vite-plugin-solid":`^2.11.14`},devDependencies:{"@solidjs/router":`^1.0.0`,"@tanstack/solid-query":`^5.102.2`,"solid-js":`^1.9.15`}},Gn=()=>{let e=e=>e?.kind===`param`&&e.parts[0]?.kind===`splat`,t=t=>t?.kind===`param`?t.parts[0]?.kind===`optional`||e(t):!1,n=r=>r.flatMap(({index:r,layout:i,children:a})=>{let{pathTokens:o}={...r,...i},s=K(o.map(e=>e.kind===`param`&&a.some(e=>e.index?.pathTokens.some(t))?{...e,parts:[{...e.parts[0],kind:`required`}]}:e)),c=o.at(-1);if(e(c)){let e=K([c]);return r&&i?[{path:s,component:i.id,children:[{path:e,component:r.id}]}]:r?[{path:s,children:[{path:e,component:r.id},...n(a)]}]:i?[{path:s,component:i.id,children:n(a)}]:[]}return r&&i?[{path:s,component:i.id,children:[{path:`/`,component:r.id},...n(a)]}]:r?[{path:s,children:[{path:`/`,component:r.id},...n(a)]}]:i?[{path:s,component:i.id,children:n(a)}]:[]});return n},K=e=>{let t=e.map(e=>e.orig).join(`/`),n=e=>e.kind===`splat`?`*${e.name}`:e.kind===`optional`?`:${e.name}?`:`:${e.name}`;return e.flatMap(e=>e.kind===`static`?[e.parts[0].value]:e.kind===`param`?[n(e.parts[0])]:(e.parts.length&&(console.warn(`❗${i([`red`,`bold`],`WARN`)}: At the moment Solid Router does not support mixed path segments.`),console.warn(` ${i([`magenta`],e.orig)} segment in ${i([`blue`],t)} route won't match as expected.`),console.warn()),[e.parts.map(e=>e.type===`static`?e.value:n(e)).join(``)])).join(`/`)},Kn=()=>{let e=[`🎉 Well done! You just created a new Solid route.`,`🚀 Success! A fresh Solid route is ready to roll.`,`🌟 Nice work! Another Solid route added to your app.`,`🧩 All set! A new Solid route has been scaffolded.`,`🔧 Scaffold complete! Your new Solid route is in place.`,`✅ Built! Your Solid route is scaffolded and ready.`,`✨ Fantastic! Your new Solid route is good to go.`,`🎯 Nailed it! A brand new Solid route just landed.`,`💫 Awesome! Another Solid route joins the party.`,`⚡ Lightning fast! A new Solid route created successfully.`];return e[Math.floor(Math.random()*e.length)]},qn=`import type { ParentComponent } from "solid-js";
4600
5502
 
4601
5503
  export const AppProvider: ParentComponent = (props) => {
4602
5504
  return props.children;
4603
5505
  };
4604
- `,En=`import type { ParentComponent } from "solid-js";
5506
+ `,Jn=`import type { ParentComponent } from "solid-js";
4605
5507
  import { type QueryClient, QueryClientProvider } from "@tanstack/solid-query";
4606
5508
 
4607
5509
  import { getQueryClient } from "./query";
@@ -4613,7 +5515,7 @@ export const AppProvider: ParentComponent<{ client?: QueryClient }> = (props) =>
4613
5515
  </QueryClientProvider>
4614
5516
  );
4615
5517
  };
4616
- `,Dn=`import { lazy, type JSX } from "solid-js";
5518
+ `,Yn=`import { lazy, type JSX } from "solid-js";
4617
5519
  import { hydrate as hydrateOrig, render } from "solid-js/web";
4618
5520
  import type { RouterFactoryReturn } from "@kosmojs/core";
4619
5521
  import { clientRenderFactory } from "@kosmojs/core/generators";
@@ -4658,7 +5560,7 @@ export const mount = async (
4658
5560
  }
4659
5561
 
4660
5562
  export default clientRenderFactory();
4661
- `,On=`{
5563
+ `,Xn=`{
4662
5564
  path: "{{path}}",
4663
5565
  {{#if component}}
4664
5566
  component: {{component}}_component,
@@ -4668,7 +5570,7 @@ export default clientRenderFactory();
4668
5570
  children: [ {{#each children}}{{> routePartial}}, {{/each}}],
4669
5571
  {{/if}}
4670
5572
  }
4671
- `,kn=`import type { JSX } from "solid-js";
5573
+ `,Zn=`import type { JSX } from "solid-js";
4672
5574
 
4673
5575
  import {
4674
5576
  generateHydrationScript,
@@ -4723,11 +5625,44 @@ export const renderWrapper: SSRRenderWrapper = (context, render) => {
4723
5625
  export const renderToString: RenderToStringWrapper<
4724
5626
  () => RouterFactoryReturn<JSX.Element>,
4725
5627
  Parameters<typeof renderToStringAsync>[1]
4726
- > = async (resolver, { headerTags = [], ...options } = {}) => {
4727
- return {
4728
- head: [...headerTags, generateHydrationScript()].join("\\n"),
4729
- html: await renderToStringAsync(() => resolver().component, options),
4730
- };
5628
+ > = async (resolver, { headerTags = [], timeoutMs = 30_000, ...options } = {}) => {
5629
+ /**
5630
+ * renderToStringAsync adaptation.
5631
+ * Solid arms its internal timeout before invoking the component,
5632
+ * so a component throwing synchronously leaves that timeout promise orphaned -
5633
+ * its rejection, 30s later, is unhandled and fatal to the process.
5634
+ * renderToStream's thenable resolves with the same html;
5635
+ * deferring the call turns a sync throw into an immediate, catchable rejection carrying the real error,
5636
+ * and the replacement timeout below is pre-observed, so it can never reject unhandled.
5637
+ * */
5638
+ let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
5639
+
5640
+ const timeout = new Promise<never>((_, reject) => {
5641
+ timeoutHandle = setTimeout(() => {
5642
+ reject(new Error("SSR: solid render timed out"));
5643
+ }, timeoutMs);
5644
+ });
5645
+
5646
+ timeout.catch(() => {});
5647
+
5648
+ try {
5649
+ return {
5650
+ head: [...headerTags, generateHydrationScript()].join("\\n"),
5651
+ html: await Promise.race([
5652
+ Promise.resolve().then(() => {
5653
+ // typed as a stream handle, but resolving to the full html at runtime.
5654
+ // renderToStringAsync itself races it the same way.
5655
+ return renderToStreamOrig(
5656
+ () => resolver().component,
5657
+ options,
5658
+ ) as unknown as Promise<string>;
5659
+ }),
5660
+ timeout,
5661
+ ]),
5662
+ };
5663
+ } finally {
5664
+ clearTimeout(timeoutHandle);
5665
+ }
4731
5666
  };
4732
5667
 
4733
5668
  export const renderToStream: RenderToStreamWrapper<
@@ -4749,7 +5684,12 @@ export const renderToStream: RenderToStreamWrapper<
4749
5684
  };
4750
5685
 
4751
5686
  export default serverRenderFactory<true>();
4752
- `,An=`/* @jsxImportSource solid-js */
5687
+ `,Qn=`declare module "virtual:kosmo/tsq-client" {
5688
+ import type { QueryClient, QueryClientConfig } from "@tanstack/solid-query";
5689
+ export const createQueryClient: (options?: QueryClientConfig) => QueryClient;
5690
+ export const getQueryClient: () => QueryClient;
5691
+ }
5692
+ `,$n=`/* @jsxImportSource solid-js */
4753
5693
 
4754
5694
  import styles from "./styles.module.css";
4755
5695
 
@@ -4790,7 +5730,7 @@ export default function PageSample(props: {
4790
5730
  </div>
4791
5731
  );
4792
5732
  }
4793
- `,jn=`/* @jsxImportSource solid-js */
5733
+ `,er=`/* @jsxImportSource solid-js */
4794
5734
 
4795
5735
  import styles from "./styles.module.css";
4796
5736
 
@@ -4842,7 +5782,7 @@ export default function PageSample(props: {
4842
5782
  </div>
4843
5783
  );
4844
5784
  }
4845
- `,Mn=`* {
5785
+ `,tr=`* {
4846
5786
  margin: 0;
4847
5787
  padding: 0;
4848
5788
  box-sizing: border-box;
@@ -4977,7 +5917,7 @@ export default function PageSample(props: {
4977
5917
  align-items: center;
4978
5918
  gap: 0.25rem;
4979
5919
  }
4980
- `,Nn=`/* @jsxImportSource solid-js */
5920
+ `,nr=`/* @jsxImportSource solid-js */
4981
5921
 
4982
5922
  import styles from "./styles.module.css";
4983
5923
 
@@ -5043,26 +5983,27 @@ export default function WelcomePage() {
5043
5983
  </div>
5044
5984
  );
5045
5985
  }
5046
- `,Pn=`import { QueryClient, type QueryClientConfig } from "@tanstack/solid-query";
5986
+ `,rr=`export * from "virtual:kosmo/tsq-client";
5987
+ `,ir=`import { QueryClient } from "@tanstack/solid-query";
5047
5988
 
5048
- let client: QueryClient | undefined;
5989
+ let client = undefined;
5049
5990
 
5050
- export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
5991
+ export const createQueryClient = (options) => {
5051
5992
  client = new QueryClient(options);
5052
5993
  return client;
5053
5994
  };
5054
5995
 
5055
- export const getQueryClient = (): QueryClient => {
5996
+ export const getQueryClient = () => {
5056
5997
  if (!client) {
5057
5998
  client = new QueryClient();
5058
5999
  }
5059
6000
  return client;
5060
6001
  };
5061
- `,Fn=`import { QueryClient, type QueryClientConfig } from "@tanstack/solid-query";
6002
+ `,ar=`import { QueryClient } from "@tanstack/solid-query";
5062
6003
 
5063
- import { store } from "{{ createImport 'lib' '@ssr/base' }}";
6004
+ import { store } from "{{ createImport 'libCore' 'ssr' }}";
5064
6005
 
5065
- export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
6006
+ export const createQueryClient = (options) => {
5066
6007
  const client = new QueryClient(options);
5067
6008
  const ctx = store?.getStore();
5068
6009
  if (ctx) {
@@ -5071,7 +6012,7 @@ export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
5071
6012
  return client;
5072
6013
  };
5073
6014
 
5074
- export const getQueryClient = (): QueryClient => {
6015
+ export const getQueryClient = () => {
5075
6016
  const ctx = store?.getStore();
5076
6017
  if (!ctx) {
5077
6018
  throw new Error("getQueryClient(): called outside an SSR request scope");
@@ -5079,9 +6020,9 @@ export const getQueryClient = (): QueryClient => {
5079
6020
  if (!ctx.tsqClient) {
5080
6021
  ctx.tsqClient = new QueryClient();
5081
6022
  }
5082
- return ctx.tsqClient as QueryClient;
6023
+ return ctx.tsqClient;
5083
6024
  };
5084
- `,In=`import type { JSX, ParentComponent } from "solid-js";
6025
+ `,or=`import type { JSX, ParentComponent } from "solid-js";
5085
6026
  import { Router, type RouteDefinition } from "@solidjs/router";
5086
6027
  import type { RouterFactoryReturn } from "@kosmojs/core";
5087
6028
  import { createRouterFactory } from "@kosmojs/core/generators";
@@ -5117,7 +6058,7 @@ export const createRouters = (
5117
6058
  }
5118
6059
 
5119
6060
  export default createRouterFactory<RouteDefinition, JSX.Element>();
5120
- `,Ln=`export type ComponentLoader = () => Promise<{
6061
+ `,sr=`export type ComponentLoader = () => Promise<{
5121
6062
  preload?: () => Promise<unknown>;
5122
6063
  }>;
5123
6064
 
@@ -5132,10 +6073,10 @@ export const loaderFactory = (opt?: { withPreload?: boolean }) => {
5132
6073
  return opt?.withPreload ? { preload } : {};
5133
6074
  };
5134
6075
  };
5135
- `,Rn=`export type MaybeWrapped<T> = import("solid-js/store").Store<T> | T;
6076
+ `,cr=`export type MaybeWrapped<T> = import("solid-js/store").Store<T> | T;
5136
6077
 
5137
6078
  export { unwrap } from "solid-js/store";
5138
- `,zn=`import type { ParentComponent } from "solid-js";
6079
+ `,lr=`import type { ParentComponent } from "solid-js";
5139
6080
  import { AppProvider } from "{{ createImport 'lib' 'app' }}";
5140
6081
 
5141
6082
  const App: ParentComponent = (props) => {
@@ -5143,7 +6084,7 @@ const App: ParentComponent = (props) => {
5143
6084
  };
5144
6085
 
5145
6086
  export default App;
5146
- `,Bn=`import { A, type AnchorProps } from "@solidjs/router";
6087
+ `,ur=`import { A, type AnchorProps } from "@solidjs/router";
5147
6088
  import { type JSXElement, splitProps } from "solid-js";
5148
6089
 
5149
6090
  import { pageRouteMap, type LinkProps } from "{{ createImport 'libCore' }}";
@@ -5168,7 +6109,7 @@ export default function Link(
5168
6109
 
5169
6110
  return <A {...{ ...restProps, href: href() }}>{knownProps.children}</A>;
5170
6111
  }
5171
- `,Vn=`import renderFactory, {
6112
+ `,dr=`import renderFactory, {
5172
6113
  createRoutes,
5173
6114
  hydrate,
5174
6115
  mount,
@@ -5195,7 +6136,7 @@ if (root) {
5195
6136
  } else {
5196
6137
  console.error("❌ Root element not found!");
5197
6138
  }
5198
- `,Hn=`import renderFactory, {
6139
+ `,fr=`import renderFactory, {
5199
6140
  createRoutes,
5200
6141
  renderToStream,
5201
6142
  renderToString,
@@ -5222,31 +6163,19 @@ export default renderFactory(() => {
5222
6163
  },
5223
6164
  };
5224
6165
  });
5225
- `,Un=`<!doctype html>
5226
- <html lang="en">
5227
- <head>
5228
- <meta charset="UTF-8" />
5229
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
5230
- <!--app-head-->
5231
- </head>
5232
- <body>
5233
- <div id="app"><!--app-html--></div>
5234
- <script type="module" src="/{{ entryDir }}/client.ts"><\/script>
5235
- </body>
5236
- </html>
5237
- `,Wn=`import PageSample from "{{ createImport 'lib' 'pageSamples/404.tsx' }}";
6166
+ `,pr=`import PageSample from "{{ createImport 'lib' 'pageSamples/404.tsx' }}";
5238
6167
 
5239
6168
  export default function Page() {
5240
6169
  return <PageSample />;
5241
6170
  }
5242
- `,Gn=`import type { ParentComponent } from "solid-js";
6171
+ `,mr=`import type { ParentComponent } from "solid-js";
5243
6172
 
5244
6173
  const Layout: ParentComponent = (props) => {
5245
6174
  return props.children;
5246
6175
  };
5247
6176
 
5248
6177
  export default Layout;
5249
- `,Kn=`import PageSample from "{{ createImport 'lib' 'pageSamples/page.tsx' }}";
6178
+ `,hr=`import PageSample from "{{ createImport 'lib' 'pageSamples/page.tsx' }}";
5250
6179
 
5251
6180
  export default function Page() {
5252
6181
  return PageSample({
@@ -5259,8 +6188,8 @@ export default function Page() {
5259
6188
  },
5260
6189
  });
5261
6190
  }
5262
- `,qn=`export { default } from "{{ createImport 'lib' 'pageSamples/welcome.tsx' }}";
5263
- `,Jn=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
6191
+ `,gr=`export { default } from "{{ createImport 'lib' 'pageSamples/welcome.tsx' }}";
6192
+ `,_r=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
5264
6193
 
5265
6194
  import app from "./app";
5266
6195
 
@@ -5275,286 +6204,130 @@ export default routerFactory((routes) => {
5275
6204
  },
5276
6205
  };
5277
6206
  });
5278
- `,Yn=g((e,t)=>{let{generators:n=[]}=e.config,{createPath:r,createImportHelpers:i}=y(e),{renderToFile:a}=x({helpers:{...i({origin:`lib`}),...w()},partials:{routePartial:On}}),{renderToFile:o}=x({helpers:i({origin:`src`})}),s=Cn(),u=e=>!e?.trim().length,d=c(t?.templates,Kn),f=async e=>{for(let{kind:t,entry:n}of e)t===`pageRoute`?await o(r.pages(n.file),n.name===`index`?qn:d(n.name,n),{route:n,message:wn()},{overwrite:u}):t===`pageLayout`&&await o(r.pages(n.file),Gn,{route:n},{overwrite:u})},p=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`?[t]:[]).sort(S),n=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`||e===`pageLayout`?[t]:[]),i=s(v(n));for(let[e,t]of[[`client.ts`,Dn],[`server.ts`,kn]])await a(r.libEntry(e),t,{pageEntries:n,nestedRoutes:i});await a(r.lib(`router.tsx`),In,{entries:e,indexRoutes:t})};return{config({command:e}){let{templates:r,...i}={...t};return{oxc:{jsx:{importSource:`solid-js`}},plugins:e===`build`?[D({...i,...n.some(e=>e.meta.slot===`ssr`)?{ssr:!0,solid:{...i?.solid,hydratable:!0}}:{}})]:[D({...i,dev:!0,hot:!0})]}},async start(){for(let[e,n]of[[`env.d.ts`,``],[`solid.ts`,Ln],[`unwrap.ts`,Rn],[`pageSamples/styles.module.css`,Mn],[`pageSamples/welcome.tsx`,Nn],[`pageSamples/page.tsx`,jn],[`pageSamples/404.tsx`,An],...t?.tanstack?.query?[[`app.tsx`,En],[`query.ts`,Pn]]:[[`app.tsx`,Tn],[`query.ts`,`/** tanstack query disabled */`]]])await a(r.lib(e),n,{});for(let[e,t]of[[`pages/404.tsx`,Wn],[`components/Link.tsx`,Bn],[`app.tsx`,zn],[`router.ts`,Jn]])await o(r.src(e),t,{entryDir:l.entryDir},{overwrite:u});await o(r.src(`index.html`),Un,{entryDir:l.entryDir},{overwrite:e=>!e?.trim().length||!e.replace(/<!--[\s\S]*?-->/g,``).trim().length});for(let[e,t]of[[`client.ts`,Vn],[`server.ts`,Hn]])await o(r.entry(e),t,{},{overwrite:u})},async watch(e,t){await f(e.filter(m(t,[`create`]))),await p(e)},async build(e){await f(e),await p(e)},async ssrBuild(){await a(r.lib(`query.ts`),t?.tanstack?.query?Fn:`/** tanstack query disabled */`,{ssrBundle:!0})}}}),Xn=h({meta:{name:`SolidJS`,jsx:`preserve`,jsxImportSource:`solid-js`},dependencies(e){return{"solid-js":G.devDependencies[`solid-js`],"@solidjs/router":G.devDependencies[`@solidjs/router`],"path-to-regexp":G.devDependencies[`path-to-regexp`],...e?.tanstack?.query?{"@tanstack/solid-query":G.devDependencies[`@tanstack/solid-query`]}:{}}},factory:Yn}),Zn=g(e=>{let{generators:i=[],refineTypeName:a,...o}={...e.config},{createPath:s}=y(e);return{async postBuild(){let a=s.distDir(`ssg`),c=n(a,`../ssr/server.js`);if(!await ce(c,le.F_OK).then(()=>!0,()=>!1)){console.error(),console.error(r(`red`,`❗Please enable ssrGenerator in ${e.name}/kosmo.config.ts`)),console.error(` SSG generator can not run without SSR server`),console.error();return}let l=te(`${e.name}: SSG`);l.append(`preparing...`);let{createDisposableServer:u}=await import(c);await O(n(a,`../client/assets`),t(a,`assets`),{recursive:!0}),l.append(`bundling routes...`),await E(_(o,...i.map(({factory:t})=>t(e).config?.({kind:`client`,command:`build`})),{root:s.lib(),appType:`custom`,plugins:[C.tsconfigPaths(e),C.nodePrefix()],resolve:{conditions:[`node`]},build:{ssr:s.lib(`ssg.ts`),target:`esnext`,sourcemap:!1,emptyOutDir:!0,rolldownOptions:{output:{dir:a,entryFileNames:`routes.js`,format:`esm`}}}}));try{let e=await import(t(a,`routes.js`)).then(e=>e.default);u(async n=>{for(let[r,i]of e.entries()){l.append(`[ ${r+1} of ${e.length} ] ${i}`);let o=await Qn(n,i);o!==void 0&&(await ue(t(a,i),{recursive:!0}),await de(t(a,t(i,`index.html`)),o,`utf8`))}l.succeed(`done ✨`)})}finally{await k(`${a}/routes.js`)}}}}),Qn=async(e,t)=>{try{let n=`http://localhost:${e}${t}`;return await(await fetch(n)).text()}catch(e){console.error(r(`red`,`✗ SSG: Failed generating ${t} route: ${e.message}`));return}},$n=h({meta:{name:`SSG`,slot:`ssg`},factory:Zn}),q={type:`module`,private:!0,name:`@kosmojs/ssr-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,vite:`^8.2.2`},devDependencies:{"@hono/node-server":`^2.1.1`,hono:`^4.13.3`,"light-my-request":`^6.6.0`,tinyglobby:`^0.2.17`}},er=`{{#if apiGenerator}}
5279
- export { default as apiApp } from "{{ createImport 'api' 'app' }}";
5280
- {{else}}
5281
- export const apiApp = undefined;
5282
- {{/if}}
5283
- `,tr=`import { AsyncLocalStorage } from "node:async_hooks";
5284
-
5285
- import type { FetchApp, NodeApp } from "@kosmojs/core";
5286
-
5287
- export type RequestContext = {
5288
- headers?: HeadersInit;
5289
- tsqClient?: unknown;
5290
- error?: unknown;
5291
- };
5292
-
5293
- export const redirectCodes = [
5294
- // Moved Permanently
5295
- 301,
5296
- // Found (temporary)
5297
- 302,
5298
- // See Other (redirect after POST)
5299
- 303,
5300
- // Temporary Redirect (preserves method)
5301
- 307,
5302
- // Permanent Redirect (preserves method)
5303
- 308,
5304
- ];
5305
-
5306
- /**
5307
- * Origin used to absolutize the relative URLs the client produces.
5308
- * Never resolved over the network; the host part is irrelevant to
5309
- * route matching in both Hono and Koa.
5310
- * */
5311
- export const ssrOrigin = "http://ssr.local";
5312
-
5313
- /**
5314
- * Maximum redirect hops, mirroring the fetch spec limit.
5315
- * */
5316
- export const maxRedirects = 5;
5317
-
5318
- /**
5319
- * Request-scoped context store.
5320
- * Server-only module - never reaches browser bundles.
5321
- * */
5322
- export const store = new AsyncLocalStorage<RequestContext>();
5323
-
5324
- export const isFetchApp = (app: FetchApp | NodeApp): app is FetchApp => {
5325
- return typeof (app as FetchApp).fetch === "function";
5326
- };
5327
- `,nr=`import { type RequestContext, store } from "./base";
5328
-
5329
- import { renderWrapper } from "{{ createImport 'libEntry' 'server' }}";
5330
-
5331
- export { default as ssrApp } from "{{ createImport 'entry' 'server' }}";
5332
- export { apiApp } from "{{ createImport 'lib' '@ssr/api' }}";
6207
+ `,vr=v((e,t)=>{let{generators:n=[]}=e.config,{createPath:r,createImportHelpers:i}=S(e),{render:a,renderToFile:o}=w({helpers:{...i({origin:`lib`}),...A()},partials:{routePartial:Xn}}),{renderToFile:s}=w({helpers:i({origin:`src`})}),c=Gn(),d=e=>!e?.trim().length,f=l(t?.templates,hr),p=async e=>{for(let{kind:t,entry:n}of e)t===`pageRoute`?await s(r.pages(n.file),n.name===`index`?gr:f(n.name,n),{route:n,message:Kn()},{overwrite:d}):t===`pageLayout`&&await s(r.pages(n.file),mr,{route:n},{overwrite:d})},m=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`?[t]:[]).sort(E),n=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`||e===`pageLayout`?[t]:[]),i=c(b(n));for(let[e,t]of[[`client.ts`,Yn],[`server.ts`,Zn]])await o(r.libEntry(e),t,{pageEntries:n,nestedRoutes:i});await o(r.lib(`router.tsx`),or,{entries:e,indexRoutes:t})};return{config({command:e}){let{templates:r,...i}={...t};return{oxc:{jsx:{importSource:`solid-js`}},plugins:e===`build`?[N({...i,...n.some(e=>e.meta.slot===`ssr`)?{ssr:!0,solid:{...i?.solid,hydratable:!0}}:{}})]:[N({...i,dev:!0,hot:!0})]}},async start(){for(let[e,n]of[[`env.d.ts`,Qn],[`solid.ts`,sr],[`unwrap.ts`,cr],[`pageSamples/styles.module.css`,tr],[`pageSamples/welcome.tsx`,nr],[`pageSamples/page.tsx`,er],[`pageSamples/404.tsx`,$n],...t?.tanstack?.query?[[`app.tsx`,Jn],[`query.ts`,rr]]:[[`app.tsx`,qn],[`query.ts`,`/** tanstack query disabled */`]]])await o(r.lib(e),n,{});for(let[e,t]of[[`pages/404.tsx`,pr],[`components/Link.tsx`,ur],[`app.tsx`,lr],[`router.ts`,_r]])await s(r.src(e),t,{entryDir:u.entryDir},{overwrite:d});for(let[e,t]of[[`client.ts`,dr],[`server.ts`,fr]])await s(r.entry(e),t,{},{overwrite:d})},async watch(e,t){await p(e.filter(g(t,[`create`]))),await m(e)},async build(e){await p(e),await m(e)},virtualModules(){return t?.tanstack?.query?[{specifier:`virtual:kosmo/tsq-client`,csr:a(ir,{}),ssr:a(ar,{})}]:[]}}}),yr=_({meta:{name:`SolidJS`,slot:`frontend`,jsx:`preserve`,jsxImportSource:`solid-js`},dependencies(e){return{"solid-js":G.devDependencies[`solid-js`],"@solidjs/router":G.devDependencies[`@solidjs/router`],...e?.tanstack?.query?{"@tanstack/solid-query":G.devDependencies[`@tanstack/solid-query`]}:{}}},factory:vr}),br=`import { join } from "node:path";
5333
6208
 
5334
- /**
5335
- * Wraps a render call, making the given context visible to every
5336
- * fetch dispatch that happens during it - across await points,
5337
- * stream chunks and parallel component data loads.
5338
- * */
5339
- export const withSsrContext = <T>(
5340
- context: RequestContext,
5341
- render: () => T,
5342
- ): T => {
5343
- return store.run(context, () => renderWrapper(context, render));
5344
- };
6209
+ import { compile } from "path-to-regexp";
5345
6210
 
5346
- export const errorProvider = () => {
5347
- return store.getStore()?.error;
5348
- };
5349
- `,rr=`import type { FetchApp, NodeApp } from "@kosmojs/core";
5350
- import type { Transport } from "@kosmojs/core/fetch";
6211
+ import type { PageRoute } from "@kosmojs/core";
5351
6212
 
5352
- import {
5353
- isFetchApp,
5354
- maxRedirects,
5355
- redirectCodes,
5356
- ssrOrigin,
5357
- store,
5358
- } from "./base";
6213
+ import routes from "{{ createImport 'lib' 'ssg:routes' }}";
6214
+ import { base } from "{{ createImport 'libCore' }}";
5359
6215
 
5360
- import { apiApp } from "{{ createImport 'lib' '@ssr/api' }}";
6216
+ type StaticParams = Array<Array<string | number | Array<string | number>>>;
5361
6217
 
5362
6218
  /**
5363
- * HeadersProvider for createTransport.
6219
+ * Where a page declares the parameter sets to pre-render:
6220
+ * - a \`staticParams\` named export - React/Solid page modules,
6221
+ * a plain \`<script>\` block in Vue, a \`<script module>\` block in Svelte
6222
+ * - \`staticParams\` in MDX frontmatter
6223
+ * Each entry is positional, in the route's parameter order.
5364
6224
  * */
5365
- const headersProvider = (): HeadersInit | undefined => {
5366
- return store.getStore()?.headers;
6225
+ const staticParamsOf = (module: unknown): StaticParams | undefined => {
6226
+ const { staticParams, frontmatter } = (module ?? {}) as {
6227
+ staticParams?: unknown;
6228
+ frontmatter?: { staticParams?: unknown };
6229
+ };
6230
+ const value = staticParams ?? frontmatter?.staticParams;
6231
+ return Array.isArray(value) ? (value as StaticParams) : undefined;
5367
6232
  };
5368
6233
 
5369
- const createDispatch = (app: FetchApp | NodeApp) => {
5370
- return isFetchApp(app)
5371
- ? app.fetch
5372
- : async (request: Request): Promise<Response> => {
5373
- const { inject } = await import("light-my-request");
5374
-
5375
- /**
5376
- * Node dispatch: serializes the web Request into light-my-request's
5377
- * injection format and lifts the injected response back into a web Response.
5378
- * */
5379
- const url = new URL(request.url);
5380
-
5381
- const payload = ["GET", "HEAD"].includes(request.method)
5382
- ? undefined
5383
- : Buffer.from(await request.arrayBuffer());
5384
-
5385
- const result = await inject(app.callback() as never, {
5386
- method: request.method as never,
5387
- url: url.pathname + url.search,
5388
- headers: Object.fromEntries(request.headers),
5389
- ...(payload?.length ? { payload } : {}),
5390
- });
5391
-
5392
- const headers = new Headers();
5393
-
5394
- for (const [key, value] of Object.entries(result.headers)) {
5395
- for (const entry of Array.isArray(value) ? value : [value]) {
5396
- if (entry !== undefined) {
5397
- headers.append(key, String(entry));
5398
- }
5399
- }
6234
+ const paramsMapper = (
6235
+ params: PageRoute["params"],
6236
+ value: StaticParams[number],
6237
+ ) => {
6238
+ return params.schema.reduce<Record<string, unknown>>(
6239
+ (map, { name, kind }, i) => {
6240
+ if (kind === "splat") {
6241
+ if (Array.isArray(value[i]) && value[i].length) {
6242
+ map[name] = value[i].map(String);
5400
6243
  }
5401
-
5402
- /**
5403
- * 204/304 responses must not carry a body per the Response
5404
- * constructor contract.
5405
- * */
5406
- const body = [204, 304].includes(result.statusCode)
5407
- ? null
5408
- : new Uint8Array(result.rawPayload);
5409
-
5410
- return new Response(body, {
5411
- status: result.statusCode,
5412
- statusText: result.statusMessage,
5413
- headers,
5414
- });
5415
- };
5416
- };
5417
-
5418
- const createTransport = (app: FetchApp | NodeApp): Transport => {
5419
- const dispatch = createDispatch(app);
5420
-
5421
- /**
5422
- * Build a fetch-compatible transport that dispatches requests
5423
- * directly into the given app - no sockets, no interception.
5424
- * Redirects are followed in-process, including the 303 and 301/302 method rewrite to GET.
5425
- * */
5426
- return async (input, init) => {
5427
- /**
5428
- * Request-scoped headers act as defaults: anything set explicitly
5429
- * on the call itself wins over forwarded values.
5430
- * */
5431
- const headers = new Headers(init?.headers);
5432
-
5433
- // When the body is FormData, the Request constructor sets a multipart
5434
- // Content-Type with a fresh boundary. A forwarded Content-Type default would
5435
- // override that boundary and desync it from the serialized body, so never
5436
- // forward Content-Type for FormData bodies.
5437
- const isFormBody = init?.body instanceof FormData;
5438
-
5439
- for (const [key, value] of new Headers(headersProvider() || undefined)) {
5440
- if (isFormBody && key.toLowerCase() === "content-type") {
5441
- continue;
5442
- }
5443
- if (!headers.has(key)) {
5444
- headers.set(key, value);
5445
- }
5446
- }
5447
-
5448
- let request = new Request(new URL(String(input), ssrOrigin), {
5449
- ...init,
5450
- headers,
5451
- });
5452
-
5453
- /**
5454
- * Bodies are buffered once so they can be replayed across
5455
- * 307/308 hops; the client only ever sends strings, FormData
5456
- * and buffer-ish payloads, so this is safe and cheap.
5457
- * */
5458
- const body = ["GET", "HEAD"].includes(request.method)
5459
- ? undefined
5460
- : await request.arrayBuffer();
5461
-
5462
- for (let hop = 0; ; hop++) {
5463
- if (hop === maxRedirects) {
5464
- throw new TypeError("Failed to fetch: too many redirects");
6244
+ } else if (value[i] !== undefined) {
6245
+ map[name] = String(value[i]);
5465
6246
  }
6247
+ return map;
6248
+ },
6249
+ {},
6250
+ );
6251
+ };
5466
6252
 
5467
- const response = await dispatch(
5468
- body === undefined || request.method === "GET"
5469
- ? new Request(request, { body: null })
5470
- : new Request(request, { body }),
5471
- );
5472
-
5473
- const location = response.headers.get("location");
5474
-
5475
- if (!location || !redirectCodes.includes(response.status)) {
5476
- return response;
5477
- }
6253
+ export default Object.entries(routes)
6254
+ .flatMap(([name, { module, pathPattern, params }]) => {
6255
+ if (!params.schema.length) {
6256
+ // static route
6257
+ return [pathPattern.replace(/^index\\/?/, "")];
6258
+ }
5478
6259
 
5479
- const method =
5480
- response.status === 303 ||
5481
- ([301, 302].includes(response.status) && request.method === "POST")
5482
- ? "GET"
5483
- : request.method;
6260
+ const staticParams = staticParamsOf(module);
5484
6261
 
5485
- request = new Request(new URL(location, request.url), {
5486
- method,
5487
- headers: request.headers,
5488
- });
6262
+ // a dynamic route without staticParams has nothing to pre-render
6263
+ if (!staticParams) {
6264
+ return [];
5489
6265
  }
5490
- };
5491
- };
5492
6266
 
5493
- const ssrTransport = apiApp ? createTransport(apiApp as never) : undefined;
6267
+ const toPath = compile(pathPattern);
5494
6268
 
5495
- export const transport = ssrTransport
5496
- ? async (input: RequestInfo | URL, init?: RequestInit) => {
6269
+ return staticParams.flatMap((entry) => {
5497
6270
  try {
5498
- const response = await ssrTransport(input, init);
5499
- if (response?.ok) {
5500
- return response;
5501
- }
5502
- // the rethrow here needed cause ssrTransport does not throw on non-2xx responses
5503
- throw new SSRFetchError([
5504
- input,
5505
- response,
5506
- typeof response?.text === "function"
5507
- ? await response.text()
5508
- : response?.statusText,
5509
- ]);
6271
+ return [toPath(paramsMapper(params, entry) as never)];
5510
6272
  } catch (error) {
5511
- /**
5512
- * Capture the fetch error at the transport level and stash it on the request store.
5513
- * Some frameworks - Solid notably - swallow a rejecting loader and still emit a partial render tree.
5514
- * Storing the error here keeps it observable regardless of how the framework handles the loader rejection.
5515
- * */
5516
- const storage = store.getStore();
5517
- if (storage) {
5518
- storage.error = error;
5519
- }
5520
- throw error;
6273
+ console.error(\`❗SSG: Failed building path for \${name}\`);
6274
+ console.error(error);
6275
+ return [];
5521
6276
  }
5522
- }
5523
- : undefined; // let fetch clients pick the transport
6277
+ });
6278
+ })
6279
+ .map((path) => join(base, path));
6280
+ `,xr=`import type { PageRoute } from "@kosmojs/core";
5524
6281
 
5525
- class SSRFetchError extends Error {
5526
- constructor([input, response, message]: [
5527
- input: RequestInfo | URL,
5528
- response: Response,
5529
- message: string | undefined,
5530
- ]) {
5531
- const pathname = pathnameOf(input);
5532
- const status = response.status ?? "unknown";
5533
- super(\`\${pathname}: \${status} [ \${message} ]\`.trim());
5534
- this.name = "SSRFetchError";
5535
- }
5536
- }
6282
+ {{#each pageRoutes}}
6283
+ import * as {{id}} from "{{ createImport 'pages' file }}";
6284
+ {{/each}}
5537
6285
 
5538
- const pathnameOf = (input: RequestInfo | URL): string => {
5539
- try {
5540
- if (typeof input === "string") {
5541
- return new URL(input, "http://x").pathname;
5542
- }
5543
- if (input instanceof URL) {
5544
- return input.pathname;
5545
- }
5546
- if (input instanceof Request) {
5547
- return new URL(input.url).pathname;
5548
- }
5549
- } catch {}
5550
- return String(input);
6286
+ type SSGRoute = {
6287
+ // the page module as imported; the shape differs per framework
6288
+ module: unknown;
6289
+ pathPattern: string;
6290
+ params: PageRoute["params"];
6291
+ };
6292
+
6293
+ const routeMap: Record<string, SSGRoute> = {
6294
+ {{#each pageRoutes}}
6295
+ "{{name}}": {
6296
+ module: {{id}},
6297
+ pathPattern: "{{pathPattern}}",
6298
+ params: {{serializeParams .}},
6299
+ },
6300
+ {{/each}}
6301
+ };
6302
+
6303
+ export default routeMap;
6304
+ `,Sr=v(a=>{let{generators:o=[],refineTypeName:s,...c}={...a.config},{base:l}=c,{createPath:u,createImportHelpers:f}=S(a),{renderToFile:p}=w({helpers:{...f({origin:`lib`}),...A(),serializeParams(e){return JSON.stringify(e.params)}}}),m=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`?[t]:[]).sort(E);await p(u.lib(`ssg:routes.ts`),xr,{pageRoutes:t})};return{async start(){await p(u.lib(`ssg.ts`),br,{})},async watch(e){await m(e)},async build(e){await m(e)},async postBuild(){let s=u.distDir(`ssg`),f=r(s,`../ssr/server.js`);if(!await ce(f,le.F_OK).then(()=>!0,()=>!1)){console.error(),console.error(i(`red`,`❗Please enable ssrGenerator in ${a.name}/kosmo.config.ts`)),console.error(` SSG generator can not run without SSR server`),console.error();return}let p=ee(`${a.name}: SSG`);p.append(`preparing...`);let{createApp:m}=await import(f);p.append(`bundling routes...`),await M(y(c,...o.map(({factory:e})=>e(a).config?.({kind:`client`,command:`build`})),{root:u.lib(),appType:`custom`,plugins:[O.tsconfigPaths(a),O.nodePrefix(),O.virtualModules(d(a,o),{kind:`csr`,command:`build`})],resolve:{conditions:[`node`]},build:{ssr:u.lib(`ssg.ts`),target:`esnext`,sourcemap:!1,emptyOutDir:!0,rolldownOptions:{output:{dir:s,entryFileNames:`routes.js`,format:`esm`}}}}));try{let i=await import(t(s,`routes.js`)).then(e=>e.default),a=new Map,o=await m(e=>{a.set(new URL(e.url).pathname,{error:e.message})});for(let[e,t]of i.entries()){p.append(`[ ${e+1} of ${i.length} ] ${t}`);try{let e=await Cr(o,t);a.has(t)||a.set(t,{html:e})}catch(e){a.has(t)||a.set(t,{error:String(e)})}}let c=[...a.entries()].flatMap(([e,t])=>`error`in t?[[e,t.error]]:[]);if(c.length)throw p.failed(`failed ❗`),Error([`SSG: failed rendering ${c.length} route(s):`,...c.map(([e,t])=>` ${e} - ${t}`)].join(`
6305
+ `));await P(r(s,`../ssr/assets`),t(s,`assets`),{recursive:!0});let u=r(s,`../ssr/public`);await x(u)&&await P(u,s,{recursive:!0});for(let[r,i]of a)if(`html`in i){let a=t(s,n.relative(l,r),`index.html`);await ue(e(a),{recursive:!0}),await de(a,i.html,`utf8`)}p.succeed(`done ✨`)}finally{await F(`${s}/routes.js`)}}}}),Cr=async(e,t)=>{let n=await e.fetch(new Request(`http://localhost${t}`));if(!n.ok)throw Error(`app responded with ${n.status}`);return n.text()},wr=_({meta:{name:`SSG`,slot:`ssg`},factory:Sr}),q={type:`module`,private:!0,name:`@kosmojs/ssr-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,vite:`^8.2.2`},devDependencies:{"@hono/node-server":`^2.1.1`,hono:`^4.13.3`,tinyglobby:`^0.2.17`}},Tr=`import { type RequestContext, store } from "{{ createImport 'libCore' 'ssr' }}";
6306
+ import { renderWrapper } from "{{ createImport 'libEntry' 'server' }}";
6307
+
6308
+ export { default as backendApp } from "virtual:kosmo/backend-app";
6309
+
6310
+ export { default as ssrApp } from "{{ createImport 'entry' 'server' }}";
6311
+
6312
+ /**
6313
+ * Wrap a render call, making the given context visible to every component
6314
+ * */
6315
+ export const withSsrContext = <T>(
6316
+ context: RequestContext,
6317
+ render: () => T,
6318
+ ): T => {
6319
+ return store.run(context, () => renderWrapper(context, render));
6320
+ };
6321
+
6322
+ export const errorProvider = () => {
6323
+ return store.getStore()?.error;
5551
6324
  };
5552
- `,ir=`export const routeMap = [
6325
+ `,Er=`export const routeMap = [
5553
6326
  {{#each pageRoutes}}
5554
6327
  { pathPattern: "{{honoPattern}}", renderMode: "{{renderMode}}" },
5555
6328
  {{/each}}
5556
6329
  ];
5557
- `,ar=`import { access, chmod, constants, readFile, unlink } from "node:fs/promises";
6330
+ `,Dr=`import { access, chmod, constants, readFile, unlink } from "node:fs/promises";
5558
6331
  import {
5559
6332
  createServer,
5560
6333
  type IncomingMessage,
@@ -5564,20 +6337,25 @@ import { extname, join, resolve } from "node:path";
5564
6337
  import { fileURLToPath } from "node:url";
5565
6338
  import { parseArgs, styleText } from "node:util";
5566
6339
 
5567
- import { createAdaptorServer, getRequestListener } from "@hono/node-server";
6340
+ import { getRequestListener } from "@hono/node-server";
5568
6341
  import { type Context, Hono } from "hono";
5569
6342
  import { HTTPException } from "hono/http-exception";
5570
6343
  import { stream } from "hono/streaming";
5571
6344
  import { glob } from "tinyglobby";
5572
6345
 
5573
- import type { FetchApp, NodeApp, SSRSetup } from "@kosmojs/core";
5574
-
5575
- import { isFetchApp, redirectCodes, ssrOrigin } from "./@ssr/base";
6346
+ import {
6347
+ type FetchApp,
6348
+ MIME_TYPES,
6349
+ type NodeApp,
6350
+ type SSRSetup,
6351
+ } from "@kosmojs/core";
5576
6352
 
5577
6353
  import { routeMap } from "{{ createImport 'lib' '@ssr/routes' }}";
5578
6354
  import { apiBase, base } from "{{ createImport 'libCore' }}";
6355
+ import { redirectCodes, ssrOrigin } from "{{ createImport 'libCore' 'ssr' }}";
5579
6356
 
5580
6357
  const ROOT = import.meta.dirname;
6358
+ const HEAD_CLOSE_PATTERN = /<\\/head\\s*>/i;
5581
6359
 
5582
6360
  type AssetInfo = {
5583
6361
  file: string;
@@ -5587,9 +6365,13 @@ type AssetInfo = {
5587
6365
  contentType: string;
5588
6366
  // Cached size to set Content-Length without re-measuring the buffer.
5589
6367
  size: number;
6368
+ // Cache-Control header - hashed assets are immutable, public/ files are not.
6369
+ cacheControl: string;
5590
6370
  };
5591
6371
 
5592
- export const createApp = async () => {
6372
+ export const createApp = async (
6373
+ errorHandler?: (error: Error & { url: string }) => void | undefined,
6374
+ ) => {
5593
6375
  // Import the SSR entry produced by Vite's ssr build.
5594
6376
  const {
5595
6377
  ssrApp,
@@ -5604,8 +6386,7 @@ export const createApp = async () => {
5604
6386
  errorProvider: () => Error | undefined;
5605
6387
  } = await import(\`\${ROOT}/app.js\`);
5606
6388
 
5607
- // Read the client index.html that includes <!--app-head--> and <!--app-html-->
5608
- // placeholders used for SSR injection.
6389
+ // Read the client index.html that includes <!--app-html--> placeholder
5609
6390
  const template = await readFile(\`\${ROOT}/index.html\`, "utf8");
5610
6391
 
5611
6392
  // Load the Vite manifest
@@ -5613,8 +6394,9 @@ export const createApp = async () => {
5613
6394
  with: { type: "json" },
5614
6395
  }).then((e) => e.default);
5615
6396
 
5616
- const { renderToString, renderToStream } = ssrApp;
5617
- const [htmlStart, htmlEnd] = template.split("<!--app-html-->");
6397
+ const { renderToString, renderToStream, onError } = ssrApp;
6398
+
6399
+ const [htmlStart, htmlEnd = ""] = template.split(/<!--\\s*app-html\\s*-->/);
5618
6400
 
5619
6401
  const assets = await loadAssets(ROOT);
5620
6402
 
@@ -5660,6 +6442,29 @@ export const createApp = async () => {
5660
6442
  };
5661
6443
  };
5662
6444
 
6445
+ const handleError = (url: string, error: Error, fallback: Function) => {
6446
+ // assign, not spread: message and stack are non-enumerable on Error,
6447
+ // a spread silently drops them
6448
+ Object.assign(error, { url });
6449
+ if (onError) {
6450
+ onError(error as never);
6451
+ } else {
6452
+ fallback();
6453
+ }
6454
+ errorHandler?.(error as never);
6455
+ };
6456
+
6457
+ const injectHead = (html: string, head: string) => {
6458
+ const error = "WARN: missing </head> - required for SSR head injection";
6459
+ if (HEAD_CLOSE_PATTERN.test(html)) {
6460
+ return html.replace(HEAD_CLOSE_PATTERN, (headEnd) => {
6461
+ return [head, headEnd].join("\\n");
6462
+ });
6463
+ }
6464
+ console.error(error);
6465
+ return \`\${html}\\n<script>console.error("\${error}")<\/script>\`;
6466
+ };
6467
+
5663
6468
  const renderPage = async (url: URL, ctx: Context) => {
5664
6469
  const {
5665
6470
  head = "",
@@ -5690,27 +6495,51 @@ export const createApp = async () => {
5690
6495
  );
5691
6496
 
5692
6497
  if (error) {
5693
- console.error("WARN: SSR failed, fallback to CSR");
5694
- console.error(error);
5695
- console.error();
6498
+ const errorMessage = "WARN: SSR failed, fallback to CSR";
6499
+ handleError(ctx.req.url, error as never, () => {
6500
+ console.error(errorMessage);
6501
+ console.error(error);
6502
+ console.error();
6503
+ });
5696
6504
  return [
5697
- htmlStart.replace(
5698
- "<!--app-head-->",
5699
- \`<script>console.error("WARN: SSR failed, fallback to CSR")<\/script>\`,
6505
+ injectHead(
6506
+ htmlStart,
6507
+ \`<script>console.error("\${errorMessage}")<\/script>\`,
5700
6508
  ),
5701
6509
  htmlEnd,
5702
6510
  ].join("");
5703
6511
  }
5704
6512
 
5705
- return [
5706
- htmlStart.replace("<!--app-head-->", head),
5707
- html ?? "",
5708
- htmlEnd,
5709
- ].join("");
6513
+ return [injectHead(htmlStart, head), html ?? "", htmlEnd].join("");
5710
6514
  };
5711
6515
 
5712
6516
  const app = new Hono({ strict: false });
5713
6517
 
6518
+ // Static files win over routes, as they do in vite dev and behind a reverse proxy.
6519
+ // This covers hashed assets/ (JS, CSS, images, fonts, .map siblings) and public/ files.
6520
+ app.use(async (ctx, next) => {
6521
+ if (!["GET", "HEAD"].includes(ctx.req.method)) {
6522
+ return next();
6523
+ }
6524
+
6525
+ const asset = assets.get(ctx.req.path);
6526
+
6527
+ if (!asset) {
6528
+ return next();
6529
+ }
6530
+
6531
+ return new Response(
6532
+ ctx.req.method === "HEAD" ? null : (asset.buffer as never),
6533
+ {
6534
+ headers: {
6535
+ "Content-Type": asset.contentType,
6536
+ "Content-Length": String(asset.size),
6537
+ "Cache-Control": asset.cacheControl,
6538
+ },
6539
+ },
6540
+ );
6541
+ });
6542
+
5714
6543
  for (const { pathPattern, renderMode } of routeMap) {
5715
6544
  app.get(join(base, pathPattern), async (ctx) => {
5716
6545
  try {
@@ -5724,16 +6553,37 @@ export const createApp = async () => {
5724
6553
  if (renderMode === "stream" && typeof renderToStream === "function") {
5725
6554
  ctx.header("Content-Type", "text/html");
5726
6555
  return stream(ctx, async (stream) => {
5727
- const { head = "", html } = await withSsrContext(
5728
- {
5729
- headers: Object.fromEntries(ctx.req.raw.headers),
5730
- url: ctx.req.url,
5731
- },
5732
- () => renderToStream(url, ssrOptions(), stream as never),
5733
- );
5734
- await stream.write(htmlStart.replace("<!--app-head-->", head));
5735
- await stream.pipe(html);
5736
- await stream.write(htmlEnd);
6556
+ let error: Error | undefined;
6557
+
6558
+ /**
6559
+ * Stream failures surface here, not in a catch upstream:
6560
+ * on solid and vue pipe rejects; react shell errors reject the render promise itself.
6561
+ * The shell may already be on the wire -
6562
+ * reporting is all that is left to do, the response cannot be replaced.
6563
+ * */
6564
+ try {
6565
+ const { head = "", html } = await withSsrContext(
6566
+ {
6567
+ headers: Object.fromEntries(ctx.req.raw.headers),
6568
+ url: ctx.req.url,
6569
+ },
6570
+ () => renderToStream(url, ssrOptions(), stream as never),
6571
+ );
6572
+ await stream.write(injectHead(htmlStart, head));
6573
+ await stream.pipe(html);
6574
+ error = errorProvider();
6575
+ await stream.write(htmlEnd);
6576
+ } catch (e: any) {
6577
+ error = e;
6578
+ }
6579
+
6580
+ if (error) {
6581
+ handleError(ctx.req.url, error, () => {
6582
+ console.error("ERROR: SSR stream render failed");
6583
+ console.error(error);
6584
+ console.error();
6585
+ });
6586
+ }
5737
6587
  });
5738
6588
  }
5739
6589
 
@@ -5758,21 +6608,6 @@ export const createApp = async () => {
5758
6608
  }
5759
6609
 
5760
6610
  app.get("/*", async (ctx) => {
5761
- const { path } = ctx.req;
5762
-
5763
- // If incoming request path matches something cached at startup, serve it directly.
5764
- // This covers JS, CSS, images, fonts, etc., including their .map siblings.
5765
- const asset = assets.get(path);
5766
-
5767
- if (asset) {
5768
- return new Response(asset.buffer as never, {
5769
- headers: {
5770
- "Content-Type": asset.contentType,
5771
- "Content-Length": String(asset.size),
5772
- },
5773
- });
5774
- }
5775
-
5776
6611
  // render 404 page
5777
6612
  if (typeof renderToString === "function") {
5778
6613
  const url = new URL(ctx.req.url);
@@ -5791,47 +6626,47 @@ export const createApp = async () => {
5791
6626
  * Build an in-memory asset graph, loading asset content into memory.
5792
6627
  * The asset graph always includes every built asset URL so the SSR server
5793
6628
  * can correctly recognize static asset requests.
6629
+ *
6630
+ * Two roots, each directory being its own allowlist - nothing else in the bundle root is served:
6631
+ * - assets/ - emitted by vite with content hashes, served at base/assets/, cacheable forever
6632
+ * - public/ - copied verbatim from the folder's public dir, served at base/, names are stable so clients must revalidate
5794
6633
  * */
5795
- const loadAssets = async (
5796
- root: string,
5797
- patterns: string | Array<string> = "**",
5798
- ) => {
5799
- const mimeTypeMap: Record<string, string> = {
5800
- ".js": "application/javascript",
5801
- ".mjs": "application/javascript",
5802
- ".css": "text/css",
5803
- ".json": "application/json",
5804
- ".png": "image/png",
5805
- ".apng": "image/png",
5806
- ".jpg": "image/jpeg",
5807
- ".jpeg": "image/jpeg",
5808
- ".gif": "image/gif",
5809
- ".svg": "image/svg+xml",
5810
- ".ico": "image/x-icon",
5811
- ".woff": "font/woff",
5812
- ".woff2": "font/woff2",
5813
- ".ttf": "font/ttf",
5814
- ".webp": "image/webp",
5815
- };
5816
-
6634
+ const loadAssets = async (root: string) => {
5817
6635
  // Resolve HTTP Content-Type from the asset's file extension.
5818
6636
  const contentTypeResolver = (filePath: string) => {
5819
6637
  const ext = extname(filePath).toLowerCase();
5820
- return mimeTypeMap[ext] || "application/octet-stream";
6638
+ return MIME_TYPES[ext] || "application/octet-stream";
5821
6639
  };
5822
6640
 
5823
6641
  // Map from URL path (as used in requests) to asset metadata.
5824
6642
  const assetCache = new Map<string, AssetInfo>();
5825
6643
 
5826
- const folder = "assets";
5827
- const cwd = resolve(root, folder);
6644
+ const roots = [
6645
+ {
6646
+ folder: "assets",
6647
+ prefix: join(base, "assets"),
6648
+ cacheControl: "public, max-age=31536000, immutable",
6649
+ },
6650
+ {
6651
+ folder: "public",
6652
+ prefix: base,
6653
+ cacheControl: "no-cache",
6654
+ },
6655
+ ];
6656
+
6657
+ for (const { folder, prefix, cacheControl } of roots) {
6658
+ const cwd = resolve(root, folder);
6659
+
6660
+ const readable = await access(cwd, constants.F_OK).then(
6661
+ () => true,
6662
+ () => false,
6663
+ );
6664
+
6665
+ if (!readable) {
6666
+ continue;
6667
+ }
5828
6668
 
5829
- if (
5830
- await access(cwd, constants.R_OK)
5831
- .then(() => true)
5832
- .catch(() => false)
5833
- ) {
5834
- const files = await glob(patterns, {
6669
+ const files = await glob("**", {
5835
6670
  cwd,
5836
6671
  onlyFiles: true,
5837
6672
  absolute: false,
@@ -5839,11 +6674,12 @@ const loadAssets = async (
5839
6674
 
5840
6675
  for (const file of files) {
5841
6676
  const buffer = new Uint8Array(await readFile(resolve(cwd, file)));
5842
- assetCache.set(join(base, folder, file), {
6677
+ assetCache.set(join(prefix, file), {
5843
6678
  file,
5844
6679
  buffer,
5845
6680
  contentType: contentTypeResolver(file),
5846
- size: buffer?.length,
6681
+ size: buffer.length,
6682
+ cacheControl,
5847
6683
  });
5848
6684
  }
5849
6685
  }
@@ -5854,11 +6690,40 @@ const loadAssets = async (
5854
6690
  type NodeListener = (req: IncomingMessage, res: ServerResponse) => void;
5855
6691
 
5856
6692
  const createNodeListener = (app: FetchApp | NodeApp): NodeListener => {
5857
- return isFetchApp(app)
6693
+ return typeof (app as FetchApp).fetch === "function"
5858
6694
  ? getRequestListener((app as FetchApp).fetch)
5859
6695
  : (app as NodeApp).callback();
5860
6696
  };
5861
6697
 
6698
+ /**
6699
+ * The folder's complete request surface as a single node:http listener:
6700
+ * API requests under \`apiBase\` go to the bundled backend, everything else to the SSR app.
6701
+ * \`startServer\` binds it to a port/socket; \`dist/run.js\` mounts it next to other folders.
6702
+ * */
6703
+ export const createListener = async (): Promise<NodeListener> => {
6704
+ const {
6705
+ backendApp,
6706
+ }: {
6707
+ backendApp: FetchApp | NodeApp;
6708
+ } = await import(\`\${ROOT}/app.js\`);
6709
+
6710
+ const ssrApp = await createApp();
6711
+ const apiPrefix = join(base, apiBase);
6712
+
6713
+ const ssrListener = createNodeListener(ssrApp as never);
6714
+
6715
+ const apiListener = backendApp
6716
+ ? createNodeListener(backendApp as never)
6717
+ : async () => {};
6718
+
6719
+ return (req, res) => {
6720
+ const { pathname } = new URL(req.url ?? "/", ssrOrigin);
6721
+ return pathname === apiPrefix || pathname.startsWith(\`\${apiPrefix}/\`)
6722
+ ? apiListener(req, res)
6723
+ : ssrListener(req, res);
6724
+ };
6725
+ };
6726
+
5862
6727
  export const startServer = async ({
5863
6728
  sock,
5864
6729
  port,
@@ -5870,12 +6735,6 @@ export const startServer = async ({
5870
6735
  throw new Error("Please provide either -p/--port or -s/--sock");
5871
6736
  }
5872
6737
 
5873
- const {
5874
- apiApp,
5875
- }: {
5876
- apiApp: FetchApp | NodeApp;
5877
- } = await import(\`\${ROOT}/app.js\`);
5878
-
5879
6738
  if (sock) {
5880
6739
  // Clean up any stale socket file before binding.
5881
6740
  await unlink(sock).catch((error) => {
@@ -5892,23 +6751,7 @@ export const startServer = async ({
5892
6751
  sock ? \`sock: \${sock}\` : \`port: \${port}\`,
5893
6752
  );
5894
6753
 
5895
- const ssrApp = await createApp();
5896
- const apiPrefix = join(base, apiBase);
5897
-
5898
- const ssrListener = createNodeListener(ssrApp as never);
5899
-
5900
- const apiListener = apiApp
5901
- ? createNodeListener(apiApp as never)
5902
- : async () => {};
5903
-
5904
- const gatewayListener: NodeListener = (req, res) => {
5905
- const { pathname } = new URL(req.url ?? "/", ssrOrigin);
5906
- return pathname === apiPrefix || pathname.startsWith(\`\${apiPrefix}/\`)
5907
- ? apiListener(req, res)
5908
- : ssrListener(req, res);
5909
- };
5910
-
5911
- const server = createServer(gatewayListener);
6754
+ const server = createServer(await createListener());
5912
6755
 
5913
6756
  server.listen(sock || port, async () => {
5914
6757
  if (sock) {
@@ -5922,24 +6765,6 @@ export const startServer = async ({
5922
6765
  return server;
5923
6766
  };
5924
6767
 
5925
- export const createDisposableServer = async (
5926
- callback: (port: number) => Promise<void>,
5927
- ) => {
5928
- const app = await createApp();
5929
- const server = createAdaptorServer(app).listen(0); // OS picks a free port
5930
- const address = server.address();
5931
-
5932
- if (!address || typeof address === "string") {
5933
- throw new Error("SSR: Failed starting disposable server on a free port");
5934
- }
5935
-
5936
- try {
5937
- await callback(address.port);
5938
- } finally {
5939
- server.close();
5940
- }
5941
- };
5942
-
5943
6768
  const isMain = fileURLToPath(import.meta.url) === resolve(process.argv[1]);
5944
6769
 
5945
6770
  if (isMain) {
@@ -5970,14 +6795,14 @@ if (isMain) {
5970
6795
  process.exit(1);
5971
6796
  }
5972
6797
  }
5973
- `,or=`string`,sr=g((e,r)=>{let{generators:i=[],refineTypeName:a,...o}=e.config,{createPath:c,createImportHelpers:l}=y(e),{renderToFile:u}=x({helpers:{...l({origin:`lib`})}});return{async build(e){let t=r?.renderMode?typeof r.renderMode==`string`?()=>r.renderMode:s(r?.renderMode,`string`):()=>or,n={renderMode:JSON.stringify(r?.renderMode||null),pageRoutes:e.flatMap(e=>e.kind===`pageRoute`?[{...e.entry,renderMode:t(e.entry.name)}]:[]).sort(S),apiGenerator:i.some(e=>e.meta.slot===`backend`)};for(let[e,t]of[[`ssr.ts`,ar],[`@ssr/api.ts`,er],[`@ssr/__kosmo_ssr_bundle.ts`,nr],[`@ssr/base.ts`,tr],[`@ssr/fetch.ts`,rr],[`@ssr/routes.ts`,ir]])await u(c.lib(e),t,n)},async postBuild(){let r=c.distDir(`ssr`),a=[C.tsconfigPaths(e),C.nodePrefix()];for(let t of i)await t.factory(e).ssrBuild?.();await E(_(o,...i.map(({factory:t})=>t(e).config?.({kind:`client`,command:`build`})),{root:c.src(),plugins:a,define:{KOSMO_PRODUCTION_BUILD:`true`},build:{ssr:c.lib(`@ssr/__kosmo_ssr_bundle`),ssrEmitAssets:!0,sourcemap:!0,emptyOutDir:!0,minify:!1,rolldownOptions:{output:{dir:r,entryFileNames:`app.js`,format:`esm`}}}})),await E({root:c.lib(),configFile:!1,appType:`custom`,plugins:a,resolve:{conditions:[`node`]},build:{ssr:c.lib(`ssr.ts`),target:`esnext`,sourcemap:!0,emptyOutDir:!0,rolldownOptions:{output:{dir:t(r,`server`),entryFileNames:`server.js`,format:`esm`}}}}),await O(n(r,`../client`),r,{recursive:!0});for(let e of[`server.js`,`server.js.map`])await O(`${r}/server/${e}`,`${r}/${e}`);await k(`${r}/server`,{recursive:!0,force:!0})}}}),cr=h({meta:{name:`SSR`,slot:`ssr`},dependencies:{tinyglobby:q.devDependencies.tinyglobby,hono:q.devDependencies.hono,"@hono/node-server":q.devDependencies[`@hono/node-server`],"light-my-request":q.devDependencies[`light-my-request`]},factory:sr}),J={type:`module`,private:!0,name:`@kosmojs/svelte-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"@sveltejs/vite-plugin-svelte":`^7.3.0`,vite:`^8.2.2`},devDependencies:{"@tanstack/svelte-query":`^6.1.41`,"path-to-regexp":`^8.4.2`,svelte:`^5.56.10`}},lr=()=>{let e=[`🎉 Well done! You just created a new Svelte page.`,`🚀 Success! A fresh Svelte page is ready to roll.`,`🌟 Nice work! Another Svelte page added to your app.`,`🧩 All set! A new Svelte page has been scaffolded.`,`🔧 Scaffold complete! Your new Svelte page is in place.`,`✅ Built! Your Svelte page is scaffolded and ready.`,`✨ Fantastic! Your new Svelte page is good to go.`,`🎯 Nailed it! A brand new Svelte page just landed.`,`💫 Awesome! Another Svelte page joins the party.`,`⚡ Lightning fast! A new Svelte page created successfully.`];return e[Math.floor(Math.random()*e.length)]},Y=`<script lang="ts">
6798
+ `,Or=`string`,kr=v((e,n)=>{let{createPath:i,createImportHelpers:a}=S(e),{generators:o,refineTypeName:s,...l}=e.config,{renderToFile:u}=w({helpers:{...a({origin:`lib`})}});return{async build(e){let t=n?.renderMode?typeof n.renderMode==`string`?()=>n.renderMode:c(n?.renderMode,`string`):()=>Or,r={renderMode:JSON.stringify(n?.renderMode||null),pageRoutes:e.flatMap(e=>e.kind===`pageRoute`?[{...e.entry,renderMode:t(e.entry.name)}]:[]).sort(E),apiGenerator:o.some(e=>e.meta.slot===`backend`)};for(let[e,t]of[[`ssr.ts`,Dr],[`@ssr/__kosmo_ssr_bundle.ts`,Tr],[`@ssr/routes.ts`,Er]])await u(i.lib(e),t,r)},async postBuild(){if(!o.some(e=>e.meta.slot===`frontend`))return;let n=i.distDir(`ssr`),a=[O.tsconfigPaths(e),O.nodePrefix(),O.virtualModules(d(e,o),{kind:`ssr`,command:`build`})];await M(y(l,...o.map(({factory:t})=>t(e).config?.({kind:`client`,command:`build`})),{root:i.src(),plugins:a,build:{ssr:i.lib(`@ssr/__kosmo_ssr_bundle`),ssrEmitAssets:!0,sourcemap:!0,emptyOutDir:!0,minify:!1,copyPublicDir:!1,rolldownOptions:{output:{dir:n,entryFileNames:`app.js`,format:`esm`}}}})),await M({root:i.lib(),configFile:!1,appType:`custom`,plugins:a,resolve:{conditions:[`node`]},build:{ssr:i.lib(`ssr.ts`),target:`esnext`,sourcemap:!0,emptyOutDir:!0,rolldownOptions:{output:{dir:t(n,`server`),entryFileNames:`server.js`,format:`esm`}}}});for(let e of[`.vite`,`assets`,`index.html`])await P(r(n,`../client`,e),t(n,e),{recursive:!0});if(![!1,``].includes(l.publicDir)){let e=r(i.src(),l.publicDir||`public`);await x(e)&&await P(e,t(n,`public`),{recursive:!0})}for(let e of[`server.js`,`server.js.map`])await P(`${n}/server/${e}`,`${n}/${e}`);await F(`${n}/server`,{recursive:!0,force:!0})}}}),Ar=_({meta:{name:`SSR`,slot:`ssr`},dependencies:{tinyglobby:q.devDependencies.tinyglobby,hono:q.devDependencies.hono,"@hono/node-server":q.devDependencies[`@hono/node-server`]},factory:kr}),J={type:`module`,private:!0,name:`@kosmojs/svelte-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"@sveltejs/vite-plugin-svelte":`^7.3.0`,vite:`^8.2.2`},devDependencies:{"@tanstack/svelte-query":`^6.1.41`,"path-to-regexp":`^8.4.2`,svelte:`^5.56.10`}},jr=()=>{let e=[`🎉 Well done! You just created a new Svelte page.`,`🚀 Success! A fresh Svelte page is ready to roll.`,`🌟 Nice work! Another Svelte page added to your app.`,`🧩 All set! A new Svelte page has been scaffolded.`,`🔧 Scaffold complete! Your new Svelte page is in place.`,`✅ Built! Your Svelte page is scaffolded and ready.`,`✨ Fantastic! Your new Svelte page is good to go.`,`🎯 Nailed it! A brand new Svelte page just landed.`,`💫 Awesome! Another Svelte page joins the party.`,`⚡ Lightning fast! A new Svelte page created successfully.`];return e[Math.floor(Math.random()*e.length)]},Y=`<script lang="ts">
5974
6799
  import type { Snippet } from "svelte";
5975
6800
 
5976
6801
  let { children }: { children: Snippet } = $props();
5977
6802
  <\/script>
5978
6803
 
5979
6804
  {@render children()}
5980
- `,ur=`<script lang="ts">
6805
+ `,Mr=`<script lang="ts">
5981
6806
  import { type QueryClient, QueryClientProvider } from "@tanstack/svelte-query";
5982
6807
  import type { Snippet } from "svelte";
5983
6808
 
@@ -5994,7 +6819,7 @@ if (isMain) {
5994
6819
  <QueryClientProvider client={queryClient}>
5995
6820
  {@render children()}
5996
6821
  </QueryClientProvider>
5997
- `,dr=`import { hydrate as hydrateOrig, mount as mountOrig } from "svelte";
6822
+ `,Nr=`import { hydrate as hydrateOrig, mount as mountOrig } from "svelte";
5998
6823
  import type { RouterFactoryReturn } from "@kosmojs/core";
5999
6824
  import { clientRenderFactory } from "@kosmojs/core/generators";
6000
6825
 
@@ -6035,7 +6860,7 @@ export const mount = async (
6035
6860
  }
6036
6861
 
6037
6862
  export default clientRenderFactory();
6038
- `,fr=`import { render as renderOrig } from "svelte/server";
6863
+ `,Pr=`import { render as renderOrig } from "svelte/server";
6039
6864
 
6040
6865
  import type {
6041
6866
  RenderToStringWrapper,
@@ -6101,12 +6926,18 @@ export const renderToString: RenderToStringWrapper<
6101
6926
  // svelte/server exposes only render() - no web-stream renderer -
6102
6927
  // so this folder is string-only SSR.
6103
6928
  export default serverRenderFactory<false>();
6104
- `,pr=`declare module "*.svelte" {
6929
+ `,Fr=`declare module "*.svelte" {
6105
6930
  import type { Component } from "svelte";
6106
6931
  const component: Component;
6107
6932
  export default component;
6108
6933
  }
6109
- `,mr=`<script lang="ts">
6934
+
6935
+ declare module "virtual:kosmo/tsq-client" {
6936
+ import type { QueryClient, QueryClientConfig } from "@tanstack/svelte-query";
6937
+ export const createQueryClient: (options?: QueryClientConfig) => QueryClient;
6938
+ export const getQueryClient: () => QueryClient;
6939
+ }
6940
+ `,Ir=`<script lang="ts">
6110
6941
  /**
6111
6942
  * Folds [app, ...layouts] around the page component.
6112
6943
  *
@@ -6140,7 +6971,7 @@ export default serverRenderFactory<false>();
6140
6971
  {/snippet}
6141
6972
 
6142
6973
  {@render layer(0)}
6143
- `,hr=`<script lang="ts">
6974
+ `,Lr=`<script lang="ts">
6144
6975
  import styles from "./styles.module.css";
6145
6976
 
6146
6977
  let { headline }: { headline?: string } = $props();
@@ -6172,7 +7003,7 @@ export default serverRenderFactory<false>();
6172
7003
  </div>
6173
7004
  </div>
6174
7005
  </div>
6175
- `,gr=`<script lang="ts">
7006
+ `,Rr=`<script lang="ts">
6176
7007
  import styles from "./styles.module.css";
6177
7008
 
6178
7009
  let {
@@ -6217,7 +7048,7 @@ export default serverRenderFactory<false>();
6217
7048
  </div>
6218
7049
  </div>
6219
7050
  </div>
6220
- `,_r=`* {
7051
+ `,zr=`* {
6221
7052
  margin: 0;
6222
7053
  padding: 0;
6223
7054
  box-sizing: border-box;
@@ -6352,7 +7183,7 @@ export default serverRenderFactory<false>();
6352
7183
  align-items: center;
6353
7184
  gap: 0.25rem;
6354
7185
  }
6355
- `,vr=`<script lang="ts">
7186
+ `,Br=`<script lang="ts">
6356
7187
  import styles from "./styles.module.css";
6357
7188
  <\/script>
6358
7189
 
@@ -6410,7 +7241,7 @@ export default serverRenderFactory<false>();
6410
7241
  </div>
6411
7242
  </div>
6412
7243
  </div>
6413
- `,yr=`export type ParamsMap = {
7244
+ `,Vr=`export type ParamsMap = {
6414
7245
  {{#each pageRoutes}}"{{name}}": {{serializeParamsLiteral .}};
6415
7246
  {{/each}}
6416
7247
  };
@@ -6419,26 +7250,27 @@ export const paramNames = {
6419
7250
  {{#each pageRoutes}}"{{name}}": [ {{#each params.schema}}"{{name}}", {{/each}}],
6420
7251
  {{/each}}
6421
7252
  } as const;
6422
- `,br=`import { QueryClient, type QueryClientConfig } from "@tanstack/svelte-query";
7253
+ `,Hr=`export * from "virtual:kosmo/tsq-client";
7254
+ `,Ur=`import { QueryClient } from "@tanstack/svelte-query";
6423
7255
 
6424
- let client: QueryClient | undefined;
7256
+ let client = undefined;
6425
7257
 
6426
- export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
7258
+ export const createQueryClient = (options) => {
6427
7259
  client = new QueryClient(options);
6428
7260
  return client;
6429
7261
  };
6430
7262
 
6431
- export const getQueryClient = (): QueryClient => {
7263
+ export const getQueryClient = () => {
6432
7264
  if (!client) {
6433
7265
  client = new QueryClient();
6434
7266
  }
6435
7267
  return client;
6436
7268
  };
6437
- `,xr=`import { QueryClient, type QueryClientConfig } from "@tanstack/svelte-query";
7269
+ `,Wr=`import { QueryClient } from "@tanstack/svelte-query";
6438
7270
 
6439
- import { store } from "{{ createImport 'lib' '@ssr/base' }}";
7271
+ import { store } from "{{ createImport 'libCore' 'ssr' }}";
6440
7272
 
6441
- export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
7273
+ export const createQueryClient = (options) => {
6442
7274
  const client = new QueryClient(options);
6443
7275
  const ctx = store?.getStore();
6444
7276
  if (ctx) {
@@ -6447,7 +7279,7 @@ export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
6447
7279
  return client;
6448
7280
  };
6449
7281
 
6450
- export const getQueryClient = (): QueryClient => {
7282
+ export const getQueryClient = () => {
6451
7283
  const ctx = store?.getStore();
6452
7284
  if (!ctx) {
6453
7285
  throw new Error("getQueryClient(): called outside an SSR request scope");
@@ -6455,9 +7287,9 @@ export const getQueryClient = (): QueryClient => {
6455
7287
  if (!ctx.tsqClient) {
6456
7288
  ctx.tsqClient = new QueryClient();
6457
7289
  }
6458
- return ctx.tsqClient as QueryClient;
7290
+ return ctx.tsqClient;
6459
7291
  };
6460
- `,Sr=`import type { RouterFactoryReturn } from "@kosmojs/core";
7292
+ `,Gr=`import type { RouterFactoryReturn } from "@kosmojs/core";
6461
7293
  import { createRouterFactory } from "@kosmojs/core/generators";
6462
7294
 
6463
7295
  import Layouts from "./Layouts.svelte";
@@ -6497,7 +7329,7 @@ export default createRouterFactory<
6497
7329
  Promise<RouteComponent>,
6498
7330
  { server: { route: Route } }
6499
7331
  >();
6500
- `,Cr=`import { match, pathToRegexp } from "path-to-regexp";
7332
+ `,Kr=`import { match, pathToRegexp } from "path-to-regexp";
6501
7333
  import { type Component, createContext } from "svelte";
6502
7334
 
6503
7335
  import { parseSearchParams } from "@kosmojs/core";
@@ -6517,7 +7349,6 @@ export type AnyComponent = Component<any, any, any>;
6517
7349
 
6518
7350
  export type RawRoute = {
6519
7351
  name: string;
6520
- pathSegments: number | undefined;
6521
7352
  regexp: RegExp;
6522
7353
  extractParams: (path: string) => Route["params"];
6523
7354
  loader: () => Promise<RouteModule>;
@@ -6608,23 +7439,19 @@ export const createRouter = (
6608
7439
  return {
6609
7440
  async resolve(url: URL = new URL(window.location.href)) {
6610
7441
  const searchParams = parseSearchParams(url);
6611
- const urlSegments = url.pathname.split("/").filter(Boolean).length;
6612
-
6613
- // 1: use lightweight \`RegExp.test()\` on linear scan - no capture allocation
6614
- const matchedRoutes = routes.filter(({ regexp }) => {
6615
- return regexp.test(url.pathname);
6616
- });
6617
7442
 
7443
+ // The routes array is generated pre-sorted by specificity
7444
+ // (static beats required beats optional beats splat, token by token),
7445
+ // the same ordering the SSR server registers routes in -
7446
+ // so the first pattern that matches IS the most specific one, and CSR resolution stays consistent with SSR.
7447
+ // A route with optional parameters matches a range of segment counts,
7448
+ // which is why no segment-count heuristic can disambiguate here.
7449
+ // Lightweight \`RegExp.test()\` on linear scan - no capture allocation.
6618
7450
  const matchedRoute =
6619
- matchedRoutes.length > 1
6620
- ? matchedRoutes.find(({ pathSegments }) => {
6621
- return pathSegments === undefined || pathSegments === urlSegments;
6622
- }) || catchallRoute
6623
- : matchedRoutes.length === 1
6624
- ? matchedRoutes[0]
6625
- : catchallRoute;
6626
-
6627
- // 2: capture params only on matched route
7451
+ routes.find(({ regexp }) => {
7452
+ return regexp.test(url.pathname);
7453
+ }) || catchallRoute;
7454
+
6628
7455
  const params = matchedRoute
6629
7456
  ? matchedRoute.extractParams(url.pathname)
6630
7457
  : {};
@@ -6711,11 +7538,6 @@ export const createRoute = (
6711
7538
  return {
6712
7539
  name,
6713
7540
  regexp,
6714
- // count segments of the same base-joined path the regexp matches against;
6715
- // resolve() compares this against the full url pathname's segment count
6716
- pathSegments: name.includes("...")
6717
- ? undefined
6718
- : path.split("/").filter(Boolean).length,
6719
7541
  extractParams: (path) => {
6720
7542
  const match = matcher(path);
6721
7543
  return match ? match.params : {};
@@ -6724,7 +7546,7 @@ export const createRoute = (
6724
7546
  layouts,
6725
7547
  };
6726
7548
  };
6727
- `,wr=`import { getRouteContext } from "./svelte";
7549
+ `,qr=`import { getRouteContext } from "./svelte";
6728
7550
 
6729
7551
  import type { ParamsMap, paramNames } from "{{ createImport 'lib' 'params' }}";
6730
7552
 
@@ -6764,7 +7586,7 @@ export const useLoaderData = <T>(key?: string): T | undefined => {
6764
7586
  const route = useRoute();
6765
7587
  return route.loaderData?.[key || route.name] as T;
6766
7588
  };
6767
- `,Tr=`<script lang="ts">
7589
+ `,Jr=`<script lang="ts">
6768
7590
  import { AppProvider } from "{{ createImport 'lib' 'app' }}";
6769
7591
  import type { Snippet } from "svelte";
6770
7592
 
@@ -6774,7 +7596,7 @@ export const useLoaderData = <T>(key?: string): T | undefined => {
6774
7596
  <AppProvider>
6775
7597
  {@render children()}
6776
7598
  </AppProvider>
6777
- `,Er=`<script lang="ts">
7599
+ `,Yr=`<script lang="ts">
6778
7600
  import type { Snippet } from "svelte";
6779
7601
  import type { HTMLAnchorAttributes } from "svelte/elements";
6780
7602
 
@@ -6798,7 +7620,7 @@ export const useLoaderData = <T>(key?: string): T | undefined => {
6798
7620
  <\/script>
6799
7621
 
6800
7622
  <a {href} {...rest}>{@render children?.()}</a>
6801
- `,Dr=`import renderFactory, {
7623
+ `,Xr=`import renderFactory, {
6802
7624
  createRoutes,
6803
7625
  hydrate,
6804
7626
  mount,
@@ -6825,7 +7647,7 @@ if (root) {
6825
7647
  } else {
6826
7648
  console.error("❌ Root element not found!");
6827
7649
  }
6828
- `,Or=`import renderFactory, {
7650
+ `,Zr=`import renderFactory, {
6829
7651
  createRoutes,
6830
7652
  renderToString,
6831
7653
  // no renderToStream on Svelte folders
@@ -6846,31 +7668,19 @@ export default renderFactory(() => {
6846
7668
  },
6847
7669
  };
6848
7670
  });
6849
- `,kr=`<!doctype html>
6850
- <html lang="en">
6851
- <head>
6852
- <meta charset="UTF-8" />
6853
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6854
- <!--app-head-->
6855
- </head>
6856
- <body>
6857
- <div id="app"><!--app-html--></div>
6858
- <script type="module" src="/{{ entryDir }}/client.ts"><\/script>
6859
- </body>
6860
- </html>
6861
- `,Ar=`<script lang="ts">
7671
+ `,Qr=`<script lang="ts">
6862
7672
  import PageSample from "{{ createImport 'lib' 'pageSamples/404.svelte' }}";
6863
7673
  <\/script>
6864
7674
 
6865
7675
  <PageSample />
6866
- `,jr=`<script lang="ts">
7676
+ `,$r=`<script lang="ts">
6867
7677
  import type { Snippet } from "svelte";
6868
7678
 
6869
7679
  let { children }: { children: Snippet } = $props();
6870
7680
  <\/script>
6871
7681
 
6872
7682
  {@render children()}
6873
- `,Mr=`<script lang="ts">
7683
+ `,ei=`<script lang="ts">
6874
7684
  import PageSample from "{{ createImport 'lib' 'pageSamples/page.svelte' }}";
6875
7685
 
6876
7686
  const pathMap = {
@@ -6889,7 +7699,7 @@ export default renderFactory(() => {
6889
7699
  routeName={"{{route.name}}"}
6890
7700
  {pathMap}
6891
7701
  />
6892
- `,Nr=`<script lang="ts">
7702
+ `,ti=`<script lang="ts">
6893
7703
  import WelcomePage from "{{ createImport 'lib' 'pageSamples/welcome.svelte' }}";
6894
7704
  <\/script>
6895
7705
 
@@ -6902,7 +7712,7 @@ export default renderFactory(() => {
6902
7712
  </svelte:head>
6903
7713
 
6904
7714
  <WelcomePage />
6905
- `,Pr=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
7715
+ `,ni=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
6906
7716
 
6907
7717
  import app from "./app.svelte";
6908
7718
 
@@ -6917,7 +7727,7 @@ export default routerFactory((routes) => {
6917
7727
  },
6918
7728
  };
6919
7729
  });
6920
- `,Fr=g((e,t)=>{let{createPath:n,createImportHelpers:r}=y(e),{renderToFile:i}=x({helpers:{...r({origin:`lib`}),...w()}}),{renderToFile:a}=x({helpers:r({origin:`src`})}),o=e=>!e?.trim().length,s=c(t?.templates,Mr),u=async e=>{for(let{kind:t,entry:r}of e)t===`pageRoute`?await a(n.pages(r.file),r.name===`index`?Nr:s(r.name,r),{route:r,title:r.name.replace(/\{([^}]+)\}/g,`$1`),message:lr()},{overwrite:o}):t===`pageLayout`&&await a(n.pages(r.file),jr,{route:r},{overwrite:o})},d=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageLayout`?[t]:[]),r=e.flatMap(({kind:e,entry:n})=>{if(e===`pageRoute`){let{name:e,file:r}=n;return[{...n,layouts:t.flatMap(t=>t.name===e||r.startsWith(`${t.name}/`)?[t]:[]).sort(S)}]}return[]}).sort(S);for(let[e,a]of[[`client.ts`,dr],[`server.ts`,fr]])await i(n.libEntry(e),a,{pageRoutes:r,layouts:t});for(let[e,t]of[[`params.ts`,yr],[`router.ts`,Sr]])await i(n.lib(e),t,{pageRoutes:r})};return{config(){let{templates:e,...n}={...t};return{plugins:fe(n)}},async start(){for(let[e,r]of[[`env.d.ts`,pr],[`svelte.ts`,Cr],[`Layouts.svelte`,mr],[`use.ts`,wr],[`pageSamples/styles.module.css`,_r],[`pageSamples/welcome.svelte`,vr],[`pageSamples/page.svelte`,gr],[`pageSamples/404.svelte`,hr],...t?.tanstack?.query?[[`app/app.svelte`,Y],[`app/app-tsq.svelte`,ur],[`app/index.ts`,`export { default as AppProvider } from "./app-tsq.svelte";`],[`query.ts`,br]]:[[`app/app.svelte`,Y],[`app/app-tsq.svelte`,`/** tanstack query disabled */`],[`app/index.ts`,`export { default as AppProvider } from "./app.svelte";`],[`query.ts`,`/** tanstack query disabled */`]]])await i(n.lib(e),r,{});for(let[e,t]of[[`pages/404.svelte`,Ar],[`components/Link.svelte`,Er],[`app.svelte`,Tr],[`router.ts`,Pr]])await a(n.src(e),t,{entryDir:l.entryDir},{overwrite:o});await a(n.src(`index.html`),kr,{entryDir:l.entryDir},{overwrite:e=>!e?.trim().length||!e.replace(/<!--[\s\S]*?-->/g,``).trim().length});for(let[e,t]of[[`client.ts`,Dr],[`server.ts`,Or]])await a(n.entry(e),t,{},{overwrite:o})},async watch(e,t){await u(e.filter(m(t,[`create`]))),await d(e)},async build(e){await u(e),await d(e)},async ssrBuild(){await i(n.lib(`query.ts`),t?.tanstack?.query?xr:`/** tanstack query disabled */`,{ssrBundle:!0})}}}),Ir=h({meta:{name:`Svelte`},dependencies(e){return{svelte:J.devDependencies.svelte,"path-to-regexp":J.devDependencies[`path-to-regexp`],...e?.tanstack?.query?{"@tanstack/svelte-query":J.devDependencies[`@tanstack/svelte-query`]}:{}}},factory:Fr}),Lr={type:`module`,private:!0,name:`@kosmojs/typebox-generator`,version:`0.3.0`,cacheVersion:`1`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/typebox-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,crc:`^4.3.2`,semver:`^7.8.5`},devDependencies:{"@kosmojs/core-generator":`workspace:^`,"@kosmojs/koa-generator":`workspace:^`,typebox:`^1.3.18`}},Rr=`import Type from "typebox";
7730
+ `,ri=v((e,t)=>{let{createPath:n,createImportHelpers:r}=S(e),{render:i,renderToFile:a}=w({helpers:{...r({origin:`lib`}),...A()}}),{renderToFile:o}=w({helpers:r({origin:`src`})}),s=e=>!e?.trim().length,c=l(t?.templates,ei),d=async e=>{for(let{kind:t,entry:r}of e)t===`pageRoute`?await o(n.pages(r.file),r.name===`index`?ti:c(r.name,r),{route:r,title:r.name.replace(/\{([^}]+)\}/g,`$1`),message:jr()},{overwrite:s}):t===`pageLayout`&&await o(n.pages(r.file),$r,{route:r},{overwrite:s})},f=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageLayout`?[t]:[]),r=e.flatMap(({kind:e,entry:n})=>{if(e===`pageRoute`){let{name:e,file:r}=n;return[{...n,layouts:t.flatMap(t=>t.name===e||r.startsWith(`${t.name}/`)?[t]:[]).sort(E)}]}return[]}).sort(D);for(let[e,i]of[[`client.ts`,Nr],[`server.ts`,Pr]])await a(n.libEntry(e),i,{pageRoutes:r,layouts:t});for(let[e,t]of[[`params.ts`,Vr],[`router.ts`,Gr]])await a(n.lib(e),t,{pageRoutes:r})};return{config(){let{templates:e,...n}={...t};return{plugins:fe(n)}},async start(){for(let[e,r]of[[`env.d.ts`,Fr],[`svelte.ts`,Kr],[`Layouts.svelte`,Ir],[`use.ts`,qr],[`pageSamples/styles.module.css`,zr],[`pageSamples/welcome.svelte`,Br],[`pageSamples/page.svelte`,Rr],[`pageSamples/404.svelte`,Lr],...t?.tanstack?.query?[[`app/app.svelte`,Y],[`app/app-tsq.svelte`,Mr],[`app/index.ts`,`export { default as AppProvider } from "./app-tsq.svelte";`],[`query.ts`,Hr]]:[[`app/app.svelte`,Y],[`app/app-tsq.svelte`,`/** tanstack query disabled */`],[`app/index.ts`,`export { default as AppProvider } from "./app.svelte";`],[`query.ts`,`/** tanstack query disabled */`]]])await a(n.lib(e),r,{});for(let[e,t]of[[`pages/404.svelte`,Qr],[`components/Link.svelte`,Yr],[`app.svelte`,Jr],[`router.ts`,ni]])await o(n.src(e),t,{entryDir:u.entryDir},{overwrite:s});for(let[e,t]of[[`client.ts`,Xr],[`server.ts`,Zr]])await o(n.entry(e),t,{},{overwrite:s})},async watch(e,t){await d(e.filter(g(t,[`create`]))),await f(e)},async build(e){await d(e),await f(e)},virtualModules(){return t?.tanstack?.query?[{specifier:`virtual:kosmo/tsq-client`,csr:i(Ur,{}),ssr:i(Wr,{})}]:[]}}}),ii=_({meta:{name:`Svelte`,slot:`frontend`},dependencies(e){return{svelte:J.devDependencies.svelte,...e?.tanstack?.query?{"@tanstack/svelte-query":J.devDependencies[`@tanstack/svelte-query`]}:{}}},factory:ri}),ai={type:`module`,private:!0,name:`@kosmojs/typebox-generator`,version:`0.3.0`,cacheVersion:`1`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/typebox-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,crc:`^4.3.2`,semver:`^7.8.5`},devDependencies:{"@kosmojs/core-generator":`workspace:^`,"@kosmojs/koa-generator":`workspace:^`,typebox:`^1.3.18`}},oi=`import Type from "typebox";
6921
7731
 
6922
7732
  /**
6923
7733
  * Custom types for JavaScript constructs that have no JSON Schema
@@ -6989,13 +7799,13 @@ export default {
6989
7799
  Buffer: TBuffer(),
6990
7800
  ArrayBuffer: TArrayBuffer(),
6991
7801
  };
6992
- `,zr=`import type { TValidationError } from "typebox/error";
7802
+ `,si=`import type { TValidationError } from "typebox/error";
6993
7803
 
6994
7804
  import type { ValidationErrorEntry } from "@kosmojs/core";
6995
7805
 
6996
7806
  /**
6997
7807
  * Message codes for i18n/l10n support
6998
- */
7808
+ * */
6999
7809
  export const MESSAGE_CODES = {
7000
7810
  // Generic messages
7001
7811
 
@@ -7185,7 +7995,7 @@ export type ValidationMessages = typeof MESSAGE_CODES;
7185
7995
  *
7186
7996
  * Usage: format(ERROR_MESSAGES[ErrorCode.STRING_MIN_LENGTH], 5)
7187
7997
  * Result: "must be at least 5 characters long"
7188
- */
7998
+ * */
7189
7999
  const MESSAGE_MAP: Record<keyof ValidationMessages, string> = {
7190
8000
  // Generic messages
7191
8001
 
@@ -7305,7 +8115,7 @@ const MESSAGE_MAP: Record<keyof ValidationMessages, string> = {
7305
8115
  * Comprehensive error handler for TypeBox validation errors.
7306
8116
  * Supports most JSON Schema validation keywords and produces human-friendly messages
7307
8117
  * with i18n/l10n support through message code mapping.
7308
- */
8118
+ * */
7309
8119
  export const errorHandlerFactory = (
7310
8120
  customMessages?: Partial<Record<keyof ValidationMessages, string>>,
7311
8121
  ) => {
@@ -7313,7 +8123,7 @@ export const errorHandlerFactory = (
7313
8123
 
7314
8124
  /**
7315
8125
  * Formats the instancePath into a human-readable field name
7316
- */
8126
+ * */
7317
8127
  function formatFieldPath(instancePath: string, schemaPath?: string): string {
7318
8128
  if (!instancePath?.trim?.()) {
7319
8129
  return "root";
@@ -7351,7 +8161,7 @@ export const errorHandlerFactory = (
7351
8161
 
7352
8162
  /**
7353
8163
  * Pluralizes a word based on count
7354
- */
8164
+ * */
7355
8165
  function pluralize(
7356
8166
  count: number,
7357
8167
  plural: string = messageMap[MESSAGE_CODES.PLURAL_SUFFIX],
@@ -7361,7 +8171,7 @@ export const errorHandlerFactory = (
7361
8171
 
7362
8172
  /**
7363
8173
  * Maps validation error to error code and formats message
7364
- */
8174
+ * */
7365
8175
  function getErrorCodeAndMessage(error: TValidationError): {
7366
8176
  code: keyof ValidationMessages;
7367
8177
  message: string;
@@ -7781,7 +8591,7 @@ export const errorHandlerFactory = (
7781
8591
  * TODO: Periodically check the TypeBox repository for implementation updates.
7782
8592
  * Some validation cases (minContains, prefixItems, etc.) are not yet
7783
8593
  * implemented by TypeBox, so they are commented out below.
7784
- */
8594
+ * */
7785
8595
 
7786
8596
  /**
7787
8597
  case "minContains": {
@@ -7935,7 +8745,7 @@ export const errorHandlerFactory = (
7935
8745
  ),
7936
8746
  };
7937
8747
  }
7938
- */
8748
+ * */
7939
8749
 
7940
8750
  // Default fallback
7941
8751
  default: {
@@ -7946,7 +8756,7 @@ export const errorHandlerFactory = (
7946
8756
 
7947
8757
  /**
7948
8758
  * Groups errors by field to avoid duplicate messages
7949
- */
8759
+ * */
7950
8760
  function groupErrorsByField(
7951
8761
  errors: TValidationError[],
7952
8762
  ): Map<string, TValidationError[]> {
@@ -7965,7 +8775,7 @@ export const errorHandlerFactory = (
7965
8775
 
7966
8776
  /**
7967
8777
  * Prioritizes errors to show the most important one per field
7968
- */
8778
+ * */
7969
8779
  function prioritizeErrors(errors: TValidationError[]): TValidationError {
7970
8780
  // Priority order: required > type > format > const/enum > other constraints
7971
8781
  const priority: Record<string, number> = {
@@ -7994,13 +8804,51 @@ export const errorHandlerFactory = (
7994
8804
  })[0];
7995
8805
  }
7996
8806
 
8807
+ /**
8808
+ * A failed union of literals arrives as one \`anyOf\` error plus one \`const\` error per branch,
8809
+ * all at the same instancePath.
8810
+ * Collapse them into a single synthetic \`enum\` error so the message reflects the whole union.
8811
+ * */
8812
+ function collapseLiteralUnion(
8813
+ fieldErrors: TValidationError[],
8814
+ ): TValidationError[] {
8815
+ const anyOfError = fieldErrors.find((e) => e.keyword === "anyOf");
8816
+ if (!anyOfError) {
8817
+ return fieldErrors;
8818
+ }
8819
+ const branchConsts = fieldErrors.filter((e) => {
8820
+ return e.keyword === "const"
8821
+ ? e.schemaPath.startsWith(\`\${anyOfError.schemaPath}/anyOf/\`)
8822
+ : false;
8823
+ });
8824
+ // bail unless EVERY sibling is a const branch of this anyOf -
8825
+ // mixed unions (literal | object) keep their current behavior
8826
+ if (
8827
+ !branchConsts.length ||
8828
+ fieldErrors.length !== branchConsts.length + 1
8829
+ ) {
8830
+ return fieldErrors;
8831
+ }
8832
+ return [
8833
+ {
8834
+ ...anyOfError,
8835
+ keyword: "enum",
8836
+ params: {
8837
+ allowedValues: branchConsts.map(
8838
+ (e) => (e.params as never)["allowedValue"],
8839
+ ),
8840
+ },
8841
+ },
8842
+ ];
8843
+ }
8844
+
7997
8845
  // ============================================================================
7998
8846
  // Public API
7999
8847
  // ============================================================================
8000
8848
 
8001
8849
  /**
8002
8850
  * Main function to format validation errors
8003
- */
8851
+ * */
8004
8852
  function formatValidationErrors(
8005
8853
  errors: TValidationError[],
8006
8854
  options: {
@@ -8035,7 +8883,7 @@ export const errorHandlerFactory = (
8035
8883
  const formatted: ValidationErrorEntry[] = [];
8036
8884
 
8037
8885
  for (const [_, fieldErrors] of grouped) {
8038
- const prioritized = prioritizeErrors(fieldErrors);
8886
+ const prioritized = prioritizeErrors(collapseLiteralUnion(fieldErrors));
8039
8887
  const { code, message } = getErrorCodeAndMessage(prioritized);
8040
8888
  formatted.push({
8041
8889
  keyword: prioritized.keyword,
@@ -8067,7 +8915,7 @@ export const errorHandlerFactory = (
8067
8915
 
8068
8916
  /**
8069
8917
  * Formats errors into a single human-readable message
8070
- */
8918
+ * */
8071
8919
  function formatValidationErrorMessage(
8072
8920
  errors: TValidationError[],
8073
8921
  options: {
@@ -8096,7 +8944,7 @@ export const errorHandlerFactory = (
8096
8944
 
8097
8945
  /**
8098
8946
  * Gets a simple error summary for quick feedback
8099
- */
8947
+ * */
8100
8948
  function getErrorSummary(errors: TValidationError[]): string {
8101
8949
  if (!errors?.length) {
8102
8950
  return messageMap[MESSAGE_CODES.VALIDATION_PASSED];
@@ -8173,7 +9021,7 @@ const format = (fmt: string, ...args: unknown[]): string => {
8173
9021
 
8174
9022
  return str;
8175
9023
  };
8176
- `,Br=`import Type from "typebox";
9024
+ `,ci=`import Type from "typebox";
8177
9025
  import { Compile } from "typebox/compile";
8178
9026
  import Value from "typebox/value";
8179
9027
 
@@ -8225,14 +9073,14 @@ export const validationSchemaFactory = (
8225
9073
  },
8226
9074
  };
8227
9075
  };
8228
- `,Vr=`import { Settings } from "typebox/system";
9076
+ `,li=`import { Settings } from "typebox/system";
8229
9077
 
8230
9078
  Settings.Set({{settings}});
8231
9079
 
8232
9080
  export { default as customTypes } from "{{customTypesImport}}";
8233
9081
 
8234
9082
  export const validationMessages = {{validationMessages}};
8235
- `,Hr=`import type { ValidationSchemas } from "@kosmojs/core";
9083
+ `,ui=`import type { ValidationSchemas } from "@kosmojs/core";
8236
9084
 
8237
9085
  import { validationSchemaFactory } from "{{ createImport 'lib' '@typebox' }}";
8238
9086
 
@@ -8293,14 +9141,14 @@ export const validationSchemas: ValidationSchemas = {
8293
9141
  {{/each}}
8294
9142
  },
8295
9143
  };
8296
- `,Ur={exactOptionalPropertyTypes:!0},Wr=g((e,t)=>{let{createPath:n,createImport:r,createImportHelpers:i}=y(e),{renderToFile:a}=x({helpers:{...i({origin:`lib`})}}),{validationMessages:s={},customTypesImport:c=r.lib([`@typebox/custom-types`],{origin:`lib`}),settings:l}={...t},u=async e=>{for(let{kind:t,entry:r}of e){if(t!==`apiRoute`)continue;let e=[r.params,...r.validationDefinitions.flatMap(e=>e.target===`response`?e.variants:[e.schema])].flatMap(({resolvedType:e})=>e?[e]:[]),i=[...new Set(r.validationDefinitions.flatMap(({target:e})=>Object.keys(o).includes(e)?[e]:[]))].map(e=>({target:e,methods:r.methods.flatMap(t=>{let n=r.validationDefinitions.find(n=>n.method===t&&n.target===e);return n?[{route:r.name,method:t,target:e,schema:n.schema,...n.runtimeValidation===void 0?{}:{runtimeValidation:JSON.stringify(n.runtimeValidation)},...n.customErrors===void 0?{}:{customErrors:JSON.stringify(n.customErrors)}}]:[]})})),s=r.methods.flatMap(e=>{let t=r.validationDefinitions.find(t=>t.method===e&&t.target===`response`);return t?[{method:e,variants:t.variants.map(e=>({route:r.name,target:`response`,...e,...t.runtimeValidation===void 0?{}:{runtimeValidation:JSON.stringify(t.runtimeValidation)},...t.customErrors===void 0?{}:{customErrors:JSON.stringify(t.customErrors)}}))}]:[]});await a(n.libApi(r.name,`schemas.ts`),Hr,{route:r,resolvedTypes:e,requestSchemas:i,responseSchemas:s})}};return{async start(){for(let[e,t]of[[`custom-types.ts`,Rr],[`error-handler.ts`,zr],[`index.ts`,Br],[`setup.ts`,Vr]])await a(n.lib(`@typebox`,e),t,{validationMessages:JSON.stringify(s),customTypesImport:c,settings:JSON.stringify({...Ur,...l})})},async watch(e,t){await u(e.filter(p(t,[`create`,`update`])))},async build(e){await u(e)}}}),X={PROPERTY:`PROPERTY`,PROPERTIES:`PROPERTIES`,ALLOWED_VALUES:`ALLOWED_VALUES`,FOUND_N_DUPLICATES:`FOUND_N_DUPLICATES`,VALIDATION_PASSED:`VALIDATION_PASSED`,VALIDATION_FAILED_PREFIX:`VALIDATION_FAILED_PREFIX`,ERROR_SUMMARY:`ERROR_SUMMARY`,PLURAL_SUFFIX:`PLURAL_SUFFIX`,FIRST:`FIRST`,SECOND:`SECOND`,THIRD:`THIRD`,FOURTH:`FOURTH`,FIFTH:`FIFTH`,TYPE_INVALID:`TYPE_INVALID`,STRING_MIN_LENGTH:`STRING_MIN_LENGTH`,STRING_MAX_LENGTH:`STRING_MAX_LENGTH`,STRING_PATTERN:`STRING_PATTERN`,STRING_FORMAT:`STRING_FORMAT`,STRING_FORMAT_EMAIL:`STRING_FORMAT_EMAIL`,STRING_FORMAT_DATE:`STRING_FORMAT_DATE`,STRING_FORMAT_DATETIME:`STRING_FORMAT_DATETIME`,STRING_FORMAT_TIME:`STRING_FORMAT_TIME`,STRING_FORMAT_URI:`STRING_FORMAT_URI`,STRING_FORMAT_URL:`STRING_FORMAT_URL`,STRING_FORMAT_UUID:`STRING_FORMAT_UUID`,STRING_FORMAT_IPV4:`STRING_FORMAT_IPV4`,STRING_FORMAT_IPV6:`STRING_FORMAT_IPV6`,STRING_FORMAT_HOSTNAME:`STRING_FORMAT_HOSTNAME`,STRING_FORMAT_JSON_POINTER:`STRING_FORMAT_JSON_POINTER`,STRING_FORMAT_REGEX:`STRING_FORMAT_REGEX`,NUMBER_MINIMUM:`NUMBER_MINIMUM`,NUMBER_MAXIMUM:`NUMBER_MAXIMUM`,NUMBER_EXCLUSIVE_MINIMUM:`NUMBER_EXCLUSIVE_MINIMUM`,NUMBER_EXCLUSIVE_MAXIMUM:`NUMBER_EXCLUSIVE_MAXIMUM`,NUMBER_MULTIPLE_OF:`NUMBER_MULTIPLE_OF`,ARRAY_MIN_ITEMS:`ARRAY_MIN_ITEMS`,ARRAY_MAX_ITEMS:`ARRAY_MAX_ITEMS`,ARRAY_UNIQUE_ITEMS:`ARRAY_UNIQUE_ITEMS`,ARRAY_CONTAINS:`ARRAY_CONTAINS`,ARRAY_MIN_CONTAINS:`ARRAY_MIN_CONTAINS`,ARRAY_MAX_CONTAINS:`ARRAY_MAX_CONTAINS`,ARRAY_PREFIX_ITEMS:`ARRAY_PREFIX_ITEMS`,ARRAY_ITEMS:`ARRAY_ITEMS`,ARRAY_UNEVALUATED_ITEMS:`ARRAY_UNEVALUATED_ITEMS`,TUPLE_MIN_ITEMS:`TUPLE_MIN_ITEMS`,TUPLE_MAX_ITEMS:`TUPLE_MAX_ITEMS`,OBJECT_REQUIRED:`OBJECT_REQUIRED`,OBJECT_ADDITIONAL_PROPERTIES:`OBJECT_ADDITIONAL_PROPERTIES`,OBJECT_MIN_PROPERTIES:`OBJECT_MIN_PROPERTIES`,OBJECT_MAX_PROPERTIES:`OBJECT_MAX_PROPERTIES`,OBJECT_PROPERTY_NAMES:`OBJECT_PROPERTY_NAMES`,OBJECT_DEPENDENCIES:`OBJECT_DEPENDENCIES`,OBJECT_UNEVALUATED_PROPERTIES:`OBJECT_UNEVALUATED_PROPERTIES`,ENUM_MISMATCH:`ENUM_MISMATCH`,CONST_MISMATCH:`CONST_MISMATCH`,CONDITIONAL_IF:`CONDITIONAL_IF`,CONDITIONAL_THEN:`CONDITIONAL_THEN`,CONDITIONAL_ELSE:`CONDITIONAL_ELSE`,COMPOSITION_ONE_OF:`COMPOSITION_ONE_OF`,COMPOSITION_ANY_OF:`COMPOSITION_ANY_OF`,COMPOSITION_ALL_OF:`COMPOSITION_ALL_OF`,COMPOSITION_NOT:`COMPOSITION_NOT`,CONTENT_DISCRIMINATOR:`CONTENT_DISCRIMINATOR`,CONTENT_ENCODING:`CONTENT_ENCODING`,CONTENT_MEDIA_TYPE:`CONTENT_MEDIA_TYPE`,CUSTOM_RANGE:`CUSTOM_RANGE`,CUSTOM_EXCLUSIVE_RANGE:`CUSTOM_EXCLUSIVE_RANGE`,CUSTOM_REGEXP:`CUSTOM_REGEXP`,CUSTOM_DYNAMIC_DEFAULTS:`CUSTOM_DYNAMIC_DEFAULTS`,CUSTOM_SELECT:`CUSTOM_SELECT`,CUSTOM_TRANSFORM:`CUSTOM_TRANSFORM`,CUSTOM_UNIQUE_ITEM_PROPERTIES:`CUSTOM_UNIQUE_ITEM_PROPERTIES`,UNKNOWN:`UNKNOWN`};X.PROPERTY,X.PROPERTIES,X.ALLOWED_VALUES,X.FOUND_N_DUPLICATES,X.VALIDATION_PASSED,X.VALIDATION_FAILED_PREFIX,X.ERROR_SUMMARY,X.PLURAL_SUFFIX,X.FIRST,X.SECOND,X.THIRD,X.FOURTH,X.FIFTH,X.TYPE_INVALID,X.STRING_MIN_LENGTH,X.STRING_MAX_LENGTH,X.STRING_PATTERN,X.STRING_FORMAT,X.STRING_FORMAT_EMAIL,X.STRING_FORMAT_DATE,X.STRING_FORMAT_DATETIME,X.STRING_FORMAT_TIME,X.STRING_FORMAT_URI,X.STRING_FORMAT_URL,X.STRING_FORMAT_UUID,X.STRING_FORMAT_IPV4,X.STRING_FORMAT_IPV6,X.STRING_FORMAT_HOSTNAME,X.STRING_FORMAT_JSON_POINTER,X.STRING_FORMAT_REGEX,X.NUMBER_MINIMUM,X.NUMBER_MAXIMUM,X.NUMBER_EXCLUSIVE_MINIMUM,X.NUMBER_EXCLUSIVE_MAXIMUM,X.NUMBER_MULTIPLE_OF,X.ARRAY_MIN_ITEMS,X.ARRAY_MAX_ITEMS,X.ARRAY_UNIQUE_ITEMS,X.ARRAY_CONTAINS,X.ARRAY_MIN_CONTAINS,X.ARRAY_MAX_CONTAINS,X.ARRAY_PREFIX_ITEMS,X.ARRAY_ITEMS,X.ARRAY_UNEVALUATED_ITEMS,X.TUPLE_MIN_ITEMS,X.TUPLE_MAX_ITEMS,X.OBJECT_REQUIRED,X.OBJECT_ADDITIONAL_PROPERTIES,X.OBJECT_MIN_PROPERTIES,X.OBJECT_MAX_PROPERTIES,X.OBJECT_PROPERTY_NAMES,X.OBJECT_DEPENDENCIES,X.OBJECT_UNEVALUATED_PROPERTIES,X.ENUM_MISMATCH,X.CONST_MISMATCH,X.CONDITIONAL_IF,X.CONDITIONAL_THEN,X.CONDITIONAL_ELSE,X.COMPOSITION_ONE_OF,X.COMPOSITION_ANY_OF,X.COMPOSITION_ALL_OF,X.COMPOSITION_NOT,X.CONTENT_DISCRIMINATOR,X.CONTENT_ENCODING,X.CONTENT_MEDIA_TYPE,X.CUSTOM_RANGE,X.CUSTOM_EXCLUSIVE_RANGE,X.CUSTOM_REGEXP,X.CUSTOM_DYNAMIC_DEFAULTS,X.CUSTOM_SELECT,X.CUSTOM_TRANSFORM,X.CUSTOM_UNIQUE_ITEM_PROPERTIES,X.UNKNOWN;var Gr=h({meta:{name:`TypeBox`,resolveTypes:!0},dependencies:{typebox:Lr.devDependencies.typebox},factory:Wr}),Z={type:`module`,private:!0,name:`@kosmojs/vue-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/vue-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"@vitejs/plugin-vue":`^6.0.8`},devDependencies:{"@tanstack/vue-query":`^5.102.2`,"path-to-regexp":`^8.4.2`,vue:`^3.5.41`,"vue-router":`^5.2.0`}},Q=e=>{let t=e=>e.kind===`splat`?`:${e.name}(.*)?`:e.kind===`optional`?`:${e.name}?`:`:${e.name}`;return e.flatMap(e=>e.kind===`static`?[e.parts[0].value]:e.kind===`param`?[t(e.parts[0])]:[e.parts.map(e=>e.type===`static`?e.value:t(e)).join(``)]).join(`/`)},Kr=()=>{let e=e=>e?.kind===`param`&&e.parts[0]?.kind===`splat`,n=(r,i)=>r.flatMap(({index:r,layout:a,children:o})=>{let{name:s,pathTokens:c}={...r,...a};if(!c)return[];let l=`${s}/layout`,u=i?Q(c):t(`/`,Q(c)),d=c.at(-1);return e(d)?r&&a?[{name:l,path:u,component:a.id,children:[{name:s,path:``,component:r.id}]}]:r?[{path:u,children:[{name:s,path:``,component:r.id},...n(o,s)]}]:a?[{name:l,path:u,component:a.id,children:n(o,s)}]:[]:r&&a?[{name:l,path:u,component:a.id,children:[{name:s,path:``,component:r.id},...n(o,s)]}]:r?[{path:u,children:[{name:s,path:``,component:r.id},...n(o,s)]}]:a?[{name:l,path:u,component:a.id,children:n(o,s)}]:[]});return n},qr=()=>{let e=[`🎉 Well done! You just created a new Vue route.`,`🚀 Success! A fresh Vue route is ready to roll.`,`🌟 Nice work! Another Vue route added to your app.`,`🧩 All set! A new Vue route has been scaffolded.`,`🔧 Scaffold complete! Your new Vue route is in place.`,`✅ Built! Your Vue route is scaffolded and ready.`,`✨ Fantastic! Your new Vue route is good to go.`,`🎯 Nailed it! A brand new Vue route just landed.`,`💫 Awesome! Another Vue route joins the party.`,`⚡ Lightning fast! A new Vue route created successfully.`];return e[Math.floor(Math.random()*e.length)]},Jr=`import type { Plugin } from "vue";
9144
+ `,di={exactOptionalPropertyTypes:!0},fi=v((e,t)=>{let{createPath:n,createImport:r,createImportHelpers:i}=S(e),{renderToFile:a}=w({helpers:{...i({origin:`lib`})}}),{validationMessages:o={},customTypesImport:c=r.lib([`@typebox/custom-types`],{origin:`lib`}),settings:l}={...t},u=async e=>{for(let{kind:t,entry:r}of e){if(t!==`apiRoute`)continue;let e=[r.params,...r.validationDefinitions.flatMap(e=>e.target===`response`?e.variants:[e.schema])].flatMap(({resolvedType:e})=>e?[e]:[]),i=[...new Set(r.validationDefinitions.flatMap(({target:e})=>Object.keys(s).includes(e)?[e]:[]))].map(e=>({target:e,methods:r.methods.flatMap(t=>{let n=r.validationDefinitions.find(n=>n.method===t&&n.target===e);return n?[{route:r.name,method:t,target:e,schema:n.schema,...n.runtimeValidation===void 0?{}:{runtimeValidation:JSON.stringify(n.runtimeValidation)},...n.customErrors===void 0?{}:{customErrors:JSON.stringify(n.customErrors)}}]:[]})})),o=r.methods.flatMap(e=>{let t=r.validationDefinitions.find(t=>t.method===e&&t.target===`response`);return t?[{method:e,variants:t.variants.map(e=>({route:r.name,target:`response`,...e,...t.runtimeValidation===void 0?{}:{runtimeValidation:JSON.stringify(t.runtimeValidation)},...t.customErrors===void 0?{}:{customErrors:JSON.stringify(t.customErrors)}}))}]:[]});await a(n.libApi(r.name,`schemas.ts`),ui,{route:r,resolvedTypes:e,requestSchemas:i,responseSchemas:o})}};return{async start(){for(let[e,t]of[[`custom-types.ts`,oi],[`error-handler.ts`,si],[`index.ts`,ci],[`setup.ts`,li]])await a(n.lib(`@typebox`,e),t,{validationMessages:JSON.stringify(o),customTypesImport:c,settings:JSON.stringify({...di,...l})})},async watch(e,t){await u(e.filter(h(t,[`create`,`update`])))},async build(e){await u(e)}}}),X={PROPERTY:`PROPERTY`,PROPERTIES:`PROPERTIES`,ALLOWED_VALUES:`ALLOWED_VALUES`,FOUND_N_DUPLICATES:`FOUND_N_DUPLICATES`,VALIDATION_PASSED:`VALIDATION_PASSED`,VALIDATION_FAILED_PREFIX:`VALIDATION_FAILED_PREFIX`,ERROR_SUMMARY:`ERROR_SUMMARY`,PLURAL_SUFFIX:`PLURAL_SUFFIX`,FIRST:`FIRST`,SECOND:`SECOND`,THIRD:`THIRD`,FOURTH:`FOURTH`,FIFTH:`FIFTH`,TYPE_INVALID:`TYPE_INVALID`,STRING_MIN_LENGTH:`STRING_MIN_LENGTH`,STRING_MAX_LENGTH:`STRING_MAX_LENGTH`,STRING_PATTERN:`STRING_PATTERN`,STRING_FORMAT:`STRING_FORMAT`,STRING_FORMAT_EMAIL:`STRING_FORMAT_EMAIL`,STRING_FORMAT_DATE:`STRING_FORMAT_DATE`,STRING_FORMAT_DATETIME:`STRING_FORMAT_DATETIME`,STRING_FORMAT_TIME:`STRING_FORMAT_TIME`,STRING_FORMAT_URI:`STRING_FORMAT_URI`,STRING_FORMAT_URL:`STRING_FORMAT_URL`,STRING_FORMAT_UUID:`STRING_FORMAT_UUID`,STRING_FORMAT_IPV4:`STRING_FORMAT_IPV4`,STRING_FORMAT_IPV6:`STRING_FORMAT_IPV6`,STRING_FORMAT_HOSTNAME:`STRING_FORMAT_HOSTNAME`,STRING_FORMAT_JSON_POINTER:`STRING_FORMAT_JSON_POINTER`,STRING_FORMAT_REGEX:`STRING_FORMAT_REGEX`,NUMBER_MINIMUM:`NUMBER_MINIMUM`,NUMBER_MAXIMUM:`NUMBER_MAXIMUM`,NUMBER_EXCLUSIVE_MINIMUM:`NUMBER_EXCLUSIVE_MINIMUM`,NUMBER_EXCLUSIVE_MAXIMUM:`NUMBER_EXCLUSIVE_MAXIMUM`,NUMBER_MULTIPLE_OF:`NUMBER_MULTIPLE_OF`,ARRAY_MIN_ITEMS:`ARRAY_MIN_ITEMS`,ARRAY_MAX_ITEMS:`ARRAY_MAX_ITEMS`,ARRAY_UNIQUE_ITEMS:`ARRAY_UNIQUE_ITEMS`,ARRAY_CONTAINS:`ARRAY_CONTAINS`,ARRAY_MIN_CONTAINS:`ARRAY_MIN_CONTAINS`,ARRAY_MAX_CONTAINS:`ARRAY_MAX_CONTAINS`,ARRAY_PREFIX_ITEMS:`ARRAY_PREFIX_ITEMS`,ARRAY_ITEMS:`ARRAY_ITEMS`,ARRAY_UNEVALUATED_ITEMS:`ARRAY_UNEVALUATED_ITEMS`,TUPLE_MIN_ITEMS:`TUPLE_MIN_ITEMS`,TUPLE_MAX_ITEMS:`TUPLE_MAX_ITEMS`,OBJECT_REQUIRED:`OBJECT_REQUIRED`,OBJECT_ADDITIONAL_PROPERTIES:`OBJECT_ADDITIONAL_PROPERTIES`,OBJECT_MIN_PROPERTIES:`OBJECT_MIN_PROPERTIES`,OBJECT_MAX_PROPERTIES:`OBJECT_MAX_PROPERTIES`,OBJECT_PROPERTY_NAMES:`OBJECT_PROPERTY_NAMES`,OBJECT_DEPENDENCIES:`OBJECT_DEPENDENCIES`,OBJECT_UNEVALUATED_PROPERTIES:`OBJECT_UNEVALUATED_PROPERTIES`,ENUM_MISMATCH:`ENUM_MISMATCH`,CONST_MISMATCH:`CONST_MISMATCH`,CONDITIONAL_IF:`CONDITIONAL_IF`,CONDITIONAL_THEN:`CONDITIONAL_THEN`,CONDITIONAL_ELSE:`CONDITIONAL_ELSE`,COMPOSITION_ONE_OF:`COMPOSITION_ONE_OF`,COMPOSITION_ANY_OF:`COMPOSITION_ANY_OF`,COMPOSITION_ALL_OF:`COMPOSITION_ALL_OF`,COMPOSITION_NOT:`COMPOSITION_NOT`,CONTENT_DISCRIMINATOR:`CONTENT_DISCRIMINATOR`,CONTENT_ENCODING:`CONTENT_ENCODING`,CONTENT_MEDIA_TYPE:`CONTENT_MEDIA_TYPE`,CUSTOM_RANGE:`CUSTOM_RANGE`,CUSTOM_EXCLUSIVE_RANGE:`CUSTOM_EXCLUSIVE_RANGE`,CUSTOM_REGEXP:`CUSTOM_REGEXP`,CUSTOM_DYNAMIC_DEFAULTS:`CUSTOM_DYNAMIC_DEFAULTS`,CUSTOM_SELECT:`CUSTOM_SELECT`,CUSTOM_TRANSFORM:`CUSTOM_TRANSFORM`,CUSTOM_UNIQUE_ITEM_PROPERTIES:`CUSTOM_UNIQUE_ITEM_PROPERTIES`,UNKNOWN:`UNKNOWN`};X.PROPERTY,X.PROPERTIES,X.ALLOWED_VALUES,X.FOUND_N_DUPLICATES,X.VALIDATION_PASSED,X.VALIDATION_FAILED_PREFIX,X.ERROR_SUMMARY,X.PLURAL_SUFFIX,X.FIRST,X.SECOND,X.THIRD,X.FOURTH,X.FIFTH,X.TYPE_INVALID,X.STRING_MIN_LENGTH,X.STRING_MAX_LENGTH,X.STRING_PATTERN,X.STRING_FORMAT,X.STRING_FORMAT_EMAIL,X.STRING_FORMAT_DATE,X.STRING_FORMAT_DATETIME,X.STRING_FORMAT_TIME,X.STRING_FORMAT_URI,X.STRING_FORMAT_URL,X.STRING_FORMAT_UUID,X.STRING_FORMAT_IPV4,X.STRING_FORMAT_IPV6,X.STRING_FORMAT_HOSTNAME,X.STRING_FORMAT_JSON_POINTER,X.STRING_FORMAT_REGEX,X.NUMBER_MINIMUM,X.NUMBER_MAXIMUM,X.NUMBER_EXCLUSIVE_MINIMUM,X.NUMBER_EXCLUSIVE_MAXIMUM,X.NUMBER_MULTIPLE_OF,X.ARRAY_MIN_ITEMS,X.ARRAY_MAX_ITEMS,X.ARRAY_UNIQUE_ITEMS,X.ARRAY_CONTAINS,X.ARRAY_MIN_CONTAINS,X.ARRAY_MAX_CONTAINS,X.ARRAY_PREFIX_ITEMS,X.ARRAY_ITEMS,X.ARRAY_UNEVALUATED_ITEMS,X.TUPLE_MIN_ITEMS,X.TUPLE_MAX_ITEMS,X.OBJECT_REQUIRED,X.OBJECT_ADDITIONAL_PROPERTIES,X.OBJECT_MIN_PROPERTIES,X.OBJECT_MAX_PROPERTIES,X.OBJECT_PROPERTY_NAMES,X.OBJECT_DEPENDENCIES,X.OBJECT_UNEVALUATED_PROPERTIES,X.ENUM_MISMATCH,X.CONST_MISMATCH,X.CONDITIONAL_IF,X.CONDITIONAL_THEN,X.CONDITIONAL_ELSE,X.COMPOSITION_ONE_OF,X.COMPOSITION_ANY_OF,X.COMPOSITION_ALL_OF,X.COMPOSITION_NOT,X.CONTENT_DISCRIMINATOR,X.CONTENT_ENCODING,X.CONTENT_MEDIA_TYPE,X.CUSTOM_RANGE,X.CUSTOM_EXCLUSIVE_RANGE,X.CUSTOM_REGEXP,X.CUSTOM_DYNAMIC_DEFAULTS,X.CUSTOM_SELECT,X.CUSTOM_TRANSFORM,X.CUSTOM_UNIQUE_ITEM_PROPERTIES,X.UNKNOWN;var pi=_({meta:{name:`TypeBox`,resolveTypes:!0},dependencies:{typebox:ai.devDependencies.typebox},factory:fi}),Z={type:`module`,private:!0,name:`@kosmojs/vue-generator`,version:`0.3.0`,author:`Slee Woo`,license:`MIT`,files:[`pkg/*`],exports:{".":{types:`./pkg/index.d.ts`,default:`./pkg/index.js`}},scripts:{build:`wsbuild src/index.ts`,test:`vitest --root ../.. --project generators/vue-generator`},dependencies:{"@kosmojs/core":`workspace:^`,"@kosmojs/lib":`workspace:^`,"@vitejs/plugin-vue":`^6.0.8`},devDependencies:{"@tanstack/vue-query":`^5.102.2`,vue:`^3.5.41`,"vue-router":`^5.2.0`}},Q=e=>{let t=e=>e.kind===`splat`?`:${e.name}(.*)?`:e.kind===`optional`?`:${e.name}?`:`:${e.name}`;return e.flatMap(e=>e.kind===`static`?[e.parts[0].value]:e.kind===`param`?[t(e.parts[0])]:[e.parts.map(e=>e.type===`static`?e.value:t(e)).join(``)]).join(`/`)},mi=()=>{let e=e=>e?.kind===`param`&&e.parts[0]?.kind===`splat`,n=(r,i)=>r.flatMap(({index:r,layout:a,children:o})=>{let{name:s,pathTokens:c}={...r,...a};if(!c)return[];let l=`${s}/layout`,u=i?Q(c):t(`/`,Q(c)),d=c.at(-1);return e(d)?r&&a?[{name:l,path:u,component:a.id,children:[{name:s,path:``,component:r.id}]}]:r?[{path:u,children:[{name:s,path:``,component:r.id},...n(o,s)]}]:a?[{name:l,path:u,component:a.id,children:n(o,s)}]:[]:r&&a?[{name:l,path:u,component:a.id,children:[{name:s,path:``,component:r.id},...n(o,s)]}]:r?[{path:u,children:[{name:s,path:``,component:r.id},...n(o,s)]}]:a?[{name:l,path:u,component:a.id,children:n(o,s)}]:[]});return n},hi=()=>{let e=[`🎉 Well done! You just created a new Vue route.`,`🚀 Success! A fresh Vue route is ready to roll.`,`🌟 Nice work! Another Vue route added to your app.`,`🧩 All set! A new Vue route has been scaffolded.`,`🔧 Scaffold complete! Your new Vue route is in place.`,`✅ Built! Your Vue route is scaffolded and ready.`,`✨ Fantastic! Your new Vue route is good to go.`,`🎯 Nailed it! A brand new Vue route just landed.`,`💫 Awesome! Another Vue route joins the party.`,`⚡ Lightning fast! A new Vue route created successfully.`];return e[Math.floor(Math.random()*e.length)]},gi=`import type { Plugin } from "vue";
8297
9145
 
8298
9146
  export { default as AppProvider } from "./provider.vue";
8299
9147
 
8300
9148
  export const appProvider: Plugin = {
8301
9149
  install() {},
8302
9150
  };
8303
- `,Yr=`import { VueQueryPlugin } from "@tanstack/vue-query";
9151
+ `,_i=`import { VueQueryPlugin } from "@tanstack/vue-query";
8304
9152
  import type { Plugin } from "vue";
8305
9153
 
8306
9154
  import { getQueryClient } from "../query";
@@ -8312,10 +9160,10 @@ export const appProvider: Plugin = {
8312
9160
  app.use(VueQueryPlugin, { queryClient: getQueryClient() });
8313
9161
  },
8314
9162
  };
8315
- `,Xr=`<template>
9163
+ `,vi=`<template>
8316
9164
  <slot />
8317
9165
  </template>
8318
- `,Zr=`import type { App } from "vue";
9166
+ `,yi=`import type { App } from "vue";
8319
9167
  import type { RouterFactoryReturn } from "@kosmojs/core";
8320
9168
  import { clientRenderFactory } from "@kosmojs/core/generators";
8321
9169
 
@@ -8355,7 +9203,7 @@ export const mount = async (
8355
9203
  }
8356
9204
 
8357
9205
  export default clientRenderFactory();
8358
- `,Qr=`{
9206
+ `,bi=`{
8359
9207
  path: "{{path}}",
8360
9208
  {{#if name}}
8361
9209
  name: "{{name}}",
@@ -8373,7 +9221,7 @@ export default clientRenderFactory();
8373
9221
  children: [ {{#each children}}{{> routePartial}}, {{/each}}],
8374
9222
  {{/if}}
8375
9223
  }
8376
- `,$r=`import type { App } from "vue";
9224
+ `,$=`import type { App } from "vue";
8377
9225
 
8378
9226
  import {
8379
9227
  renderToString as renderToStringOrig,
@@ -8464,12 +9312,18 @@ export const renderToStream: RenderToStreamWrapper<
8464
9312
  }
8465
9313
 
8466
9314
  export default serverRenderFactory();
8467
- `,$=`declare module "*.vue" {
9315
+ `,xi=`declare module "*.vue" {
8468
9316
  import type { DefineComponent } from "vue";
8469
9317
  const component: DefineComponent<{}, {}, any>;
8470
9318
  export default component;
8471
9319
  }
8472
- `,ei=`<script setup lang="ts">
9320
+
9321
+ declare module "virtual:kosmo/tsq-client" {
9322
+ import type { QueryClient, QueryClientConfig } from "@tanstack/vue-query";
9323
+ export const createQueryClient: (options?: QueryClientConfig) => QueryClient;
9324
+ export const getQueryClient: () => QueryClient;
9325
+ }
9326
+ `,Si=`<script setup lang="ts">
8473
9327
  import styles from "./styles.module.css";
8474
9328
  defineProps<{
8475
9329
  headline?: string;
@@ -8508,7 +9362,7 @@ defineProps<{
8508
9362
  </div>
8509
9363
  </div>
8510
9364
  </template>
8511
- `,ti=`<script setup lang="ts">
9365
+ `,Ci=`<script setup lang="ts">
8512
9366
  import styles from "./styles.module.css";
8513
9367
  defineProps<{
8514
9368
  message: string;
@@ -8557,7 +9411,7 @@ defineProps<{
8557
9411
  </div>
8558
9412
  </div>
8559
9413
  </template>
8560
- `,ni=`* {
9414
+ `,wi=`* {
8561
9415
  margin: 0;
8562
9416
  padding: 0;
8563
9417
  box-sizing: border-box;
@@ -8690,7 +9544,7 @@ defineProps<{
8690
9544
  align-items: center;
8691
9545
  gap: 0.25rem;
8692
9546
  }
8693
- `,ri=`<script setup lang="ts">
9547
+ `,Ti=`<script setup lang="ts">
8694
9548
  import styles from "./styles.module.css";
8695
9549
  <\/script>
8696
9550
 
@@ -8754,26 +9608,27 @@ import styles from "./styles.module.css";
8754
9608
  </div>
8755
9609
  </div>
8756
9610
  </template>
8757
- `,ii=`import { QueryClient, type QueryClientConfig } from "@tanstack/vue-query";
9611
+ `,Ei=`export * from "virtual:kosmo/tsq-client";
9612
+ `,Di=`import { QueryClient } from "@tanstack/vue-query";
8758
9613
 
8759
- let client: QueryClient | undefined;
9614
+ let client = undefined
8760
9615
 
8761
- export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
9616
+ export const createQueryClient = (options) => {
8762
9617
  client = new QueryClient(options);
8763
9618
  return client;
8764
9619
  };
8765
9620
 
8766
- export const getQueryClient = (): QueryClient => {
9621
+ export const getQueryClient = () => {
8767
9622
  if (!client) {
8768
9623
  client = new QueryClient();
8769
9624
  }
8770
9625
  return client;
8771
9626
  };
8772
- `,ai=`import { QueryClient, type QueryClientConfig } from "@tanstack/vue-query";
9627
+ `,Oi=`import { QueryClient } from "@tanstack/vue-query";
8773
9628
 
8774
- import { store } from "{{ createImport 'lib' '@ssr/base' }}";
9629
+ import { store } from "{{ createImport 'libCore' 'ssr' }}";
8775
9630
 
8776
- export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
9631
+ export const createQueryClient = (options) => {
8777
9632
  const client = new QueryClient(options);
8778
9633
  const ctx = store?.getStore();
8779
9634
  if (ctx) {
@@ -8782,7 +9637,7 @@ export const createQueryClient = (options?: QueryClientConfig): QueryClient => {
8782
9637
  return client;
8783
9638
  };
8784
9639
 
8785
- export const getQueryClient = (): QueryClient => {
9640
+ export const getQueryClient = () => {
8786
9641
  const ctx = store?.getStore();
8787
9642
  if (!ctx) {
8788
9643
  throw new Error("getQueryClient(): called outside an SSR request scope");
@@ -8790,9 +9645,9 @@ export const getQueryClient = (): QueryClient => {
8790
9645
  if (!ctx.tsqClient) {
8791
9646
  ctx.tsqClient = new QueryClient();
8792
9647
  }
8793
- return ctx.tsqClient as QueryClient;
9648
+ return ctx.tsqClient;
8794
9649
  };
8795
- `,oi=`import {
9650
+ `,ki=`import {
8796
9651
  type App,
8797
9652
  type Component,
8798
9653
  createApp,
@@ -8923,7 +9778,7 @@ export const createRouters = (
8923
9778
 
8924
9779
  let { pathname } = url;
8925
9780
 
8926
- if (base !== "/") {
9781
+ if ((base as string) !== "/") {
8927
9782
  // strip the base from pushed paths
8928
9783
  if (pathname === base) {
8929
9784
  pathname = "/";
@@ -8954,14 +9809,14 @@ export default createRouterFactory<
8954
9809
  Promise<App>,
8955
9810
  { server: { loaderData: Record<string, unknown> } }
8956
9811
  >();
8957
- `,si=`import { type Ref, unref } from "vue";
9812
+ `,Ai=`import { type Ref, unref } from "vue";
8958
9813
 
8959
9814
  export type MaybeWrapped<T> = Ref<T> | T;
8960
9815
 
8961
9816
  export function unwrap<T>(value: MaybeWrapped<T>): T {
8962
9817
  return unref(value);
8963
9818
  }
8964
- `,ci=`import { useRoute, useRouter } from "vue-router";
9819
+ `,ji=`import { useRoute, useRouter } from "vue-router";
8965
9820
 
8966
9821
  import type { RouterWithLoaderData } from "./router";
8967
9822
 
@@ -8976,7 +9831,7 @@ export const useLoaderData = <T>(key?: string): T | undefined => {
8976
9831
  const route = useRoute();
8977
9832
  return router.__loaderData?.[key || (route.name as string)] as T;
8978
9833
  };
8979
- `,li=`<script setup lang="ts">
9834
+ `,Mi=`<script setup lang="ts">
8980
9835
  import { AppProvider } from "_/app";
8981
9836
  <\/script>
8982
9837
 
@@ -8985,7 +9840,7 @@ import { AppProvider } from "_/app";
8985
9840
  <RouterView />
8986
9841
  </AppProvider>
8987
9842
  </template>
8988
- `,ui=`<script setup lang="ts" generic="T extends LinkProps">
9843
+ `,Ni=`<script setup lang="ts" generic="T extends LinkProps">
8989
9844
  import { computed } from "vue";
8990
9845
  import { RouterLink } from "vue-router";
8991
9846
 
@@ -9018,7 +9873,7 @@ const linkProps = computed(() => ({
9018
9873
  <slot />
9019
9874
  </RouterLink>
9020
9875
  </template>
9021
- `,di=`import renderFactory, {
9876
+ `,Pi=`import renderFactory, {
9022
9877
  createRoutes,
9023
9878
  hydrate,
9024
9879
  mount,
@@ -9045,7 +9900,7 @@ if (root) {
9045
9900
  } else {
9046
9901
  console.error("❌ Root element not found!");
9047
9902
  }
9048
- `,fi=`import renderFactory, {
9903
+ `,Fi=`import renderFactory, {
9049
9904
  createRoutes,
9050
9905
  renderToStream,
9051
9906
  renderToString,
@@ -9072,29 +9927,17 @@ export default renderFactory(() => {
9072
9927
  },
9073
9928
  };
9074
9929
  });
9075
- `,pi=`<!doctype html>
9076
- <html lang="en">
9077
- <head>
9078
- <meta charset="UTF-8" />
9079
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9080
- <!--app-head-->
9081
- </head>
9082
- <body>
9083
- <div id="app"><!--app-html--></div>
9084
- <script type="module" src="/{{ entryDir }}/client.ts"><\/script>
9085
- </body>
9086
- </html>
9087
- `,mi=`<script setup lang="ts">
9930
+ `,Ii=`<script setup lang="ts">
9088
9931
  import PageSample from "{{ createImport 'lib' 'pageSamples/404.vue' }}";
9089
9932
  <\/script>
9090
9933
 
9091
9934
  <template>
9092
9935
  <PageSample />
9093
9936
  </template>
9094
- `,hi=`<template>
9937
+ `,Li=`<template>
9095
9938
  <router-view />
9096
9939
  </template>
9097
- `,gi=`<script setup lang="ts">
9940
+ `,Ri=`<script setup lang="ts">
9098
9941
  import PageSample from "{{ createImport 'lib' 'pageSamples/page.vue' }}";
9099
9942
  <\/script>
9100
9943
 
@@ -9109,14 +9952,14 @@ import PageSample from "{{ createImport 'lib' 'pageSamples/page.vue' }}";
9109
9952
  }"
9110
9953
  />
9111
9954
  </template>
9112
- `,_i=`<script setup lang="ts">
9955
+ `,zi=`<script setup lang="ts">
9113
9956
  import WelcomePage from "{{ createImport 'lib' 'pageSamples/welcome.vue' }}";
9114
9957
  <\/script>
9115
9958
 
9116
9959
  <template>
9117
9960
  <WelcomePage />
9118
9961
  </template>
9119
- `,vi=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
9962
+ `,Bi=`import routerFactory, { createRouters } from "{{ createImport 'lib' 'router' }}";
9120
9963
  import { appProvider } from "{{ createImport 'lib' 'app' }}";
9121
9964
 
9122
9965
  import app from "./app.vue";
@@ -9135,5 +9978,5 @@ export default routerFactory((routes) => {
9135
9978
  },
9136
9979
  };
9137
9980
  });
9138
- `,yi=g((e,t)=>{let{createPath:n,createImportHelpers:r}=y(e),{renderToFile:i}=x({helpers:{...r({origin:`lib`}),...w()},partials:{routePartial:Qr}}),{renderToFile:a}=x({helpers:r({origin:`src`})}),o=Kr(),s=e=>!e?.trim().length,u=c(t?.templates,gi),d=async e=>{for(let{kind:t,entry:r}of e)t===`pageRoute`?await a(n.pages(r.file),r.name===`index`?_i:u(r.name,r),{route:r,message:qr()},{overwrite:s}):t===`pageLayout`&&await a(n.pages(r.file),hi,{route:r},{overwrite:s})},f=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`?[t]:[]).sort(S),r=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`||e===`pageLayout`?[t]:[]),a=o(v(r));for(let[e,t]of[[`client.ts`,Zr],[`server.ts`,$r]])await i(n.libEntry(e),t,{pageEntries:r,nestedRoutes:a,lazyLoad:e===`client.ts`});await i(n.lib(`router.ts`),oi,{entries:e,indexRoutes:t})};return{config(){let{templates:e,...n}={...t};return{plugins:[pe(n)]}},async start(){for(let[e,r]of[[`env.d.ts`,$],[`unwrap.ts`,si],[`use.ts`,ci],[`pageSamples/styles.module.css`,ni],[`pageSamples/welcome.vue`,ri],[`pageSamples/page.vue`,ti],[`pageSamples/404.vue`,ei],[`app/provider.vue`,Xr],...t?.tanstack?.query?[[`app/index.ts`,Yr],[`query.ts`,ii]]:[[`app/index.ts`,Jr],[`query.ts`,`/** tanstack query disabled */`]]])await i(n.lib(e),r,{});for(let[e,t]of[[`pages/404.vue`,mi],[`components/Link.vue`,ui],[`app.vue`,li],[`router.ts`,vi]])await a(n.src(e),t,{entryDir:l.entryDir},{overwrite:s});await a(n.src(`index.html`),pi,{entryDir:l.entryDir},{overwrite:e=>!e?.trim().length||!e.replace(/<!--[\s\S]*?-->/g,``).trim().length});for(let[e,t]of[[`client.ts`,di],[`server.ts`,fi]])await a(n.entry(e),t,{},{overwrite:s})},async watch(e,t){await d(e.filter(m(t,[`create`]))),await f(e)},async build(e){await d(e),await f(e)},async ssrBuild(){await i(n.lib(`query.ts`),t?.tanstack?.query?ai:`/** tanstack query disabled */`,{ssrBundle:!0})}}}),bi=h({meta:{name:`Vue`,jsxImportSource:`vue`},dependencies(e){return{vue:Z.devDependencies.vue,"vue-router":Z.devDependencies[`vue-router`],"path-to-regexp":Z.devDependencies[`path-to-regexp`],...e?.tanstack?.query?{"@tanstack/vue-query":Z.devDependencies[`@tanstack/vue-query`]}:{}}},factory:yi}),xi=e=>{let n=process.env.NODE_ENV||`development`,a=typeof e.base==`string`?e.base:e.base[n];if(!a?.trim())throw Error(r([`red`],`ERROR: Invalid Config - no base provided`));return{...e,base:t(`/`,a),apiBase:t(`/`,e.apiBase||i)}};export{e as coreGenerator,xi as defineConfig,I as fetchGenerator,ke as h3Generator,Je as honoGenerator,pt as koaGenerator,Ht as mdxGenerator,qt as openapiGenerator,Sn as reactGenerator,Xn as solidGenerator,$n as ssgGenerator,cr as ssrGenerator,Ir as svelteGenerator,Gr as typeboxGenerator,bi as vueGenerator};
9981
+ `,Vi=v((e,t)=>{let{createPath:n,createImportHelpers:r}=S(e),{render:i,renderToFile:a}=w({helpers:{...r({origin:`lib`}),...A()},partials:{routePartial:bi}}),{renderToFile:o}=w({helpers:r({origin:`src`})}),s=mi(),c=e=>!e?.trim().length,d=l(t?.templates,Ri),f=async e=>{for(let{kind:t,entry:r}of e)t===`pageRoute`?await o(n.pages(r.file),r.name===`index`?zi:d(r.name,r),{route:r,message:hi()},{overwrite:c}):t===`pageLayout`&&await o(n.pages(r.file),Li,{route:r},{overwrite:c})},p=async e=>{let t=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`?[t]:[]).sort(E),r=e.flatMap(({kind:e,entry:t})=>e===`pageRoute`||e===`pageLayout`?[t]:[]),i=s(b(r));for(let[e,t]of[[`client.ts`,yi],[`server.ts`,$]])await a(n.libEntry(e),t,{pageEntries:r,nestedRoutes:i,lazyLoad:e===`client.ts`});await a(n.lib(`router.ts`),ki,{entries:e,indexRoutes:t})};return{config(){let{templates:e,...n}={...t};return{plugins:[pe(n)]}},async start(){for(let[e,r]of[[`env.d.ts`,xi],[`unwrap.ts`,Ai],[`use.ts`,ji],[`pageSamples/styles.module.css`,wi],[`pageSamples/welcome.vue`,Ti],[`pageSamples/page.vue`,Ci],[`pageSamples/404.vue`,Si],[`app/provider.vue`,vi],...t?.tanstack?.query?[[`app/index.ts`,_i],[`query.ts`,Ei]]:[[`app/index.ts`,gi],[`query.ts`,`/** tanstack query disabled */`]]])await a(n.lib(e),r,{});for(let[e,t]of[[`pages/404.vue`,Ii],[`components/Link.vue`,Ni],[`app.vue`,Mi],[`router.ts`,Bi]])await o(n.src(e),t,{entryDir:u.entryDir},{overwrite:c});for(let[e,t]of[[`client.ts`,Pi],[`server.ts`,Fi]])await o(n.entry(e),t,{},{overwrite:c})},async watch(e,t){await f(e.filter(g(t,[`create`]))),await p(e)},async build(e){await f(e),await p(e)},virtualModules(){return t?.tanstack?.query?[{specifier:`virtual:kosmo/tsq-client`,csr:i(Di,{}),ssr:i(Oi,{})}]:[]}}}),Hi=_({meta:{name:`Vue`,slot:`frontend`,jsxImportSource:`vue`},dependencies(e){return{vue:Z.devDependencies.vue,"vue-router":Z.devDependencies[`vue-router`],...e?.tanstack?.query?{"@tanstack/vue-query":Z.devDependencies[`@tanstack/vue-query`]}:{}}},factory:Vi}),Ui=e=>{let n=process.env.NODE_ENV||`development`,r=typeof e.base==`string`?e.base:e.base[n];if(!r?.trim())throw Error(i([`red`],`ERROR: Invalid Config - no base provided`));return{...e,base:t(`/`,r),apiBase:t(`/`,e.apiBase||a),generators:Wi(e)}},Wi=e=>{let t=[],n={};for(let r of e.generators||[])r.meta.slot?n[r.meta.slot]=r:t.push(r);return[B(),...n.backend?[n.backend]:[],...n.fetch&&n.backend?[n.fetch]:[],...n.frontend?[n.frontend]:[],...t,...n.ssr?[n.ssr]:[],...n.ssg?[n.ssg]:[]]};export{B as coreGenerator,Ui as defineConfig,Ae as fetchGenerator,Ze as h3Generator,_t as honoGenerator,Lt as koaGenerator,un as mdxGenerator,hn as openapiGenerator,Wn as reactGenerator,yr as solidGenerator,wr as ssgGenerator,Ar as ssrGenerator,ii as svelteGenerator,pi as typeboxGenerator,Hi as vueGenerator};
9139
9982
  //# sourceMappingURL=index.js.map