@timber-js/app 0.2.0-alpha.165 → 0.2.0-alpha.166

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/_chunks/{actions-CSDD6x7U.js → actions-CDPfMp_I.js} +3 -3
  2. package/dist/_chunks/{actions-CSDD6x7U.js.map → actions-CDPfMp_I.js.map} +1 -1
  3. package/dist/_chunks/{cache-api-eb1gydM7.js → cache-api-DygSeKCB.js} +2 -2
  4. package/dist/_chunks/{cache-api-eb1gydM7.js.map → cache-api-DygSeKCB.js.map} +1 -1
  5. package/dist/_chunks/{cli-schema-sync-mGfRbjh2.js → cli-schema-sync-EXGYPhI2.js} +3 -3
  6. package/dist/_chunks/{cli-schema-sync-mGfRbjh2.js.map → cli-schema-sync-EXGYPhI2.js.map} +1 -1
  7. package/dist/_chunks/{define-CFmvb4Bt.js → define-COtkxMRT.js} +16 -28
  8. package/dist/_chunks/define-COtkxMRT.js.map +1 -0
  9. package/dist/_chunks/{define-Bssfp6ot.js → define-c4au4I9R.js} +2 -2
  10. package/dist/_chunks/{define-Bssfp6ot.js.map → define-c4au4I9R.js.map} +1 -1
  11. package/dist/_chunks/{logger-B_O6-mdJ.js → logger-t3uxAmbX.js} +3 -3
  12. package/dist/_chunks/{logger-B_O6-mdJ.js.map → logger-t3uxAmbX.js.map} +1 -1
  13. package/dist/_chunks/{plugin-context-BnaiU_cF.js → plugin-context---kTF5v8.js} +2 -2
  14. package/dist/_chunks/{plugin-context-BnaiU_cF.js.map → plugin-context---kTF5v8.js.map} +1 -1
  15. package/dist/_chunks/{resolve-schema-3iUvBV5T.js → resolve-schema-Dz3fcFUo.js} +2 -2
  16. package/dist/_chunks/{resolve-schema-3iUvBV5T.js.map → resolve-schema-Dz3fcFUo.js.map} +1 -1
  17. package/dist/_chunks/{schema-bridge-BY3QLBL7.js → schema-bridge-DT_Tn0Xf.js} +2 -2
  18. package/dist/_chunks/{schema-bridge-BY3QLBL7.js.map → schema-bridge-DT_Tn0Xf.js.map} +1 -1
  19. package/dist/_chunks/{use-query-states-CbeQmext.js → use-query-states-DFvWd-EA.js} +65 -7
  20. package/dist/_chunks/use-query-states-DFvWd-EA.js.map +1 -0
  21. package/dist/_chunks/{walkers-BL3MCMgO.js → walkers-DBVzXuWc.js} +3 -3
  22. package/dist/_chunks/{walkers-BL3MCMgO.js.map → walkers-DBVzXuWc.js.map} +1 -1
  23. package/dist/adapters/nitro.js +1 -1
  24. package/dist/cache/index.js +1 -1
  25. package/dist/cli.js +2 -2
  26. package/dist/client/internal.js +1 -1
  27. package/dist/client/use-query-states.d.ts.map +1 -1
  28. package/dist/codec.js +1 -1
  29. package/dist/cookies/index.js +1 -1
  30. package/dist/dev-tools/logs.d.ts +15 -1
  31. package/dist/dev-tools/logs.d.ts.map +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +104 -20
  34. package/dist/index.js.map +1 -1
  35. package/dist/params/index.js +1 -1
  36. package/dist/routing/index.js +2 -2
  37. package/dist/schema-bridge.d.ts.map +1 -1
  38. package/dist/search-params/define.d.ts +35 -4
  39. package/dist/search-params/define.d.ts.map +1 -1
  40. package/dist/search-params/index.js +3 -7
  41. package/dist/search-params/index.js.map +1 -1
  42. package/dist/search-params/wrappers.d.ts +2 -2
  43. package/dist/search-params/wrappers.d.ts.map +1 -1
  44. package/dist/segment-params/index.js +1 -1
  45. package/dist/server/index.js +2 -2
  46. package/dist/server/internal.js +8 -6
  47. package/dist/server/internal.js.map +1 -1
  48. package/dist/server/stream-utils.d.ts.map +1 -1
  49. package/docs/api/33-api-search-params.mdx +13 -0
  50. package/docs/learn/00-introduction.mdx +1 -2
  51. package/package.json +8 -7
  52. package/src/cli.ts +0 -0
  53. package/src/client/use-query-states.ts +99 -7
  54. package/src/dev-tools/logs.ts +119 -34
  55. package/src/index.ts +14 -1
  56. package/src/schema-bridge.ts +4 -3
  57. package/src/search-params/define.ts +57 -51
  58. package/src/search-params/wrappers.ts +17 -26
  59. package/src/server/stream-utils.ts +8 -6
  60. package/LICENSE +0 -8
  61. package/dist/_chunks/define-CFmvb4Bt.js.map +0 -1
  62. package/dist/_chunks/use-query-states-CbeQmext.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import { o as __exportAll } from "./dist-BA3u1z3W.js";
2
+ import { parseAst } from "vite";
2
3
  import { join, resolve } from "node:path";
3
4
  import { createRequire } from "node:module";
4
5
  import { existsSync } from "node:fs";
5
- import { parseAst } from "vite";
6
6
  import { performance } from "node:perf_hooks";
7
7
  //#region src/utils/startup-timer.ts
