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.
- package/dist/devTools/host.d.ts +2 -2
- package/dist/devTools/host.d.ts.map +1 -1
- package/dist/devTools/host.js +1 -1
- package/dist/experimental/machine/machine.d.ts +278 -84
- package/dist/experimental/machine/machine.d.ts.map +1 -1
- package/dist/experimental/machine/machine.js +362 -212
- package/dist/runtime/browserListeners.d.ts +7 -1
- package/dist/runtime/browserListeners.d.ts.map +1 -1
- package/dist/runtime/browserScheduler.d.ts +3 -0
- package/dist/runtime/browserScheduler.d.ts.map +1 -0
- package/dist/runtime/browserScheduler.js +22 -0
- package/dist/runtime/crashUI.d.ts +36 -5
- package/dist/runtime/crashUI.d.ts.map +1 -1
- package/dist/runtime/crashUI.js +55 -2
- package/dist/runtime/devToolsConfig.d.ts +85 -0
- package/dist/runtime/devToolsConfig.d.ts.map +1 -0
- package/dist/runtime/devToolsConfig.js +49 -0
- package/dist/runtime/devToolsIntegration.d.ts +56 -0
- package/dist/runtime/devToolsIntegration.d.ts.map +1 -0
- package/dist/runtime/devToolsIntegration.js +186 -0
- package/dist/runtime/dispatch.d.ts +10 -0
- package/dist/runtime/dispatch.d.ts.map +1 -0
- package/dist/runtime/dispatch.js +4 -0
- package/dist/runtime/documentMetadata.d.ts +3 -0
- package/dist/runtime/documentMetadata.d.ts.map +1 -0
- package/dist/runtime/documentMetadata.js +148 -0
- package/dist/runtime/duplicateIdScanner.d.ts +16 -0
- package/dist/runtime/duplicateIdScanner.d.ts.map +1 -0
- package/dist/runtime/duplicateIdScanner.js +70 -0
- package/dist/runtime/hmrModelBridge.d.ts +4 -0
- package/dist/runtime/hmrModelBridge.d.ts.map +1 -0
- package/dist/runtime/hmrModelBridge.js +43 -0
- package/dist/runtime/hostConnector.d.ts +61 -0
- package/dist/runtime/hostConnector.d.ts.map +1 -0
- package/dist/runtime/hostConnector.js +141 -0
- package/dist/runtime/hydrationHandoff.d.ts +44 -0
- package/dist/runtime/hydrationHandoff.d.ts.map +1 -0
- package/dist/runtime/hydrationHandoff.js +395 -0
- package/dist/runtime/index.d.ts +16 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +5 -1
- package/dist/runtime/makeApplication.d.ts +71 -0
- package/dist/runtime/makeApplication.d.ts.map +1 -0
- package/dist/runtime/makeApplication.js +98 -0
- package/dist/runtime/makeElement.d.ts +69 -0
- package/dist/runtime/makeElement.d.ts.map +1 -0
- package/dist/runtime/makeElement.js +81 -0
- package/dist/runtime/managedResourceFibers.d.ts +18 -0
- package/dist/runtime/managedResourceFibers.d.ts.map +1 -0
- package/dist/runtime/managedResourceFibers.js +47 -0
- package/dist/runtime/messageQueue.d.ts +27 -0
- package/dist/runtime/messageQueue.d.ts.map +1 -0
- package/dist/runtime/messageQueue.js +133 -0
- package/dist/runtime/public.d.ts +2 -3
- package/dist/runtime/public.d.ts.map +1 -1
- package/dist/runtime/public.js +1 -1
- package/dist/runtime/renderer.d.ts +69 -0
- package/dist/runtime/renderer.d.ts.map +1 -0
- package/dist/runtime/renderer.js +512 -0
- package/dist/runtime/resourceProvider.d.ts +33 -0
- package/dist/runtime/resourceProvider.d.ts.map +1 -0
- package/dist/runtime/resourceProvider.js +98 -0
- package/dist/runtime/runtime.d.ts +133 -412
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +118 -2184
- package/dist/runtime/runtimeStatus.d.ts +15 -0
- package/dist/runtime/runtimeStatus.d.ts.map +1 -0
- package/dist/runtime/runtimeStatus.js +6 -0
- package/dist/runtime/slowPhase.d.ts +82 -0
- package/dist/runtime/slowPhase.d.ts.map +1 -0
- package/dist/runtime/slowPhase.js +83 -0
- package/dist/runtime/start.d.ts +96 -0
- package/dist/runtime/start.d.ts.map +1 -0
- package/dist/runtime/start.js +99 -0
- package/dist/runtime/subscriptionFibers.d.ts +21 -0
- package/dist/runtime/subscriptionFibers.d.ts.map +1 -0
- package/dist/runtime/subscriptionFibers.js +47 -0
- package/dist/runtime/visibility.d.ts +8 -0
- package/dist/runtime/visibility.d.ts.map +1 -0
- package/dist/runtime/visibility.js +6 -0
- package/dist/test/scene.js +1 -1
- 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
|
+
});
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
|
-
export
|
|
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"}
|
package/dist/runtime/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
export
|
|
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
|
+
}
|