@timber-js/app 0.2.0-alpha.60 → 0.2.0-alpha.61

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 (40) hide show
  1. package/dist/_chunks/{define-D5STJpIr.js → define-CT98cU9c.js} +2 -2
  2. package/dist/_chunks/{define-D5STJpIr.js.map → define-CT98cU9c.js.map} +1 -1
  3. package/dist/_chunks/{define-cookie-DtAavax4.js → define-cookie-BWr_52kY.js} +4 -4
  4. package/dist/_chunks/{define-cookie-DtAavax4.js.map → define-cookie-BWr_52kY.js.map} +1 -1
  5. package/dist/_chunks/{request-context-0wfZsnhh.js → request-context-rju2rbga.js} +2 -2
  6. package/dist/_chunks/{request-context-0wfZsnhh.js.map → request-context-rju2rbga.js.map} +1 -1
  7. package/dist/_chunks/{stale-reload-DKN3aXxR.js → stale-reload-BSSym1MJ.js} +5 -2
  8. package/dist/_chunks/{stale-reload-DKN3aXxR.js.map → stale-reload-BSSym1MJ.js.map} +1 -1
  9. package/dist/client/index.js +3 -3
  10. package/dist/client/index.js.map +1 -1
  11. package/dist/client/rsc-fetch.d.ts.map +1 -1
  12. package/dist/client/stale-reload.d.ts.map +1 -1
  13. package/dist/cookies/index.js +1 -1
  14. package/dist/index.js +3 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/params/define.d.ts.map +1 -1
  17. package/dist/params/index.js +1 -1
  18. package/dist/plugins/dev-browser-logs.d.ts.map +1 -1
  19. package/dist/server/fallback-error.d.ts.map +1 -1
  20. package/dist/server/index.js +3 -1
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/server/pipeline.d.ts.map +1 -1
  23. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  24. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  25. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  26. package/dist/server/stream-utils.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/client/browser-entry.ts +12 -0
  29. package/src/client/rsc-fetch.ts +3 -1
  30. package/src/client/stale-reload.ts +19 -2
  31. package/src/params/define.ts +11 -4
  32. package/src/plugins/dev-browser-logs.ts +10 -0
  33. package/src/plugins/routing.ts +1 -1
  34. package/src/server/fallback-error.ts +19 -7
  35. package/src/server/pipeline.ts +9 -0
  36. package/src/server/rsc-entry/error-renderer.ts +10 -1
  37. package/src/server/rsc-entry/index.ts +12 -0
  38. package/src/server/rsc-entry/rsc-payload.ts +1 -1
  39. package/src/server/rsc-entry/ssr-renderer.ts +12 -1
  40. package/src/server/stream-utils.ts +10 -6
@@ -106,7 +106,7 @@ function defineSegmentParams(codecs) {
106
106
  }
107
107
  async function load() {
108
108
  if (typeof window !== "undefined") throw new Error("[timber] segmentParams.load() is server-only. Use useSegmentParams() on the client.");
109
- return parse(await getRawSegmentParams());
109
+ return await getRawSegmentParams();
110
110
  }
