@timber-js/app 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/request-context-BzES06i1.js.map +1 -1
- package/dist/_chunks/use-query-states-wEXY2JQB.js +109 -0
- package/dist/_chunks/use-query-states-wEXY2JQB.js.map +1 -0
- package/dist/client/index.js +1 -83
- package/dist/client/index.js.map +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/search-params/create.d.ts.map +1 -1
- package/dist/search-params/index.js +13 -4
- package/dist/search-params/index.js.map +1 -1
- package/dist/server/fallback-error.d.ts +28 -0
- package/dist/server/fallback-error.d.ts.map +1 -0
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/pipeline.d.ts +12 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/package.json +23 -23
- package/src/client/browser-entry.ts +1 -1
- package/src/client/use-query-states.ts +13 -1
- package/src/index.ts +16 -16
- package/src/plugins/dev-server.ts +3 -1
- package/src/plugins/entries.ts +2 -1
- package/src/plugins/routing.ts +5 -4
- package/src/plugins/server-bundle.ts +15 -6
- package/src/routing/status-file-lint.ts +1 -3
- package/src/search-params/create.ts +15 -8
- package/src/server/error-formatter.ts +12 -0
- package/src/server/fallback-error.ts +159 -0
- package/src/server/html-injectors.ts +9 -4
- package/src/server/pipeline.ts +24 -0
- package/src/server/request-context.ts +0 -1
- package/src/server/route-matcher.ts +1 -4
- package/src/server/rsc-entry/index.ts +88 -36
- package/src/server/slot-resolver.ts +38 -5
- package/src/server/ssr-entry.ts +4 -1
- package/src/server/tree-builder.ts +4 -1
- package/src/shims/server-only-noop.js +1 -0
- package/dist/_chunks/registry-BfPM41ri.js +0 -20
- package/dist/_chunks/registry-BfPM41ri.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../src/plugins/entries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../src/plugins/entries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAqGhD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA2FxD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/plugins/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/plugins/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAWlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA2DhD,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA2GxD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-bundle.d.ts","sourceRoot":"","sources":["../../src/plugins/server-bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"server-bundle.d.ts","sourceRoot":"","sources":["../../src/plugins/server-bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CA0G7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-file-lint.d.ts","sourceRoot":"","sources":["../../src/routing/status-file-lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAMzD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,SAAS,GAAG,qBAAqB,EAAE,CAIjF;AAoDD;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"status-file-lint.d.ts","sourceRoot":"","sources":["../../src/routing/status-file-lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAMzD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,SAAS,GAAG,qBAAqB,EAAE,CAIjF;AAoDD;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,MAAM,CAiBtF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/search-params/create.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/search-params/create.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as registerSearchParams, n as useQueryStates, r as getSearchParams } from "../_chunks/use-query-states-wEXY2JQB.js";
|
|
2
2
|
//#region src/search-params/create.ts
|
|
3
3
|
/**
|
|
4
|
+
* createSearchParams — factory for SearchParamsDefinition<T>.
|
|
5
|
+
*
|
|
6
|
+
* Creates a typed, composable definition for a route's search parameters.
|
|
7
|
+
* Supports codec protocol, URL key aliasing, default-omission serialization,
|
|
8
|
+
* and composition via .extend() / .pick().
|
|
9
|
+
*
|
|
10
|
+
* Design doc: design/09-typescript.md §"Typed searchParams — search-params.ts"
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
4
13
|
* Convert URLSearchParams or a plain record to a normalized record
|
|
5
14
|
* where repeated keys produce arrays.
|
|
6
15
|
*/
|
|
@@ -109,12 +118,12 @@ function buildDefinition(codecMap, urlKeys) {
|
|
|
109
118
|
}
|
|
110
119
|
return buildDefinition(pickedCodecs, pickedUrlKeys);
|
|
111
120
|
}
|
|
112
|
-
function useQueryStates(
|
|
113
|
-
|
|
121
|
+
function useQueryStates$1(options) {
|
|
122
|
+
return useQueryStates(codecMap, options, Object.freeze({ ...urlKeys }));
|
|
114
123
|
}
|
|
115
124
|
return {
|
|
116
125
|
parse,
|
|
117
|
-
useQueryStates,
|
|
126
|
+
useQueryStates: useQueryStates$1,
|
|
118
127
|
extend,
|
|
119
128
|
pick,
|
|
120
129
|
serialize,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/search-params/create.ts","../../src/search-params/codecs.ts","../../src/search-params/analyze.ts"],"sourcesContent":["/**\n * createSearchParams — factory for SearchParamsDefinition<T>.\n *\n * Creates a typed, composable definition for a route's search parameters.\n * Supports codec protocol, URL key aliasing, default-omission serialization,\n * and composition via .extend() / .pick().\n *\n * Design doc: design/09-typescript.md §\"Typed searchParams — search-params.ts\"\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A codec that converts between URL string values and typed values.\n *\n * nuqs parsers (parseAsInteger, parseAsString, etc.) implement this\n * interface natively — no adapter needed.\n */\nexport interface SearchParamCodec<T> {\n /** URL string → typed value. Receives undefined when the param is absent. */\n parse(value: string | string[] | undefined): T;\n /** Typed value → URL string. Return null to omit from URL. */\n serialize(value: T): string | null;\n}\n\n/** Infer the output type of a codec. */\nexport type InferCodec<C> = C extends SearchParamCodec<infer T> ? T : never;\n\n/** Map of property names to codecs. */\nexport type CodecMap<T extends Record<string, unknown>> = {\n [K in keyof T]: SearchParamCodec<T[K]>;\n};\n\n/** Options for useQueryStates setter. */\nexport interface SetParamsOptions {\n /** Update URL without server roundtrip (default: false). */\n shallow?: boolean;\n /** Scroll to top after update (default: true). */\n scroll?: boolean;\n /** 'push' (default) or 'replace' for history state. */\n history?: 'push' | 'replace';\n}\n\n/** Setter function returned by useQueryStates. */\nexport type SetParams<T> = (values: Partial<T>, options?: SetParamsOptions) => void;\n\n/** Options for useQueryStates hook. */\nexport interface QueryStatesOptions {\n /** Update URL without server roundtrip (default: false). */\n shallow?: boolean;\n /** Scroll to top after update (default: true). */\n scroll?: boolean;\n /** 'push' (default) or 'replace' for history state. */\n history?: 'push' | 'replace';\n}\n\n/** Options for createSearchParams and .extend(). */\nexport interface SearchParamsOptions<Keys extends string = string> {\n /** Map property names to different URL query parameter keys. */\n urlKeys?: Partial<Record<Keys, string>>;\n}\n\n/**\n * A fully typed, composable search params definition.\n *\n * Returned by createSearchParams(). Carries a phantom _type property\n * for build-time type extraction.\n */\nexport interface SearchParamsDefinition<T extends Record<string, unknown>> {\n /** Parse raw URL search params into typed values. */\n parse(raw: URLSearchParams | Record<string, string | string[] | undefined>): T;\n\n /** Client hook — reads current URL params and returns typed values + setter. */\n useQueryStates(options?: QueryStatesOptions): [T, SetParams<T>];\n\n /** Extend with additional codecs. Key collisions are a type error. */\n extend<U extends Record<string, SearchParamCodec<unknown>>>(\n codecs: U,\n options?: SearchParamsOptions<string>\n ): SearchParamsDefinition<T & { [K in keyof U]: InferCodec<U[K]> }>;\n\n /** Pick a subset of keys. Preserves codecs and aliases. */\n pick<K extends keyof T & string>(...keys: K[]): SearchParamsDefinition<Pick<T, K>>;\n\n /** Serialize values to a query string (no leading '?'), omitting defaults. */\n serialize(values: Partial<T>): string;\n\n /** Build a full path with query string, omitting defaults. */\n href(pathname: string, values: Partial<T>): string;\n\n /** Build a URLSearchParams instance, omitting defaults. */\n toSearchParams(values: Partial<T>): URLSearchParams;\n\n /** Read-only codec map for spreading into .extend(). Aliases NOT carried. */\n codecs: { [K in keyof T]: SearchParamCodec<T[K]> };\n\n /** Read-only URL key alias map. Maps property names to URL query parameter keys. */\n readonly urlKeys: Readonly<Record<string, string>>;\n\n /**\n * Phantom property for build-time type extraction.\n * Never set at runtime — exists only in the type system.\n */\n readonly _type?: T;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert URLSearchParams or a plain record to a normalized record\n * where repeated keys produce arrays.\n */\nfunction normalizeRaw(\n raw: URLSearchParams | Record<string, string | string[] | undefined>\n): Record<string, string | string[] | undefined> {\n if (raw instanceof URLSearchParams) {\n const result: Record<string, string | string[] | undefined> = {};\n for (const key of new Set(raw.keys())) {\n const values = raw.getAll(key);\n result[key] = values.length === 1 ? values[0] : values;\n }\n return result;\n }\n return raw;\n}\n\n/**\n * Compute the serialized default value for a codec. Used for\n * default-omission: when serialize(value) === serialize(parse(undefined)),\n * the field is omitted from the URL.\n */\nfunction getDefaultSerialized<T>(codec: SearchParamCodec<T>): string | null {\n return codec.serialize(codec.parse(undefined));\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a SearchParamsDefinition from a codec map and optional URL key aliases.\n *\n * ```ts\n * import { createSearchParams, fromSchema } from '@timber-js/app/search-params'\n * import { z } from 'zod/v4'\n *\n * export default createSearchParams({\n * page: fromSchema(z.coerce.number().int().min(1).default(1)),\n * q: { parse: (v) => v ?? null, serialize: (v) => v },\n * }, {\n * urlKeys: { q: 'search' },\n * })\n * ```\n */\nexport function createSearchParams<C extends Record<string, SearchParamCodec<unknown>>>(\n codecs: C,\n options?: SearchParamsOptions<Extract<keyof C, string>>\n): SearchParamsDefinition<{ [K in keyof C]: InferCodec<C[K]> }> {\n type T = { [K in keyof C]: InferCodec<C[K]> };\n const urlKeys: Record<string, string> = {};\n if (options?.urlKeys) {\n for (const [k, v] of Object.entries(options.urlKeys)) {\n if (v !== undefined) urlKeys[k] = v;\n }\n }\n\n return buildDefinition<T>(codecs as unknown as CodecMap<T>, urlKeys);\n}\n\n/**\n * Internal: build a SearchParamsDefinition from a typed codec map and url keys.\n */\nfunction buildDefinition<T extends Record<string, unknown>>(\n codecMap: CodecMap<T>,\n urlKeys: Record<string, string>\n): SearchParamsDefinition<T> {\n // Pre-compute default serialized values for omission check\n const defaultSerialized: Record<string, string | null> = {};\n for (const key of Object.keys(codecMap)) {\n defaultSerialized[key] = getDefaultSerialized(codecMap[key as keyof T]);\n }\n\n function getUrlKey(prop: string): string {\n return urlKeys[prop] ?? prop;\n }\n\n // ---- parse ----\n function parse(raw: URLSearchParams | Record<string, string | string[] | undefined>): T {\n const normalized = normalizeRaw(raw);\n const result: Record<string, unknown> = {};\n\n for (const prop of Object.keys(codecMap)) {\n const urlKey = getUrlKey(prop);\n const rawValue = normalized[urlKey];\n result[prop] = (codecMap[prop as keyof T] as SearchParamCodec<unknown>).parse(rawValue);\n }\n\n return result as T;\n }\n\n // ---- serialize ----\n function serialize(values: Partial<T>): string {\n const parts: string[] = [];\n\n for (const prop of Object.keys(codecMap)) {\n if (!(prop in values)) continue;\n const codec = codecMap[prop as keyof T] as SearchParamCodec<unknown>;\n const serialized = codec.serialize(values[prop as keyof T] as unknown);\n\n // Omit if serialized value matches the default\n if (serialized === defaultSerialized[prop]) continue;\n if (serialized === null) continue;\n\n parts.push(`${encodeURIComponent(getUrlKey(prop))}=${encodeURIComponent(serialized)}`);\n }\n\n return parts.join('&');\n }\n\n // ---- href ----\n function href(pathname: string, values: Partial<T>): string {\n const qs = serialize(values);\n return qs ? `${pathname}?${qs}` : pathname;\n }\n\n // ---- toSearchParams ----\n function toSearchParams(values: Partial<T>): URLSearchParams {\n const usp = new URLSearchParams();\n\n for (const prop of Object.keys(codecMap)) {\n if (!(prop in values)) continue;\n const codec = codecMap[prop as keyof T] as SearchParamCodec<unknown>;\n const serialized = codec.serialize(values[prop as keyof T] as unknown);\n\n if (serialized === defaultSerialized[prop]) continue;\n if (serialized === null) continue;\n\n usp.set(getUrlKey(prop), serialized);\n }\n\n return usp;\n }\n\n // ---- extend ----\n function extend<U extends Record<string, SearchParamCodec<unknown>>>(\n newCodecs: U,\n extendOptions?: SearchParamsOptions<string>\n ): SearchParamsDefinition<T & { [K in keyof U]: InferCodec<U[K]> }> {\n type Combined = T & { [K in keyof U]: InferCodec<U[K]> };\n\n const combinedCodecs = {\n ...codecMap,\n ...newCodecs,\n } as unknown as CodecMap<Combined>;\n\n // Merge URL keys: extend options override, but do NOT inherit from base\n // (aliases are route-level, not carried through .codecs)\n const combinedUrlKeys: Record<string, string> = { ...urlKeys };\n if (extendOptions?.urlKeys) {\n for (const [k, v] of Object.entries(extendOptions.urlKeys)) {\n if (v !== undefined) combinedUrlKeys[k] = v;\n }\n }\n\n return buildDefinition<Combined>(combinedCodecs, combinedUrlKeys);\n }\n\n // ---- pick ----\n function pick<K extends keyof T & string>(...keys: K[]): SearchParamsDefinition<Pick<T, K>> {\n const pickedCodecs: Record<string, SearchParamCodec<unknown>> = {};\n const pickedUrlKeys: Record<string, string> = {};\n\n for (const key of keys) {\n pickedCodecs[key] = codecMap[key] as SearchParamCodec<unknown>;\n if (key in urlKeys) {\n pickedUrlKeys[key] = urlKeys[key];\n }\n }\n\n return buildDefinition<Pick<T, K>>(\n pickedCodecs as unknown as CodecMap<Pick<T, K>>,\n pickedUrlKeys\n );\n }\n\n // ---- useQueryStates ----\n // This is a placeholder that will be replaced by the client runtime.\n // At import time in a server context, calling this throws.\n // The actual implementation wraps nuqs and lives in @timber-js/app/client.\n function useQueryStates(_options?: QueryStatesOptions): [T, SetParams<T>] {\n throw new Error(\n 'useQueryStates() can only be called in a client component. ' +\n 'Import from @timber-js/app/client instead.'\n );\n }\n\n const definition: SearchParamsDefinition<T> = {\n parse,\n useQueryStates,\n extend,\n pick,\n serialize,\n href,\n toSearchParams,\n codecs: codecMap,\n urlKeys: Object.freeze({ ...urlKeys }),\n };\n\n return definition;\n}\n","/**\n * Built-in codecs and the fromSchema bridge for Standard Schema-compatible\n * validation libraries (Zod, Valibot, ArkType).\n *\n * Design doc: design/09-typescript.md §\"The SearchParamCodec Protocol\"\n */\n\nimport type { SearchParamCodec } from './create.js';\n\n// ---------------------------------------------------------------------------\n// Standard Schema interface (subset)\n//\n// Standard Schema (https://github.com/standard-schema/standard-schema) defines\n// a minimal interface that Zod ≥3.24, Valibot ≥1.0, and ArkType all implement.\n// We depend only on `~standard.validate` to avoid coupling to any specific lib.\n// ---------------------------------------------------------------------------\n\ninterface StandardSchemaV1<Output = unknown> {\n '~standard': {\n validate(value: unknown): StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n };\n}\n\ntype StandardSchemaResult<Output> =\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> };\n\n// ---------------------------------------------------------------------------\n// Sync validate helper\n// ---------------------------------------------------------------------------\n\n/**\n * Zod v4's ~standard.validate() signature includes Promise in the return union\n * to satisfy the Standard Schema spec, but in practice Zod always validates\n * synchronously for the schema types we use. We assert the result is sync and\n * throw if it isn't — search params parsing must be synchronous.\n */\nfunction validateSync<Output>(\n schema: StandardSchemaV1<Output>,\n value: unknown\n): StandardSchemaResult<Output> {\n const result = schema['~standard'].validate(value);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] fromSchema: schema returned a Promise — only sync schemas are supported for search params.'\n );\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// fromSchema — bridge from Standard Schema to SearchParamCodec\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema-compatible schema (Zod, Valibot, ArkType) to a\n * SearchParamCodec.\n *\n * Parse: coerces the raw URL string through the schema. On validation failure,\n * parses `undefined` to get the schema's default value (the schema should have\n * a `.default()` call). If that also fails, returns `undefined`.\n *\n * Serialize: uses `String()` for primitives, `null` for null/undefined.\n *\n * ```ts\n * import { fromSchema } from '@timber-js/app/search-params'\n * import { z } from 'zod/v4'\n *\n * const pageCodec = fromSchema(z.coerce.number().int().min(1).default(1))\n * ```\n */\nexport function fromSchema<T>(schema: StandardSchemaV1<T>): SearchParamCodec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // For array inputs, take the last value (consistent with URLSearchParams.get())\n const input = Array.isArray(value) ? value[value.length - 1] : value;\n\n // Try parsing the raw value\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // On failure, try parsing undefined to get the default\n const defaultResult = validateSync(schema, undefined);\n if (!defaultResult.issues) {\n return defaultResult.value;\n }\n\n // No default available — return undefined (codec design choice)\n return undefined as T;\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n return String(value);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// fromArraySchema — bridge for array-valued search params\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema for array values. Handles both single strings\n * and repeated query keys (`?tag=a&tag=b`).\n *\n * ```ts\n * import { fromArraySchema } from '@timber-js/app/search-params'\n * import { z } from 'zod/v4'\n *\n * const tagsCodec = fromArraySchema(z.array(z.string()).default([]))\n * ```\n */\nexport function fromArraySchema<T>(schema: StandardSchemaV1<T>): SearchParamCodec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // Coerce single string to array for array schemas\n let input: unknown = value;\n if (typeof value === 'string') {\n input = [value];\n } else if (value === undefined) {\n input = undefined;\n }\n\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // On failure, try undefined for default\n const defaultResult = validateSync(schema, undefined);\n if (!defaultResult.issues) {\n return defaultResult.value;\n }\n\n return undefined as T;\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (Array.isArray(value)) {\n return value.length === 0 ? null : value.join(',');\n }\n return String(value);\n },\n };\n}\n","/**\n * Static analyzability checker for search-params.ts files.\n *\n * Validates that a search-params.ts file's default export is statically\n * analyzable — a createSearchParams() call or a chain of .extend()/.pick()\n * calls on a SearchParamsDefinition.\n *\n * Non-analyzable files produce a hard build error with a diagnostic.\n *\n * Design doc: design/09-typescript.md §\"Static Analyzability\"\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Result of analyzing a search-params.ts file. */\nexport interface AnalyzeResult {\n /** Whether the file is statically analyzable. */\n valid: boolean;\n /** Error details when valid is false. */\n error?: AnalyzeError;\n}\n\n/** Diagnostic error for non-analyzable search-params.ts. */\nexport interface AnalyzeError {\n /** Absolute file path. */\n filePath: string;\n /** Description of the non-analyzable expression. */\n expression: string;\n /** Suggested fix. */\n suggestion: string;\n}\n\n// ---------------------------------------------------------------------------\n// AST-free source analysis\n//\n// We use a lightweight regex-based approach to validate the structure of the\n// default export. This avoids requiring a TypeScript compiler instance at\n// build time for the initial validation pass. The full type extraction\n// (reading T from SearchParamsDefinition<T>) still happens via the TypeScript\n// compiler in the codegen step — this module just validates the *shape*.\n// ---------------------------------------------------------------------------\n\n/**\n * Patterns that indicate a valid default export:\n *\n * 1. `export default createSearchParams(...)`\n * 2. `export default someVar.extend(...)`\n * 3. `export default someVar.pick(...)`\n * 4. `export default someVar.extend(...).extend(...)` (chained)\n * 5. `export default someVar.extend(...).pick(...)` (chained)\n * 6. `export default createSearchParams(...).extend(...)`\n *\n * Invalid patterns:\n * - `export default someFunction(...)` (arbitrary factory)\n * - `export default condition ? a : b` (runtime conditional)\n * - `export default variable` (opaque reference without call)\n */\n\n/**\n * Analyze a search-params.ts file source for static analyzability.\n *\n * @param source - The file content as a string\n * @param filePath - Absolute path to the file (for diagnostics)\n */\nexport function analyzeSearchParams(source: string, filePath: string): AnalyzeResult {\n // Strip comments to avoid false matches\n const stripped = stripComments(source);\n\n // Find the default export\n const defaultExport = extractDefaultExport(stripped);\n\n if (!defaultExport) {\n return {\n valid: false,\n error: {\n filePath,\n expression: '(no default export found)',\n suggestion:\n 'search-params.ts must have a default export. Use: export default createSearchParams({ ... })',\n },\n };\n }\n\n // Validate the expression\n if (isValidExpression(defaultExport.trim())) {\n return { valid: true };\n }\n\n return {\n valid: false,\n error: {\n filePath,\n expression: defaultExport.trim(),\n suggestion:\n 'The default export must be a createSearchParams() call, or a chain of ' +\n '.extend() / .pick() calls on a SearchParamsDefinition. Arbitrary factory ' +\n 'functions and runtime conditionals are not supported.',\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Strip single-line and multi-line comments from source. */\nfunction stripComments(source: string): string {\n // Remove multi-line comments\n let result = source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n // Remove single-line comments\n result = result.replace(/\\/\\/.*$/gm, '');\n return result;\n}\n\n/**\n * Extract the expression from `export default <expr>`.\n *\n * Handles both:\n * export default createSearchParams(...)\n * export default expr\\n (terminated by newline or semicolon before next statement)\n */\nfunction extractDefaultExport(source: string): string | undefined {\n // Match `export default` followed by the expression\n const match = source.match(\n /export\\s+default\\s+([\\s\\S]+?)(?:;|\\n(?=export|import|const|let|var|function|class|type|interface|declare))/\n );\n if (match) {\n return match[1];\n }\n\n // Fallback: match everything after `export default` to end of file\n const fallback = source.match(/export\\s+default\\s+([\\s\\S]+)$/);\n if (fallback) {\n return fallback[1].replace(/;\\s*$/, '');\n }\n\n return undefined;\n}\n\n/**\n * Check if an expression is a valid statically-analyzable pattern.\n *\n * Valid patterns:\n * - Starts with `createSearchParams(`\n * - Contains `.extend(` or `.pick(` chains (possibly starting with createSearchParams or a variable)\n * - A variable identifier followed by chaining\n */\nfunction isValidExpression(expr: string): boolean {\n // Normalize whitespace\n const normalized = expr.replace(/\\s+/g, ' ').trim();\n\n // Pattern 1: starts with createSearchParams(\n if (normalized.startsWith('createSearchParams(')) {\n return true;\n }\n\n // Pattern 2: chain ending with .extend(...) or .pick(...)\n // This covers: someVar.extend(...), createSearchParams(...).extend(...).pick(...), etc.\n if (/\\.(extend|pick)\\s*\\(/.test(normalized)) {\n // Reject ternaries and other conditional patterns\n if (/\\?/.test(normalized) && /:/.test(normalized)) {\n return false;\n }\n // Reject function declarations/expressions\n if (/^\\s*(function|=>|\\()/.test(normalized)) {\n return false;\n }\n return true;\n }\n\n return false;\n}\n\n/**\n * Format an AnalyzeError into a human-readable build error message.\n */\nexport function formatAnalyzeError(error: AnalyzeError): string {\n return [\n `[timber] Non-analyzable search-params.ts`,\n ``,\n ` File: ${error.filePath}`,\n ` Expression: ${error.expression}`,\n ``,\n ` ${error.suggestion}`,\n ``,\n ` The framework must be able to statically extract the type from your`,\n ` search-params.ts at build time. Dynamic values, conditionals, and`,\n ` arbitrary factory functions prevent this analysis.`,\n ].join('\\n');\n}\n"],"mappings":";;;;;;AAoHA,SAAS,aACP,KAC+C;AAC/C,KAAI,eAAe,iBAAiB;EAClC,MAAM,SAAwD,EAAE;AAChE,OAAK,MAAM,OAAO,IAAI,IAAI,IAAI,MAAM,CAAC,EAAE;GACrC,MAAM,SAAS,IAAI,OAAO,IAAI;AAC9B,UAAO,OAAO,OAAO,WAAW,IAAI,OAAO,KAAK;;AAElD,SAAO;;AAET,QAAO;;;;;;;AAQT,SAAS,qBAAwB,OAA2C;AAC1E,QAAO,MAAM,UAAU,MAAM,MAAM,KAAA,EAAU,CAAC;;;;;;;;;;;;;;;;;AAsBhD,SAAgB,mBACd,QACA,SAC8D;CAE9D,MAAM,UAAkC,EAAE;AAC1C,KAAI,SAAS;OACN,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,QAAQ,QAAQ,CAClD,KAAI,MAAM,KAAA,EAAW,SAAQ,KAAK;;AAItC,QAAO,gBAAmB,QAAkC,QAAQ;;;;;AAMtE,SAAS,gBACP,UACA,SAC2B;CAE3B,MAAM,oBAAmD,EAAE;AAC3D,MAAK,MAAM,OAAO,OAAO,KAAK,SAAS,CACrC,mBAAkB,OAAO,qBAAqB,SAAS,KAAgB;CAGzE,SAAS,UAAU,MAAsB;AACvC,SAAO,QAAQ,SAAS;;CAI1B,SAAS,MAAM,KAAyE;EACtF,MAAM,aAAa,aAAa,IAAI;EACpC,MAAM,SAAkC,EAAE;AAE1C,OAAK,MAAM,QAAQ,OAAO,KAAK,SAAS,EAAE;GAExC,MAAM,WAAW,WADF,UAAU,KAAK;AAE9B,UAAO,QAAS,SAAS,MAA+C,MAAM,SAAS;;AAGzF,SAAO;;CAIT,SAAS,UAAU,QAA4B;EAC7C,MAAM,QAAkB,EAAE;AAE1B,OAAK,MAAM,QAAQ,OAAO,KAAK,SAAS,EAAE;AACxC,OAAI,EAAE,QAAQ,QAAS;GAEvB,MAAM,aADQ,SAAS,MACE,UAAU,OAAO,MAA4B;AAGtE,OAAI,eAAe,kBAAkB,MAAO;AAC5C,OAAI,eAAe,KAAM;AAEzB,SAAM,KAAK,GAAG,mBAAmB,UAAU,KAAK,CAAC,CAAC,GAAG,mBAAmB,WAAW,GAAG;;AAGxF,SAAO,MAAM,KAAK,IAAI;;CAIxB,SAAS,KAAK,UAAkB,QAA4B;EAC1D,MAAM,KAAK,UAAU,OAAO;AAC5B,SAAO,KAAK,GAAG,SAAS,GAAG,OAAO;;CAIpC,SAAS,eAAe,QAAqC;EAC3D,MAAM,MAAM,IAAI,iBAAiB;AAEjC,OAAK,MAAM,QAAQ,OAAO,KAAK,SAAS,EAAE;AACxC,OAAI,EAAE,QAAQ,QAAS;GAEvB,MAAM,aADQ,SAAS,MACE,UAAU,OAAO,MAA4B;AAEtE,OAAI,eAAe,kBAAkB,MAAO;AAC5C,OAAI,eAAe,KAAM;AAEzB,OAAI,IAAI,UAAU,KAAK,EAAE,WAAW;;AAGtC,SAAO;;CAIT,SAAS,OACP,WACA,eACkE;EAGlE,MAAM,iBAAiB;GACrB,GAAG;GACH,GAAG;GACJ;EAID,MAAM,kBAA0C,EAAE,GAAG,SAAS;AAC9D,MAAI,eAAe;QACZ,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,cAAc,QAAQ,CACxD,KAAI,MAAM,KAAA,EAAW,iBAAgB,KAAK;;AAI9C,SAAO,gBAA0B,gBAAgB,gBAAgB;;CAInE,SAAS,KAAiC,GAAG,MAA+C;EAC1F,MAAM,eAA0D,EAAE;EAClE,MAAM,gBAAwC,EAAE;AAEhD,OAAK,MAAM,OAAO,MAAM;AACtB,gBAAa,OAAO,SAAS;AAC7B,OAAI,OAAO,QACT,eAAc,OAAO,QAAQ;;AAIjC,SAAO,gBACL,cACA,cACD;;CAOH,SAAS,eAAe,UAAkD;AACxE,QAAM,IAAI,MACR,wGAED;;AAeH,QAZ8C;EAC5C;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR,SAAS,OAAO,OAAO,EAAE,GAAG,SAAS,CAAC;EACvC;;;;;;;;;;ACjRH,SAAS,aACP,QACA,OAC8B;CAC9B,MAAM,SAAS,OAAO,aAAa,SAAS,MAAM;AAClD,KAAI,kBAAkB,QACpB,OAAM,IAAI,MACR,sGACD;AAEH,QAAO;;;;;;;;;;;;;;;;;;;AAwBT,SAAgB,WAAc,QAAkD;AAC9E,QAAO;EACL,MAAM,OAAyC;GAK7C,MAAM,SAAS,aAAa,QAHd,MAAM,QAAQ,MAAM,GAAG,MAAM,MAAM,SAAS,KAAK,MAGrB;AAC1C,OAAI,CAAC,OAAO,OACV,QAAO,OAAO;GAIhB,MAAM,gBAAgB,aAAa,QAAQ,KAAA,EAAU;AACrD,OAAI,CAAC,cAAc,OACjB,QAAO,cAAc;;EAOzB,UAAU,OAAyB;AACjC,OAAI,UAAU,QAAQ,UAAU,KAAA,EAC9B,QAAO;AAET,UAAO,OAAO,MAAM;;EAEvB;;;;;;;;;;;;;AAkBH,SAAgB,gBAAmB,QAAkD;AACnF,QAAO;EACL,MAAM,OAAyC;GAE7C,IAAI,QAAiB;AACrB,OAAI,OAAO,UAAU,SACnB,SAAQ,CAAC,MAAM;YACN,UAAU,KAAA,EACnB,SAAQ,KAAA;GAGV,MAAM,SAAS,aAAa,QAAQ,MAAM;AAC1C,OAAI,CAAC,OAAO,OACV,QAAO,OAAO;GAIhB,MAAM,gBAAgB,aAAa,QAAQ,KAAA,EAAU;AACrD,OAAI,CAAC,cAAc,OACjB,QAAO,cAAc;;EAMzB,UAAU,OAAyB;AACjC,OAAI,UAAU,QAAQ,UAAU,KAAA,EAC9B,QAAO;AAET,OAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,WAAW,IAAI,OAAO,MAAM,KAAK,IAAI;AAEpD,UAAO,OAAO,MAAM;;EAEvB;;;;;;;;;;;;;;;;;;;;;;;;;ACrFH,SAAgB,oBAAoB,QAAgB,UAAiC;CAKnF,MAAM,gBAAgB,qBAHL,cAAc,OAAO,CAGc;AAEpD,KAAI,CAAC,cACH,QAAO;EACL,OAAO;EACP,OAAO;GACL;GACA,YAAY;GACZ,YACE;GACH;EACF;AAIH,KAAI,kBAAkB,cAAc,MAAM,CAAC,CACzC,QAAO,EAAE,OAAO,MAAM;AAGxB,QAAO;EACL,OAAO;EACP,OAAO;GACL;GACA,YAAY,cAAc,MAAM;GAChC,YACE;GAGH;EACF;;;AAQH,SAAS,cAAc,QAAwB;CAE7C,IAAI,SAAS,OAAO,QAAQ,qBAAqB,GAAG;AAEpD,UAAS,OAAO,QAAQ,aAAa,GAAG;AACxC,QAAO;;;;;;;;;AAUT,SAAS,qBAAqB,QAAoC;CAEhE,MAAM,QAAQ,OAAO,MACnB,6GACD;AACD,KAAI,MACF,QAAO,MAAM;CAIf,MAAM,WAAW,OAAO,MAAM,gCAAgC;AAC9D,KAAI,SACF,QAAO,SAAS,GAAG,QAAQ,SAAS,GAAG;;;;;;;;;;AAc3C,SAAS,kBAAkB,MAAuB;CAEhD,MAAM,aAAa,KAAK,QAAQ,QAAQ,IAAI,CAAC,MAAM;AAGnD,KAAI,WAAW,WAAW,sBAAsB,CAC9C,QAAO;AAKT,KAAI,uBAAuB,KAAK,WAAW,EAAE;AAE3C,MAAI,KAAK,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,CAC/C,QAAO;AAGT,MAAI,uBAAuB,KAAK,WAAW,CACzC,QAAO;AAET,SAAO;;AAGT,QAAO;;;;;AAMT,SAAgB,mBAAmB,OAA6B;AAC9D,QAAO;EACL;EACA;EACA,WAAW,MAAM;EACjB,iBAAiB,MAAM;EACvB;EACA,KAAK,MAAM;EACX;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/search-params/create.ts","../../src/search-params/codecs.ts","../../src/search-params/analyze.ts"],"sourcesContent":["/**\n * createSearchParams — factory for SearchParamsDefinition<T>.\n *\n * Creates a typed, composable definition for a route's search parameters.\n * Supports codec protocol, URL key aliasing, default-omission serialization,\n * and composition via .extend() / .pick().\n *\n * Design doc: design/09-typescript.md §\"Typed searchParams — search-params.ts\"\n */\n\nimport { useQueryStates as clientUseQueryStates } from '#/client/use-query-states.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A codec that converts between URL string values and typed values.\n *\n * nuqs parsers (parseAsInteger, parseAsString, etc.) implement this\n * interface natively — no adapter needed.\n */\nexport interface SearchParamCodec<T> {\n /** URL string → typed value. Receives undefined when the param is absent. */\n parse(value: string | string[] | undefined): T;\n /** Typed value → URL string. Return null to omit from URL. */\n serialize(value: T): string | null;\n}\n\n/** Infer the output type of a codec. */\nexport type InferCodec<C> = C extends SearchParamCodec<infer T> ? T : never;\n\n/** Map of property names to codecs. */\nexport type CodecMap<T extends Record<string, unknown>> = {\n [K in keyof T]: SearchParamCodec<T[K]>;\n};\n\n/** Options for useQueryStates setter. */\nexport interface SetParamsOptions {\n /** Update URL without server roundtrip (default: false). */\n shallow?: boolean;\n /** Scroll to top after update (default: true). */\n scroll?: boolean;\n /** 'push' (default) or 'replace' for history state. */\n history?: 'push' | 'replace';\n}\n\n/** Setter function returned by useQueryStates. */\nexport type SetParams<T> = (values: Partial<T>, options?: SetParamsOptions) => void;\n\n/** Options for useQueryStates hook. */\nexport interface QueryStatesOptions {\n /** Update URL without server roundtrip (default: false). */\n shallow?: boolean;\n /** Scroll to top after update (default: true). */\n scroll?: boolean;\n /** 'push' (default) or 'replace' for history state. */\n history?: 'push' | 'replace';\n}\n\n/** Options for createSearchParams and .extend(). */\nexport interface SearchParamsOptions<Keys extends string = string> {\n /** Map property names to different URL query parameter keys. */\n urlKeys?: Partial<Record<Keys, string>>;\n}\n\n/**\n * A fully typed, composable search params definition.\n *\n * Returned by createSearchParams(). Carries a phantom _type property\n * for build-time type extraction.\n */\nexport interface SearchParamsDefinition<T extends Record<string, unknown>> {\n /** Parse raw URL search params into typed values. */\n parse(raw: URLSearchParams | Record<string, string | string[] | undefined>): T;\n\n /** Client hook — reads current URL params and returns typed values + setter. */\n useQueryStates(options?: QueryStatesOptions): [T, SetParams<T>];\n\n /** Extend with additional codecs. Key collisions are a type error. */\n extend<U extends Record<string, SearchParamCodec<unknown>>>(\n codecs: U,\n options?: SearchParamsOptions<string>\n ): SearchParamsDefinition<T & { [K in keyof U]: InferCodec<U[K]> }>;\n\n /** Pick a subset of keys. Preserves codecs and aliases. */\n pick<K extends keyof T & string>(...keys: K[]): SearchParamsDefinition<Pick<T, K>>;\n\n /** Serialize values to a query string (no leading '?'), omitting defaults. */\n serialize(values: Partial<T>): string;\n\n /** Build a full path with query string, omitting defaults. */\n href(pathname: string, values: Partial<T>): string;\n\n /** Build a URLSearchParams instance, omitting defaults. */\n toSearchParams(values: Partial<T>): URLSearchParams;\n\n /** Read-only codec map for spreading into .extend(). Aliases NOT carried. */\n codecs: { [K in keyof T]: SearchParamCodec<T[K]> };\n\n /** Read-only URL key alias map. Maps property names to URL query parameter keys. */\n readonly urlKeys: Readonly<Record<string, string>>;\n\n /**\n * Phantom property for build-time type extraction.\n * Never set at runtime — exists only in the type system.\n */\n readonly _type?: T;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert URLSearchParams or a plain record to a normalized record\n * where repeated keys produce arrays.\n */\nfunction normalizeRaw(\n raw: URLSearchParams | Record<string, string | string[] | undefined>\n): Record<string, string | string[] | undefined> {\n if (raw instanceof URLSearchParams) {\n const result: Record<string, string | string[] | undefined> = {};\n for (const key of new Set(raw.keys())) {\n const values = raw.getAll(key);\n result[key] = values.length === 1 ? values[0] : values;\n }\n return result;\n }\n return raw;\n}\n\n/**\n * Compute the serialized default value for a codec. Used for\n * default-omission: when serialize(value) === serialize(parse(undefined)),\n * the field is omitted from the URL.\n */\nfunction getDefaultSerialized<T>(codec: SearchParamCodec<T>): string | null {\n return codec.serialize(codec.parse(undefined));\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a SearchParamsDefinition from a codec map and optional URL key aliases.\n *\n * ```ts\n * import { createSearchParams, fromSchema } from '@timber-js/app/search-params'\n * import { z } from 'zod/v4'\n *\n * export default createSearchParams({\n * page: fromSchema(z.coerce.number().int().min(1).default(1)),\n * q: { parse: (v) => v ?? null, serialize: (v) => v },\n * }, {\n * urlKeys: { q: 'search' },\n * })\n * ```\n */\nexport function createSearchParams<C extends Record<string, SearchParamCodec<unknown>>>(\n codecs: C,\n options?: SearchParamsOptions<Extract<keyof C, string>>\n): SearchParamsDefinition<{ [K in keyof C]: InferCodec<C[K]> }> {\n type T = { [K in keyof C]: InferCodec<C[K]> };\n const urlKeys: Record<string, string> = {};\n if (options?.urlKeys) {\n for (const [k, v] of Object.entries(options.urlKeys)) {\n if (v !== undefined) urlKeys[k] = v;\n }\n }\n\n return buildDefinition<T>(codecs as unknown as CodecMap<T>, urlKeys);\n}\n\n/**\n * Internal: build a SearchParamsDefinition from a typed codec map and url keys.\n */\nfunction buildDefinition<T extends Record<string, unknown>>(\n codecMap: CodecMap<T>,\n urlKeys: Record<string, string>\n): SearchParamsDefinition<T> {\n // Pre-compute default serialized values for omission check\n const defaultSerialized: Record<string, string | null> = {};\n for (const key of Object.keys(codecMap)) {\n defaultSerialized[key] = getDefaultSerialized(codecMap[key as keyof T]);\n }\n\n function getUrlKey(prop: string): string {\n return urlKeys[prop] ?? prop;\n }\n\n // ---- parse ----\n function parse(raw: URLSearchParams | Record<string, string | string[] | undefined>): T {\n const normalized = normalizeRaw(raw);\n const result: Record<string, unknown> = {};\n\n for (const prop of Object.keys(codecMap)) {\n const urlKey = getUrlKey(prop);\n const rawValue = normalized[urlKey];\n result[prop] = (codecMap[prop as keyof T] as SearchParamCodec<unknown>).parse(rawValue);\n }\n\n return result as T;\n }\n\n // ---- serialize ----\n function serialize(values: Partial<T>): string {\n const parts: string[] = [];\n\n for (const prop of Object.keys(codecMap)) {\n if (!(prop in values)) continue;\n const codec = codecMap[prop as keyof T] as SearchParamCodec<unknown>;\n const serialized = codec.serialize(values[prop as keyof T] as unknown);\n\n // Omit if serialized value matches the default\n if (serialized === defaultSerialized[prop]) continue;\n if (serialized === null) continue;\n\n parts.push(`${encodeURIComponent(getUrlKey(prop))}=${encodeURIComponent(serialized)}`);\n }\n\n return parts.join('&');\n }\n\n // ---- href ----\n function href(pathname: string, values: Partial<T>): string {\n const qs = serialize(values);\n return qs ? `${pathname}?${qs}` : pathname;\n }\n\n // ---- toSearchParams ----\n function toSearchParams(values: Partial<T>): URLSearchParams {\n const usp = new URLSearchParams();\n\n for (const prop of Object.keys(codecMap)) {\n if (!(prop in values)) continue;\n const codec = codecMap[prop as keyof T] as SearchParamCodec<unknown>;\n const serialized = codec.serialize(values[prop as keyof T] as unknown);\n\n if (serialized === defaultSerialized[prop]) continue;\n if (serialized === null) continue;\n\n usp.set(getUrlKey(prop), serialized);\n }\n\n return usp;\n }\n\n // ---- extend ----\n function extend<U extends Record<string, SearchParamCodec<unknown>>>(\n newCodecs: U,\n extendOptions?: SearchParamsOptions<string>\n ): SearchParamsDefinition<T & { [K in keyof U]: InferCodec<U[K]> }> {\n type Combined = T & { [K in keyof U]: InferCodec<U[K]> };\n\n const combinedCodecs = {\n ...codecMap,\n ...newCodecs,\n } as unknown as CodecMap<Combined>;\n\n // Merge URL keys: extend options override, but do NOT inherit from base\n // (aliases are route-level, not carried through .codecs)\n const combinedUrlKeys: Record<string, string> = { ...urlKeys };\n if (extendOptions?.urlKeys) {\n for (const [k, v] of Object.entries(extendOptions.urlKeys)) {\n if (v !== undefined) combinedUrlKeys[k] = v;\n }\n }\n\n return buildDefinition<Combined>(combinedCodecs, combinedUrlKeys);\n }\n\n // ---- pick ----\n function pick<K extends keyof T & string>(...keys: K[]): SearchParamsDefinition<Pick<T, K>> {\n const pickedCodecs: Record<string, SearchParamCodec<unknown>> = {};\n const pickedUrlKeys: Record<string, string> = {};\n\n for (const key of keys) {\n pickedCodecs[key] = codecMap[key] as SearchParamCodec<unknown>;\n if (key in urlKeys) {\n pickedUrlKeys[key] = urlKeys[key];\n }\n }\n\n return buildDefinition<Pick<T, K>>(\n pickedCodecs as unknown as CodecMap<Pick<T, K>>,\n pickedUrlKeys\n );\n }\n\n // ---- useQueryStates ----\n // Delegates to the 'use client' implementation from use-query-states.ts.\n //\n // In the RSC environment: use-query-states.ts is transformed by the RSC\n // plugin into a client reference proxy. Calling it throws — correct,\n // because hooks can't run during server component rendering.\n // In SSR: use-query-states.ts is the real nuqs-backed function. Hooks\n // work during SSR's renderToReadableStream, so this works correctly.\n // On the client: same as SSR — the real function is available.\n function useQueryStates(options?: QueryStatesOptions): [T, SetParams<T>] {\n return clientUseQueryStates(codecMap, options, Object.freeze({ ...urlKeys })) as [\n T,\n SetParams<T>,\n ];\n }\n\n const definition: SearchParamsDefinition<T> = {\n parse,\n useQueryStates,\n extend,\n pick,\n serialize,\n href,\n toSearchParams,\n codecs: codecMap,\n urlKeys: Object.freeze({ ...urlKeys }),\n };\n\n return definition;\n}\n","/**\n * Built-in codecs and the fromSchema bridge for Standard Schema-compatible\n * validation libraries (Zod, Valibot, ArkType).\n *\n * Design doc: design/09-typescript.md §\"The SearchParamCodec Protocol\"\n */\n\nimport type { SearchParamCodec } from './create.js';\n\n// ---------------------------------------------------------------------------\n// Standard Schema interface (subset)\n//\n// Standard Schema (https://github.com/standard-schema/standard-schema) defines\n// a minimal interface that Zod ≥3.24, Valibot ≥1.0, and ArkType all implement.\n// We depend only on `~standard.validate` to avoid coupling to any specific lib.\n// ---------------------------------------------------------------------------\n\ninterface StandardSchemaV1<Output = unknown> {\n '~standard': {\n validate(value: unknown): StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n };\n}\n\ntype StandardSchemaResult<Output> =\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<{ message: string }> };\n\n// ---------------------------------------------------------------------------\n// Sync validate helper\n// ---------------------------------------------------------------------------\n\n/**\n * Zod v4's ~standard.validate() signature includes Promise in the return union\n * to satisfy the Standard Schema spec, but in practice Zod always validates\n * synchronously for the schema types we use. We assert the result is sync and\n * throw if it isn't — search params parsing must be synchronous.\n */\nfunction validateSync<Output>(\n schema: StandardSchemaV1<Output>,\n value: unknown\n): StandardSchemaResult<Output> {\n const result = schema['~standard'].validate(value);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] fromSchema: schema returned a Promise — only sync schemas are supported for search params.'\n );\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// fromSchema — bridge from Standard Schema to SearchParamCodec\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema-compatible schema (Zod, Valibot, ArkType) to a\n * SearchParamCodec.\n *\n * Parse: coerces the raw URL string through the schema. On validation failure,\n * parses `undefined` to get the schema's default value (the schema should have\n * a `.default()` call). If that also fails, returns `undefined`.\n *\n * Serialize: uses `String()` for primitives, `null` for null/undefined.\n *\n * ```ts\n * import { fromSchema } from '@timber-js/app/search-params'\n * import { z } from 'zod/v4'\n *\n * const pageCodec = fromSchema(z.coerce.number().int().min(1).default(1))\n * ```\n */\nexport function fromSchema<T>(schema: StandardSchemaV1<T>): SearchParamCodec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // For array inputs, take the last value (consistent with URLSearchParams.get())\n const input = Array.isArray(value) ? value[value.length - 1] : value;\n\n // Try parsing the raw value\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // On failure, try parsing undefined to get the default\n const defaultResult = validateSync(schema, undefined);\n if (!defaultResult.issues) {\n return defaultResult.value;\n }\n\n // No default available — return undefined (codec design choice)\n return undefined as T;\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n return String(value);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// fromArraySchema — bridge for array-valued search params\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge a Standard Schema for array values. Handles both single strings\n * and repeated query keys (`?tag=a&tag=b`).\n *\n * ```ts\n * import { fromArraySchema } from '@timber-js/app/search-params'\n * import { z } from 'zod/v4'\n *\n * const tagsCodec = fromArraySchema(z.array(z.string()).default([]))\n * ```\n */\nexport function fromArraySchema<T>(schema: StandardSchemaV1<T>): SearchParamCodec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n // Coerce single string to array for array schemas\n let input: unknown = value;\n if (typeof value === 'string') {\n input = [value];\n } else if (value === undefined) {\n input = undefined;\n }\n\n const result = validateSync(schema, input);\n if (!result.issues) {\n return result.value;\n }\n\n // On failure, try undefined for default\n const defaultResult = validateSync(schema, undefined);\n if (!defaultResult.issues) {\n return defaultResult.value;\n }\n\n return undefined as T;\n },\n\n serialize(value: T): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (Array.isArray(value)) {\n return value.length === 0 ? null : value.join(',');\n }\n return String(value);\n },\n };\n}\n","/**\n * Static analyzability checker for search-params.ts files.\n *\n * Validates that a search-params.ts file's default export is statically\n * analyzable — a createSearchParams() call or a chain of .extend()/.pick()\n * calls on a SearchParamsDefinition.\n *\n * Non-analyzable files produce a hard build error with a diagnostic.\n *\n * Design doc: design/09-typescript.md §\"Static Analyzability\"\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Result of analyzing a search-params.ts file. */\nexport interface AnalyzeResult {\n /** Whether the file is statically analyzable. */\n valid: boolean;\n /** Error details when valid is false. */\n error?: AnalyzeError;\n}\n\n/** Diagnostic error for non-analyzable search-params.ts. */\nexport interface AnalyzeError {\n /** Absolute file path. */\n filePath: string;\n /** Description of the non-analyzable expression. */\n expression: string;\n /** Suggested fix. */\n suggestion: string;\n}\n\n// ---------------------------------------------------------------------------\n// AST-free source analysis\n//\n// We use a lightweight regex-based approach to validate the structure of the\n// default export. This avoids requiring a TypeScript compiler instance at\n// build time for the initial validation pass. The full type extraction\n// (reading T from SearchParamsDefinition<T>) still happens via the TypeScript\n// compiler in the codegen step — this module just validates the *shape*.\n// ---------------------------------------------------------------------------\n\n/**\n * Patterns that indicate a valid default export:\n *\n * 1. `export default createSearchParams(...)`\n * 2. `export default someVar.extend(...)`\n * 3. `export default someVar.pick(...)`\n * 4. `export default someVar.extend(...).extend(...)` (chained)\n * 5. `export default someVar.extend(...).pick(...)` (chained)\n * 6. `export default createSearchParams(...).extend(...)`\n *\n * Invalid patterns:\n * - `export default someFunction(...)` (arbitrary factory)\n * - `export default condition ? a : b` (runtime conditional)\n * - `export default variable` (opaque reference without call)\n */\n\n/**\n * Analyze a search-params.ts file source for static analyzability.\n *\n * @param source - The file content as a string\n * @param filePath - Absolute path to the file (for diagnostics)\n */\nexport function analyzeSearchParams(source: string, filePath: string): AnalyzeResult {\n // Strip comments to avoid false matches\n const stripped = stripComments(source);\n\n // Find the default export\n const defaultExport = extractDefaultExport(stripped);\n\n if (!defaultExport) {\n return {\n valid: false,\n error: {\n filePath,\n expression: '(no default export found)',\n suggestion:\n 'search-params.ts must have a default export. Use: export default createSearchParams({ ... })',\n },\n };\n }\n\n // Validate the expression\n if (isValidExpression(defaultExport.trim())) {\n return { valid: true };\n }\n\n return {\n valid: false,\n error: {\n filePath,\n expression: defaultExport.trim(),\n suggestion:\n 'The default export must be a createSearchParams() call, or a chain of ' +\n '.extend() / .pick() calls on a SearchParamsDefinition. Arbitrary factory ' +\n 'functions and runtime conditionals are not supported.',\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Strip single-line and multi-line comments from source. */\nfunction stripComments(source: string): string {\n // Remove multi-line comments\n let result = source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n // Remove single-line comments\n result = result.replace(/\\/\\/.*$/gm, '');\n return result;\n}\n\n/**\n * Extract the expression from `export default <expr>`.\n *\n * Handles both:\n * export default createSearchParams(...)\n * export default expr\\n (terminated by newline or semicolon before next statement)\n */\nfunction extractDefaultExport(source: string): string | undefined {\n // Match `export default` followed by the expression\n const match = source.match(\n /export\\s+default\\s+([\\s\\S]+?)(?:;|\\n(?=export|import|const|let|var|function|class|type|interface|declare))/\n );\n if (match) {\n return match[1];\n }\n\n // Fallback: match everything after `export default` to end of file\n const fallback = source.match(/export\\s+default\\s+([\\s\\S]+)$/);\n if (fallback) {\n return fallback[1].replace(/;\\s*$/, '');\n }\n\n return undefined;\n}\n\n/**\n * Check if an expression is a valid statically-analyzable pattern.\n *\n * Valid patterns:\n * - Starts with `createSearchParams(`\n * - Contains `.extend(` or `.pick(` chains (possibly starting with createSearchParams or a variable)\n * - A variable identifier followed by chaining\n */\nfunction isValidExpression(expr: string): boolean {\n // Normalize whitespace\n const normalized = expr.replace(/\\s+/g, ' ').trim();\n\n // Pattern 1: starts with createSearchParams(\n if (normalized.startsWith('createSearchParams(')) {\n return true;\n }\n\n // Pattern 2: chain ending with .extend(...) or .pick(...)\n // This covers: someVar.extend(...), createSearchParams(...).extend(...).pick(...), etc.\n if (/\\.(extend|pick)\\s*\\(/.test(normalized)) {\n // Reject ternaries and other conditional patterns\n if (/\\?/.test(normalized) && /:/.test(normalized)) {\n return false;\n }\n // Reject function declarations/expressions\n if (/^\\s*(function|=>|\\()/.test(normalized)) {\n return false;\n }\n return true;\n }\n\n return false;\n}\n\n/**\n * Format an AnalyzeError into a human-readable build error message.\n */\nexport function formatAnalyzeError(error: AnalyzeError): string {\n return [\n `[timber] Non-analyzable search-params.ts`,\n ``,\n ` File: ${error.filePath}`,\n ` Expression: ${error.expression}`,\n ``,\n ` ${error.suggestion}`,\n ``,\n ` The framework must be able to statically extract the type from your`,\n ` search-params.ts at build time. Dynamic values, conditionals, and`,\n ` arbitrary factory functions prevent this analysis.`,\n ].join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback error rendering — handles catastrophic errors that escape the
|
|
3
|
+
* render pipeline entirely (e.g. module evaluation failures).
|
|
4
|
+
*
|
|
5
|
+
* In dev mode: renders a styled HTML page with error details and stack trace.
|
|
6
|
+
* The Vite client script is included so the error overlay still fires.
|
|
7
|
+
*
|
|
8
|
+
* In production: attempts to render root error pages (500.tsx / 5xx.tsx /
|
|
9
|
+
* error.tsx) via the normal RSC → SSR pipeline. Stack traces are never
|
|
10
|
+
* exposed to the client (design/13-security.md principle 4).
|
|
11
|
+
*/
|
|
12
|
+
import type { ManifestSegmentNode } from '#/server/route-matcher.js';
|
|
13
|
+
import type { ClientBootstrapConfig } from '#/server/html-injectors.js';
|
|
14
|
+
/**
|
|
15
|
+
* Render a fallback error page when the render pipeline throws.
|
|
16
|
+
*
|
|
17
|
+
* In dev: styled HTML with error details.
|
|
18
|
+
* In prod: renders root error pages via renderErrorPage.
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderFallbackError(error: unknown, req: Request, responseHeaders: Headers, isDev: boolean, rootSegment: ManifestSegmentNode, clientBootstrap: ClientBootstrapConfig): Promise<Response>;
|
|
21
|
+
/**
|
|
22
|
+
* Render a dev-mode 500 error page with error message and stack trace.
|
|
23
|
+
*
|
|
24
|
+
* Returns an HTML Response that displays the error in a styled page.
|
|
25
|
+
* The Vite HMR client script is included so the error overlay still fires.
|
|
26
|
+
*/
|
|
27
|
+
export declare function renderDevErrorPage(error: unknown): Response;
|
|
28
|
+
//# sourceMappingURL=fallback-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback-error.d.ts","sourceRoot":"","sources":["../../src/server/fallback-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGxE;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,EACxB,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,mBAAmB,EAChC,eAAe,EAAE,qBAAqB,GACrC,OAAO,CAAC,QAAQ,CAAC,CA4BnB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAmF3D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html-injectors.d.ts","sourceRoot":"","sources":["../../src/server/html-injectors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiEH;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,QAAQ,EAAE,MAAM,GACf,cAAc,CAAC,UAAU,CAAC,CAE5B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,WAAW,EAAE,MAAM,GAClB,cAAc,CAAC,UAAU,CAAC,CAE5B;AAkBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GACpC,cAAc,CAAC,UAAU,CAAC,CA4B5B;AAyID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,cAAc,CAAC,UAAU,CAAC,EACtC,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,GAChD,cAAc,CAAC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"html-injectors.d.ts","sourceRoot":"","sources":["../../src/server/html-injectors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiEH;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,QAAQ,EAAE,MAAM,GACf,cAAc,CAAC,UAAU,CAAC,CAE5B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,WAAW,EAAE,MAAM,GAClB,cAAc,CAAC,UAAU,CAAC,CAE5B;AAkBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GACpC,cAAc,CAAC,UAAU,CAAC,CA4B5B;AAyID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,cAAc,CAAC,UAAU,CAAC,EACtC,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,GAChD,cAAc,CAAC,UAAU,CAAC,CAS5B;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;CACtB;AAqBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;IACjE,GAAG,EAAE,OAAO,CAAC;IACb,aAAa,CAAC,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC;CAC7D,GAAG,qBAAqB,CA8DxB"}
|
package/dist/server/index.js
CHANGED
|
@@ -365,6 +365,7 @@ function extractErrorHint(message) {
|
|
|
365
365
|
const notFnMatch = message.match(/(\w+) is not a function/);
|
|
366
366
|
if (notFnMatch) return `"${notFnMatch[1]}" is not a function — check imports and exports`;
|
|
367
367
|
if (message.includes("Element type is invalid")) return "A component resolved to undefined/null — check default exports and import paths";
|
|
368
|
+
if (message.includes("Invalid hook call")) return "A hook was called outside of a React component render. If this is a 'use client' component, ensure the directive is at the very top of the file (before any imports) and that @vitejs/plugin-rsc is loaded correctly. Barrel re-exports from non-'use client' files do not propagate the directive.";
|
|
368
369
|
return null;
|
|
369
370
|
}
|
|
370
371
|
/**
|
|
@@ -737,6 +738,9 @@ function createPipeline(config) {
|
|
|
737
738
|
});
|
|
738
739
|
await fireOnRequestError(error, req, "render");
|
|
739
740
|
if (onPipelineError && error instanceof Error) onPipelineError(error, "render");
|
|
741
|
+
if (config.renderFallbackError) try {
|
|
742
|
+
return await config.renderFallbackError(error, req, responseHeaders);
|
|
743
|
+
} catch {}
|
|
740
744
|
return new Response(null, { status: 500 });
|
|
741
745
|
}
|
|
742
746
|
}
|