@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.2
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/LICENSE.md +664 -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 +2499 -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 +84 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +99 -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 +258 -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 +2470 -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 +2920 -0
- package/dist/frqtl/objects/ObjectFactory.js +296 -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 +710 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +984 -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 +411 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +351 -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 +309 -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 +820 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +261 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +298 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +468 -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 +250 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +326 -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 +277 -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 +1258 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +240 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +177 -0
- package/dist/frqtl/objects/v1-replacements.js +29 -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 +34 -0
- package/dist/frqtl/parts/CollisionTypes.js +30 -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 +489 -0
- package/dist/frqtl/parts/LargeObject.js +405 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +717 -0
- package/dist/frqtl/parts/OvenBox.js +611 -0
- package/dist/frqtl/parts/PointSource.js +762 -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 +243 -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 +211 -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 +690 -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 +325 -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 +671 -0
- package/dist/shim/notebook/knob.js +150 -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 +507 -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 +213 -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 +804 -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 +63 -4
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* link.js — THE link site: a project (one or more ES module files held as text, or a served folder)
|
|
5
|
+
* becomes imported modules, and its entry's default export is the scene.
|
|
6
|
+
*
|
|
7
|
+
* linkProject(project) → Promise<{ buildExperiment, files }> files: { path: Uint8Array } — the bytes linked
|
|
8
|
+
* project = { files: { 'sketch.js': text, 'parts/X.js': text, … }, entry, mint, bare? }
|
|
9
|
+
* | { url: '<served folder>/', entry, mint?, bare? }
|
|
10
|
+
* url absolute, or relative to the document's base URL in a page ('./'); ends in "/"
|
|
11
|
+
* entry REQUIRED — the root-relative path of the file whose default export is
|
|
12
|
+
* `function buildExperiment(exp)`
|
|
13
|
+
* mint REQUIRED — the host's (text, path) → { url, release? }: `blobMint` in a page, `dataMint` in
|
|
14
|
+
* node. Hosts differ ONLY here. A served folder is minted too: each file is fetched ONCE, as
|
|
15
|
+
* bytes; those bytes are the identity, and the same bytes, decoded once (UTF-8, fatal), are
|
|
16
|
+
* the text that runs. Nothing is fetched a second time.
|
|
17
|
+
* bare (spec) → URL for a `@descix/frqtl-web/...` specifier; default: the specifier's
|
|
18
|
+
* lib-identity path (specifiers.js libPathOf) under THIS copy's package root
|
|
19
|
+
* (compat/package-root.js), so a project links against the very modules its boot loaded.
|
|
20
|
+
* A host whose modules are not served one file per URL (frqtl-web.min.js) passes its own.
|
|
21
|
+
* resolves { buildExperiment, files } — files is the project's BYTES by path (fetched, for a served
|
|
22
|
+
* folder; the UTF-8 encoding of each text, for a file map), the input of projectIdentity (identity.js).
|
|
23
|
+
*
|
|
24
|
+
* In a project, a file imports another project file by a RELATIVE specifier, and the package by
|
|
25
|
+
* `@descix/frqtl-web/...`; nothing else. Relative specifiers are rewritten to the minted URLs of the files
|
|
26
|
+
* they name, in topological order (a file is minted after everything it imports). REFUSED before anything
|
|
27
|
+
* is minted, each naming the file, the specifier and the reason: a relative specifier that climbs out of
|
|
28
|
+
* the root (package internals have no public specifier), one that names no project file (never an HTTP
|
|
29
|
+
* sibling), a @descix/frqtl-web specifier that is not a row of FRQTL_WEB_SPECIFIERS, any other specifier;
|
|
30
|
+
* an import cycle among project files; a file-map entry the entry never reaches (a project is exactly the
|
|
31
|
+
* module graph its entry reaches, so every host hashes one set); a served file that is not UTF-8; a missing or non-function
|
|
32
|
+
* default export of the entry, a missing entry, an empty project. Every argument error REJECTS.
|
|
33
|
+
*
|
|
34
|
+
* blobMint(text) → { url, release() } a `blob:` module URL (a page)
|
|
35
|
+
* dataMint(text) → { url, release() } a `data:` module URL (node)
|
|
36
|
+
*
|
|
37
|
+
* A one-string sketch is the one-file project: `{ files: { 'sketch.js': text }, entry: 'sketch.js' }` —
|
|
38
|
+
* sketchProject(text) is that shape's one writer inside the tree (SKETCH_ENTRY its entry path).
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { libPathOf, FRQTL_WEB_PACKAGE, FRQTL_WEB_SPECIFIERS } from './specifiers.js';
|
|
42
|
+
import { checkProjectPath, projectBytes } from './identity.js';
|
|
43
|
+
import { packageRoot } from '../../frqtl/compat/package-root.js';
|
|
44
|
+
|
|
45
|
+
const SKETCH_FORM = 'export default function buildExperiment(exp) { … }';
|
|
46
|
+
|
|
47
|
+
/** THE decoder of served bytes: invalid UTF-8 throws; a BOM is stripped from the text (never from the identity bytes). */
|
|
48
|
+
const _UTF8 = new TextDecoder('utf-8', { fatal: true });
|
|
49
|
+
|
|
50
|
+
function _fail(msg) {
|
|
51
|
+
throw new Error('[link] ' + msg + ' (FAIL-LOUD.)');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The text with every comment blanked to spaces (newlines kept), so offsets into it are offsets into
|
|
56
|
+
* the text. String and template literals are skipped, so a '//' inside a string is not a comment.
|
|
57
|
+
* @param {string} text
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
function _blankComments(text) {
|
|
61
|
+
const out = text.split('');
|
|
62
|
+
let i = 0;
|
|
63
|
+
const n = text.length;
|
|
64
|
+
while (i < n) {
|
|
65
|
+
const c = text[i];
|
|
66
|
+
if (c === '/' && text[i + 1] === '/') {
|
|
67
|
+
while (i < n && text[i] !== '\n') { out[i] = ' '; i++; }
|
|
68
|
+
} else if (c === '/' && text[i + 1] === '*') {
|
|
69
|
+
const end = text.indexOf('*/', i + 2);
|
|
70
|
+
const stop = end === -1 ? n : end + 2;
|
|
71
|
+
for (; i < stop; i++) { if (text[i] !== '\n') { out[i] = ' '; } }
|
|
72
|
+
} else if (c === '\'' || c === '"' || c === '`') {
|
|
73
|
+
i++;
|
|
74
|
+
while (i < n && text[i] !== c) { if (text[i] === '\\') { i++; } i++; }
|
|
75
|
+
i++;
|
|
76
|
+
} else {
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out.join('');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const _IMPORT_RE = /(?:^|[;\n}])\s*(?:import|export)\b[^'";]*?\bfrom\s*(['"])([^'"\n]+)\1|(?:^|[;\n}])\s*import\s*(['"])([^'"\n]+)\3|\bimport\s*\(\s*(['"])([^'"\n]+)\5\s*\)/dg;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Every static import / export-from / side-effect import / literal dynamic import of `text`.
|
|
87
|
+
* @param {string} text
|
|
88
|
+
* @returns {{spec: string, start: number, end: number}[]} start/end bracket the specifier's characters
|
|
89
|
+
*/
|
|
90
|
+
function _imports(text) {
|
|
91
|
+
const code = _blankComments(text);
|
|
92
|
+
const out = [];
|
|
93
|
+
let m;
|
|
94
|
+
_IMPORT_RE.lastIndex = 0;
|
|
95
|
+
while ((m = _IMPORT_RE.exec(code))) {
|
|
96
|
+
const g = m[2] !== undefined ? 2 : (m[4] !== undefined ? 4 : 6);
|
|
97
|
+
out.push({ spec: m[g], start: m.indices[g][0], end: m.indices[g][1] });
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function _isRelative(spec) {
|
|
103
|
+
return spec.startsWith('./') || spec.startsWith('../');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A row of FRQTL_WEB_SPECIFIERS names it (libPathOf, the table's one reader). */
|
|
107
|
+
function _isPublished(spec) {
|
|
108
|
+
try { libPathOf(spec); return true; } catch (_) { return false; }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function _publishedList() {
|
|
112
|
+
return FRQTL_WEB_SPECIFIERS.map(function (r) { return r.specifier + (r.specifier.endsWith('/') ? '<file>' : ''); }).join(', ');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function _isPackage(spec) {
|
|
116
|
+
return spec === FRQTL_WEB_PACKAGE || spec.startsWith(FRQTL_WEB_PACKAGE + '/');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The project path a relative specifier names from `fromPath`, or null when it climbs out of the root. */
|
|
120
|
+
function _resolveRelative(fromPath, spec) {
|
|
121
|
+
const segs = fromPath.split('/');
|
|
122
|
+
segs.pop();
|
|
123
|
+
for (const s of spec.split('/')) {
|
|
124
|
+
if (s === '.' || s === '') { continue; }
|
|
125
|
+
if (s === '..') {
|
|
126
|
+
if (segs.length === 0) { return null; }
|
|
127
|
+
segs.pop();
|
|
128
|
+
} else {
|
|
129
|
+
segs.push(s);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return segs.join('/');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The project's import graph from `entry`: each file's imports, classified and checked.
|
|
137
|
+
* @param {string} entry
|
|
138
|
+
* @param {function(string): Promise<{text: string, bytes: Uint8Array}>} sourceOf — path → its text and bytes
|
|
139
|
+
* (throws naming a file it lacks)
|
|
140
|
+
* @returns {Promise<{order: string[], files: Object<string,string>, bytes: Object<string,Uint8Array>, edges: Object<string, object[]>}>}
|
|
141
|
+
*/
|
|
142
|
+
async function _walk(entry, sourceOf) {
|
|
143
|
+
const files = {};
|
|
144
|
+
const bytes = {};
|
|
145
|
+
const edges = {};
|
|
146
|
+
const order = [];
|
|
147
|
+
const state = {}; // path → 'visiting' | 'done'
|
|
148
|
+
async function visit(p, stack, spec) {
|
|
149
|
+
if (state[p] === 'done') { return; }
|
|
150
|
+
if (state[p] === 'visiting') {
|
|
151
|
+
const loop = stack.slice(stack.indexOf(p)).concat(p);
|
|
152
|
+
_fail('the project files import each other in a cycle: ' + loop.join(' → ') + ' — a project file graph ' +
|
|
153
|
+
'is acyclic (each file is minted after everything it imports).');
|
|
154
|
+
}
|
|
155
|
+
state[p] = 'visiting';
|
|
156
|
+
const src = await sourceOf(p, stack[stack.length - 1], spec);
|
|
157
|
+
const text = src.text;
|
|
158
|
+
files[p] = text;
|
|
159
|
+
bytes[p] = src.bytes;
|
|
160
|
+
const list = [];
|
|
161
|
+
for (const imp of _imports(text)) {
|
|
162
|
+
if (_isRelative(imp.spec)) {
|
|
163
|
+
const target = _resolveRelative(p, imp.spec);
|
|
164
|
+
if (target === null) {
|
|
165
|
+
_fail('"' + p + '" imports "' + imp.spec + '" — outside the project; package internals have no ' +
|
|
166
|
+
'public specifier (a project file imports the package by a @descix/frqtl-web/... row).');
|
|
167
|
+
}
|
|
168
|
+
list.push(Object.assign({ kind: 'file', target: target }, imp));
|
|
169
|
+
} else if (_isPackage(imp.spec)) {
|
|
170
|
+
if (!_isPublished(imp.spec)) {
|
|
171
|
+
_fail('"' + p + '" imports "' + imp.spec + '" — not a published specifier; the table ' +
|
|
172
|
+
'(lib/shim/project/specifiers.js FRQTL_WEB_SPECIFIERS) offers: ' + _publishedList() + '.');
|
|
173
|
+
}
|
|
174
|
+
list.push(Object.assign({ kind: 'package' }, imp));
|
|
175
|
+
} else {
|
|
176
|
+
_fail('"' + p + '" imports "' + imp.spec + '" — a project file imports another project file by a ' +
|
|
177
|
+
'relative specifier (./X.js) and the package by @descix/frqtl-web/...; nothing else.');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
edges[p] = list;
|
|
181
|
+
for (const e of list) {
|
|
182
|
+
if (e.kind === 'file') { await visit(e.target, stack.concat(p), e.spec); }
|
|
183
|
+
}
|
|
184
|
+
state[p] = 'done';
|
|
185
|
+
order.push(p);
|
|
186
|
+
}
|
|
187
|
+
await visit(entry, []);
|
|
188
|
+
return { order: order, files: files, bytes: bytes, edges: edges };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The served folder's absolute URL: an absolute `url` as given; a relative one ('./', as a project's own
|
|
193
|
+
* index.html writes it) against the document's base URL. A host with no document REJECTS a relative one.
|
|
194
|
+
* @param {string} url
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
function _folderUrl(url) {
|
|
198
|
+
try { return new URL(url).href; } catch (_) { /* relative: resolved below */ }
|
|
199
|
+
if (typeof document === 'undefined' || !document || typeof document.baseURI !== 'string') {
|
|
200
|
+
_fail('linkProject({ url }): ' + JSON.stringify(url) + ' is relative and this host has no document to ' +
|
|
201
|
+
'resolve it against — pass the absolute folder URL (e.g. new URL(\'./proj/\', import.meta.url).href).');
|
|
202
|
+
}
|
|
203
|
+
return new URL(url, document.baseURI).href;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Rewrite each import specifier of `text` through `urlOf(edge)`, escaped for the literal's own quote. */
|
|
207
|
+
function _rewrite(text, list, urlOf) {
|
|
208
|
+
let out = text;
|
|
209
|
+
for (const e of list.slice().sort((a, b) => b.start - a.start)) {
|
|
210
|
+
const quote = text[e.start - 1];
|
|
211
|
+
const lit = String(urlOf(e)).replace(/\\/g, '\\\\').split(quote).join('\\' + quote);
|
|
212
|
+
out = out.slice(0, e.start) + lit + out.slice(e.end);
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* A project is EXACTLY the module graph its entry reaches, so every host hashes one set: a served folder is
|
|
219
|
+
* that set by construction, and a file map may hold nothing else. REFUSES a file-map entry the walk did not
|
|
220
|
+
* reach, naming it.
|
|
221
|
+
*/
|
|
222
|
+
function _refuseUnreached(project, g) {
|
|
223
|
+
if (project.files === undefined) { return; }
|
|
224
|
+
const unused = Object.keys(project.files).filter(function (p) { return !Object.prototype.hasOwnProperty.call(g.files, p); });
|
|
225
|
+
if (unused.length) {
|
|
226
|
+
_fail('unused project file "' + unused[0] + '" — a project holds the files its entry imports' +
|
|
227
|
+
(unused.length > 1 ? ' (also unused: ' + unused.slice(1).join(', ') + ')' : '') + '.');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function _defaultBare(spec) {
|
|
232
|
+
return new URL(libPathOf(spec), packageRoot()).href;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function _entryFunction(ns, entry) {
|
|
236
|
+
if (typeof ns.default !== 'function') {
|
|
237
|
+
_fail('the entry "' + entry + '" has no default export function — its form is ' + SKETCH_FORM +
|
|
238
|
+
' (a classic sketch\'s `window.buildExperiment` tail is retired: write the ES module form).');
|
|
239
|
+
}
|
|
240
|
+
return ns.default;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @param {object} project — see the file header
|
|
245
|
+
* @returns {Promise<{buildExperiment: function, files: Object<string, string>}>}
|
|
246
|
+
*/
|
|
247
|
+
export async function linkProject(project) {
|
|
248
|
+
if (!project || typeof project !== 'object') {
|
|
249
|
+
_fail('linkProject(project): project is { files, entry, mint } or { url, entry } (got ' + typeof project + ').');
|
|
250
|
+
}
|
|
251
|
+
const entry = project.entry;
|
|
252
|
+
if (typeof entry !== 'string' || !entry) {
|
|
253
|
+
_fail('linkProject: `entry` is REQUIRED — the path of the file whose default export is the scene ' +
|
|
254
|
+
'(a one-string sketch: { files: { "sketch.js": text }, entry: "sketch.js" }).');
|
|
255
|
+
}
|
|
256
|
+
checkProjectPath(entry);
|
|
257
|
+
const hasFiles = project.files !== undefined;
|
|
258
|
+
const hasUrl = project.url !== undefined;
|
|
259
|
+
if (hasFiles === hasUrl) {
|
|
260
|
+
_fail('linkProject: a project is EITHER { files, entry } OR { url, entry } (got ' +
|
|
261
|
+
(hasFiles ? 'both' : 'neither') + ').');
|
|
262
|
+
}
|
|
263
|
+
const mint = project.mint;
|
|
264
|
+
if (typeof mint !== 'function') {
|
|
265
|
+
_fail('linkProject: mint is REQUIRED — the host\'s (text, path) → { url, release? } (blobMint in a page, ' +
|
|
266
|
+
'dataMint in node; lib/shim/project/link.js). A served folder is minted from the bytes it fetched.');
|
|
267
|
+
}
|
|
268
|
+
const bare = project.bare === undefined ? _defaultBare : project.bare;
|
|
269
|
+
if (typeof bare !== 'function') {
|
|
270
|
+
_fail('linkProject: bare is (specifier) → URL for a @descix/frqtl-web/... specifier.');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let sourceOf;
|
|
274
|
+
let base = null;
|
|
275
|
+
if (hasFiles) {
|
|
276
|
+
const files = project.files;
|
|
277
|
+
if (!files || typeof files !== 'object' || Object.keys(files).length === 0) {
|
|
278
|
+
_fail('linkProject: project.files is the map { path: text } and has no files.');
|
|
279
|
+
}
|
|
280
|
+
for (const p of Object.keys(files)) {
|
|
281
|
+
checkProjectPath(p);
|
|
282
|
+
if (typeof files[p] !== 'string') { _fail('linkProject: file "' + p + '" is not text.'); }
|
|
283
|
+
}
|
|
284
|
+
const encoded = projectBytes(files);
|
|
285
|
+
sourceOf = async function (p, from, spec) {
|
|
286
|
+
if (!Object.prototype.hasOwnProperty.call(files, p)) {
|
|
287
|
+
_fail(from === undefined
|
|
288
|
+
? 'the entry "' + p + '" is not a project file (files: ' + Object.keys(files).join(', ') + ').'
|
|
289
|
+
: '"' + from + '" imports "' + spec + '" (' + p + '), which is not a project file (files: ' +
|
|
290
|
+
Object.keys(files).join(', ') + ') — a relative specifier names a file in the project, never an HTTP ' +
|
|
291
|
+
'sibling; package internals have no public specifier.');
|
|
292
|
+
}
|
|
293
|
+
const t = files[p];
|
|
294
|
+
if (!t.trim()) { _fail('the project file "' + p + '" is empty.'); }
|
|
295
|
+
return { text: t, bytes: encoded[p] };
|
|
296
|
+
};
|
|
297
|
+
} else {
|
|
298
|
+
if (typeof project.url !== 'string' || !project.url.endsWith('/')) {
|
|
299
|
+
_fail('linkProject({ url }): url is the served project folder, ending in "/" (got ' + JSON.stringify(project.url) + ').');
|
|
300
|
+
}
|
|
301
|
+
base = _folderUrl(project.url);
|
|
302
|
+
sourceOf = async function (p, from, spec) {
|
|
303
|
+
const u = new URL(p, base).href;
|
|
304
|
+
let res;
|
|
305
|
+
try { res = await fetch(u); }
|
|
306
|
+
catch (e) { _fail('fetching "' + p + '" (' + u + ') failed: ' + String((e && e.message) || e) + '.'); }
|
|
307
|
+
if (!res.ok) {
|
|
308
|
+
_fail((from === undefined ? 'the entry "' + p + '"' : '"' + from + '" imports "' + spec + '" (' + p + '), which') +
|
|
309
|
+
' is not served at ' + u + ' (HTTP ' + res.status + ')' +
|
|
310
|
+
(from === undefined ? '.' : ' — a relative specifier names a file in the project; package internals have no public specifier.'));
|
|
311
|
+
}
|
|
312
|
+
const raw = new Uint8Array(await res.arrayBuffer());
|
|
313
|
+
let text;
|
|
314
|
+
try { text = _UTF8.decode(raw); }
|
|
315
|
+
catch (e) { _fail('"' + p + '" is not valid UTF-8 (' + u + ') — a project file is a UTF-8 text module.'); }
|
|
316
|
+
if (!text.trim()) { _fail('the project file "' + p + '" is empty.'); }
|
|
317
|
+
return { text: text, bytes: raw };
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const g = await _walk(entry, sourceOf);
|
|
322
|
+
_refuseUnreached(project, g);
|
|
323
|
+
|
|
324
|
+
const urls = {};
|
|
325
|
+
const minted = [];
|
|
326
|
+
try {
|
|
327
|
+
for (const p of g.order) {
|
|
328
|
+
const text = _rewrite(g.files[p], g.edges[p], function (e) {
|
|
329
|
+
return e.kind === 'file' ? urls[e.target] : bare(e.spec);
|
|
330
|
+
});
|
|
331
|
+
const m = mint(text, p);
|
|
332
|
+
if (!m || typeof m.url !== 'string') { _fail('mint(text, path) must return { url, release? } (for "' + p + '").'); }
|
|
333
|
+
minted.push(m);
|
|
334
|
+
urls[p] = m.url;
|
|
335
|
+
}
|
|
336
|
+
const ns = await import(urls[entry]);
|
|
337
|
+
return { buildExperiment: _entryFunction(ns, entry), files: g.bytes };
|
|
338
|
+
} finally {
|
|
339
|
+
for (const m of minted) { if (typeof m.release === 'function') { m.release(); } }
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** The entry path of a one-string sketch held as a project. */
|
|
344
|
+
export const SKETCH_ENTRY = 'sketch.js';
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The one-file project of a sketch held as one string.
|
|
348
|
+
* @param {string} text
|
|
349
|
+
* @returns {{files: Object<string, string>, entry: string}}
|
|
350
|
+
*/
|
|
351
|
+
export function sketchProject(text) {
|
|
352
|
+
if (typeof text !== 'string') {
|
|
353
|
+
_fail('sketchProject(text): the sketch module text (got ' + typeof text + ').');
|
|
354
|
+
}
|
|
355
|
+
const files = {};
|
|
356
|
+
files[SKETCH_ENTRY] = text;
|
|
357
|
+
return { files: files, entry: SKETCH_ENTRY };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** A `blob:` module URL for `text` (a page). */
|
|
361
|
+
export function blobMint(text) {
|
|
362
|
+
const url = URL.createObjectURL(new Blob([text], { type: 'text/javascript' }));
|
|
363
|
+
return { url: url, release: function () { URL.revokeObjectURL(url); } };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The `bare` of a host whose modules are NOT served one file per URL (frqtl-web.min.js): a
|
|
368
|
+
* `@descix/frqtl-web/...` specifier resolves to a minted module that re-exports the namespace this
|
|
369
|
+
* host already evaluated for libPathOf(spec), read through `readExpr` (a global expression the minted
|
|
370
|
+
* module can reach, e.g. 'FRQTL.modules'). One minted URL per module, kept for the page's lifetime,
|
|
371
|
+
* so every project links against the same instances. A specifier whose module the host does not carry
|
|
372
|
+
* THROWS naming it.
|
|
373
|
+
* @param {Object<string, object>} modules — lib-relative path → module namespace
|
|
374
|
+
* @param {string} readExpr — the expression that evaluates to `modules` in a minted module
|
|
375
|
+
* @param {function(string): {url: string}} mint
|
|
376
|
+
* @returns {function(string): string}
|
|
377
|
+
*/
|
|
378
|
+
export function namespaceBare(modules, readExpr, mint) {
|
|
379
|
+
const urls = {};
|
|
380
|
+
return function bare(spec) {
|
|
381
|
+
const libPath = libPathOf(spec);
|
|
382
|
+
if (urls[libPath]) { return urls[libPath]; }
|
|
383
|
+
const ns = modules[libPath];
|
|
384
|
+
if (!ns) {
|
|
385
|
+
_fail('"' + spec + '" names ' + libPath + ', which this host does not carry (it carries: ' +
|
|
386
|
+
Object.keys(modules).join(', ') + ').');
|
|
387
|
+
}
|
|
388
|
+
const lines = ['const ns = ' + readExpr + '[' + JSON.stringify(libPath) + '];'];
|
|
389
|
+
for (const k of Object.keys(ns)) {
|
|
390
|
+
if (k === 'default') { lines.push('export default ns.default;'); }
|
|
391
|
+
else if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k)) { lines.push('export const ' + k + ' = ns.' + k + ';'); }
|
|
392
|
+
else { _fail(libPath + ' exports "' + k + '", which is not an identifier.'); }
|
|
393
|
+
}
|
|
394
|
+
urls[libPath] = mint(lines.join('\n') + '\n').url;
|
|
395
|
+
return urls[libPath];
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** A `data:` module URL for `text` (node). */
|
|
400
|
+
export function dataMint(text) {
|
|
401
|
+
return { url: 'data:text/javascript;charset=utf-8,' + encodeURIComponent(text), release: function () {} };
|
|
402
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* node-specifier-hook.mjs — node resolves a sketch's `@descix/frqtl-web/...` self-reference through
|
|
5
|
+
* ONE hook, mapped by the ONE table (specifiers.js libPathOf) to this tree's lib-identity files.
|
|
6
|
+
*
|
|
7
|
+
* registerFrqtlWebSpecifiers() install the hook in this process (idempotent). Call it once
|
|
8
|
+
* before importing a scene file; the scene realm, the digest
|
|
9
|
+
* harness and the engine-at-eval gate do.
|
|
10
|
+
* resolve(specifier, context, next) the hook itself (module.register's resolve export)
|
|
11
|
+
*
|
|
12
|
+
* In a package, the hook maps into that package's own dist (this file sits at dist/shim/project/); the published
|
|
13
|
+
* door is `@descix/frqtl-node/register` (register-node.mjs). Node ≥ 20.6 (module.register).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { register } from 'node:module';
|
|
17
|
+
import { libPathOf, FRQTL_WEB_PACKAGE } from './specifiers.js';
|
|
18
|
+
|
|
19
|
+
const LIB_ROOT = new URL('../../', import.meta.url); // lib/ in the tree, dist/ in a package (this file is <root>/shim/project/)
|
|
20
|
+
|
|
21
|
+
/** The module.register resolve hook. */
|
|
22
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
23
|
+
if (specifier === FRQTL_WEB_PACKAGE || specifier.startsWith(FRQTL_WEB_PACKAGE + '/')) {
|
|
24
|
+
return { url: new URL(libPathOf(specifier), LIB_ROOT).href, shortCircuit: true };
|
|
25
|
+
}
|
|
26
|
+
return nextResolve(specifier, context);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let _registered = false;
|
|
30
|
+
/** Install the hook in this process (once). */
|
|
31
|
+
export function registerFrqtlWebSpecifiers() {
|
|
32
|
+
if (_registered) { return; }
|
|
33
|
+
register(import.meta.url);
|
|
34
|
+
_registered = true;
|
|
35
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* register-node.mjs — `@descix/frqtl-node/register`: importing it installs the resolve hook for a sketch's
|
|
5
|
+
* `@descix/frqtl-web/...` imports in this node process (node-specifier-hook.mjs), mapped by the one table to
|
|
6
|
+
* this package's own files.
|
|
7
|
+
*
|
|
8
|
+
* node --import @descix/frqtl-node/register my-scene.mjs
|
|
9
|
+
* // or, before the first import of a sketch: import '@descix/frqtl-node/register';
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { registerFrqtlWebSpecifiers } from './node-specifier-hook.mjs';
|
|
13
|
+
|
|
14
|
+
registerFrqtlWebSpecifiers();
|
|
15
|
+
|
|
16
|
+
/** Re-exported for a host that registers explicitly (idempotent: importing this module already did). */
|
|
17
|
+
export { registerFrqtlWebSpecifiers };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* specifiers.js — THE one table of `@descix/frqtl-web/...` specifiers a sketch may import.
|
|
5
|
+
*
|
|
6
|
+
* FRQTL_WEB_SPECIFIERS rows { specifier, libPath, why } — a row whose specifier ends in `/`
|
|
7
|
+
* is a PREFIX row (one part = one file under it)
|
|
8
|
+
* libPathOf(specifier) → the lib-identity path it names; THROWS on an unknown specifier
|
|
9
|
+
* importMapFor(libRootUrl) → { imports } — the rows as an import map, every libPath appended to
|
|
10
|
+
* the URL (relative or absolute) of the lib-identity root AS SEEN FROM
|
|
11
|
+
* THE PAGE; prefix rows stay prefixes. No filesystem computation, so
|
|
12
|
+
* a vendored dist root takes the same call.
|
|
13
|
+
* FRQTL_WEB_BOOT { specifier: '@descix/frqtl-web/boot', libPath } — the scene doors a HOST
|
|
14
|
+
* PAGE imports (the package's `./boot`). Not a sketch specifier: libPathOf
|
|
15
|
+
* refuses it, so a sketch cannot boot a page.
|
|
16
|
+
* pageImportMapFor(libRootUrl) → importMapFor(libRootUrl) plus the FRQTL_WEB_BOOT row: the map of a page
|
|
17
|
+
* that boots a scene through the doors and runs sketches beside it.
|
|
18
|
+
*
|
|
19
|
+
* Inside lib/ the specifier is a SELF-REFERENCE — the package naming itself, the way node resolves a
|
|
20
|
+
* package importing itself by name — not a lib→sdk edge. It is legal ONLY in sketch content
|
|
21
|
+
* (lib/frqtl/scenes/**, codegen output, sketch and notebook text); closure modules import relatively
|
|
22
|
+
* (scripts/verify-arrows.mjs holds the scope). Consumers ferry this table and never restate it:
|
|
23
|
+
* every page import map (importMapFor), the node resolve hook (node-specifier-hook.mjs) and the sdk
|
|
24
|
+
* build's module closure (libPathOf).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** The published surface a sketch imports. Every row is a public exports entry of both packages (sdk/_build/exports-contract.mjs derives them from this table). */
|
|
28
|
+
export const FRQTL_WEB_SPECIFIERS = Object.freeze([
|
|
29
|
+
Object.freeze({ specifier: '@descix/frqtl-web/parts', libPath: 'frqtl/parts/index.js',
|
|
30
|
+
why: 'the parts barrel' }),
|
|
31
|
+
Object.freeze({ specifier: '@descix/frqtl-web/parts/', libPath: 'frqtl/parts/',
|
|
32
|
+
why: 'one part = one file' }),
|
|
33
|
+
Object.freeze({ specifier: '@descix/frqtl-web/shim/controls/registry.js', libPath: 'shim/controls/registry.js',
|
|
34
|
+
why: 'knob registration (blackbody, circuit-sat, collision-matrix-3d, dse-tunnel-rebuild)' }),
|
|
35
|
+
Object.freeze({ specifier: '@descix/frqtl-web/shim/render/sun-look.js', libPath: 'shim/render/sun-look.js',
|
|
36
|
+
why: "the one owner of the sun's FX bag (dse-tunnel-rebuild)" }),
|
|
37
|
+
Object.freeze({ specifier: '@descix/frqtl-web/compat/renderer/display-slot.js', libPath: 'frqtl/compat/renderer/display-slot.js',
|
|
38
|
+
why: 'the booted display name before co-init (mirror-route, dse-tunnel-rebuild)' }),
|
|
39
|
+
Object.freeze({ specifier: '@descix/frqtl-web/compat/renderer/orbit-bag.js', libPath: 'frqtl/compat/renderer/orbit-bag.js',
|
|
40
|
+
why: 'hasAngularPose, THE 3D-ness predicate (atomic-model)' }),
|
|
41
|
+
Object.freeze({ specifier: '@descix/frqtl-web/objects/setup/_collisionMatrixLanes.js', libPath: 'frqtl/objects/setup/_collisionMatrixLanes.js',
|
|
42
|
+
why: 'the one lane table (collision-matrix-3d)' }),
|
|
43
|
+
Object.freeze({ specifier: '@descix/frqtl-web/compat/engine.js', libPath: 'frqtl/compat/engine.js',
|
|
44
|
+
why: 'the engine accessor (atomic-model builds its frames by hand)' }),
|
|
45
|
+
Object.freeze({ specifier: '@descix/frqtl-web/kinds', libPath: 'frqtl/objects/kinds-entry.js',
|
|
46
|
+
why: 'the kind registry: a composed fork registers its renamed class (registerKind, kindNamed, kindOf, registeredKinds)' })
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/** The scene doors a host page imports — the package's `./boot` export; never a sketch specifier. */
|
|
50
|
+
export const FRQTL_WEB_BOOT = Object.freeze({ specifier: '@descix/frqtl-web/boot', libPath: 'frqtl/experiments/_shared/doors.js' });
|
|
51
|
+
|
|
52
|
+
/** The package a self-reference names. */
|
|
53
|
+
export const FRQTL_WEB_PACKAGE = '@descix/frqtl-web';
|
|
54
|
+
|
|
55
|
+
function _isPrefix(row) { return row.specifier.endsWith('/'); }
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} specifier — a `@descix/frqtl-web/...` specifier
|
|
59
|
+
* @returns {string} the lib-identity path (e.g. 'frqtl/parts/Wall.js')
|
|
60
|
+
*/
|
|
61
|
+
export function libPathOf(specifier) {
|
|
62
|
+
if (typeof specifier !== 'string') {
|
|
63
|
+
throw new TypeError('[specifiers] libPathOf(specifier): a string specifier (got ' + typeof specifier + '). (FAIL-LOUD.)');
|
|
64
|
+
}
|
|
65
|
+
for (const row of FRQTL_WEB_SPECIFIERS) {
|
|
66
|
+
if (!_isPrefix(row) && row.specifier === specifier) { return row.libPath; }
|
|
67
|
+
}
|
|
68
|
+
for (const row of FRQTL_WEB_SPECIFIERS) {
|
|
69
|
+
if (_isPrefix(row) && specifier.startsWith(row.specifier) && specifier.length > row.specifier.length) {
|
|
70
|
+
const rest = specifier.slice(row.specifier.length);
|
|
71
|
+
if (rest.split('/').indexOf('..') !== -1) {
|
|
72
|
+
throw new Error('[specifiers] "' + specifier + '" climbs out of its prefix row (' + row.specifier + '). (FAIL-LOUD.)');
|
|
73
|
+
}
|
|
74
|
+
return row.libPath + rest;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
throw new Error('[specifiers] "' + specifier + '" is not a @descix/frqtl-web specifier — the table ' +
|
|
78
|
+
'(lib/shim/project/specifiers.js FRQTL_WEB_SPECIFIERS) offers: ' +
|
|
79
|
+
FRQTL_WEB_SPECIFIERS.map(function (r) { return r.specifier + (_isPrefix(r) ? '<file>' : ''); }).join(', ') +
|
|
80
|
+
'. (FAIL-LOUD.)');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} libRootUrl — the lib-identity root as seen from the page, ending in `/`
|
|
85
|
+
* (e.g. '../../', '/pub/abc123/', 'vendor/frqtl-web/0.6.0/dist/')
|
|
86
|
+
* @returns {{imports: Object<string, string>}}
|
|
87
|
+
*/
|
|
88
|
+
export function importMapFor(libRootUrl) {
|
|
89
|
+
if (typeof libRootUrl !== 'string' || !libRootUrl.endsWith('/')) {
|
|
90
|
+
throw new TypeError('[specifiers] importMapFor(libRootUrl): the lib-identity root URL as seen from the page, ' +
|
|
91
|
+
'ending in "/" (got ' + JSON.stringify(libRootUrl) + '). (FAIL-LOUD.)');
|
|
92
|
+
}
|
|
93
|
+
const imports = {};
|
|
94
|
+
for (const row of FRQTL_WEB_SPECIFIERS) { imports[row.specifier] = libRootUrl + row.libPath; }
|
|
95
|
+
return { imports: imports };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {string} libRootUrl — as importMapFor's
|
|
100
|
+
* @returns {{imports: Object<string, string>}} importMapFor(libRootUrl) plus the FRQTL_WEB_BOOT row
|
|
101
|
+
*/
|
|
102
|
+
export function pageImportMapFor(libRootUrl) {
|
|
103
|
+
const map = importMapFor(libRootUrl);
|
|
104
|
+
map.imports[FRQTL_WEB_BOOT.specifier] = libRootUrl + FRQTL_WEB_BOOT.libPath;
|
|
105
|
+
return map;
|
|
106
|
+
}
|