8
8
  /**
@@ -312,4 +312,4 @@ function mergeFileConfig(inline, fileConfig) {
312
312
  //#endregion
313
313
  export { resolveAppDir as a, createNoopTimer as c, plugin_context_exports as i, loadTimberConfigFile as n, resolveBuildDir as o, mergeFileConfig as r, resolveClientJavascript as s, createPluginContext as t };
314
314
 
315
- //# sourceMappingURL=plugin-context-BnaiU_cF.js.map
315
+ //# sourceMappingURL=plugin-context---kTF5v8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-context-BnaiU_cF.js","names":[],"sources":["../../src/utils/startup-timer.ts","../../src/plugin-context.ts"],"sourcesContent":["/**\n * Startup timer — records named phases with their durations.\n *\n * Used by the plugin system to instrument cold start and report a\n * timing breakdown in dev mode. Zero overhead in production (disabled).\n *\n * See design/18-build-system.md, TIM-155.\n */\n\nimport { performance } from 'node:perf_hooks';\n\nexport interface PhaseRecord {\n name: string;\n startMs: number;\n durationMs: number;\n}\n\nexport interface StartupTimer {\n /** Mark the beginning of a named phase. */\n start(phase: string): void;\n /** Mark the end of a named phase. Returns duration in ms. */\n end(phase: string): number;\n /** Get all completed phase records, ordered by start time. */\n getPhases(): PhaseRecord[];\n /** Total elapsed time from first start() to last end(). */\n totalMs(): number;\n /** Format a human-readable summary string. */\n formatSummary(): string;\n}\n\n/**\n * Create a startup timer that records phase durations.\n */\nexport function createStartupTimer(): StartupTimer {\n const pending = new Map<string, number>();\n const phases: PhaseRecord[] = [];\n\n return {\n start(phase: string): void {\n pending.set(phase, performance.now());\n },\n\n end(phase: string): number {\n const startMs = pending.get(phase);\n if (startMs === undefined) {\n return 0;\n }\n pending.delete(phase);\n const durationMs = performance.now() - startMs;\n phases.push({ name: phase, startMs, durationMs });\n return durationMs;\n },\n\n getPhases(): PhaseRecord[] {\n return [...phases].sort((a, b) => a.startMs - b.startMs);\n },\n\n totalMs(): number {\n if (phases.length === 0) return 0;\n const sorted = this.getPhases();\n const first = sorted[0];\n const last = sorted[sorted.length - 1];\n return last.startMs + last.durationMs - first.startMs;\n },\n\n formatSummary(): string {\n const sorted = this.getPhases();\n if (sorted.length === 0) return 'No phases recorded.';\n\n const lines = sorted.map((p) => {\n const ms = p.durationMs.toFixed(1);\n return ` ${p.name.padEnd(30)} ${ms.padStart(8)}ms`;\n });\n\n const total = this.totalMs().toFixed(1);\n lines.push(` ${'total'.padEnd(30)} ${total.padStart(8)}ms`);\n\n return ['[timber] startup timing:', ...lines].join('\\n');\n },\n };\n}\n\n/**\n * No-op timer for production builds — all methods are empty.\n */\nexport function createNoopTimer(): StartupTimer {\n return {\n start() {},\n end() {\n return 0;\n },\n getPhases() {\n return [];\n },\n totalMs() {\n return 0;\n },\n formatSummary() {\n return '';\n },\n };\n}\n","/**\n * Plugin context — internal types and helpers for timber sub-plugins.\n *\n * These are NOT part of the user-facing API. They are consumed by\n * sub-plugins (routing, entries, shims, etc.) via relative imports.\n * The root entry point (index.ts) does not re-export them.\n *\n * Design doc: 18-build-system.md §\"Shared Plugin Context\"\n */\n\nimport { existsSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { createRequire } from 'node:module';\nimport { parseAst } from 'vite';\nimport type { RouteTree } from './routing/types';\nimport type { BuildManifest } from './server/build-manifest';\nimport type { CaptureSummary } from './server/prebuilt-builder';\nimport type { StartupTimer } from './utils/startup-timer';\nimport { createStartupTimer } from './utils/startup-timer';\nimport type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';\nimport type { HoldingServer } from './dev-tools/holding-server.js';\nimport type { ProgramNode } from './plugins/callsite-ast.js';\n\n// Re-export for sub-plugin convenience — they import from plugin-context.ts\nexport type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';\n\n// ── Client JavaScript config ─────────────────────────────────────────────\n\n/** Fully resolved client JavaScript configuration (no optionals). */\nexport interface ResolvedClientJavascript {\n disabled: boolean;\n enableHMRInDev: boolean;\n}\n\n/**\n * Resolve `clientJavascript` into a fully resolved config.\n */\nexport function resolveClientJavascript(config: TimberUserConfig): ResolvedClientJavascript {\n if (config.clientJavascript !== undefined) {\n if (typeof config.clientJavascript === 'boolean') {\n return {\n disabled: !config.clientJavascript,\n enableHMRInDev: !config.clientJavascript,\n };\n }\n return {\n disabled: config.clientJavascript.disabled,\n enableHMRInDev: config.clientJavascript.enableHMRInDev ?? config.clientJavascript.disabled,\n };\n }\n return { disabled: false, enableHMRInDev: false };\n}\n\n// ── Plugin Context ────────────────────────────────────────────────────────\n\n/**\n * Shared context object passed to all sub-plugins via closure.\n *\n * Sub-plugins communicate through this context — not through Vite's\n * plugin API or global state.\n * See design/18-build-system.md §\"Shared Plugin Context\".\n */\nexport interface PluginContext {\n config: TimberUserConfig;\n /** Resolved client JavaScript configuration */\n clientJavascript: ResolvedClientJavascript;\n /** The scanned route tree (populated by timber-routing, consumed by timber-entries) */\n routeTree: RouteTree | null;\n /** Absolute path to the app/ directory */\n appDir: string;\n /** Absolute path to the project root */\n root: string;\n /** Whether the dev server is running (set by timber-root-sync in configResolved) */\n dev: boolean;\n /** CSS build manifest (populated by adapter after client build, null in dev) */\n buildManifest: BuildManifest | null;\n /** Per-build deployment ID for version skew detection (null in dev) */\n deploymentId: string | null;\n /** Startup timer for profiling cold start phases (active in dev, no-op in prod) */\n timer: StartupTimer;\n /** Holding server that binds the port during dev startup (closed in timber-dev-server) */\n holdingServer?: HoldingServer | null;\n /**\n * Resolved absolute path to the build output directory.\n *\n * Defaults to `<root>/.timber/dist`. Can be overridden via\n * `timber.config.ts` `buildDir` or Vite's `build.outDir`.\n * Timber config takes precedence when both are set.\n *\n * Used by adapter-build and cli preview.\n */\n buildDir: string;\n /**\n * Per-route client reference keys captured during the RSC build.\n * Maps absolute route file paths to the set of client reference\n * hash keys they transitively import. Used by adapter-build to\n * populate per-route modulepreload entries from clientReferenceDeps.\n */\n routeClientRefKeys?: Map<string, Set<string>>;\n /**\n * Maps absolute module IDs of client references in non-facade RSC\n * chunks to the absolute paths of their consuming facade modules.\n * Built during the RSC `generateBundle` phase by timber-chunks,\n * consumed by `clientChunkGroup` to reclassify `shared:` modules\n * that are only used by a single route boundary.\n * See design/27-chunking-strategy.md, TIM-1107.\n */\n sharedModuleFacades?: Map<string, string[]>;\n /**\n * Module ID → prebuilt component IDs declared in it. Recorded by the\n * timber-prebuilt transform (RSC environment, production builds only),\n * consumed by the RSC `generateBundle` module-graph walk that computes\n * `routePrebuiltComponents`. See design/44-render-at-build-time.md.\n */\n prebuiltModuleComponents?: Map<string, string[]>;\n /**\n * Module ID → mapping of variable name to component ID for the prebuilt\n * components declared in that module. Used at `generateBundle` time to\n * trace imports from other modules back to specific component IDs.\n */\n prebuiltBindingNames?: Map<string, Map<string, string>>;\n /**\n * Component ID → distinct static call-site prop objects captured from\n * JSX usage sites during the transform and generateBundle phases.\n * Consumed by the post-build capture pass to render with actual props\n * instead of empty `{}`. See design/44 §Props Derivation.\n */\n prebuiltCallSiteProps?: Map<string, Record<string, unknown>[]>;\n /**\n * Route file path (page or layout, absolute) → prebuilt component IDs\n * transitively reachable from it. Computed during RSC `generateBundle`,\n * consumed by the post-build capture pass (TIM-1119) to decide which\n * components to render under which route's param combos.\n */\n routePrebuiltComponents?: Map<string, string[]>;\n /**\n * Page file paths that were desugared from `export const prerender` by\n * timber-prerender-sugar. The prebuilt transform uses this to verify\n * the generated `cache.component(...)` call was picked up.\n */\n prerenderSugarPages?: Set<string>;\n /**\n * Component ID → built chunk file name for prebuilt-declaring modules\n * that are only reachable via dynamic imports (import.meta.glob, lazy\n * routes). The capture pass imports these chunks to trigger\n * cache.component() registration before rendering. Populated during\n * RSC generateBundle alongside routePrebuiltComponents.\n */\n prebuiltLazyChunks?: Map<string, string>;\n /** Post-build prebuilt capture summary (populated by timber-prebuilt, consumed by build-report). */\n captureSummary?: CaptureSummary;\n /**\n * Content-keyed AST parse memo shared across cache/prebuilt/prerender-sugar\n * transforms. Collapses both cross-plugin and cross-environment duplicate\n * parses: identical code hits, rewritten code misses (correct because Vite\n * transforms chain — a rewrite produces different code). Bounded LRU (32\n * entries) so it doesn't hold programs for the whole build.\n *\n * Consumers MUST treat the returned ProgramNode as read-only — they collect\n * ranges and rewrite CODE via MagicString; the AST itself is never mutated.\n */\n parseCached?: ParseMemo;\n}\n\n// ── AST parse memo ───────────────────────────────────────────────────────\n\nconst PARSE_MEMO_CAPACITY = 32;\n\n/**\n * Content-keyed LRU memo for `parseAst`. Transforms for the same module run\n * back-to-back across plugins and environments, so a tiny window captures the\n * wins. The returned ProgramNode is shared — callers MUST NOT mutate it.\n */\nexport class ParseMemo {\n private cache = new Map<string, ProgramNode>();\n\n /**\n * Parse code, returning a cached ProgramNode if the same code was parsed\n * before. The returned AST MUST NOT be mutated — it may be shared across\n * multiple consumers.\n */\n parse(code: string): ProgramNode {\n const existing = this.cache.get(code);\n if (existing) {\n // LRU refresh: move to end\n this.cache.delete(code);\n this.cache.set(code, existing);\n return existing;\n }\n const program = parseAst(code, { lang: 'tsx' }) as unknown as ProgramNode;\n if (this.cache.size >= PARSE_MEMO_CAPACITY) {\n // Evict oldest (first inserted)\n const firstKey = this.cache.keys().next().value!;\n this.cache.delete(firstKey);\n }\n this.cache.set(code, program);\n return program;\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n\n// ── App directory resolution ──────────────────────────────────────────────\n\n/**\n * Resolve the app directory. Checks (in order):\n * 1. Explicit `configAppDir` from timber.config.ts\n * 2. `<root>/app`\n * 3. `<root>/src/app`\n *\n * Throws if none exist.\n */\nexport function resolveAppDir(root: string, configAppDir?: string): string {\n if (configAppDir) {\n const explicit = join(root, configAppDir);\n if (!existsSync(explicit)) {\n throw new Error(`[timber] Configured appDir \"${configAppDir}\" does not exist at ${explicit}`);\n }\n return explicit;\n }\n\n const rootApp = join(root, 'app');\n if (existsSync(rootApp)) return rootApp;\n\n const srcApp = join(root, 'src', 'app');\n if (existsSync(srcApp)) return srcApp;\n\n throw new Error(\n `[timber] Could not find app directory. Expected \"app/\" or \"src/app/\" in ${root}. ` +\n `You can set appDir in timber.config.ts to specify a custom location.`\n );\n}\n\n// ── Plugin context factory ────────────────────────────────────────────────\n\nexport function createPluginContext(config?: TimberUserConfig, root?: string): PluginContext {\n const projectRoot = root ?? process.cwd();\n const resolvedConfig: TimberUserConfig = { ...config };\n return {\n config: resolvedConfig,\n clientJavascript: resolveClientJavascript(resolvedConfig),\n routeTree: null,\n appDir: join(projectRoot, 'app'),\n root: projectRoot,\n dev: false,\n buildManifest: null,\n deploymentId: null,\n timer: createStartupTimer(),\n holdingServer: null,\n buildDir: resolveBuildDir(projectRoot, resolvedConfig.buildDir),\n parseCached: new ParseMemo(),\n };\n}\n\n// ── Build directory resolution ────────────────────────────────────────────\n\n/** Default build output directory (relative to root). */\nexport const DEFAULT_BUILD_DIR = join('.timber', 'dist');\n\n/**\n * Resolve the build output directory.\n *\n * Priority:\n * 1. Explicit `timberBuildDir` from timber.config.ts\n * 2. Explicit `viteBuildOutDir` from Vite's build.outDir (if not the default 'dist')\n * 3. Default: `.timber/dist`\n *\n * Returns an absolute path.\n */\nexport function resolveBuildDir(\n root: string,\n timberBuildDir?: string,\n viteBuildOutDir?: string\n): string {\n if (timberBuildDir) {\n return resolve(root, timberBuildDir);\n }\n if (viteBuildOutDir && viteBuildOutDir !== 'dist') {\n return resolve(root, viteBuildOutDir);\n }\n return join(root, DEFAULT_BUILD_DIR);\n}\n\n// ── Config file loading ───────────────────────────────────────────────────\n\n/**\n * Map known timber.config load failures to actionable hints (TIM-1040).\n * Returns null when the error has no known remedy beyond its own message.\n *\n * Known failure modes of loading timber.config.ts via native require():\n * - Node < 22.18 has no default type stripping → ERR_UNKNOWN_FILE_EXTENSION\n * - Non-erasable TS syntax (enums, namespaces, parameter properties)\n * → ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX / ERR_INVALID_TYPESCRIPT_SYNTAX\n * - ESM syntax in a `\"type\": \"commonjs\"` package → the stripped .ts is\n * parsed as CJS and throws SyntaxError: \"Unexpected token 'export'\"\n * (export statements) or \"Cannot use import statement outside a\n * module\" (import statements)\n */\nexport function configLoadHint(configName: string, err: unknown): string | null {\n const code = (err as NodeJS.ErrnoException | null)?.code;\n const isTs = configName.endsWith('.ts');\n\n if (isTs && code === 'ERR_UNKNOWN_FILE_EXTENSION') {\n return (\n 'Loading a TypeScript config requires Node.js 22.18+ (built-in type stripping). ' +\n 'Upgrade Node.js or rewrite the config as timber.config.js.'\n );\n }\n if (code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX' || code === 'ERR_INVALID_TYPESCRIPT_SYNTAX') {\n return (\n 'Node.js loads the config with type stripping, which only supports erasable TypeScript syntax — ' +\n 'replace enums, namespaces, and constructor parameter properties with plain objects and types.'\n );\n }\n if (\n isTs &&\n err instanceof SyntaxError &&\n /Unexpected token '(export|import)'|Cannot use import statement outside a module/.test(\n err.message\n )\n ) {\n return (\n 'The config uses ESM syntax but Node.js parsed it as CommonJS — ' +\n 'set \"type\": \"module\" in package.json, or use module.exports instead of export default.'\n );\n }\n return null;\n}\n\n/**\n * Load timber.config.ts (or .js, .mjs) from the project root.\n * Returns the config object or null if no config file is found.\n * Throws a descriptive error naming the file when a config file exists\n * but fails to load — a broken config must fail loudly, never silently\n * degrade (see TIM-1066). Known failure modes get an actionable hint\n * appended (see configLoadHint, TIM-1040).\n *\n * Uses require() — Node 22.12+ can require() ESM modules, and Node\n * 22.18+ strips types from .ts files by default (hence the engines\n * field requiring >=22.18.0). This keeps timber() synchronous — no\n * async config loading needed.\n * This is the single config loader — dev, build, and preview all use it.\n */\nexport function loadTimberConfigFile(root: string): TimberUserConfig | null {\n const configNames = ['timber.config.ts', 'timber.config.js', 'timber.config.mjs'];\n const req = createRequire(join(root, 'package.json'));\n\n for (const name of configNames) {\n const configPath = join(root, name);\n if (existsSync(configPath)) {\n try {\n const mod = req(configPath);\n return (mod.default ?? mod) as TimberUserConfig;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const hint = configLoadHint(name, err);\n throw new Error(\n `[timber] Failed to load ${configPath}: ${message}${hint ? `\\n${hint}` : ''}`,\n { cause: err }\n );\n }\n }\n }\n return null;\n}\n\n/**\n * Detect config keys set in both inline (vite.config.ts) and file (timber.config.ts)\n * and warn the user. The `output` key is excluded because it defaults to 'server'\n * in createPluginContext and would always appear as an inline key.\n *\n * Returns the list of conflicting key names (for testing).\n */\nexport function warnConfigConflicts(\n inline: TimberUserConfig,\n fileConfig: TimberUserConfig\n): string[] {\n const conflicts: string[] = [];\n for (const key of Object.keys(fileConfig) as (keyof TimberUserConfig)[]) {\n if (key in inline && inline[key] !== undefined) {\n conflicts.push(key);\n }\n }\n if (conflicts.length > 0) {\n console.warn(\n `[timber] Config conflict: ${conflicts.map((k) => `\"${k}\"`).join(', ')} set in both ` +\n `vite.config.ts (inline) and timber.config.ts. ` +\n `Move all config to timber.config.ts to avoid confusion. ` +\n `The inline value from vite.config.ts will be used.`\n );\n }\n return conflicts;\n}\n\n/**\n * Drop own keys whose value is `undefined` so they don't clobber file\n * config during spread merging. `timber({ buildDir: process.env.BUILD_DIR })`\n * with the var unset must not erase the timber.config.ts value.\n */\nfunction stripUndefined<T extends object>(obj: T): T {\n return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as T;\n}\n\n/**\n * Merge file-based config (timber.config.ts) with inline config (vite.config.ts).\n * Inline config takes precedence — file config only fills in missing fields.\n * Inline keys explicitly set to `undefined` are treated as absent, matching\n * the `!== undefined` check in warnConfigConflicts.\n */\nexport function mergeFileConfig(\n inline: TimberUserConfig,\n fileConfig: TimberUserConfig\n): TimberUserConfig {\n warnConfigConflicts(inline, fileConfig);\n const definedInline = stripUndefined(inline);\n\n return {\n ...fileConfig,\n ...definedInline,\n ...(fileConfig.limits && definedInline.limits\n ? { limits: { ...fileConfig.limits, ...stripUndefined(definedInline.limits) } }\n : {}),\n ...(fileConfig.dev && definedInline.dev\n ? { dev: { ...fileConfig.dev, ...stripUndefined(definedInline.dev) } }\n : {}),\n ...(fileConfig.mdx && definedInline.mdx\n ? { mdx: { ...fileConfig.mdx, ...stripUndefined(definedInline.mdx) } }\n : {}),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin-context---kTF5v8.js","names":[],"sources":["../../src/utils/startup-timer.ts","../../src/plugin-context.ts"],"sourcesContent":["/**\n * Startup timer — records named phases with their durations.\n *\n * Used by the plugin system to instrument cold start and report a\n * timing breakdown in dev mode. Zero overhead in production (disabled).\n *\n * See design/18-build-system.md, TIM-155.\n */\n\nimport { performance } from 'node:perf_hooks';\n\nexport interface PhaseRecord {\n name: string;\n startMs: number;\n durationMs: number;\n}\n\nexport interface StartupTimer {\n /** Mark the beginning of a named phase. */\n start(phase: string): void;\n /** Mark the end of a named phase. Returns duration in ms. */\n end(phase: string): number;\n /** Get all completed phase records, ordered by start time. */\n getPhases(): PhaseRecord[];\n /** Total elapsed time from first start() to last end(). */\n totalMs(): number;\n /** Format a human-readable summary string. */\n formatSummary(): string;\n}\n\n/**\n * Create a startup timer that records phase durations.\n */\nexport function createStartupTimer(): StartupTimer {\n const pending = new Map<string, number>();\n const phases: PhaseRecord[] = [];\n\n return {\n start(phase: string): void {\n pending.set(phase, performance.now());\n },\n\n end(phase: string): number {\n const startMs = pending.get(phase);\n if (startMs === undefined) {\n return 0;\n }\n pending.delete(phase);\n const durationMs = performance.now() - startMs;\n phases.push({ name: phase, startMs, durationMs });\n return durationMs;\n },\n\n getPhases(): PhaseRecord[] {\n return [...phases].sort((a, b) => a.startMs - b.startMs);\n },\n\n totalMs(): number {\n if (phases.length === 0) return 0;\n const sorted = this.getPhases();\n const first = sorted[0];\n const last = sorted[sorted.length - 1];\n return last.startMs + last.durationMs - first.startMs;\n },\n\n formatSummary(): string {\n const sorted = this.getPhases();\n if (sorted.length === 0) return 'No phases recorded.';\n\n const lines = sorted.map((p) => {\n const ms = p.durationMs.toFixed(1);\n return ` ${p.name.padEnd(30)} ${ms.padStart(8)}ms`;\n });\n\n const total = this.totalMs().toFixed(1);\n lines.push(` ${'total'.padEnd(30)} ${total.padStart(8)}ms`);\n\n return ['[timber] startup timing:', ...lines].join('\\n');\n },\n };\n}\n\n/**\n * No-op timer for production builds — all methods are empty.\n */\nexport function createNoopTimer(): StartupTimer {\n return {\n start() {},\n end() {\n return 0;\n },\n getPhases() {\n return [];\n },\n totalMs() {\n return 0;\n },\n formatSummary() {\n return '';\n },\n };\n}\n","/**\n * Plugin context — internal types and helpers for timber sub-plugins.\n *\n * These are NOT part of the user-facing API. They are consumed by\n * sub-plugins (routing, entries, shims, etc.) via relative imports.\n * The root entry point (index.ts) does not re-export them.\n *\n * Design doc: 18-build-system.md §\"Shared Plugin Context\"\n */\n\nimport { existsSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { createRequire } from 'node:module';\nimport { parseAst } from 'vite';\nimport type { RouteTree } from './routing/types';\nimport type { BuildManifest } from './server/build-manifest';\nimport type { CaptureSummary } from './server/prebuilt-builder';\nimport type { StartupTimer } from './utils/startup-timer';\nimport { createStartupTimer } from './utils/startup-timer';\nimport type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';\nimport type { HoldingServer } from './dev-tools/holding-server.js';\nimport type { ProgramNode } from './plugins/callsite-ast.js';\n\n// Re-export for sub-plugin convenience — they import from plugin-context.ts\nexport type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';\n\n// ── Client JavaScript config ─────────────────────────────────────────────\n\n/** Fully resolved client JavaScript configuration (no optionals). */\nexport interface ResolvedClientJavascript {\n disabled: boolean;\n enableHMRInDev: boolean;\n}\n\n/**\n * Resolve `clientJavascript` into a fully resolved config.\n */\nexport function resolveClientJavascript(config: TimberUserConfig): ResolvedClientJavascript {\n if (config.clientJavascript !== undefined) {\n if (typeof config.clientJavascript === 'boolean') {\n return {\n disabled: !config.clientJavascript,\n enableHMRInDev: !config.clientJavascript,\n };\n }\n return {\n disabled: config.clientJavascript.disabled,\n enableHMRInDev: config.clientJavascript.enableHMRInDev ?? config.clientJavascript.disabled,\n };\n }\n return { disabled: false, enableHMRInDev: false };\n}\n\n// ── Plugin Context ────────────────────────────────────────────────────────\n\n/**\n * Shared context object passed to all sub-plugins via closure.\n *\n * Sub-plugins communicate through this context — not through Vite's\n * plugin API or global state.\n * See design/18-build-system.md §\"Shared Plugin Context\".\n */\nexport interface PluginContext {\n config: TimberUserConfig;\n /** Resolved client JavaScript configuration */\n clientJavascript: ResolvedClientJavascript;\n /** The scanned route tree (populated by timber-routing, consumed by timber-entries) */\n routeTree: RouteTree | null;\n /** Absolute path to the app/ directory */\n appDir: string;\n /** Absolute path to the project root */\n root: string;\n /** Whether the dev server is running (set by timber-root-sync in configResolved) */\n dev: boolean;\n /** CSS build manifest (populated by adapter after client build, null in dev) */\n buildManifest: BuildManifest | null;\n /** Per-build deployment ID for version skew detection (null in dev) */\n deploymentId: string | null;\n /** Startup timer for profiling cold start phases (active in dev, no-op in prod) */\n timer: StartupTimer;\n /** Holding server that binds the port during dev startup (closed in timber-dev-server) */\n holdingServer?: HoldingServer | null;\n /**\n * Resolved absolute path to the build output directory.\n *\n * Defaults to `<root>/.timber/dist`. Can be overridden via\n * `timber.config.ts` `buildDir` or Vite's `build.outDir`.\n * Timber config takes precedence when both are set.\n *\n * Used by adapter-build and cli preview.\n */\n buildDir: string;\n /**\n * Per-route client reference keys captured during the RSC build.\n * Maps absolute route file paths to the set of client reference\n * hash keys they transitively import. Used by adapter-build to\n * populate per-route modulepreload entries from clientReferenceDeps.\n */\n routeClientRefKeys?: Map<string, Set<string>>;\n /**\n * Maps absolute module IDs of client references in non-facade RSC\n * chunks to the absolute paths of their consuming facade modules.\n * Built during the RSC `generateBundle` phase by timber-chunks,\n * consumed by `clientChunkGroup` to reclassify `shared:` modules\n * that are only used by a single route boundary.\n * See design/27-chunking-strategy.md, TIM-1107.\n */\n sharedModuleFacades?: Map<string, string[]>;\n /**\n * Module ID → prebuilt component IDs declared in it. Recorded by the\n * timber-prebuilt transform (RSC environment, production builds only),\n * consumed by the RSC `generateBundle` module-graph walk that computes\n * `routePrebuiltComponents`. See design/44-render-at-build-time.md.\n */\n prebuiltModuleComponents?: Map<string, string[]>;\n /**\n * Module ID → mapping of variable name to component ID for the prebuilt\n * components declared in that module. Used at `generateBundle` time to\n * trace imports from other modules back to specific component IDs.\n */\n prebuiltBindingNames?: Map<string, Map<string, string>>;\n /**\n * Component ID → distinct static call-site prop objects captured from\n * JSX usage sites during the transform and generateBundle phases.\n * Consumed by the post-build capture pass to render with actual props\n * instead of empty `{}`. See design/44 §Props Derivation.\n */\n prebuiltCallSiteProps?: Map<string, Record<string, unknown>[]>;\n /**\n * Route file path (page or layout, absolute) → prebuilt component IDs\n * transitively reachable from it. Computed during RSC `generateBundle`,\n * consumed by the post-build capture pass (TIM-1119) to decide which\n * components to render under which route's param combos.\n */\n routePrebuiltComponents?: Map<string, string[]>;\n /**\n * Page file paths that were desugared from `export const prerender` by\n * timber-prerender-sugar. The prebuilt transform uses this to verify\n * the generated `cache.component(...)` call was picked up.\n */\n prerenderSugarPages?: Set<string>;\n /**\n * Component ID → built chunk file name for prebuilt-declaring modules\n * that are only reachable via dynamic imports (import.meta.glob, lazy\n * routes). The capture pass imports these chunks to trigger\n * cache.component() registration before rendering. Populated during\n * RSC generateBundle alongside routePrebuiltComponents.\n */\n prebuiltLazyChunks?: Map<string, string>;\n /** Post-build prebuilt capture summary (populated by timber-prebuilt, consumed by build-report). */\n captureSummary?: CaptureSummary;\n /**\n * Content-keyed AST parse memo shared across cache/prebuilt/prerender-sugar\n * transforms. Collapses both cross-plugin and cross-environment duplicate\n * parses: identical code hits, rewritten code misses (correct because Vite\n * transforms chain — a rewrite produces different code). Bounded LRU (32\n * entries) so it doesn't hold programs for the whole build.\n *\n * Consumers MUST treat the returned ProgramNode as read-only — they collect\n * ranges and rewrite CODE via MagicString; the AST itself is never mutated.\n */\n parseCached?: ParseMemo;\n}\n\n// ── AST parse memo ───────────────────────────────────────────────────────\n\nconst PARSE_MEMO_CAPACITY = 32;\n\n/**\n * Content-keyed LRU memo for `parseAst`. Transforms for the same module run\n * back-to-back across plugins and environments, so a tiny window captures the\n * wins. The returned ProgramNode is shared — callers MUST NOT mutate it.\n */\nexport class ParseMemo {\n private cache = new Map<string, ProgramNode>();\n\n /**\n * Parse code, returning a cached ProgramNode if the same code was parsed\n * before. The returned AST MUST NOT be mutated — it may be shared across\n * multiple consumers.\n */\n parse(code: string): ProgramNode {\n const existing = this.cache.get(code);\n if (existing) {\n // LRU refresh: move to end\n this.cache.delete(code);\n this.cache.set(code, existing);\n return existing;\n }\n const program = parseAst(code, { lang: 'tsx' }) as unknown as ProgramNode;\n if (this.cache.size >= PARSE_MEMO_CAPACITY) {\n // Evict oldest (first inserted)\n const firstKey = this.cache.keys().next().value!;\n this.cache.delete(firstKey);\n }\n this.cache.set(code, program);\n return program;\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n\n// ── App directory resolution ──────────────────────────────────────────────\n\n/**\n * Resolve the app directory. Checks (in order):\n * 1. Explicit `configAppDir` from timber.config.ts\n * 2. `<root>/app`\n * 3. `<root>/src/app`\n *\n * Throws if none exist.\n */\nexport function resolveAppDir(root: string, configAppDir?: string): string {\n if (configAppDir) {\n const explicit = join(root, configAppDir);\n if (!existsSync(explicit)) {\n throw new Error(`[timber] Configured appDir \"${configAppDir}\" does not exist at ${explicit}`);\n }\n return explicit;\n }\n\n const rootApp = join(root, 'app');\n if (existsSync(rootApp)) return rootApp;\n\n const srcApp = join(root, 'src', 'app');\n if (existsSync(srcApp)) return srcApp;\n\n throw new Error(\n `[timber] Could not find app directory. Expected \"app/\" or \"src/app/\" in ${root}. ` +\n `You can set appDir in timber.config.ts to specify a custom location.`\n );\n}\n\n// ── Plugin context factory ────────────────────────────────────────────────\n\nexport function createPluginContext(config?: TimberUserConfig, root?: string): PluginContext {\n const projectRoot = root ?? process.cwd();\n const resolvedConfig: TimberUserConfig = { ...config };\n return {\n config: resolvedConfig,\n clientJavascript: resolveClientJavascript(resolvedConfig),\n routeTree: null,\n appDir: join(projectRoot, 'app'),\n root: projectRoot,\n dev: false,\n buildManifest: null,\n deploymentId: null,\n timer: createStartupTimer(),\n holdingServer: null,\n buildDir: resolveBuildDir(projectRoot, resolvedConfig.buildDir),\n parseCached: new ParseMemo(),\n };\n}\n\n// ── Build directory resolution ────────────────────────────────────────────\n\n/** Default build output directory (relative to root). */\nexport const DEFAULT_BUILD_DIR = join('.timber', 'dist');\n\n/**\n * Resolve the build output directory.\n *\n * Priority:\n * 1. Explicit `timberBuildDir` from timber.config.ts\n * 2. Explicit `viteBuildOutDir` from Vite's build.outDir (if not the default 'dist')\n * 3. Default: `.timber/dist`\n *\n * Returns an absolute path.\n */\nexport function resolveBuildDir(\n root: string,\n timberBuildDir?: string,\n viteBuildOutDir?: string\n): string {\n if (timberBuildDir) {\n return resolve(root, timberBuildDir);\n }\n if (viteBuildOutDir && viteBuildOutDir !== 'dist') {\n return resolve(root, viteBuildOutDir);\n }\n return join(root, DEFAULT_BUILD_DIR);\n}\n\n// ── Config file loading ───────────────────────────────────────────────────\n\n/**\n * Map known timber.config load failures to actionable hints (TIM-1040).\n * Returns null when the error has no known remedy beyond its own message.\n *\n * Known failure modes of loading timber.config.ts via native require():\n * - Node < 22.18 has no default type stripping → ERR_UNKNOWN_FILE_EXTENSION\n * - Non-erasable TS syntax (enums, namespaces, parameter properties)\n * → ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX / ERR_INVALID_TYPESCRIPT_SYNTAX\n * - ESM syntax in a `\"type\": \"commonjs\"` package → the stripped .ts is\n * parsed as CJS and throws SyntaxError: \"Unexpected token 'export'\"\n * (export statements) or \"Cannot use import statement outside a\n * module\" (import statements)\n */\nexport function configLoadHint(configName: string, err: unknown): string | null {\n const code = (err as NodeJS.ErrnoException | null)?.code;\n const isTs = configName.endsWith('.ts');\n\n if (isTs && code === 'ERR_UNKNOWN_FILE_EXTENSION') {\n return (\n 'Loading a TypeScript config requires Node.js 22.18+ (built-in type stripping). ' +\n 'Upgrade Node.js or rewrite the config as timber.config.js.'\n );\n }\n if (code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX' || code === 'ERR_INVALID_TYPESCRIPT_SYNTAX') {\n return (\n 'Node.js loads the config with type stripping, which only supports erasable TypeScript syntax — ' +\n 'replace enums, namespaces, and constructor parameter properties with plain objects and types.'\n );\n }\n if (\n isTs &&\n err instanceof SyntaxError &&\n /Unexpected token '(export|import)'|Cannot use import statement outside a module/.test(\n err.message\n )\n ) {\n return (\n 'The config uses ESM syntax but Node.js parsed it as CommonJS — ' +\n 'set \"type\": \"module\" in package.json, or use module.exports instead of export default.'\n );\n }\n return null;\n}\n\n/**\n * Load timber.config.ts (or .js, .mjs) from the project root.\n * Returns the config object or null if no config file is found.\n * Throws a descriptive error naming the file when a config file exists\n * but fails to load — a broken config must fail loudly, never silently\n * degrade (see TIM-1066). Known failure modes get an actionable hint\n * appended (see configLoadHint, TIM-1040).\n *\n * Uses require() — Node 22.12+ can require() ESM modules, and Node\n * 22.18+ strips types from .ts files by default (hence the engines\n * field requiring >=22.18.0). This keeps timber() synchronous — no\n * async config loading needed.\n * This is the single config loader — dev, build, and preview all use it.\n */\nexport function loadTimberConfigFile(root: string): TimberUserConfig | null {\n const configNames = ['timber.config.ts', 'timber.config.js', 'timber.config.mjs'];\n const req = createRequire(join(root, 'package.json'));\n\n for (const name of configNames) {\n const configPath = join(root, name);\n if (existsSync(configPath)) {\n try {\n const mod = req(configPath);\n return (mod.default ?? mod) as TimberUserConfig;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const hint = configLoadHint(name, err);\n throw new Error(\n `[timber] Failed to load ${configPath}: ${message}${hint ? `\\n${hint}` : ''}`,\n { cause: err }\n );\n }\n }\n }\n return null;\n}\n\n/**\n * Detect config keys set in both inline (vite.config.ts) and file (timber.config.ts)\n * and warn the user. The `output` key is excluded because it defaults to 'server'\n * in createPluginContext and would always appear as an inline key.\n *\n * Returns the list of conflicting key names (for testing).\n */\nexport function warnConfigConflicts(\n inline: TimberUserConfig,\n fileConfig: TimberUserConfig\n): string[] {\n const conflicts: string[] = [];\n for (const key of Object.keys(fileConfig) as (keyof TimberUserConfig)[]) {\n if (key in inline && inline[key] !== undefined) {\n conflicts.push(key);\n }\n }\n if (conflicts.length > 0) {\n console.warn(\n `[timber] Config conflict: ${conflicts.map((k) => `\"${k}\"`).join(', ')} set in both ` +\n `vite.config.ts (inline) and timber.config.ts. ` +\n `Move all config to timber.config.ts to avoid confusion. ` +\n `The inline value from vite.config.ts will be used.`\n );\n }\n return conflicts;\n}\n\n/**\n * Drop own keys whose value is `undefined` so they don't clobber file\n * config during spread merging. `timber({ buildDir: process.env.BUILD_DIR })`\n * with the var unset must not erase the timber.config.ts value.\n */\nfunction stripUndefined<T extends object>(obj: T): T {\n return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as T;\n}\n\n/**\n * Merge file-based config (timber.config.ts) with inline config (vite.config.ts).\n * Inline config takes precedence — file config only fills in missing fields.\n * Inline keys explicitly set to `undefined` are treated as absent, matching\n * the `!== undefined` check in warnConfigConflicts.\n */\nexport function mergeFileConfig(\n inline: TimberUserConfig,\n fileConfig: TimberUserConfig\n): TimberUserConfig {\n warnConfigConflicts(inline, fileConfig);\n const definedInline = stripUndefined(inline);\n\n return {\n ...fileConfig,\n ...definedInline,\n ...(fileConfig.limits && definedInline.limits\n ? { limits: { ...fileConfig.limits, ...stripUndefined(definedInline.limits) } }\n : {}),\n ...(fileConfig.dev && definedInline.dev\n ? { dev: { ...fileConfig.dev, ...stripUndefined(definedInline.dev) } }\n : {}),\n ...(fileConfig.mdx && definedInline.mdx\n ? { mdx: { ...fileConfig.mdx, ...stripUndefined(definedInline.mdx) } }\n : {}),\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { a as resolveCodecOrSchema, r as isCodec } from "./schema-bridge-BY3QLBL7.js";
1
+ import { a as resolveCodecOrSchema, r as isCodec } from "./schema-bridge-DT_Tn0Xf.js";
2
2
  //#region src/params/resolve-schema.ts
3
3
  /**
4
4
  * Reconstruct the bracket key for a segment from its type and param name.
@@ -47,4 +47,4 @@ function resolveSchemaCodecs(segmentParams) {
47
47
  //#endregion
48
48
  export { toBracketKey as n, resolveSchemaCodecs as t };
49
49
 
50
- //# sourceMappingURL=resolve-schema-3iUvBV5T.js.map
50
+ //# sourceMappingURL=resolve-schema-Dz3fcFUo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-schema-3iUvBV5T.js","names":[],"sources":["../../src/params/resolve-schema.ts"],"sourcesContent":["/**\n * Schema resolution — resolves the global schema's segmentParams into\n * a runtime-ready codec map keyed by bracket name (e.g. '[id]').\n *\n * Bracket keys are preserved so that `[id]`, `[...id]`, and `[[...id]]`\n * remain distinct entries. The coercion layer reconstructs bracket keys\n * from matched segment metadata to look up the correct codec.\n *\n * Design doc: design/41-global-params.md §Pipeline Integration\n */\n\nimport type { Codec } from '../codec.js';\nimport { isCodec, resolveCodecOrSchema } from '../schema-bridge.js';\n\n/**\n * Reconstruct the bracket key for a segment from its type and param name.\n * Used by the coercion layer to look up codecs in the bracket-keyed map.\n *\n * 'dynamic' + 'id' → '[id]'\n * 'catch-all' + 'slug' → '[...slug]'\n * 'optional-catch-all' + 'slug' → '[[...slug]]'\n */\nexport function toBracketKey(segmentType: string, paramName: string): string {\n switch (segmentType) {\n case 'catch-all':\n return `[...${paramName}]`;\n case 'optional-catch-all':\n return `[[...${paramName}]]`;\n default:\n return `[${paramName}]`;\n }\n}\n\nconst CATCH_ALL_RE = /^\\[\\.\\.\\..+\\]$/;\nconst OPTIONAL_CATCH_ALL_RE = /^\\[\\[\\.\\.\\..+\\]\\]$/;\n\nfunction isCatchAllKey(key: string): boolean {\n return CATCH_ALL_RE.test(key) || OPTIONAL_CATCH_ALL_RE.test(key);\n}\n\nfunction isOptionalCatchAllKey(key: string): boolean {\n return OPTIONAL_CATCH_ALL_RE.test(key);\n}\n\n/**\n * Resolve the global schema's segmentParams into a map of bracket-keyed\n * param name → resolved Codec.\n *\n * Keys are preserved exactly as written in `app/schema.ts` so that\n * `[id]`, `[...id]`, and `[[...id]]` remain distinct entries.\n * The coercion layer in `param-coercion.ts` reconstructs bracket keys\n * from matched segment metadata to look up the correct codec.\n *\n * Input: { '[id]': z.coerce.number(), '[...slug]': codec.string }\n * Output: { '[id]': Codec<number>, '[...slug]': Codec<string> }\n */\nexport function resolveSchemaCodecs(\n segmentParams: Record<string, unknown>\n): Record<string, Codec<unknown>> {\n const resolved: Record<string, Codec<unknown>> = Object.create(null);\n\n for (const [bracketName, value] of Object.entries(segmentParams)) {\n if (isCatchAllKey(bracketName) && isCodec(value) && !('__catchAll' in value)) {\n throw new Error(\n `[timber] Schema error: '${bracketName}' is a catch-all segment and requires an array codec ` +\n `(e.g. codec.catchAll(codec.string)). Got a scalar codec.`\n );\n }\n if (\n isOptionalCatchAllKey(bracketName) &&\n isCodec(value) &&\n '__catchAll' in value &&\n !('__optional' in value)\n ) {\n throw new Error(\n `[timber] Schema error: '${bracketName}' is an optional catch-all segment and requires codec.optionalCatchAll() ` +\n `(e.g. codec.optionalCatchAll(codec.string)). Got a non-optional catch-all codec.`\n );\n }\n resolved[bracketName] = resolveCodecOrSchema(bracketName, value, 'param');\n }\n\n return resolved;\n}\n"],"mappings":";;;;;;;;;;AAsBA,SAAgB,aAAa,aAAqB,WAA2B;CAC3E,QAAQ,aAAR;EACE,KAAK,aACH,OAAO,OAAO,UAAU;EAC1B,KAAK,sBACH,OAAO,QAAQ,UAAU;EAC3B,SACE,OAAO,IAAI,UAAU;CACzB;AACF;AAEA,IAAM,eAAe;AACrB,IAAM,wBAAwB;AAE9B,SAAS,cAAc,KAAsB;CAC3C,OAAO,aAAa,KAAK,GAAG,KAAK,sBAAsB,KAAK,GAAG;AACjE;AAEA,SAAS,sBAAsB,KAAsB;CACnD,OAAO,sBAAsB,KAAK,GAAG;AACvC;;;;;;;;;;;;;AAcA,SAAgB,oBACd,eACgC;CAChC,MAAM,WAA2C,OAAO,OAAO,IAAI;CAEnE,KAAK,MAAM,CAAC,aAAa,UAAU,OAAO,QAAQ,aAAa,GAAG;EAChE,IAAI,cAAc,WAAW,KAAK,QAAQ,KAAK,KAAK,EAAE,gBAAgB,QACpE,MAAM,IAAI,MACR,2BAA2B,YAAY,8GAEzC;EAEF,IACE,sBAAsB,WAAW,KACjC,QAAQ,KAAK,KACb,gBAAgB,SAChB,EAAE,gBAAgB,QAElB,MAAM,IAAI,MACR,2BAA2B,YAAY,0JAEzC;EAEF,SAAS,eAAe,qBAAqB,aAAa,OAAO,OAAO;CAC1E;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"resolve-schema-Dz3fcFUo.js","names":[],"sources":["../../src/params/resolve-schema.ts"],"sourcesContent":["/**\n * Schema resolution — resolves the global schema's segmentParams into\n * a runtime-ready codec map keyed by bracket name (e.g. '[id]').\n *\n * Bracket keys are preserved so that `[id]`, `[...id]`, and `[[...id]]`\n * remain distinct entries. The coercion layer reconstructs bracket keys\n * from matched segment metadata to look up the correct codec.\n *\n * Design doc: design/41-global-params.md §Pipeline Integration\n */\n\nimport type { Codec } from '../codec.js';\nimport { isCodec, resolveCodecOrSchema } from '../schema-bridge.js';\n\n/**\n * Reconstruct the bracket key for a segment from its type and param name.\n * Used by the coercion layer to look up codecs in the bracket-keyed map.\n *\n * 'dynamic' + 'id' → '[id]'\n * 'catch-all' + 'slug' → '[...slug]'\n * 'optional-catch-all' + 'slug' → '[[...slug]]'\n */\nexport function toBracketKey(segmentType: string, paramName: string): string {\n switch (segmentType) {\n case 'catch-all':\n return `[...${paramName}]`;\n case 'optional-catch-all':\n return `[[...${paramName}]]`;\n default:\n return `[${paramName}]`;\n }\n}\n\nconst CATCH_ALL_RE = /^\\[\\.\\.\\..+\\]$/;\nconst OPTIONAL_CATCH_ALL_RE = /^\\[\\[\\.\\.\\..+\\]\\]$/;\n\nfunction isCatchAllKey(key: string): boolean {\n return CATCH_ALL_RE.test(key) || OPTIONAL_CATCH_ALL_RE.test(key);\n}\n\nfunction isOptionalCatchAllKey(key: string): boolean {\n return OPTIONAL_CATCH_ALL_RE.test(key);\n}\n\n/**\n * Resolve the global schema's segmentParams into a map of bracket-keyed\n * param name → resolved Codec.\n *\n * Keys are preserved exactly as written in `app/schema.ts` so that\n * `[id]`, `[...id]`, and `[[...id]]` remain distinct entries.\n * The coercion layer in `param-coercion.ts` reconstructs bracket keys\n * from matched segment metadata to look up the correct codec.\n *\n * Input: { '[id]': z.coerce.number(), '[...slug]': codec.string }\n * Output: { '[id]': Codec<number>, '[...slug]': Codec<string> }\n */\nexport function resolveSchemaCodecs(\n segmentParams: Record<string, unknown>\n): Record<string, Codec<unknown>> {\n const resolved: Record<string, Codec<unknown>> = Object.create(null);\n\n for (const [bracketName, value] of Object.entries(segmentParams)) {\n if (isCatchAllKey(bracketName) && isCodec(value) && !('__catchAll' in value)) {\n throw new Error(\n `[timber] Schema error: '${bracketName}' is a catch-all segment and requires an array codec ` +\n `(e.g. codec.catchAll(codec.string)). Got a scalar codec.`\n );\n }\n if (\n isOptionalCatchAllKey(bracketName) &&\n isCodec(value) &&\n '__catchAll' in value &&\n !('__optional' in value)\n ) {\n throw new Error(\n `[timber] Schema error: '${bracketName}' is an optional catch-all segment and requires codec.optionalCatchAll() ` +\n `(e.g. codec.optionalCatchAll(codec.string)). Got a non-optional catch-all codec.`\n );\n }\n resolved[bracketName] = resolveCodecOrSchema(bracketName, value, 'param');\n }\n\n return resolved;\n}\n"],"mappings":";;;;;;;;;;AAsBA,SAAgB,aAAa,aAAqB,WAA2B;CAC3E,QAAQ,aAAR;EACE,KAAK,aACH,OAAO,OAAO,UAAU;EAC1B,KAAK,sBACH,OAAO,QAAQ,UAAU;EAC3B,SACE,OAAO,IAAI,UAAU;CACzB;AACF;AAEA,IAAM,eAAe;AACrB,IAAM,wBAAwB;AAE9B,SAAS,cAAc,KAAsB;CAC3C,OAAO,aAAa,KAAK,GAAG,KAAK,sBAAsB,KAAK,GAAG;AACjE;AAEA,SAAS,sBAAsB,KAAsB;CACnD,OAAO,sBAAsB,KAAK,GAAG;AACvC;;;;;;;;;;;;;AAcA,SAAgB,oBACd,eACgC;CAChC,MAAM,WAA2C,OAAO,OAAO,IAAI;CAEnE,KAAK,MAAM,CAAC,aAAa,UAAU,OAAO,QAAQ,aAAa,GAAG;EAChE,IAAI,cAAc,WAAW,KAAK,QAAQ,KAAK,KAAK,EAAE,gBAAgB,QACpE,MAAM,IAAI,MACR,2BAA2B,YAAY,8GAEzC;EAEF,IACE,sBAAsB,WAAW,KACjC,QAAQ,KAAK,KACb,gBAAgB,SAChB,EAAE,gBAAgB,QAElB,MAAM,IAAI,MACR,2BAA2B,YAAY,0JAEzC;EAEF,SAAS,eAAe,qBAAqB,aAAa,OAAO,OAAO;CAC1E;CAEA,OAAO;AACT"}
@@ -114,6 +114,6 @@ function fromArraySchema(schema) {
114
114
  };
