@askrjs/askr 0.0.69 → 0.0.71

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 (64) hide show
  1. package/capabilities.json +66 -0
  2. package/dist/actions/index.d.ts +1 -1
  3. package/dist/actions/index.js +9 -2
  4. package/dist/activity-C2tPn9Fr.js +152 -0
  5. package/dist/activity-DZ-NCYr8.d.ts +10 -0
  6. package/dist/{authoring-uaJ556aG.js → authoring-CXmUcaCe.js} +2 -2
  7. package/dist/benchmark.d.ts +1 -1
  8. package/dist/benchmark.js +6 -6
  9. package/dist/boot/index.d.ts +2 -2
  10. package/dist/boot/index.js +1 -1
  11. package/dist/{boot-aTei9rjA.js → boot-DIKbEA4Y.js} +11 -10
  12. package/dist/{cleanup-UyStxIHG.js → cleanup-D8Ufd9Ix.js} +29 -8
  13. package/dist/components/index.d.ts +1 -0
  14. package/dist/components/index.js +1 -0
  15. package/dist/control/index.d.ts +1 -1
  16. package/dist/control/index.js +1 -1
  17. package/dist/{control-CA45Yvvu.js → control-DVRBkN2X.js} +7 -2
  18. package/dist/{csp-nonce-HGZ0vbqT.js → csp-nonce-CwSRP3Jl.js} +16 -1
  19. package/dist/css-BuPaTOfe.js +70 -0
  20. package/dist/data/index.js +3 -3
  21. package/dist/{data-DHt_zO1G.js → data-DFcSmGvN.js} +2 -2
  22. package/dist/{data-runtime-SJ1BSUvk.js → data-runtime-rpV6-3XW.js} +13 -2
  23. package/dist/{deferred-zHtUheIQ.js → deferred-Cr58yIQD.js} +3 -4
  24. package/dist/foundations/index.d.ts +1 -1
  25. package/dist/foundations/index.js +1 -1
  26. package/dist/foundations/structures/index.d.ts +1 -1
  27. package/dist/foundations/structures/index.js +1 -1
  28. package/dist/{index-BFnTM1a8.d.ts → index-BOBtoY_r.d.ts} +34 -2
  29. package/dist/{index-DSClfSoC.d.ts → index-DxYJPVC4.d.ts} +1 -1
  30. package/dist/{index-Dh6ZF9yW.d.ts → index-UmwPZ8ZN.d.ts} +5 -0
  31. package/dist/{index-DjusBb1I.d.ts → index-u3KYoHlM.d.ts} +1 -1
  32. package/dist/index.d.ts +4 -5
  33. package/dist/index.js +12 -7
  34. package/dist/jsx-dev-runtime.d.ts +1 -0
  35. package/dist/jsx-runtime-C6Fs8Mmc.d.ts +2 -0
  36. package/dist/{jsx-runtime-aYZ8zb4q.d.ts → jsx-runtime-CL2r2HHQ.d.ts} +1 -0
  37. package/dist/jsx-runtime.d.ts +1 -1
  38. package/dist/{lifecycle-operations-SlPEK68H.js → lifecycle-operations-Db-_MG-O.js} +9 -12
  39. package/dist/{navigate-CV-RDqYP.d.ts → navigate-Cbq6QHVl.d.ts} +1 -1
  40. package/dist/{navigate-DpR-cML9.js → navigate-CuGQzS_Y.js} +8 -104
  41. package/dist/{portal-D7TC986A.js → portal-wY3j0vvp.js} +59 -1
  42. package/dist/{query-registry-CQIT4mXr.js → query-registry-Bu_cuMch.js} +1 -1
  43. package/dist/{renderer-CBBbYZUO.js → renderer-BJYY2Y8s.js} +91 -10
  44. package/dist/resources/index.d.ts +27 -8
  45. package/dist/resources/index.js +218 -4
  46. package/dist/{route-matching-DLBWDeXX.js → route-matching-M-f1VQRu.js} +276 -2
  47. package/dist/router/index.d.ts +7 -7
  48. package/dist/router/index.js +28 -17
  49. package/dist/{router-C0XQA62W.d.ts → router-C3vENhUh.d.ts} +1 -1
  50. package/dist/{selector-DueeR6Oz.js → selector-DEfqO1rA.js} +8 -0
  51. package/dist/ssg/index.d.ts +8 -1
  52. package/dist/ssg/index.js +53 -17
  53. package/dist/ssr/index.d.ts +15 -1
  54. package/dist/ssr/index.js +1 -1
  55. package/dist/{ssr-vE-KUgDV.js → ssr-BSUA9Y64.js} +72 -78
  56. package/dist/testing/index.d.ts +35 -2
  57. package/dist/testing/index.js +182 -3
  58. package/dist/{verify-hydration-ChyEiBAn.js → verify-hydration-CVSGv6Vs.js} +1 -1
  59. package/package.json +4 -2
  60. package/dist/attr-names-CjhzmHOn.js +0 -15
  61. package/dist/component-scope-CvPba6O6.d.ts +0 -12
  62. package/dist/invalidation-listeners-Des3AHYs.js +0 -13
  63. package/dist/jsx-runtime-X2SX2y8V.d.ts +0 -2
  64. package/dist/resource-operation-BJiLoLYZ.js +0 -276
