@askrjs/askr 0.2.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/access-CEA2pOry.js +1046 -0
  3. package/dist/actions/index.d.ts +16 -2
  4. package/dist/actions/index.js +5 -5
  5. package/dist/{activity-FirwPXA_.js → activity-oy2YFTFF.js} +10 -16
  6. package/dist/benchmark.js +8 -9
  7. package/dist/boot/index.d.ts +19 -9
  8. package/dist/boot/index.js +1 -1
  9. package/dist/{boot-tWyF6121.js → boot-DLplW9h4.js} +700 -441
  10. package/dist/capabilities-Bbf0mlp9.js +42 -0
  11. package/dist/components/index.d.ts +11 -8
  12. package/dist/components/index.js +1 -1
  13. package/dist/{compose-ref-Bfaf4kUC.js → compose-ref-v0qGlHoN.js} +7 -1
  14. package/dist/control/index.d.ts +20 -2
  15. package/dist/control/index.js +1 -1
  16. package/dist/{control-C-wSaq9V.js → control-BR_mrX__.js} +4 -4
  17. package/dist/core.d.ts +163 -0
  18. package/dist/csp-nonce-BfFWcd0w.js +173 -0
  19. package/dist/data/index.d.ts +74 -2
  20. package/dist/data/index.js +3 -3
  21. package/dist/{data-ghKopAbp.js → data-2edW0jhF.js} +108 -122
  22. package/dist/{data-runtime-7t_Y0iv3.js → data-runtime-CxoY98lX.js} +22 -15
  23. package/dist/{deferred-CxjWCkIn.js → deferred-CbgNhf67.js} +5 -5
  24. package/dist/dom-renderer.d.ts +77 -0
  25. package/dist/domains/actions.d.ts +70 -0
  26. package/dist/domains/component.d.ts +119 -0
  27. package/dist/domains/context.d.ts +73 -0
  28. package/dist/domains/control.d.ts +328 -0
  29. package/dist/domains/data.d.ts +461 -0
  30. package/dist/domains/diagnostics.d.ts +21 -0
  31. package/dist/domains/lifecycle.d.ts +112 -0
  32. package/dist/domains/refs.d.ts +18 -0
  33. package/dist/domains/renderer.d.ts +127 -0
  34. package/dist/domains/routing.d.ts +501 -0
  35. package/dist/domains/scheduler.d.ts +79 -0
  36. package/dist/domains/server.d.ts +112 -0
  37. package/dist/domains/state.d.ts +129 -0
  38. package/dist/domains/telemetry.d.ts +29 -0
  39. package/dist/eager-control.d.ts +8 -0
  40. package/dist/{effect-CNwstBBD.js → effect-C3Zsc1k6.js} +14 -41
  41. package/dist/{types-DWVrKn9N.d.ts → elements.d.ts} +79 -42
  42. package/dist/{for-internal-DyBHfKJA.js → for-state-BDykeOJL.js} +598 -494
  43. package/dist/foundations/icon/index.d.ts +67 -16
  44. package/dist/foundations/icon/index.js +1 -1
  45. package/dist/foundations/index.d.ts +26 -3
  46. package/dist/foundations/index.js +1 -1
  47. package/dist/foundations/interactions/index.d.ts +73 -34
  48. package/dist/foundations/interactions/index.js +1 -1
  49. package/dist/foundations/state/index.d.ts +12 -5
  50. package/dist/foundations/state/index.js +1 -1
  51. package/dist/foundations/structures/index.d.ts +44 -3
  52. package/dist/foundations/structures/index.js +1 -1
  53. package/dist/foundations/utilities/index.d.ts +34 -3
  54. package/dist/foundations/utilities/index.js +1 -1
  55. package/dist/fx/index.d.ts +72 -25
  56. package/dist/fx/index.js +8 -8
  57. package/dist/index.d.ts +121 -4
  58. package/dist/index.js +316 -47
  59. package/dist/{component-internal-C4hKKQBF.js → instance-VAFYVJOa.js} +495 -283
  60. package/dist/{interactions-D102JxBR.js → interactions-CTeACdT_.js} +1 -1
  61. package/dist/jsx-dev-runtime.d.ts +51 -11
  62. package/dist/jsx-globals.d.ts +2 -0
  63. package/dist/jsx-runtime-CPJXAOBF.js +43 -0
  64. package/dist/jsx-runtime.d.ts +17 -3
  65. package/dist/jsx-runtime.js +2 -42
  66. package/dist/jsx.d.ts +108 -0
  67. package/dist/{manifest-D6rfj_jC.js → manifest-D1P3UNWV.js} +4 -9
  68. package/dist/{navigate-ZZu1IV0B.js → navigate-prC5V8XV.js} +125 -472
  69. package/dist/{navigate-F4sHgWbw.d.ts → navigation.d.ts} +8 -8
  70. package/dist/{readable-Di1fzSwm.js → notify-DxxuDPeY.js} +96 -34
  71. package/dist/{query-registry-Bua3cDyb.js → query-registry-LrYpQsDJ.js} +1 -1
  72. package/dist/{compose-ref-DOJHoW5X.d.ts → refs.d.ts} +11 -7
  73. package/dist/{ssr-Doog-AfJ.js → render-resolved-BC0W7_5b.js} +165 -558
  74. package/dist/{renderer-Cl23O0hV.js → renderer-CT0HeY58.js} +6825 -5692
  75. package/dist/{resolution-ClD-K7Lz.js → resolution-3DskUkjG.js} +189 -180
  76. package/dist/{resource-operation-Vz1S5h2k.js → resource-18-sFRl9.js} +98 -58
  77. package/dist/resources/index.d.ts +64 -12
  78. package/dist/resources/index.js +214 -8
  79. package/dist/route-activity.d.ts +21 -0
  80. package/dist/{route-matching-Bo6UqG-5.js → route-matching-CUzZ31gT.js} +68 -82
  81. package/dist/router/index.d.ts +378 -79
  82. package/dist/router/index.js +9 -9
  83. package/dist/router-internal-7nuNEHfG.js +36 -0
  84. package/dist/scope-Cq_iYbTR.js +403 -0
  85. package/dist/{selector-CsKuomw0.js → selector-BSPVkjKP.js} +26 -27
  86. package/dist/{shared-CYBH3F8I.js → shared-kU8yvryp.js} +3 -6
  87. package/dist/{snapshot-source-BB42xgwb.js → snapshot-source-An4iKajG.js} +1 -1
  88. package/dist/ssg/index.d.ts +51 -15
  89. package/dist/ssg/index.js +269 -209
  90. package/dist/ssr/index.d.ts +121 -68
  91. package/dist/ssr/index.js +2 -1
  92. package/dist/ssr-CiNm6Vzl.js +393 -0
  93. package/dist/{ssr-DSHd_Q9K.js → ssr-EPjwJH5R.js} +5 -1
  94. package/dist/{state-BGJgYSyQ.js → state-BXJfudq1.js} +10 -15
  95. package/dist/{structures-DFyK5AdQ.js → structures-CjEzfj4h.js} +37 -57
  96. package/dist/{index-CTpMmzgN.d.ts → structures.d.ts} +57 -36
  97. package/dist/testing/index.d.ts +123 -24
  98. package/dist/testing/index.js +24 -61
  99. package/dist/timer-C7uqnJVt.js +85 -0
  100. package/dist/{index-B1gu0_m0.d.ts → utilities.d.ts} +24 -13
  101. package/dist/{verify-hydration-BZPJ_kYa.js → verify-hydration-ikaxotf7.js} +8 -5
  102. package/package.json +12 -14
  103. package/dist/access-BucjYZcX.js +0 -509
  104. package/dist/activity-DooIuxQO.d.ts +0 -14
  105. package/dist/benchmark.d.ts +0 -26
  106. package/dist/cleanup-TXSsApEl.js +0 -535
  107. package/dist/component-cleanup-jdbgjkT1.js +0 -143
  108. package/dist/component-scope-4I3yEMkb.js +0 -195
  109. package/dist/control-Cxag1yqV.d.ts +0 -8
  110. package/dist/csp-nonce-9NFUb7hJ.js +0 -72
  111. package/dist/css-BuPaTOfe.js +0 -70
  112. package/dist/fastlane-BKZduiBQ.js +0 -166
  113. package/dist/index-Dgjbo2gU.d.ts +0 -1456
  114. package/dist/jsx-runtime-Cg7GqY1S.d.ts +0 -2
  115. package/dist/jsx-runtime-gVIaTPw0.d.ts +0 -38
  116. package/dist/lifecycle-batch-63aXFf2S.js +0 -253
  117. package/dist/lifecycle-operations-Q0k3EFnG.js +0 -287
