@timber-js/app 0.2.0-alpha.90 → 0.2.0-alpha.91

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.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Shared codegen helpers — import-path computation, codec chain type
3
+ * builder, and searchParams type formatter.
4
+ *
5
+ * Extracted from `codegen.ts` so `link-codegen.ts` can use the same
6
+ * helpers without a cyclic import.
7
+ */
8
+ import type { ParamEntry, RouteEntry } from './codegen-types.js';
9
+ /**
10
+ * Compute a relative import specifier for a codec/page file, stripping
11
+ * the .ts/.tsx extension and resolving against the codegen output dir.
12
+ */
13
+ export declare function codecImportPath(codecFilePath: string, importBase: string | undefined): string;
14
+ /**
15
+ * Build a TypeScript type expression that resolves a single param's
16
+ * codec by walking a chain of params.ts files in priority order.
17
+ *
18
+ * Each entry in the chain emits a conditional:
19
+ * `(typeof import(file))['segmentParams'] extends ParamsDefinition<infer T>
20
+ * ? K extends keyof T ? T[K] : <next>
21
+ * : <next>`
22
+ *
23
+ * The closest match (position 0 in the chain) is checked first; if its
24
+ * `segmentParams` definition declares the key, its inferred type wins.
25
+ * Otherwise we fall through to the next ancestor, and finally to the
26
+ * provided fallback. See TIM-834.
27
+ */
28
+ export declare function buildCodecChainType(p: ParamEntry, importBase: string | undefined, fallback: string): string;
29
+ /**
30
+ * Format the searchParams type for a route entry.
31
+ *
32
+ * When a page.tsx (or params.ts) exports searchParams, we reference its
33
+ * inferred type via an import type. The import path is relative to
34
+ * `importBase` (the directory where the .d.ts will be written). When
35
+ * importBase is undefined, falls back to a bare relative path.
36
+ */
37
+ export declare function formatSearchParamsType(route: RouteEntry, importBase?: string): string;
38
+ //# sourceMappingURL=codegen-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen-shared.d.ts","sourceRoot":"","sources":["../../src/routing/codegen-shared.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEjE;;;GAGG;AACH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAM7F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,UAAU,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,QAAQ,EAAE,MAAM,GACf,MAAM,CAeR;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAOrF"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared types for route codegen modules.
3
+ *
4
+ * Lives in its own file so `link-codegen.ts` and `codegen.ts` can import
5
+ * the same `RouteEntry` / `ParamEntry` shapes without a cyclic
6
+ * dependency between the two.
7
+ */
8
+ /** A single route entry extracted from the segment tree. */
9
+ export interface RouteEntry {
10
+ /** URL path pattern (e.g. "/products/[id]") */
11
+ urlPath: string;
12
+ /** Accumulated params from all ancestor dynamic segments */
13
+ params: ParamEntry[];
14
+ /** Whether the page.tsx exports searchParams */
15
+ hasSearchParams: boolean;
16
+ /** Absolute path to the page file that exports searchParams (for import paths) */
17
+ searchParamsPagePath?: string;
18
+ /** Whether this is an API route (route.ts) vs page route */
19
+ isApiRoute: boolean;
20
+ }
21
+ export interface ParamEntry {
22
+ name: string;
23
+ type: 'string' | 'string[]' | 'string[] | undefined';
24
+ /**
25
+ * Ordered list of params.ts (or layout/page) files that may declare a
26
+ * codec for this param, in priority order — closest match first.
27
+ *
28
+ * Position 0 is the segment's own params.ts (if present and exporting
29
+ * `segmentParams`); subsequent entries are ancestor params.ts files
30
+ * walking from closest ancestor up to the route root. The first entry
31
+ * in the chain whose `segmentParams` definition declares this param key
32
+ * wins. See TIM-834.
33
+ */
34
+ codecFilePaths?: string[];
35
+ }
36
+ //# sourceMappingURL=codegen-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen-types.d.ts","sourceRoot":"","sources":["../../src/routing/codegen-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,gDAAgD;IAChD,eAAe,EAAE,OAAO,CAAC;IACzB,kFAAkF;IAClF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,4DAA4D;IAC5D,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,sBAAsB,CAAC;IACrD;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/routing/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAuBzD,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CAWtF"}
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/routing/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAKzD,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CAWtF"}
@@ -1,3 +1,3 @@
1
1
  import { t as classifyUrlSegment } from "../_chunks/segment-classify-BDNn6EzD.js";