@@ -1,4 +1,278 @@
1
- import { n as getActiveRenderContext } from "./render-context-Clw3rFiO.js";
1
+ import { t as enqueueRuntimeLane, v as logger } from "./access-ePqdQqkV.js";
2
+ import { c as registerCommitOperationForInstance, d as getCurrentContextFrame, g as withAsyncResourceContext } from "./component-internal-CPjM4n8M.js";
3
+ import { f as getCurrentComponentInstance } from "./component-scope-CC3cojX9.js";
4
+ import { p as isPromiseLike } from "./lifecycle-batch-COgl8jMS.js";
5
+ import { t as brandSnapshotSource } from "./snapshot-source-BB42xgwb.js";
6
+ import { i as getNextRenderKey, l as throwSSRDataMissing, n as getActiveRenderContext, r as getCurrentRenderData, u as SSRDataMissingError } from "./render-context-Clw3rFiO.js";
7
+ import { t as state } from "./state-CVuQVjvK.js";
8
+ //#region src/runtime/resource-cell.ts
9
+ /**
10
+ * Pure, component-agnostic ResourceCell state machine.
11
+ * - Holds value/pending/error/generation/controller
12
+ * - Exposes a stable `snapshot` object: { value, pending, error, refresh }
13
+ * - Uses `withAsyncResourceContext` to bind the synchronous execution step
14
+ * to a captured frame. Continuations after await do not see the frame.
15
+ */
16
+ var ResourceCell = class {
17
+ constructor(fn, deps, resourceFrame) {
18
+ this.value = null;
19
+ this.pending = true;
20
+ this.error = null;
21
+ this.generation = 0;
22
+ this.controller = null;
23
+ this.deps = null;
24
+ this.resourceFrame = null;
25
+ this.subscribers = /* @__PURE__ */ new Set();
26
+ this.disposed = false;
27
+ this.fn = fn;
28
+ this.deps = deps ? deps.slice() : null;
29
+ this.resourceFrame = resourceFrame;
30
+ this.snapshot = brandSnapshotSource({
31
+ value: null,
32
+ pending: true,
33
+ error: null,
34
+ refresh: () => this.refresh()
35
+ });
36
+ }
37
+ setLoader(fn) {
38
+ this.fn = fn;
39
+ }
40
+ subscribe(cb) {
41
+ this.subscribers.add(cb);
42
+ return () => this.subscribers.delete(cb);
43
+ }
44
+ notifySubscribers() {
45
+ this.snapshot.value = this.value;
46
+ this.snapshot.pending = this.pending;
47
+ this.snapshot.error = this.error;
48
+ for (const cb of this.subscribers) cb();
49
+ }
50
+ start(ssr = false, notify = true) {
51
+ if (this.disposed) return;
52
+ const generation = this.generation;
53
+ this.controller?.abort();
54
+ const controller = new AbortController();
55
+ this.controller = controller;
56
+ this.pending = true;
57
+ this.error = null;
58
+ if (notify) this.notifySubscribers();
59
+ let result;
60
+ try {
61
+ result = withAsyncResourceContext(this.resourceFrame, () => this.fn({ signal: controller.signal }));
62
+ } catch (err) {
63
+ this.pending = false;
64
+ this.error = err instanceof Error ? err : new Error(String(err));
65
+ if (notify) this.notifySubscribers();
66
+ return;
67
+ }
68
+ if (!isPromiseLike(result)) {
69
+ this.value = result;
70
+ this.pending = false;
71
+ this.error = null;
72
+ if (notify) this.notifySubscribers();
73
+ return;
74
+ }
75
+ if (ssr) throwSSRDataMissing();
76
+ Promise.resolve(result).then((val) => {
77
+ if (this.generation !== generation) return;
78
+ if (this.controller !== controller) return;
79
+ if (controller.signal.aborted) return;
80
+ this.value = val;
81
+ this.pending = false;
82
+ this.error = null;
83
+ this.notifySubscribers();
84
+ }).catch((err) => {
85
+ if (this.generation !== generation) return;
86
+ if (this.controller !== controller) return;
87
+ if (controller.signal.aborted || err instanceof Error && err.name === "AbortError" || typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError") return;
88
+ this.pending = false;
89
+ this.error = err instanceof Error ? err : new Error(String(err));
90
+ try {
91
+ if (this.ownerName) logger.error(`[Askr] Async resource error in ${this.ownerName}:`, err);
92
+ else logger.error("[Askr] Async resource error:", err);
93
+ } catch {}
94
+ this.notifySubscribers();
95
+ });
96
+ }
97
+ refresh() {
98
+ if (this.disposed) return;
99
+ this.generation++;
100
+ this.controller?.abort();
101
+ this.start();
102
+ }
103
+ abort() {
104
+ this.controller?.abort();
105
+ }
106
+ dispose() {
107
+ if (this.disposed) return;
108
+ this.disposed = true;
109
+ this.controller?.abort();
110
+ this.subscribers.clear();
111
+ }
112
+ };
113
+ //#endregion
114
+ //#region src/runtime/resource-operation.ts
115
+ /**
116
+ * Resource primitive — simple, deterministic async primitive
117
+ * Usage: resource(fn, deps)
118
+ * - fn receives { signal }
119
+ * - captures execution context once at creation (synchronous step only)
120
+ * - executes at most once per generation; stale async results are ignored
121
+ * - refresh() cancels in-flight execution, increments generation and re-runs
122
+ * - exposes { value, pending, error, refresh }
123
+ * - during SSR, async results are disallowed and will throw synchronously
124
+ */
125
+ function resource(fn, deps = []) {
126
+ const instance = getCurrentComponentInstance();
127
+ const inst = instance;
128
+ if (!instance) {
129
+ const renderData = getCurrentRenderData()?.resources;
130
+ if (renderData && (getActiveRenderContext()?.resourceDataProvided || Object.keys(renderData).some((key) => key.startsWith("r:")))) {
131
+ const key = getNextRenderKey();
132
+ if (!(key in renderData)) throwSSRDataMissing();
133
+ const val = renderData[key];
134
+ return brandSnapshotSource({
135
+ value: val,
136
+ pending: false,
137
+ error: null,
138
+ refresh: () => {}
139
+ });
140
+ }
141
+ if (getActiveRenderContext()) throwSSRDataMissing();
142
+ throw new Error("[Askr] resource() must be called during component render inside an app. Do not create resources at module scope or outside render.");
143
+ }
144
+ const renderData = getCurrentRenderData()?.resources;
145
+ if (renderData && (getActiveRenderContext()?.resourceDataProvided || Object.keys(renderData).some((key) => key.startsWith("r:")))) {
146
+ const key = getNextRenderKey();
147
+ if (!(key in renderData)) throwSSRDataMissing();
148
+ const val = renderData[key];
149
+ const h = state({
150
+ cell: void 0,
151
+ snapshot: brandSnapshotSource({
152
+ value: val,
153
+ pending: false,
154
+ error: null,
155
+ refresh: () => {}
156
+ })
157
+ })();
158
+ h.snapshot.value = val;
159
+ h.snapshot.pending = false;
160
+ h.snapshot.error = null;
161
+ return h.snapshot;
162
+ }
163
+ const holder = state({
164
+ cell: void 0,
165
+ snapshot: brandSnapshotSource({
166
+ value: null,
167
+ pending: true,
168
+ error: null,
169
+ refresh: () => {}
170
+ })
171
+ });
172
+ const h = holder();
173
+ if (!h.cell) {
174
+ const cell = new ResourceCell(fn, deps, getCurrentContextFrame());
175
+ cell.ownerName = inst.fn?.name || "<anonymous>";
176
+ h.cell = cell;
177
+ h.snapshot = cell.snapshot;
178
+ const unsubscribe = cell.subscribe(() => {
179
+ const cur = holder();
180
+ cur.snapshot.value = cell.snapshot.value;
181
+ cur.snapshot.pending = cell.snapshot.pending;
182
+ cur.snapshot.error = cell.snapshot.error;
183
+ holder.set(cur);
184
+ try {
185
+ inst.notifyUpdate?.();
186
+ } catch {}
187
+ });
188
+ (inst.cleanupFns ??= []).push(() => {
189
+ unsubscribe();
190
+ cell.dispose();
191
+ });
192
+ if (inst.ssr) {
193
+ cell.start(true, false);
194
+ if (!cell.pending) {
195
+ const cur = holder();
196
+ cur.snapshot.value = cell.value;
197
+ cur.snapshot.pending = cell.pending;
198
+ cur.snapshot.error = cell.error;
199
+ }
200
+ } else {
201
+ const scheduledGeneration = cell.generation;
202
+ registerCommitOperationForInstance(inst, () => {
203
+ enqueueRuntimeLane("post", () => {
204
+ if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) return;
205
+ try {
206
+ cell.start(false, false);
207
+ } catch (err) {
208
+ const cur = holder();
209
+ cur.snapshot.value = cell.value;
210
+ cur.snapshot.pending = cell.pending;
211
+ cur.snapshot.error = err ?? null;
212
+ holder.set(cur);
213
+ inst.notifyUpdate?.();
214
+ return;
215
+ }
216
+ if (!cell.pending) {
217
+ const cur = holder();
218
+ cur.snapshot.value = cell.value;
219
+ cur.snapshot.pending = cell.pending;
220
+ cur.snapshot.error = cell.error;
221
+ holder.set(cur);
222
+ inst.notifyUpdate?.();
223
+ }
224
+ });
225
+ });
226
+ }
227
+ }
228
+ const cell = h.cell;
229
+ cell.setLoader(fn);
230
+ if (!cell.deps || cell.deps.length !== deps.length || cell.deps.some((d, i) => !Object.is(d, deps[i]))) {
231
+ cell.deps = deps.slice();
232
+ cell.generation++;
233
+ cell.pending = true;
234
+ cell.error = null;
235
+ h.snapshot.pending = true;
236
+ h.snapshot.error = null;
237
+ try {
238
+ if (inst.ssr) {
239
+ cell.start(true, false);
240
+ if (!cell.pending) {
241
+ const cur = holder();
242
+ cur.snapshot.value = cell.value;
243
+ cur.snapshot.pending = cell.pending;
244
+ cur.snapshot.error = cell.error;
245
+ }
246
+ } else {
247
+ const scheduledGeneration = cell.generation;
248
+ registerCommitOperationForInstance(inst, () => {
249
+ enqueueRuntimeLane("post", () => {
250
+ if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) return;
251
+ cell.start(false, false);
252
+ if (!cell.pending) {
253
+ const cur = holder();
254
+ cur.snapshot.value = cell.value;
255
+ cur.snapshot.pending = cell.pending;
256
+ cur.snapshot.error = cell.error;
257
+ holder.set(cur);
258
+ inst.notifyUpdate?.();
259
+ }
260
+ });
261
+ });
262
+ }
263
+ } catch (err) {
264
+ if (err instanceof SSRDataMissingError) throw err;
265
+ cell.error = err;
266
+ cell.pending = false;
267
+ const cur = holder();
268
+ cur.snapshot.value = cell.value;
269
+ cur.snapshot.pending = cell.pending;
270
+ cur.snapshot.error = cell.error;
271
+ }
272
+ }
273
+ return h.snapshot;
274
+ }
275
+ //#endregion
2
276
  //#region src/router/match.ts