@@ -0,0 +1,127 @@
1
+ import { JSXElementType, JSXElement, Props } from '../elements.js';
2
+ import '../jsx-globals.js';
3
+ import { AuthContext, AuthRequirement } from '@askrjs/auth';
4
+ import { InferSchema, ObjectSchema } from '@askrjs/schema';
5
+ import { VNode, ContextFrame } from './context.js';
6
+ import { ComponentInstance, ReadableSource } from './component.js';
7
+ import { Scheduler } from './scheduler.js';
8
+
9
+ /**
10
+ * Internal DOM range shape shared by runtime ownership records and the
11
+ * renderer. A singleton range uses the node itself for both anchors; a
12
+ * multi-node or empty range uses deterministic comment anchors.
13
+ */
14
+ interface DOMRange {
15
+ start: Node;
16
+ end: Node;
17
+ single: boolean;
18
+ }
19
+
20
+ interface ChildScope {
21
+ key: string | number;
22
+ componentInstance: ComponentInstance;
23
+ previousVnode: VNode | undefined;
24
+ vnode: VNode | undefined;
25
+ dom?: Node;
26
+ /** @internal Fast singleton node plus an anchor-backed multi-node range. */
27
+ range?: DOMRange;
28
+ needsDomUpdate: boolean;
29
+ hydrationPending: boolean;
30
+ /** @internal Stable owner for validated intrinsic blueprints in list items. */
31
+ blueprintOwner?: object;
32
+ render(renderFn: () => VNode): VNode;
33
+ markDirty(): void;
34
+ dispose(): void;
35
+ }
36
+
37
+ interface ChildScopeOwnership {
38
+ add(scope: ChildScope): void;
39
+ delete(scope: ChildScope): void;
40
+ bulkDispose(run: () => void): void;
41
+ }
42
+
43
+ /** @internal Snapshot used to restore a child scope after a failed commit. */
44
+ interface ChildScopeTransactionSnapshot {
45
+ previousVnode: VNode | undefined;
46
+ vnode: VNode | undefined;
47
+ dom: Node | undefined;
48
+ range: DOMRange | undefined;
49
+ domTextData: string | undefined;
50
+ needsDomUpdate: boolean;
51
+ hydrationPending: boolean;
52
+ renderFn: (() => VNode) | undefined;
53
+ renderedOwnerFrame: ContextFrame | null;
54
+ }
55
+
56
+ /** Diagnostic breakdown of a keyed-list reorder decision, returned by {@link RuntimeRendererHost.isKeyedReorderFastPathEligible}. */
57
+ interface RuntimeKeyedReorderDecision {
58
+ useFastPath: boolean;
59
+ totalKeyed: number;
60
+ totalChildren: number;
61
+ currentKeyCount: number;
62
+ moveCount: number;
63
+ lisLen: number;
64
+ hasPropChanges: boolean;
65
+ isWholeKeyedList: boolean;
66
+ }
67
+
68
+ /** The renderer implementation an {@link AskrRuntime} delegates DOM evaluation and cleanup to. */
69
+ interface RuntimeRendererHost {
70
+ evaluate(
71
+ node: unknown,
72
+ target: Element | null,
73
+ context?: object,
74
+ retainedOwner?: ComponentInstance
75
+ ): void;
76
+ cleanupInstancesUnder(node: Node): void;
77
+ replaceComponentRange(
78
+ instance: ComponentInstance,
79
+ result: unknown,
80
+ host: Element | Comment
81
+ ): Node | null;
82
+ resolveChildScopeRange?(scope: ChildScope): DOMRange | null;
83
+ teardownNodeSubtree(root: Node): void;
84
+ populateKeyMapForElement(parent: Element): void;
85
+ getKeyMapForElement(
86
+ parent: Element
87
+ ): Map<string | number, Element> | undefined;
88
+ isKeyedReorderFastPathEligible(
89
+ parent: Element,
90
+ children: unknown[],
91
+ oldKeyMap: Map<string | number, Element> | undefined
92
+ ): RuntimeKeyedReorderDecision;
93
+ markReactivePropsDirtySource(source: ReadableSource<unknown>): void;
94
+ }
95
+
96
+ /** Options for {@link createRuntime}. */
97
+ interface AskrRuntimeOptions {
98
+ scheduler?: Scheduler;
99
+ renderer?: RuntimeRendererHost;
100
+ }
101
+
102
+ /** Construction-only scheduler and renderer wiring. Mounting uses the default runtime. */
103
+ declare class AskrRuntime {
104
+ readonly scheduler: Scheduler;
105
+ private rendererHost;
106
+ constructor(options?: AskrRuntimeOptions);
107
+ get renderer(): RuntimeRendererHost;
108
+ configureRenderer(renderer: RuntimeRendererHost): void;
109
+ }
110
+
111
+ /** Create construction-only runtime wiring. Omitted schedulers share the default scheduler; mounting uses the default runtime. */
112
+ declare function createRuntime(options?: AskrRuntimeOptions): AskrRuntime;
113
+
114
+ /** Get the process-wide default {@link AskrRuntime}. */
115
+ declare function getDefaultRuntime(): AskrRuntime;
116
+ export {
117
+ DOMRange,
118
+ ChildScope,
119
+ ChildScopeOwnership,
120
+ ChildScopeTransactionSnapshot,
121
+ RuntimeKeyedReorderDecision,
122
+ RuntimeRendererHost,
123
+ AskrRuntimeOptions,
124
+ AskrRuntime,
125
+ createRuntime,
126
+ getDefaultRuntime,
127
+ };
@@ -0,0 +1,501 @@
1
+ import { JSXElementType, JSXElement, Props } from '../elements.js';
2
+ import '../jsx-globals.js';
3
+ import { AuthContext, AuthRequirement } from '@askrjs/auth';
4
+ import { InferSchema, ObjectSchema } from '@askrjs/schema';
5
+ import { state, selector } from './state.js';
6
+ import { RenderableChild } from './context.js';
7
+ import { QueryPrefetchContext } from './data.js';
8
+ import { CoreTelemetry } from './telemetry.js';
9
+ import { on, capture } from './lifecycle.js';
10
+ import { For } from './control.js';
11
+ import { ActionDescriptor } from './actions.js';
12
+
13
+ /**
14
+ * Common call contracts: Router types
15
+ */
16
+ /** Path parameters captured for a matched route, keyed by parameter name. */
17
+ type RouteParams = Record<string, string>;
18
+
19
+ type StripRoutePathSuffix<Path extends string> =
20
+ Path extends `${infer Base}?${string}`
21
+ ? StripRoutePathSuffix<Base>
22
+ : Path extends `${infer Base}#${string}`
23
+ ? StripRoutePathSuffix<Base>
24
+ : Path;
25
+
26
+ type TrimRoutePathSlashes<Path extends string> = Path extends `/${infer Rest}`
27
+ ? TrimRoutePathSlashes<Rest>
28
+ : Path extends `${infer Rest}/`
29
+ ? TrimRoutePathSlashes<Rest>
30
+ : Path;
31
+
32
+ type TrimRoutePathWhitespace<Path extends string> =
33
+ Path extends `${' ' | '\n' | '\t' | '\r'}${infer Rest}`
34
+ ? TrimRoutePathWhitespace<Rest>
35
+ : Path extends `${infer Rest}${' ' | '\n' | '\t' | '\r'}`
36
+ ? TrimRoutePathWhitespace<Rest>
37
+ : Path;
38
+
39
+ type ExtractRouteSegmentParam<Segment extends string> =
40
+ Segment extends `{${infer Param}}`
41
+ ? TrimRoutePathWhitespace<Param> extends `*${infer SplatParam}`
42
+ ? TrimRoutePathWhitespace<SplatParam> extends ''
43
+ ? never
44
+ : TrimRoutePathWhitespace<SplatParam> extends '*'
45
+ ? never
46
+ : TrimRoutePathWhitespace<SplatParam>
47
+ : TrimRoutePathWhitespace<Param> extends ''
48
+ ? never
49
+ : TrimRoutePathWhitespace<Param>
50
+ : Segment extends '*'
51
+ ? '*'
52
+ : never;
53
+
54
+ type ExtractRoutePathParamNames<Path extends string> =
55
+ TrimRoutePathSlashes<
56
+ StripRoutePathSuffix<Path>
57
+ > extends `${infer Segment}/${infer Rest}`
58
+ ? ExtractRouteSegmentParam<Segment> | ExtractRoutePathParamNames<Rest>
59
+ : ExtractRouteSegmentParam<
60
+ TrimRoutePathSlashes<StripRoutePathSuffix<Path>>
61
+ >;
62
+
63
+ /** Statically infers the param record shape from a route path string literal, e.g. `/posts/{id}`. */
64
+ type RoutePathParams<Path extends string> = [
65
+ ExtractRoutePathParamNames<Path>,
66
+ ] extends [never]
67
+ ? Record<never, string>
68
+ : { [Key in ExtractRoutePathParamNames<Path>]: string };
69
+
70
+ /**
71
+ * A route page component: a regular component that receives route params as
72
+ * props derived from the URL pattern.
73
+ *
74
+ * Components may accept no params at all — zero-argument components are still
75
+ * assignable.
76
+ */
77
+ type RouteComponent<TParams extends RouteParams = RouteParams> = (
78
+ props: TParams
79
+ ) => RenderableChild;
80
+
81
+ /** The rendering mode a route is currently being evaluated under. */
82
+ type RouteMode = 'spa' | 'ssr' | 'ssg';
83
+
84
+ type AccessRedirectStatus = 301 | 302 | 303 | 307 | 308;
85
+
86
+ type AccessDenyStatus = 401 | 403 | 404;
87
+
88
+ interface AccessAllowDecision {
89
+ kind: 'allow';
90
+ }
91
+
92
+ /** Policy decision produced by {@link redirect}: sends the visitor to another URL. */
93
+ interface AccessRedirectDecision {
94
+ kind: 'redirect';
95
+ to: string;
96
+ status?: AccessRedirectStatus;
97
+ replace?: boolean;
98
+ }
99
+
100
+ /** Policy decision produced by {@link deny}/{@link unauthorized}/{@link forbidden}/{@link notFound}. */
101
+ interface AccessDenyDecision {
102
+ kind: 'deny';
103
+ status: AccessDenyStatus;
104
+ }
105
+
106
+ /** Outcome of a {@link RoutePolicy} evaluation: allow, redirect, or deny. */
107
+ type AccessDecision =
108
+ | AccessAllowDecision
109
+ | AccessRedirectDecision
110
+ | AccessDenyDecision;
111
+
112
+ /** Context passed to route policies, auth resolvers, and loaders. */
113
+ interface RouteContext<TParams extends RouteParams = RouteParams> {
114
+ mode: RouteMode;
115
+ params: TParams;
116
+ pathname: string;
117
+ search: string;
118
+ hash: string;
119
+ href: string;
120
+ auth: AuthContext;
121
+ signal: AbortSignal;
122
+ }
123
+
124
+ /** A route access-control check, evaluated against {@link RouteContext} to produce an {@link AccessDecision}. */
125
+ type RoutePolicy = (
126
+ context: RouteContext
127
+ ) => AccessDecision | PromiseLike<AccessDecision>;
128
+
129
+ /** Resolves the {@link AuthContext} for a route request. */
130
+ type RouteAuthResolver = (
131
+ context: Omit<RouteContext, 'auth'>
132
+ ) => AuthContext | PromiseLike<AuthContext>;
133
+
134
+ /** Auth configuration shared across a route registry or a single route. */
135
+ interface RouteAuthOptions {
136
+ resolve: RouteAuthResolver;
137
+ loginPath?:
138
+ | string
139
+ | ((context: RouteContext) => string | PromiseLike<string>);
140
+ authenticatedRedirectTo?:
141
+ | string
142
+ | ((context: RouteContext) => string | PromiseLike<string>);
143
+ }
144
+
145
+ interface CommonAccessOptions {
146
+ auth?: AuthRequirement;
147
+ policies?: readonly RoutePolicy[];
148
+ }
149
+
150
+ /** A route's metadata, or a function computing it from the resolved context. */
151
+ type RouteMetaSource<TParams extends RouteParams = RouteParams> =
152
+ | RouteMeta
153
+ | ((context: RouteContext<TParams>) => RouteMeta | PromiseLike<RouteMeta>);
154
+
155
+ /**
156
+ * Options for `route()` declarations.
157
+ *
158
+ * - `loader`: server data loader called before render, result passed as SSR data
159
+ * - `entries`: SSG entry generator — returns one param map per static page
160
+ * - `title`: page title hint used by SSG and document-meta integrations
161
+ * - `namespace`: MFE namespace key for grouped route management
162
+ */
163
+ interface RouteOptions<
164
+ TParams extends RouteParams = RouteParams,
165
+ TSearchSchema extends ObjectSchema<RouteSearch> | undefined =
166
+ | ObjectSchema<RouteSearch>
167
+ | undefined,
168
+ TLoaderData = unknown,
169
+ TDehydratedData = TLoaderData,
170
+ > extends CommonAccessOptions {
171
+ loader?: (
172
+ context: RouteContext<TParams> & {
173
+ request?: Request;
174
+ }
175
+ ) => TLoaderData | PromiseLike<TLoaderData>;
176
+ /**
177
+ * Select the loader data transported to the browser for initial hydration.
178
+ *
179
+ * Server rendering still receives the complete loader value. The selector
180
+ * must be synchronous; client navigations rerun the loader and receive its
181
+ * complete result.
182
+ */
183
+ dehydrate?: (
184
+ data: TLoaderData,
185
+ context: RouteContext<TParams> & {
186
+ request?: Request;
187
+ }
188
+ ) => TDehydratedData extends PromiseLike<unknown> ? never : TDehydratedData;
189
+ preload?: (
190
+ context: RouteContext<TParams> & {
191
+ request?: Request;
192
+ data: QueryPrefetchContext;
193
+ }
194
+ ) => unknown;
195
+ entries?: () => Array<TParams> | Promise<Array<TParams>>;
196
+ /** Optional invalidation keys used by incremental SSG generation. */
197
+ invalidationKeys?: readonly string[];
198
+ title?: string;
199
+ namespace?: string;
200
+ search?: TSearchSchema;
201
+ meta?: RouteMetaSource<TParams>;
202
+ actions?: readonly ActionDescriptor[];
203
+ }
204
+
205
+ interface RouteMeta {
206
+ title?: string;
207
+ description?: string;
208
+ canonical?: string;
209
+ robots?: string;
210
+ openGraph?: Record<string, string>;
211
+ links?: readonly {
212
+ rel: string;
213
+ href: string;
214
+ [key: string]: string;
215
+ }[];
216
+ jsonLd?: unknown | readonly unknown[];
217
+ html?: {
218
+ lang?: string;
219
+ dir?: 'ltr' | 'rtl' | 'auto';
220
+ };
221
+ }
222
+
223
+ /** A stable, typed reference returned by route() for destination construction. */
224
+ type RouteSearchValue =
225
+ | string
226
+ | number
227
+ | boolean
228
+ | null
229
+ | undefined
230
+ | readonly (string | number | boolean | null)[];
231
+
232
+ /** A route's query-string parameters, keyed by name. */
233
+ type RouteSearch = Record<string, RouteSearchValue>;
234
+
235
+ /** Stable, typed reference to a route returned by `route()`, used to build destinations. */
236
+ interface RouteRef<
237
+ TParams extends RouteParams = RouteParams,
238
+ TSearch = RouteSearch,
239
+ > {
240
+ readonly path: string;
241
+ /** @internal Executable schema retained for destination validation. */
242
+ readonly searchSchema?: ObjectSchema<TSearch & RouteSearch>;
243
+ /** @internal Public mount point captured by createRouteRegistry(). */
244
+ readonly basePath?: string;
245
+ readonly __params?: TParams;
246
+ readonly __search?: TSearch;
247
+ }
248
+
249
+ type RouteRefSearch<TSchema extends ObjectSchema<RouteSearch> | undefined> =
250
+ TSchema extends ObjectSchema<RouteSearch>
251
+ ? InferSchema<TSchema>
252
+ : RouteSearch;
253
+
254
+ /** A resolved navigation target with a computed `href`, produced by {@link to}. */
255
+ interface RouteDestination {
256
+ readonly href: string;
257
+ }
258
+
259
+ /** Options accepted by the `page()` route-declaration helper. */
260
+ interface PageHelperOptions extends CommonAccessOptions {
261
+ preload?: (
262
+ context: RouteContext & {
263
+ request?: Request;
264
+ data: QueryPrefetchContext;
265
+ }
266
+ ) => unknown;
267
+ meta?: RouteMetaSource;
268
+ }
269
+
270
+ /**
271
+ * A single parsed segment from a route path.
272
+ *
273
+ * - `static`: a literal path segment, e.g. `"users"` in `/users/{id}`
274
+ * - `param`: a `{name}` capture group — `value` holds the param name
275
+ * - `wildcard`: a bare `*` segment that captures exactly one segment
276
+ * - `splat`: a `{*name}` capture group that captures the remaining path
277
+ * - `catchall`: the `/*` catch-all that matches any depth
278
+ */
279
+ interface ParsedSegment {
280
+ kind: 'static' | 'param' | 'wildcard' | 'splat' | 'catchall';
281
+ /** For static/wildcard/catchall: the literal text; for param: the param name. */
282
+ value: string;
283
+ }
284
+
285
+ /** Resolved layout component as stored in a route record's layout chain. */
286
+ interface LayoutScopeRecord {
287
+ component: (props: { children?: RenderableChild }) => RenderableChild;
288
+ }
289
+
290
+ /** Resolved page host component as stored in a route record's page chain. */
291
+ interface PageScopeRecord {
292
+ component: RouteComponent;
293
+ }
294
+
295
+ /** Options for {@link createRouteRegistry}. */
296
+ interface RouteRegistryOptions {
297
+ auth?: RouteAuthOptions;
298
+ /** Public pathname prefix for applications mounted below the origin root. */
299
+ basePath?: string;
300
+ }
301
+
302
+ /** A callback that declares routes via `route()`/`page()`/`group()`, passed to {@link createRouteRegistry}. */
303
+ type RouteDefinition = () => void;
304
+
305
+ /** Options for resolving a route request (used internally by `createSPA`/`hydrateSPA`/SSR). */
306
+ interface RouteRequestOptions {
307
+ /** Explicit route source shared by the application renderers. */
308
+ registry: RouteRegistry;
309
+ mode?: RouteMode;
310
+ /** @internal Hydration adopts server loader data instead of rerunning it. */
311
+ load?: boolean;
312
+ auth?: RouteAuthOptions;
313
+ authContext?: AuthContext;
314
+ signal?: AbortSignal;
315
+ request?: Request;
316
+ telemetry?: CoreTelemetry;
317
+ }
318
+
319
+ /** A resolved route request that should render `handler` with `params`. */
320
+ interface RouteRenderResult<TParams extends RouteParams = RouteParams> {
321
+ kind: 'render';
322
+ handler: RouteHandler<TParams>;
323
+ params: TParams;
324
+ record?: RouteRecord;
325
+ }
326
+
327
+ /** Outcome of resolving a route request: render, redirect, deny, or no match. */
328
+ type RouteRequestResult<TParams extends RouteParams = RouteParams> =
329
+ | RouteRenderResult<TParams>
330
+ | AccessRedirectDecision
331
+ | AccessDenyDecision
332
+ | null;
333
+
334
+ /** Options accepted by the `group()` route-declaration helper. */
335
+ interface GroupHelperOptions extends CommonAccessOptions {
336
+ layout?: (props: { children?: RenderableChild }) => RenderableChild;
337
+ meta?: RouteMetaSource;
338
+ }
339
+
340
+ /**
341
+ * A fully normalized route record produced by `route(path, Component, options?)`.
342
+ *
343
+ * This is the canonical representation shared by:
344
+ * - SPA matching and navigation
345
+ * - SSR request resolution
346
+ * - SSG manifest expansion
347
+ */
348
+ interface RouteRecord {
349
+ /** Canonical normalized absolute path, e.g. `/posts/{slug}` */
350
+ path: string;
351
+ /** The page component to render when this route is active */
352
+ component: RouteComponent;
353
+ /** Pre-parsed segment list for fast matching and typed param extraction */
354
+ segments: ParsedSegment[];
355
+ /** Pre-computed specificity rank (higher = more specific) */
356
+ rank: number;
357
+ /** Layout chain from outermost to innermost, applied automatically on render */
358
+ layoutChain: LayoutScopeRecord[];
359
+ /** Page chain from outermost to innermost, composed through Outlet before layouts apply */
360
+ pageChain: PageScopeRecord[];
361
+ /** Route metadata: loader, entries, policies, title, namespace */
362
+ options: RouteOptions;
363
+ /** Metadata sources ordered from outermost group/page to the route leaf. */
364
+ metaChain?: readonly RouteMetaSource[];
365
+ /** True when this is the `/*` catch-all fallback route */
366
+ isFallback: boolean;
367
+ /**
368
+ * Runtime-ready handler with layout composition baked in.
369
+ * Compatible with the low-level `RouteHandler` signature so that navigation
370
+ * and SSR rendering do not need to know about layout chains.
371
+ */
372
+ handler: RouteHandler;
373
+ }
374
+
375
+ /**
376
+ * The normalized route manifest produced by registered route definitions.
377
+ * declarations. Pass it to `createSPA`, `hydrateSPA`, or `renderToString`
378
+ * instead of assembling plain `Route[]` arrays.
379
+ *
380
+ * ```ts
381
+ * import { createRouteRegistry } from '@askrjs/askr/router';
382
+ * const registry = createRouteRegistry(() => { ... });
383
+ * await createSPA({ root: '#app', registry });
384
+ * ```
385
+ */
386
+ interface RouteManifest {
387
+ records: RouteRecord[];
388
+ auth?: RouteAuthOptions;
389
+ /** Normalized public pathname prefix. Empty and root mounts omit it. */
390
+ basePath?: string;
391
+ }
392
+
393
+ declare const routeRegistryBrand: unique symbol;
394
+
395
+ /** A function rendering a matched route's page content, with layouts already composed. */
396
+ interface RouteHandler<TParams extends RouteParams = RouteParams> {
397
+ (
398
+ params: TParams,
399
+ context?: {
400
+ signal: AbortSignal;
401
+ }
402
+ ): RenderableChild;
403
+ }
404
+
405
+ /** A single path-to-handler binding as seen by low-level navigation code. */
406
+ interface Route<TParams extends RouteParams = RouteParams> {
407
+ path: string;
408
+ handler: RouteHandler<TParams>;
409
+ namespace?: string;
410
+ }
411
+
412
+ /** Opaque handle produced by {@link createRouteRegistry}, required by `createSPA`/`hydrateSPA`. */
413
+ interface RouteRegistry {
414
+ /** Internal brand: registries must come from createRouteRegistry(). */
415
+ readonly [routeRegistryBrand]: true;
416
+ manifest: RouteManifest;
417
+ routes: readonly Route[];
418
+ }
419
+
420
+ /** A single matched route, as reported by {@link currentRoute} and activity predicates. */
421
+ interface RouteMatch<TParams extends RouteParams = RouteParams> {
422
+ path: string;
423
+ params: Readonly<TParams>;
424
+ name?: string;
425
+ namespace?: string;
426
+ }
427
+
428
+ /** Read-only accessor for the current route's query-string parameters. */
429
+ interface RouteQuery {
430
+ get(key: string): string | null;
431
+ getAll(key: string): string[];
432
+ has(key: string): boolean;
433
+ toJSON(): Record<string, string | string[]>;
434
+ }
435
+
436
+ /** Full description of the currently active route, returned by {@link currentRoute}. */
437
+ interface RouteSnapshot<
438
+ TParams extends RouteParams = RouteParams,
439
+ TState = unknown,
440
+ > {
441
+ path: string;
442
+ params: Readonly<TParams>;
443
+ query: Readonly<RouteQuery>;
444
+ hash: string | null;
445
+ /** Whether the current browser history entry was given explicit location state. */
446
+ hasState: boolean;
447
+ /** Entry-local state supplied to navigate(); absent during SSR and when no state was supplied. */
448
+ state: TState | undefined;
449
+ name?: string;
450
+ namespace?: string;
451
+ matches: readonly RouteMatch<TParams>[];
452
+ }
453
+ export {
454
+ RouteParams,
455
+ StripRoutePathSuffix,
456
+ TrimRoutePathSlashes,
457
+ TrimRoutePathWhitespace,
458
+ ExtractRouteSegmentParam,
459
+ ExtractRoutePathParamNames,
460
+ RoutePathParams,
461
+ RouteComponent,
462
+ RouteMode,
463
+ AccessRedirectStatus,
464
+ AccessDenyStatus,
465
+ AccessAllowDecision,
466
+ AccessRedirectDecision,
467
+ AccessDenyDecision,
468
+ AccessDecision,
469
+ RouteContext,
470
+ RoutePolicy,
471
+ RouteAuthResolver,
472
+ RouteAuthOptions,
473
+ CommonAccessOptions,
474
+ RouteMetaSource,
475
+ RouteOptions,
476
+ RouteMeta,
477
+ RouteSearchValue,
478
+ RouteSearch,
479
+ RouteRef,
480
+ RouteRefSearch,
481
+ RouteDestination,
482
+ PageHelperOptions,
483
+ ParsedSegment,
484
+ LayoutScopeRecord,
485
+ PageScopeRecord,
486
+ RouteRegistryOptions,
487
+ RouteDefinition,
488
+ RouteRequestOptions,
489
+ RouteRenderResult,
490
+ RouteRequestResult,
491
+ GroupHelperOptions,
492
+ RouteRecord,
493
+ RouteManifest,
494
+ routeRegistryBrand,
495
+ RouteHandler,
496
+ Route,
497
+ RouteRegistry,
498
+ RouteMatch,
499
+ RouteQuery,
500
+ RouteSnapshot,
501
+ };
@@ -0,0 +1,79 @@
1
+ import { JSXElementType, JSXElement, Props } from '../elements.js';
2
+ import '../jsx-globals.js';
3
+ import { AuthContext, AuthRequirement } from '@askrjs/auth';
4
+ import { InferSchema, ObjectSchema } from '@askrjs/schema';
5
+ import { task } from './lifecycle.js';
6
+
7
+ /**
8
+ * Serialized update scheduler — safer design (no inline execution, explicit flush)
9
+ *
10
+ * Key ideas:
11
+ * - Never execute a task inline from `enqueue`.
12
+ * - `flush()` is explicit and non-reentrant.
13
+ * - `runWithSyncProgress()` allows enqueues temporarily but does not run tasks
14
+ * inline; it runs `fn` and then does an explicit `flush()`.
15
+ * - `waitForFlush()` is race-free with a monotonic `flushVersion`.
16
+ */
17
+ type Task = () => void;
18
+
19
+ type SchedulerLane = 'derived' | 'component' | 'reactive' | 'post';
20
+
21
+ type SchedulerBulkCommitProbe = () => boolean;
22
+
23
+ declare class Scheduler {
24
+ private bulkCommitProbe;
25
+ private lanes;
26
+ private running;
27
+ private inHandler;
28
+ private depth;
29
+ private executionDepth;
30
+ private flushVersion;
31
+ private kickScheduled;
32
+ private allowSyncProgress;
33
+ private waiters;
34
+ private taskCount;
35
+ setBulkCommitProbe(probe: SchedulerBulkCommitProbe): void;
36
+ private isBulkCommitActive;
37
+ private hasPendingTasks;
38
+ private getPendingTaskCount;
39
+ private compactLane;
40
+ private scheduleFlushKick;
41
+ enqueue(task: Task): void;
42
+ enqueueInLane(lane: SchedulerLane, task: Task): void;
43
+ flush(): void;
44
+ runWithSyncProgress<T>(fn: () => T): T;
45
+ waitForFlush(targetVersion?: number, timeoutMs?: number): Promise<void>;
46
+ getState(): {
47
+ queueLength: number;
48
+ running: boolean;
49
+ depth: number;
50
+ executionDepth: number;
51
+ taskCount: number;
52
+ flushVersion: number;
53
+ laneQueues: {
54
+ derived: number;
55
+ component: number;
56
+ reactive: number;
57
+ post: number;
58
+ };
59
+ inHandler: boolean;
60
+ allowSyncProgress: boolean;
61
+ };
62
+ getFlushVersion(): number;
63
+ flushIfQueued(): void;
64
+ runInHandlerScope<T>(fn: () => T, flushMode?: 'defer' | 'sync'): T;
65
+ setInHandler(v: boolean): void;
66
+ isInHandler(): boolean;
67
+ isExecuting(): boolean;
68
+ clearPendingSyncTasks(): number;
69
+ private resolveWaiters;
70
+ }
71
+
72
+ declare function scheduleEventHandler(handler: EventListener): EventListener;
73
+ export {
74
+ Task,
75
+ SchedulerLane,
76
+ SchedulerBulkCommitProbe,
77
+ Scheduler,
78
+ scheduleEventHandler,
79
+ };