@timber-js/app 0.1.10 → 0.1.12
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/ssr-data-BgSwMbN9.js +38 -0
- package/dist/_chunks/ssr-data-BgSwMbN9.js.map +1 -0
- package/dist/_chunks/{use-cookie-HcvNlW4L.js → use-cookie-D2cZu0jK.js} +3 -37
- package/dist/_chunks/use-cookie-D2cZu0jK.js.map +1 -0
- package/dist/_chunks/use-query-states-wEXY2JQB.js +109 -0
- package/dist/_chunks/use-query-states-wEXY2JQB.js.map +1 -0
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +8 -0
- package/dist/client/error-boundary.js.map +1 -1
- package/dist/client/index.js +3 -84
- package/dist/client/index.js.map +1 -1
- package/dist/client/ssr-data.d.ts +9 -0
- package/dist/client/ssr-data.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -12
- 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/plugins/shims.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 +13 -10
- 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 +7 -0
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +10 -0
- 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/error-boundary.tsx +22 -0
- package/src/client/ssr-data.ts +7 -0
- 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/plugins/shims.ts +8 -14
- 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 +98 -39
- package/src/server/slot-resolver.ts +38 -5
- package/src/server/ssr-entry.ts +12 -1
- package/src/server/tree-builder.ts +39 -11
- 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
- package/dist/_chunks/use-cookie-HcvNlW4L.js.map +0 -1
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { SegmentNode, RouteFile } from '#/routing/types.js';
|
|
14
|
-
import { TimberErrorBoundary } from '#/client/error-boundary.js';
|
|
15
14
|
|
|
16
15
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
17
16
|
|
|
@@ -55,6 +54,16 @@ export interface TreeBuilderConfig {
|
|
|
55
54
|
loadModule: ModuleLoader;
|
|
56
55
|
/** React.createElement or equivalent. */
|
|
57
56
|
createElement: CreateElement;
|
|
57
|
+
/**
|
|
58
|
+
* Error boundary component for wrapping segments.
|
|
59
|
+
*
|
|
60
|
+
* This is injected by the caller rather than imported directly to avoid
|
|
61
|
+
* pulling 'use client' code into the server barrel (@timber-js/app/server).
|
|
62
|
+
* In the RSC environment, the RSC plugin transforms this import to a
|
|
63
|
+
* client reference proxy — the caller handles the import so the server
|
|
64
|
+
* barrel stays free of client dependencies.
|
|
65
|
+
*/
|
|
66
|
+
errorBoundaryComponent?: unknown;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
// ─── Component wrappers ──────────────────────────────────────────────────────
|
|
@@ -79,7 +88,10 @@ export interface AccessGateProps {
|
|
|
79
88
|
* - 'pass': render children
|
|
80
89
|
* - DenySignal/RedirectSignal: throw synchronously
|
|
81
90
|
*/
|
|
82
|
-
verdict?:
|
|
91
|
+
verdict?:
|
|
92
|
+
| 'pass'
|
|
93
|
+
| import('./primitives.js').DenySignal
|
|
94
|
+
| import('./primitives.js').RedirectSignal;
|
|
83
95
|
children: ReactElement;
|
|
84
96
|
}
|
|
85
97
|
|
|
@@ -131,7 +143,8 @@ export interface TreeBuildResult {
|
|
|
131
143
|
* Parallel slots are resolved at each layout level and composed as named props.
|
|
132
144
|
*/
|
|
133
145
|
export async function buildElementTree(config: TreeBuilderConfig): Promise<TreeBuildResult> {
|
|
134
|
-
const { segments, params, searchParams, loadModule, createElement } =
|
|
146
|
+
const { segments, params, searchParams, loadModule, createElement, errorBoundaryComponent } =
|
|
147
|
+
config;
|
|
135
148
|
|
|
136
149
|
if (segments.length === 0) {
|
|
137
150
|
throw new Error('[timber] buildElementTree: empty segment chain');
|
|
@@ -163,7 +176,13 @@ export async function buildElementTree(config: TreeBuilderConfig): Promise<TreeB
|
|
|
163
176
|
const segment = segments[i];
|
|
164
177
|
|
|
165
178
|
// Wrap in error boundaries (status-code files + error.tsx)
|
|
166
|
-
element = await wrapWithErrorBoundaries(
|
|
179
|
+
element = await wrapWithErrorBoundaries(
|
|
180
|
+
segment,
|
|
181
|
+
element,
|
|
182
|
+
loadModule,
|
|
183
|
+
createElement,
|
|
184
|
+
errorBoundaryComponent
|
|
185
|
+
);
|
|
167
186
|
|
|
168
187
|
// Wrap in AccessGate if segment has access.ts
|
|
169
188
|
if (segment.access) {
|
|
@@ -195,7 +214,8 @@ export async function buildElementTree(config: TreeBuilderConfig): Promise<TreeB
|
|
|
195
214
|
params,
|
|
196
215
|
searchParams,
|
|
197
216
|
loadModule,
|
|
198
|
-
createElement
|
|
217
|
+
createElement,
|
|
218
|
+
errorBoundaryComponent
|
|
199
219
|
);
|
|
200
220
|
}
|
|
201
221
|
}
|
|
@@ -226,7 +246,8 @@ async function buildSlotElement(
|
|
|
226
246
|
params: Record<string, string | string[]>,
|
|
227
247
|
searchParams: unknown,
|
|
228
248
|
loadModule: ModuleLoader,
|
|
229
|
-
createElement: CreateElement
|
|
249
|
+
createElement: CreateElement,
|
|
250
|
+
errorBoundaryComponent: unknown
|
|
230
251
|
): Promise<ReactElement> {
|
|
231
252
|
// Load slot page
|
|
232
253
|
const pageModule = slotNode.page ? await loadModule(slotNode.page) : null;
|
|
@@ -246,7 +267,13 @@ async function buildSlotElement(
|
|
|
246
267
|
let element: ReactElement = createElement(PageComponent, { params, searchParams });
|
|
247
268
|
|
|
248
269
|
// Wrap in error boundaries
|
|
249
|
-
element = await wrapWithErrorBoundaries(
|
|
270
|
+
element = await wrapWithErrorBoundaries(
|
|
271
|
+
slotNode,
|
|
272
|
+
element,
|
|
273
|
+
loadModule,
|
|
274
|
+
createElement,
|
|
275
|
+
errorBoundaryComponent
|
|
276
|
+
);
|
|
250
277
|
|
|
251
278
|
// Wrap in SlotAccessGate if slot has access.ts
|
|
252
279
|
if (slotNode.access) {
|
|
@@ -298,7 +325,8 @@ async function wrapWithErrorBoundaries(
|
|
|
298
325
|
segment: SegmentNode,
|
|
299
326
|
element: ReactElement,
|
|
300
327
|
loadModule: ModuleLoader,
|
|
301
|
-
createElement: CreateElement
|
|
328
|
+
createElement: CreateElement,
|
|
329
|
+
errorBoundaryComponent: unknown
|
|
302
330
|
): Promise<ReactElement> {
|
|
303
331
|
// Wrapping is applied inside-out. The last wrap call produces the outermost boundary.
|
|
304
332
|
// Order: specific status → category → error.tsx (outermost)
|
|
@@ -312,7 +340,7 @@ async function wrapWithErrorBoundaries(
|
|
|
312
340
|
const mod = await loadModule(file);
|
|
313
341
|
const Component = mod.default;
|
|
314
342
|
if (Component) {
|
|
315
|
-
element = createElement(
|
|
343
|
+
element = createElement(errorBoundaryComponent, {
|
|
316
344
|
fallbackComponent: Component,
|
|
317
345
|
status,
|
|
318
346
|
children: element,
|
|
@@ -328,7 +356,7 @@ async function wrapWithErrorBoundaries(
|
|
|
328
356
|
const mod = await loadModule(file);
|
|
329
357
|
const Component = mod.default;
|
|
330
358
|
if (Component) {
|
|
331
|
-
element = createElement(
|
|
359
|
+
element = createElement(errorBoundaryComponent, {
|
|
332
360
|
fallbackComponent: Component,
|
|
333
361
|
status: key === '4xx' ? 400 : 500, // category marker
|
|
334
362
|
children: element,
|
|
@@ -343,7 +371,7 @@ async function wrapWithErrorBoundaries(
|
|
|
343
371
|
const errorModule = await loadModule(segment.error);
|
|
344
372
|
const ErrorComponent = errorModule.default;
|
|
345
373
|
if (ErrorComponent) {
|
|
346
|
-
element = createElement(
|
|
374
|
+
element = createElement(errorBoundaryComponent, {
|
|
347
375
|
fallbackComponent: ErrorComponent,
|
|
348
376
|
children: element,
|
|
349
377
|
} satisfies ErrorBoundaryProps);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
//#region src/search-params/registry.ts
|
|
2
|
-
var registry = /* @__PURE__ */ new Map();
|
|
3
|
-
/**
|
|
4
|
-
* Register a route's search params definition.
|
|
5
|
-
* Called by the generated route manifest loader when a route's modules load.
|
|
6
|
-
*/
|
|
7
|
-
function registerSearchParams(route, definition) {
|
|
8
|
-
registry.set(route, definition);
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Look up a route's search params definition.
|
|
12
|
-
* Returns undefined if the route hasn't been loaded yet.
|
|
13
|
-
*/
|
|
14
|
-
function getSearchParams(route) {
|
|
15
|
-
return registry.get(route);
|
|
16
|
-
}
|
|
17
|
-
//#endregion
|
|
18
|
-
export { registerSearchParams as n, getSearchParams as t };
|
|
19
|
-
|
|
20
|
-
//# sourceMappingURL=registry-BfPM41ri.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry-BfPM41ri.js","names":[],"sources":["../../src/search-params/registry.ts"],"sourcesContent":["/**\n * Runtime registry for route-scoped search params definitions.\n *\n * When a route's modules load, the framework registers its search-params\n * definition here. useQueryStates('/route') resolves codecs from this map.\n *\n * Design doc: design/23-search-params.md §\"Runtime: Registration at Route Load\"\n */\n\nimport type { SearchParamsDefinition } from './create.js';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst registry = new Map<string, SearchParamsDefinition<any>>();\n\n/**\n * Register a route's search params definition.\n * Called by the generated route manifest loader when a route's modules load.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function registerSearchParams(route: string, definition: SearchParamsDefinition<any>): void {\n registry.set(route, definition);\n}\n\n/**\n * Look up a route's search params definition.\n * Returns undefined if the route hasn't been loaded yet.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function getSearchParams(route: string): SearchParamsDefinition<any> | undefined {\n return registry.get(route);\n}\n"],"mappings":";AAYA,IAAM,2BAAW,IAAI,KAA0C;;;;;AAO/D,SAAgB,qBAAqB,OAAe,YAA+C;AACjG,UAAS,IAAI,OAAO,WAAW;;;;;;AAQjC,SAAgB,gBAAgB,OAAwD;AACtF,QAAO,SAAS,IAAI,MAAM"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"use-cookie-HcvNlW4L.js","names":[],"sources":["../../src/client/ssr-data.ts","../../src/client/use-cookie.ts"],"sourcesContent":["/**\n * SSR Data — per-request state for client hooks during server-side rendering.\n *\n * RSC and SSR are separate Vite module graphs (see design/18-build-system.md),\n * so the RSC environment's request-context ALS is not visible to SSR modules.\n * This module provides getter/setter functions that ssr-entry.ts uses to\n * populate per-request data for React's render.\n *\n * Request isolation: On the server, ssr-entry.ts registers an ALS-backed\n * provider via registerSsrDataProvider(). getSsrData() reads from the ALS\n * store, ensuring correct per-request data even when Suspense boundaries\n * resolve asynchronously across concurrent requests. The module-level\n * setSsrData/clearSsrData functions are kept as a fallback for tests\n * and environments without ALS.\n *\n * IMPORTANT: This module must NOT import node:async_hooks or any Node.js-only\n * APIs, as it's imported by 'use client' hooks that are bundled for the browser.\n * The ALS instance lives in ssr-entry.ts (server-only); this module only holds\n * a reference to the provider function.\n */\n\n// ─── Types ────────────────────────────────────────────────────────\n\nexport interface SsrData {\n /** The request's URL pathname (e.g. '/dashboard/settings') */\n pathname: string;\n /** The request's search params as a plain record */\n searchParams: Record<string, string>;\n /** The request's cookies as name→value pairs */\n cookies: Map<string, string>;\n /** The request's route params (e.g. { id: '123' }) */\n params: Record<string, string | string[]>;\n}\n\n// ─── ALS-Backed Provider ─────────────────────────────────────────\n//\n// Server-side code (ssr-entry.ts) registers a provider that reads\n// from AsyncLocalStorage. This avoids importing node:async_hooks\n// in this browser-bundled module.\n\nlet _ssrDataProvider: (() => SsrData | undefined) | undefined;\n\n/**\n * Register an ALS-backed SSR data provider. Called once at module load\n * by ssr-entry.ts to wire up per-request data via AsyncLocalStorage.\n *\n * When registered, getSsrData() reads from the provider (ALS store)\n * instead of module-level state, ensuring correct isolation for\n * concurrent requests with streaming Suspense.\n */\nexport function registerSsrDataProvider(provider: () => SsrData | undefined): void {\n _ssrDataProvider = provider;\n}\n\n// ─── Module-Level Fallback ────────────────────────────────────────\n//\n// Used by tests and as a fallback when no ALS provider is registered.\n\nlet currentSsrData: SsrData | undefined;\n\n/**\n * Set the SSR data for the current request via module-level state.\n *\n * In production, ssr-entry.ts uses ALS (runWithSsrData) instead.\n * This function is retained for tests and as a fallback.\n */\nexport function setSsrData(data: SsrData): void {\n currentSsrData = data;\n}\n\n/**\n * Clear the SSR data after rendering completes.\n *\n * In production, ALS scope handles cleanup automatically.\n * This function is retained for tests and as a fallback.\n */\nexport function clearSsrData(): void {\n currentSsrData = undefined;\n}\n\n/**\n * Read the current request's SSR data. Returns undefined when called\n * outside an SSR render (i.e. on the client after hydration).\n *\n * Prefers the ALS-backed provider when registered (server-side),\n * falling back to module-level state (tests, legacy).\n *\n * Used by client hooks' server snapshot functions.\n */\nexport function getSsrData(): SsrData | undefined {\n if (_ssrDataProvider) {\n return _ssrDataProvider();\n }\n return currentSsrData;\n}\n","/**\n * useCookie — reactive client-side cookie hook.\n *\n * Uses useSyncExternalStore for SSR-safe, reactive cookie access.\n * All components reading the same cookie name re-render on change.\n * No cross-tab sync (intentional — see design/29-cookies.md).\n *\n * See design/29-cookies.md §\"useCookie(name) Hook\"\n */\n\nimport { useSyncExternalStore } from 'react';\nimport { getSsrData } from './ssr-data.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface ClientCookieOptions {\n /** URL path scope. Default: '/'. */\n path?: string;\n /** Domain scope. Default: omitted (current domain). */\n domain?: string;\n /** Max age in seconds. */\n maxAge?: number;\n /** Expiration date. */\n expires?: Date;\n /** Cross-site policy. Default: 'lax'. */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Only send over HTTPS. Default: true in production. */\n secure?: boolean;\n}\n\nexport type CookieSetter = (value: string, options?: ClientCookieOptions) => void;\n\n// ─── Module-Level Cookie Store ────────────────────────────────────────────\n\ntype Listener = () => void;\n\n/** Per-name subscriber sets. */\nconst listeners = new Map<string, Set<Listener>>();\n\n/** Parse a cookie name from document.cookie. */\nfunction getCookieValue(name: string): string | undefined {\n if (typeof document === 'undefined') return undefined;\n const match = document.cookie.match(\n new RegExp('(?:^|;\\\\s*)' + name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + '\\\\s*=\\\\s*([^;]*)')\n );\n return match ? decodeURIComponent(match[1]) : undefined;\n}\n\n/** Serialize options into a cookie string suffix. */\nfunction serializeOptions(options?: ClientCookieOptions): string {\n if (!options) return '; Path=/; SameSite=Lax';\n const parts: string[] = [];\n parts.push(`Path=${options.path ?? '/'}`);\n if (options.domain) parts.push(`Domain=${options.domain}`);\n if (options.maxAge !== undefined) parts.push(`Max-Age=${options.maxAge}`);\n if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);\n const sameSite = options.sameSite ?? 'lax';\n parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);\n if (options.secure) parts.push('Secure');\n return '; ' + parts.join('; ');\n}\n\n/** Notify all subscribers for a given cookie name. */\nfunction notify(name: string): void {\n const subs = listeners.get(name);\n if (subs) {\n for (const fn of subs) fn();\n }\n}\n\n// ─── Hook ─────────────────────────────────────────────────────────────────\n\n/**\n * Reactive hook for reading/writing a client-side cookie.\n *\n * Returns `[value, setCookie, deleteCookie]`:\n * - `value`: current cookie value (string | undefined)\n * - `setCookie`: sets the cookie and triggers re-renders\n * - `deleteCookie`: deletes the cookie and triggers re-renders\n *\n * @param name - Cookie name.\n * @param defaultOptions - Default options for setCookie calls.\n */\nexport function useCookie(\n name: string,\n defaultOptions?: ClientCookieOptions\n): [value: string | undefined, setCookie: CookieSetter, deleteCookie: () => void] {\n const subscribe = (callback: Listener): (() => void) => {\n let subs = listeners.get(name);\n if (!subs) {\n subs = new Set();\n listeners.set(name, subs);\n }\n subs.add(callback);\n return () => {\n subs!.delete(callback);\n if (subs!.size === 0) listeners.delete(name);\n };\n };\n\n const getSnapshot = (): string | undefined => getCookieValue(name);\n const getServerSnapshot = (): string | undefined => getSsrData()?.cookies.get(name);\n\n const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n const setCookie: CookieSetter = (newValue: string, options?: ClientCookieOptions) => {\n const merged = { ...defaultOptions, ...options };\n document.cookie = `${name}=${encodeURIComponent(newValue)}${serializeOptions(merged)}`;\n notify(name);\n };\n\n const deleteCookie = (): void => {\n const path = defaultOptions?.path ?? '/';\n const domain = defaultOptions?.domain;\n let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;\n if (domain) cookieStr += `; Domain=${domain}`;\n document.cookie = cookieStr;\n notify(name);\n };\n\n return [value, setCookie, deleteCookie];\n}\n"],"mappings":";;AAwCA,IAAI;AAkBJ,IAAI;;;;;;;AAQJ,SAAgB,WAAW,MAAqB;AAC9C,kBAAiB;;;;;;;;AASnB,SAAgB,eAAqB;AACnC,kBAAiB,KAAA;;;;;;;;;;;AAYnB,SAAgB,aAAkC;AAChD,KAAI,iBACF,QAAO,kBAAkB;AAE3B,QAAO;;;;;;;;;;;;;;ACxDT,IAAM,4BAAY,IAAI,KAA4B;;AAGlD,SAAS,eAAe,MAAkC;AACxD,KAAI,OAAO,aAAa,YAAa,QAAO,KAAA;CAC5C,MAAM,QAAQ,SAAS,OAAO,MAC5B,IAAI,OAAO,gBAAgB,KAAK,QAAQ,uBAAuB,OAAO,GAAG,mBAAmB,CAC7F;AACD,QAAO,QAAQ,mBAAmB,MAAM,GAAG,GAAG,KAAA;;;AAIhD,SAAS,iBAAiB,SAAuC;AAC/D,KAAI,CAAC,QAAS,QAAO;CACrB,MAAM,QAAkB,EAAE;AAC1B,OAAM,KAAK,QAAQ,QAAQ,QAAQ,MAAM;AACzC,KAAI,QAAQ,OAAQ,OAAM,KAAK,UAAU,QAAQ,SAAS;AAC1D,KAAI,QAAQ,WAAW,KAAA,EAAW,OAAM,KAAK,WAAW,QAAQ,SAAS;AACzE,KAAI,QAAQ,QAAS,OAAM,KAAK,WAAW,QAAQ,QAAQ,aAAa,GAAG;CAC3E,MAAM,WAAW,QAAQ,YAAY;AACrC,OAAM,KAAK,YAAY,SAAS,OAAO,EAAE,CAAC,aAAa,GAAG,SAAS,MAAM,EAAE,GAAG;AAC9E,KAAI,QAAQ,OAAQ,OAAM,KAAK,SAAS;AACxC,QAAO,OAAO,MAAM,KAAK,KAAK;;;AAIhC,SAAS,OAAO,MAAoB;CAClC,MAAM,OAAO,UAAU,IAAI,KAAK;AAChC,KAAI,KACF,MAAK,MAAM,MAAM,KAAM,KAAI;;;;;;;;;;;;;AAiB/B,SAAgB,UACd,MACA,gBACgF;CAChF,MAAM,aAAa,aAAqC;EACtD,IAAI,OAAO,UAAU,IAAI,KAAK;AAC9B,MAAI,CAAC,MAAM;AACT,0BAAO,IAAI,KAAK;AAChB,aAAU,IAAI,MAAM,KAAK;;AAE3B,OAAK,IAAI,SAAS;AAClB,eAAa;AACX,QAAM,OAAO,SAAS;AACtB,OAAI,KAAM,SAAS,EAAG,WAAU,OAAO,KAAK;;;CAIhD,MAAM,oBAAwC,eAAe,KAAK;CAClE,MAAM,0BAA8C,YAAY,EAAE,QAAQ,IAAI,KAAK;CAEnF,MAAM,QAAQ,qBAAqB,WAAW,aAAa,kBAAkB;CAE7E,MAAM,aAA2B,UAAkB,YAAkC;EACnF,MAAM,SAAS;GAAE,GAAG;GAAgB,GAAG;GAAS;AAChD,WAAS,SAAS,GAAG,KAAK,GAAG,mBAAmB,SAAS,GAAG,iBAAiB,OAAO;AACpF,SAAO,KAAK;;CAGd,MAAM,qBAA2B;EAC/B,MAAM,OAAO,gBAAgB,QAAQ;EACrC,MAAM,SAAS,gBAAgB;EAC/B,IAAI,YAAY,GAAG,KAAK,4DAA4D;AACpF,MAAI,OAAQ,cAAa,YAAY;AACrC,WAAS,SAAS;AAClB,SAAO,KAAK;;AAGd,QAAO;EAAC;EAAO;EAAW;EAAa"}
|