@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.1
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/CLAUDE.md +87 -0
- package/README.md +84 -2
- package/dist/dist-manifest.json +218 -0
- package/dist/eyes/index.js +109 -0
- package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
- package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
- package/dist/fat/pkg-node/package.json +12 -0
- package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
- package/dist/frqtl/compat/boot-node.js +34 -0
- package/dist/frqtl/compat/engine.js +127 -0
- package/dist/frqtl/compat/package-root.js +24 -0
- package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
- package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
- package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
- package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
- package/dist/frqtl/compat/renderer/Filters.js +163 -0
- package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
- package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
- package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
- package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
- package/dist/frqtl/compat/renderer/display-slot.js +96 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
- package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
- package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
- package/dist/frqtl/compat/renderer/providers/index.js +22 -0
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
- package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
- package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
- package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
- package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
- package/dist/frqtl/compat/vendor.js +196 -0
- package/dist/frqtl/node-entry.js +17 -0
- package/dist/frqtl/objects/FrqtlApp.js +2922 -0
- package/dist/frqtl/objects/ObjectFactory.js +297 -0
- package/dist/frqtl/objects/RecipeContext.js +322 -0
- package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
- package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
- package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
- package/dist/frqtl/objects/instruments/capture-host.js +453 -0
- package/dist/frqtl/objects/instruments/capture.js +711 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
- package/dist/frqtl/objects/kinds-entry.js +14 -0
- package/dist/frqtl/objects/kinds.js +190 -0
- package/dist/frqtl/objects/objects/Box.js +273 -0
- package/dist/frqtl/objects/objects/Bullet.js +155 -0
- package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
- package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
- package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +354 -0
- package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
- package/dist/frqtl/objects/objects/Tube.js +471 -0
- package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
- package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
- package/dist/frqtl/objects/objects/controlAction.js +89 -0
- package/dist/frqtl/objects/objects/controlBind.js +278 -0
- package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
- package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
- package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
- package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
- package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
- package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
- package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
- package/dist/frqtl/objects/schema/index.js +256 -0
- package/dist/frqtl/objects/schema/scopes.js +457 -0
- package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
- package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
- package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
- package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
- package/dist/frqtl/objects/setup/_worldInit.js +162 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
- package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
- package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
- package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
- package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
- package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
- package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
- package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
- package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
- package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
- package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
- package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
- package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
- package/dist/frqtl/objects/v1-replacements.js +30 -0
- package/dist/frqtl/package-core.js +23 -0
- package/dist/frqtl/parts/CameraControls.js +643 -0
- package/dist/frqtl/parts/CameraPath.js +575 -0
- package/dist/frqtl/parts/CollisionActions.js +35 -0
- package/dist/frqtl/parts/CollisionTypes.js +31 -0
- package/dist/frqtl/parts/DetectorWall.js +496 -0
- package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
- package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
- package/dist/frqtl/parts/FrqtlVision.js +818 -0
- package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
- package/dist/frqtl/parts/LargeObject.js +406 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +720 -0
- package/dist/frqtl/parts/OvenBox.js +612 -0
- package/dist/frqtl/parts/PointSource.js +763 -0
- package/dist/frqtl/parts/Rectangle.js +200 -0
- package/dist/frqtl/parts/SlitLightSource.js +234 -0
- package/dist/frqtl/parts/SlitScreen.js +628 -0
- package/dist/frqtl/parts/SphereEmitter.js +946 -0
- package/dist/frqtl/parts/Trace.js +508 -0
- package/dist/frqtl/parts/Tunnel.js +1057 -0
- package/dist/frqtl/parts/Wall.js +1211 -0
- package/dist/frqtl/parts/index.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall.js +32 -0
- package/dist/frqtl/scenes/atomic-model.js +88 -0
- package/dist/frqtl/scenes/big-bang.js +46 -0
- package/dist/frqtl/scenes/blackbody.js +81 -0
- package/dist/frqtl/scenes/circuit-sat.js +127 -0
- package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
- package/dist/frqtl/scenes/counting-wall.js +38 -0
- package/dist/frqtl/scenes/double-slit.js +31 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
- package/dist/frqtl/scenes/dse-one-slit.js +114 -0
- package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
- package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
- package/dist/frqtl/scenes/dse-two-slits.js +132 -0
- package/dist/frqtl/scenes/empty.js +14 -0
- package/dist/frqtl/scenes/laser-buildup.js +49 -0
- package/dist/frqtl/scenes/mirror-3d.js +56 -0
- package/dist/frqtl/scenes/mirror-route.js +140 -0
- package/dist/frqtl/scenes/mirror.js +38 -0
- package/dist/frqtl/scenes/omni-3d.js +27 -0
- package/dist/frqtl/scenes/omni-source.js +22 -0
- package/dist/frqtl/scenes/self-interference.js +39 -0
- package/dist/frqtl/scenes/smoking-gun.js +122 -0
- package/dist/frqtl/scenes/straight-walk.js +22 -0
- package/dist/frqtl/scenes/sun.js +39 -0
- package/dist/frqtl/scenes/two-source.js +37 -0
- package/dist/frqtl/scenes/wave.js +26 -0
- package/dist/shim/bundle/frqnb.js +112 -0
- package/dist/shim/bundle/index.js +121 -0
- package/dist/shim/bundle/indexHtml.js +107 -0
- package/dist/shim/bundle/manifest.js +95 -0
- package/dist/shim/bundle/zip.js +243 -0
- package/dist/shim/bus.js +67 -0
- package/dist/shim/controls/index.js +627 -0
- package/dist/shim/controls/registry.js +274 -0
- package/dist/shim/engine/artifact-shell.js +212 -0
- package/dist/shim/engine/boot-phases.js +44 -0
- package/dist/shim/engine/engine-stack-boot.js +59 -0
- package/dist/shim/engine/router.js +692 -0
- package/dist/shim/experiment-frame/index.js +296 -0
- package/dist/shim/expr/ast-schema.js +369 -0
- package/dist/shim/field-descriptor/index.js +326 -0
- package/dist/shim/frame-data/index.js +224 -0
- package/dist/shim/index.js +67 -0
- package/dist/shim/notebook/canvas-guard.js +82 -0
- package/dist/shim/notebook/catalog.js +35 -0
- package/dist/shim/notebook/format.js +736 -0
- package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
- package/dist/shim/notebook/kernel.js +672 -0
- package/dist/shim/notebook/knob.js +151 -0
- package/dist/shim/notebook/module-resolver.js +260 -0
- package/dist/shim/notebook/notebook-knobs.js +65 -0
- package/dist/shim/notebook/produced-bindings.js +67 -0
- package/dist/shim/notebook/realm-registry.js +121 -0
- package/dist/shim/notebook/realm-transport.js +73 -0
- package/dist/shim/notebook/setup-builder.js +510 -0
- package/dist/shim/notebook/store.js +159 -0
- package/dist/shim/notebook/vendor-importmap.js +134 -0
- package/dist/shim/notebook/view-builder.js +189 -0
- package/dist/shim/notebook/view-ctx.js +100 -0
- package/dist/shim/notebook/view-handle.js +202 -0
- package/dist/shim/notebook/warmup.js +102 -0
- package/dist/shim/package.json +4 -0
- package/dist/shim/page-params/index.js +214 -0
- package/dist/shim/project/identity.js +112 -0
- package/dist/shim/project/index.js +19 -0
- package/dist/shim/project/link.js +402 -0
- package/dist/shim/project/node-specifier-hook.mjs +35 -0
- package/dist/shim/project/register-node.mjs +17 -0
- package/dist/shim/project/specifiers.js +106 -0
- package/dist/shim/registry/copy-gate.js +110 -0
- package/dist/shim/registry/exhibits.js +455 -0
- package/dist/shim/registry/experiments.js +215 -0
- package/dist/shim/registry/index.js +19 -0
- package/dist/shim/registry/scene-source.js +157 -0
- package/dist/shim/registry/walkthrough.js +807 -0
- package/dist/shim/render/capture-envelope.js +279 -0
- package/dist/shim/render/heat-colormap.js +92 -0
- package/dist/shim/render/render-stages.js +855 -0
- package/dist/shim/render/sun-look.js +33 -0
- package/dist/shim/render-state/apply.js +262 -0
- package/dist/shim/render-state/index.js +74 -0
- package/dist/shim/rpc/index.js +207 -0
- package/dist/shim/runtime/index.js +1023 -0
- package/dist/shim/storage/boot.js +69 -0
- package/dist/shim/storage/index.js +169 -0
- package/dist/shim/storage/local-storage-provider.js +230 -0
- package/dist/shim/storage/make-namespace-store.js +132 -0
- package/dist/shim/storage/modules.js +249 -0
- package/dist/shim/storage/projects.js +150 -0
- package/llms.txt +12 -0
- package/package.json +62 -4
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* lib/shim/notebook/store.js — NotebookDocumentStore.
|
|
5
|
+
*
|
|
6
|
+
* Thin CRUD helpers that persist a serialized <frqtl-notebook> document on top
|
|
7
|
+
* of any StorageProvider (lib/shim/storage/index.js) using the "notebooks"
|
|
8
|
+
* namespace. Mirrors the shape of lib/shim/storage/modules.js and
|
|
9
|
+
* lib/shim/storage/projects.js — ONE persistent-store pattern across Studio
|
|
10
|
+
* projects, Studio modules, and Notebooks.
|
|
11
|
+
*
|
|
12
|
+
* ── Why this lives in lib/shim ──────────────────────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* The serialized text is owned by format.serialize (lib/shim/notebook/format.js)
|
|
15
|
+
* and the storage seam is owned by StorageProvider (lib/shim/storage/index.js).
|
|
16
|
+
* Both are host-neutral shim contracts. The notebook-pane (lib/components)
|
|
17
|
+
* consumes this module and does not re-implement the store helpers. Arrow is legal: lib/shim → lib/shim.
|
|
18
|
+
*
|
|
19
|
+
* ── Notebook envelope ────────────────────────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* {
|
|
22
|
+
* id: string — unique identifier (newNotebookId())
|
|
23
|
+
* title: string — human-readable title (mirrors notebook.title)
|
|
24
|
+
* html: string — the serialized <frqtl-notebook>…</frqtl-notebook>
|
|
25
|
+
* text produced by format.serialize(). The value
|
|
26
|
+
* the pane writes is the CANONICAL writer's output —
|
|
27
|
+
* never hand-assembled markup.
|
|
28
|
+
* createdAt: string — ISO-8601 creation timestamp
|
|
29
|
+
* updatedAt: string — ISO-8601 last-updated timestamp
|
|
30
|
+
* schemaVersion: number — integer version; currently 1
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* ── Namespace ──────────────────────────────────────────────────────────────
|
|
34
|
+
*
|
|
35
|
+
* All helpers use the "notebooks" namespace. Keys are notebook ids.
|
|
36
|
+
*
|
|
37
|
+
* ── The ONE persistence contract (§2.4 of the spec) ────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* The web IDE drives:
|
|
40
|
+
* provider.put('notebooks', id, { html: format.serialize(nb), … })
|
|
41
|
+
*
|
|
42
|
+
* ── Usage ──────────────────────────────────────────────────────────────────
|
|
43
|
+
*
|
|
44
|
+
* import { newNotebookId, saveNotebook, loadNotebook,
|
|
45
|
+
* listNotebooks, deleteNotebook } from 'lib/shim/notebook/store.js';
|
|
46
|
+
* import { getProvider } from 'lib/shim/storage';
|
|
47
|
+
*
|
|
48
|
+
* const provider = getProvider();
|
|
49
|
+
*
|
|
50
|
+
* const id = newNotebookId();
|
|
51
|
+
* const html = serialize(notebook); // format.serialize — sole writer
|
|
52
|
+
* saveNotebook(provider, { id, title: 'My notebook', html,
|
|
53
|
+
* createdAt: now, updatedAt: now, schemaVersion: 1 });
|
|
54
|
+
*
|
|
55
|
+
* const record = loadNotebook(provider, id); // { id, title, html, … } | null
|
|
56
|
+
* const all = listNotebooks(provider); // [{ id, title, updatedAt }]
|
|
57
|
+
* deleteNotebook(provider, id);
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
'use strict';
|
|
61
|
+
|
|
62
|
+
import { makeNamespaceStore } from '../storage/make-namespace-store.js';
|
|
63
|
+
|
|
64
|
+
/** @type {string} The storage namespace for all notebook records. */
|
|
65
|
+
export var NOTEBOOKS_NAMESPACE = 'notebooks';
|
|
66
|
+
|
|
67
|
+
/** @type {number} Current schema version. Increment on breaking envelope changes. */
|
|
68
|
+
export var NOTEBOOK_SCHEMA_VERSION = 1;
|
|
69
|
+
|
|
70
|
+
// ── Envelope validation ──────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
function _validateRecord(record) {
|
|
73
|
+
if (!record || typeof record !== 'object') {
|
|
74
|
+
throw new Error('[notebook/store] record must be a non-null object');
|
|
75
|
+
}
|
|
76
|
+
if (typeof record.id !== 'string' || record.id.length === 0) {
|
|
77
|
+
throw new Error('[notebook/store] record.id must be a non-empty string');
|
|
78
|
+
}
|
|
79
|
+
if (typeof record.title !== 'string') {
|
|
80
|
+
throw new Error('[notebook/store] record.title must be a string');
|
|
81
|
+
}
|
|
82
|
+
if (typeof record.html !== 'string' || record.html.length === 0) {
|
|
83
|
+
throw new Error('[notebook/store] record.html must be a non-empty string; use format.serialize() to produce it');
|
|
84
|
+
}
|
|
85
|
+
if (typeof record.schemaVersion !== 'number') {
|
|
86
|
+
throw new Error('[notebook/store] record.schemaVersion must be a number');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Summary projection ────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
function _summaryFields(record) {
|
|
93
|
+
return {
|
|
94
|
+
id: record.id,
|
|
95
|
+
title: record.title || '',
|
|
96
|
+
updatedAt: record.updatedAt || '',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Store instance ────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
var _store = makeNamespaceStore({
|
|
103
|
+
namespace: NOTEBOOKS_NAMESPACE,
|
|
104
|
+
schemaVersion: NOTEBOOK_SCHEMA_VERSION,
|
|
105
|
+
idPrefix: 'nb_',
|
|
106
|
+
validate: _validateRecord,
|
|
107
|
+
summaryFields: _summaryFields,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ── Public API (names and signatures unchanged) ───────────────────────────────
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generate a new unique notebook id.
|
|
114
|
+
* @returns {string} e.g. "nb_lxyz0abc1234"
|
|
115
|
+
*/
|
|
116
|
+
export var newNotebookId = _store.newId;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Save (create or update) a notebook record.
|
|
120
|
+
*
|
|
121
|
+
* The `html` field MUST be the output of `format.serialize()` — the single
|
|
122
|
+
* canonical writer. This function does NOT call serialize itself; callers are
|
|
123
|
+
* responsible for producing the text first.
|
|
124
|
+
*
|
|
125
|
+
* `updatedAt` is NOT automatically set — callers must set it before passing.
|
|
126
|
+
*
|
|
127
|
+
* @param {import('../storage/index.js').StorageProvider} provider
|
|
128
|
+
* @param {object} record
|
|
129
|
+
* @throws {Error} on validation failure or provider write failure.
|
|
130
|
+
*/
|
|
131
|
+
export var saveNotebook = _store.save;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Load a notebook record by id.
|
|
135
|
+
* @param {import('../storage/index.js').StorageProvider} provider
|
|
136
|
+
* @param {string} id
|
|
137
|
+
* @returns {object|null} The Notebook envelope, or null if not found.
|
|
138
|
+
*/
|
|
139
|
+
export var loadNotebook = _store.load;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* List all notebooks, returning lightweight summary objects.
|
|
143
|
+
*
|
|
144
|
+
* Returns an array of { id, title, updatedAt } objects suitable for a notebook
|
|
145
|
+
* picker. Records with a missing or malformed id are silently skipped.
|
|
146
|
+
* Sorted by updatedAt descending.
|
|
147
|
+
*
|
|
148
|
+
* @param {import('../storage/index.js').StorageProvider} provider
|
|
149
|
+
* @returns {Array<{id: string, title: string, updatedAt: string}>}
|
|
150
|
+
*/
|
|
151
|
+
export var listNotebooks = _store.list;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Delete a notebook record by id.
|
|
155
|
+
* No-op if the record does not exist.
|
|
156
|
+
* @param {import('../storage/index.js').StorageProvider} provider
|
|
157
|
+
* @param {string} id
|
|
158
|
+
*/
|
|
159
|
+
export var deleteNotebook = _store['delete'];
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Copyright 2023-2026 by Essam Abadir
|
|
2
|
+
// lib/shim/notebook/vendor-importmap.js — the ONE pure import-map generator.
|
|
3
|
+
//
|
|
4
|
+
// WS-NOTEBOOK-VENDOR Phase 2a (§2a.3). Maps the committed vendor manifest
|
|
5
|
+
// (lib/frqtl/assets/vendor/manifest.json) → a browser `<script type="importmap">`
|
|
6
|
+
// payload `{ imports: { <spec>: <url> } }`, so a realm cell's builder/view module
|
|
7
|
+
// can `import * as d3 from 'd3'` and the realm's native module loader resolves the
|
|
8
|
+
// bare specifier to a PLACED /vendor artifact.
|
|
9
|
+
//
|
|
10
|
+
// PURE: no DOM, no Runtime, no engine import. Node-testable.
|
|
11
|
+
//
|
|
12
|
+
// PATH AUTHORITY (the canonical-stack DRY rule): `vendorBase` is supplied by the
|
|
13
|
+
// caller from `router.vendorBase` (the single path authority — DEV_CORE_PROFILE /
|
|
14
|
+
// DIST_PROFILE `__vendorBase`). This module NEVER hardcodes a `/vendor` literal; it
|
|
15
|
+
// only joins the caller-provided base with each manifest entry.
|
|
16
|
+
//
|
|
17
|
+
// FAIL-LOUD (the slit-objects-hardcoded-list bug class): a malformed manifest (no
|
|
18
|
+
// `.libs`, or a lib without `.entry`) THROWS — never silently drops a lib.
|
|
19
|
+
|
|
20
|
+
import { buildFrqtlSetupsImportMap } from './frqtl-setups-importmap.js';
|
|
21
|
+
import { importMapFor } from '../project/specifiers.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {object} VendorManifest
|
|
25
|
+
* @property {number} [version]
|
|
26
|
+
* @property {Object<string, { entry: string }>} libs
|
|
27
|
+
* bare-specifier → { entry: path RELATIVE TO THE VENDOR ROOT }.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build a browser import-map from the vendor manifest.
|
|
32
|
+
*
|
|
33
|
+
* @param {VendorManifest} manifest parsed manifest.json (`{ libs: { spec: { entry } } }`).
|
|
34
|
+
* @param {string} vendorBase router.vendorBase, e.g. `'/vendor'` (no trailing slash needed).
|
|
35
|
+
* @returns {{ imports: Object<string, string> }}
|
|
36
|
+
* `{ imports: { <spec>: <vendorBase>/<entry> } }`.
|
|
37
|
+
* @throws {Error} FAIL-LOUD on a malformed manifest or a missing vendorBase.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* buildImportMap({ libs: { d3: { entry: 'd3/dist/d3.min.js' } } }, '/vendor')
|
|
41
|
+
* // → { imports: { d3: '/vendor/d3/dist/d3.min.js' } }
|
|
42
|
+
*/
|
|
43
|
+
export function buildImportMap(manifest, vendorBase) {
|
|
44
|
+
if (!manifest || typeof manifest !== 'object' || !manifest.libs || typeof manifest.libs !== 'object') {
|
|
45
|
+
throw new Error('[vendor-importmap] malformed manifest: missing `.libs` object (FAIL-LOUD).');
|
|
46
|
+
}
|
|
47
|
+
if (typeof vendorBase !== 'string' || vendorBase.length === 0) {
|
|
48
|
+
throw new Error('[vendor-importmap] vendorBase is required (read it from router.vendorBase) (FAIL-LOUD).');
|
|
49
|
+
}
|
|
50
|
+
// Normalise the base: strip a single trailing slash so the join is unambiguous.
|
|
51
|
+
var base = vendorBase.replace(/\/+$/, '');
|
|
52
|
+
var imports = {};
|
|
53
|
+
var specs = Object.keys(manifest.libs);
|
|
54
|
+
for (var i = 0; i < specs.length; i++) {
|
|
55
|
+
var spec = specs[i];
|
|
56
|
+
var rec = manifest.libs[spec];
|
|
57
|
+
if (!rec || typeof rec.entry !== 'string' || rec.entry.length === 0) {
|
|
58
|
+
throw new Error('[vendor-importmap] manifest lib "' + spec + '" has no `.entry` (FAIL-LOUD — a lib is never silently dropped).');
|
|
59
|
+
}
|
|
60
|
+
// entry is relative to the vendor root; strip any leading slash so the join is clean.
|
|
61
|
+
var entry = rec.entry.replace(/^\/+/, '');
|
|
62
|
+
imports[spec] = base + '/' + entry;
|
|
63
|
+
}
|
|
64
|
+
return { imports: imports };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The lib-identity root, as the document sees it, for a router: where `frqtl/parts/index.js` is
|
|
69
|
+
* placed, minus that path (every profile places the game layer at its lib/-relative path).
|
|
70
|
+
*
|
|
71
|
+
* @param {{resolve:Function}} router
|
|
72
|
+
* @returns {string} ending in '/'
|
|
73
|
+
* @throws {Error} FAIL-LOUD when the profile does not place the parts at their lib/-relative path.
|
|
74
|
+
*/
|
|
75
|
+
export function libRootOf(router) {
|
|
76
|
+
var parts = router.resolve('parts/index.js');
|
|
77
|
+
var tail = 'frqtl/parts/index.js';
|
|
78
|
+
if (parts.slice(-tail.length) !== tail) {
|
|
79
|
+
throw new Error('[vendor-importmap] the router places parts/index.js at "' + parts + '", not at its ' +
|
|
80
|
+
'lib/-relative placement (…/' + tail + ') — the document\'s lib root cannot be located (FAIL-LOUD).');
|
|
81
|
+
}
|
|
82
|
+
var root = parts.slice(0, -tail.length);
|
|
83
|
+
return root === '' ? './' : root;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The REALM import map — the ONE merged `{imports}` every host injects into a document:
|
|
88
|
+
* the vendor libs (`buildImportMap(manifest, router.vendorBase)`), the canonical
|
|
89
|
+
* setup-factory bare specifiers (`buildFrqtlSetupsImportMap(router)`) and the sketch's
|
|
90
|
+
* `@descix/frqtl-web/...` specifiers (lib/shim/project/specifiers.js importMapFor, at this
|
|
91
|
+
* router's lib root). A document may carry only ONE import map, so the fragments always travel
|
|
92
|
+
* together, resolved through the SAME router (never a vendor base from one profile and module
|
|
93
|
+
* URLs from another).
|
|
94
|
+
*
|
|
95
|
+
* @param {VendorManifest} manifest
|
|
96
|
+
* @param {{resolve:Function, vendorBase:string}} router an engineRouter() instance.
|
|
97
|
+
* @returns {{ imports: Object<string, string> }}
|
|
98
|
+
* @throws {Error} FAIL-LOUD on a malformed manifest or a missing router.
|
|
99
|
+
*/
|
|
100
|
+
export function buildRealmImportMap(manifest, router) {
|
|
101
|
+
if (!router || typeof router.resolve !== 'function') {
|
|
102
|
+
throw new Error('[vendor-importmap] buildRealmImportMap(manifest, router) requires an engineRouter() instance (FAIL-LOUD).');
|
|
103
|
+
}
|
|
104
|
+
var vendorMap = buildImportMap(manifest, router.vendorBase);
|
|
105
|
+
var setupsMap = buildFrqtlSetupsImportMap(router);
|
|
106
|
+
var webMap = importMapFor(libRootOf(router));
|
|
107
|
+
return { imports: Object.assign({}, vendorMap.imports, setupsMap.imports, webMap.imports) };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Splice the realm import map into an HTML document as
|
|
112
|
+
* `<script type="importmap" data-frqtl-vendor-importmap>`, immediately BEFORE the first
|
|
113
|
+
* `<script type="module"` (a map added after module loading starts is ignored), else
|
|
114
|
+
* before `</head>`, else prepended. Idempotent: a document already carrying the
|
|
115
|
+
* `data-frqtl-vendor-importmap` tag is returned unchanged.
|
|
116
|
+
*
|
|
117
|
+
* The ONE splice for every host: the dev serve-time splice
|
|
118
|
+
* (lib/server/realm-html.js) and the static build-time bake (apps/ide/vite.config.js).
|
|
119
|
+
*
|
|
120
|
+
* @param {string} html
|
|
121
|
+
* @param {VendorManifest} manifest
|
|
122
|
+
* @param {{resolve:Function, vendorBase:string}} router the host's engineRouter() instance.
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
export function spliceRealmImportMap(html, manifest, router) {
|
|
126
|
+
if (/data-frqtl-vendor-importmap/.test(html)) { return html; } // idempotent
|
|
127
|
+
var body = JSON.stringify(buildRealmImportMap(manifest, router));
|
|
128
|
+
var tag = '<script type="importmap" data-frqtl-vendor-importmap>' + body + '</script>\n ';
|
|
129
|
+
var moduleIdx = html.search(/<script\s+type=["']module["']/i);
|
|
130
|
+
if (moduleIdx !== -1) { return html.slice(0, moduleIdx) + tag + html.slice(moduleIdx); }
|
|
131
|
+
var headIdx = html.search(/<\/head>/i);
|
|
132
|
+
if (headIdx !== -1) { return html.slice(0, headIdx) + ' ' + tag + html.slice(headIdx); }
|
|
133
|
+
return tag + html;
|
|
134
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* view-builder — the SOLE authority that synthesizes the canonical VIEW-ENTRY source
|
|
5
|
+
* from a `view` cell's declarative configuration (WS-NOTEBOOK-VENDOR-VIEW Phase 2b).
|
|
6
|
+
*
|
|
7
|
+
* Named ESM export. PURE module — NO DOM / engine / Runtime / Bus import. It is
|
|
8
|
+
* host-neutral and node-testable: hosts (the notebook-pane in-app) import this one
|
|
9
|
+
* helper and NONE inlines the template (the §router single-path-authority rule applied to the view-entry
|
|
10
|
+
* template). It is the SIBLING of `setup-builder.js` for the generic `view` cell,
|
|
11
|
+
* and it REUSES setup-builder's export-resolution + module-resolution helpers (DRY —
|
|
12
|
+
* one single-export-else-FAIL-LOUD authority, one in-memory-module-set resolver).
|
|
13
|
+
*
|
|
14
|
+
* ── What it does ─────────────────────────────────────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* Given a view declaration — `module="<module-name>#<exportName>"` — and the
|
|
17
|
+
* notebook's user-code module SET (`{ name → source }`), it returns the canonical
|
|
18
|
+
* VIEW-ENTRY SOURCE STRING + the realm-internal output name. The entry imports the
|
|
19
|
+
* user render module by name and RETURNS its render export under the realm-internal
|
|
20
|
+
* output name:
|
|
21
|
+
*
|
|
22
|
+
* import { <export> } from '<module>';
|
|
23
|
+
* return { <outName>: <export> };
|
|
24
|
+
*
|
|
25
|
+
* `Runtime.runCell` runs THIS entry IN THE REALM (the SAME executor + module-compile
|
|
26
|
+
* path the declared canvas uses). The entry's `import { <export> } from '<module>'`
|
|
27
|
+
* resolves against the notebook's in-memory module closure; the USER module's OWN
|
|
28
|
+
* `import * as d3 from 'd3'` bare-specifier imports resolve via the realm's injected
|
|
29
|
+
* 2a import-map (bare specifiers pass through the blob-module rewrite verbatim — see
|
|
30
|
+
* module-resolver `rewriteRelativeToSiblingBlobs`, "bare → host loader"). The render
|
|
31
|
+
* export is returned as a LIVE realm-internal ref (NOT a kernel binding, NOT crossed
|
|
32
|
+
* over the data plane) — exactly as the declared-canvas synthesis returns a live
|
|
33
|
+
* `runner` ref. The HOST then invokes `renderExport(ctx)` with the view `ctx`
|
|
34
|
+
* (mount + frozen handle + binding seam) it assembles realm-side (see view-ctx.js).
|
|
35
|
+
*
|
|
36
|
+
* ── single-export resolution (FAIL-LOUD, never guess) ─────────────────────────
|
|
37
|
+
*
|
|
38
|
+
* `#export` may be omitted ONLY when the named module has EXACTLY ONE export. Zero or
|
|
39
|
+
* multiple exports → FAIL-LOUD with a precise message (never a guess). This reuses
|
|
40
|
+
* setup-builder's `moduleExportNames` + the same resolution discipline (C5 mirror).
|
|
41
|
+
*
|
|
42
|
+
* ── the import resolves against the in-memory module closure ──────────────────
|
|
43
|
+
*
|
|
44
|
+
* The emitted `import` uses the DECLARED module name (e.g. `'./views/d3-bars.js'`),
|
|
45
|
+
* resolved against the notebook's `moduleSet()` via the existing in-memory blob
|
|
46
|
+
* closure. An unknown/missing module → FAIL-LOUD HERE so the caller never ships an
|
|
47
|
+
* entry that 404s the relative import downstream.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
'use strict';
|
|
51
|
+
|
|
52
|
+
import { canonKey } from './module-resolver.js';
|
|
53
|
+
import { moduleExportNames } from './setup-builder.js';
|
|
54
|
+
|
|
55
|
+
/* ── parse "<module-name>#<exportName>" ──────────────────────────────────────── */
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Split the view module declaration into `{ moduleName, exportName }`. The
|
|
59
|
+
* `exportName` is the substring after the FIRST '#'; absent → ''. The `moduleName`
|
|
60
|
+
* is verbatim (kept with its relative './…' prefix so the emitted import matches the
|
|
61
|
+
* module cell name the closure is keyed by).
|
|
62
|
+
*
|
|
63
|
+
* @param {string} moduleDecl — `"<module-name>#<exportName>"` (exportName optional).
|
|
64
|
+
* @returns {{ moduleName: string, exportName: string }}
|
|
65
|
+
* @throws {Error} FAIL-LOUD on an empty declaration or a declaration with no module name.
|
|
66
|
+
*/
|
|
67
|
+
export function parseViewDeclaration(moduleDecl) {
|
|
68
|
+
if (typeof moduleDecl !== 'string' || !moduleDecl.trim()) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'[view-builder] a `view` cell requires a `module="<module-name>#<exportName>"` ' +
|
|
71
|
+
'attribute (got: ' + JSON.stringify(moduleDecl) + '). (FAIL-LOUD.)'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
var s = moduleDecl.trim();
|
|
75
|
+
var hash = s.indexOf('#');
|
|
76
|
+
var moduleName = (hash === -1) ? s : s.slice(0, hash);
|
|
77
|
+
var exportName = (hash === -1) ? '' : s.slice(hash + 1);
|
|
78
|
+
moduleName = moduleName.trim();
|
|
79
|
+
exportName = exportName.trim();
|
|
80
|
+
if (!moduleName) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
'[view-builder] `module` declaration "' + moduleDecl + '" has no module name ' +
|
|
83
|
+
'before the "#". Expected "<module-name>#<exportName>". (FAIL-LOUD.)'
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return { moduleName: moduleName, exportName: exportName };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* ── resolve the module source from the module set ───────────────────────────── */
|
|
90
|
+
|
|
91
|
+
// Look the declared module up in the module set, matching by canonical key so
|
|
92
|
+
// './views/x.js' and 'views/x.js' resolve the same. FAIL-LOUD on absence.
|
|
93
|
+
function _resolveModuleSource(moduleName, modules) {
|
|
94
|
+
var set = modules || {};
|
|
95
|
+
if (Object.prototype.hasOwnProperty.call(set, moduleName)) { return set[moduleName]; }
|
|
96
|
+
var wantKey = canonKey(moduleName);
|
|
97
|
+
var keys = Object.keys(set);
|
|
98
|
+
for (var i = 0; i < keys.length; i++) {
|
|
99
|
+
if (canonKey(keys[i]) === wantKey) { return set[keys[i]]; }
|
|
100
|
+
}
|
|
101
|
+
throw new Error(
|
|
102
|
+
'[view-builder] declared view module "' + moduleName + '" is not a ' +
|
|
103
|
+
'`type="module"` cell in this notebook (known modules: [' + keys.join(', ') +
|
|
104
|
+
']). (FAIL-LOUD — the `view` cell references an editable in-notebook module ' +
|
|
105
|
+
'cell, never an opaque by-name load.)'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ── resolve the export name (single-export-else-FAIL-LOUD) ───────────────────── */
|
|
110
|
+
|
|
111
|
+
// Return the export name to import: the explicit `exportName` when given (verified
|
|
112
|
+
// present); otherwise the module's SOLE export. Zero or multiple exports with no
|
|
113
|
+
// explicit `#export` → FAIL-LOUD (never guess). Mirrors setup-builder's C5 rule.
|
|
114
|
+
function _resolveExportName(moduleName, exportName, source) {
|
|
115
|
+
var exports = moduleExportNames(source);
|
|
116
|
+
if (exportName) {
|
|
117
|
+
if (exports.indexOf(exportName) === -1) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'[view-builder] declared view export "' + exportName + '" is not exported ' +
|
|
120
|
+
'by module "' + moduleName + '" (its exports: [' + exports.join(', ') +
|
|
121
|
+
']). (FAIL-LOUD.)'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return exportName;
|
|
125
|
+
}
|
|
126
|
+
if (exports.length === 1) { return exports[0]; }
|
|
127
|
+
if (exports.length === 0) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
'[view-builder] view module "' + moduleName + '" has NO named export — ' +
|
|
130
|
+
'declare `module="' + moduleName + '#<exportName>"` once the module exports a ' +
|
|
131
|
+
'render function. (FAIL-LOUD — never guess.)'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
throw new Error(
|
|
135
|
+
'[view-builder] view module "' + moduleName + '" has MULTIPLE exports [' +
|
|
136
|
+
exports.join(', ') + '] — the `#export` part of `module` is required to pick one ' +
|
|
137
|
+
'(e.g. module="' + moduleName + '#' + exports[0] + '"). (FAIL-LOUD — never guess.)'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ── the synthesis (SOLE authority) ──────────────────────────────────────────── */
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* synthesizeViewEntrySource — turn a `view` cell declaration into the canonical
|
|
145
|
+
* VIEW-ENTRY source string + the realm-internal output name.
|
|
146
|
+
*
|
|
147
|
+
* @param {object} decl
|
|
148
|
+
* @param {string} decl.module — `"<module-name>#<exportName>"` (exportName optional).
|
|
149
|
+
* @param {Object<string,string>} decl.modules — the notebook module set `{ name → source }`,
|
|
150
|
+
* used to resolve a single export + verify the module exists.
|
|
151
|
+
* @param {string} [decl.outName] — realm-internal output name; default `'_view'`.
|
|
152
|
+
* @returns {{ entryCode: string, outName: string, moduleName: string, exportName: string }}
|
|
153
|
+
* @throws {Error} FAIL-LOUD on a missing module / unknown export / zero-or-multiple
|
|
154
|
+
* exports with no `#export`.
|
|
155
|
+
*/
|
|
156
|
+
export function synthesizeViewEntrySource(decl) {
|
|
157
|
+
decl = decl || {};
|
|
158
|
+
var parsed = parseViewDeclaration(decl.module);
|
|
159
|
+
var source = _resolveModuleSource(parsed.moduleName, decl.modules);
|
|
160
|
+
var exportName = _resolveExportName(parsed.moduleName, parsed.exportName, source);
|
|
161
|
+
|
|
162
|
+
// The realm-internal output name is emitted as an UNQUOTED object key in the
|
|
163
|
+
// synthesized `return { <outName>: <export> }`. Callers pass `cell.id + '_view'`,
|
|
164
|
+
// and notebook cell ids contain hyphens (e.g. `c-d3-view`) — a bare hyphen in a
|
|
165
|
+
// key is a SyntaxError. Sanitize to a valid JS identifier (it is realm-internal,
|
|
166
|
+
// never user-referenced, so the exact spelling does not matter — only that it is a
|
|
167
|
+
// legal key and that the value we RETURN as `outName` matches the key the template
|
|
168
|
+
// emits, so the caller's `result.outputs[outName]` lookup hits). Mirrors
|
|
169
|
+
// setup-builder's outName sanitization (the hyphen bug class).
|
|
170
|
+
var rawOutName = decl.outName || '_view';
|
|
171
|
+
var outName = String(rawOutName).replace(/[^A-Za-z0-9_$]/g, '_');
|
|
172
|
+
if (/^[0-9]/.test(outName)) { outName = '_' + outName; }
|
|
173
|
+
|
|
174
|
+
// The canonical VIEW-ENTRY template. The synthesized entry:
|
|
175
|
+
// - imports the editable in-notebook view module's render export by name,
|
|
176
|
+
// - returns it as a realm-internal LIVE ref (the host invokes it with the ctx).
|
|
177
|
+
// The USER module's OWN bare-specifier imports (e.g. `import * as d3 from 'd3'`)
|
|
178
|
+
// resolve via the realm's injected 2a import-map — they are not rewritten here.
|
|
179
|
+
var entryCode =
|
|
180
|
+
"import { " + exportName + " } from '" + parsed.moduleName + "';\n" +
|
|
181
|
+
"return { " + outName + ": " + exportName + " };\n";
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
entryCode: entryCode,
|
|
185
|
+
outName: outName,
|
|
186
|
+
moduleName: parsed.moduleName,
|
|
187
|
+
exportName: exportName
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* view-ctx — the SOLE authority that assembles the `view` cell CONTEXT object handed
|
|
5
|
+
* to a user render module's export (WS-NOTEBOOK-VENDOR-VIEW Phase 2b).
|
|
6
|
+
*
|
|
7
|
+
* Named ESM export. PURE module — NO DOM / engine / Runtime / Bus import. It is a
|
|
8
|
+
* THIN convenience WRAPPER over the EXISTING frozen ViewHandle + the realm→kernel
|
|
9
|
+
* binding seam; it introduces NO new handle shape (the design's hard constraint). It
|
|
10
|
+
* is host-neutral + node-testable: a test passes a FAKE handle + a plain object as
|
|
11
|
+
* the `mount` + fake binding callbacks and asserts the ctx verbs delegate correctly
|
|
12
|
+
* and that `commit` enforces serializability (VETO-CLASS #1) — WITHOUT a DOM.
|
|
13
|
+
*
|
|
14
|
+
* The user render function is invoked as `render(ctx)` where `ctx` is:
|
|
15
|
+
*
|
|
16
|
+
* {
|
|
17
|
+
* mount, // DOM element inside the realm host (the view's render target)
|
|
18
|
+
* handle, // the frozen ViewHandle for THIS realm (on/call/start/stop/step/dispose)
|
|
19
|
+
* on, // convenience = handle.on (SAME-CELL local tick stream)
|
|
20
|
+
* call, // convenience = handle.call
|
|
21
|
+
* getBinding, // (name) → the kernel binding value for an `in` name (data IN)
|
|
22
|
+
* commit, // (name, value) → commit a `produces` value (data OUT, serializable-asserted)
|
|
23
|
+
* imports, // optional: pre-resolved blessed modules (e.g. { makeChartEnv } for liveChart)
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* `mount`/`handle`/`on`/`call` are LIVE refs (the same realm trust zone the canvas
|
|
27
|
+
* builder runs in). `getBinding`/`commit` are the kernel binding seam: `getBinding`
|
|
28
|
+
* reads from a host-supplied resolver (the host pre-resolves `in` bindings before
|
|
29
|
+
* invoking render); `commit` routes a serializable value through a host-supplied
|
|
30
|
+
* commit callback (in-app: kernel.commitViewOutputs),
|
|
31
|
+
* asserting serializability HERE (FAIL-LOUD on a non-serializable commit — the same
|
|
32
|
+
* gate the frozen handle's data plane enforces).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
'use strict';
|
|
36
|
+
|
|
37
|
+
import { assertSerializable } from './view-handle.js';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* makeViewCtx(opts) → ctx
|
|
41
|
+
*
|
|
42
|
+
* @param {object} opts
|
|
43
|
+
* @param {*} opts.mount — the view's DOM render target (a live element; opaque here).
|
|
44
|
+
* @param {object} opts.handle — the frozen ViewHandle for the booted realm (REQUIRED;
|
|
45
|
+
* must expose `on` + `call`). The ctx NEVER reshapes it — it is passed through and
|
|
46
|
+
* its `on`/`call` are surfaced as convenience refs.
|
|
47
|
+
* @param {(name: string) => *} [opts.getBinding] — resolve an `in` binding value by
|
|
48
|
+
* name. Default: a resolver over `opts.bindings` (a plain `{ name → value }` map);
|
|
49
|
+
* absent name → `undefined` (the host validated `in` presence before invoking).
|
|
50
|
+
* @param {Object<string,*>} [opts.bindings] — pre-resolved `in` bindings (used by the
|
|
51
|
+
* default getBinding when no explicit resolver is supplied).
|
|
52
|
+
* @param {(name: string, value: *) => void} [opts.commit] — host commit callback
|
|
53
|
+
* (in-app: kernel.commitViewOutputs). When absent,
|
|
54
|
+
* `commit` FAILS LOUD (a view that declares `produces` requires a commit sink).
|
|
55
|
+
* @param {Object<string,*>} [opts.imports] — pre-resolved blessed modules.
|
|
56
|
+
* @returns {object} the view ctx.
|
|
57
|
+
* @throws {Error} FAIL-LOUD if `handle` is missing `on`/`call`.
|
|
58
|
+
*/
|
|
59
|
+
export function makeViewCtx(opts) {
|
|
60
|
+
opts = opts || {};
|
|
61
|
+
var mount = opts.mount;
|
|
62
|
+
var handle = opts.handle;
|
|
63
|
+
if (!handle || typeof handle.on !== 'function' || typeof handle.call !== 'function') {
|
|
64
|
+
throw new Error('[view-ctx] requires a frozen ViewHandle with on()/call(). (FAIL-LOUD.)');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var bindings = opts.bindings || {};
|
|
68
|
+
var getBinding = (typeof opts.getBinding === 'function')
|
|
69
|
+
? opts.getBinding
|
|
70
|
+
: function (name) {
|
|
71
|
+
return Object.prototype.hasOwnProperty.call(bindings, name) ? bindings[name] : undefined;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
var commitSink = (typeof opts.commit === 'function') ? opts.commit : null;
|
|
75
|
+
function commit(name, value) {
|
|
76
|
+
if (typeof name !== 'string' || !name) {
|
|
77
|
+
throw new Error('[view-ctx] commit: name must be a non-empty string. (FAIL-LOUD.)');
|
|
78
|
+
}
|
|
79
|
+
if (!commitSink) {
|
|
80
|
+
throw new Error('[view-ctx] commit("' + name + '"): no commit sink wired for this ' +
|
|
81
|
+
'view (declare a `produces` binding + supply a host commit callback). (FAIL-LOUD.)');
|
|
82
|
+
}
|
|
83
|
+
// VETO-CLASS #1: a committed value MUST be structured-clone-serializable (it
|
|
84
|
+
// crosses into the kernel binding store, which a marshalling host may carry).
|
|
85
|
+
assertSerializable(value, 'view commit("' + name + '") value');
|
|
86
|
+
commitSink(name, value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
mount: mount,
|
|
91
|
+
handle: handle,
|
|
92
|
+
// convenience refs over the frozen handle — bind so a destructured `on`/`call`
|
|
93
|
+
// keeps the handle as `this` is irrelevant (the handle methods are closures).
|
|
94
|
+
on: function (event, cb) { return handle.on(event, cb); },
|
|
95
|
+
call: function (fn, args) { return handle.call(fn, args); },
|
|
96
|
+
getBinding: getBinding,
|
|
97
|
+
commit: commit,
|
|
98
|
+
imports: opts.imports || {}
|
|
99
|
+
};
|
|
100
|
+
}
|