115
115
  }
116
116
  //#endregion
117
- export { resolveCodecOrSchema as a, isStandardSchema as i, fromSchema as n, validateSync as o, isCodec as r, fromArraySchema as t };
117
+ export { resolveCodecOrSchema as a, isStandardSchema as i, fromSchema as n, isCodec as r, fromArraySchema as t };
118
118
 
119
- //# sourceMappingURL=schema-bridge-BY3QLBL7.js.map
119
+ //# sourceMappingURL=schema-bridge-DT_Tn0Xf.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-bridge-BY3QLBL7.js","names":[],"sources":["../../src/schema-bridge.ts"],"sourcesContent":["/**\n * Standard Schema bridge — shared helpers for bridging Standard Schema-compatible\n * validation libraries (Zod, Valibot, ArkType) to the Codec<T> protocol.\n *\n * This module is the single source of truth for:\n * - StandardSchemaV1 interface (subset of the Standard Schema spec)\n * - validateSync() helper\n * - fromSchema() — bridge from Standard Schema to Codec<T>\n * - fromArraySchema() — bridge for array-valued codecs\n *\n * These are re-exported from @timber-js/app/search-params, @timber-js/app/segment-params,\n * and @timber-js/app/cookies for convenience. The canonical import is\n * @timber-js/app/codec.\n *\n * Design doc: design/23a-search-params-triage.md §\"Unify Codec<T> type\"\n */\n\nimport type { Codec } from './codec.js';\n\n// ---------------------------------------------------------------------------\n// Standard Schema interface (subset)\n//\n// Standard Schema (https://github.com/standard-schema/standard-schema) defines\n// a minimal interface that Zod ≥3.24, Valibot ≥1.0, and ArkType all implement.\n// We depend only on `~standard.validate` to avoid coupling to any specific lib.\n// ---------------------------------------------------------------------------\n\n/** Minimal Standard Schema interface for auto-detection. */\nexport interface StandardSchemaV1<Output = unknown> {\n '~standard': {\n validate(value: unknown): StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n };\n}\n\nexport type StandardSchemaResult<Output> =\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> };\n\n// ---------------------------------------------------------------------------\n// Sync validate helper\n// ---------------------------------------------------------------------------\n\n/**\n * Run a Standard Schema's `~standard.validate()` synchronously.\n *\n * Zod v4's signature includes `Promise` in the return union to satisfy the\n * Standard Schema spec, but in practice Zod always validates synchronously\n * for the schema types we use. We assert the result is sync and throw if\n * it isn't — codec parsing must be synchronous.\n */\nexport function validateSync<Output>(\n schema: StandardSchemaV1<Output>,\n value: unknown\n): StandardSchemaResult<Output> {\n const result = schema['~standard'].validate(value);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] fromSchema: schema returned a Promise — only sync schemas are supported.'\n );\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Type guards\n// ---------------------------------------------------------------------------\n\n/** Check if a value is a Standard Schema object. */\nexport function isStandardSchema(value: unknown): value is StandardSchemaV1 {\n return (\n typeof value === 'object' &&\n value !== null &&\n '~standard' in value &&\n typeof (value as StandardSchemaV1)['~standard']?.validate === 'function'\n );\n}\n\n/** Check if a value is a Codec (has parse + serialize methods). */\nexport function isCodec(value: unknown): value is Codec<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as Codec<unknown>).parse === 'function' &&\n typeof (value as Codec<unknown>).serialize === 'function'\n );\n}\n\n// ---------------------------------------------------------------------------\n// fromParamSchema — bridge from Standard Schema to Codec<T> for route params\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema to a Codec for route params.\n * Parse throws on failure (invalid param → 404). Serialize returns string.\n */\nexport function fromParamSchema<T>(fieldName: string, schema: StandardSchemaV1<T>): Codec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n const result = validateSync(schema, value);\n if (!result.issues) {\n return result.value;\n }\n const messages = result.issues.map((i) => i.message).join(', ');\n throw new Error(`[timber] Param '${fieldName}' coercion failed: ${messages}`);\n },\n serialize(value: T): string | null {\n if (value === null || value === undefined) return null;\n if (Array.isArray(value)) return value.join('/');\n return String(value);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// resolveCodecOrSchema — generic resolver for any codec-or-schema field\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a field value to a Codec. Accepts Codec<T>, StandardSchemaV1<T>,\n * or (for route params) auto-wraps via fromParamSchema.\n *\n * @param fieldName - used in error messages\n * @param value - the codec or schema to resolve\n * @param mode - 'param' uses fromParamSchema (throws on parse failure),\n * 'search' uses fromSchema (falls back to default on failure)\n */\nexport function resolveCodecOrSchema(\n fieldName: string,\n value: unknown,\n mode: 'param' | 'search' = 'search'\n): Codec<unknown> {\n if (isCodec(value)) return value;\n if (isStandardSchema(value)) {\n return mode === 'param'\n ? fromParamSchema(fieldName, value)\n : (fromSchema(value) as Codec<unknown>);\n }\n throw new Error(\n `[timber] Field '${fieldName}' is not a valid codec or Standard Schema. ` +\n `Expected an object with { parse, serialize } methods, or a Standard Schema object ` +\n `(Zod, Valibot, ArkType).`\n );\n}\n\n// ---------------------------------------------------------------------------\n// fromSchema — bridge from Standard Schema to Codec<T>\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema-compatible schema (Zod, Valibot, ArkType) to a\n * Codec<T>.\n *\n * Parse: coerces the raw string through the schema. On validation failure,\n * parses `undefined` to get the schema's default value (the schema should have\n * a `.default()` call). If that also fails, returns `undefined`.\n *\n * Serialize: uses `String()` for primitives, `null` for null/undefined.\n *\n * ```ts\n * import { fromSchema } from '@timber-js/app/codec'\n * import { z } from 'zod/v4'\n *\n * const pageCodec = fromSchema(z.coerce.number().int().min(1).default(1))\n * ```\n */\nexport function fromSchema<T>(schema: StandardSchemaV1<T>): Codec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // For array inputs (duplicate query keys), use the first value.\n // Browsers and URLSearchParams.get() return the first occurrence.\n const input = Array.isArray(value) ? value[0] : value;\n\n // Try parsing the raw value\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // On failure, try parsing undefined to get the default.\n // Re-validate each time so factory defaults (e.g. .default(() => []))\n // produce fresh values.\n const defaultResult = validateSync(schema, undefined);\n if (!defaultResult.issues) {\n return defaultResult.value;\n }\n\n // No default available — return undefined (codec design choice).\n // Callers like defineSearchParams validate this at definition time\n // via validateSchemaDefaults() to catch it early.\n return undefined as T;\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n return String(value);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// fromArraySchema — bridge for array-valued codecs\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema for array values. Handles both single strings\n * and repeated query keys (`?tag=a&tag=b`).\n *\n * ```ts\n * import { fromArraySchema } from '@timber-js/app/codec'\n * import { z } from 'zod/v4'\n *\n * const tagsCodec = fromArraySchema(z.array(z.string()).default([]))\n * ```\n */\nexport function fromArraySchema<T>(schema: StandardSchemaV1<T>): Codec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // Coerce single string to array for array schemas\n let input: unknown = value;\n if (typeof value === 'string') {\n input = [value];\n } else if (value === undefined) {\n input = undefined;\n }\n\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // On failure, try undefined for default\n const defaultResult = validateSync(schema, undefined);\n if (!defaultResult.issues) {\n return defaultResult.value;\n }\n\n return undefined as T;\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (Array.isArray(value)) {\n return value.length === 0 ? null : value.join(',');\n }\n return String(value);\n },\n };\n}\n"],"mappings":";;;;;;;;;AAkDA,SAAgB,aACd,QACA,OAC8B;CAC9B,MAAM,SAAS,OAAO,YAAY,CAAC,SAAS,KAAK;CACjD,IAAI,kBAAkB,SACpB,MAAM,IAAI,MACR,mFACF;CAEF,OAAO;AACT;;AAOA,SAAgB,iBAAiB,OAA2C;CAC1E,OACE,OAAO,UAAU,YACjB,UAAU,QACV,eAAe,SACf,OAAQ,MAA2B,YAAY,EAAE,aAAa;AAElE;;AAGA,SAAgB,QAAQ,OAAyC;CAC/D,OACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAyB,UAAU,cAC3C,OAAQ,MAAyB,cAAc;AAEnD;;;;;AAUA,SAAgB,gBAAmB,WAAmB,QAAuC;CAC3F,OAAO;EACL,MAAM,OAAyC;GAC7C,MAAM,SAAS,aAAa,QAAQ,KAAK;GACzC,IAAI,CAAC,OAAO,QACV,OAAO,OAAO;GAEhB,MAAM,WAAW,OAAO,OAAO,KAAK,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI;GAC9D,MAAM,IAAI,MAAM,mBAAmB,UAAU,qBAAqB,UAAU;EAC9E;EACA,UAAU,OAAyB;GACjC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;GAClD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,GAAG;GAC/C,OAAO,OAAO,KAAK;EACrB;CACF;AACF;;;;;;;;;;AAeA,SAAgB,qBACd,WACA,OACA,OAA2B,UACX;CAChB,IAAI,QAAQ,KAAK,GAAG,OAAO;CAC3B,IAAI,iBAAiB,KAAK,GACxB,OAAO,SAAS,UACZ,gBAAgB,WAAW,KAAK,IAC/B,WAAW,KAAK;CAEvB,MAAM,IAAI,MACR,mBAAmB,UAAU,sJAG/B;AACF;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,WAAc,QAAuC;CACnE,OAAO;EACL,MAAM,OAAyC;GAM7C,MAAM,SAAS,aAAa,QAHd,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,KAGP;GACzC,IAAI,CAAC,OAAO,QACV,OAAO,OAAO;GAMhB,MAAM,gBAAgB,aAAa,QAAQ,KAAA,CAAS;GACpD,IAAI,CAAC,cAAc,QACjB,OAAO,cAAc;EAOzB;EAEA,UAAU,OAAyB;GACjC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;GAET,OAAO,OAAO,KAAK;EACrB;CACF;AACF;;;;;;;;;;;;AAiBA,SAAgB,gBAAmB,QAAuC;CACxE,OAAO;EACL,MAAM,OAAyC;GAE7C,IAAI,QAAiB;GACrB,IAAI,OAAO,UAAU,UACnB,QAAQ,CAAC,KAAK;QACT,IAAI,UAAU,KAAA,GACnB,QAAQ,KAAA;GAGV,MAAM,SAAS,aAAa,QAAQ,KAAK;GACzC,IAAI,CAAC,OAAO,QACV,OAAO,OAAO;GAIhB,MAAM,gBAAgB,aAAa,QAAQ,KAAA,CAAS;GACpD,IAAI,CAAC,cAAc,QACjB,OAAO,cAAc;EAIzB;EAEA,UAAU,OAAyB;GACjC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;GAET,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,WAAW,IAAI,OAAO,MAAM,KAAK,GAAG;GAEnD,OAAO,OAAO,KAAK;EACrB;CACF;AACF"}
1
+ {"version":3,"file":"schema-bridge-DT_Tn0Xf.js","names":[],"sources":["../../src/schema-bridge.ts"],"sourcesContent":["/**\n * Standard Schema bridge — shared helpers for bridging Standard Schema-compatible\n * validation libraries (Zod, Valibot, ArkType) to the Codec<T> protocol.\n *\n * This module is the single source of truth for:\n * - StandardSchemaV1 interface (subset of the Standard Schema spec)\n * - validateSync() helper\n * - fromSchema() — bridge from Standard Schema to Codec<T>\n * - fromArraySchema() — bridge for array-valued codecs\n *\n * These are re-exported from @timber-js/app/search-params, @timber-js/app/segment-params,\n * and @timber-js/app/cookies for convenience. The canonical import is\n * @timber-js/app/codec.\n *\n * Design doc: design/23a-search-params-triage.md §\"Unify Codec<T> type\"\n */\n\nimport type { Codec } from './codec.js';\n\n// ---------------------------------------------------------------------------\n// Standard Schema interface (subset)\n//\n// Standard Schema (https://github.com/standard-schema/standard-schema) defines\n// a minimal interface that Zod ≥3.24, Valibot ≥1.0, and ArkType all implement.\n// We depend only on `~standard.validate` to avoid coupling to any specific lib.\n// ---------------------------------------------------------------------------\n\n/** Minimal Standard Schema interface for auto-detection. */\nexport interface StandardSchemaV1<Output = unknown> {\n '~standard': {\n validate(value: unknown): StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n };\n}\n\nexport type StandardSchemaResult<Output> =\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> };\n\n// ---------------------------------------------------------------------------\n// Sync validate helper\n// ---------------------------------------------------------------------------\n\n/**\n * Run a Standard Schema's `~standard.validate()` synchronously.\n *\n * Zod v4's signature includes `Promise` in the return union to satisfy the\n * Standard Schema spec, but in practice Zod always validates synchronously\n * for the schema types we use. We assert the result is sync and throw if\n * it isn't — codec parsing must be synchronous.\n */\nexport function validateSync<Output>(\n schema: StandardSchemaV1<Output>,\n value: unknown\n): StandardSchemaResult<Output> {\n const result = schema['~standard'].validate(value);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] fromSchema: schema returned a Promise — only sync schemas are supported.'\n );\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Type guards\n// ---------------------------------------------------------------------------\n\n/** Check if a value is a Standard Schema object. */\nexport function isStandardSchema(value: unknown): value is StandardSchemaV1 {\n return (\n typeof value === 'object' &&\n value !== null &&\n '~standard' in value &&\n typeof (value as StandardSchemaV1)['~standard']?.validate === 'function'\n );\n}\n\n/** Check if a value is a Codec (has parse + serialize methods). */\nexport function isCodec(value: unknown): value is Codec<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as Codec<unknown>).parse === 'function' &&\n typeof (value as Codec<unknown>).serialize === 'function'\n );\n}\n\n// ---------------------------------------------------------------------------\n// fromParamSchema — bridge from Standard Schema to Codec<T> for route params\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema to a Codec for route params.\n * Parse throws on failure (invalid param → 404). Serialize returns string.\n */\nexport function fromParamSchema<T>(fieldName: string, schema: StandardSchemaV1<T>): Codec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n const result = validateSync(schema, value);\n if (!result.issues) {\n return result.value;\n }\n const messages = result.issues.map((i) => i.message).join(', ');\n throw new Error(`[timber] Param '${fieldName}' coercion failed: ${messages}`);\n },\n serialize(value: T): string | null {\n if (value === null || value === undefined) return null;\n if (Array.isArray(value)) return value.join('/');\n return String(value);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// resolveCodecOrSchema — generic resolver for any codec-or-schema field\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a field value to a Codec. Accepts Codec<T>, StandardSchemaV1<T>,\n * or (for route params) auto-wraps via fromParamSchema.\n *\n * @param fieldName - used in error messages\n * @param value - the codec or schema to resolve\n * @param mode - 'param' uses fromParamSchema (throws on parse failure),\n * 'search' uses fromSchema (falls back to default on failure)\n */\nexport function resolveCodecOrSchema(\n fieldName: string,\n value: unknown,\n mode: 'param' | 'search' = 'search'\n): Codec<unknown> {\n if (isCodec(value)) return value;\n if (isStandardSchema(value)) {\n return mode === 'param'\n ? fromParamSchema(fieldName, value)\n : (fromSchema(value) as Codec<unknown>);\n }\n throw new Error(\n `[timber] Field '${fieldName}' is not a valid codec or Standard Schema. ` +\n `Expected an object with { parse, serialize } methods, or a Standard Schema object ` +\n `(Zod, Valibot, ArkType).`\n );\n}\n\n// ---------------------------------------------------------------------------\n// fromSchema — bridge from Standard Schema to Codec<T>\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema-compatible schema (Zod, Valibot, ArkType) to a\n * Codec<T>.\n *\n * Parse: coerces the raw string through the schema. On validation failure,\n * parses `undefined` to get the schema's default value (the schema should have\n * a `.default()` call). If that also fails, returns `undefined`.\n *\n * Serialize: uses `String()` for primitives, `null` for null/undefined.\n *\n * ```ts\n * import { fromSchema } from '@timber-js/app/codec'\n * import { z } from 'zod/v4'\n *\n * const pageCodec = fromSchema(z.coerce.number().int().min(1).default(1))\n * ```\n */\nexport function fromSchema<T>(schema: StandardSchemaV1<T>): Codec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // For array inputs (duplicate query keys), use the first value.\n // Browsers and URLSearchParams.get() return the first occurrence.\n const input = Array.isArray(value) ? value[0] : value;\n\n // Try parsing the raw value\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // On failure, try parsing undefined to get the default.\n // Re-validate each time so factory defaults (e.g. .default(() => []))\n // produce fresh values.\n const defaultResult = validateSync(schema, undefined);\n if (!defaultResult.issues) {\n return defaultResult.value;\n }\n\n // No default available — the field is implicitly optional. Return\n // undefined; defineSearchParams widens the field's inferred type to\n // T | undefined via InferField so this doesn't lie.\n // design/23-search-params.md §\"Implicit Optionality\"\n return undefined as T;\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n return String(value);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// fromArraySchema — bridge for array-valued codecs\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema for array values. Handles both single strings\n * and repeated query keys (`?tag=a&tag=b`).\n *\n * ```ts\n * import { fromArraySchema } from '@timber-js/app/codec'\n * import { z } from 'zod/v4'\n *\n * const tagsCodec = fromArraySchema(z.array(z.string()).default([]))\n * ```\n */\nexport function fromArraySchema<T>(schema: StandardSchemaV1<T>): Codec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // Coerce single string to array for array schemas\n let input: unknown = value;\n if (typeof value === 'string') {\n input = [value];\n } else if (value === undefined) {\n input = undefined;\n }\n\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // On failure, try undefined for default\n const defaultResult = validateSync(schema, undefined);\n if (!defaultResult.issues) {\n return defaultResult.value;\n }\n\n return undefined as T;\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (Array.isArray(value)) {\n return value.length === 0 ? null : value.join(',');\n }\n return String(value);\n },\n };\n}\n"],"mappings":";;;;;;;;;AAkDA,SAAgB,aACd,QACA,OAC8B;CAC9B,MAAM,SAAS,OAAO,YAAY,CAAC,SAAS,KAAK;CACjD,IAAI,kBAAkB,SACpB,MAAM,IAAI,MACR,mFACF;CAEF,OAAO;AACT;;AAOA,SAAgB,iBAAiB,OAA2C;CAC1E,OACE,OAAO,UAAU,YACjB,UAAU,QACV,eAAe,SACf,OAAQ,MAA2B,YAAY,EAAE,aAAa;AAElE;;AAGA,SAAgB,QAAQ,OAAyC;CAC/D,OACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAyB,UAAU,cAC3C,OAAQ,MAAyB,cAAc;AAEnD;;;;;AAUA,SAAgB,gBAAmB,WAAmB,QAAuC;CAC3F,OAAO;EACL,MAAM,OAAyC;GAC7C,MAAM,SAAS,aAAa,QAAQ,KAAK;GACzC,IAAI,CAAC,OAAO,QACV,OAAO,OAAO;GAEhB,MAAM,WAAW,OAAO,OAAO,KAAK,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI;GAC9D,MAAM,IAAI,MAAM,mBAAmB,UAAU,qBAAqB,UAAU;EAC9E;EACA,UAAU,OAAyB;GACjC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;GAClD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,GAAG;GAC/C,OAAO,OAAO,KAAK;EACrB;CACF;AACF;;;;;;;;;;AAeA,SAAgB,qBACd,WACA,OACA,OAA2B,UACX;CAChB,IAAI,QAAQ,KAAK,GAAG,OAAO;CAC3B,IAAI,iBAAiB,KAAK,GACxB,OAAO,SAAS,UACZ,gBAAgB,WAAW,KAAK,IAC/B,WAAW,KAAK;CAEvB,MAAM,IAAI,MACR,mBAAmB,UAAU,sJAG/B;AACF;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,WAAc,QAAuC;CACnE,OAAO;EACL,MAAM,OAAyC;GAM7C,MAAM,SAAS,aAAa,QAHd,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,KAGP;GACzC,IAAI,CAAC,OAAO,QACV,OAAO,OAAO;GAMhB,MAAM,gBAAgB,aAAa,QAAQ,KAAA,CAAS;GACpD,IAAI,CAAC,cAAc,QACjB,OAAO,cAAc;EAQzB;EAEA,UAAU,OAAyB;GACjC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;GAET,OAAO,OAAO,KAAK;EACrB;CACF;AACF;;;;;;;;;;;;AAiBA,SAAgB,gBAAmB,QAAuC;CACxE,OAAO;EACL,MAAM,OAAyC;GAE7C,IAAI,QAAiB;GACrB,IAAI,OAAO,UAAU,UACnB,QAAQ,CAAC,KAAK;QACT,IAAI,UAAU,KAAA,GACnB,QAAQ,KAAA;GAGV,MAAM,SAAS,aAAa,QAAQ,KAAK;GACzC,IAAI,CAAC,OAAO,QACV,OAAO,OAAO;GAIhB,MAAM,gBAAgB,aAAa,QAAQ,KAAA,CAAS;GACpD,IAAI,CAAC,cAAc,QACjB,OAAO,cAAc;EAIzB;EAEA,UAAU,OAAyB;GACjC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;GAET,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,WAAW,IAAI,OAAO,MAAM,KAAK,GAAG;GAEnD,OAAO,OAAO,KAAK;EACrB;CACF;AACF"}
@@ -10,18 +10,51 @@ import { useQueryStates } from "nuqs";
10
10
  *
