foldkit 0.157.0 → 0.158.0

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 (82) hide show
  1. package/dist/devTools/host.d.ts +2 -2
  2. package/dist/devTools/host.d.ts.map +1 -1
  3. package/dist/devTools/host.js +1 -1
  4. package/dist/experimental/machine/machine.d.ts +278 -84
  5. package/dist/experimental/machine/machine.d.ts.map +1 -1
  6. package/dist/experimental/machine/machine.js +362 -212
  7. package/dist/runtime/browserListeners.d.ts +7 -1
  8. package/dist/runtime/browserListeners.d.ts.map +1 -1
  9. package/dist/runtime/browserScheduler.d.ts +3 -0
  10. package/dist/runtime/browserScheduler.d.ts.map +1 -0
  11. package/dist/runtime/browserScheduler.js +22 -0
  12. package/dist/runtime/crashUI.d.ts +36 -5
  13. package/dist/runtime/crashUI.d.ts.map +1 -1
  14. package/dist/runtime/crashUI.js +55 -2
  15. package/dist/runtime/devToolsConfig.d.ts +85 -0
  16. package/dist/runtime/devToolsConfig.d.ts.map +1 -0
  17. package/dist/runtime/devToolsConfig.js +49 -0
  18. package/dist/runtime/devToolsIntegration.d.ts +56 -0
  19. package/dist/runtime/devToolsIntegration.d.ts.map +1 -0
  20. package/dist/runtime/devToolsIntegration.js +186 -0
  21. package/dist/runtime/dispatch.d.ts +10 -0
  22. package/dist/runtime/dispatch.d.ts.map +1 -0
  23. package/dist/runtime/dispatch.js +4 -0
  24. package/dist/runtime/documentMetadata.d.ts +3 -0
  25. package/dist/runtime/documentMetadata.d.ts.map +1 -0
  26. package/dist/runtime/documentMetadata.js +148 -0
  27. package/dist/runtime/duplicateIdScanner.d.ts +16 -0
  28. package/dist/runtime/duplicateIdScanner.d.ts.map +1 -0
  29. package/dist/runtime/duplicateIdScanner.js +70 -0
  30. package/dist/runtime/hmrModelBridge.d.ts +4 -0
  31. package/dist/runtime/hmrModelBridge.d.ts.map +1 -0
  32. package/dist/runtime/hmrModelBridge.js +43 -0
  33. package/dist/runtime/hostConnector.d.ts +61 -0
  34. package/dist/runtime/hostConnector.d.ts.map +1 -0
  35. package/dist/runtime/hostConnector.js +141 -0
  36. package/dist/runtime/hydrationHandoff.d.ts +44 -0
  37. package/dist/runtime/hydrationHandoff.d.ts.map +1 -0
  38. package/dist/runtime/hydrationHandoff.js +395 -0
  39. package/dist/runtime/index.d.ts +16 -1
  40. package/dist/runtime/index.d.ts.map +1 -1
  41. package/dist/runtime/index.js +5 -1
  42. package/dist/runtime/makeApplication.d.ts +71 -0
  43. package/dist/runtime/makeApplication.d.ts.map +1 -0
  44. package/dist/runtime/makeApplication.js +98 -0
  45. package/dist/runtime/makeElement.d.ts +69 -0
  46. package/dist/runtime/makeElement.d.ts.map +1 -0
  47. package/dist/runtime/makeElement.js +81 -0
  48. package/dist/runtime/managedResourceFibers.d.ts +18 -0
  49. package/dist/runtime/managedResourceFibers.d.ts.map +1 -0
  50. package/dist/runtime/managedResourceFibers.js +47 -0
  51. package/dist/runtime/messageQueue.d.ts +27 -0
  52. package/dist/runtime/messageQueue.d.ts.map +1 -0
  53. package/dist/runtime/messageQueue.js +133 -0
  54. package/dist/runtime/public.d.ts +2 -3
  55. package/dist/runtime/public.d.ts.map +1 -1
  56. package/dist/runtime/public.js +1 -1
  57. package/dist/runtime/renderer.d.ts +69 -0
  58. package/dist/runtime/renderer.d.ts.map +1 -0
  59. package/dist/runtime/renderer.js +512 -0
  60. package/dist/runtime/resourceProvider.d.ts +33 -0
  61. package/dist/runtime/resourceProvider.d.ts.map +1 -0
  62. package/dist/runtime/resourceProvider.js +98 -0
  63. package/dist/runtime/runtime.d.ts +133 -412
  64. package/dist/runtime/runtime.d.ts.map +1 -1
  65. package/dist/runtime/runtime.js +118 -2184
  66. package/dist/runtime/runtimeStatus.d.ts +15 -0
  67. package/dist/runtime/runtimeStatus.d.ts.map +1 -0
  68. package/dist/runtime/runtimeStatus.js +6 -0
  69. package/dist/runtime/slowPhase.d.ts +82 -0
  70. package/dist/runtime/slowPhase.d.ts.map +1 -0
  71. package/dist/runtime/slowPhase.js +83 -0
  72. package/dist/runtime/start.d.ts +96 -0
  73. package/dist/runtime/start.d.ts.map +1 -0
  74. package/dist/runtime/start.js +99 -0
  75. package/dist/runtime/subscriptionFibers.d.ts +21 -0
  76. package/dist/runtime/subscriptionFibers.d.ts.map +1 -0
  77. package/dist/runtime/subscriptionFibers.js +47 -0
  78. package/dist/runtime/visibility.d.ts +8 -0
  79. package/dist/runtime/visibility.d.ts.map +1 -0
  80. package/dist/runtime/visibility.js +6 -0
  81. package/dist/test/scene.js +1 -1
  82. package/package.json +1 -1
