ignotum 0.0.13 → 0.0.15

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 (131) hide show
  1. package/README.md +6 -6
  2. package/dist/cli/bin.mjs +6111 -4448
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/cli/emscripten-module-D5GzfUNv.mjs +533 -0
  5. package/dist/cli/emscripten-module-D5GzfUNv.mjs.map +1 -0
  6. package/dist/cli/ffi-7IElU4Co.mjs +148 -0
  7. package/dist/cli/ffi-7IElU4Co.mjs.map +1 -0
  8. package/dist/cli/managed-release-WHYLZwaS.mjs +10902 -0
  9. package/dist/cli/managed-release-WHYLZwaS.mjs.map +1 -0
  10. package/dist/cli/managed-release.d.mts +1 -0
  11. package/dist/cli/managed-release.mjs +14 -0
  12. package/dist/cli/managed-release.mjs.map +1 -0
  13. package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs +1289 -0
  14. package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs.map +1 -0
  15. package/dist/runtime/{api-Cv3hMbzo.d.ts → api-B4fwp5a2.d.ts} +4 -4
  16. package/dist/runtime/bootstrap-BISQSn_O.js +916 -0
  17. package/dist/runtime/bootstrap-BISQSn_O.js.map +1 -0
  18. package/dist/runtime/bootstrap-BTFScKAc.d.ts +545 -0
  19. package/dist/runtime/client.d.ts +50 -15
  20. package/dist/runtime/client.js +7 -1138
  21. package/dist/runtime/client.js.map +1 -1
  22. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js +269 -0
  23. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js.map +1 -0
  24. package/dist/runtime/{id-Bt9XWRGL.js → id--1m0NuUL.js} +4 -4
  25. package/dist/runtime/id--1m0NuUL.js.map +1 -0
  26. package/dist/runtime/{id-BzFHf3Wo-DGPCjgrf.d.ts → id-7an3nxTu-DZJSM5xh.d.ts} +2 -2
  27. package/dist/runtime/id-CBOt2kDo.d.ts +1 -0
  28. package/dist/runtime/identity-QjtJRxBD.d.ts +2 -0
  29. package/dist/runtime/image-Djtjy4Z8.js +19 -0
  30. package/dist/runtime/image-Djtjy4Z8.js.map +1 -0
  31. package/dist/runtime/index-D4seNHlX.d.ts +184 -0
  32. package/dist/runtime/internal/api.d.ts +1 -1
  33. package/dist/runtime/internal/api.js +30 -1
  34. package/dist/runtime/internal/api.js.map +1 -0
  35. package/dist/runtime/internal/bootstrap.d.ts +2 -0
  36. package/dist/runtime/internal/bootstrap.js +2 -0
  37. package/dist/runtime/internal/client.d.ts +2 -0
  38. package/dist/runtime/internal/client.js +75 -0
  39. package/dist/runtime/internal/client.js.map +1 -0
  40. package/dist/runtime/internal/host.d.ts +25 -8
  41. package/dist/runtime/internal/host.js +27 -6
  42. package/dist/runtime/internal/host.js.map +1 -1
  43. package/dist/runtime/internal/routes.d.ts +2 -0
  44. package/dist/runtime/internal/routes.js +2 -0
  45. package/dist/runtime/internal/server.d.ts +1 -1
  46. package/dist/runtime/internal/server.js +1 -1
  47. package/dist/runtime/internal/types.d.ts +2 -1
  48. package/dist/runtime/internal/types.js +1 -1
  49. package/dist/runtime/pagination-CBOt2kDo.d.ts +1 -0
  50. package/dist/runtime/pagination-D3qd6s9N.js +33 -0
  51. package/dist/runtime/pagination-D3qd6s9N.js.map +1 -0
  52. package/dist/runtime/result-BkziOG1L.d.ts +1 -0
  53. package/dist/runtime/router-BSBI1oN1.js +2377 -0
  54. package/dist/runtime/router-BSBI1oN1.js.map +1 -0
  55. package/dist/runtime/routes-Ce8TVP-T.js +690 -0
  56. package/dist/runtime/routes-Ce8TVP-T.js.map +1 -0
  57. package/dist/runtime/{schema-1Zs03-iS.js → schema-D1wOqiNH.js} +40 -12
  58. package/dist/runtime/schema-D1wOqiNH.js.map +1 -0
  59. package/dist/runtime/server.d.ts +5 -5
  60. package/dist/runtime/server.js +2 -2
  61. package/dist/runtime/{sync-Bs8J3fIr.d.ts → sync-D-GK2sv9.d.ts} +3 -2
  62. package/dist/runtime/{api-CAgKDij7.js → sync-DzUwA8W9.js} +22 -41
  63. package/dist/runtime/sync-DzUwA8W9.js.map +1 -0
  64. package/dist/runtime/types-DeCCyqLU-BIm7rU-0.d.ts +341 -0
  65. package/package.json +29 -4
  66. package/src/cli/agent-files.ts +14 -8
  67. package/src/cli/build/managed-client.ts +411 -0
  68. package/src/cli/build/managed-server.ts +294 -0
  69. package/src/cli/build/managed.ts +67 -0
  70. package/src/cli/build/output.ts +47 -0
  71. package/src/cli/build/server.ts +51 -220
  72. package/src/cli/client-config.ts +1 -140
  73. package/src/cli/client-styles.ts +1 -0
  74. package/src/cli/codegen.ts +9 -4
  75. package/src/cli/command.ts +28 -5
  76. package/src/cli/control-client.ts +84 -101
  77. package/src/cli/deploy.ts +133 -177
  78. package/src/cli/dev.ts +506 -107
  79. package/src/cli/image-assets.ts +499 -0
  80. package/src/cli/managed-client-boundaries.ts +103 -0
  81. package/src/cli/managed-dev-platform.ts +118 -0
  82. package/src/cli/managed-exports.ts +219 -0
  83. package/src/cli/managed-release-bin.ts +13 -0
  84. package/src/cli/managed-release.ts +260 -0
  85. package/src/cli/module-boundaries.ts +11 -1
  86. package/src/cli/new-app.ts +111 -87
  87. package/src/cli/route-codegen.ts +311 -0
  88. package/src/cli/route-static.ts +710 -0
  89. package/src/client/bootstrap.ts +110 -0
  90. package/src/client/errors.ts +13 -7
  91. package/src/client/files.ts +64 -0
  92. package/src/client/id.ts +9 -5
  93. package/src/client/image.ts +28 -0
  94. package/src/client/index.ts +25 -2
  95. package/src/client/managed-client.ts +729 -0
  96. package/src/client/managed-upgrade.ts +283 -0
  97. package/src/client/recovery-journal.ts +195 -0
  98. package/src/client/route-boundaries.ts +184 -0
  99. package/src/client/router-history.ts +152 -0
  100. package/src/client/router-store.ts +644 -0
  101. package/src/client/router.ts +324 -0
  102. package/src/client/routes.ts +28 -0
  103. package/src/client/sync.ts +66 -592
  104. package/src/dev-runtime/database.ts +77 -2
  105. package/src/dev-runtime/functions.ts +3 -3
  106. package/src/dev-runtime/id.ts +19 -4
  107. package/src/dev-runtime/managed-functions.ts +255 -0
  108. package/src/dev-runtime/managed-sync.ts +263 -0
  109. package/src/dev-runtime/managed-websocket.ts +71 -0
  110. package/src/dev-runtime/migrations.ts +20 -0
  111. package/src/dev-runtime/sync.ts +236 -236
  112. package/src/image-assets.d.ts +36 -0
  113. package/src/internal/client.ts +186 -0
  114. package/src/internal/host.ts +1 -1
  115. package/src/internal/http-paths.ts +0 -3
  116. package/src/internal/routes.ts +45 -0
  117. package/dist/runtime/api-CAgKDij7.js.map +0 -1
  118. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +0 -154
  119. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +0 -1
  120. package/dist/runtime/id-Bt9XWRGL.js.map +0 -1
  121. package/dist/runtime/id-Dz0apuB3.d.ts +0 -1
  122. package/dist/runtime/index-D54flWtH.d.ts +0 -402
  123. package/dist/runtime/pagination-DnKg3dkI-r5ZUxBBx.d.ts +0 -112
  124. package/dist/runtime/pagination-Dz0apuB3.d.ts +0 -1
  125. package/dist/runtime/result-DKAA4gpS.d.ts +0 -1
  126. package/dist/runtime/schema-1Zs03-iS.js.map +0 -1
  127. package/src/cli/build/client.ts +0 -119
  128. package/src/cli/build/public.ts +0 -186
  129. package/src/cli/client-entry.ts +0 -152
  130. package/src/cli/client-plugin.ts +0 -136
  131. package/src/client/app.ts +0 -15