11
11
  * Design doc: design/23-search-params.md §"Codec Bridge"
12
12
  */
13
+ var NULL_SENTINEL = { timberSentinel: "null" };
14
+ var UNDEFINED_SENTINEL = { timberSentinel: "undefined" };
15
+ function wrapNuqsValue(value) {
16
+ if (value === null) return NULL_SENTINEL;
17
+ if (value === void 0) return UNDEFINED_SENTINEL;
18
+ return value;
19
+ }
20
+ function unwrapNuqsValue(value) {
21
+ if (value === NULL_SENTINEL) return null;
22
+ if (value === UNDEFINED_SENTINEL) return void 0;
23
+ return value;
24
+ }
13
25
  /**
14
26
  * Bridge a timber SearchParamCodec to a nuqs-compatible SingleParser.
15
27
  *
16
28
  * nuqs parsers: { parse(string) → T|null, serialize?(T) → string, eq?, defaultValue? }
17
29
  * timber codecs: { parse(string|string[]|undefined) → T, serialize(T) → string|null }
30
+ *
31
+ * The defaultValue is computed eagerly. Codecs are documented to return a
32
+ * default rather than throw, but a throwing codec must not crash every
33
+ * component that mounts the hook — treat its default as undefined and let
34
+ * its error surface from server-side parse() instead.
18
35
  */
