ParaShape

Store

Session-global singleton. Four read-only Maps — the source of truth for everything the engine needs before it can run.

import { store } from "@parashape/parametric"

await store.load(bundle)

The Store is the ASYNC layer of the architecture: it scans the RAW model JSON for the engine loader parameters (loadModel/loadFont/loadImage) and pre-loads their assets before the graph ever evaluates — the Store's own job, not a registry hook. The graph itself never does async I/O; a missing asset at evaluate time is a lookup error, never a fetch.

Stores

PropertyTypeDescription
store.modelsMap<string, unknown>Raw model JSONs by ID. Engine reads from here instead of fetching.
store.materialsMap<string, MaterialDefJSON>Platform-native materials by name (host-bridge palette, e.g. SketchUp) — see registerMaterial. Model-declared (createMaterial parameter) materials live in the node graph, not here.
store.fontsMap<string, FontData>Opentype-parsed font objects by name or URL. Must be pre-loaded in Phase 1 (fetch+parse is async) so the text generator can read metrics synchronously in Phase 2.
store.imagesMap<string, string>Image URIs by name or URL.

Fill the stores

MethodDescription
store.load(bundle, { rootId? })Main entry point. Accept { model, deps } bundle, register the model JSONs, scan them for loader parameters (fonts/images/sub-models), then load fonts async. Does not touch materials — register those separately.
store.registerModel(id, json, ns?)Register one raw model JSON directly (also scans it for loader parameters).
store.registerFromModel(json, ns?)Scan a raw JSON for loader parameters (loadFont/loadImage/loadModel) and pre-register their assets; image keys take the optional namespace prefix.
store.registerMaterial(mat)Add one material by name; no-op if that name is already registered.
store.registerMaterials(mats)Bulk-add platform materials (e.g. SketchUp palette).
store.registerFont(name, src)Queue a font URL for loading under a declared name.
store.queueFontUrl(src)Queue a font URL; the loaded font registers under its PostScript name (used by the loadFont scan).
store.registerImage(key, uri)Store an image URI.
store.loadFonts()Fetch + parse all queued fonts in parallel. Called automatically by load().

Read from stores

MethodReturnsDescription
store.getModel(id)unknownRaw model JSON by ID.
store.getMaterial(name)MaterialDefJSON | nullMaterial by name.
store.getFont(key)FontData | nullParsed font by name or URL.
store.getImage(key)string | nullImage URI by key.
store.reset()voidClear all stores — tests or new session.

Two-phase usage

Phase 1 fills the stores (async OK). Phase 2 evaluates — fully synchronous, reads stores directly, no fetching.

// Phase 1 — async
const bundle = await db.fetchWithDeps("modelA")           // server resolves full dep graph flat
await store.load(bundle, { rootId: "modelA" })            // fills models + images + fonts
store.registerMaterials(sketchup.getMaterials())          // platform palette into store.materials

// Phase 2 — sync, reads from stores
const registry = createRegistry(nodeRegistry)
const model = Model.fromJSON(store.getModel("modelA"), { registry, assets: store })
const result = model.evaluate(params)

Platform bridge

A host bridge registers its native palette so a material argument passed as a plain name string (not a node reference) resolves via store.getMaterial() — used internally by argument coercion:

store.registerMaterials(
  sketchup.getMaterials().map(m => ({
    name: m.display_name,
    baseColor: "#" + m.color.to_hex,   // hex string
  }))
)
// Entity can now reference "Oak" even without a `createMaterial` parameter declaring it

Sub-model namespacing

Materials/layers/fonts inside a placed sub-model are namespaced by the placing placeModel node's own key — this happens during model evaluation, not in the Store — so two placements of the same source never collide:

placeModel key "door"  places a sub-model declaring material "Oak"  →  becomes "door:Oak"
entity ref             targets material "Oak"                       →  stamped "door:Oak" by the engine
renderer               looks up "door:Oak"                          →  found ✓

store.registerFromModel(json, ns?) takes the same kind of optional prefix for the assets it scans (images only, today).

ModelBundle

type ModelBundle = {
  model: unknown                    // main model JSON
  deps?: Record<string, unknown>    // id → dep JSON, flat (server resolved graph)
}