3
277
  function splitPathSegments(path) {
4
278
  const normalized = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
@@ -510,4 +784,4 @@ function _resolveRouteMatchFromRoutes(pathname, routeList) {
510
784
  return findBestResolvedRouteFromRoutes(pathname, routeList);
511
785
  }
512
786
  //#endregion
513
- export { insertRecordSorted as A, getCurrentPathPrefix as C, getRouteRecords as D, getRouteList as E, snapshotRouteState as F, computeRank as I, normalizeRouteSegmentName as L, pushRegistrationScope as M, restoreRouteState as N, hasActivePageScope as O, setDefaultRouteAuthOptions as P, parseSegments as R, getCurrentPageScope as S, getDefaultRouteAuthOptions as T, getCurrentInheritedAuthRequirements as _, resolveRouteFromRoutes as a, getCurrentLayoutChain as b, deepFreeze as c, _setActiveRouteAuthOptions as d, addRouteToStores as f, getActiveRoutes as g, getActiveRouteAuthOptions as h, getMatchingRouteRecord as i, lockRouteRegistration as j, hasRegisteredRoutes as k, makeQuery as l, clearRouteState as m, computeMatchesFromRoutes as n, buildRouteContext as o, assertRouteRegistrationUnlocked as p, computeRouteActivityMatches as r, buildRouteContextBase as s, _resolveRouteMatchFromRoutes as t, parseLocation as u, getCurrentInheritedMeta as v, getCurrentScopeKind as w, getCurrentPageChain as x, getCurrentInheritedPolicies as y };
787
+ export { insertRecordSorted as A, getCurrentPathPrefix as C, getRouteRecords as D, getRouteList as E, snapshotRouteState as F, computeRank as I, normalizeRouteSegmentName as L, pushRegistrationScope as M, restoreRouteState as N, hasActivePageScope as O, setDefaultRouteAuthOptions as P, parseSegments as R, getCurrentPageScope as S, getDefaultRouteAuthOptions as T, getCurrentInheritedAuthRequirements as _, resolveRouteFromRoutes as a, getCurrentLayoutChain as b, deepFreeze as c, _setActiveRouteAuthOptions as d, addRouteToStores as f, getActiveRoutes as g, getActiveRouteAuthOptions as h, getMatchingRouteRecord as i, lockRouteRegistration as j, hasRegisteredRoutes as k, makeQuery as l, clearRouteState as m, computeMatchesFromRoutes as n, buildRouteContext as o, assertRouteRegistrationUnlocked as p, computeRouteActivityMatches as r, buildRouteContextBase as s, _resolveRouteMatchFromRoutes as t, parseLocation as u, getCurrentInheritedMeta as v, getCurrentScopeKind as w, getCurrentPageChain as x, getCurrentInheritedPolicies as y, resource as z };
@@ -1,8 +1,9 @@
1
1
  import { c as RenderableChild } from "../control-DO-iBEtV.js";
2
2
  import { r as JSXElement, s as Props } from "../jsx-Dvr2SGRG.js";
3
3
  import "../types-DEhT-Nrv.js";
4
- import { A as RouteRecord, B as RouteSnapshot, C as RouteMetaSource, D as RoutePathParams, E as RouteParams, F as RouteRenderResult, I as RouteRequestOptions, L as RouteRequestResult, M as RouteRefSearch, N as RouteRegistry, O as RoutePolicy, P as RouteRegistryOptions, R as RouteSearch, S as RouteMeta, T as RouteOptions, _ as RouteDefinition, a as AccessRedirectDecision, b as RouteManifest, c as LayoutScopeRecord, d as ParsedSegment, f as Route, g as RouteContext, h as RouteComponent, i as AccessDenyStatus, j as RouteRef, k as RouteQuery, l as PageHelperOptions, m as RouteAuthResolver, n as AccessDecision, o as AccessRedirectStatus, p as RouteAuthOptions, r as AccessDenyDecision, s as GroupHelperOptions, t as AccessAllowDecision, u as PageScopeRecord, v as RouteDestination, w as RouteMode, x as RouteMatch, y as RouteHandler, z as RouteSearchValue } from "../router-C0XQA62W.js";
5
- import { a as ScrollRestorationOptions, i as NavigationScrollBehavior, n as NavigateOptions, r as HistoryScrollBehavior, t as navigate } from "../navigate-CV-RDqYP.js";
4
+ import { A as RouteRecord, B as RouteSnapshot, C as RouteMetaSource, D as RoutePathParams, E as RouteParams, F as RouteRenderResult, I as RouteRequestOptions, L as RouteRequestResult, M as RouteRefSearch, N as RouteRegistry, O as RoutePolicy, P as RouteRegistryOptions, R as RouteSearch, S as RouteMeta, T as RouteOptions, _ as RouteDefinition, a as AccessRedirectDecision, b as RouteManifest, c as LayoutScopeRecord, d as ParsedSegment, f as Route, g as RouteContext, h as RouteComponent, i as AccessDenyStatus, j as RouteRef, k as RouteQuery, l as PageHelperOptions, m as RouteAuthResolver, n as AccessDecision, o as AccessRedirectStatus, p as RouteAuthOptions, r as AccessDenyDecision, s as GroupHelperOptions, t as AccessAllowDecision, u as PageScopeRecord, v as RouteDestination, w as RouteMode, x as RouteMatch, y as RouteHandler, z as RouteSearchValue } from "../router-C3vENhUh.js";
5
+ import { a as ScrollRestorationOptions, i as NavigationScrollBehavior, n as NavigateOptions, r as HistoryScrollBehavior, t as navigate } from "../navigate-Cbq6QHVl.js";
6
+ import { i as onRouteChange, n as RouteChangeOptions, r as currentRoute, t as RouteChangeCleanup } from "../activity-DZ-NCYr8.js";
6
7
  import { AuthContext, AuthContext as AuthContext$1, AuthRequirement, AuthRequirement as AuthRequirement$1 } from "@askrjs/auth";
7
8
  import { ObjectSchema } from "@askrjs/schema";
8
9
  //#region src/router/route-query.d.ts
@@ -39,9 +40,6 @@ declare function fallback(Component: RouteComponent): void;
39
40
  declare function route<const TPath extends string, const TSearchSchema extends ObjectSchema<RouteSearch> | undefined = undefined>(path: TPath, Component: RouteComponent<RoutePathParams<TPath>>, options?: RouteOptions<RoutePathParams<TPath>, TSearchSchema>): RouteRef<RoutePathParams<TPath>, RouteRefSearch<TSearchSchema>>;
40
41
  declare function route<const TPath extends string, TComponent extends AnyRouteComponent, const TSearchSchema extends ObjectSchema<RouteSearch> | undefined = undefined>(path: TPath, Component: CompatibleRouteComponent<TPath, TComponent>, options?: RouteOptionsForComponent<TPath, TComponent, TSearchSchema>): RouteRef<RoutePathParams<TPath>, RouteRefSearch<TSearchSchema>>;
41
42
  //#endregion
42
- //#region src/router/activity.d.ts
43
- declare function currentRoute<TParams extends RouteParams = RouteParams>(): RouteSnapshot<TParams>;
44
- //#endregion
45
43
  //#region src/router/auth.d.ts
46
44
  /** Return the identity resolved for the route currently being rendered. */
47
45
  declare function currentAuth(): AuthContext$1;
@@ -128,6 +126,8 @@ type LinkBaseProps = Omit<Props, 'children' | 'href' | 'class' | 'rel' | 'target
128
126
  * Optional aria-label for accessibility when link text isn't descriptive enough.
129
127
  */
130
128
  'aria-label'?: string;
129
+ onPress?: (event: Event) => void;
130
+ onClick?: (event: MouseEvent) => void;
131
131
  };