2
- import { a as DEFAULT_PAGE_EXTENSIONS, i as scanRoutes, n as generateRouteMap, o as INTERCEPTION_MARKERS, r as classifySegment, t as collectInterceptionRewrites } from "../_chunks/interception-DRlhJWbu.js";
2
+ import { a as DEFAULT_PAGE_EXTENSIONS, i as scanRoutes, n as generateRouteMap, o as INTERCEPTION_MARKERS, r as classifySegment, t as collectInterceptionRewrites } from "../_chunks/interception-ErnB33JX.js";
3
3
  export { DEFAULT_PAGE_EXTENSIONS, INTERCEPTION_MARKERS, classifySegment, classifyUrlSegment, collectInterceptionRewrites, generateRouteMap, scanRoutes };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Typed `<Link>` codegen — interface augmentation generation.
3
+ *
4
+ * Extracted from `codegen.ts` to keep that file under the project's
5
+ * 500-line cap. This module owns everything that emits the
6
+ * `interface LinkFunction { ... }` augmentation blocks in the generated
7
+ * `.timber/timber-routes.d.ts`.
8
+ *
9
+ * Two augmentation blocks are emitted:
10
+ *
11
+ * 1. **Per-route discriminated union** (`formatTypedLinkOverloads`) —
12
+ * one call signature whose props is a union keyed on `href`. TS
13
+ * narrows by literal href and reports prop errors against the
14
+ * matched variant. See TIM-835 and `design/09-typescript.md`.
15
+ *
16
+ * 2. **Catch-all overloads** (`formatLinkCatchAllOverloads`) — external
17
+ * href literals (`http://`, `mailto:`, etc.) and a computed-string
18
+ * `<H extends string>` signature for runtime-computed paths.
19
+ *
20
+ * Block ordering is critical for error UX: per-route is emitted FIRST
21
+ * so that, after TS's "later overload set ordered first" merge rule,
22
+ * the discriminated union ends up LAST in resolution order — the
23
+ * overload TS reports against on failure.
24
+ */
25
+ import type { ParamEntry, RouteEntry } from './codegen-types.js';
26
+ /** Shared Link base-props type literal used in every emitted call signature. */
27
+ export declare const LINK_BASE_PROPS_TYPE = "Omit<import('react').AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> & { prefetch?: boolean; scroll?: boolean; preserveSearchParams?: true | string[]; onNavigate?: import('./client/link.js').OnNavigateHandler; children?: import('react').ReactNode }";
28
+ /**
29
+ * Build a TypeScript template literal pattern for a dynamic route.
30
+ * e.g. '/products/[id]' → '/products/${string}'
31
+ * '/blog/[...slug]' → '/blog/${string}'
32
+ * '/docs/[[...path]]' → '/docs/${string}' (also matches /docs)
33
+ * '/[org]/[repo]' → '/${string}/${string}'
34
+ */
35
+ export declare function buildResolvedPattern(route: RouteEntry): string | null;
36
+ /**
37
+ * Format the segmentParams type for Link overloads.
38
+ *
39
+ * Link params accept `string | number` for single dynamic segments
40
+ * (convenience — values are stringified at runtime). Catch-all and
41
+ * optional catch-all remain `string[]` / `string[] | undefined`.
42
+ *
43
+ * When the segment's params chain (TIM-834) declares a typed codec, the
44
+ * inferred type from the codec wins via a nested conditional.
45
+ */
46
+ export declare function formatLinkParamsType(params: ParamEntry[], importBase?: string): string;
47
+ /**
48
+ * Catch-all call signatures for `<Link>` — external hrefs and computed
49
+ * `string` variables. Emitted from codegen in a SEPARATE
50
+ * `declare module` block (declared AFTER the per-route block) so the TS
51
+ * "later overload set ordered first" rule places these catch-all
52
+ * signatures ahead of per-route in resolution order, leaving per-route
53
+ * as the final overload whose error message is reported on failure.
54
+ *
55
+ * The conditional `string extends H ? ... : never` protection preserves
56
+ * TIM-624's guarantee that unknown internal path literals don't match
57
+ * the catch-all — typos like `<Link href="/typo" />` still error.
58
+ */
59
+ export declare function formatLinkCatchAllOverloads(): string[];
60
+ /**
61
+ * Generate typed per-route Link call signatures via LinkFunction
62
+ * interface merging.
63
+ *
64
+ * TIM-835: This emits a SINGLE call signature whose props is a
65
+ * discriminated union keyed on `href`. TypeScript narrows the union by
66
+ * the literal `href` at the call site, then checks the rest of the
67
+ * props against the matched variant. When `segmentParams` or
68
+ * `searchParams` is wrong, TS reports the error against the matched
69
+ * variant — naming the user's actual `href` and the offending field.
70
+ *
71
+ * Before TIM-835, this function emitted N separate per-route overloads
72
+ * (one per route, sometimes two for dynamic routes). When ALL overloads
73
+ * failed, TS would pick an arbitrary failed overload (heuristically the
74
+ * "last tried") to render the diagnostic, often pointing at a route
75
+ * completely unrelated to the one the user wrote. The discriminated
76
+ * union sidesteps overload-resolution heuristics entirely.
77
+ *
78
+ * Open property shapes (`Record<string, unknown>` instead of `never`)
79
+ * for `segmentParams` on routes that don't declare them keep generic
80
+ * `LinkProps`-spreading wrappers compiling. (Aligned with TIM-833.)
81
+ *
82
+ * TIM-832: this function still emits the per-route block FIRST and the
83
+ * catch-all block follows, so per-route remains the LAST overload set in
84
+ * resolution order — the one TS reports against on failure. With a
85
+ * discriminated union there is only one call signature in this block,
86
+ * so the resolved-template-vs-pattern ordering reduces to placing the
87
+ * pattern variant before the resolved-template variant inside the union.
88
+ */
89
+ export declare function formatTypedLinkOverloads(routes: RouteEntry[], importBase?: string): string[];
90
+ //# sourceMappingURL=link-codegen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-codegen.d.ts","sourceRoot":"","sources":["../../src/routing/link-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGjE,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,8PAC4N,CAAC;AAE9P;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAkBrE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAWtF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAiDtD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAuF5F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.90",
3
+ "version": "0.2.0-alpha.91",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Shared codegen helpers — import-path computation, codec chain type
3
+ * builder, and searchParams type formatter.
4
+ *
5
+ * Extracted from `codegen.ts` so `link-codegen.ts` can use the same
6
+ * helpers without a cyclic import.
7
+ */
8
+
9
+ import { relative, posix } from 'node:path';
10
+ import type { ParamEntry, RouteEntry } from './codegen-types.js';
11
+
12
+ /**
13
+ * Compute a relative import specifier for a codec/page file, stripping
14
+ * the .ts/.tsx extension and resolving against the codegen output dir.
15
+ */
16
+ export function codecImportPath(codecFilePath: string, importBase: string | undefined): string {
17
+ const absPath = codecFilePath.replace(/\.(ts|tsx)$/, '');
18
+ if (importBase) {
19
+ return './' + relative(importBase, absPath).replace(/\\/g, '/');
20
+ }
21
+ return './' + posix.basename(absPath);
22
+ }
23
+
24
+ /**
25
+ * Build a TypeScript type expression that resolves a single param's
26
+ * codec by walking a chain of params.ts files in priority order.
27
+ *
28
+ * Each entry in the chain emits a conditional:
29
+ * `(typeof import(file))['segmentParams'] extends ParamsDefinition<infer T>
30
+ * ? K extends keyof T ? T[K] : <next>
31
+ * : <next>`
32
+ *
33
+ * The closest match (position 0 in the chain) is checked first; if its
34
+ * `segmentParams` definition declares the key, its inferred type wins.
35
+ * Otherwise we fall through to the next ancestor, and finally to the
36
+ * provided fallback. See TIM-834.
37
+ */
38
+ export function buildCodecChainType(
39
+ p: ParamEntry,
40
+ importBase: string | undefined,
41
+ fallback: string
42
+ ): string {
43
+ const files = p.codecFilePaths;
44
+ if (!files || files.length === 0) return fallback;
45
+ const key = JSON.stringify(p.name);
46
+ // Build the nested conditional from the inside out so the closest
47
+ // entry (files[0]) ends up as the OUTERMOST check.
48
+ let inner = fallback;
49
+ for (let i = files.length - 1; i >= 0; i--) {
50
+ const importPath = codecImportPath(files[i], importBase);
51
+ inner =
52
+ `((typeof import('${importPath}'))['segmentParams'] extends ` +
53
+ `import('@timber-js/app/segment-params').ParamsDefinition<infer T> ` +
54
+ `? (${key} extends keyof T ? T[${key}] : ${inner}) : ${inner})`;
55
+ }
56
+ return inner;
57
+ }
58
+
59
+ /**
60
+ * Format the searchParams type for a route entry.
61
+ *
62
+ * When a page.tsx (or params.ts) exports searchParams, we reference its
63
+ * inferred type via an import type. The import path is relative to
64
+ * `importBase` (the directory where the .d.ts will be written). When
65
+ * importBase is undefined, falls back to a bare relative path.
66
+ */
67
+ export function formatSearchParamsType(route: RouteEntry, importBase?: string): string {
68
+ if (route.hasSearchParams && route.searchParamsPagePath) {
69
+ const importPath = codecImportPath(route.searchParamsPagePath, importBase);
70
+ // Extract the type from the named 'searchParams' export of the page module.
71
+ return `(typeof import('${importPath}'))['searchParams'] extends import('@timber-js/app/search-params').SearchParamsDefinition<infer T> ? T : never`;
72
+ }
73
+ return '{}';
74
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared types for route codegen modules.
3
+ *
4
+ * Lives in its own file so `link-codegen.ts` and `codegen.ts` can import
5
+ * the same `RouteEntry` / `ParamEntry` shapes without a cyclic
6
+ * dependency between the two.
7
+ */
8
+
9
+ /** A single route entry extracted from the segment tree. */
10
+ export interface RouteEntry {
11
+ /** URL path pattern (e.g. "/products/[id]") */
12
+ urlPath: string;
13
+ /** Accumulated params from all ancestor dynamic segments */
14
+ params: ParamEntry[];
15
+ /** Whether the page.tsx exports searchParams */
16
+ hasSearchParams: boolean;
17
+ /** Absolute path to the page file that exports searchParams (for import paths) */
18
+ searchParamsPagePath?: string;
19
+ /** Whether this is an API route (route.ts) vs page route */
20
+ isApiRoute: boolean;
21
+ }
22
+
23
+ export interface ParamEntry {
24
+ name: string;
25
+ type: 'string' | 'string[]' | 'string[] | undefined';
26
+ /**
27
+ * Ordered list of params.ts (or layout/page) files that may declare a
28
+ * codec for this param, in priority order — closest match first.
29
+ *
30
+ * Position 0 is the segment's own params.ts (if present and exporting
31
+ * `segmentParams`); subsequent entries are ancestor params.ts files
32
+ * walking from closest ancestor up to the route root. The first entry
33
+ * in the chain whose `segmentParams` definition declares this param key
34
+ * wins. See TIM-834.
35
+ */
36
+ codecFilePaths?: string[];
37
+ }