@@ -0,0 +1,395 @@
1
+ import { Array, Effect, Option, Predicate, Schema, pipe } from 'effect';
2
+ import { HYDRATION_BUILD_ATTRIBUTE } from '../buildToken.js';
3
+ import { FOLDKIT_APP_ATTRIBUTE, FOLDKIT_FLAGS_ATTRIBUTE, } from '../hydrationMarker.js';
4
+ const hydrationForRoot = (root, isFlagsRequired) => {
5
+ const runtimeId = root.getAttribute(FOLDKIT_APP_ATTRIBUTE) ?? '';
6
+ const flagsScripts = pipe(Array.fromIterable(document.querySelectorAll(`script[${FOLDKIT_FLAGS_ATTRIBUTE}]`)), Array.filter(script => script.getAttribute(FOLDKIT_FLAGS_ATTRIBUTE) === runtimeId));
7
+ return { root, runtimeId, flagsScripts, isFlagsRequired };
8
+ };
9
+ // NOTE: hydration is scoped to the app's own stamped root so a server-rendered
10
+ // app never adopts another app's DOM. A container that carries the stamp is that
11
+ // root. A container that does not is a non-root element the caller resolved,
12
+ // which happens when the rendered view has its own element with the container's
13
+ // id (a descendant `id="root"`) and `getElementById` returned that inner element
14
+ // instead of the stamped root above it. That inner element resolves to the app
15
+ // root only when the page has exactly one stamped root and this container sits
16
+ // inside it. A container outside that root is a refused handoff, not a fresh
17
+ // application beside server markup that remains live. A null container is the
18
+ // replace-parity case, where the server root took the placeholder's place and
19
+ // `getElementById` no longer finds it, so the stamp is the only handle.
20
+ // A runtime id names one application for the whole page: it pairs a root with
21
+ // its Flags payload, and it keys the Model and scroll position hot reloading
22
+ // preserves. Two roots sharing one are not two applications but one claimed
23
+ // twice, so whichever boots second would read the other's handoff and restore
24
+ // the other's Model. `injectIntoTemplate` refuses to build such a page; this is
25
+ // the check for a page assembled some other way.
26
+ //
27
+ // Distinct ids do not make two hydrated applications independent, which is not
28
+ // a supported arrangement: each page-owning application rewrites the document's
29
+ // metadata and installs document-wide navigation listeners.
30
+ const assertRuntimeIdsAreUnique = (stampedRoots) => {
31
+ const seen = new Set();
32
+ for (const root of stampedRoots) {
33
+ const runtimeId = root.getAttribute(FOLDKIT_APP_ATTRIBUTE) ?? '';
34
+ if (runtimeId === '') {
35
+ containRefusedPage(root.ownerDocument);
36
+ throw new Error('[foldkit] Found a server-rendered root with an empty ' +
37
+ `\`${FOLDKIT_APP_ATTRIBUTE}\` stamp. A hydratable root must carry ` +
38
+ 'a nonempty runtime id so the runtime can pair it with its Flags ' +
39
+ 'payload and preserved HMR state.');
40
+ }
41
+ if (!root.ownerDocument.body?.contains(root)) {
42
+ containRefusedPage(root.ownerDocument);
43
+ throw new Error('[foldkit] Found a server-rendered root outside the document body. ' +
44
+ 'Runtime.hydrate supports one page-owning application in the ' +
45
+ 'document light DOM. Do not hydrate a root in the head, a shadow ' +
46
+ 'tree, or a detached subtree.');
47
+ }
48
+ if (seen.has(runtimeId)) {
49
+ containRefusedPage(root.ownerDocument);
50
+ throw new Error(`[foldkit] Found more than one server-rendered root stamped ` +
51
+ `"${runtimeId}". A runtime id names one application for the whole ` +
52
+ 'page: it pairs a root with its Flags payload and keys the Model and ' +
53
+ 'scroll position hot reloading preserves, so two roots sharing one ' +
54
+ "would take each other's state. Remove the duplicate root. Foldkit " +
55
+ 'hydrates one page-owning application per document.');
56
+ }
57
+ seen.add(runtimeId);
58
+ }
59
+ };
60
+ const assertSinglePageApplication = (stampedRoots) => {
61
+ if (stampedRoots.length > 1) {
62
+ containRefusedPage(document);
63
+ throw new Error('[foldkit] Found more than one page-owning application stamped with ' +
64
+ `\`${FOLDKIT_APP_ATTRIBUTE}\`. Hydrating multiple applications in ` +
65
+ 'one document is not supported: each application owns the document ' +
66
+ 'metadata and installs document-wide navigation listeners. Render ' +
67
+ 'one application per page.');
68
+ }
69
+ };
70
+ // The reason this page cannot be adopted by this client, or `undefined` when
71
+ // the two name the same deployment.
72
+ //
73
+ // The client's id is required and must be non-empty. An absent one would
74
+ // otherwise equal the absent marker on a page served before build ids existed,
75
+ // which reads a page from an unknown deployment as one of this build's own: the
76
+ // exact case the id exists to refuse.
77
+ export const buildSkew = (root, buildId, runtimeId) => {
78
+ if (!Predicate.isString(buildId) || buildId === '') {
79
+ return new Error('[foldkit] Runtime.hydrate was given no build id. Hydration compares ' +
80
+ 'the id the server stamped on the root with this client’s own before ' +
81
+ 'it adopts any DOM, and without one a page from any deployment would ' +
82
+ 'be adopted as this one. Pass ' +
83
+ '`buildId: import.meta.env.FOLDKIT_BUILD_ID`, the same value the ' +
84
+ 'server entry passes to `renderToString`.');
85
+ }
86
+ const servedBuild = root.getAttribute(HYDRATION_BUILD_ATTRIBUTE);
87
+ if (servedBuild === buildId) {
88
+ return undefined;
89
+ }
90
+ return new Error(`[foldkit] Runtime.hydrate found application "${runtimeId}" served by ` +
91
+ `${servedBuild === null ? 'no known deployment' : `deployment "${servedBuild}"`}` +
92
+ `, but this client belongs to deployment "${buildId}". Startup stops ` +
93
+ 'here rather than reading a handoff written by other code: the Flags in ' +
94
+ 'the page are that deployment’s, and this build could accept them while ' +
95
+ 'every value in them means something else. Serve the page from the ' +
96
+ 'running deployment, and keep stale HTML out of shared caches.');
97
+ };
98
+ // Take the served page out of reach before startup stops.
99
+ //
100
+ // Refusing to adopt a page keeps this build's code away from it, but the markup
101
+ // is still a live document: its links navigate, its forms submit to whatever
102
+ // action the deployment that wrote them intended, and its controls take focus.
103
+ // A visitor who clicks after a failed handoff would be acting on a page with no
104
+ // running code to reconsider.
105
+ //
106
+ // The boundary is an element the served page already has, marked in place.
107
+ // Nothing moves. Wrapping the root in a fresh inert element would reparent it:
108
+ // every upgraded custom element in the subtree would run `disconnectedCallback`
109
+ // and then `connectedCallback` again, and every embedded browsing context would
110
+ // reload. Marking a stable ancestor costs neither.
111
+ //
112
+ // That ancestor is the document's body, which is where a hydratable root always
113
+ // sits: `renderToString` refuses to render `html`, `head`, or `body` as the
114
+ // root, and `hydrate` is for an application that owns the page. Inertness
115
+ // propagates from an inert HTML element to every descendant whatever their
116
+ // namespace, so an SVG or MathML root is covered too. `inert` is an HTML
117
+ // attribute, so marking such a root directly would only create an expando.
118
+ // Verified in Chromium for HTML, SVG foreign content, and MathML.
119
+ //
120
+ // A modal dialog lives in the top layer, where ancestor inertness does not reach
121
+ // it. The refusal shield is itself a modal dialog opened after the served page's
122
+ // top-layer content, so it covers an old dialog even inside a closed shadow
123
+ // root. It is a sibling of `body`, outside the inert and `aria-hidden` boundary,
124
+ // covers the viewport, and refuses its cancel action. Existing dialogs are not
125
+ // closed: calling author-owned `close` or `cancel` listeners while startup is
126
+ // failing can run arbitrary stale code. Same-document input guards stop
127
+ // physical keyboard input if older top-layer content requests focus.
128
+ //
129
+ // This runs when the client reaches the failure, so it cannot undo what the page
130
+ // already did: subresources the parser fetched, custom elements that upgraded,
131
+ // scripts the served deployment authored, or anything a visitor managed before
132
+ // the client entry ran. Nor is this a script sandbox. A capture listener on
133
+ // `window` or `document` runs before an event reaches the shield, and a timer or
134
+ // listener can open newer top-layer UI. The boundary stops pointer and physical
135
+ // keyboard input from activating the old page's same-document native links,
136
+ // forms, and controls without reconnecting its DOM.
137
+ const REFUSED_ATTRIBUTE = 'data-foldkit-refused';
138
+ const REFUSAL_SHIELD_ATTRIBUTE = 'data-foldkit-refusal-shield';
139
+ const refusalShields = new WeakMap();
140
+ const REFUSAL_SHIELD_INPUT_EVENTS = [
141
+ 'auxclick',
142
+ 'click',
143
+ 'contextmenu',
144
+ 'dblclick',
145
+ 'keydown',
146
+ 'keypress',
147
+ 'keyup',
148
+ 'mousedown',
149
+ 'mouseup',
150
+ 'pointerdown',
151
+ 'pointerup',
152
+ 'touchend',
153
+ 'touchstart',
154
+ ];
155
+ const REFUSAL_DOCUMENT_INPUT_EVENTS = ['keydown', 'keypress', 'keyup'];
156
+ const preventRefusalShieldInteraction = (event) => {
157
+ event.preventDefault();
158
+ event.stopImmediatePropagation();
159
+ };
160
+ // NOTE: `showModal` throws when the owning document is not fully active. A
161
+ // foreign or detached document still needs the original Foldkit refusal rather
162
+ // than a browser exception from its containment UI.
163
+ const tryOpenRefusalShieldAsModal = (shield) => {
164
+ if (typeof shield.showModal !== 'function') {
165
+ return false;
166
+ }
167
+ try {
168
+ shield.showModal();
169
+ return true;
170
+ }
171
+ catch {
172
+ return false;
173
+ }
174
+ };
175
+ const openRefusalShield = (shield) => {
176
+ shield.inert = true;
177
+ shield.setAttribute('inert', '');
178
+ if (shield.open && typeof shield.close === 'function') {
179
+ shield.close();
180
+ }
181
+ if (!tryOpenRefusalShieldAsModal(shield)) {
182
+ shield.setAttribute('open', '');
183
+ }
184
+ shield.inert = false;
185
+ shield.removeAttribute('inert');
186
+ shield.focus({ preventScroll: true });
187
+ };
188
+ const installRefusalShield = (ownerDocument) => {
189
+ const existing = refusalShields.get(ownerDocument);
190
+ if (existing !== undefined && existing.isConnected) {
191
+ openRefusalShield(existing);
192
+ return;
193
+ }
194
+ const shield = ownerDocument.createElement('dialog');
195
+ shield.setAttribute(REFUSAL_SHIELD_ATTRIBUTE, '');
196
+ shield.setAttribute('aria-label', 'Page unavailable');
197
+ shield.setAttribute('aria-modal', 'true');
198
+ shield.setAttribute('closedby', 'none');
199
+ shield.tabIndex = -1;
200
+ shield.textContent =
201
+ 'This page could not start safely. Reload to get the current version.';
202
+ shield.style.alignItems = 'center';
203
+ shield.style.background = 'rgba(15, 23, 42, 0.96)';
204
+ shield.style.border = '0';
205
+ shield.style.boxSizing = 'border-box';
206
+ shield.style.color = 'white';
207
+ shield.style.font = '600 1rem/1.5 system-ui, sans-serif';
208
+ shield.style.display = 'grid';
209
+ shield.style.height = '100vh';
210
+ shield.style.inset = '0';
211
+ shield.style.margin = '0';
212
+ shield.style.maxHeight = 'none';
213
+ shield.style.maxWidth = 'none';
214
+ shield.style.overflow = 'hidden';
215
+ shield.style.padding = '2rem';
216
+ shield.style.position = 'fixed';
217
+ shield.style.touchAction = 'none';
218
+ shield.style.userSelect = 'none';
219
+ shield.style.width = '100vw';
220
+ shield.addEventListener('cancel', preventRefusalShieldInteraction);
221
+ for (const eventName of REFUSAL_SHIELD_INPUT_EVENTS) {
222
+ shield.addEventListener(eventName, preventRefusalShieldInteraction, {
223
+ capture: true,
224
+ passive: false,
225
+ });
226
+ }
227
+ for (const eventName of REFUSAL_DOCUMENT_INPUT_EVENTS) {
228
+ ownerDocument.addEventListener(eventName, preventRefusalShieldInteraction, {
229
+ capture: true,
230
+ passive: false,
231
+ });
232
+ }
233
+ ownerDocument.documentElement.appendChild(shield);
234
+ refusalShields.set(ownerDocument, shield);
235
+ openRefusalShield(shield);
236
+ };
237
+ export const containRefusedPage = (ownerDocument) => {
238
+ const boundary = ownerDocument.body ?? ownerDocument.documentElement;
239
+ boundary.inert = true;
240
+ boundary.setAttribute('inert', '');
241
+ boundary.setAttribute('aria-hidden', 'true');
242
+ boundary.setAttribute(REFUSED_ATTRIBUTE, '');
243
+ installRefusalShield(ownerDocument);
244
+ };
245
+ // Whether this page carries anything a Foldkit server render leaves behind. A
246
+ // resolution failure on such a page is a refused handoff, and the markup is
247
+ // contained; the same failure on a page with none of these markers is a client
248
+ // application whose container never existed, where there is no server render to
249
+ // refuse and nothing to take out of reach.
250
+ export const hasServerRenderedMarkup = (ownerDocument) => ownerDocument.querySelector(`[${FOLDKIT_APP_ATTRIBUTE}], [${HYDRATION_BUILD_ATTRIBUTE}], ` +
251
+ `[${FOLDKIT_FLAGS_ATTRIBUTE}]`) !== null;
252
+ export const findDocumentHydration = (container, isFlagsRequired) => {
253
+ const stampedRoots = Array.fromIterable(document.querySelectorAll(`[${FOLDKIT_APP_ATTRIBUTE}]`));
254
+ assertRuntimeIdsAreUnique(stampedRoots);
255
+ assertSinglePageApplication(stampedRoots);
256
+ if (container !== null) {
257
+ const stampedAncestor = container.closest(`[${FOLDKIT_APP_ATTRIBUTE}]`);
258
+ if (container.hasAttribute(FOLDKIT_APP_ATTRIBUTE)) {
259
+ const isDocumentRoot = Option.match(Array.head(stampedRoots), {
260
+ onNone: () => false,
261
+ onSome: root => root === container,
262
+ });
263
+ if (!isDocumentRoot) {
264
+ containRefusedPage(container.ownerDocument);
265
+ throw new Error('[foldkit] Runtime.hydrate received a stamped container that is ' +
266
+ 'not the single server root in the document light DOM. Hydration ' +
267
+ 'supports one page-owning application under the document body. ' +
268
+ 'Do not hydrate a root in a shadow tree or detached subtree.');
269
+ }
270
+ return hydrationForRoot(container, isFlagsRequired);
271
+ }
272
+ return Array.match(stampedRoots, {
273
+ onEmpty: () => {
274
+ if (stampedAncestor === null) {
275
+ return undefined;
276
+ }
277
+ containRefusedPage(container.ownerDocument);
278
+ throw new Error('[foldkit] Runtime.hydrate received a container under a stamped ' +
279
+ 'root outside the document body light DOM. Do not hydrate a root ' +
280
+ 'in a shadow tree, detached subtree, or another document.');
281
+ },
282
+ onNonEmpty: roots => {
283
+ const onlyRoot = Array.headNonEmpty(roots);
284
+ if (stampedAncestor === onlyRoot) {
285
+ return hydrationForRoot(onlyRoot, isFlagsRequired);
286
+ }
287
+ containRefusedPage(container.ownerDocument);
288
+ throw new Error('[foldkit] Runtime.hydrate received a container outside the ' +
289
+ "document's server-rendered application root. A page-owning " +
290
+ 'application must adopt that single root rather than boot beside ' +
291
+ 'server markup it does not own.');
292
+ },
293
+ });
294
+ }
295
+ return Option.match(Array.head(stampedRoots), {
296
+ onNone: () => undefined,
297
+ onSome: root => hydrationForRoot(root, isFlagsRequired),
298
+ });
299
+ };
300
+ /**
301
+ * Decides whether this boot adopts the server-rendered root and where its
302
+ * Flags come from. A hydrating boot with no stamped root, a root served by
303
+ * another deployment, or a missing, duplicated, or undecodable Flags
304
+ * payload contains the served page and stops startup. The build id is
305
+ * compared before the payload text is read and before `init` runs, so
306
+ * nothing acts on another deployment's data. An HMR-restored Model skips
307
+ * adoption and gets a fresh patch against the stamped root.
308
+ */
309
+ export const resolveHydrationHandoff = ({ bootMode, hydration, bootFlags, configuredFlags, isFlagsRequired, FlagsCodec, hmrModel, container, buildId, provideResources, }) => Effect.gen(function* () {
310
+ const maybeResolveFreshFlags = Option.orElse(Option.fromNullishOr(bootFlags), () => configuredFlags);
311
+ const resolveFreshFlags = Option.match(maybeResolveFreshFlags, {
312
+ onNone: () => isFlagsRequired
313
+ ? Effect.die(new Error('[foldkit] This application declares Flags. Pass its ' +
314
+ 'Flags Effect to Runtime.run or Runtime.embed.'))
315
+ : /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
316
+ Effect.succeed(undefined),
317
+ onSome: provideResources,
318
+ });
319
+ // Every hydration refusal that knows which root it was going to adopt
320
+ // contains that root first. The build id is one reason to refuse; a
321
+ // missing, duplicated, malformed, or Schema-incompatible Flags payload
322
+ // is another, and the page left behind is just as live in each case.
323
+ const refuseHydration = (root, message, cause) => {
324
+ containRefusedPage(root.ownerDocument);
325
+ return Effect.die(cause === undefined
326
+ ? new Error(message)
327
+ : new Error(message, { cause }));
328
+ };
329
+ const decodeFlagsPayload = (payload, runtimeId, root) => Effect.try({
330
+ try: () => {
331
+ const parsedPayload = JSON.parse(payload);
332
+ return pipe(
333
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
334
+ Schema.toCodecJson(FlagsCodec), Schema.decodeUnknownSync, decode => decode(parsedPayload));
335
+ },
336
+ catch: cause => cause,
337
+ }).pipe(Effect.catch(cause => refuseHydration(root, '[foldkit] Runtime.hydrate could not decode the server ' +
338
+ `Flags payload for application "${runtimeId}". The HTML ` +
339
+ 'and client bundle must use the same Flags Schema.', cause)));
340
+ const maybeRequestedHydration = bootMode === 'Hydrate'
341
+ ? Option.fromNullishOr(hydration)
342
+ : Option.none();
343
+ if (bootMode === 'Hydrate' && Option.isNone(maybeRequestedHydration)) {
344
+ // A hydrating client that finds no stamped root will not adopt
345
+ // whatever the page holds, so the page is contained whether or not the
346
+ // caller named a container.
347
+ containRefusedPage(container === null ? document : container.ownerDocument);
348
+ return yield* Effect.die(new Error('[foldkit] Runtime.hydrate could not find a server-rendered ' +
349
+ `root stamped with \`${FOLDKIT_APP_ATTRIBUTE}\`. Use ` +
350
+ 'Runtime.run for a fresh client boot.'));
351
+ }
352
+ // The build the served page came from is settled here, before the Flags
353
+ // payload text is accessed, parsed, or decoded, before `init` runs, and
354
+ // therefore before any Command, Subscription, ManagedResource, or port
355
+ // this boot would start. A page from another deployment carries that
356
+ // deployment's Flags, which the current Schema may well accept while
357
+ // every value in them means something else, so deferring the comparison
358
+ // to the DOM patch lets stale data reach new code that already acted on
359
+ // it.
360
+ if (Option.isSome(maybeRequestedHydration)) {
361
+ const skew = buildSkew(maybeRequestedHydration.value.root, buildId, maybeRequestedHydration.value.runtimeId);
362
+ if (skew !== undefined) {
363
+ containRefusedPage(maybeRequestedHydration.value.root.ownerDocument);
364
+ return yield* Effect.die(skew);
365
+ }
366
+ }
367
+ // NOTE: an HMR-restored Model wins over DOM adoption because the
368
+ // server DOM reflects older code. The hydration handoff is still
369
+ // required, but the restored Model gets a fresh patch against its
370
+ // stamped root.
371
+ const maybeHydrationRoot = Predicate.isUndefined(hmrModel)
372
+ ? Option.map(maybeRequestedHydration, requestedHydration => requestedHydration.root)
373
+ : Option.none();
374
+ const maybeHydrationFlags = yield* Option.match(maybeRequestedHydration, {
375
+ onNone: () => Effect.succeed(Option.none()),
376
+ onSome: requestedHydration => Effect.map(requestedHydration.isFlagsRequired
377
+ ? Array.match(requestedHydration.flagsScripts, {
378
+ onEmpty: () => refuseHydration(requestedHydration.root, '[foldkit] Runtime.hydrate found application ' +
379
+ `"${requestedHydration.runtimeId}" but its ` +
380
+ 'server Flags payload is missing.'),
381
+ onNonEmpty: ([payloadScript, ...remainingScripts]) => Array.isArrayNonEmpty(remainingScripts)
382
+ ? refuseHydration(requestedHydration.root, '[foldkit] Runtime.hydrate found multiple ' +
383
+ 'server Flags payloads for application ' +
384
+ `"${requestedHydration.runtimeId}".`)
385
+ : decodeFlagsPayload(payloadScript.textContent ?? '', requestedHydration.runtimeId, requestedHydration.root),
386
+ })
387
+ : /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
388
+ Effect.succeed(undefined), Option.some),
389
+ });
390
+ const resolveFlags = Option.match(maybeHydrationFlags, {
391
+ onNone: () => resolveFreshFlags,
392
+ onSome: Effect.succeed,
393
+ });
394
+ return { maybeHydrationRoot, resolveFlags };
395
+ });
@@ -1,2 +1,17 @@
1
- export * from './runtime.js';
1
+ export type { RoutingConfig } from './browserListeners.js';
2
+ export type { CrashConfig, CrashContext, ElementCrashConfig, } from './crashUI.js';
3
+ export type { DevToolsConfig, DevToolsMode, DevToolsModeConfig, DevToolsPosition, } from './devToolsConfig.js';
4
+ export { Dispatch } from './dispatch.js';
5
+ export type { EmbedHandle, InboundPortHandle, InboundPortHandles, OutboundPortHandle, OutboundPortHandles, PortHandles, } from './hostConnector.js';
6
+ export { makeApplication } from './makeApplication.js';
7
+ export type { ApplicationConfig, ApplicationConfigWithFlags, ApplicationInit, RoutingApplicationConfig, RoutingApplicationConfigWithFlags, RoutingApplicationInit, } from './makeApplication.js';
8
+ export { makeElement } from './makeElement.js';
9
+ export type { ElementConfig, ElementConfigWithFlags, ElementInit, } from './makeElement.js';
10
+ export type { MakeRuntimeReturn } from './runtime.js';
11
+ export { SlowPhase, defaultSlowCallback } from './slowPhase.js';
12
+ export type { SlowConfig, SlowContext, SlowPatchContext, SlowSubscriptionDependenciesContext, SlowThresholdOverrides, SlowUpdateContext, SlowViewContext, } from './slowPhase.js';
13
+ export { embed, hydrate, run } from './start.js';
14
+ export type { HydrateOptions, RunOptions } from './start.js';
15
+ export type { ViewTransitionConfig, ViewTransitionContext, ViewTransitionDecision, } from './viewTransition.js';
16
+ export type { Visibility } from './visibility.js';
2
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE1D,YAAY,EACV,WAAW,EACX,YAAY,EACZ,kBAAkB,GACnB,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,YAAY,EACV,iBAAiB,EACjB,0BAA0B,EAC1B,eAAe,EACf,wBAAwB,EACxB,iCAAiC,EACjC,sBAAsB,GACvB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,YAAY,EACV,aAAa,EACb,sBAAsB,EACtB,WAAW,GACZ,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAErD,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAE/D,YAAY,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,mCAAmC,EACnC,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,GAChB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA;AAEhD,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5D,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,qBAAqB,CAAA;AAE5B,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA"}
@@ -1 +1,5 @@
1
- export * from './runtime.js';
1
+ export { Dispatch } from './dispatch.js';
2
+ export { makeApplication } from './makeApplication.js';
3
+ export { makeElement } from './makeElement.js';
4
+ export { SlowPhase, defaultSlowCallback } from './slowPhase.js';
5
+ export { embed, hydrate, run } from './start.js';
@@ -0,0 +1,71 @@
1
+ import { Layer, Schema } from 'effect';
2
+ import { Document, type HtmlBuilder } from '../html/index.js';
3
+ import type { ManagedResources } from '../managedResource/index.js';
4
+ import type { Ports } from '../port/index.js';
5
+ import type { Subscriptions } from '../subscription/subscription.js';
6
+ import type { Return as UpdateReturn } from '../update/index.js';
7
+ import { Url } from '../url/index.js';
8
+ import type { RoutingConfig } from './browserListeners.js';
9
+ import type { CrashConfig } from './crashUI.js';
10
+ import type { DevToolsConfig } from './devToolsConfig.js';
11
+ import { type FlagsSchemaConfig, type MakeRuntimeReturn } from './runtime.js';
12
+ import type { SlowConfig } from './slowPhase.js';
13
+ import type { ViewTransitionConfig } from './viewTransition.js';
14
+ type BaseApplicationConfig<Model, Message, Resources = never, ManagedResourceServices = never, P extends Ports | undefined = undefined> = Readonly<{
15
+ Model: Schema.Codec<Model, any, unknown, unknown>;
16
+ update: (model: Model, message: Message) => UpdateReturn<Model, Message, Resources | ManagedResourceServices>;
17
+ view: (model: Model, h: HtmlBuilder<Message>) => Document;
18
+ subscriptions?: Subscriptions<Model, Message, Resources | ManagedResourceServices>;
19
+ container: HTMLElement | null;
20
+ ports?: P;
21
+ crash?: CrashConfig<Model, Message>;
22
+ slow?: SlowConfig<Model, Message>;
23
+ viewTransition?: ViewTransitionConfig<Model, Message>;
24
+ freezeModel?: boolean;
25
+ preserveScroll?: boolean;
26
+ resources?: Layer.Layer<Resources>;
27
+ managedResources?: ManagedResources<Model, Message, ManagedResourceServices>;
28
+ devTools?: DevToolsConfig;
29
+ }>;
30
+ /** Configuration for `makeApplication` with Flags and URL routing. */
31
+ export type RoutingApplicationConfigWithFlags<Model, Message, Flags, Resources = never, ManagedResourceServices = never, P extends Ports | undefined = undefined> = BaseApplicationConfig<Model, Message, Resources, ManagedResourceServices, P> & FlagsSchemaConfig<Flags> & Readonly<{
32
+ routing: RoutingConfig<Message>;
33
+ init: (flags: Flags, url: Url) => UpdateReturn<Model, Message, Resources | ManagedResourceServices>;
34
+ }>;
35
+ /** Configuration for `makeApplication` with URL routing but no Flags. */
36
+ export type RoutingApplicationConfig<Model, Message, Resources = never, ManagedResourceServices = never, P extends Ports | undefined = undefined> = BaseApplicationConfig<Model, Message, Resources, ManagedResourceServices, P> & Readonly<{
37
+ routing: RoutingConfig<Message>;
38
+ init: (url: Url) => UpdateReturn<Model, Message, Resources | ManagedResourceServices>;
39
+ }>;
40
+ /** Configuration for `makeApplication` with Flags but no URL routing. */
41
+ export type ApplicationConfigWithFlags<Model, Message, Flags, Resources = never, ManagedResourceServices = never, P extends Ports | undefined = undefined> = BaseApplicationConfig<Model, Message, Resources, ManagedResourceServices, P> & FlagsSchemaConfig<Flags> & Readonly<{
42
+ init: (flags: Flags) => UpdateReturn<Model, Message, Resources | ManagedResourceServices>;
43
+ }>;
44
+ /** Configuration for `makeApplication` without Flags or URL routing. */
45
+ export type ApplicationConfig<Model, Message, Resources = never, ManagedResourceServices = never, P extends Ports | undefined = undefined> = BaseApplicationConfig<Model, Message, Resources, ManagedResourceServices, P> & Readonly<{
46
+ init: () => UpdateReturn<Model, Message, Resources | ManagedResourceServices>;
47
+ }>;
48
+ /** The `init` function type for a `makeApplication` app without URL routing. */
49
+ export type ApplicationInit<Model, Message, Flags = void, Resources = never, ManagedResourceServices = never> = Flags extends void ? () => UpdateReturn<Model, Message, Resources | ManagedResourceServices> : (flags: Flags) => UpdateReturn<Model, Message, Resources | ManagedResourceServices>;
50
+ /** The `init` function type for a `makeApplication` app with URL routing, receives the current URL and optional Flags. */
51
+ export type RoutingApplicationInit<Model, Message, Flags = void, Resources = never, ManagedResourceServices = never> = Flags extends void ? (url: Url) => UpdateReturn<Model, Message, Resources | ManagedResourceServices> : (flags: Flags, url: Url) => UpdateReturn<Model, Message, Resources | ManagedResourceServices>;
52
+ /** Creates a Foldkit application that owns the page and returns a runtime that
53
+ * can be passed to `run`. The `view` returns a `Document`, so the runtime
54
+ * manages `document.title` and the canonical / og:url tags. Add a `routing`
55
+ * config for URL routing. Use one page-owning application per document. To
56
+ * mount an app scoped to a node without touching the document `<head>`, use
57
+ * `makeElement`. */
58
+ export declare function makeApplication<Model, Message extends {
59
+ _tag: string;
60
+ }, Flags, Resources = never, ManagedResourceServices = never, P extends Ports | undefined = undefined>(config: RoutingApplicationConfigWithFlags<Model, Message, Flags, Resources, ManagedResourceServices, P>): MakeRuntimeReturn<P, Flags, Resources, 'Application'>;
61
+ export declare function makeApplication<Model, Message extends {
62
+ _tag: string;
63
+ }, Resources = never, ManagedResourceServices = never, P extends Ports | undefined = undefined>(config: RoutingApplicationConfig<Model, Message, Resources, ManagedResourceServices, P>): MakeRuntimeReturn<P, void, Resources, 'Application'>;
64
+ export declare function makeApplication<Model, Message extends {
65
+ _tag: string;
66
+ }, Flags, Resources = never, ManagedResourceServices = never, P extends Ports | undefined = undefined>(config: ApplicationConfigWithFlags<Model, Message, Flags, Resources, ManagedResourceServices, P>): MakeRuntimeReturn<P, Flags, Resources, 'Application'>;
67
+ export declare function makeApplication<Model, Message extends {
68
+ _tag: string;
69
+ }, Resources = never, ManagedResourceServices = never, P extends Ports | undefined = undefined>(config: ApplicationConfig<Model, Message, Resources, ManagedResourceServices, P>): MakeRuntimeReturn<P, void, Resources, 'Application'>;
70
+ export {};
71
+ //# sourceMappingURL=makeApplication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"makeApplication.d.ts","sourceRoot":"","sources":["../../src/runtime/makeApplication.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ import { Option, Predicate, Schema } from 'effect';
2
+ import { fromString as urlFromString } from '../url/index.js';
3
+ import { containRefusedPage, findDocumentHydration, hasServerRenderedMarkup, } from './hydrationHandoff.js';
4
+ import { makeRuntime, } from './runtime.js';
5
+ export function makeApplication(config) {
6
+ const { container } = config;
7
+ const hasRouting = 'routing' in config;
8
+ const hasFlags = 'Flags' in config;
9
+ const hydration = findDocumentHydration(container, hasFlags);
10
+ const resolvedContainer = hydration?.root ?? container;
11
+ if (resolvedContainer === null) {
12
+ // A server-rendered page whose root lost its stamp reaches exactly here:
13
+ // template injection put the render where the placeholder was, so
14
+ // `getElementById` finds nothing and the stamp that would have named the
15
+ // root is gone. There is no handoff to refuse further along, and the markup
16
+ // is as live as any other refused page, so it is contained here.
17
+ if (hasServerRenderedMarkup(document)) {
18
+ containRefusedPage(document);
19
+ }
20
+ throw new Error('[foldkit] Container is null. Make sure the element exists in the DOM ' +
21
+ 'before calling makeApplication (e.g. that your <div id="root"></div> has ' +
22
+ 'rendered, and your script runs after it). On a server-rendered page ' +
23
+ 'the runtime instead finds the root by its `data-foldkit-app` stamp.');
24
+ }
25
+ const currentUrl = hasRouting
26
+ ? Option.getOrThrow(urlFromString(window.location.href))
27
+ : undefined;
28
+ const baseConfig = {
29
+ kind: 'Application',
30
+ Model: config.Model,
31
+ update: config.update,
32
+ view: config.view,
33
+ manageDocument: true,
34
+ ports: config.ports,
35
+ ...(config.subscriptions && { subscriptions: config.subscriptions }),
36
+ container: resolvedContainer,
37
+ ...(hydration && { hydration }),
38
+ ...(hasRouting && { routing: config.routing }),
39
+ ...(config.crash && { crash: config.crash }),
40
+ ...(Predicate.isNotUndefined(config.slow) && {
41
+ slow: config.slow,
42
+ }),
43
+ ...(Predicate.isNotUndefined(config.viewTransition) && {
44
+ viewTransition: config.viewTransition,
45
+ }),
46
+ ...(Predicate.isNotUndefined(config.freezeModel) && {
47
+ freezeModel: config.freezeModel,
48
+ }),
49
+ ...(Predicate.isNotUndefined(config.preserveScroll) && {
50
+ preserveScroll: config.preserveScroll,
51
+ }),
52
+ ...(config.resources && { resources: config.resources }),
53
+ ...(config.managedResources && {
54
+ managedResources: config.managedResources,
55
+ }),
56
+ ...(Predicate.isNotUndefined(config.devTools) && {
57
+ devTools: config.devTools,
58
+ }),
59
+ };
60
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
61
+ if (hasFlags && hasRouting) {
62
+ return makeRuntime({
63
+ ...baseConfig,
64
+ Flags: config.Flags,
65
+ configuredFlags: Option.none(),
66
+ isFlagsRequired: true,
67
+ init: (flags, url) => config.init(flags, url ?? currentUrl),
68
+ });
69
+ }
70
+ else if (hasRouting) {
71
+ return makeRuntime({
72
+ ...baseConfig,
73
+ Flags: Schema.Void,
74
+ configuredFlags: Option.none(),
75
+ isFlagsRequired: false,
76
+ init: (_flags, url) => config.init(url ?? currentUrl),
77
+ });
78
+ }
79
+ else if (hasFlags) {
80
+ return makeRuntime({
81
+ ...baseConfig,
82
+ Flags: config.Flags,
83
+ configuredFlags: Option.none(),
84
+ isFlagsRequired: true,
85
+ init: (flags) => config.init(flags),
86
+ });
87
+ }
88
+ else {
89
+ return makeRuntime({
90
+ ...baseConfig,
91
+ Flags: Schema.Void,
92
+ configuredFlags: Option.none(),
93
+ isFlagsRequired: false,
94
+ init: () => config.init(),
95
+ });
96
+ }
97
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
98
+ }