132
132
  type LinkProps = LinkBaseProps & ({
133
133
  href: string;
@@ -163,6 +163,6 @@ type LinkProps = LinkBaseProps & ({
163
163
  * - Keyboard handling automatic
164
164
  * - Composable via mergeProps
165
165
  */
166
- declare function Link({ href: suppliedHref, to, class: className, children, rel, target, 'aria-current': ariaCurrent, 'aria-label': ariaLabel, ...rest }: LinkProps): JSXElement;
166
+ declare function Link({ href: suppliedHref, to, class: className, children, rel, target, 'aria-current': ariaCurrent, 'aria-label': ariaLabel, onPress, onClick, ...rest }: LinkProps): JSXElement;
167
167
  //#endregion
168
- export { type AccessDecision, type AccessDenyDecision, type AccessRedirectDecision, type AuthContext, type AuthRequirement, type Deferred, type DeferredState, type GroupHelperOptions, type HistoryScrollBehavior, type LayoutScopeRecord, type LazyRouteComponent, Link, type LinkProps, type NavigateOptions, type NavigationScrollBehavior, Outlet, type PageHelperOptions, type PageScopeRecord, type ParsedSegment, Resolve, type ResolveProps, type Route, type RouteAuthOptions, type RouteAuthResolver, type RouteComponent, type RouteContext, type RouteDefinition, type RouteDestination, type RouteHandler, type RouteManifest, type RouteMatch, type RouteMeta, type RouteMetaSource, type RouteMode, type RouteOptions, type RouteParams, type RoutePathParams, type RoutePolicy, type RouteQuery, type RouteQueryParamInput, type RouteQueryParamValue, type RouteQueryUpdater, type RouteQueryUpdates, type RouteRecord, type RouteRef, type RouteRegistry, type RouteRegistryOptions, type RouteRenderResult, type RouteRequestOptions, type RouteRequestResult, type RouteSearch, type RouteSearchValue, type RouteSnapshot, type ScrollRestorationOptions, type UpdateRouteQueryOptions, allow, createRouteRegistry, currentAuth, currentRoute, defer, deny, fallback, forbidden, group, index, isDeferred, lazy, navigate, notFound, page, reconcileRouteMeta, redirect, resolveDeferredValues, resolveRouteMeta, resolveRouteRequest, route, routeData, serializeRouteMeta, to$1 as to, unauthorized, updateRouteQuery };
168
+ export { type AccessDecision, type AccessDenyDecision, type AccessRedirectDecision, type AuthContext, type AuthRequirement, type Deferred, type DeferredState, type GroupHelperOptions, type HistoryScrollBehavior, type LayoutScopeRecord, type LazyRouteComponent, Link, type LinkProps, type NavigateOptions, type NavigationScrollBehavior, Outlet, type PageHelperOptions, type PageScopeRecord, type ParsedSegment, Resolve, type ResolveProps, type Route, type RouteAuthOptions, type RouteAuthResolver, type RouteChangeCleanup, type RouteChangeOptions, type RouteComponent, type RouteContext, type RouteDefinition, type RouteDestination, type RouteHandler, type RouteManifest, type RouteMatch, type RouteMeta, type RouteMetaSource, type RouteMode, type RouteOptions, type RouteParams, type RoutePathParams, type RoutePolicy, type RouteQuery, type RouteQueryParamInput, type RouteQueryParamValue, type RouteQueryUpdater, type RouteQueryUpdates, type RouteRecord, type RouteRef, type RouteRegistry, type RouteRegistryOptions, type RouteRenderResult, type RouteRequestOptions, type RouteRequestResult, type RouteSearch, type RouteSearchValue, type RouteSnapshot, type ScrollRestorationOptions, type UpdateRouteQueryOptions, allow, createRouteRegistry, currentAuth, currentRoute, defer, deny, fallback, forbidden, group, index, isDeferred, lazy, navigate, notFound, onRouteChange, page, reconcileRouteMeta, redirect, resolveDeferredValues, resolveRouteMeta, resolveRouteRequest, route, routeData, serializeRouteMeta, to$1 as to, unauthorized, updateRouteQuery };
@@ -1,7 +1,9 @@
1
- import { C as lazy, a as resolveDeferredValues, b as Outlet, i as isDeferred, l as createRouteRegistry, m as resolveRouteRequest, n as Resolve, r as defer, s as routeData, y as currentAuth } from "../deferred-zHtUheIQ.js";
1
+ import { C as lazy, a as resolveDeferredValues, b as Outlet, i as isDeferred, l as createRouteRegistry, m as resolveRouteRequest, n as Resolve, r as defer, s as routeData, y as currentAuth } from "../deferred-Cr58yIQD.js";
2
2
  import { jsx } from "../jsx-runtime.js";
3
- import { a as route, i as page, n as group, r as index, t as fallback } from "../authoring-uaJ556aG.js";
4
- import { _ as currentRoute, a as serializeRouteMeta, c as getCurrentHref, d as parseTargetUrl, h as syncRegisteredRouteSnapshot, i as resolveRouteMeta, l as getRegisteredAppsSnapshot, m as syncAppRegistrationLocation, n as navigate, p as setCurrentRouteLocation, r as reconcileRouteMeta, s as saveScrollPosition, u as getWindowHref } from "../navigate-DpR-cML9.js";
3
+ import { a as isSafeHref } from "../csp-nonce-CwSRP3Jl.js";
4
+ import { a as route, i as page, n as group, r as index, t as fallback } from "../authoring-CXmUcaCe.js";
5
+ import { n as onRouteChange, t as currentRoute } from "../activity-C2tPn9Fr.js";
6
+ import { a as serializeRouteMeta, c as getCurrentHref, d as parseTargetUrl, h as syncRegisteredRouteSnapshot, i as resolveRouteMeta, l as getRegisteredAppsSnapshot, m as syncAppRegistrationLocation, n as navigate, p as setCurrentRouteLocation, r as reconcileRouteMeta, s as saveScrollPosition, u as getWindowHref } from "../navigate-CuGQzS_Y.js";
5
7
  import { o as mergeProps } from "../compose-ref-Cc_YkUpM.js";
6
8
  import { t as applyInteractionPolicy } from "../interactions-w7gQN4ha.js";
7
9
  //#region src/router/route-query.ts
@@ -162,22 +164,31 @@ function isSameOriginNavigableHref(href) {
162
164
  * - Keyboard handling automatic
163
165
  * - Composable via mergeProps
164
166
  */
165
- function Link({ href: suppliedHref, to, class: className, children, rel, target, "aria-current": ariaCurrent, "aria-label": ariaLabel, ...rest }) {
167
+ function Link({ href: suppliedHref, to, class: className, children, rel, target, "aria-current": ariaCurrent, "aria-label": ariaLabel, onPress, onClick, ...rest }) {
166
168
  const href = to?.href ?? suppliedHref;
167
169
  if (!href) throw new Error("Link requires href or to.");
170
+ if (!isSafeHref(href)) throw new TypeError("Link href uses an unsafe URL scheme.");
171
+ const handleNavigation = (e) => {
172
+ if (e.defaultPrevented) return;
173
+ const event = e;
174
+ if ((event.button ?? 0) !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
175
+ if (target || !isSameOriginNavigableHref(href)) return;
176
+ event.preventDefault();
177
+ navigate(href);
178
+ };
179
+ const interaction = applyInteractionPolicy({
180
+ isNative: true,
181
+ disabled: false,
182
+ onPress: handleNavigation
183
+ });
184
+ const consumerActivation = (e) => {
185
+ onPress?.(e);
186
+ onClick?.(e);
187
+ };
168
188
  return /* @__PURE__ */ jsx("a", {
169
- ...mergeProps(rest, {
170
- ...applyInteractionPolicy({
171
- isNative: true,
172
- disabled: false,
173
- onPress: (e) => {
174
- const event = e;
175
- if ((event.button ?? 0) !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
176
- if (target || !isSameOriginNavigableHref(href)) return;
177
- event.preventDefault();
178
- navigate(href);
179
- }
180
- }),
189
+ ...mergeProps(interaction, {
190
+ ...rest,
191
+ onClick: consumerActivation,
181
192
  href,
182
193
  class: className,
183
194
  rel,
@@ -189,4 +200,4 @@ function Link({ href: suppliedHref, to, class: className, children, rel, target,
189
200
  });
190
201
  }
191
202
  //#endregion
192
- export { Link, Outlet, Resolve, allow, createRouteRegistry, currentAuth, currentRoute, defer, deny, fallback, forbidden, group, index, isDeferred, lazy, navigate, notFound, page, reconcileRouteMeta, redirect, resolveDeferredValues, resolveRouteMeta, resolveRouteRequest, route, routeData, serializeRouteMeta, to, unauthorized, updateRouteQuery };
203
+ export { Link, Outlet, Resolve, allow, createRouteRegistry, currentAuth, currentRoute, defer, deny, fallback, forbidden, group, index, isDeferred, lazy, navigate, notFound, onRouteChange, page, reconcileRouteMeta, redirect, resolveDeferredValues, resolveRouteMeta, resolveRouteRequest, route, routeData, serializeRouteMeta, to, unauthorized, updateRouteQuery };
@@ -1,6 +1,6 @@
1
1
  import { c as RenderableChild } from "./control-DO-iBEtV.js";
2
2
  import { D as QueryPrefetchContext, u as CoreTelemetry } from "./index-Bki2rqcr.js";
3
- import { t as ActionDescriptor } from "./index-DSClfSoC.js";
3
+ import { t as ActionDescriptor } from "./index-DxYJPVC4.js";
4
4
  import { AuthContext, AuthRequirement } from "@askrjs/auth";
5
5
  import { InferSchema, ObjectSchema } from "@askrjs/schema";
6
6
  //#region src/common/router.d.ts
@@ -261,6 +261,14 @@ function getOrCreateSelectorHook(instance, hookIndex, source, equals) {
261
261
  store.set(hookIndex, created);
262
262
  return created;
263
263
  }
264
+ /**
265
+ * Creates a render-scoped predicate for keyed membership in reactive list rows.
266
+ *
267
+ * Use this when a `<For>` child needs to compare each stable item with a
268
+ * changing selected value. Unlike a plain closure capture, the predicate
269
+ * subscribes the affected rows and updates them without rebuilding the list.
270
+ * Must be called during component render.
271
+ */
264
272
  function selector(source, equals = Object.is) {
265
273
  markReadableUsage(source);
266
274
  const instance = getCurrentInstance();
@@ -1,5 +1,5 @@
1
1
  import { d as DocumentRenderContext, f as DocumentRenderer, u as DocumentRenderArgs } from "../control-DO-iBEtV.js";
2
- import { D as RoutePathParams, N as RouteRegistry, O as RoutePolicy, T as RouteOptions, y as RouteHandler } from "../router-C0XQA62W.js";
2
+ import { D as RoutePathParams, N as RouteRegistry, O as RoutePolicy, T as RouteOptions, y as RouteHandler } from "../router-C3vENhUh.js";
3
3
  import { AuthRequirement } from "@askrjs/auth";
4
4
  import "node:fs/promises";
5
5
  //#region src/ssg/types.d.ts
@@ -105,6 +105,13 @@ interface RouteRenderResult {
105
105
  written: boolean;
106
106
  /** Error message if rendering failed */
107
107
  error?: string;
108
+ /** Original exception preserved for programmatic diagnostics. */
109
+ errorCause?: unknown;
110
+ /** Route and phase context for the original exception. */
111
+ errorContext?: {
112
+ route: string;
113
+ phase: 'load' | 'render' | 'write';
114
+ };
108
115
  }
109
116
  /** Overall result from SSG generation */
110
117
  interface SSGResult {
package/dist/ssg/index.js CHANGED
@@ -1,14 +1,18 @@
1
1
  import "../access-ePqdQqkV.js";
2
- import { S as getRenderHandler, a as resolveDeferredValues, l as createRouteRegistry, u as bindResolvedRouteData } from "../deferred-zHtUheIQ.js";
3
- import { a as route } from "../authoring-uaJ556aG.js";
2
+ import { S as getRenderHandler, a as resolveDeferredValues, l as createRouteRegistry, u as bindResolvedRouteData } from "../deferred-Cr58yIQD.js";
3
+ import { a as route } from "../authoring-CXmUcaCe.js";
4
4
  import { n as renderDocument } from "../ssr-CbQO5xd3.js";
5
- import { n as renderToString } from "../ssr-vE-KUgDV.js";
5
+ import { n as renderToString } from "../ssr-BSUA9Y64.js";
6
6
  import * as fs from "node:fs/promises";
7
7
  import * as pathModule from "node:path";
8
8
  import * as os from "node:os";
9
9
  import * as fsSync from "node:fs";
10
10
  //#region src/ssg/route-utils.ts
11
+ function assertSafeRoutePath(pathStr) {
12
+ if (!pathStr.startsWith("/") || pathStr.startsWith("//") || pathStr.includes("\\") || pathStr.includes("\0") || pathStr.split("/").some((segment) => segment === "." || segment === "..")) throw new Error(`SSG route path must be an absolute URL path without dot segments or backslashes: ${pathStr}`);
13
+ }
11
14
  function getOutputFilePath(pathStr) {
15
+ assertSafeRoutePath(pathStr);
12
16
  if (pathStr === "/") return "index.html";
13
17
  return `${pathStr.replace(/^\/|\/$/g, "")}/index.html`;
14
18
  }
@@ -153,6 +157,7 @@ async function batchRenderRoutes(routes, options = {}) {
153
157
  const baseData = resolvedData.hasData ? resolvedData.data : void 0;
154
158
  const resourceCount = resolvedData.hasData && baseData ? Object.keys(baseData).length : 0;
155
159
  const mergedHandler = route$2.handler;
160
+ let phase = route$2.loader ? "load" : "render";
156
161
  try {
157
162
  let routeData;
158
163
  let hasRouteData = false;
@@ -182,11 +187,13 @@ async function batchRenderRoutes(routes, options = {}) {
182
187
  hasRouteData = true;
183
188
  }
184
189
  const renderedHandler = hasRouteData ? bindResolvedRouteData(mergedHandler, routeData) : mergedHandler;
190
+ const registry = hasRouteData || !sharedRegistry ? createRouteRegistry(() => {
191
+ route(route$2.path, renderedHandler, { namespace: route$2.namespace });
192
+ }) : sharedRegistry;
193
+ phase = "render";
185
194
  const html = renderToString({
186
195
  url,
187
- registry: hasRouteData || !sharedRegistry ? createRouteRegistry(() => {
188
- route(route$2.path, renderedHandler, { namespace: route$2.namespace });
189
- }) : sharedRegistry,
196
+ registry,
190
197
  seed,
191
198
  data: baseData,
192
199
  document: document === void 0 ? void 0 : ({ appHtml }) => renderDocument(document, {
@@ -231,7 +238,12 @@ async function batchRenderRoutes(routes, options = {}) {
231
238
  status: "error",
232
239
  reason: "full",
233
240
  written: false,
234
- error: error instanceof Error ? error.message : String(error)
241
+ error: error instanceof Error ? error.message : String(error),
242
+ errorCause: error,
243
+ errorContext: {
244
+ route: url,
245
+ phase
246
+ }
235
247
  };
236
248
  }
237
249
  };
@@ -247,6 +259,15 @@ async function batchRenderRoutes(routes, options = {}) {
247
259
  return results;
248
260
  }
249
261
  //#endregion
262
+ //#region src/ssg/output-path.ts
263
+ function resolveSsgOutputPath(outputDir, filePath) {
264
+ const root = pathModule.resolve(outputDir);
265
+ const candidate = pathModule.resolve(root, filePath);
266
+ const relative = pathModule.relative(root, candidate);
267
+ if (!relative || relative === ".." || relative.startsWith(`..${pathModule.sep}`) || pathModule.isAbsolute(relative)) throw new Error(`SSG output path must stay inside outputDir: ${filePath}`);
268
+ return candidate;
269
+ }
270
+ //#endregion
250
271
  //#region src/ssg/write-static-files.ts
251
272
  /**
252
273
  * File I/O for Static Site Generation
@@ -262,17 +283,22 @@ async function writeStaticFiles(results, outputDir, options = {}, fileOperations
262
283
  await fileOperations.mkdir(outputDir, { recursive: true });
263
284
  for (const result of results) {
264
285
  if (result.status !== "removed") continue;
265
- const fullPath = pathModule.join(outputDir, result.filePath);
286
+ const fullPath = resolveSsgOutputPath(outputDir, result.filePath);
266
287
  if (fsSync.existsSync(fullPath)) {
267
288
  await fileOperations.rm(fullPath, { force: true });
268
289
  await pruneEmptyDirs(pathModule.dirname(fullPath), outputDir, fileOperations);
269
290
  }
270
291
  }
271
292
  const pendingWrites = [];
293
+ const reportedErrors = /* @__PURE__ */ new Set();
272
294
  for (let index = 0; index < results.length; index += 1) {
273
295
  const result = results[index];
274
296
  if (result.status === "error") {
275
- console.warn(`Skipping failed route: ${result.path} - ${result.error}`);
297
+ const diagnostic = `Skipping failed route: ${result.path} - ${result.error}`;
298
+ if (!reportedErrors.has(diagnostic)) {
299
+ reportedErrors.add(diagnostic);
300
+ console.warn(diagnostic);
301
+ }
276
302
  continue;
277
303
  }
278
304
  if (result.status === "success" && result.written) pendingWrites.push(result);
@@ -280,7 +306,7 @@ async function writeStaticFiles(results, outputDir, options = {}, fileOperations
280
306
  const directories = [];
281
307
  const seenDirectories = /* @__PURE__ */ new Set();
282
308
  for (const result of pendingWrites) {
283
- const dir = pathModule.dirname(pathModule.join(outputDir, result.filePath));
309
+ const dir = pathModule.dirname(resolveSsgOutputPath(outputDir, result.filePath));
284
310
  if (!seenDirectories.has(dir)) {
285
311
  seenDirectories.add(dir);
286
312
  directories.push(dir);
@@ -295,7 +321,7 @@ async function writeStaticFiles(results, outputDir, options = {}, fileOperations
295
321
  nextIndex += 1;
296
322
  if (current >= pendingWrites.length) return;
297
323
  const result = pendingWrites[current];
298
- const fullPath = pathModule.join(outputDir, result.filePath);
324
+ const fullPath = resolveSsgOutputPath(outputDir, result.filePath);
299
325
  const tempPath = pathModule.join(pathModule.dirname(fullPath), `.${pathModule.basename(fullPath)}.askr-${process.pid}-${current}.tmp`);
300
326
  try {
301
327
  await fileOperations.writeFile(tempPath, result.html, "utf8");
@@ -310,11 +336,12 @@ async function writeStaticFiles(results, outputDir, options = {}, fileOperations
310
336
  async function pruneEmptyDirs(startDir, rootDir, fileOperations) {
311
337
  let current = startDir;
312
338
  const normalizedRoot = pathModule.resolve(rootDir);
313
- while (current.startsWith(normalizedRoot)) {
339
+ while (pathModule.resolve(current) !== normalizedRoot) {
340
+ const relative = pathModule.relative(normalizedRoot, current);
341
+ if (relative === ".." || relative.startsWith(`..${pathModule.sep}`) || pathModule.isAbsolute(relative)) break;
314
342
  if (!fsSync.existsSync(current)) break;
315
343
  if ((await fileOperations.readdir(current)).length > 0) break;
316
344
  await fileOperations.rmdir(current);
317
- if (pathModule.resolve(current) === normalizedRoot) break;
318
345
  current = pathModule.dirname(current);
319
346
  }
320
347
  }
@@ -418,7 +445,7 @@ function hashHtml(html) {
418
445
  return (hash >>> 0).toString(16).padStart(8, "0");
419
446
  }
420
447
  function getIncrementalManifestPath(outputDir) {
421
- return pathModule.join(outputDir, MANIFEST_DIR, MANIFEST_FILE);
448
+ return resolveSsgOutputPath(outputDir, pathModule.join(MANIFEST_DIR, MANIFEST_FILE));
422
449
  }
423
450
  function readIncrementalManifest(outputDir, seed) {
424
451
  const manifestPath = getIncrementalManifestPath(outputDir);
@@ -427,7 +454,7 @@ function readIncrementalManifest(outputDir, seed) {
427
454
  const raw = fsSync.readFileSync(manifestPath, "utf8");
428
455
  const parsed = JSON.parse(raw);
429
456
  if (parsed.schemaVersion !== 1 || parsed.seed !== seed || !Array.isArray(parsed.routes)) return null;
430
- const routes = parsed.routes.filter((entry) => !!entry && typeof entry.routeId === "string" && typeof entry.path === "string" && typeof entry.filePath === "string" && Array.isArray(entry.invalidationKeys) && (entry.htmlHash === null || typeof entry.htmlHash === "string") && (entry.lastStatus === "success" || entry.lastStatus === "error"));
457
+ const routes = parsed.routes.filter((entry) => !!entry && typeof entry.routeId === "string" && typeof entry.path === "string" && typeof entry.filePath === "string" && isContainedManifestFilePath(outputDir, entry.filePath) && Array.isArray(entry.invalidationKeys) && (entry.htmlHash === null || typeof entry.htmlHash === "string") && (entry.lastStatus === "success" || entry.lastStatus === "error"));
431
458
  if (routes.length !== parsed.routes.length) return null;
432
459
  return {
433
460
  schemaVersion: 1,
@@ -439,13 +466,22 @@ function readIncrementalManifest(outputDir, seed) {
439
466
  return null;
440
467
  }
441
468
  }
469
+ function isContainedManifestFilePath(outputDir, filePath) {
470
+ try {
471
+ resolveSsgOutputPath(outputDir, filePath);
472
+ return true;
473
+ } catch {
474
+ return false;
475
+ }
476
+ }
442
477
  async function writeIncrementalManifest(manifest, outputDir) {
478
+ if (!manifest.routes.every((entry) => isContainedManifestFilePath(outputDir, entry.filePath))) throw new Error("SSG manifest contains a path outside outputDir.");
443
479
  const manifestPath = getIncrementalManifestPath(outputDir);
444
480
  await fs.mkdir(pathModule.dirname(manifestPath), { recursive: true });
445
481
  await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
446
482
  }
447
483
  function getExistingOutputFileSize(outputDir, filePath) {
448
- const fullPath = pathModule.join(outputDir, filePath);
484
+ const fullPath = resolveSsgOutputPath(outputDir, filePath);
449
485
  if (!fsSync.existsSync(fullPath)) return 0;
450
486
  try {
451
487
  return fsSync.statSync(fullPath).size;
@@ -454,7 +490,7 @@ function getExistingOutputFileSize(outputDir, filePath) {
454
490
  }
455
491
  }
456
492
  function outputFileExists(outputDir, filePath) {
457
- return fsSync.existsSync(pathModule.join(outputDir, filePath));
493
+ return fsSync.existsSync(resolveSsgOutputPath(outputDir, filePath));
458
494
  }
459
495
  //#endregion
460
496
  //#region src/ssg/generation-plan.ts
@@ -1,7 +1,7 @@
1
1
  import { c as RenderableChild, d as DocumentRenderContext, f as DocumentRenderer, p as SSRData, u as DocumentRenderArgs } from "../control-DO-iBEtV.js";
2
2
  import { r as JSXElement, s as Props } from "../jsx-Dvr2SGRG.js";
3
3
  import { D as QueryPrefetchContext, n as ServerQueryRegistry, u as CoreTelemetry, v as DataRuntime } from "../index-Bki2rqcr.js";
4
- import { A as RouteRecord, L as RouteRequestResult, N as RouteRegistry, a as AccessRedirectDecision, f as Route, p as RouteAuthOptions, r as AccessDenyDecision, y as RouteHandler } from "../router-C0XQA62W.js";
4
+ import { A as RouteRecord, L as RouteRequestResult, N as RouteRegistry, a as AccessRedirectDecision, f as Route, p as RouteAuthOptions, r as AccessDenyDecision, y as RouteHandler } from "../router-C3vENhUh.js";
5
5
  import { AuthContext } from "@askrjs/auth";
6
6
  //#region src/common/page-render-envelope.d.ts
7
7
  declare const PAGE_RENDER_ENVELOPE_VERSION: 1;
@@ -19,6 +19,19 @@ interface DeferredBoundaryRegistration {
19
19
  fulfilled(value: unknown): RenderableChild;
20
20
  rejected(error: unknown): RenderableChild;
21
21
  }
22
+ interface SSRPortalHostRegistration {
23
+ token: string;
24
+ automatic: boolean;
25
+ }
26
+ interface SSRPortalSlot {
27
+ hasValue: boolean;
28
+ value: RenderableChild | undefined;
29
+ hosts: SSRPortalHostRegistration[];
30
+ }
31
+ interface SSRPortalState {
32
+ slots: Map<object, SSRPortalSlot>;
33
+ nextHostId: number;
34
+ }
22
35
  //#endregion
23
36
  //#region src/common/ssr-errors.d.ts
24
37
  /**
@@ -49,6 +62,7 @@ interface RenderContext {
49
62
  renderData: PageRenderEnvelope | null;
50
63
  hydrationData: PageRenderEnvelope | null;
51
64
  deferredBoundaries: DeferredBoundaryRegistration[];
65
+ ssrPortals: SSRPortalState;
52
66
  cspNonce?: string;
53
67
  }
54
68
  declare function createRenderContext(seed?: number, opts?: {