@timber-js/app 0.2.0-alpha.114 → 0.2.0-alpha.115

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.
@@ -1 +1 @@
1
- {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/plugins/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAoBlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA0F1D,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAmNxD"}
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/plugins/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAwBlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA2F1D,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAuNxD"}
@@ -1,23 +1,36 @@
1
1
  /**
2
- * _segment type generation and directory-to-route mapping.
2
+ * $segment type generation and directory-to-route mapping.
3
3
  *
4
- * Generates per-route _segment.d.ts files in .timber/types/ that mirror
4
+ * Generates per-route $segment.d.ts files in .timber/types/ that mirror
5
5
  * the app/ directory structure. TypeScript's rootDirs feature makes these
6
- * importable as './_segment' from route files.
6
+ * importable as './$segment' from route files.
7
7
  *
8
- * Design doc: design/41-global-params.md §The _segment Module
8
+ * Design doc: design/41-global-params.md §The $segment Module
9
9
  */
10
10
  import type { RouteTree } from './types.js';
11
+ /** Info carried per directory for type generation. */
12
+ export interface SegmentInfo {
13
+ /** The URL route pattern (groups and slots stripped). e.g. '/[artistSlug]/[year]' */
14
+ route: string;
15
+ /** The full tree path including groups and slots — uniquely identifies a segment.
16
+ * e.g. '/(browse)/[artistSlug]/@shows/[year]' */
17
+ segmentPath: string;
18
+ }
11
19
  /**
12
- * Build a map of absolute filesystem directory → route pattern.
13
- * Used by resolveId to map a _segment import's importer to the correct route.
20
+ * Build a map of absolute filesystem directory → segment info.
21
+ * Used by resolveId to map a $segment import's importer to the correct route,
22
+ * and by writeSegmentTypes to generate per-route $segment.d.ts files.
14
23
  *
15
24
  * Walks every segment (not just leaves) so layouts and middleware
16
- * at any depth can import _segment.
25
+ * at any depth can import $segment.
17
26
  */
18
- export declare function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, string>;
27
+ export declare function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, SegmentInfo>;
19
28
  /**
20
- * Generate per-route _segment.d.ts files in .timber/types/ mirroring
29
+ * Generate the full $segment.d.ts content for a segment.
30
+ */
31
+ export declare function generateSegmentDts(info: SegmentInfo): string;
32
+ /**
33
+ * Generate per-route $segment.d.ts files in .timber/types/ mirroring
21
34
  * the app/ directory structure. These files are importable via TypeScript's
22
35
  * rootDirs feature.
23
36
  */
@@ -1 +1 @@
1
- {"version":3,"file":"segment-codegen.d.ts","sourceRoot":"","sources":["../../src/routing/segment-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBvF;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAqBf"}
1
+ {"version":3,"file":"segment-codegen.d.ts","sourceRoot":"","sources":["../../src/routing/segment-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAEzD,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC1B,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IACd;sDACkD;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAuB5F;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAM5D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAqBf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.114",
3
+ "version": "0.2.0-alpha.115",
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",
@@ -15,7 +15,11 @@ import { join } from 'node:path';
15
15
  import { scanRoutes } from '../routing/scanner.js';
16
16
  import { generateRouteMap } from '../routing/codegen.js';
17
17
  import { collectInterceptionRewrites } from '../routing/interception.js';
18
- import { buildDirToRouteMap, writeSegmentTypes } from '../routing/segment-codegen.js';
18
+ import {
19
+ buildDirToRouteMap,
20
+ writeSegmentTypes,
21
+ type SegmentInfo,
22
+ } from '../routing/segment-codegen.js';
19
23
  import {
20
24
  collectDynamicSegments,
21
25
  findSchemaFile,
@@ -56,12 +60,13 @@ const RESOLVED_SEARCH_PARAMS_REGISTRY_ID = `\0${SEARCH_PARAMS_REGISTRY_ID}`;
56
60
  const SCHEMA_ID = 'virtual:timber-schema';
57
61
  const RESOLVED_SCHEMA_ID = `\0${SCHEMA_ID}`;
58
62
 
59
- // TIM-935: _segment virtual module prefix.
63
+ // TIM-935: $segment virtual module prefix.
64
+ // Resolved ID encodes segment info as JSON after the prefix.
60
65
  //
61
66
  // Each route directory gets a virtual module that exports the route
62
- // pattern string. `import { route } from './_segment'` resolves to
63
- // `\0_segment:<route-pattern>` which returns `export const route = '...'`.
64
- const SEGMENT_PREFIX = '\0_segment:';
67
+ // and segmentPath. `import { route, segmentPath } from './$segment'`
68
+ // resolves to `\0$segment:<json>`.
69
+ const SEGMENT_PREFIX = '\0$segment:';
65
70
 
66
71
  /**
67
72
  * File convention names we track for changes that require manifest regeneration.
@@ -122,8 +127,8 @@ export function timberRouting(ctx: PluginContext): Plugin {
122
127
  /** Track warned files to avoid repeating warnings on rescan. */
123
128
  const warnedFiles = new Set<string>();
124
129
 
125
- /** Maps absolute filesystem directory → route pattern for _segment resolution. */
126
- let dirToRoute: Map<string, string> = new Map();
130
+ /** Maps absolute filesystem directory → segment info for $segment resolution. */
131
+ let dirToRoute: Map<string, SegmentInfo> = new Map();
127
132
 
128
133
  function rescan(): void {
129
134
  // Check app/ directory exists before scanning
@@ -204,19 +209,19 @@ export function timberRouting(ctx: PluginContext): Plugin {
204
209
  return RESOLVED_SCHEMA_ID;
205
210
  }
206
211
 
207
- // TIM-935: _segment virtual module.
208
- // Resolves `import { route } from './_segment'` based on the
212
+ // TIM-935: $segment virtual module.
213
+ // Resolves `import { route } from './$segment'` based on the
209
214
  // importing file's directory in the app/ tree.
210
215
  if (
211
216
  importer &&
212
- (cleanId === './_segment' || cleanId === './_segment.js' || cleanId === './_segment.ts')
217
+ (cleanId === './$segment' || cleanId === './$segment.js' || cleanId === './$segment.ts')
213
218
  ) {
214
219
  // Strip \0 prefix from importer if present
215
220
  const cleanImporter = importer.startsWith('\0') ? importer.slice(1) : importer;
216
221
  const importerDir = cleanImporter.substring(0, cleanImporter.lastIndexOf('/'));
217
- const routePattern = dirToRoute.get(importerDir);
218
- if (routePattern !== undefined) {
219
- return `${SEGMENT_PREFIX}${routePattern}`;
222
+ const segmentInfo = dirToRoute.get(importerDir);
223
+ if (segmentInfo !== undefined) {
224
+ return `${SEGMENT_PREFIX}${JSON.stringify(segmentInfo)}`;
220
225
  }
221
226
  }
222
227
 
@@ -249,10 +254,14 @@ export function timberRouting(ctx: PluginContext): Plugin {
249
254
  return generateSchemaModule(ctx.appDir);
250
255
  }
251
256
 
252
- // TIM-935: _segment virtual module — exports the route pattern string.
257
+ // TIM-935: $segment virtual module — exports route and segmentPath.
253
258
  if (id.startsWith(SEGMENT_PREFIX)) {
254
- const routePattern = id.slice(SEGMENT_PREFIX.length);
255
- return `export const route = ${JSON.stringify(routePattern)};\n`;
259
+ const info: SegmentInfo = JSON.parse(id.slice(SEGMENT_PREFIX.length));
260
+ return [
261
+ `export const ROUTE = ${JSON.stringify(info.route)};`,
262
+ `export const SEGMENT_PATH = ${JSON.stringify(info.segmentPath)};`,
263
+ '',
264
+ ].join('\n');
256
265
  }
257
266
 
258
267
  return null;
@@ -383,7 +392,7 @@ function invalidateManifest(server: ViteDevServer): void {
383
392
  }
384
393
  }
385
394
 
386
- // TIM-935: Invalidate all _segment virtual modules (prefixed \0_segment:)
395
+ // TIM-935: Invalidate all $segment virtual modules (prefixed \0$segment:)
387
396
  for (const mod of env.moduleGraph.idToModuleMap.values()) {
388
397
  if (mod.id?.startsWith(SEGMENT_PREFIX)) {
389
398
  env.moduleGraph.invalidateModule(mod);
@@ -1,47 +1,72 @@
1
1
  /**
2
- * _segment type generation and directory-to-route mapping.
2
+ * $segment type generation and directory-to-route mapping.
3
3
  *
4
- * Generates per-route _segment.d.ts files in .timber/types/ that mirror
4
+ * Generates per-route $segment.d.ts files in .timber/types/ that mirror
5
5
  * the app/ directory structure. TypeScript's rootDirs feature makes these
6
- * importable as './_segment' from route files.
6
+ * importable as './$segment' from route files.
7
7
  *
8
- * Design doc: design/41-global-params.md §The _segment Module
8
+ * Design doc: design/41-global-params.md §The $segment Module
9
9
  */
10
10
 
11
11
  import { writeFile, mkdir } from 'node:fs/promises';
12
12
  import { join } from 'node:path';
13
13
  import type { RouteTree, SegmentNode } from './types.js';
14
14
 
15
+ /** Info carried per directory for type generation. */
16
+ export interface SegmentInfo {
17
+ /** The URL route pattern (groups and slots stripped). e.g. '/[artistSlug]/[year]' */
18
+ route: string;
19
+ /** The full tree path including groups and slots — uniquely identifies a segment.
20
+ * e.g. '/(browse)/[artistSlug]/@shows/[year]' */
21
+ segmentPath: string;
22
+ }
23
+
15
24
  /**
16
- * Build a map of absolute filesystem directory → route pattern.
17
- * Used by resolveId to map a _segment import's importer to the correct route.
25
+ * Build a map of absolute filesystem directory → segment info.
26
+ * Used by resolveId to map a $segment import's importer to the correct route,
27
+ * and by writeSegmentTypes to generate per-route $segment.d.ts files.
18
28
  *
19
29
  * Walks every segment (not just leaves) so layouts and middleware
20
- * at any depth can import _segment.
30
+ * at any depth can import $segment.
21
31
  */
22
- export function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, string> {
23
- const map = new Map<string, string>();
32
+ export function buildDirToRouteMap(tree: RouteTree, appDir: string): Map<string, SegmentInfo> {
33
+ const map = new Map<string, SegmentInfo>();
24
34
 
25
- function walk(node: SegmentNode, fsPath: string): void {
26
- const routePattern = node.urlPath || '/';
27
- map.set(fsPath, routePattern);
35
+ function walk(node: SegmentNode, fsPath: string, treePath: string): void {
36
+ map.set(fsPath, {
37
+ route: node.urlPath || '/',
38
+ segmentPath: treePath || '/',
39
+ });
28
40
 
29
41
  for (const child of node.children) {
30
42
  const childDir = join(fsPath, child.segmentName);
31
- walk(child, childDir);
43
+ const childTreePath = `${treePath}/${child.segmentName}`;
44
+ walk(child, childDir, childTreePath);
32
45
  }
33
46
  for (const [slotName, slotNode] of Object.entries(node.slots)) {
34
- const slotDir = join(fsPath, slotName);
35
- walk(slotNode, slotDir);
47
+ const slotDir = join(fsPath, `@${slotName}`);
48
+ const slotTreePath = `${treePath}/@${slotName}`;
49
+ walk(slotNode, slotDir, slotTreePath);
36
50
  }
37
51
  }
38
52
 
39
- walk(tree.root, appDir);
53
+ walk(tree.root, appDir, '');
40
54
  return map;
41
55
  }
42
56
 
43
57
  /**
44
- * Generate per-route _segment.d.ts files in .timber/types/ mirroring
58
+ * Generate the full $segment.d.ts content for a segment.
59
+ */
60
+ export function generateSegmentDts(info: SegmentInfo): string {
61
+ return [
62
+ `export const ROUTE: '${info.route}';`,
63
+ `export const SEGMENT_PATH: '${info.segmentPath}';`,
64
+ '',
65
+ ].join('\n');
66
+ }
67
+
68
+ /**
69
+ * Generate per-route $segment.d.ts files in .timber/types/ mirroring
45
70
  * the app/ directory structure. These files are importable via TypeScript's
46
71
  * rootDirs feature.
47
72
  */
@@ -57,15 +82,15 @@ export async function writeSegmentTypes(
57
82
  const map = buildDirToRouteMap(routeTree, appDir);
58
83
 
59
84
  const writes: Promise<void>[] = [];
60
- for (const [fsDir, routePattern] of map) {
85
+ for (const [fsDir, info] of map) {
61
86
  // Mirror the app/ structure under .timber/types/
62
87
  const relDir = fsDir.slice(appDir.length); // e.g. '/[artistSlug]/[year]'
63
88
  const segmentDir = join(typesBase, relDir);
64
- const segmentPath = join(segmentDir, '_segment.d.ts');
65
- const content = `export const route: '${routePattern}';\n`;
89
+ const segmentFile = join(segmentDir, '$segment.d.ts');
90
+ const content = generateSegmentDts(info);
66
91
 
67
92
  writes.push(
68
- mkdir(segmentDir, { recursive: true }).then(() => writeFile(segmentPath, content, 'utf-8'))
93
+ mkdir(segmentDir, { recursive: true }).then(() => writeFile(segmentFile, content, 'utf-8'))
69
94
  );
70
95
  }
71
96
 
@@ -115,7 +115,7 @@ export function getSegmentParams(route?: string): Record<string, string | string
115
115
 
116
116
  // Dev-mode validation: warn when the route argument expects dynamic
117
117
  // segments that don't exist on the actual matched route. This catches
118
- // bugs like passing a child route's _segment to a parent layout.
118
+ // bugs like passing a child route's $segment to a parent layout.
119
119
  // See design/41-global-params.md §Runtime Validation
120
120
  if (route && isDebug() && store.matchedRoutePattern) {
121
121
  const expected = extractDynamicSegments(route);