111
111
  return {
112
112
  parse,
@@ -118,4 +118,4 @@ function defineSegmentParams(codecs) {
118
118
  //#endregion
119
119
  export { defineSegmentParams as n, _setRawSegmentParamsFn as t };
120
120
 
121
- //# sourceMappingURL=define-D5STJpIr.js.map
121
+ //# sourceMappingURL=define-CT98cU9c.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"define-D5STJpIr.js","names":[],"sources":["../../src/params/define.ts"],"sourcesContent":["/**\n * defineParams — factory for typed route param coercion.\n *\n * Creates a ParamsDefinition that coerces raw string params from the\n * URL into typed values. Used by exporting from layout.tsx (segment-level)\n * or page.tsx (fallback).\n *\n * Reuses the shared Codec<T> protocol with Standard Schema auto-detection,\n * same pattern as defineSearchParams. Runtime constraints are stricter:\n * - serialize must return string (not null — path segments can't be omitted)\n * - parse throwing → 404 (invalid param value)\n *\n * Design doc: design/07a-route-params-triage.md\n */\n\nimport type { Codec } from '../codec.js';\n\n// ---------------------------------------------------------------------------\n// Server-only ALS reference for .load()\n// ---------------------------------------------------------------------------\n\n// Same pattern as search-params: eagerly registered at server startup\n// to avoid dynamic imports that lose ALS context. See TIM-523.\nlet _rawSegmentParams: (() => Promise<Record<string, string | string[]>>) | undefined;\n\n/**\n * Register the rawSegmentParams function. Called once at module load time\n * from request-context.ts to avoid dynamic import at call time.\n * @internal\n */\nexport function _setRawSegmentParamsFn(fn: () => Promise<Record<string, string | string[]>>): void {\n _rawSegmentParams = fn;\n}\n\nfunction getRawSegmentParams(): Promise<Record<string, string | string[]>> {\n if (!_rawSegmentParams) {\n throw new Error(\n '[timber] segmentParams.load() is only available on the server. ' +\n 'Use useSegmentParams() on the client.'\n );\n }\n return _rawSegmentParams();\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Infer the output type from a Codec or StandardSchemaV1. */\nexport type InferParamField<V> =\n V extends Codec<infer T> ? T : V extends StandardSchemaV1<infer T> ? T : never;\n\n/** Acceptable field value for defineParams: a Codec or a Standard Schema. */\nexport type ParamField<T = unknown> = Codec<T> | StandardSchemaV1<T>;\n\n/**\n * A typed route params definition.\n *\n * Returned by defineParams(). Provides parse (string → typed) and\n * serialize (typed → string) for each declared param.\n */\nexport interface ParamsDefinition<T extends Record<string, unknown>> {\n /** Parse raw string params into typed values. Throws on invalid values. */\n parse(raw: Record<string, string | string[]>): T;\n\n /** Serialize typed values back to strings for URL construction. */\n serialize(values: T): Record<string, string>;\n\n /**\n * Load typed segment params from the current request context (ALS).\n *\n * Server-only. Reads rawSegmentParams() from ALS and coerces through\n * this definition's codecs, returning fully typed params.\n *\n * ```ts\n * // app/products/[id]/params.ts\n * export const segmentParams = defineSegmentParams({ id: z.coerce.number() })\n *\n * // app/products/[id]/page.tsx\n * import { segmentParams } from './params'\n * export default async function Page() {\n * const { id } = await segmentParams.load() // id: number\n * }\n * ```\n */\n load(): Promise<T>;\n\n /** Read-only codec map. */\n codecs: { [K in keyof T]: Codec<T[K]> };\n}\n\n// ---------------------------------------------------------------------------\n// Standard Schema interface (subset — same as in search-params/define.ts)\n// ---------------------------------------------------------------------------\n\ninterface StandardSchemaV1<Output = unknown> {\n '~standard': {\n validate(\n value: unknown\n ):\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> }\n | Promise<\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> }\n >;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction 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\nfunction 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 * Validate sync for Standard Schema (same helper as search-params/codecs.ts).\n */\nfunction validateSync<Output>(\n schema: StandardSchemaV1<Output>,\n value: unknown\n):\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> } {\n const result = schema['~standard'].validate(value);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] defineParams: schema returned a Promise — only sync schemas are supported.'\n );\n }\n return result;\n}\n\n/**\n * Wrap a Standard Schema into a Codec for route params.\n *\n * Unlike fromSchema for search params:\n * - Parse throws on failure (no fallback to default)\n * - Serialize returns string (not null)\n */\nfunction fromParamSchema<T>(fieldName: string, schema: StandardSchemaV1<T>): Codec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // Route params are always strings (single segment) or string[] (catch-all)\n const input = Array.isArray(value) ? value : value;\n\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // For route params, parse failure means the param is invalid → throw\n const messages = result.issues.map((i) => i.message).join(', ');\n throw new Error(`[timber] Param '${fieldName}' coercion failed: ${messages}`);\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n // Catch-all segments produce arrays — join with '/' for path reconstruction\n if (Array.isArray(value)) {\n return value.join('/');\n }\n return String(value);\n },\n };\n}\n\n/**\n * Resolve a field value to a Codec. Auto-detects Standard Schema objects.\n */\nfunction resolveField(fieldName: string, value: ParamField): Codec<unknown> {\n if (isCodec(value)) {\n return value;\n }\n\n if (isStandardSchema(value)) {\n return fromParamSchema(fieldName, value);\n }\n\n throw new Error(\n `[timber] defineParams: 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 * Validate that no codec's serialize returns null.\n * Route params are structural — they must produce a valid path segment.\n */\nfunction validateSerialize(codecMap: Record<string, Codec<unknown>>): void {\n for (const [key, codec] of Object.entries(codecMap)) {\n // Test serialize with a sample parsed value to check for null\n // We can't exhaustively test, but we can check that serialize(parse(\"test\"))\n // doesn't return null for a basic input.\n try {\n const testValue = codec.parse('test');\n const serialized = codec.serialize(testValue);\n if (serialized === null) {\n throw new Error(\n `[timber] defineParams: field '${key}' codec.serialize() returned null.\\n` +\n ` Route params are path segments — they cannot be omitted.\\n` +\n ` Ensure serialize() always returns a string.`\n );\n }\n } catch (e) {\n // parse('test') may throw for strict codecs (e.g., number-only).\n // That's fine — it means the codec validates. We only care about\n // serialize returning null, which we can't test without a valid value.\n if (e instanceof Error && e.message.includes('returned null')) {\n throw e;\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a ParamsDefinition from a map of codecs and/or Standard Schema objects.\n *\n * ```ts\n * // app/products/[id]/layout.tsx\n * import { defineParams } from '@timber-js/app/params'\n * import { z } from 'zod/v4'\n *\n * export const params = defineParams({\n * id: z.coerce.number().int().positive(),\n * })\n *\n * export default function Layout({ children }) { return children }\n * ```\n */\nexport function defineSegmentParams<C extends Record<string, ParamField>>(\n codecs: C\n): ParamsDefinition<{ [K in keyof C]: InferParamField<C[K]> }> {\n type T = { [K in keyof C]: InferParamField<C[K]> };\n\n const resolvedCodecs: Record<string, Codec<unknown>> = {};\n\n for (const [key, value] of Object.entries(codecs)) {\n resolvedCodecs[key] = resolveField(key, value as ParamField);\n }\n\n // Validate that serialize doesn't return null\n validateSerialize(resolvedCodecs);\n\n // ---- parse ----\n function parse(raw: Record<string, string | string[]>): T {\n const result: Record<string, unknown> = {};\n\n for (const [key, codec] of Object.entries(resolvedCodecs)) {\n const rawValue = raw[key];\n // Route params are always present (the route matched)\n result[key] = codec.parse(rawValue);\n }\n\n return result as T;\n }\n\n // ---- serialize ----\n function serialize(values: T): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const [key, codec] of Object.entries(resolvedCodecs)) {\n const serialized = codec.serialize(values[key as keyof T] as unknown);\n if (serialized === null) {\n throw new Error(\n `[timber] params.serialize: field '${key}' serialized to null. ` +\n `Route params must produce a valid path segment.`\n );\n }\n result[key] = serialized;\n }\n\n return result;\n }\n\n // ---- load ----\n // ALS-backed: reads rawSegmentParams() from the current request context\n // and parses through codecs. Server-only — throws on client.\n async function load(): Promise<T> {\n if (typeof window !== 'undefined') {\n throw new Error(\n '[timber] segmentParams.load() is server-only. ' + 'Use useSegmentParams() on the client.'\n );\n }\n const raw = await getRawSegmentParams();\n return parse(raw);\n }\n\n const definition: ParamsDefinition<T> = {\n parse,\n serialize,\n load,\n codecs: resolvedCodecs as { [K in keyof T]: Codec<T[K]> },\n };\n\n return definition;\n}\n"],"mappings":";AAuBA,IAAI;;;;;;AAOJ,SAAgB,uBAAuB,IAA4D;AACjG,qBAAoB;;AAGtB,SAAS,sBAAkE;AACzE,KAAI,CAAC,kBACH,OAAM,IAAI,MACR,uGAED;AAEH,QAAO,mBAAmB;;AAwE5B,SAAS,iBAAiB,OAA2C;AACnE,QACE,OAAO,UAAU,YACjB,UAAU,QACV,eAAe,SACf,OAAQ,MAA2B,cAAc,aAAa;;AAIlE,SAAS,QAAQ,OAAyC;AACxD,QACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAyB,UAAU,cAC3C,OAAQ,MAAyB,cAAc;;;;;AAOnD,SAAS,aACP,QACA,OAGoE;CACpE,MAAM,SAAS,OAAO,aAAa,SAAS,MAAM;AAClD,KAAI,kBAAkB,QACpB,OAAM,IAAI,MACR,sFACD;AAEH,QAAO;;;;;;;;;AAUT,SAAS,gBAAmB,WAAmB,QAAuC;AACpF,QAAO;EACL,MAAM,OAAyC;GAI7C,MAAM,SAAS,aAAa,QAFd,MAAM,QAAQ,MAAM,GAAG,QAAQ,MAEH;AAC1C,OAAI,CAAC,OAAO,OACV,QAAO,OAAO;GAIhB,MAAM,WAAW,OAAO,OAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,KAAK,KAAK;AAC/D,SAAM,IAAI,MAAM,mBAAmB,UAAU,qBAAqB,WAAW;;EAG/E,UAAU,OAAyB;AACjC,OAAI,UAAU,QAAQ,UAAU,KAAA,EAC9B,QAAO;AAGT,OAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,KAAK,IAAI;AAExB,UAAO,OAAO,MAAM;;EAEvB;;;;;AAMH,SAAS,aAAa,WAAmB,OAAmC;AAC1E,KAAI,QAAQ,MAAM,CAChB,QAAO;AAGT,KAAI,iBAAiB,MAAM,CACzB,QAAO,gBAAgB,WAAW,MAAM;AAG1C,OAAM,IAAI,MACR,iCAAiC,UAAU,uJAG5C;;;;;;AAOH,SAAS,kBAAkB,UAAgD;AACzE,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,CAIjD,KAAI;EACF,MAAM,YAAY,MAAM,MAAM,OAAO;AAErC,MADmB,MAAM,UAAU,UAAU,KAC1B,KACjB,OAAM,IAAI,MACR,iCAAiC,IAAI,+IAGtC;UAEI,GAAG;AAIV,MAAI,aAAa,SAAS,EAAE,QAAQ,SAAS,gBAAgB,CAC3D,OAAM;;;;;;;;;;;;;;;;;;AAyBd,SAAgB,oBACd,QAC6D;CAG7D,MAAM,iBAAiD,EAAE;AAEzD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC/C,gBAAe,OAAO,aAAa,KAAK,MAAoB;AAI9D,mBAAkB,eAAe;CAGjC,SAAS,MAAM,KAA2C;EACxD,MAAM,SAAkC,EAAE;AAE1C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,eAAe,EAAE;GACzD,MAAM,WAAW,IAAI;AAErB,UAAO,OAAO,MAAM,MAAM,SAAS;;AAGrC,SAAO;;CAIT,SAAS,UAAU,QAAmC;EACpD,MAAM,SAAiC,EAAE;AAEzC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,eAAe,EAAE;GACzD,MAAM,aAAa,MAAM,UAAU,OAAO,KAA2B;AACrE,OAAI,eAAe,KACjB,OAAM,IAAI,MACR,qCAAqC,IAAI,uEAE1C;AAEH,UAAO,OAAO;;AAGhB,SAAO;;CAMT,eAAe,OAAmB;AAChC,MAAI,OAAO,WAAW,YACpB,OAAM,IAAI,MACR,sFACD;AAGH,SAAO,MADK,MAAM,qBAAqB,CACtB;;AAUnB,QAPwC;EACtC;EACA;EACA;EACA,QAAQ;EACT"}
1
+ {"version":3,"file":"define-CT98cU9c.js","names":[],"sources":["../../src/params/define.ts"],"sourcesContent":["/**\n * defineParams — factory for typed route param coercion.\n *\n * Creates a ParamsDefinition that coerces raw string params from the\n * URL into typed values. Used by exporting from layout.tsx (segment-level)\n * or page.tsx (fallback).\n *\n * Reuses the shared Codec<T> protocol with Standard Schema auto-detection,\n * same pattern as defineSearchParams. Runtime constraints are stricter:\n * - serialize must return string (not null — path segments can't be omitted)\n * - parse throwing → 404 (invalid param value)\n *\n * Design doc: design/07a-route-params-triage.md\n */\n\nimport type { Codec } from '../codec.js';\n\n// ---------------------------------------------------------------------------\n// Server-only ALS reference for .load()\n// ---------------------------------------------------------------------------\n\n// Same pattern as search-params: eagerly registered at server startup\n// to avoid dynamic imports that lose ALS context. See TIM-523.\nlet _rawSegmentParams: (() => Promise<Record<string, string | string[]>>) | undefined;\n\n/**\n * Register the rawSegmentParams function. Called once at module load time\n * from request-context.ts to avoid dynamic import at call time.\n * @internal\n */\nexport function _setRawSegmentParamsFn(fn: () => Promise<Record<string, string | string[]>>): void {\n _rawSegmentParams = fn;\n}\n\nfunction getRawSegmentParams(): Promise<Record<string, string | string[]>> {\n if (!_rawSegmentParams) {\n throw new Error(\n '[timber] segmentParams.load() is only available on the server. ' +\n 'Use useSegmentParams() on the client.'\n );\n }\n return _rawSegmentParams();\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Infer the output type from a Codec or StandardSchemaV1. */\nexport type InferParamField<V> =\n V extends Codec<infer T> ? T : V extends StandardSchemaV1<infer T> ? T : never;\n\n/** Acceptable field value for defineParams: a Codec or a Standard Schema. */\nexport type ParamField<T = unknown> = Codec<T> | StandardSchemaV1<T>;\n\n/**\n * A typed route params definition.\n *\n * Returned by defineParams(). Provides parse (string → typed) and\n * serialize (typed → string) for each declared param.\n */\nexport interface ParamsDefinition<T extends Record<string, unknown>> {\n /** Parse raw string params into typed values. Throws on invalid values. */\n parse(raw: Record<string, string | string[]>): T;\n\n /** Serialize typed values back to strings for URL construction. */\n serialize(values: T): Record<string, string>;\n\n /**\n * Load typed segment params from the current request context (ALS).\n *\n * Server-only. Reads rawSegmentParams() from ALS and coerces through\n * this definition's codecs, returning fully typed params.\n *\n * ```ts\n * // app/products/[id]/params.ts\n * export const segmentParams = defineSegmentParams({ id: z.coerce.number() })\n *\n * // app/products/[id]/page.tsx\n * import { segmentParams } from './params'\n * export default async function Page() {\n * const { id } = await segmentParams.load() // id: number\n * }\n * ```\n */\n load(): Promise<T>;\n\n /** Read-only codec map. */\n codecs: { [K in keyof T]: Codec<T[K]> };\n}\n\n// ---------------------------------------------------------------------------\n// Standard Schema interface (subset — same as in search-params/define.ts)\n// ---------------------------------------------------------------------------\n\ninterface StandardSchemaV1<Output = unknown> {\n '~standard': {\n validate(\n value: unknown\n ):\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> }\n | Promise<\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> }\n >;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction 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\nfunction 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 * Validate sync for Standard Schema (same helper as search-params/codecs.ts).\n */\nfunction validateSync<Output>(\n schema: StandardSchemaV1<Output>,\n value: unknown\n):\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> } {\n const result = schema['~standard'].validate(value);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] defineParams: schema returned a Promise — only sync schemas are supported.'\n );\n }\n return result;\n}\n\n/**\n * Wrap a Standard Schema into a Codec for route params.\n *\n * Unlike fromSchema for search params:\n * - Parse throws on failure (no fallback to default)\n * - Serialize returns string (not null)\n */\nfunction fromParamSchema<T>(fieldName: string, schema: StandardSchemaV1<T>): Codec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // Route params are always strings (single segment) or string[] (catch-all)\n const input = Array.isArray(value) ? value : value;\n\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // For route params, parse failure means the param is invalid → throw\n const messages = result.issues.map((i) => i.message).join(', ');\n throw new Error(`[timber] Param '${fieldName}' coercion failed: ${messages}`);\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n // Catch-all segments produce arrays — join with '/' for path reconstruction\n if (Array.isArray(value)) {\n return value.join('/');\n }\n return String(value);\n },\n };\n}\n\n/**\n * Resolve a field value to a Codec. Auto-detects Standard Schema objects.\n */\nfunction resolveField(fieldName: string, value: ParamField): Codec<unknown> {\n if (isCodec(value)) {\n return value;\n }\n\n if (isStandardSchema(value)) {\n return fromParamSchema(fieldName, value);\n }\n\n throw new Error(\n `[timber] defineParams: 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 * Validate that no codec's serialize returns null.\n * Route params are structural — they must produce a valid path segment.\n */\nfunction validateSerialize(codecMap: Record<string, Codec<unknown>>): void {\n for (const [key, codec] of Object.entries(codecMap)) {\n // Test serialize with a sample parsed value to check for null\n // We can't exhaustively test, but we can check that serialize(parse(\"test\"))\n // doesn't return null for a basic input.\n try {\n const testValue = codec.parse('test');\n const serialized = codec.serialize(testValue);\n if (serialized === null) {\n throw new Error(\n `[timber] defineParams: field '${key}' codec.serialize() returned null.\\n` +\n ` Route params are path segments — they cannot be omitted.\\n` +\n ` Ensure serialize() always returns a string.`\n );\n }\n } catch (e) {\n // parse('test') may throw for strict codecs (e.g., number-only).\n // That's fine — it means the codec validates. We only care about\n // serialize returning null, which we can't test without a valid value.\n if (e instanceof Error && e.message.includes('returned null')) {\n throw e;\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a ParamsDefinition from a map of codecs and/or Standard Schema objects.\n *\n * ```ts\n * // app/products/[id]/layout.tsx\n * import { defineParams } from '@timber-js/app/params'\n * import { z } from 'zod/v4'\n *\n * export const params = defineParams({\n * id: z.coerce.number().int().positive(),\n * })\n *\n * export default function Layout({ children }) { return children }\n * ```\n */\nexport function defineSegmentParams<C extends Record<string, ParamField>>(\n codecs: C\n): ParamsDefinition<{ [K in keyof C]: InferParamField<C[K]> }> {\n type T = { [K in keyof C]: InferParamField<C[K]> };\n\n const resolvedCodecs: Record<string, Codec<unknown>> = {};\n\n for (const [key, value] of Object.entries(codecs)) {\n resolvedCodecs[key] = resolveField(key, value as ParamField);\n }\n\n // Validate that serialize doesn't return null\n validateSerialize(resolvedCodecs);\n\n // ---- parse ----\n function parse(raw: Record<string, string | string[]>): T {\n const result: Record<string, unknown> = {};\n\n for (const [key, codec] of Object.entries(resolvedCodecs)) {\n const rawValue = raw[key];\n // Route params are always present (the route matched)\n result[key] = codec.parse(rawValue);\n }\n\n return result as T;\n }\n\n // ---- serialize ----\n function serialize(values: T): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const [key, codec] of Object.entries(resolvedCodecs)) {\n const serialized = codec.serialize(values[key as keyof T] as unknown);\n if (serialized === null) {\n throw new Error(\n `[timber] params.serialize: field '${key}' serialized to null. ` +\n `Route params must produce a valid path segment.`\n );\n }\n result[key] = serialized;\n }\n\n return result;\n }\n\n // ---- load ----\n // ALS-backed: reads segment params from the current request context.\n // Server-only — throws on client.\n //\n // The pipeline already coerces params via coerceSegmentParams() which\n // calls parse() and stores typed values in ALS via setSegmentParams().\n // We return those directly instead of re-parsing, because codecs may\n // not be idempotent (e.g., a codec that only accepts raw strings would\n // throw if given an already-parsed value). See TIM-574.\n async function load(): Promise<T> {\n if (typeof window !== 'undefined') {\n throw new Error(\n '[timber] segmentParams.load() is server-only. ' + 'Use useSegmentParams() on the client.'\n );\n }\n const params = await getRawSegmentParams();\n // params are already coerced by the pipeline — return as-is.\n return params as unknown as T;\n }\n\n const definition: ParamsDefinition<T> = {\n parse,\n serialize,\n load,\n codecs: resolvedCodecs as { [K in keyof T]: Codec<T[K]> },\n };\n\n return definition;\n}\n"],"mappings":";AAuBA,IAAI;;;;;;AAOJ,SAAgB,uBAAuB,IAA4D;AACjG,qBAAoB;;AAGtB,SAAS,sBAAkE;AACzE,KAAI,CAAC,kBACH,OAAM,IAAI,MACR,uGAED;AAEH,QAAO,mBAAmB;;AAwE5B,SAAS,iBAAiB,OAA2C;AACnE,QACE,OAAO,UAAU,YACjB,UAAU,QACV,eAAe,SACf,OAAQ,MAA2B,cAAc,aAAa;;AAIlE,SAAS,QAAQ,OAAyC;AACxD,QACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAyB,UAAU,cAC3C,OAAQ,MAAyB,cAAc;;;;;AAOnD,SAAS,aACP,QACA,OAGoE;CACpE,MAAM,SAAS,OAAO,aAAa,SAAS,MAAM;AAClD,KAAI,kBAAkB,QACpB,OAAM,IAAI,MACR,sFACD;AAEH,QAAO;;;;;;;;;AAUT,SAAS,gBAAmB,WAAmB,QAAuC;AACpF,QAAO;EACL,MAAM,OAAyC;GAI7C,MAAM,SAAS,aAAa,QAFd,MAAM,QAAQ,MAAM,GAAG,QAAQ,MAEH;AAC1C,OAAI,CAAC,OAAO,OACV,QAAO,OAAO;GAIhB,MAAM,WAAW,OAAO,OAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,KAAK,KAAK;AAC/D,SAAM,IAAI,MAAM,mBAAmB,UAAU,qBAAqB,WAAW;;EAG/E,UAAU,OAAyB;AACjC,OAAI,UAAU,QAAQ,UAAU,KAAA,EAC9B,QAAO;AAGT,OAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,KAAK,IAAI;AAExB,UAAO,OAAO,MAAM;;EAEvB;;;;;AAMH,SAAS,aAAa,WAAmB,OAAmC;AAC1E,KAAI,QAAQ,MAAM,CAChB,QAAO;AAGT,KAAI,iBAAiB,MAAM,CACzB,QAAO,gBAAgB,WAAW,MAAM;AAG1C,OAAM,IAAI,MACR,iCAAiC,UAAU,uJAG5C;;;;;;AAOH,SAAS,kBAAkB,UAAgD;AACzE,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,CAIjD,KAAI;EACF,MAAM,YAAY,MAAM,MAAM,OAAO;AAErC,MADmB,MAAM,UAAU,UAAU,KAC1B,KACjB,OAAM,IAAI,MACR,iCAAiC,IAAI,+IAGtC;UAEI,GAAG;AAIV,MAAI,aAAa,SAAS,EAAE,QAAQ,SAAS,gBAAgB,CAC3D,OAAM;;;;;;;;;;;;;;;;;;AAyBd,SAAgB,oBACd,QAC6D;CAG7D,MAAM,iBAAiD,EAAE;AAEzD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC/C,gBAAe,OAAO,aAAa,KAAK,MAAoB;AAI9D,mBAAkB,eAAe;CAGjC,SAAS,MAAM,KAA2C;EACxD,MAAM,SAAkC,EAAE;AAE1C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,eAAe,EAAE;GACzD,MAAM,WAAW,IAAI;AAErB,UAAO,OAAO,MAAM,MAAM,SAAS;;AAGrC,SAAO;;CAIT,SAAS,UAAU,QAAmC;EACpD,MAAM,SAAiC,EAAE;AAEzC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,eAAe,EAAE;GACzD,MAAM,aAAa,MAAM,UAAU,OAAO,KAA2B;AACrE,OAAI,eAAe,KACjB,OAAM,IAAI,MACR,qCAAqC,IAAI,uEAE1C;AAEH,UAAO,OAAO;;AAGhB,SAAO;;CAYT,eAAe,OAAmB;AAChC,MAAI,OAAO,WAAW,YACpB,OAAM,IAAI,MACR,sFACD;AAIH,SAFe,MAAM,qBAAqB;;AAY5C,QAPwC;EACtC;EACA;EACA;EACA,QAAQ;EACT"}
@@ -43,12 +43,12 @@ function defineCookie(name, options) {
43
43
  options: resolvedOptions,
44
44
  codec,
45
45
  async getCookie() {
46
- const { cookies } = await import("./request-context-0wfZsnhh.js").then((n) => n.l);
46
+ const { cookies } = await import("./request-context-rju2rbga.js").then((n) => n.l);
47
47
  const raw = cookies().get(name);
48
48
  return codec.parse(raw);
49
49
  },
50
50
  async setCookie(value) {
51
- const { cookies } = await import("./request-context-0wfZsnhh.js").then((n) => n.l);
51
+ const { cookies } = await import("./request-context-rju2rbga.js").then((n) => n.l);
52
52
  const serialized = codec.serialize(value);
53
53
  if (serialized === null) cookies().delete(name, {
54
54
  path: resolvedOptions.path,
@@ -57,7 +57,7 @@ function defineCookie(name, options) {
57
57
  else cookies().set(name, serialized, resolvedOptions);
58
58
  },
59
59
  async deleteCookie() {
60
- const { cookies } = await import("./request-context-0wfZsnhh.js").then((n) => n.l);
60
+ const { cookies } = await import("./request-context-rju2rbga.js").then((n) => n.l);
61
61
  cookies().delete(name, {
62
62
  path: resolvedOptions.path,
63
63
  domain: resolvedOptions.domain
@@ -90,4 +90,4 @@ function defineCookie(name, options) {
90
90
  //#endregion
91
91
  export { defineCookie as n, _registerUseCookieModule as t };
92
92
 
93
- //# sourceMappingURL=define-cookie-DtAavax4.js.map
93
+ //# sourceMappingURL=define-cookie-BWr_52kY.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"define-cookie-DtAavax4.js","names":[],"sources":["../../src/cookies/define-cookie.ts"],"sourcesContent":["/**\n * defineCookie — typed cookie definitions.\n *\n * Bundles name + codec + options into a reusable CookieDefinition<T>\n * with async .getCookie(), .setCookie(), .deleteCookie() server methods\n * and a sync .useCookie() client hook.\n *\n * Server methods are async to future-proof the API for v2 features\n * (signed cookies via crypto.subtle, encrypted cookies, external stores).\n *\n * Reuses the SearchParamCodec protocol via fromSchema() bridge.\n * Validation on read returns the codec default (never throws).\n *\n * IMPORTANT: This module must NOT have top-level value imports from either\n * server or client modules. Server methods lazy-import request-context;\n * useCookie() lazy-imports use-cookie. This ensures:\n * - Client bundles don't pull in ALS/server code\n * - RSC bundles don't pull in useSyncExternalStore/client code\n * - Tree-shaking is not required for correctness\n *\n * See design/29-cookies.md §\"Typed Cookies with Schema Validation\"\n */\n\nimport type { CookieOptions } from '../server/request-context.js';\nimport type { ClientCookieOptions } from '../client/use-cookie.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nimport type { Codec } from '../codec.js';\n\n/**\n * A codec that converts between string cookie values and typed values.\n * Type alias for the shared Codec<T> protocol.\n */\nexport type CookieCodec<T> = Codec<T>;\n\n/** Options for defineCookie: codec + CookieOptions merged. */\nexport interface DefineCookieOptions<T> extends CookieOptions {\n /** Codec for parsing/serializing the cookie value. */\n codec: CookieCodec<T>;\n}\n\n/** A fully typed cookie definition with server and client methods. */\nexport interface CookieDefinition<T> {\n /** The cookie name. */\n readonly name: string;\n /** The resolved cookie options (without codec). */\n readonly options: CookieOptions;\n /** The codec used for parsing/serializing. */\n readonly codec: CookieCodec<T>;\n\n /** Server: read the typed value from the current request. */\n getCookie(): Promise<T>;\n /** Server: set the typed value on the response. */\n setCookie(value: T): Promise<void>;\n /** Server: delete the cookie. */\n deleteCookie(): Promise<void>;\n\n /** Client: React hook for reading/writing this cookie. Returns [value, setter, deleter]. */\n useCookie(): [T, (value: T) => void, () => void];\n}\n\n// ─── Lazy Module References ───────────────────────────────────────────────\n//\n// These are resolved on first use, not at module load time. This prevents\n// the server module graph from pulling in client code and vice versa.\n// The dynamic import() in server methods is natural (they're async).\n// For useCookie() (sync), we cache the module reference after first load.\n\nlet _useCookieModule: typeof import('../client/use-cookie.js') | undefined;\n\nfunction getUseCookieModule(): typeof import('../client/use-cookie.js') {\n if (!_useCookieModule) {\n // In the client/SSR environment, this module is already in the module\n // graph (imported by the client entry). The throw is a safeguard —\n // if useCookie() is somehow called before the module is available,\n // the developer gets a clear error instead of a silent failure.\n throw new Error(\n '[timber] defineCookie().useCookie() requires @timber-js/app/client to be loaded. ' +\n 'This hook can only be used in client components.'\n );\n }\n return _useCookieModule;\n}\n\n/**\n * Register the client cookie module. Called by the client entry to wire\n * up the lazy reference without a top-level import.\n *\n * @internal — framework use only\n */\nexport function _registerUseCookieModule(mod: typeof import('../client/use-cookie.js')): void {\n _useCookieModule = mod;\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────\n\n/**\n * Define a typed cookie.\n *\n * ```ts\n * import { defineCookie } from '@timber-js/app/cookies';\n * import { fromSchema } from '@timber-js/app/search-params';\n * import { z } from 'zod/v4';\n *\n * export const themeCookie = defineCookie('theme', {\n * codec: fromSchema(z.enum(['light', 'dark', 'system']).default('system')),\n * httpOnly: false,\n * maxAge: 60 * 60 * 24 * 365,\n * });\n *\n * // Server\n * const theme = await themeCookie.getCookie();\n * await themeCookie.setCookie('dark');\n *\n * // Client\n * const [theme, setTheme] = themeCookie.useCookie();\n * ```\n */\nexport function defineCookie<T>(\n name: string,\n options: DefineCookieOptions<T>\n): CookieDefinition<T> {\n const { codec, ...cookieOpts } = options;\n const resolvedOptions: CookieOptions = { ...cookieOpts };\n\n return {\n name,\n options: resolvedOptions,\n codec,\n\n async getCookie(): Promise<T> {\n const { cookies } = await import('../server/request-context.js');\n const jar = cookies();\n const raw = jar.get(name);\n return codec.parse(raw);\n },\n\n async setCookie(value: T): Promise<void> {\n const { cookies } = await import('../server/request-context.js');\n const serialized = codec.serialize(value);\n if (serialized === null) {\n cookies().delete(name, {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n });\n } else {\n cookies().set(name, serialized, resolvedOptions);\n }\n },\n\n async deleteCookie(): Promise<void> {\n const { cookies } = await import('../server/request-context.js');\n cookies().delete(name, {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n });\n },\n\n useCookie(): [T, (value: T) => void, () => void] {\n const { useCookie: useRawCookie } = getUseCookieModule();\n\n // Extract client-safe options (no httpOnly — client cookies can't be httpOnly)\n const clientOpts: ClientCookieOptions = {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n maxAge: resolvedOptions.maxAge,\n expires: resolvedOptions.expires,\n sameSite: resolvedOptions.sameSite,\n secure: resolvedOptions.secure,\n };\n\n const [raw, setRaw, deleteRaw] = useRawCookie(name, clientOpts);\n const parsed = codec.parse(raw);\n\n const setTyped = (value: T): void => {\n const serialized = codec.serialize(value);\n if (serialized === null) {\n deleteRaw();\n } else {\n setRaw(serialized);\n }\n };\n\n return [parsed, setTyped, deleteRaw];\n },\n };\n}\n"],"mappings":";AAqEA,IAAI;AAEJ,SAAS,qBAA+D;AACtE,KAAI,CAAC,iBAKH,OAAM,IAAI,MACR,oIAED;AAEH,QAAO;;;;;;;;AAST,SAAgB,yBAAyB,KAAqD;AAC5F,oBAAmB;;;;;;;;;;;;;;;;;;;;;;;;AA2BrB,SAAgB,aACd,MACA,SACqB;CACrB,MAAM,EAAE,OAAO,GAAG,eAAe;CACjC,MAAM,kBAAiC,EAAE,GAAG,YAAY;AAExD,QAAO;EACL;EACA,SAAS;EACT;EAEA,MAAM,YAAwB;GAC5B,MAAM,EAAE,YAAY,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;GAEjC,MAAM,MADM,SAAS,CACL,IAAI,KAAK;AACzB,UAAO,MAAM,MAAM,IAAI;;EAGzB,MAAM,UAAU,OAAyB;GACvC,MAAM,EAAE,YAAY,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;GACjC,MAAM,aAAa,MAAM,UAAU,MAAM;AACzC,OAAI,eAAe,KACjB,UAAS,CAAC,OAAO,MAAM;IACrB,MAAM,gBAAgB;IACtB,QAAQ,gBAAgB;IACzB,CAAC;OAEF,UAAS,CAAC,IAAI,MAAM,YAAY,gBAAgB;;EAIpD,MAAM,eAA8B;GAClC,MAAM,EAAE,YAAY,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;AACjC,YAAS,CAAC,OAAO,MAAM;IACrB,MAAM,gBAAgB;IACtB,QAAQ,gBAAgB;IACzB,CAAC;;EAGJ,YAAiD;GAC/C,MAAM,EAAE,WAAW,iBAAiB,oBAAoB;GAYxD,MAAM,CAAC,KAAK,QAAQ,aAAa,aAAa,MATN;IACtC,MAAM,gBAAgB;IACtB,QAAQ,gBAAgB;IACxB,QAAQ,gBAAgB;IACxB,SAAS,gBAAgB;IACzB,UAAU,gBAAgB;IAC1B,QAAQ,gBAAgB;IACzB,CAE8D;GAC/D,MAAM,SAAS,MAAM,MAAM,IAAI;GAE/B,MAAM,YAAY,UAAmB;IACnC,MAAM,aAAa,MAAM,UAAU,MAAM;AACzC,QAAI,eAAe,KACjB,YAAW;QAEX,QAAO,WAAW;;AAItB,UAAO;IAAC;IAAQ;IAAU;IAAU;;EAEvC"}
1
+ {"version":3,"file":"define-cookie-BWr_52kY.js","names":[],"sources":["../../src/cookies/define-cookie.ts"],"sourcesContent":["/**\n * defineCookie — typed cookie definitions.\n *\n * Bundles name + codec + options into a reusable CookieDefinition<T>\n * with async .getCookie(), .setCookie(), .deleteCookie() server methods\n * and a sync .useCookie() client hook.\n *\n * Server methods are async to future-proof the API for v2 features\n * (signed cookies via crypto.subtle, encrypted cookies, external stores).\n *\n * Reuses the SearchParamCodec protocol via fromSchema() bridge.\n * Validation on read returns the codec default (never throws).\n *\n * IMPORTANT: This module must NOT have top-level value imports from either\n * server or client modules. Server methods lazy-import request-context;\n * useCookie() lazy-imports use-cookie. This ensures:\n * - Client bundles don't pull in ALS/server code\n * - RSC bundles don't pull in useSyncExternalStore/client code\n * - Tree-shaking is not required for correctness\n *\n * See design/29-cookies.md §\"Typed Cookies with Schema Validation\"\n */\n\nimport type { CookieOptions } from '../server/request-context.js';\nimport type { ClientCookieOptions } from '../client/use-cookie.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nimport type { Codec } from '../codec.js';\n\n/**\n * A codec that converts between string cookie values and typed values.\n * Type alias for the shared Codec<T> protocol.\n */\nexport type CookieCodec<T> = Codec<T>;\n\n/** Options for defineCookie: codec + CookieOptions merged. */\nexport interface DefineCookieOptions<T> extends CookieOptions {\n /** Codec for parsing/serializing the cookie value. */\n codec: CookieCodec<T>;\n}\n\n/** A fully typed cookie definition with server and client methods. */\nexport interface CookieDefinition<T> {\n /** The cookie name. */\n readonly name: string;\n /** The resolved cookie options (without codec). */\n readonly options: CookieOptions;\n /** The codec used for parsing/serializing. */\n readonly codec: CookieCodec<T>;\n\n /** Server: read the typed value from the current request. */\n getCookie(): Promise<T>;\n /** Server: set the typed value on the response. */\n setCookie(value: T): Promise<void>;\n /** Server: delete the cookie. */\n deleteCookie(): Promise<void>;\n\n /** Client: React hook for reading/writing this cookie. Returns [value, setter, deleter]. */\n useCookie(): [T, (value: T) => void, () => void];\n}\n\n// ─── Lazy Module References ───────────────────────────────────────────────\n//\n// These are resolved on first use, not at module load time. This prevents\n// the server module graph from pulling in client code and vice versa.\n// The dynamic import() in server methods is natural (they're async).\n// For useCookie() (sync), we cache the module reference after first load.\n\nlet _useCookieModule: typeof import('../client/use-cookie.js') | undefined;\n\nfunction getUseCookieModule(): typeof import('../client/use-cookie.js') {\n if (!_useCookieModule) {\n // In the client/SSR environment, this module is already in the module\n // graph (imported by the client entry). The throw is a safeguard —\n // if useCookie() is somehow called before the module is available,\n // the developer gets a clear error instead of a silent failure.\n throw new Error(\n '[timber] defineCookie().useCookie() requires @timber-js/app/client to be loaded. ' +\n 'This hook can only be used in client components.'\n );\n }\n return _useCookieModule;\n}\n\n/**\n * Register the client cookie module. Called by the client entry to wire\n * up the lazy reference without a top-level import.\n *\n * @internal — framework use only\n */\nexport function _registerUseCookieModule(mod: typeof import('../client/use-cookie.js')): void {\n _useCookieModule = mod;\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────\n\n/**\n * Define a typed cookie.\n *\n * ```ts\n * import { defineCookie } from '@timber-js/app/cookies';\n * import { fromSchema } from '@timber-js/app/search-params';\n * import { z } from 'zod/v4';\n *\n * export const themeCookie = defineCookie('theme', {\n * codec: fromSchema(z.enum(['light', 'dark', 'system']).default('system')),\n * httpOnly: false,\n * maxAge: 60 * 60 * 24 * 365,\n * });\n *\n * // Server\n * const theme = await themeCookie.getCookie();\n * await themeCookie.setCookie('dark');\n *\n * // Client\n * const [theme, setTheme] = themeCookie.useCookie();\n * ```\n */\nexport function defineCookie<T>(\n name: string,\n options: DefineCookieOptions<T>\n): CookieDefinition<T> {\n const { codec, ...cookieOpts } = options;\n const resolvedOptions: CookieOptions = { ...cookieOpts };\n\n return {\n name,\n options: resolvedOptions,\n codec,\n\n async getCookie(): Promise<T> {\n const { cookies } = await import('../server/request-context.js');\n const jar = cookies();\n const raw = jar.get(name);\n return codec.parse(raw);\n },\n\n async setCookie(value: T): Promise<void> {\n const { cookies } = await import('../server/request-context.js');\n const serialized = codec.serialize(value);\n if (serialized === null) {\n cookies().delete(name, {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n });\n } else {\n cookies().set(name, serialized, resolvedOptions);\n }\n },\n\n async deleteCookie(): Promise<void> {\n const { cookies } = await import('../server/request-context.js');\n cookies().delete(name, {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n });\n },\n\n useCookie(): [T, (value: T) => void, () => void] {\n const { useCookie: useRawCookie } = getUseCookieModule();\n\n // Extract client-safe options (no httpOnly — client cookies can't be httpOnly)\n const clientOpts: ClientCookieOptions = {\n path: resolvedOptions.path,\n domain: resolvedOptions.domain,\n maxAge: resolvedOptions.maxAge,\n expires: resolvedOptions.expires,\n sameSite: resolvedOptions.sameSite,\n secure: resolvedOptions.secure,\n };\n\n const [raw, setRaw, deleteRaw] = useRawCookie(name, clientOpts);\n const parsed = codec.parse(raw);\n\n const setTyped = (value: T): void => {\n const serialized = codec.serialize(value);\n if (serialized === null) {\n deleteRaw();\n } else {\n setRaw(serialized);\n }\n };\n\n return [parsed, setTyped, deleteRaw];\n },\n };\n}\n"],"mappings":";AAqEA,IAAI;AAEJ,SAAS,qBAA+D;AACtE,KAAI,CAAC,iBAKH,OAAM,IAAI,MACR,oIAED;AAEH,QAAO;;;;;;;;AAST,SAAgB,yBAAyB,KAAqD;AAC5F,oBAAmB;;;;;;;;;;;;;;;;;;;;;;;;AA2BrB,SAAgB,aACd,MACA,SACqB;CACrB,MAAM,EAAE,OAAO,GAAG,eAAe;CACjC,MAAM,kBAAiC,EAAE,GAAG,YAAY;AAExD,QAAO;EACL;EACA,SAAS;EACT;EAEA,MAAM,YAAwB;GAC5B,MAAM,EAAE,YAAY,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;GAEjC,MAAM,MADM,SAAS,CACL,IAAI,KAAK;AACzB,UAAO,MAAM,MAAM,IAAI;;EAGzB,MAAM,UAAU,OAAyB;GACvC,MAAM,EAAE,YAAY,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;GACjC,MAAM,aAAa,MAAM,UAAU,MAAM;AACzC,OAAI,eAAe,KACjB,UAAS,CAAC,OAAO,MAAM;IACrB,MAAM,gBAAgB;IACtB,QAAQ,gBAAgB;IACzB,CAAC;OAEF,UAAS,CAAC,IAAI,MAAM,YAAY,gBAAgB;;EAIpD,MAAM,eAA8B;GAClC,MAAM,EAAE,YAAY,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;AACjC,YAAS,CAAC,OAAO,MAAM;IACrB,MAAM,gBAAgB;IACtB,QAAQ,gBAAgB;IACzB,CAAC;;EAGJ,YAAiD;GAC/C,MAAM,EAAE,WAAW,iBAAiB,oBAAoB;GAYxD,MAAM,CAAC,KAAK,QAAQ,aAAa,aAAa,MATN;IACtC,MAAM,gBAAgB;IACtB,QAAQ,gBAAgB;IACxB,QAAQ,gBAAgB;IACxB,SAAS,gBAAgB;IACzB,UAAU,gBAAgB;IAC1B,QAAQ,gBAAgB;IACzB,CAE8D;GAC/D,MAAM,SAAS,MAAM,MAAM,IAAI;GAE/B,MAAM,YAAY,UAAmB;IACnC,MAAM,aAAa,MAAM,UAAU,MAAM;AACzC,QAAI,eAAe,KACjB,YAAW;QAEX,QAAO,WAAW;;AAItB,UAAO;IAAC;IAAQ;IAAU;IAAU;;EAEvC"}
@@ -2,7 +2,7 @@ import { n as __exportAll } from "./chunk-DYhsFzuS.js";
2
2
  import { t as isDebug } from "./debug-ECi_61pb.js";
3
3
  import { r as requestContextAls } from "./als-registry-Ba7URUIn.js";
4
4
  import { t as _setRawSearchParamsFn } from "./define-TK8C1M3x.js";
5
- import { t as _setRawSegmentParamsFn } from "./define-D5STJpIr.js";
5
+ import { t as _setRawSegmentParamsFn } from "./define-CT98cU9c.js";
6
6
  //#region src/server/request-context.ts
7
7
  /**
8
8
  * Request Context — per-request ALS store for headers() and cookies().
@@ -355,4 +355,4 @@ function serializeCookieEntry(entry) {
355
355
  //#endregion
356
356
  export { headers as a, rawSegmentParams as c, setMutableCookieContext as d, setSegmentParams as f, getSetCookieHeaders as i, request_context_exports as l, cookies as n, markResponseFlushed as o, getRequestSearchString as r, rawSearchParams as s, applyRequestHeaderOverlay as t, runWithRequestContext as u };
357
357
 
358
- //# sourceMappingURL=request-context-0wfZsnhh.js.map
358
+ //# sourceMappingURL=request-context-rju2rbga.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"request-context-0wfZsnhh.js","names":[],"sources":["../../src/server/request-context.ts"],"sourcesContent":["/**\n * Request Context — per-request ALS store for headers() and cookies().\n *\n * Follows the same pattern as tracing.ts: a module-level AsyncLocalStorage\n * instance, public accessor functions that throw outside request scope,\n * and a framework-internal `runWithRequestContext()` to establish scope.\n *\n * See design/04-authorization.md §\"AccessContext does not include cookies or headers\"\n * and design/11-platform.md §\"AsyncLocalStorage\".\n * See design/29-cookies.md for cookie mutation semantics.\n */\n\nimport { requestContextAls, type RequestContextStore, type CookieEntry } from './als-registry.js';\nimport { isDebug } from './debug.js';\nimport { _setRawSearchParamsFn } from '../search-params/define.js';\nimport { _setRawSegmentParamsFn } from '../params/define.js';\n\n// Re-export the ALS for framework-internal consumers that need direct access.\nexport { requestContextAls };\n\n// No fallback needed — we use enterWith() instead of run() to ensure\n// the ALS context persists for the entire request lifecycle including\n// async stream consumption by React's renderToReadableStream.\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns a read-only view of the current request's headers.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n */\nexport function headers(): ReadonlyHeaders {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] headers() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n return store.headers;\n}\n\n/**\n * Returns a cookie accessor for the current request.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n *\n * Read methods (.get, .has, .getAll) are always available and reflect\n * read-your-own-writes from .set() calls in the same request.\n *\n * Mutation methods (.set, .delete, .clear) are only available in mutable\n * contexts (middleware.ts, server actions, route.ts handlers). Calling them\n * in read-only contexts (access.ts, server components) throws.\n *\n * See design/29-cookies.md\n */\nexport function cookies(): RequestCookies {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] cookies() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n\n // Parse cookies lazily on first access\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n const map = store.parsedCookies;\n return {\n get(name: string): string | undefined {\n return map.get(name);\n },\n has(name: string): boolean {\n return map.has(name);\n },\n getAll(): Array<{ name: string; value: string }> {\n return Array.from(map.entries()).map(([name, value]) => ({ name, value }));\n },\n get size(): number {\n return map.size;\n },\n\n set(name: string, value: string, options?: CookieOptions): void {\n assertMutable(store, 'set');\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: cookies().set('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be sent. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n const opts = { ...DEFAULT_COOKIE_OPTIONS, ...options };\n store.cookieJar.set(name, { name, value, options: opts });\n // Read-your-own-writes: update the parsed cookies map\n map.set(name, value);\n },\n\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void {\n assertMutable(store, 'delete');\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: cookies().delete('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be deleted. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n const opts: CookieOptions = {\n ...DEFAULT_COOKIE_OPTIONS,\n ...options,\n maxAge: 0,\n expires: new Date(0),\n };\n store.cookieJar.set(name, { name, value: '', options: opts });\n // Remove from read view\n map.delete(name);\n },\n\n clear(): void {\n assertMutable(store, 'clear');\n if (store.flushed) return;\n // Delete every incoming cookie\n for (const name of Array.from(map.keys())) {\n store.cookieJar.set(name, {\n name,\n value: '',\n options: { ...DEFAULT_COOKIE_OPTIONS, maxAge: 0, expires: new Date(0) },\n });\n }\n map.clear();\n },\n\n toString(): string {\n return Array.from(map.entries())\n .map(([name, value]) => `${name}=${value}`)\n .join('; ');\n },\n };\n}\n\n/**\n * Returns a Promise resolving to the current request's raw URLSearchParams.\n *\n * For typed, parsed search params, import the definition from params.ts\n * and call `.load()` or `.parse()`:\n *\n * ```ts\n * import { searchParams } from './params'\n * const parsed = await searchParams.load()\n * ```\n *\n * Or explicitly:\n *\n * ```ts\n * import { rawSearchParams } from '@timber-js/app/server'\n * import { searchParams } from './params'\n * const parsed = searchParams.parse(await rawSearchParams())\n * ```\n *\n * Throws if called outside a request context.\n */\nexport function rawSearchParams(): Promise<URLSearchParams> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] rawSearchParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n return store.searchParamsPromise;\n}\n\n// Eagerly register rawSearchParams with the search-params module so\n// searchParams.load() can call it synchronously without a dynamic import.\n// Dynamic imports lose ALS context in React's RSC Flight renderer,\n// breaking rawSearchParams() in parallel slot pages. See TIM-523.\n_setRawSearchParamsFn(rawSearchParams);\n\n// Eagerly register rawSegmentParams with the params module so\n// segmentParams.load() can call it synchronously without a dynamic import.\n// Same pattern as search params — dynamic imports lose ALS context. See TIM-523.\n_setRawSegmentParamsFn(rawSegmentParams);\n\n/**\n * Returns a Promise resolving to the current request's coerced segment params.\n *\n * Segment params are set by the pipeline after route matching and param\n * coercion (via params.ts codecs). When no params.ts exists, values are\n * raw strings. When codecs are defined, values are already coerced\n * (e.g., `id` is a `number` if `defineSegmentParams({ id: z.coerce.number() })`).\n *\n * This is the primary way page and layout components access route params:\n *\n * ```ts\n * import { rawSegmentParams } from '@timber-js/app/server'\n *\n * export default async function Page() {\n * const { slug } = await rawSegmentParams()\n * // ...\n * }\n * ```\n *\n * Throws if called outside a request context.\n */\nexport function rawSegmentParams(): Promise<Record<string, string | string[]>> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] rawSegmentParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n if (!store.segmentParamsPromise) {\n throw new Error(\n '[timber] rawSegmentParams() called before route matching completed. ' +\n 'Segment params are not available until after the route is matched.'\n );\n }\n return store.segmentParamsPromise;\n}\n\n/**\n * Set the segment params promise on the current request context.\n * Called by the pipeline after route matching and param coercion.\n *\n * @internal — framework use only\n */\nexport function setSegmentParams(params: Record<string, string | string[]>): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] setSegmentParams() called outside of a request context.');\n }\n store.segmentParamsPromise = Promise.resolve(params);\n}\n\n/**\n * Returns the raw search string from the current request URL (e.g. \"?foo=bar\").\n * Synchronous — safe for use in `redirect()` which throws synchronously.\n *\n * Returns empty string if called outside a request context (non-throwing for\n * use in redirect's optional preserveSearchParams path).\n *\n * @internal — used by redirect() for preserveSearchParams support.\n */\nexport function getRequestSearchString(): string {\n const store = requestContextAls.getStore();\n return store?.searchString ?? '';\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Read-only Headers interface. The standard Headers class is mutable;\n * this type narrows it to read-only methods. The underlying object is\n * still a Headers instance, but user code should not mutate it.\n */\nexport type ReadonlyHeaders = Pick<\n Headers,\n 'get' | 'has' | 'entries' | 'keys' | 'values' | 'forEach' | typeof Symbol.iterator\n>;\n\n/** Options for setting a cookie. See design/29-cookies.md. */\nexport interface CookieOptions {\n /** Domain scope. Default: omitted (current domain only). */\n domain?: string;\n /** URL path scope. Default: '/'. */\n path?: string;\n /** Expiration date. Mutually exclusive with maxAge. */\n expires?: Date;\n /** Max age in seconds. Mutually exclusive with expires. */\n maxAge?: number;\n /** Prevent client-side JS access. Default: true. */\n httpOnly?: boolean;\n /** Only send over HTTPS. Default: true. */\n secure?: boolean;\n /** Cross-site request policy. Default: 'lax'. */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Partitioned (CHIPS) — isolate cookie per top-level site. Default: false. */\n partitioned?: boolean;\n}\n\nconst DEFAULT_COOKIE_OPTIONS: CookieOptions = {\n path: '/',\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n};\n\n/**\n * Cookie accessor returned by `cookies()`.\n *\n * Read methods are always available. Mutation methods throw in read-only\n * contexts (access.ts, server components).\n */\nexport interface RequestCookies {\n /** Get a cookie value by name. Returns undefined if not present. */\n get(name: string): string | undefined;\n /** Check if a cookie exists. */\n has(name: string): boolean;\n /** Get all cookies as an array of { name, value } pairs. */\n getAll(): Array<{ name: string; value: string }>;\n /** Number of cookies. */\n readonly size: number;\n /** Set a cookie. Only available in mutable contexts (middleware, actions, route handlers). */\n set(name: string, value: string, options?: CookieOptions): void;\n /** Delete a cookie. Only available in mutable contexts. */\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void;\n /** Delete all cookies. Only available in mutable contexts. */\n clear(): void;\n /** Serialize cookies as a Cookie header string. */\n toString(): string;\n}\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Run a callback within a request context. Used by the pipeline to establish\n * per-request ALS scope so that `headers()` and `cookies()` work.\n *\n * @param req - The incoming Request object.\n * @param fn - The function to run within the request context.\n */\nexport function runWithRequestContext<T>(req: Request, fn: () => T): T {\n const originalCopy = new Headers(req.headers);\n const parsedUrl = new URL(req.url);\n const store: RequestContextStore = {\n headers: freezeHeaders(req.headers),\n originalHeaders: originalCopy,\n cookieHeader: req.headers.get('cookie') ?? '',\n searchParamsPromise: Promise.resolve(parsedUrl.searchParams),\n searchString: parsedUrl.search,\n cookieJar: new Map(),\n flushed: false,\n mutableContext: false,\n };\n return requestContextAls.run(store, fn);\n}\n\n/**\n * Enable cookie mutation for the current context. Called by the framework\n * when entering middleware.ts, server actions, or route.ts handlers.\n *\n * See design/29-cookies.md §\"Context Tracking\"\n */\nexport function setMutableCookieContext(mutable: boolean): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.mutableContext = mutable;\n }\n}\n\n/**\n * Mark the response as flushed (headers committed). After this point,\n * cookie mutations log a warning instead of throwing.\n *\n * See design/29-cookies.md §\"Streaming Constraint: Post-Flush Cookie Warning\"\n */\nexport function markResponseFlushed(): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.flushed = true;\n }\n}\n\n/**\n * Build a Map of cookie name → value reflecting the current request's\n * read-your-own-writes state. Includes incoming cookies plus any\n * mutations from cookies().set() / cookies().delete() in the same request.\n *\n * Used by SSR renderers to populate NavContext.cookies so that\n * useCookie()'s server snapshot matches the actual response state.\n *\n * See design/29-cookies.md §\"Read-Your-Own-Writes\"\n * See design/triage/TIM-441-cookie-api-triage.md §4\n */\nexport function getCookiesForSsr(): Map<string, string> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] getCookiesForSsr() called outside of a request context.');\n }\n\n // Trigger lazy parsing if not yet done\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n // The parsedCookies map already reflects read-your-own-writes:\n // - cookies().set() updates the map via map.set(name, value)\n // - cookies().delete() removes from the map via map.delete(name)\n // Return a copy so callers can't mutate the internal map.\n return new Map(store.parsedCookies);\n}\n\n/**\n * Collect all Set-Cookie headers from the cookie jar.\n * Called by the framework at flush time to apply cookies to the response.\n *\n * Returns an array of serialized Set-Cookie header values.\n */\nexport function getSetCookieHeaders(): string[] {\n const store = requestContextAls.getStore();\n if (!store) return [];\n return Array.from(store.cookieJar.values()).map(serializeCookieEntry);\n}\n\n/**\n * Apply middleware-injected request headers to the current request context.\n *\n * Called by the pipeline after middleware.ts runs. Merges overlay headers\n * on top of the original request headers so downstream code (access.ts,\n * server components, server actions) sees them via `headers()`.\n *\n * The original request headers are never mutated — a new frozen Headers\n * object is created with the overlay applied on top.\n *\n * See design/07-routing.md §\"Request Header Injection\"\n */\nexport function applyRequestHeaderOverlay(overlay: Headers): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] applyRequestHeaderOverlay() called outside of a request context.');\n }\n\n // Check if the overlay has any headers — skip if empty\n let hasOverlay = false;\n overlay.forEach(() => {\n hasOverlay = true;\n });\n if (!hasOverlay) return;\n\n // Merge: start with original headers, overlay on top\n const merged = new Headers(store.originalHeaders);\n overlay.forEach((value, key) => {\n merged.set(key, value);\n });\n store.headers = freezeHeaders(merged);\n}\n\n// ─── Read-Only Headers ────────────────────────────────────────────────────\n\nconst MUTATING_METHODS = new Set(['set', 'append', 'delete']);\n\n/**\n * Wrap a Headers object in a Proxy that throws on mutating methods.\n * Object.freeze doesn't work on Headers (native internal slots), so we\n * intercept property access and reject set/append/delete at runtime.\n *\n * Read methods (get, has, entries, etc.) must be bound to the underlying\n * Headers instance because they access private #headersList slots.\n */\nfunction freezeHeaders(source: Headers): Headers {\n const copy = new Headers(source);\n return new Proxy(copy, {\n get(target, prop) {\n if (typeof prop === 'string' && MUTATING_METHODS.has(prop)) {\n return () => {\n throw new Error(\n `[timber] headers() returns a read-only Headers object. ` +\n `Calling .${prop}() is not allowed. ` +\n `Use ctx.requestHeaders in middleware to inject headers for downstream components.`\n );\n };\n }\n const value = Reflect.get(target, prop);\n // Bind methods to the real Headers instance so private slot access works\n if (typeof value === 'function') {\n return value.bind(target);\n }\n return value;\n },\n });\n}\n\n// ─── Cookie Helpers ───────────────────────────────────────────────────────\n\n/** Throw if cookie mutation is attempted in a read-only context. */\nfunction assertMutable(store: RequestContextStore, method: string): void {\n if (!store.mutableContext) {\n throw new Error(\n `[timber] cookies().${method}() cannot be called in this context.\\n` +\n ` Set cookies in middleware.ts, server actions, or route.ts handlers.`\n );\n }\n}\n\n/**\n * Parse a Cookie header string into a Map of name → value pairs.\n * Follows RFC 6265 §4.2.1: cookies are semicolon-separated key=value pairs.\n */\nfunction parseCookieHeader(header: string): Map<string, string> {\n const map = new Map<string, string>();\n if (!header) return map;\n\n for (const pair of header.split(';')) {\n const eqIndex = pair.indexOf('=');\n if (eqIndex === -1) continue;\n const name = pair.slice(0, eqIndex).trim();\n const value = pair.slice(eqIndex + 1).trim();\n if (name) {\n map.set(name, value);\n }\n }\n\n return map;\n}\n\n/** Serialize a CookieEntry into a Set-Cookie header value. */\nfunction serializeCookieEntry(entry: CookieEntry): string {\n const parts = [`${entry.name}=${entry.value}`];\n const opts = entry.options;\n\n if (opts.domain) parts.push(`Domain=${opts.domain}`);\n if (opts.path) parts.push(`Path=${opts.path}`);\n if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);\n if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`);\n if (opts.httpOnly) parts.push('HttpOnly');\n if (opts.secure) parts.push('Secure');\n if (opts.sameSite) {\n parts.push(`SameSite=${opts.sameSite.charAt(0).toUpperCase()}${opts.sameSite.slice(1)}`);\n }\n if (opts.partitioned) parts.push('Partitioned');\n\n return parts.join('; ');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"request-context-rju2rbga.js","names":[],"sources":["../../src/server/request-context.ts"],"sourcesContent":["/**\n * Request Context — per-request ALS store for headers() and cookies().\n *\n * Follows the same pattern as tracing.ts: a module-level AsyncLocalStorage\n * instance, public accessor functions that throw outside request scope,\n * and a framework-internal `runWithRequestContext()` to establish scope.\n *\n * See design/04-authorization.md §\"AccessContext does not include cookies or headers\"\n * and design/11-platform.md §\"AsyncLocalStorage\".\n * See design/29-cookies.md for cookie mutation semantics.\n */\n\nimport { requestContextAls, type RequestContextStore, type CookieEntry } from './als-registry.js';\nimport { isDebug } from './debug.js';\nimport { _setRawSearchParamsFn } from '../search-params/define.js';\nimport { _setRawSegmentParamsFn } from '../params/define.js';\n\n// Re-export the ALS for framework-internal consumers that need direct access.\nexport { requestContextAls };\n\n// No fallback needed — we use enterWith() instead of run() to ensure\n// the ALS context persists for the entire request lifecycle including\n// async stream consumption by React's renderToReadableStream.\n\n// ─── Public API ───────────────────────────────────────────────────────────\n\n/**\n * Returns a read-only view of the current request's headers.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n */\nexport function headers(): ReadonlyHeaders {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] headers() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n return store.headers;\n}\n\n/**\n * Returns a cookie accessor for the current request.\n *\n * Available in middleware, access checks, server components, and server actions.\n * Throws if called outside a request context (security principle #2: no global fallback).\n *\n * Read methods (.get, .has, .getAll) are always available and reflect\n * read-your-own-writes from .set() calls in the same request.\n *\n * Mutation methods (.set, .delete, .clear) are only available in mutable\n * contexts (middleware.ts, server actions, route.ts handlers). Calling them\n * in read-only contexts (access.ts, server components) throws.\n *\n * See design/29-cookies.md\n */\nexport function cookies(): RequestCookies {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] cookies() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n\n // Parse cookies lazily on first access\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n const map = store.parsedCookies;\n return {\n get(name: string): string | undefined {\n return map.get(name);\n },\n has(name: string): boolean {\n return map.has(name);\n },\n getAll(): Array<{ name: string; value: string }> {\n return Array.from(map.entries()).map(([name, value]) => ({ name, value }));\n },\n get size(): number {\n return map.size;\n },\n\n set(name: string, value: string, options?: CookieOptions): void {\n assertMutable(store, 'set');\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: cookies().set('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be sent. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n const opts = { ...DEFAULT_COOKIE_OPTIONS, ...options };\n store.cookieJar.set(name, { name, value, options: opts });\n // Read-your-own-writes: update the parsed cookies map\n map.set(name, value);\n },\n\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void {\n assertMutable(store, 'delete');\n if (store.flushed) {\n if (isDebug()) {\n console.warn(\n `[timber] warn: cookies().delete('${name}') called after response headers were committed.\\n` +\n ` The cookie will NOT be deleted. Move cookie mutations to middleware.ts, a server action,\\n` +\n ` or a route.ts handler.`\n );\n }\n return;\n }\n const opts: CookieOptions = {\n ...DEFAULT_COOKIE_OPTIONS,\n ...options,\n maxAge: 0,\n expires: new Date(0),\n };\n store.cookieJar.set(name, { name, value: '', options: opts });\n // Remove from read view\n map.delete(name);\n },\n\n clear(): void {\n assertMutable(store, 'clear');\n if (store.flushed) return;\n // Delete every incoming cookie\n for (const name of Array.from(map.keys())) {\n store.cookieJar.set(name, {\n name,\n value: '',\n options: { ...DEFAULT_COOKIE_OPTIONS, maxAge: 0, expires: new Date(0) },\n });\n }\n map.clear();\n },\n\n toString(): string {\n return Array.from(map.entries())\n .map(([name, value]) => `${name}=${value}`)\n .join('; ');\n },\n };\n}\n\n/**\n * Returns a Promise resolving to the current request's raw URLSearchParams.\n *\n * For typed, parsed search params, import the definition from params.ts\n * and call `.load()` or `.parse()`:\n *\n * ```ts\n * import { searchParams } from './params'\n * const parsed = await searchParams.load()\n * ```\n *\n * Or explicitly:\n *\n * ```ts\n * import { rawSearchParams } from '@timber-js/app/server'\n * import { searchParams } from './params'\n * const parsed = searchParams.parse(await rawSearchParams())\n * ```\n *\n * Throws if called outside a request context.\n */\nexport function rawSearchParams(): Promise<URLSearchParams> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] rawSearchParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n return store.searchParamsPromise;\n}\n\n// Eagerly register rawSearchParams with the search-params module so\n// searchParams.load() can call it synchronously without a dynamic import.\n// Dynamic imports lose ALS context in React's RSC Flight renderer,\n// breaking rawSearchParams() in parallel slot pages. See TIM-523.\n_setRawSearchParamsFn(rawSearchParams);\n\n// Eagerly register rawSegmentParams with the params module so\n// segmentParams.load() can call it synchronously without a dynamic import.\n// Same pattern as search params — dynamic imports lose ALS context. See TIM-523.\n_setRawSegmentParamsFn(rawSegmentParams);\n\n/**\n * Returns a Promise resolving to the current request's coerced segment params.\n *\n * Segment params are set by the pipeline after route matching and param\n * coercion (via params.ts codecs). When no params.ts exists, values are\n * raw strings. When codecs are defined, values are already coerced\n * (e.g., `id` is a `number` if `defineSegmentParams({ id: z.coerce.number() })`).\n *\n * This is the primary way page and layout components access route params:\n *\n * ```ts\n * import { rawSegmentParams } from '@timber-js/app/server'\n *\n * export default async function Page() {\n * const { slug } = await rawSegmentParams()\n * // ...\n * }\n * ```\n *\n * Throws if called outside a request context.\n */\nexport function rawSegmentParams(): Promise<Record<string, string | string[]>> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error(\n '[timber] rawSegmentParams() called outside of a request context. ' +\n 'It can only be used in middleware, access checks, server components, and server actions.'\n );\n }\n if (!store.segmentParamsPromise) {\n throw new Error(\n '[timber] rawSegmentParams() called before route matching completed. ' +\n 'Segment params are not available until after the route is matched.'\n );\n }\n return store.segmentParamsPromise;\n}\n\n/**\n * Set the segment params promise on the current request context.\n * Called by the pipeline after route matching and param coercion.\n *\n * @internal — framework use only\n */\nexport function setSegmentParams(params: Record<string, string | string[]>): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] setSegmentParams() called outside of a request context.');\n }\n store.segmentParamsPromise = Promise.resolve(params);\n}\n\n/**\n * Returns the raw search string from the current request URL (e.g. \"?foo=bar\").\n * Synchronous — safe for use in `redirect()` which throws synchronously.\n *\n * Returns empty string if called outside a request context (non-throwing for\n * use in redirect's optional preserveSearchParams path).\n *\n * @internal — used by redirect() for preserveSearchParams support.\n */\nexport function getRequestSearchString(): string {\n const store = requestContextAls.getStore();\n return store?.searchString ?? '';\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\n/**\n * Read-only Headers interface. The standard Headers class is mutable;\n * this type narrows it to read-only methods. The underlying object is\n * still a Headers instance, but user code should not mutate it.\n */\nexport type ReadonlyHeaders = Pick<\n Headers,\n 'get' | 'has' | 'entries' | 'keys' | 'values' | 'forEach' | typeof Symbol.iterator\n>;\n\n/** Options for setting a cookie. See design/29-cookies.md. */\nexport interface CookieOptions {\n /** Domain scope. Default: omitted (current domain only). */\n domain?: string;\n /** URL path scope. Default: '/'. */\n path?: string;\n /** Expiration date. Mutually exclusive with maxAge. */\n expires?: Date;\n /** Max age in seconds. Mutually exclusive with expires. */\n maxAge?: number;\n /** Prevent client-side JS access. Default: true. */\n httpOnly?: boolean;\n /** Only send over HTTPS. Default: true. */\n secure?: boolean;\n /** Cross-site request policy. Default: 'lax'. */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Partitioned (CHIPS) — isolate cookie per top-level site. Default: false. */\n partitioned?: boolean;\n}\n\nconst DEFAULT_COOKIE_OPTIONS: CookieOptions = {\n path: '/',\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n};\n\n/**\n * Cookie accessor returned by `cookies()`.\n *\n * Read methods are always available. Mutation methods throw in read-only\n * contexts (access.ts, server components).\n */\nexport interface RequestCookies {\n /** Get a cookie value by name. Returns undefined if not present. */\n get(name: string): string | undefined;\n /** Check if a cookie exists. */\n has(name: string): boolean;\n /** Get all cookies as an array of { name, value } pairs. */\n getAll(): Array<{ name: string; value: string }>;\n /** Number of cookies. */\n readonly size: number;\n /** Set a cookie. Only available in mutable contexts (middleware, actions, route handlers). */\n set(name: string, value: string, options?: CookieOptions): void;\n /** Delete a cookie. Only available in mutable contexts. */\n delete(name: string, options?: Pick<CookieOptions, 'path' | 'domain'>): void;\n /** Delete all cookies. Only available in mutable contexts. */\n clear(): void;\n /** Serialize cookies as a Cookie header string. */\n toString(): string;\n}\n\n// ─── Framework-Internal Helpers ───────────────────────────────────────────\n\n/**\n * Run a callback within a request context. Used by the pipeline to establish\n * per-request ALS scope so that `headers()` and `cookies()` work.\n *\n * @param req - The incoming Request object.\n * @param fn - The function to run within the request context.\n */\nexport function runWithRequestContext<T>(req: Request, fn: () => T): T {\n const originalCopy = new Headers(req.headers);\n const parsedUrl = new URL(req.url);\n const store: RequestContextStore = {\n headers: freezeHeaders(req.headers),\n originalHeaders: originalCopy,\n cookieHeader: req.headers.get('cookie') ?? '',\n searchParamsPromise: Promise.resolve(parsedUrl.searchParams),\n searchString: parsedUrl.search,\n cookieJar: new Map(),\n flushed: false,\n mutableContext: false,\n };\n return requestContextAls.run(store, fn);\n}\n\n/**\n * Enable cookie mutation for the current context. Called by the framework\n * when entering middleware.ts, server actions, or route.ts handlers.\n *\n * See design/29-cookies.md §\"Context Tracking\"\n */\nexport function setMutableCookieContext(mutable: boolean): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.mutableContext = mutable;\n }\n}\n\n/**\n * Mark the response as flushed (headers committed). After this point,\n * cookie mutations log a warning instead of throwing.\n *\n * See design/29-cookies.md §\"Streaming Constraint: Post-Flush Cookie Warning\"\n */\nexport function markResponseFlushed(): void {\n const store = requestContextAls.getStore();\n if (store) {\n store.flushed = true;\n }\n}\n\n/**\n * Build a Map of cookie name → value reflecting the current request's\n * read-your-own-writes state. Includes incoming cookies plus any\n * mutations from cookies().set() / cookies().delete() in the same request.\n *\n * Used by SSR renderers to populate NavContext.cookies so that\n * useCookie()'s server snapshot matches the actual response state.\n *\n * See design/29-cookies.md §\"Read-Your-Own-Writes\"\n * See design/triage/TIM-441-cookie-api-triage.md §4\n */\nexport function getCookiesForSsr(): Map<string, string> {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] getCookiesForSsr() called outside of a request context.');\n }\n\n // Trigger lazy parsing if not yet done\n if (!store.parsedCookies) {\n store.parsedCookies = parseCookieHeader(store.cookieHeader);\n }\n\n // The parsedCookies map already reflects read-your-own-writes:\n // - cookies().set() updates the map via map.set(name, value)\n // - cookies().delete() removes from the map via map.delete(name)\n // Return a copy so callers can't mutate the internal map.\n return new Map(store.parsedCookies);\n}\n\n/**\n * Collect all Set-Cookie headers from the cookie jar.\n * Called by the framework at flush time to apply cookies to the response.\n *\n * Returns an array of serialized Set-Cookie header values.\n */\nexport function getSetCookieHeaders(): string[] {\n const store = requestContextAls.getStore();\n if (!store) return [];\n return Array.from(store.cookieJar.values()).map(serializeCookieEntry);\n}\n\n/**\n * Apply middleware-injected request headers to the current request context.\n *\n * Called by the pipeline after middleware.ts runs. Merges overlay headers\n * on top of the original request headers so downstream code (access.ts,\n * server components, server actions) sees them via `headers()`.\n *\n * The original request headers are never mutated — a new frozen Headers\n * object is created with the overlay applied on top.\n *\n * See design/07-routing.md §\"Request Header Injection\"\n */\nexport function applyRequestHeaderOverlay(overlay: Headers): void {\n const store = requestContextAls.getStore();\n if (!store) {\n throw new Error('[timber] applyRequestHeaderOverlay() called outside of a request context.');\n }\n\n // Check if the overlay has any headers — skip if empty\n let hasOverlay = false;\n overlay.forEach(() => {\n hasOverlay = true;\n });\n if (!hasOverlay) return;\n\n // Merge: start with original headers, overlay on top\n const merged = new Headers(store.originalHeaders);\n overlay.forEach((value, key) => {\n merged.set(key, value);\n });\n store.headers = freezeHeaders(merged);\n}\n\n// ─── Read-Only Headers ────────────────────────────────────────────────────\n\nconst MUTATING_METHODS = new Set(['set', 'append', 'delete']);\n\n/**\n * Wrap a Headers object in a Proxy that throws on mutating methods.\n * Object.freeze doesn't work on Headers (native internal slots), so we\n * intercept property access and reject set/append/delete at runtime.\n *\n * Read methods (get, has, entries, etc.) must be bound to the underlying\n * Headers instance because they access private #headersList slots.\n */\nfunction freezeHeaders(source: Headers): Headers {\n const copy = new Headers(source);\n return new Proxy(copy, {\n get(target, prop) {\n if (typeof prop === 'string' && MUTATING_METHODS.has(prop)) {\n return () => {\n throw new Error(\n `[timber] headers() returns a read-only Headers object. ` +\n `Calling .${prop}() is not allowed. ` +\n `Use ctx.requestHeaders in middleware to inject headers for downstream components.`\n );\n };\n }\n const value = Reflect.get(target, prop);\n // Bind methods to the real Headers instance so private slot access works\n if (typeof value === 'function') {\n return value.bind(target);\n }\n return value;\n },\n });\n}\n\n// ─── Cookie Helpers ───────────────────────────────────────────────────────\n\n/** Throw if cookie mutation is attempted in a read-only context. */\nfunction assertMutable(store: RequestContextStore, method: string): void {\n if (!store.mutableContext) {\n throw new Error(\n `[timber] cookies().${method}() cannot be called in this context.\\n` +\n ` Set cookies in middleware.ts, server actions, or route.ts handlers.`\n );\n }\n}\n\n/**\n * Parse a Cookie header string into a Map of name → value pairs.\n * Follows RFC 6265 §4.2.1: cookies are semicolon-separated key=value pairs.\n */\nfunction parseCookieHeader(header: string): Map<string, string> {\n const map = new Map<string, string>();\n if (!header) return map;\n\n for (const pair of header.split(';')) {\n const eqIndex = pair.indexOf('=');\n if (eqIndex === -1) continue;\n const name = pair.slice(0, eqIndex).trim();\n const value = pair.slice(eqIndex + 1).trim();\n if (name) {\n map.set(name, value);\n }\n }\n\n return map;\n}\n\n/** Serialize a CookieEntry into a Set-Cookie header value. */\nfunction serializeCookieEntry(entry: CookieEntry): string {\n const parts = [`${entry.name}=${entry.value}`];\n const opts = entry.options;\n\n if (opts.domain) parts.push(`Domain=${opts.domain}`);\n if (opts.path) parts.push(`Path=${opts.path}`);\n if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);\n if (opts.maxAge !== undefined) parts.push(`Max-Age=${opts.maxAge}`);\n if (opts.httpOnly) parts.push('HttpOnly');\n if (opts.secure) parts.push('Secure');\n if (opts.sameSite) {\n parts.push(`SameSite=${opts.sameSite.charAt(0).toUpperCase()}${opts.sameSite.slice(1)}`);\n }\n if (opts.partitioned) parts.push('Partitioned');\n\n return parts.join('; ');\n}\n"],"mappings":"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"}
@@ -45,11 +45,14 @@ function triggerStaleReload() {
45
45
  window.location.reload();
46
46
  return true;
47
47
  } catch {
48
- if (memoryReloadCount > 0) {
48
+ if (document.cookie.includes(RELOAD_FLAG_KEY + "=1") || memoryReloadCount > 0) {
49
49
  console.warn("[timber] Stale client reference detected again after reload. Not reloading to prevent infinite loop. This may indicate a deployment issue — try a hard refresh.");
50
50
  return false;
51
51
  }
52
52
  memoryReloadCount++;
53
+ try {
54
+ document.cookie = `${RELOAD_FLAG_KEY}=1; max-age=60; path=/; SameSite=Lax`;
55
+ } catch {}
53
56
  console.warn("[timber] Stale client reference detected — the server has been redeployed with new bundles. Reloading to pick up the new version.");
54
57
  window.location.reload();
55
58
  return true;
@@ -58,4 +61,4 @@ function triggerStaleReload() {
58
61
  //#endregion
59
62
  export { triggerStaleReload };
60
63
 
61
- //# sourceMappingURL=stale-reload-DKN3aXxR.js.map
64
+ //# sourceMappingURL=stale-reload-BSSym1MJ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stale-reload-DKN3aXxR.js","names":[],"sources":["../../src/client/stale-reload.ts"],"sourcesContent":["/**\n * Stale Client Reference Reload\n *\n * When a new deployment ships updated bundles, clients running stale\n * JavaScript may encounter \"Could not find the module\" errors during\n * RSC Flight stream decoding. This happens because the RSC payload\n * references module IDs from the new bundle that don't exist in the\n * old client bundle.\n *\n * This module detects these specific errors and triggers a full page\n * reload so the browser fetches the new bundle. A sessionStorage flag\n * guards against infinite reload loops.\n *\n * See: LOCAL-332\n */\n\nconst RELOAD_FLAG_KEY = '__timber_stale_reload';\n\n/**\n * In-memory fallback counter for environments where sessionStorage is\n * unavailable (private browsing, storage full, extension interference).\n * Incremented each time triggerStaleReload() falls into the catch path.\n * If the counter exceeds 0 on a subsequent call, the reload is suppressed\n * to prevent an infinite loop. Resets naturally on page load (module\n * re-evaluates) and can be manually reset via clearStaleReloadFlag().\n */\nlet memoryReloadCount = 0;\n\n/**\n * Check if an error is a stale client reference error from React's\n * Flight client. These errors have the message pattern:\n * \"Could not find the module \\\"<id>\\\"\"\n *\n * This is thrown by react-server-dom-webpack's client when the RSC\n * payload references a module ID that doesn't exist in the client's\n * module map — typically because the server has been redeployed with\n * new bundle hashes while the client is still running old JavaScript.\n */\nexport function isStaleClientReference(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const msg = error.message;\n return msg.includes('Could not find the module') || msg.includes('client reference not found');\n}\n\n/**\n * Check if an error is a chunk load failure from a dynamic import.\n *\n * After a deployment, old chunk filenames no longer exist. When the client\n * tries to dynamically import a chunk that's been replaced, the browser\n * throws one of these errors:\n *\n * - Chromium: \"Failed to fetch dynamically imported module: <url>\"\n * - Firefox: \"error loading dynamically imported module: <url>\"\n * - Safari: \"Importing a module script failed.\"\n * - Vite/Rollup: \"Unable to preload CSS for <url>\"\n *\n * See TIM-446\n */\nexport function isChunkLoadError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const msg = error.message.toLowerCase();\n return (\n msg.includes('failed to fetch dynamically imported module') ||\n msg.includes('error loading dynamically imported module') ||\n msg.includes('importing a module script failed') ||\n msg.includes('unable to preload css') ||\n // Webpack-style chunk load errors (unlikely in Vite but defensive)\n msg.includes('loading chunk') ||\n msg.includes('loading css chunk')\n );\n}\n\n/**\n * Trigger a full page reload to pick up new bundles.\n *\n * Sets a sessionStorage flag before reloading. If the flag is already\n * set (meaning we already reloaded once for this reason), we don't\n * reload again — this prevents infinite reload loops if the error\n * persists after reload (e.g., a genuine bug rather than a stale bundle).\n *\n * @returns true if a reload was triggered, false if suppressed (loop guard)\n */\nexport function triggerStaleReload(): boolean {\n try {\n // Check if we already reloaded — prevent infinite loop\n if (sessionStorage.getItem(RELOAD_FLAG_KEY)) {\n console.warn(\n '[timber] Stale client reference detected again after reload. ' +\n 'Not reloading to prevent infinite loop. ' +\n 'This may indicate a deployment issue — try a hard refresh.'\n );\n return false;\n }\n\n // Set the flag before reloading\n sessionStorage.setItem(RELOAD_FLAG_KEY, '1');\n\n console.warn(\n '[timber] Stale client reference detected — the server has been ' +\n 'redeployed with new bundles. Reloading to pick up the new version.'\n );\n\n window.location.reload();\n return true;\n } catch {\n // sessionStorage unavailable (private browsing, storage full, etc.)\n // Use in-memory counter as fallback loop guard\n if (memoryReloadCount > 0) {\n console.warn(\n '[timber] Stale client reference detected again after reload. ' +\n 'Not reloading to prevent infinite loop. ' +\n 'This may indicate a deployment issue — try a hard refresh.'\n );\n return false;\n }\n\n memoryReloadCount++;\n console.warn(\n '[timber] Stale client reference detected — the server has been ' +\n 'redeployed with new bundles. Reloading to pick up the new version.'\n );\n window.location.reload();\n return true;\n }\n}\n\n/**\n * Clear the stale reload flag. Called on successful bootstrap to reset\n * the loop guard — if the page loaded successfully, the next stale\n * reference error should trigger a fresh reload attempt.\n */\nexport function clearStaleReloadFlag(): void {\n memoryReloadCount = 0;\n try {\n sessionStorage.removeItem(RELOAD_FLAG_KEY);\n } catch {\n // sessionStorage unavailable — nothing to clear\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAM,kBAAkB;;;;;;;;;AAUxB,IAAI,oBAAoB;;;;;;;;;;;AAwDxB,SAAgB,qBAA8B;AAC5C,KAAI;AAEF,MAAI,eAAe,QAAQ,gBAAgB,EAAE;AAC3C,WAAQ,KACN,kKAGD;AACD,UAAO;;AAIT,iBAAe,QAAQ,iBAAiB,IAAI;AAE5C,UAAQ,KACN,oIAED;AAED,SAAO,SAAS,QAAQ;AACxB,SAAO;SACD;AAGN,MAAI,oBAAoB,GAAG;AACzB,WAAQ,KACN,kKAGD;AACD,UAAO;;AAGT;AACA,UAAQ,KACN,oIAED;AACD,SAAO,SAAS,QAAQ;AACxB,SAAO"}
1
+ {"version":3,"file":"stale-reload-BSSym1MJ.js","names":[],"sources":["../../src/client/stale-reload.ts"],"sourcesContent":["/**\n * Stale Client Reference Reload\n *\n * When a new deployment ships updated bundles, clients running stale\n * JavaScript may encounter \"Could not find the module\" errors during\n * RSC Flight stream decoding. This happens because the RSC payload\n * references module IDs from the new bundle that don't exist in the\n * old client bundle.\n *\n * This module detects these specific errors and triggers a full page\n * reload so the browser fetches the new bundle. A sessionStorage flag\n * guards against infinite reload loops.\n *\n * See: LOCAL-332\n */\n\nconst RELOAD_FLAG_KEY = '__timber_stale_reload';\n\n/**\n * In-memory fallback counter for environments where sessionStorage is\n * unavailable (private browsing, storage full, extension interference).\n * Incremented each time triggerStaleReload() falls into the catch path.\n * If the counter exceeds 0 on a subsequent call, the reload is suppressed\n * to prevent an infinite loop. Resets naturally on page load (module\n * re-evaluates) and can be manually reset via clearStaleReloadFlag().\n */\nlet memoryReloadCount = 0;\n\n/**\n * Check if an error is a stale client reference error from React's\n * Flight client. These errors have the message pattern:\n * \"Could not find the module \\\"<id>\\\"\"\n *\n * This is thrown by react-server-dom-webpack's client when the RSC\n * payload references a module ID that doesn't exist in the client's\n * module map — typically because the server has been redeployed with\n * new bundle hashes while the client is still running old JavaScript.\n */\nexport function isStaleClientReference(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const msg = error.message;\n return msg.includes('Could not find the module') || msg.includes('client reference not found');\n}\n\n/**\n * Check if an error is a chunk load failure from a dynamic import.\n *\n * After a deployment, old chunk filenames no longer exist. When the client\n * tries to dynamically import a chunk that's been replaced, the browser\n * throws one of these errors:\n *\n * - Chromium: \"Failed to fetch dynamically imported module: <url>\"\n * - Firefox: \"error loading dynamically imported module: <url>\"\n * - Safari: \"Importing a module script failed.\"\n * - Vite/Rollup: \"Unable to preload CSS for <url>\"\n *\n * See TIM-446\n */\nexport function isChunkLoadError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const msg = error.message.toLowerCase();\n return (\n msg.includes('failed to fetch dynamically imported module') ||\n msg.includes('error loading dynamically imported module') ||\n msg.includes('importing a module script failed') ||\n msg.includes('unable to preload css') ||\n // Webpack-style chunk load errors (unlikely in Vite but defensive)\n msg.includes('loading chunk') ||\n msg.includes('loading css chunk')\n );\n}\n\n/**\n * Trigger a full page reload to pick up new bundles.\n *\n * Sets a sessionStorage flag before reloading. If the flag is already\n * set (meaning we already reloaded once for this reason), we don't\n * reload again — this prevents infinite reload loops if the error\n * persists after reload (e.g., a genuine bug rather than a stale bundle).\n *\n * @returns true if a reload was triggered, false if suppressed (loop guard)\n */\nexport function triggerStaleReload(): boolean {\n try {\n // Check if we already reloaded — prevent infinite loop\n if (sessionStorage.getItem(RELOAD_FLAG_KEY)) {\n console.warn(\n '[timber] Stale client reference detected again after reload. ' +\n 'Not reloading to prevent infinite loop. ' +\n 'This may indicate a deployment issue — try a hard refresh.'\n );\n return false;\n }\n\n // Set the flag before reloading\n sessionStorage.setItem(RELOAD_FLAG_KEY, '1');\n\n console.warn(\n '[timber] Stale client reference detected — the server has been ' +\n 'redeployed with new bundles. Reloading to pick up the new version.'\n );\n\n window.location.reload();\n return true;\n } catch {\n // sessionStorage unavailable (private browsing, storage full, etc.)\n // Use document.cookie as a reload-persistent fallback loop guard.\n // Module-level memoryReloadCount resets on every reload, so it can't\n // detect cross-reload loops. Cookies persist across reloads and are\n // available even when sessionStorage is blocked (TIM-576).\n const cookieFlag = document.cookie.includes(RELOAD_FLAG_KEY + '=1');\n if (cookieFlag || memoryReloadCount > 0) {\n console.warn(\n '[timber] Stale client reference detected again after reload. ' +\n 'Not reloading to prevent infinite loop. ' +\n 'This may indicate a deployment issue — try a hard refresh.'\n );\n return false;\n }\n\n memoryReloadCount++;\n // Set a short-lived cookie (60s) as the persistent loop guard.\n // It auto-expires so it won't block future legitimate reloads.\n try {\n document.cookie = `${RELOAD_FLAG_KEY}=1; max-age=60; path=/; SameSite=Lax`;\n } catch {\n // Cookie API unavailable — proceed anyway, memoryReloadCount guards same-page loops\n }\n console.warn(\n '[timber] Stale client reference detected — the server has been ' +\n 'redeployed with new bundles. Reloading to pick up the new version.'\n );\n window.location.reload();\n return true;\n }\n}\n\n/**\n * Clear the stale reload flag. Called on successful bootstrap to reset\n * the loop guard — if the page loaded successfully, the next stale\n * reference error should trigger a fresh reload attempt.\n */\nexport function clearStaleReloadFlag(): void {\n memoryReloadCount = 0;\n try {\n sessionStorage.removeItem(RELOAD_FLAG_KEY);\n } catch {\n // sessionStorage unavailable — nothing to clear\n }\n // Also clear the cookie fallback\n try {\n document.cookie = `${RELOAD_FLAG_KEY}=; max-age=0; path=/; SameSite=Lax`;\n } catch {\n // Cookie API unavailable\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAM,kBAAkB;;;;;;;;;AAUxB,IAAI,oBAAoB;;;;;;;;;;;AAwDxB,SAAgB,qBAA8B;AAC5C,KAAI;AAEF,MAAI,eAAe,QAAQ,gBAAgB,EAAE;AAC3C,WAAQ,KACN,kKAGD;AACD,UAAO;;AAIT,iBAAe,QAAQ,iBAAiB,IAAI;AAE5C,UAAQ,KACN,oIAED;AAED,SAAO,SAAS,QAAQ;AACxB,SAAO;SACD;AAON,MADmB,SAAS,OAAO,SAAS,kBAAkB,KAAK,IACjD,oBAAoB,GAAG;AACvC,WAAQ,KACN,kKAGD;AACD,UAAO;;AAGT;AAGA,MAAI;AACF,YAAS,SAAS,GAAG,gBAAgB;UAC/B;AAGR,UAAQ,KACN,oIAED;AACD,SAAO,SAAS,QAAQ;AACxB,SAAO"}
@@ -3,7 +3,7 @@ import { n as __exportAll } from "../_chunks/chunk-DYhsFzuS.js";
3
3
  import { n as useQueryStates, t as bindUseQueryStates } from "../_chunks/use-query-states-wEXY2JQB.js";
4
4
  import { n as useSegmentContext, r as mergePreservedSearchParams, t as SegmentProvider } from "../_chunks/segment-context-CyaM1mrD.js";
5
5
  import { a as _setCachedSearch, c as cachedSearch, d as globalRouter, i as setSsrData, l as cachedSearchParams, n as clearSsrData, o as _setCurrentParams, r as getSsrData, s as _setGlobalRouter, t as TimberErrorBoundary, u as currentParams } from "../_chunks/error-boundary-DpZJBCqh.js";
6
- import { t as _registerUseCookieModule } from "../_chunks/define-cookie-DtAavax4.js";
6
+ import { t as _registerUseCookieModule } from "../_chunks/define-cookie-BWr_52kY.js";
7
7
  import React, { cloneElement, createContext, createElement, isValidElement, useActionState as useActionState$1, useContext, useEffect, useRef, useState, useSyncExternalStore, useTransition } from "react";
8
8
  import { jsx } from "react/jsx-runtime";
9
9
  //#region src/client/use-link-status.ts
@@ -965,7 +965,7 @@ async function fetchRscPayload(url, deps, stateTree, currentUrl) {
965
965
  if (checkReloadSignal(response)) throw new VersionSkewError();
966
966
  const redirectLocation = response.headers.get("X-Timber-Redirect") || (response.status >= 300 && response.status < 400 ? response.headers.get("Location") : null);
967
967
  if (redirectLocation) throw new RedirectError(redirectLocation);
968
- if (response.headers.get("X-Timber-Error") === "1") throw new ServerErrorResponse(response.status, url);
968
+ if (response.headers.get("X-Timber-Error") === "1" && response.status >= 500) throw new ServerErrorResponse(response.status, url);
969
969
  headElements = extractHeadElements(response);
970
970
  segmentInfo = extractSegmentInfo(response);
971
971
  params = extractParams(response);
@@ -1162,7 +1162,7 @@ function createRouter(deps) {
1162
1162
  restoreScrollAfterPaint(scroll ? 0 : currentScrollY);
1163
1163
  } catch (error) {
1164
1164
  if (error instanceof VersionSkewError) {
1165
- const { triggerStaleReload } = await import("../_chunks/stale-reload-DKN3aXxR.js");
1165
+ const { triggerStaleReload } = await import("../_chunks/stale-reload-BSSym1MJ.js");
1166
1166
  triggerStaleReload();
1167
1167
  return new Promise(() => {});
1168
1168
  }