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,148 @@
|
|
|
1
|
+
import { Array } from 'effect';
|
|
2
|
+
import { textDirectionToAttribute } from '../html/index.js';
|
|
3
|
+
const currentLocationUrl = () => {
|
|
4
|
+
const { origin, pathname, search } = window.location;
|
|
5
|
+
return `${origin}${pathname}${search}`;
|
|
6
|
+
};
|
|
7
|
+
const documentMetadataStates = new WeakMap();
|
|
8
|
+
const observeDocumentMetadataMutations = (observer) => {
|
|
9
|
+
const observedHead = document.head;
|
|
10
|
+
observer.observe(observedHead, {
|
|
11
|
+
attributes: true,
|
|
12
|
+
attributeFilter: ['rel', 'href', 'property', 'content'],
|
|
13
|
+
childList: true,
|
|
14
|
+
characterData: true,
|
|
15
|
+
subtree: true,
|
|
16
|
+
});
|
|
17
|
+
observer.observe(document.documentElement, {
|
|
18
|
+
attributes: true,
|
|
19
|
+
attributeFilter: ['lang', 'dir'],
|
|
20
|
+
});
|
|
21
|
+
return observedHead;
|
|
22
|
+
};
|
|
23
|
+
const getOrCreateDocumentMetadataState = () => {
|
|
24
|
+
const existingState = documentMetadataStates.get(document);
|
|
25
|
+
if (existingState !== undefined) {
|
|
26
|
+
return existingState;
|
|
27
|
+
}
|
|
28
|
+
const invalidation = { isInvalidated: true };
|
|
29
|
+
const observer = new MutationObserver(() => {
|
|
30
|
+
invalidation.isInvalidated = true;
|
|
31
|
+
});
|
|
32
|
+
const metadataState = {
|
|
33
|
+
elements: {},
|
|
34
|
+
observer,
|
|
35
|
+
invalidation,
|
|
36
|
+
observedHead: observeDocumentMetadataMutations(observer),
|
|
37
|
+
};
|
|
38
|
+
documentMetadataStates.set(document, metadataState);
|
|
39
|
+
return metadataState;
|
|
40
|
+
};
|
|
41
|
+
const findOrCreateDocumentMetadataElements = (metadataState) => {
|
|
42
|
+
const { elements } = metadataState;
|
|
43
|
+
let canonical = elements.canonical;
|
|
44
|
+
if (canonical === undefined || canonical.parentNode !== document.head) {
|
|
45
|
+
canonical =
|
|
46
|
+
document.head.querySelector('link[rel="canonical"]') ??
|
|
47
|
+
document.head.appendChild(document.createElement('link'));
|
|
48
|
+
elements.canonical = canonical;
|
|
49
|
+
}
|
|
50
|
+
let ogUrl = elements.ogUrl;
|
|
51
|
+
if (ogUrl === undefined || ogUrl.parentNode !== document.head) {
|
|
52
|
+
ogUrl =
|
|
53
|
+
document.head.querySelector('meta[property="og:url"]') ??
|
|
54
|
+
document.head.appendChild(document.createElement('meta'));
|
|
55
|
+
elements.ogUrl = ogUrl;
|
|
56
|
+
}
|
|
57
|
+
return { canonical, ogUrl };
|
|
58
|
+
};
|
|
59
|
+
const readOrCacheCurrentLocationUrl = (metadataState) => {
|
|
60
|
+
const currentLocationHref = window.location.href;
|
|
61
|
+
if (metadataState.cachedLocationHref === currentLocationHref &&
|
|
62
|
+
metadataState.cachedLocationCanonical !== undefined) {
|
|
63
|
+
return metadataState.cachedLocationCanonical;
|
|
64
|
+
}
|
|
65
|
+
const canonicalUrl = currentLocationUrl();
|
|
66
|
+
metadataState.cachedLocationHref = currentLocationHref;
|
|
67
|
+
metadataState.cachedLocationCanonical = canonicalUrl;
|
|
68
|
+
return canonicalUrl;
|
|
69
|
+
};
|
|
70
|
+
const rebindDocumentMetadataObserverToCurrentHead = (metadataState) => {
|
|
71
|
+
// NOTE: a MutationObserver remains attached to a detached head after
|
|
72
|
+
// document.head is replaced. Rebind it and invalidate the metadata snapshot
|
|
73
|
+
// before the next unchanged guard.
|
|
74
|
+
metadataState.observer.disconnect();
|
|
75
|
+
metadataState.observedHead = observeDocumentMetadataMutations(metadataState.observer);
|
|
76
|
+
metadataState.invalidation.isInvalidated = true;
|
|
77
|
+
};
|
|
78
|
+
const reconcileDocumentMetadata = (metadataState, nextMetadata) => {
|
|
79
|
+
if (document.title !== nextMetadata.title) {
|
|
80
|
+
document.title = nextMetadata.title;
|
|
81
|
+
}
|
|
82
|
+
const { documentElement } = document;
|
|
83
|
+
if (nextMetadata.lang !== undefined &&
|
|
84
|
+
documentElement.lang !== nextMetadata.lang) {
|
|
85
|
+
documentElement.lang = nextMetadata.lang;
|
|
86
|
+
}
|
|
87
|
+
if (nextMetadata.dirAttribute !== undefined &&
|
|
88
|
+
documentElement.dir !== nextMetadata.dirAttribute) {
|
|
89
|
+
documentElement.dir = nextMetadata.dirAttribute;
|
|
90
|
+
}
|
|
91
|
+
const metadataElements = findOrCreateDocumentMetadataElements(metadataState);
|
|
92
|
+
if (metadataElements.canonical.getAttribute('rel') !== 'canonical') {
|
|
93
|
+
metadataElements.canonical.setAttribute('rel', 'canonical');
|
|
94
|
+
}
|
|
95
|
+
if (metadataElements.canonical.getAttribute('href') !==
|
|
96
|
+
nextMetadata.canonicalUrl) {
|
|
97
|
+
metadataElements.canonical.setAttribute('href', nextMetadata.canonicalUrl);
|
|
98
|
+
}
|
|
99
|
+
if (metadataElements.ogUrl.getAttribute('property') !== 'og:url') {
|
|
100
|
+
metadataElements.ogUrl.setAttribute('property', 'og:url');
|
|
101
|
+
}
|
|
102
|
+
if (metadataElements.ogUrl.getAttribute('content') !== nextMetadata.ogUrl) {
|
|
103
|
+
metadataElements.ogUrl.setAttribute('content', nextMetadata.ogUrl);
|
|
104
|
+
}
|
|
105
|
+
metadataState.lastApplied = nextMetadata;
|
|
106
|
+
metadataState.invalidation.isInvalidated = false;
|
|
107
|
+
// NOTE: clear the records produced by Foldkit's own writes before the
|
|
108
|
+
// observer callback runs. Otherwise the next unchanged render would be
|
|
109
|
+
// marked dirty and repeat every DOM read this cache is meant to avoid.
|
|
110
|
+
metadataState.observer.takeRecords();
|
|
111
|
+
};
|
|
112
|
+
export const applyDocumentMetadata = (nextDocument, mountedRoot) => {
|
|
113
|
+
if (!mountedRoot || !document.body.contains(mountedRoot)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const metadataState = getOrCreateDocumentMetadataState();
|
|
117
|
+
if (metadataState.observedHead !== document.head) {
|
|
118
|
+
rebindDocumentMetadataObserverToCurrentHead(metadataState);
|
|
119
|
+
}
|
|
120
|
+
const canonicalUrl = nextDocument.canonical ?? readOrCacheCurrentLocationUrl(metadataState);
|
|
121
|
+
const ogUrl = nextDocument.ogUrl ?? canonicalUrl;
|
|
122
|
+
const dirAttribute = nextDocument.dir === undefined
|
|
123
|
+
? undefined
|
|
124
|
+
: textDirectionToAttribute(nextDocument.dir);
|
|
125
|
+
// NOTE: MutationObserver callbacks are asynchronous. Consume queued records
|
|
126
|
+
// synchronously before trusting the unchanged fast path.
|
|
127
|
+
if (Array.isArrayNonEmpty(metadataState.observer.takeRecords())) {
|
|
128
|
+
metadataState.invalidation.isInvalidated = true;
|
|
129
|
+
}
|
|
130
|
+
const lastApplied = metadataState.lastApplied;
|
|
131
|
+
const isAppliedMetadataCurrent = !metadataState.invalidation.isInvalidated &&
|
|
132
|
+
lastApplied !== undefined &&
|
|
133
|
+
lastApplied.title === nextDocument.title &&
|
|
134
|
+
lastApplied.lang === nextDocument.lang &&
|
|
135
|
+
lastApplied.dirAttribute === dirAttribute &&
|
|
136
|
+
lastApplied.canonicalUrl === canonicalUrl &&
|
|
137
|
+
lastApplied.ogUrl === ogUrl;
|
|
138
|
+
if (isAppliedMetadataCurrent) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
reconcileDocumentMetadata(metadataState, {
|
|
142
|
+
title: nextDocument.title,
|
|
143
|
+
lang: nextDocument.lang,
|
|
144
|
+
dirAttribute,
|
|
145
|
+
canonicalUrl,
|
|
146
|
+
ogUrl,
|
|
147
|
+
});
|
|
148
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The coalescing scanner: `schedule` queues a scan of a root, `cancel` drops it. */
|
|
2
|
+
export type DuplicateIdScanner = Readonly<{
|
|
3
|
+
schedule: (root: Node | undefined) => void;
|
|
4
|
+
cancel: () => void;
|
|
5
|
+
}>;
|
|
6
|
+
/** Coalesces `warnDuplicateIds` scans so rapid successive renders trigger at
|
|
7
|
+
* most one full-tree scan per `DUPLICATE_ID_SCAN_INTERVAL_MS`, bounding the
|
|
8
|
+
* cost under high-frequency dev rendering such as animationFrame subscriptions
|
|
9
|
+
* or per-keystroke input. A real duplicate id persists across renders, so
|
|
10
|
+
* scanning the latest tree on a trailing timer never misses a genuine
|
|
11
|
+
* collision, and the retained `warnedIds` keeps each id to a single warning.
|
|
12
|
+
* Dev-only: the runtime both creates the scanner and calls `schedule` behind
|
|
13
|
+
* `import.meta.hot`, so the scanner and its DOM scan tree-shake out of
|
|
14
|
+
* production builds. */
|
|
15
|
+
export declare const createDuplicateIdScanner: () => DuplicateIdScanner;
|
|
16
|
+
//# sourceMappingURL=duplicateIdScanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicateIdScanner.d.ts","sourceRoot":"","sources":["../../src/runtime/duplicateIdScanner.ts"],"names":[],"mappings":"AAiDA,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,KAAK,IAAI,CAAA;IAC1C,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB,CAAC,CAAA;AAEF;;;;;;;;yBAQyB;AACzB,eAAO,MAAM,wBAAwB,QAAO,kBAyB3C,CAAA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Array } from 'effect';
|
|
2
|
+
/** Dev-only scan for duplicate DOM ids within the Foldkit-rendered root.
|
|
3
|
+
*
|
|
4
|
+
* Duplicate ids are invalid HTML but browsers do not report them, so
|
|
5
|
+
* `getElementById` / `querySelector` silently resolve to the first match and
|
|
6
|
+
* focus or ARIA labelling can target the wrong element with no error. The
|
|
7
|
+
* scan is scoped to `root` (not the whole document) so unrelated page ids do
|
|
8
|
+
* not trigger it, warns rather than throws, and dedupes through `warnedIds`
|
|
9
|
+
* so the same collision is reported at most once instead of every render. */
|
|
10
|
+
const warnDuplicateIds = (root, warnedIds) => {
|
|
11
|
+
if (!(root instanceof Element)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const seenIds = new Set();
|
|
15
|
+
const duplicateIds = new Set();
|
|
16
|
+
const elementsWithId = Array.fromIterable(root.querySelectorAll('[id]'));
|
|
17
|
+
if (root.id !== '') {
|
|
18
|
+
elementsWithId.unshift(root);
|
|
19
|
+
}
|
|
20
|
+
for (const element of elementsWithId) {
|
|
21
|
+
const { id } = element;
|
|
22
|
+
if (seenIds.has(id)) {
|
|
23
|
+
duplicateIds.add(id);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
seenIds.add(id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const id of duplicateIds) {
|
|
30
|
+
if (!warnedIds.has(id)) {
|
|
31
|
+
warnedIds.add(id);
|
|
32
|
+
console.warn(`[foldkit] Duplicate DOM id "${id}" in the rendered tree. Ids must be ` +
|
|
33
|
+
'unique within the document; otherwise focus and ARIA labelling can ' +
|
|
34
|
+
'silently target the wrong element. Give each element a unique id.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const DUPLICATE_ID_SCAN_INTERVAL_MS = 1000;
|
|
39
|
+
/** Coalesces `warnDuplicateIds` scans so rapid successive renders trigger at
|
|
40
|
+
* most one full-tree scan per `DUPLICATE_ID_SCAN_INTERVAL_MS`, bounding the
|
|
41
|
+
* cost under high-frequency dev rendering such as animationFrame subscriptions
|
|
42
|
+
* or per-keystroke input. A real duplicate id persists across renders, so
|
|
43
|
+
* scanning the latest tree on a trailing timer never misses a genuine
|
|
44
|
+
* collision, and the retained `warnedIds` keeps each id to a single warning.
|
|
45
|
+
* Dev-only: the runtime both creates the scanner and calls `schedule` behind
|
|
46
|
+
* `import.meta.hot`, so the scanner and its DOM scan tree-shake out of
|
|
47
|
+
* production builds. */
|
|
48
|
+
export const createDuplicateIdScanner = () => {
|
|
49
|
+
const warnedIds = new Set();
|
|
50
|
+
let timeoutHandle;
|
|
51
|
+
let latestRoot;
|
|
52
|
+
const schedule = (root) => {
|
|
53
|
+
latestRoot = root;
|
|
54
|
+
if (timeoutHandle !== undefined) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
timeoutHandle = setTimeout(() => {
|
|
58
|
+
timeoutHandle = undefined;
|
|
59
|
+
warnDuplicateIds(latestRoot, warnedIds);
|
|
60
|
+
}, DUPLICATE_ID_SCAN_INTERVAL_MS);
|
|
61
|
+
};
|
|
62
|
+
const cancel = () => {
|
|
63
|
+
if (timeoutHandle !== undefined) {
|
|
64
|
+
clearTimeout(timeoutHandle);
|
|
65
|
+
timeoutHandle = undefined;
|
|
66
|
+
}
|
|
67
|
+
latestRoot = undefined;
|
|
68
|
+
};
|
|
69
|
+
return { schedule, cancel };
|
|
70
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hmrModelBridge.d.ts","sourceRoot":"","sources":["../../src/runtime/hmrModelBridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgC,MAAM,QAAQ,CAAA;AAa7D,eAAO,MAAM,aAAa,OACpB,MAAM,gBACI,OAAO,eACR,OAAO,KACnB,IASF,CAAA;AAQD,eAAO,MAAM,eAAe,cAAe,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAqCxE,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Effect, Exit, Function, Schema, pipe } from 'effect';
|
|
2
|
+
import { PreserveModelMessage, RequestModelMessage, RestoreModelMessage, } from './hmrProtocol.js';
|
|
3
|
+
const encodePreserveModelMessage = Schema.encodeUnknownSync(PreserveModelMessage);
|
|
4
|
+
const encodeRequestModelMessage = Schema.encodeUnknownSync(RequestModelMessage);
|
|
5
|
+
const decodeRestoreModelMessage = Schema.decodeUnknownExit(RestoreModelMessage);
|
|
6
|
+
export const preserveModel = (id, encodedModel, isHmrReload) => {
|
|
7
|
+
if (import.meta.hot) {
|
|
8
|
+
import.meta.hot.send('foldkit:preserve-model', encodePreserveModelMessage(PreserveModelMessage.make({ id, model: encodedModel, isHmrReload })));
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const PLUGIN_RESPONSE_TIMEOUT_MS = 500;
|
|
12
|
+
// NOTE: asks @foldkit/vite-plugin for a model preserved across the last HMR
|
|
13
|
+
// reload. The plugin only serves a model whose preservation was flushed by a
|
|
14
|
+
// reload, so a host-driven dispose-then-embed remount initializes fresh while
|
|
15
|
+
// a code reload restores state.
|
|
16
|
+
export const resolveHmrModel = (runtimeId) => {
|
|
17
|
+
const hot = import.meta.hot;
|
|
18
|
+
if (!hot) {
|
|
19
|
+
return Effect.succeed(undefined);
|
|
20
|
+
}
|
|
21
|
+
return pipe(Effect.callback(resume => {
|
|
22
|
+
const handler = (message) => {
|
|
23
|
+
Exit.match(decodeRestoreModelMessage(message), {
|
|
24
|
+
onFailure: Function.constVoid,
|
|
25
|
+
onSuccess: ({ id, model }) => {
|
|
26
|
+
if (id === runtimeId) {
|
|
27
|
+
hot.off('foldkit:restore-model', handler);
|
|
28
|
+
resume(Effect.succeed(model));
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
hot.on('foldkit:restore-model', handler);
|
|
34
|
+
hot.send('foldkit:request-model', encodeRequestModelMessage(RequestModelMessage.make({ id: runtimeId })));
|
|
35
|
+
return Effect.sync(() => hot.off('foldkit:restore-model', handler));
|
|
36
|
+
}), Effect.timeout(PLUGIN_RESPONSE_TIMEOUT_MS), Effect.catchTag('TimeoutError', () => {
|
|
37
|
+
console.warn('[foldkit] No response from @foldkit/vite-plugin. Add it to your vite.config.ts for HMR model preservation:\n\n' +
|
|
38
|
+
" import { foldkit } from '@foldkit/vite-plugin'\n\n" +
|
|
39
|
+
' export default defineConfig({ plugins: [foldkit()] })\n\n' +
|
|
40
|
+
'Starting without HMR support.');
|
|
41
|
+
return Effect.succeed(undefined);
|
|
42
|
+
}));
|
|
43
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Exit, Option, Schema } from 'effect';
|
|
2
|
+
import { type Inbound, type Outbound, type Ports, type __PortChannels } from '../port/index.js';
|
|
3
|
+
/** Host-side handle for one inbound Port. `send` validates the value by
|
|
4
|
+
* decoding it against the Port's Schema: on success the decoded value enters
|
|
5
|
+
* the app through the Port's Subscription; on failure nothing reaches the
|
|
6
|
+
* app, the failure is logged, and the returned `Exit` carries the
|
|
7
|
+
* `SchemaError`. Sends after `dispose` are no-ops. */
|
|
8
|
+
export type InboundPortHandle<Encoded> = Readonly<{
|
|
9
|
+
send: (value: Encoded) => Exit.Exit<void, Schema.SchemaError>;
|
|
10
|
+
}>;
|
|
11
|
+
/** Host-side handle for one outbound Port. `subscribe` registers a listener
|
|
12
|
+
* for the encoded values the app emits with `Port.emit` and returns an
|
|
13
|
+
* unsubscribe function. Multiple listeners receive each value in
|
|
14
|
+
* registration order. */
|
|
15
|
+
export type OutboundPortHandle<Encoded> = Readonly<{
|
|
16
|
+
subscribe: (listener: (value: Encoded) => void) => () => void;
|
|
17
|
+
}>;
|
|
18
|
+
/** The inbound half of `PortHandles`: one `InboundPortHandle` per declared
|
|
19
|
+
* inbound Port, keyed by Port name. */
|
|
20
|
+
export type InboundPortHandles<InboundPorts> = InboundPorts extends Readonly<Record<string, Inbound<any, any>>> ? {
|
|
21
|
+
readonly [Name in keyof InboundPorts]: InboundPorts[Name] extends Inbound<any, infer Encoded> ? InboundPortHandle<Encoded> : never;
|
|
22
|
+
} : unknown;
|
|
23
|
+
/** The outbound half of `PortHandles`: one `OutboundPortHandle` per declared
|
|
24
|
+
* outbound Port, keyed by Port name. */
|
|
25
|
+
export type OutboundPortHandles<OutboundPorts> = OutboundPorts extends Readonly<Record<string, Outbound<any, any>>> ? {
|
|
26
|
+
readonly [Name in keyof OutboundPorts]: OutboundPorts[Name] extends Outbound<any, infer Encoded> ? OutboundPortHandle<Encoded> : never;
|
|
27
|
+
} : unknown;
|
|
28
|
+
/** The `ports` field of an `EmbedHandle`: one `InboundPortHandle` or
|
|
29
|
+
* `OutboundPortHandle` per declared Port, keyed by Port name. */
|
|
30
|
+
export type PortHandles<P extends Ports | undefined> = P extends Ports ? InboundPortHandles<P['inbound']> & OutboundPortHandles<P['outbound']> : unknown;
|
|
31
|
+
/**
|
|
32
|
+
* The handle returned by `embed`. The host talks to the embedded app only
|
|
33
|
+
* through it: `ports.<name>.send` pushes values in, `ports.<name>.subscribe`
|
|
34
|
+
* listens to values the app emits, and `dispose` shuts the runtime down.
|
|
35
|
+
*
|
|
36
|
+
* `dispose` is idempotent. It interrupts the runtime and runs all cleanup:
|
|
37
|
+
* Subscriptions, ManagedResources, Mounts, listeners, and in-flight Commands
|
|
38
|
+
* stop, and the rendered DOM is removed with the container element restored
|
|
39
|
+
* empty in its place, ready for a fresh `embed`.
|
|
40
|
+
*/
|
|
41
|
+
export type EmbedHandle<P extends Ports | undefined = undefined> = Readonly<{
|
|
42
|
+
ports: PortHandles<P>;
|
|
43
|
+
dispose: () => void;
|
|
44
|
+
}>;
|
|
45
|
+
export type HostConnector = Readonly<{
|
|
46
|
+
sendInbound: (portName: string, port: Inbound<any, any>, value: unknown) => Exit.Exit<void, Schema.SchemaError>;
|
|
47
|
+
addListener: (port: Outbound<any, any>, listener: (encodedValue: unknown) => void) => () => void;
|
|
48
|
+
deliverOutbound: (port: Outbound<any, any>, encodedValue: unknown) => void;
|
|
49
|
+
bind: (deliverInbound: (port: Inbound<any, any>, value: unknown) => void) => void;
|
|
50
|
+
unbind: () => void;
|
|
51
|
+
dispose: () => void;
|
|
52
|
+
}>;
|
|
53
|
+
export declare const makeHostConnector: () => HostConnector;
|
|
54
|
+
export type PortChannelsBundle = Readonly<{
|
|
55
|
+
channels: __PortChannels;
|
|
56
|
+
deliverInbound: (port: Inbound<any, any>, value: unknown) => void;
|
|
57
|
+
}>;
|
|
58
|
+
export declare const makePortChannels: (ports: Ports, maybeConnector: Option.Option<HostConnector>) => PortChannelsBundle;
|
|
59
|
+
export declare const validatePorts: (ports: Ports) => void;
|
|
60
|
+
export declare const buildPortHandles: <P extends Ports | undefined>(ports: P, connector: HostConnector) => PortHandles<P>;
|
|
61
|
+
//# sourceMappingURL=hostConnector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostConnector.d.ts","sourceRoot":"","sources":["../../src/runtime/hostConnector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Cause, Exit, Function, Option, Predicate, Schema } from 'effect';
|
|
2
|
+
import { __makeInboundChannel, } from '../port/index.js';
|
|
3
|
+
export const makeHostConnector = () => {
|
|
4
|
+
let isDisposed = false;
|
|
5
|
+
let maybeDeliverInbound = Option.none();
|
|
6
|
+
const pendingInboundSends = [];
|
|
7
|
+
const listenersByPort = new Map();
|
|
8
|
+
const sendInbound = (portName, port, value) => {
|
|
9
|
+
if (isDisposed) {
|
|
10
|
+
return Exit.void;
|
|
11
|
+
}
|
|
12
|
+
const decodeExit = Schema.decodeUnknownExit(port.schema)(value);
|
|
13
|
+
Exit.match(decodeExit, {
|
|
14
|
+
onFailure: cause => {
|
|
15
|
+
console.error(`[foldkit] Inbound port "${portName}" rejected a value:`, Cause.squash(cause));
|
|
16
|
+
},
|
|
17
|
+
onSuccess: decodedValue => {
|
|
18
|
+
Option.match(maybeDeliverInbound, {
|
|
19
|
+
onNone: () => {
|
|
20
|
+
pendingInboundSends.push({ port, value: decodedValue });
|
|
21
|
+
},
|
|
22
|
+
onSome: deliverInbound => deliverInbound(port, decodedValue),
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
return Exit.asVoid(decodeExit);
|
|
27
|
+
};
|
|
28
|
+
const addListener = (port, listener) => {
|
|
29
|
+
if (isDisposed) {
|
|
30
|
+
return Function.constVoid;
|
|
31
|
+
}
|
|
32
|
+
const listeners = listenersByPort.get(port) ?? new Set();
|
|
33
|
+
listenersByPort.set(port, listeners);
|
|
34
|
+
listeners.add(listener);
|
|
35
|
+
return () => {
|
|
36
|
+
listeners.delete(listener);
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
// NOTE: delivery is deferred to a microtask so a host listener never runs
|
|
40
|
+
// inside the runtime's Command fiber (a listener that synchronously calls
|
|
41
|
+
// send or dispose must not re-enter the runtime), and so a host that
|
|
42
|
+
// subscribes synchronously right after embed() returns still receives
|
|
43
|
+
// emissions from init Commands.
|
|
44
|
+
const deliverOutbound = (port, encodedValue) => {
|
|
45
|
+
if (isDisposed) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
queueMicrotask(() => {
|
|
49
|
+
if (isDisposed) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const listeners = listenersByPort.get(port) ?? new Set();
|
|
53
|
+
listeners.forEach(listener => {
|
|
54
|
+
try {
|
|
55
|
+
listener(encodedValue);
|
|
56
|
+
}
|
|
57
|
+
catch (listenerError) {
|
|
58
|
+
console.error('[foldkit] An outbound port listener threw:', listenerError);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
const bind = (deliverInbound) => {
|
|
64
|
+
maybeDeliverInbound = Option.some(deliverInbound);
|
|
65
|
+
const flushedSends = pendingInboundSends.splice(0);
|
|
66
|
+
flushedSends.forEach(({ port, value }) => deliverInbound(port, value));
|
|
67
|
+
};
|
|
68
|
+
const unbind = () => {
|
|
69
|
+
maybeDeliverInbound = Option.none();
|
|
70
|
+
};
|
|
71
|
+
const dispose = () => {
|
|
72
|
+
isDisposed = true;
|
|
73
|
+
pendingInboundSends.length = 0;
|
|
74
|
+
listenersByPort.forEach(listeners => listeners.clear());
|
|
75
|
+
listenersByPort.clear();
|
|
76
|
+
};
|
|
77
|
+
return { sendInbound, addListener, deliverOutbound, bind, unbind, dispose };
|
|
78
|
+
};
|
|
79
|
+
export const makePortChannels = (ports, maybeConnector) => {
|
|
80
|
+
const inboundChannelsByPort = new Map();
|
|
81
|
+
Object.values(ports.inbound ?? {}).forEach(port => {
|
|
82
|
+
inboundChannelsByPort.set(port, __makeInboundChannel());
|
|
83
|
+
});
|
|
84
|
+
const outboundPorts = new Set(Object.values(ports.outbound ?? {}));
|
|
85
|
+
const channels = {
|
|
86
|
+
isConfigured: true,
|
|
87
|
+
lookupInbound: port => Option.fromNullishOr(inboundChannelsByPort.get(port)),
|
|
88
|
+
lookupOutbound: port => outboundPorts.has(port)
|
|
89
|
+
? Option.some(encodedValue => Option.match(maybeConnector, {
|
|
90
|
+
onNone: Function.constVoid,
|
|
91
|
+
onSome: connector => connector.deliverOutbound(port, encodedValue),
|
|
92
|
+
}))
|
|
93
|
+
: Option.none(),
|
|
94
|
+
};
|
|
95
|
+
const deliverInbound = (port, value) => {
|
|
96
|
+
Option.match(Option.fromNullishOr(inboundChannelsByPort.get(port)), {
|
|
97
|
+
onNone: Function.constVoid,
|
|
98
|
+
onSome: channel => channel.deliver(value),
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
return { channels, deliverInbound };
|
|
102
|
+
};
|
|
103
|
+
export const validatePorts = (ports) => {
|
|
104
|
+
const inboundEntries = Object.entries(ports.inbound ?? {});
|
|
105
|
+
const outboundEntries = Object.entries(ports.outbound ?? {});
|
|
106
|
+
const inboundNames = new Set(inboundEntries.map(([name]) => name));
|
|
107
|
+
outboundEntries.forEach(([name]) => {
|
|
108
|
+
if (inboundNames.has(name)) {
|
|
109
|
+
throw new Error(`[foldkit] Port name "${name}" appears in both inbound and outbound. ` +
|
|
110
|
+
'Port names share one namespace on the EmbedHandle, so each name ' +
|
|
111
|
+
'must be unique across both records.');
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const seenPorts = new Set();
|
|
115
|
+
const allEntries = [...inboundEntries, ...outboundEntries];
|
|
116
|
+
allEntries.forEach(([name, port]) => {
|
|
117
|
+
if (seenPorts.has(port)) {
|
|
118
|
+
throw new Error(`[foldkit] The Port registered as "${name}" is also registered under ` +
|
|
119
|
+
'another name. Each entry in the ports record needs its own ' +
|
|
120
|
+
'Port.inbound or Port.outbound value.');
|
|
121
|
+
}
|
|
122
|
+
seenPorts.add(port);
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
export const buildPortHandles = (ports, connector) => {
|
|
126
|
+
const handles = {};
|
|
127
|
+
if (Predicate.isNotUndefined(ports)) {
|
|
128
|
+
Object.entries(ports.inbound ?? {}).forEach(([portName, port]) => {
|
|
129
|
+
handles[portName] = {
|
|
130
|
+
send: (value) => connector.sendInbound(portName, port, value),
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
Object.entries(ports.outbound ?? {}).forEach(([portName, port]) => {
|
|
134
|
+
handles[portName] = {
|
|
135
|
+
subscribe: (listener) => connector.addListener(port, listener),
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
140
|
+
return handles;
|
|
141
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Effect, Option, Schema } from 'effect';
|
|
2
|
+
/** Whether a boot adopts a server-rendered root (`Hydrate`) or builds the DOM
|
|
3
|
+
* fresh in the container (`Fresh`). */
|
|
4
|
+
export type BootMode = 'Fresh' | 'Hydrate';
|
|
5
|
+
export type HydrationConfig = Readonly<{
|
|
6
|
+
root: HTMLElement;
|
|
7
|
+
runtimeId: string;
|
|
8
|
+
flagsScripts: ReadonlyArray<HTMLScriptElement>;
|
|
9
|
+
isFlagsRequired: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const buildSkew: (root: HTMLElement, buildId: unknown, runtimeId: string) => Error | undefined;
|
|
12
|
+
export declare const containRefusedPage: (ownerDocument: Document) => void;
|
|
13
|
+
export declare const hasServerRenderedMarkup: (ownerDocument: Document) => boolean;
|
|
14
|
+
export declare const findDocumentHydration: (container: HTMLElement | null, isFlagsRequired: boolean) => HydrationConfig | undefined;
|
|
15
|
+
/** What the boot takes from the handoff: the server-rendered root the first
|
|
16
|
+
* render adopts, when this boot adopts one, and the Effect that produces the
|
|
17
|
+
* Flags `init` runs with, from the page's payload or from the app's own
|
|
18
|
+
* Flags Effect. */
|
|
19
|
+
export type ResolvedHydrationHandoff<Flags> = Readonly<{
|
|
20
|
+
maybeHydrationRoot: Option.Option<HTMLElement>;
|
|
21
|
+
resolveFlags: Effect.Effect<Flags>;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Decides whether this boot adopts the server-rendered root and where its
|
|
25
|
+
* Flags come from. A hydrating boot with no stamped root, a root served by
|
|
26
|
+
* another deployment, or a missing, duplicated, or undecodable Flags
|
|
27
|
+
* payload contains the served page and stops startup. The build id is
|
|
28
|
+
* compared before the payload text is read and before `init` runs, so
|
|
29
|
+
* nothing acts on another deployment's data. An HMR-restored Model skips
|
|
30
|
+
* adoption and gets a fresh patch against the stamped root.
|
|
31
|
+
*/
|
|
32
|
+
export declare const resolveHydrationHandoff: <Flags, Resources>({ bootMode, hydration, bootFlags, configuredFlags, isFlagsRequired, FlagsCodec, hmrModel, container, buildId, provideResources, }: Readonly<{
|
|
33
|
+
bootMode: BootMode;
|
|
34
|
+
hydration: HydrationConfig | undefined;
|
|
35
|
+
bootFlags: Effect.Effect<Flags, never, Resources> | undefined;
|
|
36
|
+
configuredFlags: Option.Option<Effect.Effect<Flags, never, Resources>>;
|
|
37
|
+
isFlagsRequired: boolean;
|
|
38
|
+
FlagsCodec: Schema.Codec<Flags, any, unknown, unknown>;
|
|
39
|
+
hmrModel: unknown;
|
|
40
|
+
container: HTMLElement;
|
|
41
|
+
buildId: string | undefined;
|
|
42
|
+
provideResources: <A>(effect: Effect.Effect<A, never, Resources>) => Effect.Effect<A>;
|
|
43
|
+
}>) => Effect.Effect<ResolvedHydrationHandoff<Flags>>;
|
|
44
|
+
//# sourceMappingURL=hydrationHandoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hydrationHandoff.d.ts","sourceRoot":"","sources":["../../src/runtime/hydrationHandoff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAE,MAAM,EAAa,MAAM,EAAQ,MAAM,QAAQ,CAAA;AAQvE;wCACwC;AACxC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAA;AAK1C,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,IAAI,EAAE,WAAW,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAA;IAC9C,eAAe,EAAE,OAAO,CAAA;CACzB,CAAC,CAAA;AAsGF,eAAO,MAAM,SAAS,SACd,WAAW,WACR,OAAO,aACL,MAAM,KAChB,KAAK,GAAG,SAwBV,CAAA;AAmJD,eAAO,MAAM,kBAAkB,kBAAmB,QAAQ,KAAG,IAO5D,CAAA;AAOD,eAAO,MAAM,uBAAuB,kBAAmB,QAAQ,KAAG,OAItD,CAAA;AAEZ,eAAO,MAAM,qBAAqB,cACrB,WAAW,GAAG,IAAI,mBACZ,OAAO,KACvB,eAAe,GAAG,SAyDpB,CAAA;AAED;;;oBAGoB;AACpB,MAAM,MAAM,wBAAwB,CAAC,KAAK,IAAI,QAAQ,CAAC;IACrD,kBAAkB,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IAC9C,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;CACnC,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,GAAI,KAAK,EAAE,SAAS,qIAWrD,QAAQ,CAAC;IACV,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,EAAE,eAAe,GAAG,SAAS,CAAA;IACtC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,SAAS,CAAA;IAC7D,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAA;IACtE,eAAe,EAAE,OAAO,CAAA;IACxB,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACtD,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,WAAW,CAAA;IACtB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,gBAAgB,EAAE,CAAC,CAAC,EAClB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,KACvC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;CACtB,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAuK9C,CAAA"}
|