@@ -0,0 +1,916 @@
1
+ import { m as upgrade, p as initial } from "./id--1m0NuUL.js";
2
+ import { _ as matchPattern, f as useRouterSnapshot, g as matchDirectory, h as compilePatterns, i as RouterContext, m as parseLocation, p as decodeRouteInputs, y as mountManagedSync } from "./router-BSBI1oN1.js";
3
+ import { i as LayoutMetadata, l as appDefinitionLimits, o as StaticMetadata, u as ExecutionSelection } from "./routes-Ce8TVP-T.js";
4
+ import { Deferred, Effect, Predicate, Schema } from "effect";
5
+ import { useErrorBoundary, useLayoutEffect, useState } from "preact/hooks";
6
+ import { Fragment, createElement, render } from "preact";
7
+ //#region ../shared/dist/routing/metadata.js
8
+ const MetadataInput = Schema.Struct({
9
+ applicationName: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)),
10
+ layouts: Schema.Array(LayoutMetadata).check(Schema.isMaxLength(appDefinitionLimits.layouts)),
11
+ page: Schema.optional(StaticMetadata),
12
+ outcome: Schema.Literals([
13
+ "Matched",
14
+ "Invalid",
15
+ "NotFound"
16
+ ])
17
+ }).annotate({ parseOptions: { onExcessProperty: "error" } });
18
+ var InvalidMetadata = class extends Schema.TaggedError()("InvalidMetadata", { message: Schema.String }) {};
19
+ /**
20
+ * Layouts run from outermost to nearest. Values remain plain text: document writers must escape
21
+ * HTML text and attributes, and browser writers must use textContent and setAttribute.
22
+ * `remove` names managed tags absent from this result, including after navigation.
23
+ */
24
+ const resolveMetadata = Effect.fn("Routing.resolveMetadata")(function* (input) {
25
+ const { applicationName, layouts, page, outcome } = yield* Schema.decodeEffect(MetadataInput)(input).pipe(Effect.mapError(() => InvalidMetadata.make({ message: "Static metadata is invalid." })));
26
+ let title = applicationName;
27
+ let template;
28
+ let description;
29
+ let robots;
30
+ for (const layout of layouts) {
31
+ title = layout.title ?? title;
32
+ template = layout.titleTemplate ?? template;
33
+ description = layout.meta?.description ?? description;
34
+ robots = layout.meta?.robots ?? robots;
35
+ }
36
+ const matchedPage = outcome === "NotFound" ? void 0 : page;
37
+ const pageTitle = matchedPage?.title;
38
+ if (pageTitle !== void 0) title = template === void 0 ? pageTitle : template.replace("%s", () => pageTitle);
39
+ description = matchedPage?.meta?.description ?? description;
40
+ robots = outcome === "Matched" ? matchedPage?.meta?.robots ?? robots : "noindex";
41
+ const remove = [];
42
+ if (description === void 0) remove.push("description");
43
+ if (robots === void 0) remove.push("robots");
44
+ return {
45
+ title,
46
+ meta: {
47
+ description,
48
+ robots
49
+ },
50
+ remove
51
+ };
52
+ });
53
+ //#endregion
54
+ //#region ../shared/dist/routing.js
55
+ const reservedSegments = (segments) => segments[0] === "_ignotum";
56
+ /** Accept a root-relative location before a URL parser can discard invalid dot segments. */
57
+ const matchLocation = Effect.fn("Routing.matchLocation")(function* (patterns, input) {
58
+ const parsed = yield* parseLocation(input).pipe(Effect.result);
59
+ if (parsed._tag === "Failure") {
60
+ const error = parsed.failure;
61
+ const partial = error.partial;
62
+ const segments = partial?.segments ?? [];
63
+ const reserved = reservedSegments(segments);
64
+ const branch = partial?.pathComplete ? matchPattern(patterns, segments) : matchDirectory(patterns, segments);
65
+ return {
66
+ _tag: "Invalid",
67
+ status: 400,
68
+ route: !reserved && partial?.pathComplete ? branch.route : void 0,
69
+ layouts: reserved ? [] : branch.layouts,
70
+ reserved,
71
+ location: void 0,
72
+ issues: error.issues
73
+ };
74
+ }
75
+ const location = parsed.success;
76
+ const reserved = reservedSegments(location.segments);
77
+ const branch = matchPattern(patterns, location.segments);
78
+ if (reserved || branch.route === void 0) return {
79
+ _tag: "NotFound",
80
+ status: 404,
81
+ layouts: reserved ? [] : branch.layouts,
82
+ reserved,
83
+ location
84
+ };
85
+ const route = branch.route;
86
+ const decoded = yield* decodeRouteInputs(route, branch.params, location.search).pipe(Effect.result);
87
+ if (decoded._tag === "Failure") return {
88
+ _tag: "Invalid",
89
+ status: 400,
90
+ route,
91
+ layouts: branch.layouts,
92
+ reserved: false,
93
+ location,
94
+ issues: decoded.failure.issues
95
+ };
96
+ return {
97
+ _tag: "Matched",
98
+ status: 200,
99
+ route,
100
+ layouts: branch.layouts,
101
+ reserved: false,
102
+ location,
103
+ ...decoded.success
104
+ };
105
+ });
106
+ //#endregion
107
+ //#region src/client/router-history.ts
108
+ const HistoryEntryV1 = Schema.Struct({
109
+ formatVersion: Schema.Literal(1),
110
+ key: Schema.String
111
+ });
112
+ const HistoryEntryV2 = Schema.Struct({
113
+ formatVersion: Schema.Literal(2),
114
+ key: Schema.String,
115
+ index: Schema.NullOr(Schema.Int)
116
+ });
117
+ const HistoryEntry = upgrade(initial(HistoryEntryV1), HistoryEntryV2, (old) => ({
118
+ formatVersion: 2,
119
+ key: old.key,
120
+ index: null
121
+ }));
122
+ const HistoryState = Schema.Struct({ __ignotumRouter: HistoryEntry });
123
+ const StateObject = Schema.Record(Schema.String, Schema.Unknown);
124
+ const hrefOf = (browser) => `${browser.location.pathname}${browser.location.search}${browser.location.hash}`;
125
+ /** Serializes router corrections to native traversal without adding duplicate entries. */
126
+ var RouterHistory = class {
127
+ browser;
128
+ onTraversal;
129
+ committed;
130
+ actual;
131
+ positions = /* @__PURE__ */ new Map();
132
+ seeking;
133
+ originalScrollRestoration;
134
+ direction = -1;
135
+ scrollPending = false;
136
+ expectedKey;
137
+ scroll = /* @__PURE__ */ new Map();
138
+ constructor(browser, onTraversal) {
139
+ this.browser = browser;
140
+ this.onTraversal = onTraversal;
141
+ const parsed = Schema.decodeUnknownOption(HistoryState)(browser.history.state);
142
+ const entry = parsed._tag === "Some" ? parsed.value.__ignotumRouter : void 0;
143
+ this.actual = this.committed = {
144
+ key: entry?.key ?? browser.crypto.randomUUID(),
145
+ index: entry?.index ?? 0,
146
+ href: hrefOf(browser)
147
+ };
148
+ this.write(this.actual, false);
149
+ this.originalScrollRestoration = browser.history.scrollRestoration;
150
+ browser.history.scrollRestoration = "manual";
151
+ browser.addEventListener("popstate", this.onPop);
152
+ browser.addEventListener("scroll", this.saveScroll);
153
+ }
154
+ write(position, push) {
155
+ const parsed = Schema.decodeUnknownOption(StateObject)(this.browser.history.state);
156
+ const owned = { __ignotumRouter: Schema.encodeSync(HistoryEntry)({
157
+ formatVersion: 2,
158
+ key: position.key,
159
+ index: position.index
160
+ }) };
161
+ const state = parsed._tag === "Some" ? {
162
+ ...parsed.value,
163
+ ...owned
164
+ } : owned;
165
+ if (push) this.browser.history.pushState(state, "", position.href);
166
+ else this.browser.history.replaceState(state, "", position.href);
167
+ this.positions.set(position.key, position.index);
168
+ }
169
+ saveScroll = () => {
170
+ if (this.scrollPending) return;
171
+ this.scroll.set(this.committed.key, {
172
+ x: this.browser.scrollX,
173
+ y: this.browser.scrollY
174
+ });
175
+ };
176
+ captureScroll() {
177
+ this.saveScroll();
178
+ this.scrollPending = true;
179
+ return this.scroll.get(this.committed.key) ?? {
180
+ x: this.browser.scrollX,
181
+ y: this.browser.scrollY
182
+ };
183
+ }
184
+ finishScroll() {
185
+ this.scrollPending = false;
186
+ this.saveScroll();
187
+ }
188
+ restoreSourceScroll() {
189
+ if (!this.scrollPending) return;
190
+ const saved = this.scroll.get(this.committed.key);
191
+ if (saved !== void 0) this.browser.scrollTo(saved.x, saved.y);
192
+ this.finishScroll();
193
+ }
194
+ onPop = () => {
195
+ const parsed = Schema.decodeUnknownOption(HistoryState)(this.browser.history.state);
196
+ const entry = parsed._tag === "Some" ? parsed.value.__ignotumRouter : void 0;
197
+ const key = entry?.key ?? this.browser.crypto.randomUUID();
198
+ const index = entry?.index ?? this.positions.get(key) ?? this.actual.index + this.direction;
199
+ this.actual = {
200
+ key,
201
+ index,
202
+ href: hrefOf(this.browser)
203
+ };
204
+ this.write(this.actual, false);
205
+ const expected = this.expectedKey;
206
+ this.expectedKey = void 0;
207
+ const seeking = this.seeking;
208
+ if (seeking !== void 0 && (expected === key || seeking.position.key === key)) {
209
+ this.advanceSeek();
210
+ return;
211
+ }
212
+ if (seeking !== void 0) {
213
+ this.seeking = void 0;
214
+ Effect.runSync(Deferred.succeed(seeking.done, false));
215
+ }
216
+ this.onTraversal(this.actual);
217
+ };
218
+ advanceSeek() {
219
+ const seeking = this.seeking;
220
+ if (seeking === void 0 || this.expectedKey !== void 0) return;
221
+ if (this.actual.key === seeking.position.key) {
222
+ this.seeking = void 0;
223
+ Effect.runSync(Deferred.succeed(seeking.done, true));
224
+ } else {
225
+ this.expectedKey = seeking.position.key;
226
+ this.browser.history.go(seeking.position.index - this.actual.index);
227
+ }
228
+ }
229
+ seek(position) {
230
+ if (this.seeking !== void 0) {
231
+ if (this.seeking.position.key === position.key) return Effect.runPromise(Deferred.await(this.seeking.done));
232
+ Effect.runSync(Deferred.succeed(this.seeking.done, false));
233
+ }
234
+ const done = Deferred.makeUnsafe();
235
+ this.seeking = {
236
+ position,
237
+ done
238
+ };
239
+ this.advanceSeek();
240
+ return Effect.runPromise(Deferred.await(done));
241
+ }
242
+ commit(position, kind) {
243
+ if (kind === "push" || kind === "replace") this.write(position, kind === "push");
244
+ this.actual = this.committed = position;
245
+ }
246
+ back = () => {
247
+ this.direction = -1;
248
+ this.browser.history.back();
249
+ };
250
+ forward = () => {
251
+ this.direction = 1;
252
+ this.browser.history.forward();
253
+ };
254
+ dispose() {
255
+ this.browser.removeEventListener("popstate", this.onPop);
256
+ this.browser.removeEventListener("scroll", this.saveScroll);
257
+ this.browser.history.scrollRestoration = this.originalScrollRestoration;
258
+ if (this.seeking !== void 0) Effect.runSync(Deferred.succeed(this.seeking.done, false));
259
+ }
260
+ };
261
+ //#endregion
262
+ //#region src/client/router-store.ts
263
+ const ErrorComponent = Schema.declare((value) => Predicate.isFunction(value));
264
+ const InvalidComponent = Schema.declare((value) => Predicate.isFunction(value));
265
+ const NotFoundComponent = Schema.declare((value) => Predicate.isFunction(value));
266
+ const PageModule = Schema.Struct({
267
+ error: Schema.optional(ErrorComponent),
268
+ invalid: Schema.optional(InvalidComponent),
269
+ component: Schema.declare((value) => Predicate.isFunction(value))
270
+ });
271
+ const LayoutModule = Schema.Struct({
272
+ error: Schema.optional(ErrorComponent),
273
+ invalid: Schema.optional(InvalidComponent),
274
+ notFound: Schema.optional(NotFoundComponent),
275
+ component: Schema.declare((value) => Predicate.isFunction(value))
276
+ });
277
+ const ModuleExports = Schema.Record(Schema.String, Schema.Unknown);
278
+ var BranchLoadError = class extends Schema.TaggedError()("BranchLoadError", {
279
+ error: Schema.Unknown,
280
+ branch: Schema.declare((value) => Predicate.isObject(value))
281
+ }) {};
282
+ const locationOf = (href, key, origin) => {
283
+ const parsed = new URL(href, origin);
284
+ let hash = parsed.hash.slice(1);
285
+ try {
286
+ hash = decodeURIComponent(hash);
287
+ } catch {}
288
+ return {
289
+ pathname: parsed.pathname,
290
+ search: parsed.search,
291
+ hash,
292
+ key
293
+ };
294
+ };
295
+ /** One store per mount. Load caches contain code and CSS only, never component/query state. */
296
+ var RouterStore = class {
297
+ registration;
298
+ browser;
299
+ selection;
300
+ patterns;
301
+ listeners = /* @__PURE__ */ new Set();
302
+ modules = /* @__PURE__ */ new Map();
303
+ styles = /* @__PURE__ */ new Map();
304
+ styleCleanups = /* @__PURE__ */ new Set();
305
+ redirects = /* @__PURE__ */ new Set();
306
+ history;
307
+ blockers = /* @__PURE__ */ new Set();
308
+ blocked;
309
+ upgrading = false;
310
+ admission;
311
+ serial = 0;
312
+ transition;
313
+ committedHref;
314
+ disposed = false;
315
+ started = false;
316
+ snapshot = {
317
+ location: void 0,
318
+ branch: void 0,
319
+ navigation: { status: "idle" },
320
+ failure: void 0,
321
+ commit: 0
322
+ };
323
+ constructor(registration, selection, browser) {
324
+ this.registration = registration;
325
+ this.browser = browser;
326
+ this.selection = Schema.decodeSync(ExecutionSelection)(selection);
327
+ this.patterns = Effect.runSync(compilePatterns(registration.routes, registration.layouts));
328
+ for (const reference of [...registration.routes.map((route) => route.page), ...registration.layouts.map((layout) => layout.module)]) if (!registration.modules.has(reference.module)) throw new Error(`Client module '${reference.module}' is missing from registration.`);
329
+ }
330
+ getSnapshot = () => this.snapshot;
331
+ subscribe = (listener) => {
332
+ this.listeners.add(listener);
333
+ return () => this.listeners.delete(listener);
334
+ };
335
+ update(snapshot) {
336
+ this.snapshot = snapshot;
337
+ for (const listener of this.listeners) listener();
338
+ }
339
+ start() {
340
+ if (this.started) throw new Error("The Ignotum router has already started.");
341
+ this.started = true;
342
+ this.history = new RouterHistory(this.browser, this.onTraversal);
343
+ this.browser.addEventListener("beforeunload", this.onBeforeUnload);
344
+ const position = this.history.committed;
345
+ return this.transitionTo(position.href, "initial", position.key, {}, position);
346
+ }
347
+ onTraversal = (position) => {
348
+ this.redirects.clear();
349
+ this.history?.seek(this.history.committed);
350
+ this.transitionTo(position.href, "traverse", position.key, {}, position);
351
+ };
352
+ back = () => this.history?.back();
353
+ forward = () => this.history?.forward();
354
+ onBeforeUnload = (event) => {
355
+ if (this.upgrading || this.blockers.size === 0) return;
356
+ event.preventDefault();
357
+ event.returnValue = "";
358
+ };
359
+ /** Internal platform upgrade entry; route confirmation never delays this reload. */
360
+ stopForUpgrade = () => {
361
+ if (this.disposed) return;
362
+ this.upgrading = true;
363
+ this.browser.removeEventListener("beforeunload", this.onBeforeUnload);
364
+ this.blockers.clear();
365
+ this.cancelPending();
366
+ };
367
+ upgrade = () => {
368
+ if (this.disposed) return;
369
+ this.stopForUpgrade();
370
+ this.browser.location.reload();
371
+ };
372
+ setAdmission(admission) {
373
+ this.admission = admission;
374
+ }
375
+ setBlocker(id, when) {
376
+ if (when) this.blockers.add(id);
377
+ else this.blockers.delete(id);
378
+ if (!when && this.blocked?.remaining.delete(id) === true) this.releaseBlocked();
379
+ }
380
+ getBlocker(id) {
381
+ const blocked = this.blocked;
382
+ const from = this.snapshot.location;
383
+ if (blocked === void 0 || from === void 0 || !blocked.remaining.has(id)) return { status: "idle" };
384
+ return {
385
+ status: "blocked",
386
+ from,
387
+ to: blocked.transition.location,
388
+ proceed: () => {
389
+ if (this.blocked !== blocked) return;
390
+ blocked.remaining.delete(id);
391
+ this.releaseBlocked();
392
+ },
393
+ reset: () => {
394
+ if (this.blocked !== blocked) return;
395
+ this.cancelPending();
396
+ this.update({
397
+ ...this.snapshot,
398
+ navigation: { status: "idle" }
399
+ });
400
+ }
401
+ };
402
+ }
403
+ releaseBlocked() {
404
+ const blocked = this.blocked;
405
+ if (blocked === void 0) return;
406
+ if (blocked.remaining.size === 0) {
407
+ this.blocked = void 0;
408
+ this.loadTransition(blocked.transition);
409
+ } else this.update({ ...this.snapshot });
410
+ }
411
+ navigate(href, options = {}, redirect = false) {
412
+ if (this.disposed || this.upgrading) return Promise.resolve({ status: "cancelled" });
413
+ if (!redirect) this.redirects.clear();
414
+ if (href === this.committedHref) {
415
+ this.cancelPending();
416
+ this.update({
417
+ ...this.snapshot,
418
+ navigation: { status: "idle" },
419
+ failure: void 0
420
+ });
421
+ return Promise.resolve({ status: "committed" });
422
+ }
423
+ if (redirect) {
424
+ if (this.committedHref !== void 0) this.redirects.add(this.committedHref);
425
+ if (this.redirects.has(href) || this.redirects.size >= 32) {
426
+ const error = /* @__PURE__ */ new Error(`Ignotum redirect loop at '${href}'.`);
427
+ this.cancelPending();
428
+ this.update({
429
+ ...this.snapshot,
430
+ navigation: { status: "idle" },
431
+ failure: {
432
+ error,
433
+ retry: () => {
434
+ this.redirects.clear();
435
+ this.navigate(href, options, true);
436
+ }
437
+ }
438
+ });
439
+ return Promise.resolve({
440
+ status: "failed",
441
+ error
442
+ });
443
+ }
444
+ }
445
+ const key = options.replace === true ? this.snapshot.location?.key ?? this.browser.crypto.randomUUID() : this.browser.crypto.randomUUID();
446
+ return this.transitionTo(href, options.replace === true ? "replace" : "push", key, options);
447
+ }
448
+ preload = async (href) => {
449
+ if (this.disposed || this.upgrading || this.admission !== void 0 && !await this.admission.validate()) return;
450
+ const outcome = Effect.runSync(matchLocation(this.patterns, href));
451
+ await this.loadBranch(outcome, false);
452
+ };
453
+ cancelPending() {
454
+ if (this.history !== void 0) this.history.seek(this.history.committed);
455
+ this.blocked = void 0;
456
+ if (this.transition !== void 0) Effect.runSync(Deferred.succeed(this.transition.completion, { status: "cancelled" }));
457
+ this.transition = void 0;
458
+ }
459
+ transitionTo(href, kind, key, options = {}, historyPosition) {
460
+ if (this.disposed || this.upgrading) return Promise.resolve({ status: "cancelled" });
461
+ const sourceScroll = this.history?.captureScroll() ?? {
462
+ x: this.browser.scrollX,
463
+ y: this.browser.scrollY
464
+ };
465
+ this.cancelPending();
466
+ const location = locationOf(href, key, this.browser.location.origin);
467
+ const previous = this.snapshot.location;
468
+ const searchOnly = previous !== void 0 && previous.pathname === location.pathname && previous.hash === location.hash;
469
+ const completion = Deferred.makeUnsafe();
470
+ const position = historyPosition ?? {
471
+ key,
472
+ href,
473
+ index: (this.history?.committed.index ?? 0) + (kind === "push" ? 1 : 0)
474
+ };
475
+ const transition = {
476
+ id: ++this.serial,
477
+ href,
478
+ location,
479
+ kind,
480
+ completion,
481
+ position,
482
+ scroll: options.scroll ?? (searchOnly ? "preserve" : "auto"),
483
+ searchOnly,
484
+ sourceScroll
485
+ };
486
+ this.transition = transition;
487
+ if (kind !== "initial" && this.blockers.size > 0 && this.snapshot.location !== void 0) {
488
+ this.blocked = {
489
+ transition,
490
+ remaining: new Set(this.blockers)
491
+ };
492
+ this.update({
493
+ ...this.snapshot,
494
+ navigation: { status: "idle" },
495
+ failure: void 0
496
+ });
497
+ } else this.loadTransition(transition);
498
+ return Effect.runPromise(Deferred.await(completion));
499
+ }
500
+ loadTransition(transition) {
501
+ const { href, location, kind } = transition;
502
+ const outcome = Effect.runSync(matchLocation(this.patterns, href));
503
+ this.update({
504
+ ...this.snapshot,
505
+ navigation: {
506
+ status: "loading",
507
+ to: location
508
+ },
509
+ failure: void 0
510
+ });
511
+ const load = async () => {
512
+ if (this.admission !== void 0 && !await this.admission.validate()) throw new Error("The app must verify its current selection before navigating.");
513
+ if (this.upgrading || this.disposed) throw new Error("The app is updating.");
514
+ return this.loadBranch(outcome);
515
+ };
516
+ load().then(async (branch) => {
517
+ if (this.transition !== transition || this.disposed) return;
518
+ try {
519
+ const history = this.history;
520
+ if (history !== void 0) {
521
+ if (!await history.seek(history.committed) || this.transition !== transition || this.disposed) return;
522
+ if (kind === "traverse") {
523
+ if (!await history.seek(transition.position) || this.transition !== transition || this.disposed) return;
524
+ }
525
+ history.commit(transition.position, kind);
526
+ }
527
+ this.applyMetadata(outcome);
528
+ this.committedHref = href;
529
+ this.update({
530
+ location,
531
+ branch,
532
+ navigation: { status: "idle" },
533
+ failure: void 0,
534
+ commit: transition.id
535
+ });
536
+ } catch (error) {
537
+ this.fail(transition, error);
538
+ }
539
+ }, (error) => {
540
+ this.fail(transition, error);
541
+ if (!this.upgrading) this.admission?.recoverLoad();
542
+ });
543
+ }
544
+ /** Called by the mounted view after Preact has committed the destination DOM. */
545
+ rendered(commit) {
546
+ if (this.transition?.id !== commit || this.snapshot.commit !== commit) {
547
+ if (this.snapshot.navigation.status === "idle" && this.blocked === void 0) this.history?.restoreSourceScroll();
548
+ return;
549
+ }
550
+ const transition = this.transition;
551
+ const document = this.browser.document;
552
+ if (!transition.searchOnly) {
553
+ const target = document.querySelector("#app main, #app [role=main]") ?? document.getElementById("app");
554
+ if (target !== null) {
555
+ if (!target.hasAttribute("tabindex")) target.setAttribute("tabindex", "-1");
556
+ target.focus({ preventScroll: true });
557
+ }
558
+ }
559
+ const saved = this.history?.scroll.get(transition.location.key);
560
+ if (transition.kind === "traverse" && saved !== void 0) this.browser.scrollTo(saved.x, saved.y);
561
+ else if (transition.scroll === "auto") {
562
+ const target = transition.location.hash === "" ? null : document.getElementById(transition.location.hash);
563
+ if (target !== null) target.scrollIntoView();
564
+ else this.browser.scrollTo(0, 0);
565
+ } else this.browser.scrollTo(transition.sourceScroll.x, transition.sourceScroll.y);
566
+ this.history?.finishScroll();
567
+ Effect.runSync(Deferred.succeed(this.transition.completion, { status: "committed" }));
568
+ this.transition = void 0;
569
+ }
570
+ fail(transition, error) {
571
+ if (this.transition !== transition || this.disposed) return;
572
+ this.transition = void 0;
573
+ const loadFailure = error instanceof BranchLoadError ? error : void 0;
574
+ const originalError = loadFailure === void 0 ? error : loadFailure.error;
575
+ this.update({
576
+ ...this.snapshot,
577
+ navigation: { status: "idle" },
578
+ failure: {
579
+ error: originalError,
580
+ branch: transition.kind === "initial" ? void 0 : loadFailure?.branch,
581
+ retry: () => {
582
+ this.transitionTo(transition.href, transition.kind, transition.location.key, { scroll: transition.scroll }, transition.position);
583
+ }
584
+ }
585
+ });
586
+ Effect.runSync(Deferred.succeed(transition.completion, {
587
+ status: "failed",
588
+ error: originalError
589
+ }));
590
+ }
591
+ loadStyle(href) {
592
+ const absolute = new URL(href, this.browser.location.origin).href;
593
+ const cached = this.styles.get(absolute);
594
+ if (cached !== void 0) return cached;
595
+ const document = this.browser.document;
596
+ const existing = Array.from(document.querySelectorAll("link[rel=\"stylesheet\"]")).find((link) => link.href === absolute);
597
+ if (existing?.sheet !== null && existing?.sheet !== void 0) return Promise.resolve();
598
+ const link = existing ?? document.createElement("link");
599
+ const promise = Effect.runPromise(Effect.callback((resume) => {
600
+ const cleanup = () => {
601
+ link.removeEventListener("load", onLoad);
602
+ link.removeEventListener("error", onError);
603
+ this.styleCleanups.delete(cancel);
604
+ };
605
+ const onLoad = () => {
606
+ cleanup();
607
+ resume(Effect.void);
608
+ };
609
+ const onError = () => {
610
+ cleanup();
611
+ link.remove();
612
+ resume(Effect.fail(/* @__PURE__ */ new Error(`Could not load route stylesheet '${href}'.`)));
613
+ };
614
+ const cancel = () => {
615
+ cleanup();
616
+ if (existing === void 0) link.remove();
617
+ resume(Effect.fail(/* @__PURE__ */ new Error("Router disposed.")));
618
+ };
619
+ this.styleCleanups.add(cancel);
620
+ link.addEventListener("load", onLoad);
621
+ link.addEventListener("error", onError);
622
+ if (existing === void 0) {
623
+ link.rel = "stylesheet";
624
+ link.crossOrigin = "anonymous";
625
+ link.href = absolute;
626
+ document.head.append(link);
627
+ }
628
+ return Effect.sync(cleanup);
629
+ })).catch((error) => {
630
+ this.styles.delete(absolute);
631
+ throw error;
632
+ });
633
+ this.styles.set(absolute, promise);
634
+ return promise;
635
+ }
636
+ loadModule(id) {
637
+ const cached = this.modules.get(id);
638
+ if (cached !== void 0) return cached;
639
+ const module = this.registration.modules.get(id);
640
+ if (module === void 0) return Promise.reject(/* @__PURE__ */ new Error(`Client module '${id}' is not registered.`));
641
+ const loaded = Promise.resolve().then(module.load).then(Schema.decodeUnknownSync(ModuleExports)).catch((error) => {
642
+ this.modules.delete(id);
643
+ throw error;
644
+ });
645
+ this.modules.set(id, loaded);
646
+ return loaded;
647
+ }
648
+ async loadExport(reference) {
649
+ const exports = await this.loadModule(reference.module);
650
+ if (!Object.hasOwn(exports, reference.exportName)) throw new Error(`Module '${reference.module}' has no '${reference.exportName}' export.`);
651
+ return exports[reference.exportName];
652
+ }
653
+ async loadBranch(outcome, withStyles = true) {
654
+ const route = outcome._tag === "NotFound" ? void 0 : outcome.route;
655
+ const available = /* @__PURE__ */ new Map();
656
+ try {
657
+ const [layouts, page] = await Promise.all([
658
+ Promise.all(outcome.layouts.map(async (layout) => {
659
+ const [loaded] = await Promise.all([this.loadExport(layout.module).then(Schema.decodeUnknownSync(LayoutModule)), withStyles ? Promise.all((this.registration.modules.get(layout.module.module)?.styles ?? []).map((href) => this.loadStyle(href))) : void 0]);
660
+ const result = {
661
+ id: layout.id,
662
+ ...loaded
663
+ };
664
+ available.set(layout.id, result);
665
+ return result;
666
+ })),
667
+ route === void 0 ? void 0 : this.loadExport(route.page).then(Schema.decodeUnknownSync(PageModule)),
668
+ Promise.all(withStyles ? [...this.registration.styles ?? [], ...route === void 0 ? [] : this.registration.modules.get(route.page.module)?.styles ?? []].map(async (href) => this.loadStyle(href)) : [])
669
+ ]);
670
+ return {
671
+ outcome,
672
+ layouts,
673
+ page: page?.component,
674
+ pageBoundaries: page
675
+ };
676
+ } catch (error) {
677
+ const layouts = [];
678
+ for (const definition of outcome.layouts) {
679
+ const loaded = available.get(definition.id);
680
+ if (loaded === void 0) break;
681
+ layouts.push(loaded);
682
+ }
683
+ throw BranchLoadError.make({
684
+ error,
685
+ branch: {
686
+ outcome,
687
+ layouts,
688
+ page: void 0
689
+ }
690
+ });
691
+ }
692
+ }
693
+ setRenderError(error) {
694
+ if (this.snapshot.branch !== void 0) this.applyMetadata(this.snapshot.branch.outcome, error);
695
+ }
696
+ applyMetadata(outcome, renderError = false) {
697
+ const resolved = Effect.runSync(resolveMetadata({
698
+ applicationName: this.registration.applicationName,
699
+ layouts: outcome.layouts.map((layout) => layout.metadata),
700
+ page: outcome._tag === "NotFound" ? void 0 : outcome.route?.metadata,
701
+ outcome: renderError ? "Invalid" : outcome._tag
702
+ }));
703
+ const document = this.browser.document;
704
+ document.title = resolved.title;
705
+ for (const name of ["description", "robots"]) {
706
+ const tags = Array.from(document.head.querySelectorAll(`meta[name="${name}"]`));
707
+ const value = resolved.meta[name];
708
+ if (value === void 0) {
709
+ for (const tag of tags) tag.remove();
710
+ continue;
711
+ }
712
+ const tag = tags.shift() ?? document.createElement("meta");
713
+ for (const duplicate of tags) duplicate.remove();
714
+ tag.name = name;
715
+ tag.content = value;
716
+ if (!tag.isConnected) document.head.append(tag);
717
+ }
718
+ }
719
+ dispose() {
720
+ this.disposed = true;
721
+ this.cancelPending();
722
+ this.history?.dispose();
723
+ this.browser.removeEventListener("beforeunload", this.onBeforeUnload);
724
+ for (const cleanup of this.styleCleanups) cleanup();
725
+ this.listeners.clear();
726
+ }
727
+ };
728
+ //#endregion
729
+ //#region src/client/route-boundaries.ts
730
+ const DefaultError = ({ error, retry }) => createElement("div", { role: "alert" }, error instanceof Error ? error.message : "This page could not load or render. ", createElement("button", { onClick: retry }, "Retry"));
731
+ const Boundary = ({ children, component, branch, router, onRetry }) => {
732
+ const [failure, setFailure] = useState();
733
+ const [, reset] = useErrorBoundary((error) => {
734
+ setFailure({
735
+ branch,
736
+ error
737
+ });
738
+ router.setRenderError(true);
739
+ });
740
+ return failure?.branch !== branch ? children : createElement(component, {
741
+ error: failure.error,
742
+ retry: () => {
743
+ router.setRenderError(false);
744
+ onRetry?.();
745
+ setFailure(void 0);
746
+ reset();
747
+ }
748
+ });
749
+ };
750
+ const Branch = ({ branch, location, router, failure }) => {
751
+ const outcome = branch.outcome;
752
+ let child;
753
+ let lastLayout = branch.layouts.length - 1;
754
+ let boundaryOwner;
755
+ if (outcome._tag === "Matched" && branch.page !== void 0) child = createElement(branch.page, {
756
+ key: outcome.route.id,
757
+ params: outcome.params,
758
+ searchParams: outcome.searchParams
759
+ });
760
+ else if (outcome._tag === "Invalid" && branch.pageBoundaries?.invalid !== void 0) child = createElement(branch.pageBoundaries.invalid, {
761
+ location,
762
+ issues: outcome.issues
763
+ });
764
+ else {
765
+ child = createElement("main", { "data-ignotum-outcome": outcome._tag }, outcome._tag === "Invalid" ? "Invalid URL" : "Page not found");
766
+ for (let index = lastLayout; index >= 0; index--) {
767
+ const layout = branch.layouts[index];
768
+ if (outcome._tag === "Invalid" && layout.invalid !== void 0) {
769
+ child = createElement(layout.invalid, {
770
+ location,
771
+ issues: outcome.issues
772
+ });
773
+ lastLayout = index;
774
+ boundaryOwner = index;
775
+ break;
776
+ }
777
+ if (outcome._tag === "NotFound" && layout.notFound !== void 0) {
778
+ child = createElement(layout.notFound, { location });
779
+ lastLayout = index;
780
+ boundaryOwner = index;
781
+ break;
782
+ }
783
+ }
784
+ }
785
+ if (branch.pageBoundaries?.error !== void 0 && outcome._tag === "Matched") child = createElement(Boundary, {
786
+ key: "page-boundary",
787
+ component: branch.pageBoundaries.error,
788
+ branch,
789
+ router,
790
+ children: child
791
+ });
792
+ let sharedLayouts = 0;
793
+ while (sharedLayouts <= lastLayout && branch.layouts[sharedLayouts]?.id === failure?.branch?.layouts[sharedLayouts]?.id) sharedLayouts++;
794
+ const loadFailure = failure === void 0 ? null : createElement(LoadFailure, {
795
+ failure,
796
+ router,
797
+ layouts: branch.layouts.slice(0, sharedLayouts)
798
+ });
799
+ for (let index = lastLayout; index >= 0; index--) {
800
+ const layout = branch.layouts[index];
801
+ if (layout.error !== void 0 && index !== boundaryOwner) child = createElement(Boundary, {
802
+ key: layout.id,
803
+ component: layout.error,
804
+ branch,
805
+ router,
806
+ children: child
807
+ });
808
+ child = createElement(layout.component, {
809
+ key: layout.id,
810
+ children: createElement(Fragment, {}, child, index === sharedLayouts - 1 ? loadFailure : null)
811
+ });
812
+ }
813
+ return createElement(Boundary, {
814
+ component: DefaultError,
815
+ branch,
816
+ router,
817
+ children: createElement(Fragment, {}, child, sharedLayouts === 0 ? loadFailure : null)
818
+ });
819
+ };
820
+ const LoadFailure = ({ failure, router, layouts }) => {
821
+ const branch = failure.branch;
822
+ if (branch === void 0) return createElement("div", { "data-ignotum-load-error": "" }, createElement(DefaultError, failure));
823
+ let child = createElement(DefaultError, failure);
824
+ let found = false;
825
+ for (const layout of [...layouts ?? branch.layouts].reverse()) {
826
+ if (layout.error === void 0) continue;
827
+ if (!found) {
828
+ child = createElement(layout.error, {
829
+ error: failure.error,
830
+ retry: failure.retry
831
+ });
832
+ found = true;
833
+ } else child = createElement(Boundary, {
834
+ component: layout.error,
835
+ branch,
836
+ router,
837
+ onRetry: failure.retry,
838
+ children: child
839
+ });
840
+ }
841
+ return createElement("div", { "data-ignotum-load-error": "" }, createElement(Boundary, {
842
+ component: DefaultError,
843
+ branch,
844
+ router,
845
+ onRetry: failure.retry,
846
+ children: child
847
+ }));
848
+ };
849
+ //#endregion
850
+ //#region src/client/bootstrap.ts
851
+ const RouterView = ({ router }) => {
852
+ const snapshot = useRouterSnapshot(router);
853
+ useLayoutEffect(() => {
854
+ router.rendered(snapshot.commit);
855
+ }, [router, snapshot]);
856
+ return createElement(RouterContext.Provider, { value: router }, snapshot.branch === void 0 || snapshot.location === void 0 ? createElement("div", { role: "status" }, snapshot.failure === void 0 ? "Loading…" : null) : createElement(Branch, {
857
+ branch: snapshot.branch,
858
+ location: snapshot.location,
859
+ router,
860
+ failure: snapshot.failure
861
+ }), snapshot.failure !== void 0 && snapshot.branch === void 0 ? createElement(LoadFailure, {
862
+ failure: snapshot.failure,
863
+ router
864
+ }) : null);
865
+ };
866
+ const mounts = /* @__PURE__ */ new WeakSet();
867
+ const ManagedStatus = ({ client }) => {
868
+ const [snapshot, setSnapshot] = useState(client.upgrade.getSnapshot);
869
+ useLayoutEffect(() => client.upgrade.subscribe(() => setSnapshot(client.upgrade.getSnapshot())), [client]);
870
+ if (snapshot.phase === "ready" && snapshot.recovery.length === 0) return null;
871
+ return createElement("aside", {
872
+ "data-ignotum-update": snapshot.phase,
873
+ role: snapshot.phase === "error" ? "alert" : "status"
874
+ }, snapshot.message ?? (snapshot.phase === "connecting" ? "Connecting to the app…" : null), snapshot.recovery.length === 0 ? null : createElement("ul", null, snapshot.recovery.map((message) => createElement("li", { key: message }, message))), snapshot.phase === "error" ? createElement("button", {
875
+ type: "button",
876
+ onClick: () => client.retry()
877
+ }, "Reload app") : null, snapshot.recovery.length === 0 ? null : createElement("button", {
878
+ type: "button",
879
+ onClick: client.upgrade.dismissRecovery
880
+ }, "Dismiss"));
881
+ };
882
+ /** The platform owns mounting. Generated registrations contain no Preact render/bootstrap code. */
883
+ const mountClient = (registration, selection) => {
884
+ const root = window.document.getElementById("app");
885
+ if (root === null) throw new Error("Ignotum mount element is missing.");
886
+ if (mounts.has(root)) throw new Error("The Ignotum client is already mounted.");
887
+ const router = new RouterStore(registration, selection, window);
888
+ const managed = mountManagedSync(selection, window, {
889
+ stopAdmission: router.stopForUpgrade,
890
+ reload: router.upgrade
891
+ });
892
+ router.setAdmission({
893
+ validate: managed.client.validate,
894
+ recoverLoad: managed.client.recoverLoad
895
+ });
896
+ render(createElement(Fragment, null, createElement(ManagedStatus, { client: managed.client }), createElement(RouterView, { router })), root);
897
+ const ready = managed.client.whenReady().then((accepted) => accepted ? router.start() : { status: "cancelled" });
898
+ mounts.add(root);
899
+ let disposed = false;
900
+ return {
901
+ ready,
902
+ upgrade: () => managed.client.upgrade.require(),
903
+ dispose: () => {
904
+ if (disposed) return;
905
+ disposed = true;
906
+ mounts.delete(root);
907
+ router.dispose();
908
+ managed.dispose();
909
+ render(null, root);
910
+ }
911
+ };
912
+ };
913
+ //#endregion
914
+ export { mountClient as t };
915
+
916
+ //# sourceMappingURL=bootstrap-BISQSn_O.js.map