@timber-js/app 0.2.0-alpha.164 → 0.2.0-alpha.166
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{actions-CSDD6x7U.js → actions-CDPfMp_I.js} +3 -3
- package/dist/_chunks/{actions-CSDD6x7U.js.map → actions-CDPfMp_I.js.map} +1 -1
- package/dist/_chunks/{cache-api-eb1gydM7.js → cache-api-DygSeKCB.js} +2 -2
- package/dist/_chunks/{cache-api-eb1gydM7.js.map → cache-api-DygSeKCB.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-mGfRbjh2.js → cli-schema-sync-EXGYPhI2.js} +3 -3
- package/dist/_chunks/{cli-schema-sync-mGfRbjh2.js.map → cli-schema-sync-EXGYPhI2.js.map} +1 -1
- package/dist/_chunks/{define-CFmvb4Bt.js → define-COtkxMRT.js} +16 -28
- package/dist/_chunks/define-COtkxMRT.js.map +1 -0
- package/dist/_chunks/{define-Bssfp6ot.js → define-c4au4I9R.js} +2 -2
- package/dist/_chunks/{define-Bssfp6ot.js.map → define-c4au4I9R.js.map} +1 -1
- package/dist/_chunks/{logger-B_O6-mdJ.js → logger-t3uxAmbX.js} +3 -3
- package/dist/_chunks/{logger-B_O6-mdJ.js.map → logger-t3uxAmbX.js.map} +1 -1
- package/dist/_chunks/{plugin-context-BnaiU_cF.js → plugin-context---kTF5v8.js} +2 -2
- package/dist/_chunks/{plugin-context-BnaiU_cF.js.map → plugin-context---kTF5v8.js.map} +1 -1
- package/dist/_chunks/{resolve-schema-3iUvBV5T.js → resolve-schema-Dz3fcFUo.js} +2 -2
- package/dist/_chunks/{resolve-schema-3iUvBV5T.js.map → resolve-schema-Dz3fcFUo.js.map} +1 -1
- package/dist/_chunks/{schema-bridge-BY3QLBL7.js → schema-bridge-DT_Tn0Xf.js} +2 -2
- package/dist/_chunks/{schema-bridge-BY3QLBL7.js.map → schema-bridge-DT_Tn0Xf.js.map} +1 -1
- package/dist/_chunks/{use-query-states-CbeQmext.js → use-query-states-DFvWd-EA.js} +65 -7
- package/dist/_chunks/use-query-states-DFvWd-EA.js.map +1 -0
- package/dist/_chunks/{walkers-BL3MCMgO.js → walkers-DBVzXuWc.js} +3 -3
- package/dist/_chunks/{walkers-BL3MCMgO.js.map → walkers-DBVzXuWc.js.map} +1 -1
- package/dist/adapters/nitro.d.ts +29 -0
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +57 -10
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/internal.js +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/codec.js +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/dev-tools/logs.d.ts +15 -1
- package/dist/dev-tools/logs.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +104 -20
- package/dist/index.js.map +1 -1
- package/dist/params/index.js +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/schema-bridge.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +35 -4
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +3 -7
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/wrappers.d.ts +2 -2
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/segment-params/index.js +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.js +8 -6
- package/dist/server/internal.js.map +1 -1
- package/dist/server/stream-utils.d.ts.map +1 -1
- package/docs/api/33-api-search-params.mdx +13 -0
- package/docs/learn/00-introduction.mdx +1 -2
- package/package.json +8 -7
- package/src/adapters/nitro.ts +106 -4
- package/src/cli.ts +0 -0
- package/src/client/use-query-states.ts +99 -7
- package/src/dev-tools/logs.ts +119 -34
- package/src/index.ts +14 -1
- package/src/schema-bridge.ts +4 -3
- package/src/search-params/define.ts +57 -51
- package/src/search-params/wrappers.ts +17 -26
- package/src/server/stream-utils.ts +8 -6
- package/LICENSE +0 -8
- package/dist/_chunks/define-CFmvb4Bt.js.map +0 -1
- package/dist/_chunks/use-query-states-CbeQmext.js.map +0 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { useQueryStates as clientUseQueryStates } from '../client/use-query-states.js';
|
|
14
|
-
import { fromSchema, isStandardSchema, isCodec
|
|
14
|
+
import { fromSchema, isStandardSchema, isCodec } from '../schema-bridge.js';
|
|
15
15
|
import type { StandardSchemaV1 } from '../schema-bridge.js';
|
|
16
16
|
import type { Codec } from '../codec.js';
|
|
17
17
|
|
|
@@ -165,9 +165,43 @@ export type { StandardSchemaV1 } from '../schema-bridge.js';
|
|
|
165
165
|
// Type-level helpers
|
|
166
166
|
// ---------------------------------------------------------------------------
|
|
167
167
|
|
|
168
|
-
/**
|
|
168
|
+
/**
|
|
169
|
+
* Extract a Standard Schema's declared *input* type from its optional
|
|
170
|
+
* `~standard.types` property (part of the Standard Schema spec; Zod, Valibot,
|
|
171
|
+
* and ArkType all declare it at the type level). Falls back to `never` when
|
|
172
|
+
* the schema doesn't declare types (e.g. hand-written schemas): with no
|
|
173
|
+
* metadata we can't know whether the schema handles `undefined`, so InferField
|
|
174
|
+
* widens conservatively rather than risk a type lie.
|
|
175
|
+
*/
|
|
176
|
+
type InferSchemaInput<V> = V extends { '~standard': { types?: infer TS } }
|
|
177
|
+
? [NonNullable<TS>] extends [{ input: infer I }]
|
|
178
|
+
? I
|
|
179
|
+
: never
|
|
180
|
+
: never;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Infer the output type from either a SearchParamCodec or a StandardSchemaV1.
|
|
184
|
+
*
|
|
185
|
+
* Schemas whose input type rejects `undefined` (e.g. bare `z.string()`, whose
|
|
186
|
+
* input is `string`) are implicitly optional: the URL might not contain the
|
|
187
|
+
* param, and fromSchema returns `undefined` when the schema rejects absent
|
|
188
|
+
* input and has no default. The output type widens to `T | undefined` so the
|
|
189
|
+
* type doesn't lie. Schemas that accept `undefined` input (`.optional()`,
|
|
190
|
+
* `.default()`) keep their declared output type.
|
|
191
|
+
*
|
|
192
|
+
* Limitation: `z.coerce.*` schemas declare input `unknown`, which accepts
|
|
193
|
+
* `undefined` at the type level — so a coerce schema without `.default()`
|
|
194
|
+
* keeps its narrow output type even though absent input yields `undefined`
|
|
195
|
+
* at runtime. Add `.default()` to coerce schemas for accurate types.
|
|
196
|
+
*/
|
|
169
197
|
export type InferField<V> =
|
|
170
|
-
V extends SearchParamCodec<infer T>
|
|
198
|
+
V extends SearchParamCodec<infer T>
|
|
199
|
+
? T
|
|
200
|
+
: V extends StandardSchemaV1<infer T>
|
|
201
|
+
? undefined extends InferSchemaInput<V>
|
|
202
|
+
? T
|
|
203
|
+
: T | undefined
|
|
204
|
+
: never;
|
|
171
205
|
|
|
172
206
|
/** Acceptable field value for defineSearchParams: a codec or a Standard Schema. */
|
|
173
207
|
export type SearchParamField<T = unknown> = SearchParamCodec<T> | StandardSchemaV1<T>;
|
|
@@ -198,9 +232,17 @@ function normalizeRaw(
|
|
|
198
232
|
* Compute the serialized default value for a codec. Used for
|
|
199
233
|
* default-omission: when serialize(value) === serialize(parse(undefined)),
|
|
200
234
|
* the field is omitted from the URL.
|
|
235
|
+
*
|
|
236
|
+
* Codecs are documented to return a default rather than throw, but a
|
|
237
|
+
* hand-written codec that throws on absent input must not turn definition
|
|
238
|
+
* into a crash — treat its default as null (nothing to omit).
|
|
201
239
|
*/
|
|
202
240
|
function getDefaultSerialized<T>(codec: SearchParamCodec<T>): string | null {
|
|
203
|
-
|
|
241
|
+
try {
|
|
242
|
+
return codec.serialize(codec.parse(undefined));
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
204
246
|
}
|
|
205
247
|
|
|
206
248
|
// isStandardSchema and isCodec are imported from schema-bridge.ts.
|
|
@@ -218,22 +260,11 @@ function resolveField(
|
|
|
218
260
|
return { codec: value, urlKey: value.urlKey };
|
|
219
261
|
}
|
|
220
262
|
|
|
221
|
-
// Auto-detect Standard Schema
|
|
263
|
+
// Auto-detect Standard Schema. Schemas that reject undefined input and
|
|
264
|
+
// have no default are implicitly optional: fromSchema returns undefined
|
|
265
|
+
// for absent params, and InferField widens the output type to
|
|
266
|
+
// T | undefined. design/23-search-params.md §"Implicit Optionality"
|
|
222
267
|
if (isStandardSchema(value)) {
|
|
223
|
-
// Validate that the schema handles absent params (undefined input).
|
|
224
|
-
// Search params are optional — the URL might not contain the param.
|
|
225
|
-
// If the schema rejects undefined and has no default, the codec would
|
|
226
|
-
// return `undefined as T`, making the type lie. Catch this at definition
|
|
227
|
-
// time instead. design/23-search-params.md §"Default Validation"
|
|
228
|
-
const absentCheck = validateSync(value, undefined);
|
|
229
|
-
if (absentCheck.issues) {
|
|
230
|
-
throw new Error(
|
|
231
|
-
`[timber] defineSearchParams: field '${fieldName}' has no default value and rejects undefined input.\n` +
|
|
232
|
-
` Search params are optional — the URL might not contain ?${fieldName}=anything.\n` +
|
|
233
|
-
` Add .default() to your schema: z.coerce.number().int().min(1).default(1)\n` +
|
|
234
|
-
` Or use .optional() if the field is truly optional.`
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
268
|
return { codec: fromSchema(value) };
|
|
238
269
|
}
|
|
239
270
|
|
|
@@ -244,25 +275,6 @@ function resolveField(
|
|
|
244
275
|
);
|
|
245
276
|
}
|
|
246
277
|
|
|
247
|
-
/**
|
|
248
|
-
* Validate that all codecs handle absent params (parse(undefined) doesn't throw).
|
|
249
|
-
* Catches schemas that throw on missing input. `undefined` and `null` are both
|
|
250
|
-
* valid defaults — `undefined` is correct for optional fields (e.g., `z.string().optional()`).
|
|
251
|
-
*/
|
|
252
|
-
function validateDefaults(codecMap: Record<string, SearchParamCodec<unknown>>): void {
|
|
253
|
-
for (const [key, codec] of Object.entries(codecMap)) {
|
|
254
|
-
try {
|
|
255
|
-
codec.parse(undefined);
|
|
256
|
-
} catch {
|
|
257
|
-
throw new Error(
|
|
258
|
-
`[timber] defineSearchParams: field '${key}' throws when the param is absent.\n` +
|
|
259
|
-
` Search params are optional — the URL might not contain ?${key}=anything.\n` +
|
|
260
|
-
` Add .default() or .optional() to your schema, or wrap with withDefault().`
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
278
|
// ---------------------------------------------------------------------------
|
|
267
279
|
// Factory
|
|
268
280
|
// ---------------------------------------------------------------------------
|
|
@@ -297,9 +309,13 @@ function validateDefaults(codecMap: Record<string, SearchParamCodec<unknown>>):
|
|
|
297
309
|
* )
|
|
298
310
|
* ```
|
|
299
311
|
*/
|
|
300
|
-
export function defineSearchParams<
|
|
301
|
-
|
|
302
|
-
|
|
312
|
+
export function defineSearchParams<
|
|
313
|
+
S extends StandardSchemaV1<Record<string, unknown>> & {
|
|
314
|
+
shape: Record<string, StandardSchemaV1<unknown>>;
|
|
315
|
+
},
|
|
316
|
+
>(
|
|
317
|
+
schema: S
|
|
318
|
+
): SearchParamsDefinition<{ [K in keyof S['shape'] & string]: InferField<S['shape'][K]> }>;
|
|
303
319
|
|
|
304
320
|
/**
|
|
305
321
|
* Overload: accept a map of codecs and/or Standard Schema objects.
|
|
@@ -357,9 +373,6 @@ function defineSearchParamsFromMap(
|
|
|
357
373
|
}
|
|
358
374
|
}
|
|
359
375
|
|
|
360
|
-
// Validate that all codecs handle absent params
|
|
361
|
-
validateDefaults(resolvedCodecs);
|
|
362
|
-
|
|
363
376
|
return buildDefinition(resolvedCodecs as unknown as CodecMap<Record<string, unknown>>, urlKeys);
|
|
364
377
|
}
|
|
365
378
|
|
|
@@ -484,11 +497,6 @@ function buildDefinition<T extends Record<string, unknown>>(
|
|
|
484
497
|
// Merge URL keys: base keys + new codec urlKeys from withUrlKey
|
|
485
498
|
const combinedUrlKeys: Record<string, string> = { ...urlKeys, ...newUrlKeys };
|
|
486
499
|
|
|
487
|
-
// Same definition-time guard as defineSearchParams — a throw-on-absent
|
|
488
|
-
// codec added via extend() gets the friendly guidance error, not a
|
|
489
|
-
// crash on first parse (TIM-1066).
|
|
490
|
-
validateDefaults(combinedCodecs as Record<string, SearchParamCodec<unknown>>);
|
|
491
|
-
|
|
492
500
|
return buildDefinition<Combined>(combinedCodecs, combinedUrlKeys);
|
|
493
501
|
}
|
|
494
502
|
|
|
@@ -515,8 +523,6 @@ function buildDefinition<T extends Record<string, unknown>>(
|
|
|
515
523
|
}
|
|
516
524
|
}
|
|
517
525
|
|
|
518
|
-
validateDefaults(pickedCodecs);
|
|
519
|
-
|
|
520
526
|
return buildDefinition<Pick<T, K>>(
|
|
521
527
|
pickedCodecs as unknown as CodecMap<Pick<T, K>>,
|
|
522
528
|
pickedUrlKeys
|
|
@@ -8,24 +8,13 @@
|
|
|
8
8
|
* Design doc: design/23-search-params.md
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
): SearchParamCodec<T> {
|
|
19
|
-
const absentCheck = validateSync(schema, undefined);
|
|
20
|
-
if (absentCheck.issues) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
`[timber] withUrlKey: schema for '${fieldHint}' has no default value and rejects undefined input.\n` +
|
|
23
|
-
` Search params are optional — the URL might not contain ?${fieldHint}=anything.\n` +
|
|
24
|
-
` Add .default() to your schema or use .optional().`
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
return fromSchema(schema);
|
|
28
|
-
}
|
|
11
|
+
import type {
|
|
12
|
+
InferField,
|
|
13
|
+
SearchParamCodec,
|
|
14
|
+
SearchParamCodecWithUrlKey,
|
|
15
|
+
SearchParamField,
|
|
16
|
+
} from './define.js';
|
|
17
|
+
import { isCodec, isStandardSchema, fromSchema } from '../schema-bridge.js';
|
|
29
18
|
|
|
30
19
|
// ---------------------------------------------------------------------------
|
|
31
20
|
// withDefault
|
|
@@ -90,17 +79,19 @@ export function withDefault<T>(
|
|
|
90
79
|
* withUrlKey(withDefault(parseAsInteger, 1), 'p')
|
|
91
80
|
* ```
|
|
92
81
|
*/
|
|
93
|
-
export function withUrlKey<
|
|
94
|
-
codecOrSchema:
|
|
82
|
+
export function withUrlKey<F extends SearchParamField>(
|
|
83
|
+
codecOrSchema: F,
|
|
95
84
|
urlKey: string
|
|
96
|
-
): SearchParamCodecWithUrlKey<
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
//
|
|
85
|
+
): SearchParamCodecWithUrlKey<InferField<F>> {
|
|
86
|
+
type T = InferField<F>;
|
|
87
|
+
// Auto-detect Standard Schema (Zod, Valibot, ArkType) and wrap. Schemas
|
|
88
|
+
// that reject undefined input are implicitly optional — fromSchema returns
|
|
89
|
+
// undefined for absent params, and InferField widens the type to include
|
|
90
|
+
// undefined. design/23-search-params.md §"Implicit Optionality"
|
|
100
91
|
const codec: SearchParamCodec<T> = isCodec(codecOrSchema)
|
|
101
|
-
? codecOrSchema
|
|
92
|
+
? (codecOrSchema as SearchParamCodec<T>)
|
|
102
93
|
: isStandardSchema(codecOrSchema)
|
|
103
|
-
?
|
|
94
|
+
? (fromSchema(codecOrSchema) as SearchParamCodec<T>)
|
|
104
95
|
: (codecOrSchema as SearchParamCodec<T>);
|
|
105
96
|
return {
|
|
106
97
|
parse: codec.parse.bind(codec),
|
|
@@ -228,12 +228,14 @@ export function wrapResponseWithStreamTiming(
|
|
|
228
228
|
let cancelled = false;
|
|
229
229
|
|
|
230
230
|
function emitLog(): void {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
`
|
|
235
|
-
|
|
236
|
-
|
|
231
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
232
|
+
const closeMs = Math.round(performance.now() - startTime);
|
|
233
|
+
console.debug(
|
|
234
|
+
`[timber] stream-lifecycle: firstChunk=${firstChunkMs}ms lastChunk=${lastChunkMs}ms ` +
|
|
235
|
+
`close=${closeMs}ms maxGap=${maxGapMs}ms chunks=${chunks} bytes=${bytes}` +
|
|
236
|
+
(cancelled ? ' cancelled=true' : '')
|
|
237
|
+
);
|
|
238
|
+
}
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
const reader = body.getReader();
|
package/LICENSE
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
DONTFUCKINGUSE LICENSE
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Daniel Saewitz
|
|
4
|
-
|
|
5
|
-
This software may not be used, copied, modified, merged, published,
|
|
6
|
-
distributed, sublicensed, or sold by anyone other than the copyright holder.
|
|
7
|
-
|
|
8
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"define-CFmvb4Bt.js","names":[],"sources":["../../src/search-params/define.ts"],"sourcesContent":["/**\n * defineSearchParams — factory for SearchParamsDefinition<T>.\n *\n * Creates a typed, composable definition for a route's search parameters.\n * Accepts both SearchParamCodec values and Standard Schema objects (Zod,\n * Valibot, ArkType) with auto-detection. Supports URL key aliasing via\n * withUrlKey(), default-omission serialization, and composition via\n * .extend() / .pick().\n *\n * Design doc: design/23-search-params.md §\"defineSearchParams — The Factory\"\n */\n\nimport { useQueryStates as clientUseQueryStates } from '../client/use-query-states.js';\nimport { fromSchema, isStandardSchema, isCodec, validateSync } from '../schema-bridge.js';\nimport type { StandardSchemaV1 } from '../schema-bridge.js';\nimport type { Codec } from '../codec.js';\n\n// Server-only reference for .get() — avoids pulling server ALS into client bundles.\n// In client environments, .get() throws before reaching this code path.\n//\n// IMPORTANT: This is set eagerly via _setGetSearchParamsFn() at server startup\n// (called from request-context.ts module initialization). It must NOT use\n// dynamic `await import()` at call time because the async microtask from the\n// dynamic import loses AsyncLocalStorage context in React's RSC Flight renderer,\n// breaking getSearchParams() in parallel slot pages. See TIM-523.\nlet _getSearchParamsFn: (() => URLSearchParams) | undefined;\n\n/**\n * Register the getSearchParams function. Called once at module load time\n * from request-context.ts to avoid dynamic import at call time.\n * @internal\n */\nexport function _setGetSearchParamsFn(fn: () => URLSearchParams): void {\n _getSearchParamsFn = fn;\n}\n\nfunction getSearchParamsFromAls(): URLSearchParams {\n if (!_getSearchParamsFn) {\n throw new Error(\n '[timber] searchParams.get() is only available on the server. ' +\n 'Use searchParams.useQueryStates() on the client.'\n );\n }\n return _getSearchParamsFn();\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A codec that converts between URL string values and typed values.\n *\n * nuqs parsers implement this interface natively — no adapter needed.\n * Standard Schema objects (Zod, Valibot, ArkType) are auto-detected\n * by defineSearchParams and wrapped via fromSchema.\n */\nexport interface SearchParamCodec<T> extends Codec<T> {\n /** Optional URL key alias, set by withUrlKey(). */\n urlKey?: string;\n}\n\n/** A codec with a URL key alias attached via withUrlKey(). */\nexport interface SearchParamCodecWithUrlKey<T> extends SearchParamCodec<T> {\n urlKey: string;\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/**\n * A fully typed, composable search params definition.\n *\n * Returned by defineSearchParams(). 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 /** Parse a Promise of URLSearchParams (e.g., from the ALS `searchParams()` API). */\n parse(raw: Promise<URLSearchParams | Record<string, string | string[] | undefined>>): Promise<T>;\n\n /**\n * Get typed search params from the current request context (ALS-backed).\n *\n * Server-only, sync. Reads getSearchParams() from ALS and parses through codecs.\n * Throws on client.\n *\n * ```tsx\n * // app/products/page.tsx\n * import { searchParams } from './params'\n * export default function Page() {\n * const { page, category } = searchParams.get()\n * }\n * ```\n */\n get(): 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 or Standard Schema objects. */\n extend<U extends Record<string, SearchParamCodec<unknown> | StandardSchemaV1<unknown>>>(\n codecs: U\n ): SearchParamsDefinition<T & { [K in keyof U]: InferField<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(). */\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// StandardSchemaV1 is imported from schema-bridge.ts — single source of truth.\n// Re-export for consumers that import it from this module.\nexport type { StandardSchemaV1 } from '../schema-bridge.js';\n\n// ---------------------------------------------------------------------------\n// Type-level helpers\n// ---------------------------------------------------------------------------\n\n/** Infer the output type from either a SearchParamCodec or a StandardSchemaV1. */\nexport type InferField<V> =\n V extends SearchParamCodec<infer T> ? T : V extends StandardSchemaV1<infer T> ? T : never;\n\n/** Acceptable field value for defineSearchParams: a codec or a Standard Schema. */\nexport type SearchParamField<T = unknown> = SearchParamCodec<T> | StandardSchemaV1<T>;\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> = Object.create(null);\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// isStandardSchema and isCodec are imported from schema-bridge.ts.\n\n/**\n * Resolve a field value to a SearchParamCodec. Auto-detects Standard Schema\n * objects and wraps them with fromSchema. Reads .urlKey from codecs.\n */\nfunction resolveField(\n fieldName: string,\n value: SearchParamField\n): { codec: SearchParamCodec<unknown>; urlKey?: string } {\n // Check for codec first (codecs may also have '~standard' if they're nuqs parsers)\n if (isCodec(value)) {\n return { codec: value, urlKey: value.urlKey };\n }\n\n // Auto-detect Standard Schema\n if (isStandardSchema(value)) {\n // Validate that the schema handles absent params (undefined input).\n // Search params are optional — the URL might not contain the param.\n // If the schema rejects undefined and has no default, the codec would\n // return `undefined as T`, making the type lie. Catch this at definition\n // time instead. design/23-search-params.md §\"Default Validation\"\n const absentCheck = validateSync(value, undefined);\n if (absentCheck.issues) {\n throw new Error(\n `[timber] defineSearchParams: field '${fieldName}' has no default value and rejects undefined input.\\n` +\n ` Search params are optional — the URL might not contain ?${fieldName}=anything.\\n` +\n ` Add .default() to your schema: z.coerce.number().int().min(1).default(1)\\n` +\n ` Or use .optional() if the field is truly optional.`\n );\n }\n return { codec: fromSchema(value) };\n }\n\n throw new Error(\n `[timber] defineSearchParams: 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 all codecs handle absent params (parse(undefined) doesn't throw).\n * Catches schemas that throw on missing input. `undefined` and `null` are both\n * valid defaults — `undefined` is correct for optional fields (e.g., `z.string().optional()`).\n */\nfunction validateDefaults(codecMap: Record<string, SearchParamCodec<unknown>>): void {\n for (const [key, codec] of Object.entries(codecMap)) {\n try {\n codec.parse(undefined);\n } catch {\n throw new Error(\n `[timber] defineSearchParams: field '${key}' throws when the param is absent.\\n` +\n ` Search params are optional — the URL might not contain ?${key}=anything.\\n` +\n ` Add .default() or .optional() to your schema, or wrap with withDefault().`\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a SearchParamsDefinition from a map of codecs and/or Standard Schema\n * objects. Accepts both SearchParamCodec values and raw Zod/Valibot/ArkType\n * schemas with auto-detection.\n *\n * ```ts\n * import { defineSearchParams, withDefault, withUrlKey } from '@timber-js/app/search-params'\n * import { parseAsString, parseAsStringEnum } from 'nuqs'\n * import { z } from 'zod/v4'\n *\n * export const searchParams = defineSearchParams({\n * page: z.coerce.number().int().min(1).default(1), // Standard Schema — auto-wrapped\n * q: withUrlKey(parseAsString, 'search'), // nuqs codec with URL alias\n * sort: withDefault(parseAsStringEnum(['price', 'name']), 'price'),\n * })\n * ```\n */\n/**\n * Overload: accept a Standard Schema object schema (e.g., z.object({...})).\n *\n * The schema must have a `.shape` property whose values are themselves\n * Standard Schema objects. Each shape property becomes a field codec\n * via fromSchema().\n *\n * ```ts\n * const searchParams = defineSearchParams(\n * z.object({ page: z.coerce.number().default(1), q: z.string().optional() })\n * )\n * ```\n */\nexport function defineSearchParams<T extends Record<string, unknown>>(\n schema: StandardSchemaV1<T> & { shape: Record<string, StandardSchemaV1<unknown>> }\n): SearchParamsDefinition<T>;\n\n/**\n * Overload: accept a map of codecs and/or Standard Schema objects.\n */\nexport function defineSearchParams<C extends Record<string, SearchParamField>>(\n codecs: C\n): SearchParamsDefinition<{ [K in keyof C]: InferField<C[K]> }>;\n\nexport function defineSearchParams(\n codecsOrSchema:\n | Record<string, SearchParamField>\n | (StandardSchemaV1<unknown> & { shape: Record<string, StandardSchemaV1<unknown>> })\n): SearchParamsDefinition<Record<string, unknown>> {\n // Detect Standard Schema object with .shape (e.g., z.object(...))\n if (isStandardSchema(codecsOrSchema) && hasShape(codecsOrSchema)) {\n const fieldCodecs: Record<string, SearchParamField> = {};\n for (const [key, fieldSchema] of Object.entries(codecsOrSchema.shape)) {\n if (isStandardSchema(fieldSchema)) {\n fieldCodecs[key] = fieldSchema;\n } else {\n throw new Error(\n `[timber] defineSearchParams: field '${key}' in schema.shape is not a Standard Schema. ` +\n `All shape properties must be Standard Schema objects (Zod, Valibot, ArkType).`\n );\n }\n }\n return defineSearchParamsFromMap(fieldCodecs);\n }\n\n return defineSearchParamsFromMap(codecsOrSchema as Record<string, SearchParamField>);\n}\n\n/** Check if a schema has a .shape property with object-type values. */\nfunction hasShape(schema: unknown): schema is { shape: Record<string, unknown> } {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n 'shape' in schema &&\n typeof (schema as { shape: unknown }).shape === 'object' &&\n (schema as { shape: unknown }).shape !== null\n );\n}\n\nfunction defineSearchParamsFromMap(\n codecs: Record<string, SearchParamField>\n): SearchParamsDefinition<Record<string, unknown>> {\n const resolvedCodecs: Record<string, SearchParamCodec<unknown>> = {};\n const urlKeys: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(codecs)) {\n const resolved = resolveField(key, value as SearchParamField);\n resolvedCodecs[key] = resolved.codec;\n if (resolved.urlKey) {\n urlKeys[key] = resolved.urlKey;\n }\n }\n\n // Validate that all codecs handle absent params\n validateDefaults(resolvedCodecs);\n\n return buildDefinition(resolvedCodecs as unknown as CodecMap<Record<string, unknown>>, urlKeys);\n}\n\n// ---------------------------------------------------------------------------\n// Internal: build the definition object\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 parseSync(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 // Overloaded parse: sync when given raw params, async when given a Promise.\n // This enables the ergonomic pattern: await def.parse(searchParams())\n function parse(raw: URLSearchParams | Record<string, string | string[] | undefined>): T;\n function parse(\n raw: Promise<URLSearchParams | Record<string, string | string[] | undefined>>\n ): Promise<T>;\n function parse(\n raw:\n | URLSearchParams\n | Record<string, string | string[] | undefined>\n | Promise<URLSearchParams | Record<string, string | string[] | undefined>>\n ): T | Promise<T> {\n if (raw instanceof Promise) {\n return raw.then(parseSync);\n }\n return parseSync(raw);\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> | StandardSchemaV1<unknown>>>(\n newCodecs: U\n ): SearchParamsDefinition<T & { [K in keyof U]: InferField<U[K]> }> {\n type Combined = T & { [K in keyof U]: InferField<U[K]> };\n\n // Resolve any Standard Schema objects in the extension\n const resolvedNewCodecs: Record<string, SearchParamCodec<unknown>> = {};\n const newUrlKeys: Record<string, string> = {};\n for (const [key, value] of Object.entries(newCodecs)) {\n const resolved = resolveField(key, value as SearchParamField);\n resolvedNewCodecs[key] = resolved.codec;\n if (resolved.urlKey) {\n newUrlKeys[key] = resolved.urlKey;\n }\n }\n\n const combinedCodecs = {\n ...codecMap,\n ...resolvedNewCodecs,\n } as unknown as CodecMap<Combined>;\n\n // Merge URL keys: base keys + new codec urlKeys from withUrlKey\n const combinedUrlKeys: Record<string, string> = { ...urlKeys, ...newUrlKeys };\n\n // Same definition-time guard as defineSearchParams — a throw-on-absent\n // codec added via extend() gets the friendly guidance error, not a\n // crash on first parse (TIM-1066).\n validateDefaults(combinedCodecs as Record<string, SearchParamCodec<unknown>>);\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 // Explicit guard — TypeScript prevents this, but JS callers and\n // casts reach here, and \"Cannot read properties of undefined\n // (reading 'serialize')\" is no help (TIM-1066).\n if (!(key in codecMap)) {\n throw new Error(\n `[timber] pick('${key}'): unknown key. ` +\n `Available keys: ${Object.keys(codecMap)\n .map((k) => `'${k}'`)\n .join(', ')}.`\n );\n }\n pickedCodecs[key] = codecMap[key] as SearchParamCodec<unknown>;\n if (key in urlKeys) {\n pickedUrlKeys[key] = urlKeys[key];\n }\n }\n\n validateDefaults(pickedCodecs);\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 // ---- get ----\n // ALS-backed: reads getSearchParams() from the current request context\n // and parses through codecs. Server-only, sync.\n function get(): T {\n if (typeof window !== 'undefined') {\n throw new Error(\n '[timber] searchParams.get() is server-only. ' +\n 'Use searchParams.useQueryStates() on the client.'\n );\n }\n const raw = getSearchParamsFromAls();\n return parseSync(raw);\n }\n\n const definition: SearchParamsDefinition<T> = {\n parse,\n get,\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"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"use-query-states-CbeQmext.js","names":[],"sources":["../../src/client/use-query-states.ts"],"sourcesContent":["/**\n * useQueryStates — client-side hook for URL-synced search params.\n *\n * Delegates to nuqs for URL synchronization, batching, React 19 transitions,\n * and throttled URL writes. Bridges timber's SearchParamCodec protocol to\n * nuqs-compatible parsers.\n *\n * Design doc: design/23-search-params.md §\"Codec Bridge\"\n */\n\n'use client';\n\nimport { useQueryStates as nuqsUseQueryStates } from 'nuqs';\nimport type { SingleParser } from 'nuqs';\nimport type {\n SearchParamCodec,\n SearchParamsDefinition,\n SetParams,\n QueryStatesOptions,\n} from '../search-params/define.js';\nimport { getSearchParamsDefinition } from '../search-params/registry.js';\n\n// ─── Codec Bridge ─────────────────────────────────────────────────\n\n/**\n * Bridge a timber SearchParamCodec to a nuqs-compatible SingleParser.\n *\n * nuqs parsers: { parse(string) → T|null, serialize?(T) → string, eq?, defaultValue? }\n * timber codecs: { parse(string|string[]|undefined) → T, serialize(T) → string|null }\n */\nfunction bridgeCodec<T>(codec: SearchParamCodec<T>): SingleParser<T> & { defaultValue: T } {\n return {\n parse: (v: string) => codec.parse(v),\n serialize: (v: T) => codec.serialize(v) ?? '',\n defaultValue: codec.parse(undefined) as T,\n eq: (a: T, b: T) => codec.serialize(a) === codec.serialize(b),\n };\n}\n\n/**\n * Bridge an entire codec map to nuqs-compatible parsers.\n */\nfunction bridgeCodecs<T extends Record<string, unknown>>(codecs: {\n [K in keyof T]: SearchParamCodec<T[K]>;\n}) {\n const result: Record<string, SingleParser<unknown> & { defaultValue: unknown }> = {};\n for (const key of Object.keys(codecs)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n result[key] = bridgeCodec(codecs[key as keyof T]) as any;\n }\n return result as { [K in keyof T]: SingleParser<T[K]> & { defaultValue: T[K] } };\n}\n\n// ─── Hook ─────────────────────────────────────────────────────────\n\n/**\n * Read and write typed search params from/to the URL.\n *\n * Delegates to nuqs internally. The timber nuqs adapter (auto-injected in\n * browser-entry.ts) handles RSC navigation on non-shallow updates.\n *\n * Usage:\n * ```ts\n * // Via a SearchParamsDefinition\n * const [params, setParams] = definition.useQueryStates()\n *\n * // Standalone with inline codecs\n * const [params, setParams] = useQueryStates({\n * page: fromSchema(z.coerce.number().int().min(1).default(1)),\n * })\n * ```\n */\nexport function useQueryStates<T extends Record<string, unknown>>(\n codecsOrRoute: { [K in keyof T]: SearchParamCodec<T[K]> } | string,\n _options?: QueryStatesOptions,\n urlKeys?: Readonly<Record<string, string>>\n): [T, SetParams<T>] {\n // Route-string overload: resolve codecs from the registry\n let codecs: { [K in keyof T]: SearchParamCodec<T[K]> };\n let resolvedUrlKeys = urlKeys;\n if (typeof codecsOrRoute === 'string') {\n const definition = getSearchParamsDefinition(codecsOrRoute);\n if (!definition) {\n throw new Error(\n `useQueryStates('${codecsOrRoute}'): no search params registered for this route. ` +\n `Either the route has no search-params.ts file, or it hasn't been loaded yet. ` +\n `For cross-route usage, import the definition explicitly.`\n );\n }\n codecs = definition.codecs as { [K in keyof T]: SearchParamCodec<T[K]> };\n resolvedUrlKeys = definition.urlKeys;\n } else {\n codecs = codecsOrRoute;\n }\n\n const bridged = bridgeCodecs(codecs);\n\n // Forward hook-level options (shallow, scroll, history) to nuqs.\n // These become the default for all setter calls from this hook instance.\n // Per-call options in setParams(values, opts) override these defaults.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const nuqsOptions: any = {};\n if (_options?.shallow !== undefined) nuqsOptions.shallow = _options.shallow;\n if (_options?.scroll !== undefined) nuqsOptions.scroll = _options.scroll;\n if (_options?.history !== undefined) nuqsOptions.history = _options.history;\n if (resolvedUrlKeys && Object.keys(resolvedUrlKeys).length > 0) {\n nuqsOptions.urlKeys = resolvedUrlKeys;\n }\n\n let values: Record<string, unknown>;\n let setValues: Function;\n try {\n [values, setValues] = nuqsUseQueryStates(bridged, nuqsOptions);\n } catch (err) {\n if (\n err instanceof Error &&\n /Invalid hook call|cannot be called|Cannot read properties of null/i.test(err.message)\n ) {\n throw new Error(\n 'useQueryStates is a client component hook and cannot be called outside a React component. ' +\n 'Use definition.parse(searchParams) in server components instead.'\n );\n }\n throw err;\n }\n\n // Wrap the nuqs setter to match timber's SetParams<T> signature.\n // nuqs's setter accepts Partial<Nullable<Values>> | UpdaterFn | null.\n // timber's setter accepts Partial<T> with optional SetParamsOptions.\n const setParams: SetParams<T> = (partial, setOptions?) => {\n const nuqsSetOptions: Record<string, unknown> = {};\n if (setOptions?.shallow !== undefined) nuqsSetOptions.shallow = setOptions.shallow;\n if (setOptions?.scroll !== undefined) nuqsSetOptions.scroll = setOptions.scroll;\n if (setOptions?.history !== undefined) nuqsSetOptions.history = setOptions.history;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n void setValues(partial as any, nuqsSetOptions);\n };\n\n return [values as T, setParams];\n}\n\n// ─── Definition binding ───────────────────────────────────────────\n\n/**\n * Create a useQueryStates binding for a SearchParamsDefinition.\n * This is used internally by SearchParamsDefinition.useQueryStates().\n */\nexport function bindUseQueryStates<T extends Record<string, unknown>>(\n definition: SearchParamsDefinition<T>\n): (options?: QueryStatesOptions) => [T, SetParams<T>] {\n return (options?: QueryStatesOptions) => {\n return useQueryStates<T>(definition.codecs, options, definition.urlKeys);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA8BA,SAAS,YAAe,OAAmE;CACzF,OAAO;EACL,QAAQ,MAAc,MAAM,MAAM,CAAC;EACnC,YAAY,MAAS,MAAM,UAAU,CAAC,KAAK;EAC3C,cAAc,MAAM,MAAM,KAAA,CAAS;EACnC,KAAK,GAAM,MAAS,MAAM,UAAU,CAAC,MAAM,MAAM,UAAU,CAAC;CAC9D;AACF;;;;AAKA,SAAS,aAAgD,QAEtD;CACD,MAAM,SAA4E,CAAC;CACnF,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAElC,OAAO,OAAO,YAAY,OAAO,IAAe;CAElD,OAAO;AACT;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,iBACd,eACA,UACA,SACmB;CAEnB,IAAI;CACJ,IAAI,kBAAkB;CACtB,IAAI,OAAO,kBAAkB,UAAU;EACrC,MAAM,aAAa,0BAA0B,aAAa;EAC1D,IAAI,CAAC,YACH,MAAM,IAAI,MACR,mBAAmB,cAAc,sLAGnC;EAEF,SAAS,WAAW;EACpB,kBAAkB,WAAW;CAC/B,OACE,SAAS;CAGX,MAAM,UAAU,aAAa,MAAM;CAMnC,MAAM,cAAmB,CAAC;CAC1B,IAAI,UAAU,YAAY,KAAA,GAAW,YAAY,UAAU,SAAS;CACpE,IAAI,UAAU,WAAW,KAAA,GAAW,YAAY,SAAS,SAAS;CAClE,IAAI,UAAU,YAAY,KAAA,GAAW,YAAY,UAAU,SAAS;CACpE,IAAI,mBAAmB,OAAO,KAAK,eAAe,CAAC,CAAC,SAAS,GAC3D,YAAY,UAAU;CAGxB,IAAI;CACJ,IAAI;CACJ,IAAI;EACF,CAAC,QAAQ,aAAa,eAAmB,SAAS,WAAW;CAC/D,SAAS,KAAK;EACZ,IACE,eAAe,SACf,qEAAqE,KAAK,IAAI,OAAO,GAErF,MAAM,IAAI,MACR,4JAEF;EAEF,MAAM;CACR;CAKA,MAAM,aAA2B,SAAS,eAAgB;EACxD,MAAM,iBAA0C,CAAC;EACjD,IAAI,YAAY,YAAY,KAAA,GAAW,eAAe,UAAU,WAAW;EAC3E,IAAI,YAAY,WAAW,KAAA,GAAW,eAAe,SAAS,WAAW;EACzE,IAAI,YAAY,YAAY,KAAA,GAAW,eAAe,UAAU,WAAW;EAE3E,UAAe,SAAgB,cAAc;CAC/C;CAEA,OAAO,CAAC,QAAa,SAAS;AAChC;;;;;AAQA,SAAgB,mBACd,YACqD;CACrD,QAAQ,YAAiC;EACvC,OAAO,iBAAkB,WAAW,QAAQ,SAAS,WAAW,OAAO;CACzE;AACF"}
|