19
36
  function bridgeCodec(codec) {
37
+ let defaultValue;
38
+ try {
39
+ defaultValue = codec.parse(void 0);
40
+ } catch {
41
+ defaultValue = void 0;
42
+ }
20
43
  return {
21
- parse: (v) => codec.parse(v),
22
- serialize: (v) => codec.serialize(v) ?? "",
23
- defaultValue: codec.parse(void 0),
24
- eq: (a, b) => codec.serialize(a) === codec.serialize(b)
44
+ parse: (v) => wrapNuqsValue(codec.parse(v)),
45
+ serialize: (v) => {
46
+ const value = unwrapNuqsValue(v);
47
+ return value === void 0 ? "" : codec.serialize(value) ?? "";
48
+ },
49
+ defaultValue: wrapNuqsValue(defaultValue),
50
+ eq: (a, b) => {
51
+ if (a === b) return true;
52
+ try {
53
+ return codec.serialize(unwrapNuqsValue(a)) === codec.serialize(unwrapNuqsValue(b));
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
25
58
  };
26
59
  }
27
60
  /**
@@ -72,14 +105,39 @@ function useQueryStates$1(codecsOrRoute, _options, urlKeys) {
72
105
  if (err instanceof Error && /Invalid hook call|cannot be called|Cannot read properties of null/i.test(err.message)) throw new Error("useQueryStates is a client component hook and cannot be called outside a React component. Use definition.parse(searchParams) in server components instead.");
73
106
  throw err;
74
107
  }
108
+ let normalized = values;
109
+ for (const key of Object.keys(bridged)) {
110
+ const value = normalized[key];
111
+ if (value === NULL_SENTINEL || value === UNDEFINED_SENTINEL) {
112
+ if (normalized === values) normalized = { ...values };
113
+ normalized[key] = unwrapNuqsValue(value);
114
+ }
115
+ }
75
116
  const setParams = (partial, setOptions) => {
76
117
  const nuqsSetOptions = {};
77
118
  if (setOptions?.shallow !== void 0) nuqsSetOptions.shallow = setOptions.shallow;
78
119
  if (setOptions?.scroll !== void 0) nuqsSetOptions.scroll = setOptions.scroll;
79
120
  if (setOptions?.history !== void 0) nuqsSetOptions.history = setOptions.history;
80
- setValues(partial, nuqsSetOptions);
121
+ let forwarded = partial;
122
+ for (const key of Object.keys(partial)) {
123
+ const value = partial[key];
124
+ if (value === void 0) {
125
+ if (forwarded === partial) forwarded = { ...partial };
126
+ forwarded[key] = null;
127
+ } else if (value === null) {
128
+ let encoded = null;
129
+ try {
130
+ encoded = codecs[key]?.serialize(null) ?? null;
131
+ } catch {}
132
+ if (encoded !== null) {
133
+ if (forwarded === partial) forwarded = { ...partial };
134
+ forwarded[key] = NULL_SENTINEL;
135
+ }
136
+ }
137
+ }
138
+ setValues(forwarded, nuqsSetOptions);
81
139
  };
82
- return [values, setParams];
140
+ return [normalized, setParams];
83
141
  }
84
142
  /**
85
143
  * Create a useQueryStates binding for a SearchParamsDefinition.
@@ -93,4 +151,4 @@ function bindUseQueryStates(definition) {
93
151
  //#endregion
94
152
  export { useQueryStates$1 as n, bindUseQueryStates as t };
95
153
 
96
- //# sourceMappingURL=use-query-states-CbeQmext.js.map
154
+ //# sourceMappingURL=use-query-states-DFvWd-EA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-query-states-DFvWd-EA.js","names":[],"sources":["../../src/client/use-query-states.ts"],"sourcesContent":["/**\n * useQueryStates — client-side hook for URL-synced search params.\n *\n * Delegates to nuqs for URL synchronization, batching, React 19 transitions,\n * and throttled URL writes. Bridges timber's SearchParamCodec protocol to\n * nuqs-compatible parsers.\n *\n * Design doc: design/23-search-params.md §\"Codec Bridge\"\n */\n\n'use client';\n\nimport { useQueryStates as nuqsUseQueryStates } from 'nuqs';\nimport type { SingleParser } from 'nuqs';\nimport type {\n SearchParamCodec,\n SearchParamsDefinition,\n SetParams,\n QueryStatesOptions,\n} from '../search-params/define.js';\nimport { getSearchParamsDefinition } from '../search-params/registry.js';\n\n// ─── Codec Bridge ─────────────────────────────────────────────────\n\n// nuqs's parser contract conflates values timber codecs distinguish:\n// parse() returning null means \"unparseable, substitute defaultValue\",\n// and undefined entries are skipped entirely. Timber codecs can\n// legitimately produce both — bare z.string() yields undefined for absent\n// params (implicit optionality), and a codec may map a present value to\n// null. Wrap those two values in sentinels across the nuqs boundary and\n// unwrap them before handing values back to the caller, so the client\n// hook returns exactly what server-side parse() returns.\n// Unique object references compared by identity — a codec can never\n// produce these from URL input, so user-controlled strings cannot collide\n// with them (unlike string sentinels), and unlike Symbols they survive\n// nuqs's internal string coercion without throwing.\nconst NULL_SENTINEL: object = { timberSentinel: 'null' };\nconst UNDEFINED_SENTINEL: object = { timberSentinel: 'undefined' };\n\nfunction wrapNuqsValue(value: unknown): unknown {\n if (value === null) return NULL_SENTINEL;\n if (value === undefined) return UNDEFINED_SENTINEL;\n return value;\n}\n\nfunction unwrapNuqsValue(value: unknown): unknown {\n if (value === NULL_SENTINEL) return null;\n if (value === UNDEFINED_SENTINEL) return undefined;\n return value;\n}\n\n/**\n * Bridge a timber SearchParamCodec to a nuqs-compatible SingleParser.\n *\n * nuqs parsers: { parse(string) → T|null, serialize?(T) → string, eq?, defaultValue? }\n * timber codecs: { parse(string|string[]|undefined) → T, serialize(T) → string|null }\n *\n * The defaultValue is computed eagerly. Codecs are documented to return a\n * default rather than throw, but a throwing codec must not crash every\n * component that mounts the hook — treat its default as undefined and let\n * its error surface from server-side parse() instead.\n */\nfunction bridgeCodec<T>(codec: SearchParamCodec<T>): SingleParser<T> & { defaultValue: T } {\n let defaultValue: unknown;\n try {\n defaultValue = codec.parse(undefined);\n } catch {\n defaultValue = undefined;\n }\n return {\n parse: (v: string) => wrapNuqsValue(codec.parse(v)),\n serialize: (v: unknown) => {\n const value = unwrapNuqsValue(v);\n // Delegate null to the codec — some codecs encode null as a real\n // query value. undefined has no encoding; nuqs requires a string.\n return value === undefined ? '' : (codec.serialize(value as T) ?? '');\n },\n defaultValue: wrapNuqsValue(defaultValue),\n eq: (a: unknown, b: unknown) => {\n if (a === b) return true;\n try {\n return (\n codec.serialize(unwrapNuqsValue(a) as T) === codec.serialize(unwrapNuqsValue(b) as T)\n );\n } catch {\n return false;\n }\n },\n } as SingleParser<T> & { defaultValue: T };\n}\n\n/**\n * Bridge an entire codec map to nuqs-compatible parsers.\n */\nfunction bridgeCodecs<T extends Record<string, unknown>>(codecs: {\n [K in keyof T]: SearchParamCodec<T[K]>;\n}) {\n const result: Record<string, SingleParser<unknown> & { defaultValue: unknown }> = {};\n for (const key of Object.keys(codecs)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n result[key] = bridgeCodec(codecs[key as keyof T]) as any;\n }\n return result as { [K in keyof T]: SingleParser<T[K]> & { defaultValue: T[K] } };\n}\n\n// ─── Hook ─────────────────────────────────────────────────────────\n\n/**\n * Read and write typed search params from/to the URL.\n *\n * Delegates to nuqs internally. The timber nuqs adapter (auto-injected in\n * browser-entry.ts) handles RSC navigation on non-shallow updates.\n *\n * Usage:\n * ```ts\n * // Via a SearchParamsDefinition\n * const [params, setParams] = definition.useQueryStates()\n *\n * // Standalone with inline codecs\n * const [params, setParams] = useQueryStates({\n * page: fromSchema(z.coerce.number().int().min(1).default(1)),\n * })\n * ```\n */\nexport function useQueryStates<T extends Record<string, unknown>>(\n codecsOrRoute: { [K in keyof T]: SearchParamCodec<T[K]> } | string,\n _options?: QueryStatesOptions,\n urlKeys?: Readonly<Record<string, string>>\n): [T, SetParams<T>] {\n // Route-string overload: resolve codecs from the registry\n let codecs: { [K in keyof T]: SearchParamCodec<T[K]> };\n let resolvedUrlKeys = urlKeys;\n if (typeof codecsOrRoute === 'string') {\n const definition = getSearchParamsDefinition(codecsOrRoute);\n if (!definition) {\n throw new Error(\n `useQueryStates('${codecsOrRoute}'): no search params registered for this route. ` +\n `Either the route has no search-params.ts file, or it hasn't been loaded yet. ` +\n `For cross-route usage, import the definition explicitly.`\n );\n }\n codecs = definition.codecs as { [K in keyof T]: SearchParamCodec<T[K]> };\n resolvedUrlKeys = definition.urlKeys;\n } else {\n codecs = codecsOrRoute;\n }\n\n const bridged = bridgeCodecs(codecs);\n\n // Forward hook-level options (shallow, scroll, history) to nuqs.\n // These become the default for all setter calls from this hook instance.\n // Per-call options in setParams(values, opts) override these defaults.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const nuqsOptions: any = {};\n if (_options?.shallow !== undefined) nuqsOptions.shallow = _options.shallow;\n if (_options?.scroll !== undefined) nuqsOptions.scroll = _options.scroll;\n if (_options?.history !== undefined) nuqsOptions.history = _options.history;\n if (resolvedUrlKeys && Object.keys(resolvedUrlKeys).length > 0) {\n nuqsOptions.urlKeys = resolvedUrlKeys;\n }\n\n let values: Record<string, unknown>;\n let setValues: Function;\n try {\n [values, setValues] = nuqsUseQueryStates(bridged, nuqsOptions);\n } catch (err) {\n if (\n err instanceof Error &&\n /Invalid hook call|cannot be called|Cannot read properties of null/i.test(err.message)\n ) {\n throw new Error(\n 'useQueryStates is a client component hook and cannot be called outside a React component. ' +\n 'Use definition.parse(searchParams) in server components instead.'\n );\n }\n throw err;\n }\n\n // Unwrap the null/undefined sentinels the bridge injected (see Codec\n // Bridge above) so callers see exactly what server-side parse() returns.\n // Copy-on-write preserves the identity of nuqs's memoized values object\n // when nothing needs unwrapping.\n let normalized = values;\n for (const key of Object.keys(bridged)) {\n const value = normalized[key];\n if (value === NULL_SENTINEL || value === UNDEFINED_SENTINEL) {\n if (normalized === values) normalized = { ...values };\n normalized[key] = unwrapNuqsValue(value);\n }\n }\n\n // Wrap the nuqs setter to match timber's SetParams<T> signature.\n // nuqs's setter accepts Partial<Nullable<Values>> | UpdaterFn | null.\n // timber's setter accepts Partial<T> with optional SetParamsOptions.\n const setParams: SetParams<T> = (partial, setOptions?) => {\n const nuqsSetOptions: Record<string, unknown> = {};\n if (setOptions?.shallow !== undefined) nuqsSetOptions.shallow = setOptions.shallow;\n if (setOptions?.scroll !== undefined) nuqsSetOptions.scroll = setOptions.scroll;\n if (setOptions?.history !== undefined) nuqsSetOptions.history = setOptions.history;\n // nuqs's update loop skips undefined entries and treats null as a\n // key deletion before serialize runs. Timber semantics:\n // - setParams({ q: undefined }) must clear ?q= (absent = undefined),\n // so explicit undefined maps to a null deletion.\n // - setParams({ q: null }) clears the key only when the codec encodes\n // null as \"omit\" (serialize(null) === null). If the codec encodes\n // null as a real query value, forward the sentinel so the bridged\n // serialize writes it — matching definition.serialize({ q: null }).\n let forwarded: Record<string, unknown> = partial;\n for (const key of Object.keys(partial)) {\n const value = partial[key as keyof T];\n if (value === undefined) {\n if (forwarded === partial) forwarded = { ...partial };\n forwarded[key] = null;\n } else if (value === null) {\n let encoded: string | null = null;\n try {\n encoded = codecs[key as keyof T]?.serialize(null as T[keyof T]) ?? null;\n } catch {\n // Codec can't serialize null — treat as a deletion.\n }\n if (encoded !== null) {\n if (forwarded === partial) forwarded = { ...partial };\n forwarded[key] = NULL_SENTINEL;\n }\n }\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n void setValues(forwarded as any, nuqsSetOptions);\n };\n\n return [normalized as T, setParams];\n}\n\n// ─── Definition binding ───────────────────────────────────────────\n\n/**\n * Create a useQueryStates binding for a SearchParamsDefinition.\n * This is used internally by SearchParamsDefinition.useQueryStates().\n */\nexport function bindUseQueryStates<T extends Record<string, unknown>>(\n definition: SearchParamsDefinition<T>\n): (options?: QueryStatesOptions) => [T, SetParams<T>] {\n return (options?: QueryStatesOptions) => {\n return useQueryStates<T>(definition.codecs, options, definition.urlKeys);\n };\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { n as classifyUrlSegment } from "./segment-classify-CDDRVKs7.js";
2
- import { n as findSchemaFile, r as parseExistingSchemaKeys } from "./cli-schema-sync-mGfRbjh2.js";
2
+ import { n as findSchemaFile, r as parseExistingSchemaKeys } from "./cli-schema-sync-EXGYPhI2.js";
3
+ import { parseAst } from "vite";
3
4
  import { join, posix, relative } from "node:path";
4
5
  import { existsSync, readFileSync, statSync } from "node:fs";
5
- import { parseAst } from "vite";
6
6
  //#region src/routing/codegen-shared.ts
7
7
  /**
8
8
  * Shared codegen helpers — import-path computation, codec chain type
@@ -985,4 +985,4 @@ function walk(node, chain, result, includeSlots) {
985
985
  //#endregion
986
986
  export { generateRouteMap as a, fileHasDirective as c, getPrerenderExport as d, validateSchemaAgainstRoutes as i, fileHasExport as l, collectInterceptionRewrites as n, fileHasAnyExport as o, collectDynamicSegmentsFromTree as r, fileHasDefaultExport as s, collectLeafRoutes as t, fileHasStarExport as u };
987
987
 
988
- //# sourceMappingURL=walkers-BL3MCMgO.js.map
988
+ //# sourceMappingURL=walkers-DBVzXuWc.js.map