@descix/frqtl-node 0.6.0-p0.2 → 0.6.0-p4.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 +2 -2
- package/README.md +8 -4
- package/dist/dist-manifest.json +70 -62
- package/dist/eyes/index.js +5 -2
- package/dist/frqtl/assets/vendor/acorn.LICENSE +21 -0
- package/dist/frqtl/assets/vendor/acorn.PINNED.json +9 -0
- package/dist/frqtl/assets/vendor/acorn.mjs +6266 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +41 -0
- package/dist/frqtl/experiments/_shared/boot-failure.js +60 -0
- package/dist/frqtl/objects/FrqtlApp.js +136 -30
- package/dist/frqtl/objects/ObjectFactory.js +4 -5
- package/dist/frqtl/objects/RecipeContext.js +617 -9
- package/dist/frqtl/objects/instruments/hosted-eyes.js +268 -57
- package/dist/frqtl/objects/objects/QuantumEmitter.js +1 -1
- package/dist/frqtl/objects/record/Record.js +326 -0
- package/dist/frqtl/objects/record/open-record.js +22 -0
- package/dist/frqtl/objects/schema/index.js +72 -0
- package/dist/frqtl/parts/DetectorWall.js +1 -1
- package/dist/frqtl/parts/FrqtlControlsPanel.js +4 -2
- package/dist/frqtl/parts/FrqtlVision.js +21 -6
- package/dist/frqtl/parts/Wall.js +2 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +18 -14
- package/dist/frqtl/scenes/absorbing-wall.js +18 -14
- package/dist/frqtl/scenes/atomic-model.js +3 -0
- package/dist/frqtl/scenes/big-bang.js +3 -0
- package/dist/frqtl/scenes/blackbody.js +14 -11
- package/dist/frqtl/scenes/circuit-sat.js +7 -4
- package/dist/frqtl/scenes/collision-matrix-3d.js +3 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +19 -15
- package/dist/frqtl/scenes/counting-wall.js +19 -15
- package/dist/frqtl/scenes/double-slit.js +3 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +64 -58
- package/dist/frqtl/scenes/dse-one-slit.js +59 -55
- package/dist/frqtl/scenes/dse-photoelectric.js +28 -24
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +124 -99
- package/dist/frqtl/scenes/dse-two-emitters.js +64 -58
- package/dist/frqtl/scenes/dse-two-slits.js +69 -65
- package/dist/frqtl/scenes/empty.js +3 -0
- package/dist/frqtl/scenes/laser-buildup.js +23 -16
- package/dist/frqtl/scenes/mirror-3d.js +31 -27
- package/dist/frqtl/scenes/mirror-route.js +4 -1
- package/dist/frqtl/scenes/mirror.js +24 -20
- package/dist/frqtl/scenes/omni-3d.js +7 -4
- package/dist/frqtl/scenes/omni-source.js +7 -4
- package/dist/frqtl/scenes/self-interference.js +13 -10
- package/dist/frqtl/scenes/smoking-gun.js +15 -11
- package/dist/frqtl/scenes/straight-walk.js +7 -4
- package/dist/frqtl/scenes/sun.js +11 -8
- package/dist/frqtl/scenes/two-source.js +22 -17
- package/dist/frqtl/scenes/wave.js +10 -7
- package/dist/shim/bundle/index.js +112 -96
- package/dist/shim/bundle/indexHtml.js +66 -39
- package/dist/shim/engine/router.js +2 -2
- package/dist/shim/index.js +1 -1
- package/dist/shim/notebook/format.js +1 -1
- package/dist/shim/notebook/store.js +2 -3
- package/dist/shim/page-params/index.js +2 -2
- package/dist/shim/project/index.js +20 -5
- package/dist/shim/project/link.js +182 -37
- package/dist/shim/project/manifest.js +151 -0
- package/dist/shim/project/parts-module.js +67 -0
- package/dist/shim/project/parts-writer.js +457 -0
- package/dist/shim/project/record-format.js +345 -0
- package/dist/shim/project/specifiers.js +41 -13
- package/dist/shim/registry/index.js +1 -1
- package/dist/shim/registry/scene-source.js +44 -30
- package/dist/shim/registry/walkthrough.js +13 -12
- package/dist/shim/render-state/apply.js +11 -14
- package/dist/shim/storage/codec-chains.js +88 -0
- package/dist/shim/storage/folders.js +205 -0
- package/dist/shim/storage/index.js +64 -8
- package/dist/shim/storage/make-namespace-store.js +5 -5
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/dist/shim/bundle/frqnb.js +0 -112
- package/dist/shim/bundle/manifest.js +0 -95
- package/dist/shim/storage/modules.js +0 -249
- package/dist/shim/storage/projects.js +0 -150
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* lib/shim/bundle/manifest.js — the bundle manifest as an ENVELOPE FERRY
|
|
5
|
-
* (E1).
|
|
6
|
-
*
|
|
7
|
-
* ── The rule (E1): manifest = the Module envelope minus `source` ─────────────
|
|
8
|
-
*
|
|
9
|
-
* The bundle is a projection of the `lib/shim/storage/modules.js` Module
|
|
10
|
-
* envelope. `experiment.js` = `module.source`. The manifest carries EVERYTHING
|
|
11
|
-
* ELSE from the envelope, ferried VERBATIM (`{...envelope}` minus `source`) plus
|
|
12
|
-
* a small set of bundle-only fields. Critically this module NEVER re-enumerates
|
|
13
|
-
* the envelope's field list (`{name: mod.name, renderState: mod.renderState, …}`)
|
|
14
|
-
* — that would be a schema mirror of modules.js. It spreads the whole envelope
|
|
15
|
-
* and deletes the ONE key (`source`) the sibling `experiment.js` file owns. Any
|
|
16
|
-
* field modules.js adds in future rides through automatically; `renderState`
|
|
17
|
-
* rides through as an ordinary envelope field, opaque (never parsed here).
|
|
18
|
-
*
|
|
19
|
-
* ── Bundle-only fields ───────────────────────────────────────────────────────
|
|
20
|
-
*
|
|
21
|
-
* sdkVersion : string — the `@descix/frqtl-web@<pin>` the index.html imports.
|
|
22
|
-
* A VALUE passed IN by the caller (the feature side
|
|
23
|
-
* DERIVES it from the built SDK's package.json at export
|
|
24
|
-
* time — truth-in-labeling; NEVER a literal in bundle
|
|
25
|
-
* code). REQUIRED — FAIL-LOUD when absent.
|
|
26
|
-
* preview : string|null — the preview image filename ref (e.g. 'preview.png'),
|
|
27
|
-
* or null when no preview was captured.
|
|
28
|
-
* descix : {} — reserved inert publish block (DeSciX now-vs-later).
|
|
29
|
-
*
|
|
30
|
-
* Layer 2 (physics): none — pure metadata projection.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
'use strict';
|
|
34
|
-
|
|
35
|
-
/** The envelope key the sibling `experiment.js` file owns (never in the manifest). */
|
|
36
|
-
export var SOURCE_KEY = 'source';
|
|
37
|
-
|
|
38
|
-
/** Bundle-only manifest keys (NOT envelope fields — stripped on import). */
|
|
39
|
-
export var BUNDLE_ONLY_KEYS = ['sdkVersion', 'preview', 'descix'];
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Build the manifest object from a Module envelope + bundle-only inputs.
|
|
43
|
-
*
|
|
44
|
-
* @param {object} envelope — a `lib/shim/storage/modules.js` Module envelope
|
|
45
|
-
* (MUST carry `source`; every other field is ferried verbatim).
|
|
46
|
-
* @param {object} opts
|
|
47
|
-
* @param {string} opts.sdkVersion — REQUIRED; the CDN pin (e.g. '0.6.0-p0.2').
|
|
48
|
-
* @param {string|null} [opts.preview] — preview filename ref, or null/omitted.
|
|
49
|
-
* @returns {object} the manifest (JSON-serializable).
|
|
50
|
-
*/
|
|
51
|
-
export function buildManifest(envelope, opts) {
|
|
52
|
-
if (!envelope || typeof envelope !== 'object') {
|
|
53
|
-
throw new Error('[bundle/manifest] buildManifest(envelope): envelope must be an object.');
|
|
54
|
-
}
|
|
55
|
-
opts = opts || {};
|
|
56
|
-
if (typeof opts.sdkVersion !== 'string' || opts.sdkVersion.length === 0) {
|
|
57
|
-
throw new Error('[bundle/manifest] buildManifest: opts.sdkVersion is REQUIRED (a derived value, ' +
|
|
58
|
-
'never a literal) — the index.html CDN pin depends on it.');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Ferry the WHOLE envelope minus `source` (no per-field re-enumeration).
|
|
62
|
-
var manifest = Object.assign({}, envelope);
|
|
63
|
-
delete manifest[SOURCE_KEY];
|
|
64
|
-
|
|
65
|
-
// Bundle-only fields.
|
|
66
|
-
manifest.sdkVersion = opts.sdkVersion;
|
|
67
|
-
manifest.preview = (opts.preview != null) ? opts.preview : null;
|
|
68
|
-
manifest.descix = {}; // reserved inert publish block
|
|
69
|
-
|
|
70
|
-
return manifest;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Recover the Module envelope from a manifest + the `experiment.js` source.
|
|
75
|
-
* Inverse of `buildManifest`: strips the bundle-only fields and re-attaches
|
|
76
|
-
* `source`. The result is a full Module envelope (validatable by modules.js).
|
|
77
|
-
*
|
|
78
|
-
* @param {object} manifest — the parsed manifest.json object.
|
|
79
|
-
* @param {string} source — the `experiment.js` recipe source.
|
|
80
|
-
* @returns {object} the reconstructed Module envelope.
|
|
81
|
-
*/
|
|
82
|
-
export function manifestToEnvelope(manifest, source) {
|
|
83
|
-
if (!manifest || typeof manifest !== 'object') {
|
|
84
|
-
throw new Error('[bundle/manifest] manifestToEnvelope(manifest): manifest must be an object.');
|
|
85
|
-
}
|
|
86
|
-
if (typeof source !== 'string') {
|
|
87
|
-
throw new Error('[bundle/manifest] manifestToEnvelope: source (experiment.js) must be a string.');
|
|
88
|
-
}
|
|
89
|
-
var envelope = Object.assign({}, manifest);
|
|
90
|
-
for (var i = 0; i < BUNDLE_ONLY_KEYS.length; i++) {
|
|
91
|
-
delete envelope[BUNDLE_ONLY_KEYS[i]];
|
|
92
|
-
}
|
|
93
|
-
envelope[SOURCE_KEY] = source;
|
|
94
|
-
return envelope;
|
|
95
|
-
}
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* lib/shim/storage/modules.js — Experiment module (.js text) storage helpers.
|
|
5
|
-
*
|
|
6
|
-
* Defines the Module envelope shape and CRUD helpers that operate on top of
|
|
7
|
-
* any StorageProvider (lib/shim/storage/index.js) using the "files" namespace.
|
|
8
|
-
*
|
|
9
|
-
* ── Module envelope ────────────────────────────────────────────────────────
|
|
10
|
-
*
|
|
11
|
-
* {
|
|
12
|
-
* id: string — unique identifier (newModuleId())
|
|
13
|
-
* name: string — human-readable module name
|
|
14
|
-
* language: 'javascript'
|
|
15
|
-
* source: string — the .js module text
|
|
16
|
-
* templateId: string|null — origin experiment id or null if blank
|
|
17
|
-
* kind: 'frqtl' | 'math' — which SDK the recipe targets (v2+)
|
|
18
|
-
* createdAt: string — ISO-8601 creation timestamp
|
|
19
|
-
* updatedAt: string — ISO-8601 last-updated timestamp
|
|
20
|
-
* schemaVersion: number — integer version (see MODULE_SCHEMA_VERSION,
|
|
21
|
-
* the authority; currently 4)
|
|
22
|
-
* scripts: string[] — v4+. OPAQUE frqtl-relative setup-factory
|
|
23
|
-
* delta the load path injects (engineRouter.resolve) BEFORE
|
|
24
|
-
* eval, so a ferried recipe whose source calls a setup factory
|
|
25
|
-
* (a walk-through rung) resolves that window global. `[]`/absent
|
|
26
|
-
* for an ordinary saved project (DX-3, the rung-source ferry).
|
|
27
|
-
* renderState: object|null — v3+. Studio's Save realignment
|
|
28
|
-
* (Studio Controls Realignment §2.6) capture bag —
|
|
29
|
-
* everything the .js recipe itself does NOT round-trip:
|
|
30
|
-
* renderer/seed (load-time), render capability
|
|
31
|
-
* values + camera + backdrop visibility + bind-backed
|
|
32
|
-
* page-level control values (runtime replay). Shape owned
|
|
33
|
-
* by `lib/features/ide/save-state.js`'s
|
|
34
|
-
* `normalizeSavedRenderState` — this module does NOT
|
|
35
|
-
* re-validate its internal shape (an optional, best-effort
|
|
36
|
-
* replay bag, never load-bearing for the recipe itself);
|
|
37
|
-
* `null`/absent is the pre-S6 legacy state and stays
|
|
38
|
-
* perfectly valid (R6 discipline extended to v3).
|
|
39
|
-
* }
|
|
40
|
-
*
|
|
41
|
-
* ── Schema migration ────────────────────────────────────────────────────────
|
|
42
|
-
*
|
|
43
|
-
* v1 → v2: `kind` field added. Legacy v1 envelopes (no `kind` field) are
|
|
44
|
-
* read without error and default to `kind:'frqtl'` (R6 binding: must never
|
|
45
|
-
* throw on legacy envelopes). The `kind` field is the ONLY new field (AM-2).
|
|
46
|
-
*
|
|
47
|
-
* v2 → v3: `renderState` field added (Studio Controls Realignment §2.6,
|
|
48
|
-
* S6). Legacy v1/v2 envelopes (no `renderState` field) are read without
|
|
49
|
-
* error and default to `renderState: null` (R6 extended) — a saved project
|
|
50
|
-
* from before S6 simply replays with NO render/camera/page-control
|
|
51
|
-
* state (byte-identical to its own pre-S6 behavior), never a throw.
|
|
52
|
-
*
|
|
53
|
-
* ── Namespace ──────────────────────────────────────────────────────────────
|
|
54
|
-
*
|
|
55
|
-
* All helpers use the "files" namespace. Keys are module ids.
|
|
56
|
-
*
|
|
57
|
-
* ── Usage ──────────────────────────────────────────────────────────────────
|
|
58
|
-
*
|
|
59
|
-
* import { newModuleId, saveModule, loadModule,
|
|
60
|
-
* listModules, deleteModule } from 'lib/shim/storage/modules.js';
|
|
61
|
-
* import { getProvider } from 'lib/shim/storage';
|
|
62
|
-
*
|
|
63
|
-
* const provider = getProvider();
|
|
64
|
-
*
|
|
65
|
-
* const id = newModuleId();
|
|
66
|
-
* const mod = {
|
|
67
|
-
* id,
|
|
68
|
-
* name: 'My wave experiment',
|
|
69
|
-
* language: 'javascript',
|
|
70
|
-
* source: 'function buildExperiment(exp) { ... }',
|
|
71
|
-
* templateId: 'double-slit',
|
|
72
|
-
* kind: 'frqtl', // or 'math' for EGPTMath recipes
|
|
73
|
-
* createdAt: new Date().toISOString(),
|
|
74
|
-
* updatedAt: new Date().toISOString(),
|
|
75
|
-
* schemaVersion: 2,
|
|
76
|
-
* };
|
|
77
|
-
* saveModule(provider, mod);
|
|
78
|
-
*
|
|
79
|
-
* const loaded = loadModule(provider, id);
|
|
80
|
-
* const all = listModules(provider);
|
|
81
|
-
* deleteModule(provider, id);
|
|
82
|
-
*/
|
|
83
|
-
|
|
84
|
-
'use strict';
|
|
85
|
-
|
|
86
|
-
import { makeNamespaceStore } from './make-namespace-store.js';
|
|
87
|
-
|
|
88
|
-
/** @type {string} The storage namespace for all module records. */
|
|
89
|
-
export var MODULES_NAMESPACE = 'files';
|
|
90
|
-
|
|
91
|
-
/** @type {number} Current schema version. Increment on breaking envelope changes. */
|
|
92
|
-
export var MODULE_SCHEMA_VERSION = 4;
|
|
93
|
-
|
|
94
|
-
// ── Envelope validation ────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
function _validateModule(mod) {
|
|
97
|
-
if (!mod || typeof mod !== 'object') {
|
|
98
|
-
throw new Error('[modules] module must be a non-null object');
|
|
99
|
-
}
|
|
100
|
-
if (typeof mod.id !== 'string' || mod.id.length === 0) {
|
|
101
|
-
throw new Error('[modules] module.id must be a non-empty string');
|
|
102
|
-
}
|
|
103
|
-
if (typeof mod.name !== 'string' || mod.name.length === 0) {
|
|
104
|
-
throw new Error('[modules] module.name must be a non-empty string');
|
|
105
|
-
}
|
|
106
|
-
if (typeof mod.source !== 'string') {
|
|
107
|
-
throw new Error('[modules] module.source must be a string');
|
|
108
|
-
}
|
|
109
|
-
if (mod.source.length === 0) {
|
|
110
|
-
throw new Error('[modules] module.source must not be empty');
|
|
111
|
-
}
|
|
112
|
-
if (mod.templateId !== null && typeof mod.templateId !== 'string') {
|
|
113
|
-
throw new Error('[modules] module.templateId must be a string or null');
|
|
114
|
-
}
|
|
115
|
-
if (mod.kind !== 'frqtl' && mod.kind !== 'math') {
|
|
116
|
-
throw new Error('[modules] module.kind must be "frqtl" or "math"');
|
|
117
|
-
}
|
|
118
|
-
if (typeof mod.schemaVersion !== 'number') {
|
|
119
|
-
throw new Error('[modules] module.schemaVersion must be a number');
|
|
120
|
-
}
|
|
121
|
-
// renderState (v3+) is OPTIONAL and best-effort (see envelope doc above) —
|
|
122
|
-
// the only shape check here is "object or null/undefined", never a deep
|
|
123
|
-
// validation of its internal fields (that tolerance lives in save-state.js's
|
|
124
|
-
// own normalizeSavedRenderState, applied at REPLAY time, not at save time).
|
|
125
|
-
if (mod.renderState !== undefined && mod.renderState !== null &&
|
|
126
|
-
(typeof mod.renderState !== 'object' || Array.isArray(mod.renderState))) {
|
|
127
|
-
throw new Error('[modules] module.renderState must be an object, null, or omitted');
|
|
128
|
-
}
|
|
129
|
-
// scripts (v4+) is OPTIONAL — an OPAQUE frqtl-relative setup-factory delta the
|
|
130
|
-
// load path injects (engineRouter.resolve) BEFORE eval, so a recipe whose source
|
|
131
|
-
// calls a setup factory (e.g. a ferried walk-through rung) resolves that global.
|
|
132
|
-
// Shape check only: an array of strings, or omitted. The loaders own the
|
|
133
|
-
// resolution + FAIL-LOUD injection, not this schema.
|
|
134
|
-
if (mod.scripts !== undefined && mod.scripts !== null) {
|
|
135
|
-
if (!Array.isArray(mod.scripts) || !mod.scripts.every(function (s) { return typeof s === 'string'; })) {
|
|
136
|
-
throw new Error('[modules] module.scripts must be an array of strings, null, or omitted');
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ── v1 → v2 migration ─────────────────────────────────────────────────────
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Migrate a raw stored envelope to the current schema version.
|
|
145
|
-
* Safe to call on already-current envelopes (idempotent).
|
|
146
|
-
*
|
|
147
|
-
* v1 → v2: default `kind` to `'frqtl'` when the field is absent.
|
|
148
|
-
* R6 (binding): this must NEVER throw on legacy v1 envelopes.
|
|
149
|
-
*
|
|
150
|
-
* v2 → v3: default `renderState` to `null` when the field is absent
|
|
151
|
-
* (Studio Controls Realignment §2.6, S6). R6 extended: never throw on
|
|
152
|
-
* a legacy v1/v2 envelope with no `renderState` field.
|
|
153
|
-
*
|
|
154
|
-
* v3 → v4: default `scripts` to `[]` when the field is absent (DX-3 —
|
|
155
|
-
* the rung-source ferry). R6 extended again: never throw on
|
|
156
|
-
* a legacy envelope with no `scripts` field (an ordinary saved project has none).
|
|
157
|
-
*
|
|
158
|
-
* @param {object} mod Raw object from storage.
|
|
159
|
-
* @returns {object} Envelope at the current schema version.
|
|
160
|
-
*/
|
|
161
|
-
function _migrate(mod) {
|
|
162
|
-
if (!mod || typeof mod !== 'object') { return mod; }
|
|
163
|
-
var patch = {};
|
|
164
|
-
if (mod.kind === undefined || mod.kind === null) { patch.kind = 'frqtl'; }
|
|
165
|
-
if (mod.renderState === undefined) { patch.renderState = null; }
|
|
166
|
-
if (mod.scripts === undefined) { patch.scripts = []; }
|
|
167
|
-
if (Object.keys(patch).length === 0) { return mod; }
|
|
168
|
-
return Object.assign({}, mod, patch);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ── kind filter ────────────────────────────────────────────────────────────
|
|
172
|
-
|
|
173
|
-
function _kindFilter(mod, opts) {
|
|
174
|
-
var kindFilter = (opts && opts.kind) ? opts.kind : null;
|
|
175
|
-
if (kindFilter !== null && mod.kind !== kindFilter) { return false; }
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ── Summary projection ─────────────────────────────────────────────────────
|
|
180
|
-
|
|
181
|
-
function _summaryFields(mod) {
|
|
182
|
-
// Skip malformed records that are missing required summary fields.
|
|
183
|
-
if (typeof mod.name !== 'string') { return null; }
|
|
184
|
-
return {
|
|
185
|
-
id: mod.id,
|
|
186
|
-
name: mod.name,
|
|
187
|
-
kind: mod.kind,
|
|
188
|
-
templateId: mod.templateId || null,
|
|
189
|
-
updatedAt: mod.updatedAt || '',
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ── Store instance ─────────────────────────────────────────────────────────
|
|
194
|
-
|
|
195
|
-
var _store = makeNamespaceStore({
|
|
196
|
-
namespace: MODULES_NAMESPACE,
|
|
197
|
-
schemaVersion: MODULE_SCHEMA_VERSION,
|
|
198
|
-
idPrefix: 'mod_',
|
|
199
|
-
validate: _validateModule,
|
|
200
|
-
summaryFields: _summaryFields,
|
|
201
|
-
migrate: _migrate,
|
|
202
|
-
listFilter: _kindFilter,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// ── Public API (names and signatures unchanged) ────────────────────────────
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Generate a new unique module id.
|
|
209
|
-
* @returns {string} e.g. "mod_lxyz0abc1234"
|
|
210
|
-
*/
|
|
211
|
-
export var newModuleId = _store.newId;
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Save (create or update) a module.
|
|
215
|
-
* @param {import('./index.js').StorageProvider} provider
|
|
216
|
-
* @param {object} mod
|
|
217
|
-
* @throws {Error} on validation failure or provider write failure.
|
|
218
|
-
*/
|
|
219
|
-
export var saveModule = _store.save;
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Load a module by id.
|
|
223
|
-
* Legacy v1 envelopes (no `kind` field) are migrated transparently (R6).
|
|
224
|
-
* @param {import('./index.js').StorageProvider} provider
|
|
225
|
-
* @param {string} id
|
|
226
|
-
* @returns {object|null}
|
|
227
|
-
*/
|
|
228
|
-
export var loadModule = _store.load;
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* List modules, returning summary objects suitable for a module picker.
|
|
232
|
-
*
|
|
233
|
-
* Returns an array of { id, name, kind, templateId, updatedAt } objects.
|
|
234
|
-
* Sorted by updatedAt descending.
|
|
235
|
-
* AM-2 (binding): filtering via opts.kind — pass to restrict, omit for all.
|
|
236
|
-
*
|
|
237
|
-
* @param {import('./index.js').StorageProvider} provider
|
|
238
|
-
* @param {object} [opts]
|
|
239
|
-
* @param {'frqtl'|'math'|undefined} [opts.kind]
|
|
240
|
-
* @returns {Array<{id: string, name: string, kind: string, templateId: string|null, updatedAt: string}>}
|
|
241
|
-
*/
|
|
242
|
-
export var listModules = _store.list;
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Delete a module by id. No-op if absent.
|
|
246
|
-
* @param {import('./index.js').StorageProvider} provider
|
|
247
|
-
* @param {string} id
|
|
248
|
-
*/
|
|
249
|
-
export var deleteModule = _store['delete'];
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* lib/shim/storage/projects.js — Project-envelope helpers.
|
|
5
|
-
*
|
|
6
|
-
* Defines the Project envelope shape and CRUD helpers that operate on top of
|
|
7
|
-
* any StorageProvider (lib/shim/storage/index.js) using the "projects"
|
|
8
|
-
* namespace.
|
|
9
|
-
*
|
|
10
|
-
* ── Project envelope ───────────────────────────────────────────────────────
|
|
11
|
-
*
|
|
12
|
-
* {
|
|
13
|
-
* id: string — UUID-style unique identifier (newProjectId())
|
|
14
|
-
* name: string — human-readable project name
|
|
15
|
-
* templateId: string|null — origin experiment id ("double-slit",
|
|
16
|
-
* "blackbody", etc.) or null if blank
|
|
17
|
-
* createdAt: string — ISO-8601 creation timestamp
|
|
18
|
-
* updatedAt: string — ISO-8601 last-updated timestamp
|
|
19
|
-
* schemaVersion: number — integer version; currently 1
|
|
20
|
-
* design: object — opaque JSON authored by AuthorView
|
|
21
|
-
* (do NOT interpret here — that is the
|
|
22
|
-
* AuthorView workstream's responsibility)
|
|
23
|
-
* }
|
|
24
|
-
*
|
|
25
|
-
* ── Namespace ──────────────────────────────────────────────────────────────
|
|
26
|
-
*
|
|
27
|
-
* All helpers use the "projects" namespace. Keys are project ids.
|
|
28
|
-
*
|
|
29
|
-
* ── Usage ──────────────────────────────────────────────────────────────────
|
|
30
|
-
*
|
|
31
|
-
* import { newProjectId, saveProject, loadProject,
|
|
32
|
-
* listProjects, deleteProject } from 'lib/shim/storage/projects.js';
|
|
33
|
-
* import { getProvider } from 'lib/shim/storage';
|
|
34
|
-
*
|
|
35
|
-
* const provider = getProvider();
|
|
36
|
-
*
|
|
37
|
-
* const id = newProjectId();
|
|
38
|
-
* const project = {
|
|
39
|
-
* id,
|
|
40
|
-
* name: 'My first project',
|
|
41
|
-
* templateId: 'double-slit',
|
|
42
|
-
* createdAt: new Date().toISOString(),
|
|
43
|
-
* updatedAt: new Date().toISOString(),
|
|
44
|
-
* schemaVersion: 1,
|
|
45
|
-
* design: {},
|
|
46
|
-
* };
|
|
47
|
-
* saveProject(provider, project);
|
|
48
|
-
*
|
|
49
|
-
* const loaded = loadProject(provider, id);
|
|
50
|
-
* const all = listProjects(provider);
|
|
51
|
-
* deleteProject(provider, id);
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
'use strict';
|
|
55
|
-
|
|
56
|
-
import { makeNamespaceStore } from './make-namespace-store.js';
|
|
57
|
-
|
|
58
|
-
/** @type {string} The storage namespace for all project records. */
|
|
59
|
-
export var PROJECTS_NAMESPACE = 'projects';
|
|
60
|
-
|
|
61
|
-
/** @type {number} Current schema version. Increment on breaking envelope changes. */
|
|
62
|
-
export var PROJECT_SCHEMA_VERSION = 1;
|
|
63
|
-
|
|
64
|
-
// ── Envelope validation ────────────────────────────────────────────────────
|
|
65
|
-
|
|
66
|
-
function _validateProject(project) {
|
|
67
|
-
if (!project || typeof project !== 'object') {
|
|
68
|
-
throw new Error('[projects] project must be a non-null object');
|
|
69
|
-
}
|
|
70
|
-
if (typeof project.id !== 'string' || project.id.length === 0) {
|
|
71
|
-
throw new Error('[projects] project.id must be a non-empty string');
|
|
72
|
-
}
|
|
73
|
-
if (typeof project.name !== 'string' || project.name.length === 0) {
|
|
74
|
-
throw new Error('[projects] project.name must be a non-empty string');
|
|
75
|
-
}
|
|
76
|
-
if (project.templateId !== null && typeof project.templateId !== 'string') {
|
|
77
|
-
throw new Error('[projects] project.templateId must be a string or null');
|
|
78
|
-
}
|
|
79
|
-
if (typeof project.schemaVersion !== 'number') {
|
|
80
|
-
throw new Error('[projects] project.schemaVersion must be a number');
|
|
81
|
-
}
|
|
82
|
-
if (typeof project.design !== 'object' || project.design === null) {
|
|
83
|
-
throw new Error('[projects] project.design must be a non-null object');
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ── Summary projection ─────────────────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
function _summaryFields(project) {
|
|
90
|
-
// Skip malformed records that are missing required summary fields.
|
|
91
|
-
if (typeof project.name !== 'string') { return null; }
|
|
92
|
-
return {
|
|
93
|
-
id: project.id,
|
|
94
|
-
name: project.name,
|
|
95
|
-
templateId: project.templateId || null,
|
|
96
|
-
updatedAt: project.updatedAt || '',
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ── Store instance ─────────────────────────────────────────────────────────
|
|
101
|
-
|
|
102
|
-
var _store = makeNamespaceStore({
|
|
103
|
-
namespace: PROJECTS_NAMESPACE,
|
|
104
|
-
schemaVersion: PROJECT_SCHEMA_VERSION,
|
|
105
|
-
idPrefix: 'proj_',
|
|
106
|
-
validate: _validateProject,
|
|
107
|
-
summaryFields: _summaryFields,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// ── Public API (names and signatures unchanged) ────────────────────────────
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Generate a new unique project id.
|
|
114
|
-
* @returns {string} e.g. "proj_lxyz0abc1234"
|
|
115
|
-
*/
|
|
116
|
-
export var newProjectId = _store.newId;
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Save (create or update) a project.
|
|
120
|
-
* @param {import('./index.js').StorageProvider} provider
|
|
121
|
-
* @param {object} project
|
|
122
|
-
* @throws {Error} on validation failure or provider write failure.
|
|
123
|
-
*/
|
|
124
|
-
export var saveProject = _store.save;
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Load a project by id.
|
|
128
|
-
* @param {import('./index.js').StorageProvider} provider
|
|
129
|
-
* @param {string} id
|
|
130
|
-
* @returns {object|null}
|
|
131
|
-
*/
|
|
132
|
-
export var loadProject = _store.load;
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* List all projects, returning summary objects suitable for a project picker.
|
|
136
|
-
*
|
|
137
|
-
* Returns an array of { id, name, templateId, updatedAt } objects.
|
|
138
|
-
* Sorted by updatedAt descending.
|
|
139
|
-
*
|
|
140
|
-
* @param {import('./index.js').StorageProvider} provider
|
|
141
|
-
* @returns {Array<{id: string, name: string, templateId: string|null, updatedAt: string}>}
|
|
142
|
-
*/
|
|
143
|
-
export var listProjects = _store.list;
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Delete a project by id. No-op if absent.
|
|
147
|
-
* @param {import('./index.js').StorageProvider} provider
|
|
148
|
-
* @param {string} id
|
|
149
|
-
*/
|
|
150
|
-
export var deleteProject = _store['delete'];
|