@wildwinter/scoperegistry 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,8 +32,12 @@ var PropertyBag = class _PropertyBag {
32
32
  * long-standing contract); a product whose names are case-significant
33
33
  * passes identity. */
34
34
  norm;
35
+ /** The address prefix this bag's rows carry, separator included (`@`,
36
+ * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */
37
+ pathPrefix;
35
38
  constructor(declarations = [], opts) {
36
39
  this.norm = opts?.normalise ?? ((n) => n.toLowerCase());
40
+ this.pathPrefix = opts?.pathPrefix ?? "";
37
41
  this.seed(declarations);
38
42
  }
39
43
  seed(declarations) {
@@ -77,7 +81,7 @@ var PropertyBag = class _PropertyBag {
77
81
  /** Examiner rows: the declared surface only (stray values are storage,
78
82
  * not surface). */
79
83
  rows() {
80
- return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), void 0, name));
84
+ return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), void 0, name, this.pathPrefix));
81
85
  }
82
86
  declarations() {
83
87
  return [...this.decls.values()];
@@ -86,7 +90,7 @@ var PropertyBag = class _PropertyBag {
86
90
  * duplicated, the normalisation policy carried, subscriptions NOT
87
91
  * carried. */
88
92
  clone() {
89
- const c = new _PropertyBag([], { normalise: this.norm });
93
+ const c = new _PropertyBag([], { normalise: this.norm, pathPrefix: this.pathPrefix });
90
94
  c.decls = new Map(this.decls);
91
95
  Object.assign(c.values, structuredClone(this.values));
92
96
  return c;
@@ -109,13 +113,19 @@ var PropertyBag = class _PropertyBag {
109
113
  for (const [k, v] of Object.entries(values)) this.values[this.norm(k)] = v;
110
114
  }
111
115
  };
112
- function rowFor(d, value, writable, name) {
116
+ function rowFor(d, value, writable, name, pathPrefix = "") {
117
+ const rowName = name ?? d.name.toLowerCase();
113
118
  return {
114
- name: name ?? d.name.toLowerCase(),
119
+ name: rowName,
120
+ path: pathPrefix + rowName,
115
121
  type: d.type,
116
122
  value,
117
123
  default: d.default ?? defaultFor(d),
118
124
  ...d.values !== void 0 ? { values: d.values } : {},
125
+ // `stages` was added to the row so an examiner could offer a quality's ladder
126
+ // instead of a free-text box, and then never populated here: every quality row
127
+ // this function built came out without one. Fixed 2026-09-02.
128
+ ...d.stages !== void 0 ? { stages: d.stages } : {},
119
129
  writable: writable ?? d.writable ?? true
120
130
  };
121
131
  }
@@ -127,8 +137,8 @@ var ScopeRegistry = class {
127
137
  * each declaration's `default` (or a type default). Owned scopes are
128
138
  * type-checked (declarations) and serialized by `save`/`load`.
129
139
  */
130
- defineOwned(token, declarations) {
131
- return this.mountOwned(token, new PropertyBag(declarations));
140
+ defineOwned(token, declarations, pathPrefix) {
141
+ return this.mountOwned(token, new PropertyBag(declarations, { pathPrefix: pathPrefix ?? `${token}.` }));
132
142
  }
133
143
  /**
134
144
  * Attach an EXISTING bag as an owned scope - the shared-container move: a
@@ -213,7 +223,13 @@ var ScopeRegistry = class {
213
223
  for (const d of e.decls.values()) {
214
224
  out.push({
215
225
  scope: token,
216
- ...rowFor(d, e.resolver.get(d.name.toLowerCase()), this.foreignWritable(e, d.name.toLowerCase()))
226
+ ...rowFor(
227
+ d,
228
+ e.resolver.get(d.name.toLowerCase()),
229
+ this.foreignWritable(e, d.name.toLowerCase()),
230
+ void 0,
231
+ `${token}.`
232
+ )
217
233
  });
218
234
  }
219
235
  }
@@ -324,6 +340,13 @@ function defaultFor(d) {
324
340
  // A quality starts at the first rung of its ladder.
325
341
  case "quality":
326
342
  return d.stages?.[0] ?? "";
343
+ // Unreachable for a well-typed declaration, and deliberately present anyway: a bundle
344
+ // is DATA, and a hand-edited or newer-than-this-build one can carry a type string the
345
+ // union does not have. Falling off the switch would seed `undefined`, which is not a
346
+ // ScalarValue and travels a long way before it fails. Patterplay's copy of this had the
347
+ // guard and this one did not, which is the drift you only find by removing a duplicate.
348
+ default:
349
+ return false;
327
350
  }
328
351
  }
329
352
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// @wildwinter/scoperegistry - the scope registry / runtime state container that\n// sits on top of @wildwinter/expr.\n//\n// expr is a stateless calculator: given an AST, an EvalContext (the state), and\n// a Dialect, it computes. This package is the *state* layer: it owns the world\n// state as a set of named scopes - each either an **owned** scope (a property\n// bag this registry stores and saves) or a **foreign** scope (host- or\n// other-engine-resolved at runtime, never stored here) - and produces the\n// `EvalContext` (for evaluation) and `ExpressionSchema` (for validation) that\n// expr consumes. Plus the `scopeRegistrySpec` interop format for importing a\n// foreign owner's scope declarations.\n//\n// Design: design/scope-registry.md (in the patter repo). expr never depends on\n// this; this depends one-way on expr.\n// ---------------------------------------------------------------------------\n\nimport type {\n EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver,\n} from \"@wildwinter/expr\";\n\nexport type { EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\n\n// ---------------------------------------------------------------------------\n// Declarations + the scopeRegistrySpec interop format\n// ---------------------------------------------------------------------------\n\n/**\n * A property declaration. `default` is used by an *owned* scope to seed its bag\n * (foreign scopes ignore it - the host owns the value). `writable: false` makes\n * a property read-only; default is read/write. (`type`/`values` feed validation.)\n */\nexport interface ScopeDeclaration {\n name: string;\n type: PropertyType;\n values?: string[]; // for enum / flags\n /** A quality's ordered ladder of stage names (quality.md). */\n stages?: string[];\n default?: ScalarValue; // owned scopes: seed value\n writable?: boolean; // default true\n}\n\n/** One scope in a `scopeRegistrySpec`: a token + (optional) declarations. */\nexport interface ScopeSpec {\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: ScopeDeclaration[];\n}\n\n/**\n * The interop format an owner (Storylet Studio, a host game) exports so another\n * engine can validate references into its scopes. Carried under the well-known\n * `scopeRegistrySpec` JSON key (inside a `.storyworld`, or a standalone file).\n */\nexport interface ScopeRegistrySpec {\n version: number;\n scopes: ScopeSpec[];\n}\n\n/** The spec versions this build understands. */\nexport const SUPPORTED_SPEC_VERSIONS = [1] as const;\n\n/**\n * Extract + validate a `scopeRegistrySpec` from any JSON value (a parsed\n * `.storyworld` bundle, or a vanilla `{ scopeRegistrySpec: ... }` manifest).\n * Returns null when the key is absent (so callers can probe arbitrary files);\n * throws on a malformed or unsupported-version spec.\n */\nexport function readScopeRegistrySpec(source: unknown): ScopeRegistrySpec | null {\n if (!source || typeof source !== \"object\") return null;\n const raw = (source as Record<string, unknown>).scopeRegistrySpec;\n if (raw === undefined) return null;\n if (typeof raw !== \"object\" || raw === null) throw new Error(\"scopeRegistrySpec must be an object\");\n const spec = raw as Record<string, unknown>;\n if (typeof spec.version !== \"number\") throw new Error(\"scopeRegistrySpec.version must be a number\");\n if (!(SUPPORTED_SPEC_VERSIONS as readonly number[]).includes(spec.version)) {\n throw new Error(`unsupported scopeRegistrySpec version ${spec.version} (supported: ${SUPPORTED_SPEC_VERSIONS.join(\", \")})`);\n }\n if (!Array.isArray(spec.scopes)) throw new Error(\"scopeRegistrySpec.scopes must be an array\");\n for (const s of spec.scopes) {\n if (!s || typeof s !== \"object\" || typeof (s as ScopeSpec).token !== \"string\") {\n throw new Error(\"each scopeRegistrySpec scope needs a string token\");\n }\n }\n return spec as unknown as ScopeRegistrySpec;\n}\n\n// ---------------------------------------------------------------------------\n// PropertyBag - the state kernel's unit of state (added 0.2.0; design:\n// storylets-new/design/engine-runtimes.md 3.1). A typed, declared property\n// bag with defaults, the firing rule (engine writes notify subscribers;\n// host writes are silent but always auditable), examiner rows, one\n// sanctioned clone door, and bare-value save/load. Owned registry scopes\n// are bags; products may also hold bag families of their own (per-box,\n// per-scene) and mount the shared ones.\n// ---------------------------------------------------------------------------\n\n/** One property change. `silent` marks a host write (the firing rule: it\n * reaches the audit hook but not subscribers); `reason` is the host's own\n * note for its log. */\nexport interface BagChange {\n name: string;\n prev?: ScalarValue;\n next: ScalarValue;\n silent: boolean;\n reason?: string;\n}\n\n/** One examiner row: what a property examiner/editor needs to render and\n * edit a declared property. */\nexport interface PropertyRow {\n name: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n /** A quality's ordered stage ladder, so an inspector can offer the stages\n * instead of a free-text box. `quality` has been in PropertyType since the\n * ladder landed, and the evaluator compares stages by LADDER POSITION and\n * refuses an unknown one, so free text is not a soft failure: a typo breaks\n * play rather than being corrected. This row is the only thing an examiner\n * sees, so a ladder it cannot carry is a ladder no editor can offer. One\n * consumer forked this whole interface to add the field; the field belongs\n * here, beside the `values` it is the closed-set twin of. */\n stages?: string[];\n writable: boolean;\n}\n\nexport class PropertyBag {\n /** The live values record (stable identity across reseed, so an\n * EvalContext built over it stays valid). Read-path for evaluation;\n * writes go through `set` so the firing rule applies. */\n readonly values: Record<string, ScalarValue> = {};\n private decls = new Map<string, ScopeDeclaration>();\n private readonly subscribers = new Set<(change: BagChange) => void>();\n private readonly auditors = new Set<(change: BagChange) => void>();\n /** Name normalisation policy: lowercase by default (the registry's\n * long-standing contract); a product whose names are case-significant\n * passes identity. */\n private readonly norm: (name: string) => string;\n\n constructor(declarations: ScopeDeclaration[] = [], opts?: { normalise?: (name: string) => string }) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\n this.seed(declarations);\n }\n\n private seed(declarations: ScopeDeclaration[]): void {\n for (const d of declarations) {\n const name = this.norm(d.name);\n this.decls.set(name, d);\n // Cloned so bags seeded from one declaration set never share a\n // mutable default (flags arrays).\n this.values[name] = structuredClone(d.default ?? defaultFor(d));\n }\n }\n\n get(name: string): ScalarValue | undefined {\n return this.values[this.norm(name)];\n }\n\n /** Write a property. Engine writes (the default) notify subscribers;\n * pass `silent: true` for a host write, which reaches only the audit\n * hook. Throws on a read-only property. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string }): BagChange {\n const n = this.norm(name);\n if (this.decls.get(n)?.writable === false) throw new Error(`'${name}' is read-only`);\n const change: BagChange = {\n name: n,\n prev: this.values[n],\n next: value,\n silent: opts?.silent ?? false,\n reason: opts?.reason,\n };\n this.values[n] = value;\n for (const audit of this.auditors) audit(change);\n if (!change.silent) for (const fn of this.subscribers) fn(change);\n return change;\n }\n\n /** Notified of engine (non-silent) writes. Returns the unsubscribe. */\n subscribe(fn: (change: BagChange) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n /** Notified of EVERY write, silent or not. Returns the unsubscribe. */\n onAudit(fn: (change: BagChange) => void): () => void {\n this.auditors.add(fn);\n return () => this.auditors.delete(fn);\n }\n\n /** Examiner rows: the declared surface only (stray values are storage,\n * not surface). */\n rows(): PropertyRow[] {\n return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), undefined, name));\n }\n\n declarations(): ScopeDeclaration[] {\n return [...this.decls.values()];\n }\n\n /** The one sanctioned copy door: values deep-copied, declarations\n * duplicated, the normalisation policy carried, subscriptions NOT\n * carried. */\n clone(): PropertyBag {\n const c = new PropertyBag([], { normalise: this.norm });\n c.decls = new Map(this.decls);\n Object.assign(c.values, structuredClone(this.values));\n return c;\n }\n\n /** Clear and re-seed from new declarations, in place (the values record\n * keeps its identity, so contexts built over it stay valid). */\n reseed(declarations: ScopeDeclaration[]): void {\n for (const k of Object.keys(this.values)) delete this.values[k];\n this.decls.clear();\n this.seed(declarations);\n }\n\n /** Bare values, ready to embed in a product's save. */\n save(): Record<string, ScalarValue> {\n return structuredClone(this.values);\n }\n\n /** Lay saved values over the current ones (call after a fresh seed:\n * orphans land as strays, new declarations keep their defaults; the\n * product decides whether to prune). Does not fire events. */\n load(values: Record<string, ScalarValue>): void {\n for (const [k, v] of Object.entries(values)) this.values[this.norm(k)] = v;\n }\n}\n\nfunction rowFor(d: ScopeDeclaration, value: ScalarValue | undefined, writable?: boolean, name?: string): PropertyRow {\n return {\n name: name ?? d.name.toLowerCase(),\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n writable: writable ?? d.writable ?? true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The registry / state container\n// ---------------------------------------------------------------------------\n\ninterface OwnedScope {\n kind: \"owned\";\n bag: PropertyBag;\n}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n}\ntype Entry = OwnedScope | ForeignScope;\n\n/** The versioned owned-state fragment both product save envelopes embed\n * (design/engine-runtimes.md 3.1: one serialisation shape for bags). */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\n\n /**\n * Register a scope this registry **owns and stores**. Its bag is seeded from\n * each declaration's `default` (or a type default). Owned scopes are\n * type-checked (declarations) and serialized by `save`/`load`.\n */\n defineOwned(token: string, declarations: ScopeDeclaration[]): this {\n return this.mountOwned(token, new PropertyBag(declarations));\n }\n\n /**\n * Attach an EXISTING bag as an owned scope - the shared-container move: a\n * host (or the other product) holds the bag; this registry reads, writes\n * and lists it like its own, but the holder saves it.\n */\n mountOwned(token: string, bag: PropertyBag): this {\n this.assertFree(token);\n this.scopes.set(token, { kind: \"owned\", bag });\n return this;\n }\n\n /** An owned scope's bag (subscribe, audit, rows live there). */\n ownedBag(token: string): PropertyBag {\n const e = this.scopes.get(token);\n if (!e || e.kind !== \"owned\") throw new Error(`'@${token}' is not an owned scope`);\n return e.bag;\n }\n\n /**\n * Re-initialise an existing **owned** scope's bag from new declarations,\n * clearing its current values. For scope-local state that resets on a context\n * change (e.g. entering a new scene / site / deck) without disturbing other\n * scopes. Mutates the bag in place, so an `EvalContext` already built from this\n * registry stays valid.\n */\n reseedOwned(token: string, declarations: ScopeDeclaration[]): this {\n this.ownedBag(token).reseed(declarations);\n return this;\n }\n\n /**\n * Register a **foreign** scope backed by a host `{ get, set? }` resolver. The\n * values live in the host/other engine and are never stored or saved here.\n * `declarations` (optional, e.g. imported from a `scopeRegistrySpec`) are used\n * only for validation; omit them for an opaque scope.\n */\n defineForeign(\n token: string,\n resolver: ScopeResolver,\n declarations: ScopeDeclaration[] = [],\n scopeWritable = true,\n ): this {\n this.assertFree(token);\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(d.name.toLowerCase(), d);\n this.scopes.set(token, { kind: \"foreign\", resolver, decls, scopeWritable });\n return this;\n }\n\n has(token: string): boolean {\n return this.scopes.has(token);\n }\n\n /** Read a property; undefined if the scope or property is not present. */\n get(scope: string, name: string): ScalarValue | undefined {\n const e = this.scopes.get(scope);\n if (!e) return undefined;\n return e.kind === \"owned\" ? e.bag.get(name) : e.resolver.get(name.toLowerCase());\n }\n\n /** Write a property (an ENGINE write: the bag's subscribers fire; use\n * the bag directly for silent host writes). Throws on an unknown or\n * read-only scope/property. */\n set(scope: string, name: string, value: ScalarValue): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n try {\n e.bag.set(name, value);\n } catch {\n throw new Error(`'@${scope}.${name}' is read-only`);\n }\n return;\n }\n const n = name.toLowerCase();\n if (!this.foreignWritable(e, n)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.resolver.set!(n, value);\n }\n\n private foreignWritable(e: ForeignScope, name: string): boolean {\n if (!e.resolver.set) return false; // no setter => read-only scope\n return e.decls.get(name)?.writable ?? e.scopeWritable;\n }\n\n /** Examiner rows across every scope with a declared surface: owned bags\n * first, then declared foreign scopes (values read through, writability\n * reflecting the resolver). Opaque foreign scopes are not listed. */\n listProperties(): ({ scope: string } & PropertyRow)[] {\n const out: ({ scope: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...row });\n } else {\n for (const d of e.decls.values()) {\n out.push({\n scope: token,\n ...rowFor(d, e.resolver.get(d.name.toLowerCase()), this.foreignWritable(e, d.name.toLowerCase())),\n });\n }\n }\n }\n return out;\n }\n\n /**\n * Build the `EvalContext` expr's `evaluate` consumes: owned scopes as static\n * bags, foreign scopes as their resolvers. `host` carries dialect-function\n * callbacks (PRNG, tag lookups) and is passed through untouched.\n */\n toEvalContext(host?: Record<string, unknown>): EvalContext {\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of this.scopes) {\n scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n }\n // The quality channel (quality.md): declared here once, so a host that\n // registers a quality gets ordering comparisons and advance() with no\n // further wiring. Only added when a quality exists, so contexts stay\n // byte-identical for products that declare none.\n const qualities = this.qualityLadders();\n return qualities.size === 0 ? { scopes, host } : {\n scopes, host,\n qualities: (scope, name) => qualities.get(scope)?.get(name.toLowerCase()),\n };\n }\n\n /** Every quality declaration's ladder, keyed scope token then name. */\n private qualityLadders(): Map<string, Map<string, readonly string[]>> {\n const out = new Map<string, Map<string, readonly string[]>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n for (const d of decls) {\n if (d.type !== \"quality\" || d.stages === undefined) continue;\n let m = out.get(token);\n if (!m) { m = new Map(); out.set(token, m); }\n m.set(d.name.toLowerCase(), d.stages);\n }\n }\n return out;\n }\n\n /**\n * Build the `ExpressionSchema` expr's validator consumes. Scopes with no\n * declarations are **omitted** (opaque - references into them are not flagged);\n * declared scopes contribute their property types for validation.\n */\n toSchema(): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const d of decls) m.set(d.name.toLowerCase(), {\n type: d.type, enumValues: d.values,\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n });\n properties.set(token, m);\n }\n return { properties };\n }\n\n /** Serialize **owned** scopes only (foreign scopes are host-owned,\n * host-saved), as bare bags - the 0.1.x shape, kept stable so existing\n * consumers' save formats are untouched. A product embedding the\n * versioned cross-product shape uses `saveFragment`. */\n save(): Record<string, Record<string, ScalarValue>> {\n const out: Record<string, Record<string, ScalarValue>> = {};\n for (const [token, e] of this.scopes) if (e.kind === \"owned\") out[token] = e.bag.save();\n return out;\n }\n\n /** Restore owned-scope values from a `save` blob. Unknown/foreign scopes\n * are ignored. */\n load(blob: Record<string, Record<string, ScalarValue>>): void {\n for (const [token, vals] of Object.entries(blob)) {\n const e = this.scopes.get(token);\n if (e?.kind === \"owned\") e.bag.load(vals);\n }\n }\n\n /** The versioned owned-state fragment (the one serialisation shape both\n * product families' save envelopes embed when they adopt the kernel;\n * design/engine-runtimes.md 3.1). `save()` wrapped with a version stamp. */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /** Restore from a versioned fragment; an unsupported version throws. */\n loadFragment(fragment: OwnedStateFragment): void {\n if (fragment.version !== SAVE_FRAGMENT_VERSION) {\n throw new Error(`unsupported owned-state fragment version ${fragment.version} (supported: ${SAVE_FRAGMENT_VERSION})`);\n }\n this.load(fragment.scopes);\n }\n\n private assertFree(token: string): void {\n if (this.scopes.has(token)) throw new Error(`scope '@${token}' is already registered`);\n }\n}\n\n/** The seed value for a declared property: its own `default`, else the type's.\n *\n * Exported because it was being written again wherever a declaration needed seeding, and a\n * copy of a defaults table is a copy that stops agreeing. Patterplay carried three of them in\n * one file, for its shared decls, its host-scope decls and its scene decls - three declaration\n * TYPES, one behaviour, and nothing to notice if a case drifted. The parameter is structurally\n * typed for exactly that reason: anything with `type` and the optional `default` / `values` /\n * `stages` fits, whatever the caller calls its declaration.\n *\n * A quality seeds at the FIRST rung of its ladder: the ladder's start is the story's start. */\nexport function defaultFor(d: Pick<ScopeDeclaration, \"type\" | \"default\" | \"values\" | \"stages\">): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"enum\": return d.values?.[0] ?? \"\";\n case \"flags\": return [];\n // A quality starts at the first rung of its ladder.\n case \"quality\": return d.stages?.[0] ?? \"\";\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// @wildwinter/scoperegistry - the scope registry / runtime state container that\n// sits on top of @wildwinter/expr.\n//\n// expr is a stateless calculator: given an AST, an EvalContext (the state), and\n// a Dialect, it computes. This package is the *state* layer: it owns the world\n// state as a set of named scopes - each either an **owned** scope (a property\n// bag this registry stores and saves) or a **foreign** scope (host- or\n// other-engine-resolved at runtime, never stored here) - and produces the\n// `EvalContext` (for evaluation) and `ExpressionSchema` (for validation) that\n// expr consumes. Plus the `scopeRegistrySpec` interop format for importing a\n// foreign owner's scope declarations.\n//\n// Design: design/scope-registry.md (in the patter repo). expr never depends on\n// this; this depends one-way on expr.\n// ---------------------------------------------------------------------------\n\nimport type {\n EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver,\n} from \"@wildwinter/expr\";\n\nexport type { EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\n\n// ---------------------------------------------------------------------------\n// Declarations + the scopeRegistrySpec interop format\n// ---------------------------------------------------------------------------\n\n/**\n * A property declaration. `default` is used by an *owned* scope to seed its bag\n * (foreign scopes ignore it - the host owns the value). `writable: false` makes\n * a property read-only; default is read/write. (`type`/`values` feed validation.)\n */\nexport interface ScopeDeclaration {\n name: string;\n type: PropertyType;\n values?: string[]; // for enum / flags\n /** A quality's ordered ladder of stage names (quality.md). */\n stages?: string[];\n default?: ScalarValue; // owned scopes: seed value\n writable?: boolean; // default true\n}\n\n/** One scope in a `scopeRegistrySpec`: a token + (optional) declarations. */\nexport interface ScopeSpec {\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: ScopeDeclaration[];\n}\n\n/**\n * The interop format an owner (Storylet Studio, a host game) exports so another\n * engine can validate references into its scopes. Carried under the well-known\n * `scopeRegistrySpec` JSON key (inside a `.storyworld`, or a standalone file).\n */\nexport interface ScopeRegistrySpec {\n version: number;\n scopes: ScopeSpec[];\n}\n\n/** The spec versions this build understands. */\nexport const SUPPORTED_SPEC_VERSIONS = [1] as const;\n\n/**\n * Extract + validate a `scopeRegistrySpec` from any JSON value (a parsed\n * `.storyworld` bundle, or a vanilla `{ scopeRegistrySpec: ... }` manifest).\n * Returns null when the key is absent (so callers can probe arbitrary files);\n * throws on a malformed or unsupported-version spec.\n */\nexport function readScopeRegistrySpec(source: unknown): ScopeRegistrySpec | null {\n if (!source || typeof source !== \"object\") return null;\n const raw = (source as Record<string, unknown>).scopeRegistrySpec;\n if (raw === undefined) return null;\n if (typeof raw !== \"object\" || raw === null) throw new Error(\"scopeRegistrySpec must be an object\");\n const spec = raw as Record<string, unknown>;\n if (typeof spec.version !== \"number\") throw new Error(\"scopeRegistrySpec.version must be a number\");\n if (!(SUPPORTED_SPEC_VERSIONS as readonly number[]).includes(spec.version)) {\n throw new Error(`unsupported scopeRegistrySpec version ${spec.version} (supported: ${SUPPORTED_SPEC_VERSIONS.join(\", \")})`);\n }\n if (!Array.isArray(spec.scopes)) throw new Error(\"scopeRegistrySpec.scopes must be an array\");\n for (const s of spec.scopes) {\n if (!s || typeof s !== \"object\" || typeof (s as ScopeSpec).token !== \"string\") {\n throw new Error(\"each scopeRegistrySpec scope needs a string token\");\n }\n }\n return spec as unknown as ScopeRegistrySpec;\n}\n\n// ---------------------------------------------------------------------------\n// PropertyBag - the state kernel's unit of state (added 0.2.0; design:\n// storylets-new/design/engine-runtimes.md 3.1). A typed, declared property\n// bag with defaults, the firing rule (engine writes notify subscribers;\n// host writes are silent but always auditable), examiner rows, one\n// sanctioned clone door, and bare-value save/load. Owned registry scopes\n// are bags; products may also hold bag families of their own (per-box,\n// per-scene) and mount the shared ones.\n// ---------------------------------------------------------------------------\n\n/** One property change. `silent` marks a host write (the firing rule: it\n * reaches the audit hook but not subscribers); `reason` is the host's own\n * note for its log. */\nexport interface BagChange {\n name: string;\n prev?: ScalarValue;\n next: ScalarValue;\n silent: boolean;\n reason?: string;\n}\n\n/** One examiner row: what a property examiner/editor needs to render and\n * edit a declared property. */\nexport interface PropertyRow {\n name: string;\n /** The address this property answers to - what getProperty/setProperty take.\n * A bag composes it from its own `pathPrefix` and the name, so a row is\n * self-describing: an examiner can render and write a row without being told\n * separately where it came from.\n *\n * The PREFIX CARRIES ITS OWN SEPARATOR rather than the bag assuming a dot,\n * because a prefix is not always a bare scope token: the Storylet Engine\n * addresses a deck's properties as `deck.<id>.name`, so the prefix is already\n * a dotted path. Patterplay's `@patter.gold` and `@scene.mood` are the plain\n * case. (`@gold` also resolves - splitRef defaults an unqualified name to the\n * patter scope - but it is the shorthand, not the address a row reports.)\n *\n * With no prefix this is just the name. Both families forked this interface\n * to add exactly this field - once per runtime - which is the same reason\n * `stages` is here. */\n path: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n /** A quality's ordered stage ladder, so an inspector can offer the stages\n * instead of a free-text box. `quality` has been in PropertyType since the\n * ladder landed, and the evaluator compares stages by LADDER POSITION and\n * refuses an unknown one, so free text is not a soft failure: a typo breaks\n * play rather than being corrected. This row is the only thing an examiner\n * sees, so a ladder it cannot carry is a ladder no editor can offer. One\n * consumer forked this whole interface to add the field; the field belongs\n * here, beside the `values` it is the closed-set twin of. */\n stages?: string[];\n writable: boolean;\n}\n\nexport class PropertyBag {\n /** The live values record (stable identity across reseed, so an\n * EvalContext built over it stays valid). Read-path for evaluation;\n * writes go through `set` so the firing rule applies. */\n readonly values: Record<string, ScalarValue> = {};\n private decls = new Map<string, ScopeDeclaration>();\n private readonly subscribers = new Set<(change: BagChange) => void>();\n private readonly auditors = new Set<(change: BagChange) => void>();\n /** Name normalisation policy: lowercase by default (the registry's\n * long-standing contract); a product whose names are case-significant\n * passes identity. */\n private readonly norm: (name: string) => string;\n\n /** The address prefix this bag's rows carry, separator included (`@`,\n * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */\n readonly pathPrefix: string;\n\n constructor(\n declarations: ScopeDeclaration[] = [],\n opts?: { normalise?: (name: string) => string; pathPrefix?: string },\n ) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\n this.pathPrefix = opts?.pathPrefix ?? \"\";\n this.seed(declarations);\n }\n\n private seed(declarations: ScopeDeclaration[]): void {\n for (const d of declarations) {\n const name = this.norm(d.name);\n this.decls.set(name, d);\n // Cloned so bags seeded from one declaration set never share a\n // mutable default (flags arrays).\n this.values[name] = structuredClone(d.default ?? defaultFor(d));\n }\n }\n\n get(name: string): ScalarValue | undefined {\n return this.values[this.norm(name)];\n }\n\n /** Write a property. Engine writes (the default) notify subscribers;\n * pass `silent: true` for a host write, which reaches only the audit\n * hook. Throws on a read-only property. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string }): BagChange {\n const n = this.norm(name);\n if (this.decls.get(n)?.writable === false) throw new Error(`'${name}' is read-only`);\n const change: BagChange = {\n name: n,\n prev: this.values[n],\n next: value,\n silent: opts?.silent ?? false,\n reason: opts?.reason,\n };\n this.values[n] = value;\n for (const audit of this.auditors) audit(change);\n if (!change.silent) for (const fn of this.subscribers) fn(change);\n return change;\n }\n\n /** Notified of engine (non-silent) writes. Returns the unsubscribe. */\n subscribe(fn: (change: BagChange) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n /** Notified of EVERY write, silent or not. Returns the unsubscribe. */\n onAudit(fn: (change: BagChange) => void): () => void {\n this.auditors.add(fn);\n return () => this.auditors.delete(fn);\n }\n\n /** Examiner rows: the declared surface only (stray values are storage,\n * not surface). */\n rows(): PropertyRow[] {\n return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), undefined, name, this.pathPrefix));\n }\n\n declarations(): ScopeDeclaration[] {\n return [...this.decls.values()];\n }\n\n /** The one sanctioned copy door: values deep-copied, declarations\n * duplicated, the normalisation policy carried, subscriptions NOT\n * carried. */\n clone(): PropertyBag {\n const c = new PropertyBag([], { normalise: this.norm, pathPrefix: this.pathPrefix });\n c.decls = new Map(this.decls);\n Object.assign(c.values, structuredClone(this.values));\n return c;\n }\n\n /** Clear and re-seed from new declarations, in place (the values record\n * keeps its identity, so contexts built over it stay valid). */\n reseed(declarations: ScopeDeclaration[]): void {\n for (const k of Object.keys(this.values)) delete this.values[k];\n this.decls.clear();\n this.seed(declarations);\n }\n\n /** Bare values, ready to embed in a product's save. */\n save(): Record<string, ScalarValue> {\n return structuredClone(this.values);\n }\n\n /** Lay saved values over the current ones (call after a fresh seed:\n * orphans land as strays, new declarations keep their defaults; the\n * product decides whether to prune). Does not fire events. */\n load(values: Record<string, ScalarValue>): void {\n for (const [k, v] of Object.entries(values)) this.values[this.norm(k)] = v;\n }\n}\n\nfunction rowFor(\n d: ScopeDeclaration,\n value: ScalarValue | undefined,\n writable?: boolean,\n name?: string,\n pathPrefix = \"\",\n): PropertyRow {\n const rowName = name ?? d.name.toLowerCase();\n return {\n name: rowName,\n path: pathPrefix + rowName,\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n // `stages` was added to the row so an examiner could offer a quality's ladder\n // instead of a free-text box, and then never populated here: every quality row\n // this function built came out without one. Fixed 2026-09-02.\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n writable: writable ?? d.writable ?? true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The registry / state container\n// ---------------------------------------------------------------------------\n\ninterface OwnedScope {\n kind: \"owned\";\n bag: PropertyBag;\n}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n}\ntype Entry = OwnedScope | ForeignScope;\n\n/** The versioned owned-state fragment both product save envelopes embed\n * (design/engine-runtimes.md 3.1: one serialisation shape for bags). */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\n\n /**\n * Register a scope this registry **owns and stores**. Its bag is seeded from\n * each declaration's `default` (or a type default). Owned scopes are\n * type-checked (declarations) and serialized by `save`/`load`.\n */\n defineOwned(token: string, declarations: ScopeDeclaration[], pathPrefix?: string): this {\n // The scope knows its own token, so its rows can address themselves: `world.hp`.\n // The ADDRESS GRAMMAR is the product's, though, not the registry's - Patterplay\n // writes `@patter.gold` where the Storylet Engine writes `world.gold` - so a\n // caller may say how its addresses look. A bag MOUNTED here keeps whatever prefix\n // its holder gave it: the holder owns the addressing.\n return this.mountOwned(token, new PropertyBag(declarations, { pathPrefix: pathPrefix ?? `${token}.` }));\n }\n\n /**\n * Attach an EXISTING bag as an owned scope - the shared-container move: a\n * host (or the other product) holds the bag; this registry reads, writes\n * and lists it like its own, but the holder saves it.\n */\n mountOwned(token: string, bag: PropertyBag): this {\n this.assertFree(token);\n this.scopes.set(token, { kind: \"owned\", bag });\n return this;\n }\n\n /** An owned scope's bag (subscribe, audit, rows live there). */\n ownedBag(token: string): PropertyBag {\n const e = this.scopes.get(token);\n if (!e || e.kind !== \"owned\") throw new Error(`'@${token}' is not an owned scope`);\n return e.bag;\n }\n\n /**\n * Re-initialise an existing **owned** scope's bag from new declarations,\n * clearing its current values. For scope-local state that resets on a context\n * change (e.g. entering a new scene / site / deck) without disturbing other\n * scopes. Mutates the bag in place, so an `EvalContext` already built from this\n * registry stays valid.\n */\n reseedOwned(token: string, declarations: ScopeDeclaration[]): this {\n this.ownedBag(token).reseed(declarations);\n return this;\n }\n\n /**\n * Register a **foreign** scope backed by a host `{ get, set? }` resolver. The\n * values live in the host/other engine and are never stored or saved here.\n * `declarations` (optional, e.g. imported from a `scopeRegistrySpec`) are used\n * only for validation; omit them for an opaque scope.\n */\n defineForeign(\n token: string,\n resolver: ScopeResolver,\n declarations: ScopeDeclaration[] = [],\n scopeWritable = true,\n ): this {\n this.assertFree(token);\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(d.name.toLowerCase(), d);\n this.scopes.set(token, { kind: \"foreign\", resolver, decls, scopeWritable });\n return this;\n }\n\n has(token: string): boolean {\n return this.scopes.has(token);\n }\n\n /** Read a property; undefined if the scope or property is not present. */\n get(scope: string, name: string): ScalarValue | undefined {\n const e = this.scopes.get(scope);\n if (!e) return undefined;\n return e.kind === \"owned\" ? e.bag.get(name) : e.resolver.get(name.toLowerCase());\n }\n\n /** Write a property (an ENGINE write: the bag's subscribers fire; use\n * the bag directly for silent host writes). Throws on an unknown or\n * read-only scope/property. */\n set(scope: string, name: string, value: ScalarValue): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n try {\n e.bag.set(name, value);\n } catch {\n throw new Error(`'@${scope}.${name}' is read-only`);\n }\n return;\n }\n const n = name.toLowerCase();\n if (!this.foreignWritable(e, n)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.resolver.set!(n, value);\n }\n\n private foreignWritable(e: ForeignScope, name: string): boolean {\n if (!e.resolver.set) return false; // no setter => read-only scope\n return e.decls.get(name)?.writable ?? e.scopeWritable;\n }\n\n /** Examiner rows across every scope with a declared surface: owned bags\n * first, then declared foreign scopes (values read through, writability\n * reflecting the resolver). Opaque foreign scopes are not listed. */\n listProperties(): ({ scope: string } & PropertyRow)[] {\n const out: ({ scope: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...row });\n } else {\n for (const d of e.decls.values()) {\n out.push({\n scope: token,\n ...rowFor(d, e.resolver.get(d.name.toLowerCase()), this.foreignWritable(e, d.name.toLowerCase()),\n undefined, `${token}.`),\n });\n }\n }\n }\n return out;\n }\n\n /**\n * Build the `EvalContext` expr's `evaluate` consumes: owned scopes as static\n * bags, foreign scopes as their resolvers. `host` carries dialect-function\n * callbacks (PRNG, tag lookups) and is passed through untouched.\n */\n toEvalContext(host?: Record<string, unknown>): EvalContext {\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of this.scopes) {\n scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n }\n // The quality channel (quality.md): declared here once, so a host that\n // registers a quality gets ordering comparisons and advance() with no\n // further wiring. Only added when a quality exists, so contexts stay\n // byte-identical for products that declare none.\n const qualities = this.qualityLadders();\n return qualities.size === 0 ? { scopes, host } : {\n scopes, host,\n qualities: (scope, name) => qualities.get(scope)?.get(name.toLowerCase()),\n };\n }\n\n /** Every quality declaration's ladder, keyed scope token then name. */\n private qualityLadders(): Map<string, Map<string, readonly string[]>> {\n const out = new Map<string, Map<string, readonly string[]>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n for (const d of decls) {\n if (d.type !== \"quality\" || d.stages === undefined) continue;\n let m = out.get(token);\n if (!m) { m = new Map(); out.set(token, m); }\n m.set(d.name.toLowerCase(), d.stages);\n }\n }\n return out;\n }\n\n /**\n * Build the `ExpressionSchema` expr's validator consumes. Scopes with no\n * declarations are **omitted** (opaque - references into them are not flagged);\n * declared scopes contribute their property types for validation.\n */\n toSchema(): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const d of decls) m.set(d.name.toLowerCase(), {\n type: d.type, enumValues: d.values,\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n });\n properties.set(token, m);\n }\n return { properties };\n }\n\n /** Serialize **owned** scopes only (foreign scopes are host-owned,\n * host-saved), as bare bags - the 0.1.x shape, kept stable so existing\n * consumers' save formats are untouched. A product embedding the\n * versioned cross-product shape uses `saveFragment`. */\n save(): Record<string, Record<string, ScalarValue>> {\n const out: Record<string, Record<string, ScalarValue>> = {};\n for (const [token, e] of this.scopes) if (e.kind === \"owned\") out[token] = e.bag.save();\n return out;\n }\n\n /** Restore owned-scope values from a `save` blob. Unknown/foreign scopes\n * are ignored. */\n load(blob: Record<string, Record<string, ScalarValue>>): void {\n for (const [token, vals] of Object.entries(blob)) {\n const e = this.scopes.get(token);\n if (e?.kind === \"owned\") e.bag.load(vals);\n }\n }\n\n /** The versioned owned-state fragment (the one serialisation shape both\n * product families' save envelopes embed when they adopt the kernel;\n * design/engine-runtimes.md 3.1). `save()` wrapped with a version stamp. */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /** Restore from a versioned fragment; an unsupported version throws. */\n loadFragment(fragment: OwnedStateFragment): void {\n if (fragment.version !== SAVE_FRAGMENT_VERSION) {\n throw new Error(`unsupported owned-state fragment version ${fragment.version} (supported: ${SAVE_FRAGMENT_VERSION})`);\n }\n this.load(fragment.scopes);\n }\n\n private assertFree(token: string): void {\n if (this.scopes.has(token)) throw new Error(`scope '@${token}' is already registered`);\n }\n}\n\n/** The seed value for a declared property: its own `default`, else the type's.\n *\n * Exported because it was being written again wherever a declaration needed seeding, and a\n * copy of a defaults table is a copy that stops agreeing. Patterplay carried three of them in\n * one file, for its shared decls, its host-scope decls and its scene decls - three declaration\n * TYPES, one behaviour, and nothing to notice if a case drifted. The parameter is structurally\n * typed for exactly that reason: anything with `type` and the optional `default` / `values` /\n * `stages` fits, whatever the caller calls its declaration.\n *\n * A quality seeds at the FIRST rung of its ladder: the ladder's start is the story's start. */\nexport function defaultFor(d: Pick<ScopeDeclaration, \"type\" | \"default\" | \"values\" | \"stages\">): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"enum\": return d.values?.[0] ?? \"\";\n case \"flags\": return [];\n // A quality starts at the first rung of its ladder.\n case \"quality\": return d.stages?.[0] ?? \"\";\n // Unreachable for a well-typed declaration, and deliberately present anyway: a bundle\n // is DATA, and a hand-edited or newer-than-this-build one can carry a type string the\n // union does not have. Falling off the switch would seed `undefined`, which is not a\n // ScalarValue and travels a long way before it fails. Patterplay's copy of this had the\n // guard and this one did not, which is the drift you only find by removing a duplicate.\n default: return false;\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -55,6 +55,22 @@ interface BagChange {
55
55
  * edit a declared property. */
56
56
  interface PropertyRow {
57
57
  name: string;
58
+ /** The address this property answers to - what getProperty/setProperty take.
59
+ * A bag composes it from its own `pathPrefix` and the name, so a row is
60
+ * self-describing: an examiner can render and write a row without being told
61
+ * separately where it came from.
62
+ *
63
+ * The PREFIX CARRIES ITS OWN SEPARATOR rather than the bag assuming a dot,
64
+ * because a prefix is not always a bare scope token: the Storylet Engine
65
+ * addresses a deck's properties as `deck.<id>.name`, so the prefix is already
66
+ * a dotted path. Patterplay's `@patter.gold` and `@scene.mood` are the plain
67
+ * case. (`@gold` also resolves - splitRef defaults an unqualified name to the
68
+ * patter scope - but it is the shorthand, not the address a row reports.)
69
+ *
70
+ * With no prefix this is just the name. Both families forked this interface
71
+ * to add exactly this field - once per runtime - which is the same reason
72
+ * `stages` is here. */
73
+ path: string;
58
74
  type: PropertyType;
59
75
  value: ScalarValue | undefined;
60
76
  default: ScalarValue;
@@ -82,8 +98,12 @@ declare class PropertyBag {
82
98
  * long-standing contract); a product whose names are case-significant
83
99
  * passes identity. */
84
100
  private readonly norm;
101
+ /** The address prefix this bag's rows carry, separator included (`@`,
102
+ * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */
103
+ readonly pathPrefix: string;
85
104
  constructor(declarations?: ScopeDeclaration[], opts?: {
86
105
  normalise?: (name: string) => string;
106
+ pathPrefix?: string;
87
107
  });
88
108
  private seed;
89
109
  get(name: string): ScalarValue | undefined;
@@ -130,7 +150,7 @@ declare class ScopeRegistry {
130
150
  * each declaration's `default` (or a type default). Owned scopes are
131
151
  * type-checked (declarations) and serialized by `save`/`load`.
132
152
  */
133
- defineOwned(token: string, declarations: ScopeDeclaration[]): this;
153
+ defineOwned(token: string, declarations: ScopeDeclaration[], pathPrefix?: string): this;
134
154
  /**
135
155
  * Attach an EXISTING bag as an owned scope - the shared-container move: a
136
156
  * host (or the other product) holds the bag; this registry reads, writes
package/dist/index.d.ts CHANGED
@@ -55,6 +55,22 @@ interface BagChange {
55
55
  * edit a declared property. */
56
56
  interface PropertyRow {
57
57
  name: string;
58
+ /** The address this property answers to - what getProperty/setProperty take.
59
+ * A bag composes it from its own `pathPrefix` and the name, so a row is
60
+ * self-describing: an examiner can render and write a row without being told
61
+ * separately where it came from.
62
+ *
63
+ * The PREFIX CARRIES ITS OWN SEPARATOR rather than the bag assuming a dot,
64
+ * because a prefix is not always a bare scope token: the Storylet Engine
65
+ * addresses a deck's properties as `deck.<id>.name`, so the prefix is already
66
+ * a dotted path. Patterplay's `@patter.gold` and `@scene.mood` are the plain
67
+ * case. (`@gold` also resolves - splitRef defaults an unqualified name to the
68
+ * patter scope - but it is the shorthand, not the address a row reports.)
69
+ *
70
+ * With no prefix this is just the name. Both families forked this interface
71
+ * to add exactly this field - once per runtime - which is the same reason
72
+ * `stages` is here. */
73
+ path: string;
58
74
  type: PropertyType;
59
75
  value: ScalarValue | undefined;
60
76
  default: ScalarValue;
@@ -82,8 +98,12 @@ declare class PropertyBag {
82
98
  * long-standing contract); a product whose names are case-significant
83
99
  * passes identity. */
84
100
  private readonly norm;
101
+ /** The address prefix this bag's rows carry, separator included (`@`,
102
+ * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */
103
+ readonly pathPrefix: string;
85
104
  constructor(declarations?: ScopeDeclaration[], opts?: {
86
105
  normalise?: (name: string) => string;
106
+ pathPrefix?: string;
87
107
  });
88
108
  private seed;
89
109
  get(name: string): ScalarValue | undefined;
@@ -130,7 +150,7 @@ declare class ScopeRegistry {
130
150
  * each declaration's `default` (or a type default). Owned scopes are
131
151
  * type-checked (declarations) and serialized by `save`/`load`.
132
152
  */
133
- defineOwned(token: string, declarations: ScopeDeclaration[]): this;
153
+ defineOwned(token: string, declarations: ScopeDeclaration[], pathPrefix?: string): this;
134
154
  /**
135
155
  * Attach an EXISTING bag as an owned scope - the shared-container move: a
136
156
  * host (or the other product) holds the bag; this registry reads, writes
package/dist/index.js CHANGED
@@ -30,8 +30,12 @@ var PropertyBag = class _PropertyBag {
30
30
  * long-standing contract); a product whose names are case-significant
31
31
  * passes identity. */
32
32
  norm;
33
+ /** The address prefix this bag's rows carry, separator included (`@`,
34
+ * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */
35
+ pathPrefix;
33
36
  constructor(declarations = [], opts) {
34
37
  this.norm = opts?.normalise ?? ((n) => n.toLowerCase());
38
+ this.pathPrefix = opts?.pathPrefix ?? "";
35
39
  this.seed(declarations);
36
40
  }
37
41
  seed(declarations) {
@@ -75,7 +79,7 @@ var PropertyBag = class _PropertyBag {
75
79
  /** Examiner rows: the declared surface only (stray values are storage,
76
80
  * not surface). */
77
81
  rows() {
78
- return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), void 0, name));
82
+ return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), void 0, name, this.pathPrefix));
79
83
  }
80
84
  declarations() {
81
85
  return [...this.decls.values()];
@@ -84,7 +88,7 @@ var PropertyBag = class _PropertyBag {
84
88
  * duplicated, the normalisation policy carried, subscriptions NOT
85
89
  * carried. */
86
90
  clone() {
87
- const c = new _PropertyBag([], { normalise: this.norm });
91
+ const c = new _PropertyBag([], { normalise: this.norm, pathPrefix: this.pathPrefix });
88
92
  c.decls = new Map(this.decls);
89
93
  Object.assign(c.values, structuredClone(this.values));
90
94
  return c;
@@ -107,13 +111,19 @@ var PropertyBag = class _PropertyBag {
107
111
  for (const [k, v] of Object.entries(values)) this.values[this.norm(k)] = v;
108
112
  }
109
113
  };
110
- function rowFor(d, value, writable, name) {
114
+ function rowFor(d, value, writable, name, pathPrefix = "") {
115
+ const rowName = name ?? d.name.toLowerCase();
111
116
  return {
112
- name: name ?? d.name.toLowerCase(),
117
+ name: rowName,
118
+ path: pathPrefix + rowName,
113
119
  type: d.type,
114
120
  value,
115
121
  default: d.default ?? defaultFor(d),
116
122
  ...d.values !== void 0 ? { values: d.values } : {},
123
+ // `stages` was added to the row so an examiner could offer a quality's ladder
124
+ // instead of a free-text box, and then never populated here: every quality row
125
+ // this function built came out without one. Fixed 2026-09-02.
126
+ ...d.stages !== void 0 ? { stages: d.stages } : {},
117
127
  writable: writable ?? d.writable ?? true
118
128
  };
119
129
  }
@@ -125,8 +135,8 @@ var ScopeRegistry = class {
125
135
  * each declaration's `default` (or a type default). Owned scopes are
126
136
  * type-checked (declarations) and serialized by `save`/`load`.
127
137
  */
128
- defineOwned(token, declarations) {
129
- return this.mountOwned(token, new PropertyBag(declarations));
138
+ defineOwned(token, declarations, pathPrefix) {
139
+ return this.mountOwned(token, new PropertyBag(declarations, { pathPrefix: pathPrefix ?? `${token}.` }));
130
140
  }
131
141
  /**
132
142
  * Attach an EXISTING bag as an owned scope - the shared-container move: a
@@ -211,7 +221,13 @@ var ScopeRegistry = class {
211
221
  for (const d of e.decls.values()) {
212
222
  out.push({
213
223
  scope: token,
214
- ...rowFor(d, e.resolver.get(d.name.toLowerCase()), this.foreignWritable(e, d.name.toLowerCase()))
224
+ ...rowFor(
225
+ d,
226
+ e.resolver.get(d.name.toLowerCase()),
227
+ this.foreignWritable(e, d.name.toLowerCase()),
228
+ void 0,
229
+ `${token}.`
230
+ )
215
231
  });
216
232
  }
217
233
  }
@@ -322,6 +338,13 @@ function defaultFor(d) {
322
338
  // A quality starts at the first rung of its ladder.
323
339
  case "quality":
324
340
  return d.stages?.[0] ?? "";
341
+ // Unreachable for a well-typed declaration, and deliberately present anyway: a bundle
342
+ // is DATA, and a hand-edited or newer-than-this-build one can carry a type string the
343
+ // union does not have. Falling off the switch would seed `undefined`, which is not a
344
+ // ScalarValue and travels a long way before it fails. Patterplay's copy of this had the
345
+ // guard and this one did not, which is the drift you only find by removing a duplicate.
346
+ default:
347
+ return false;
325
348
  }
326
349
  }
327
350
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// @wildwinter/scoperegistry - the scope registry / runtime state container that\n// sits on top of @wildwinter/expr.\n//\n// expr is a stateless calculator: given an AST, an EvalContext (the state), and\n// a Dialect, it computes. This package is the *state* layer: it owns the world\n// state as a set of named scopes - each either an **owned** scope (a property\n// bag this registry stores and saves) or a **foreign** scope (host- or\n// other-engine-resolved at runtime, never stored here) - and produces the\n// `EvalContext` (for evaluation) and `ExpressionSchema` (for validation) that\n// expr consumes. Plus the `scopeRegistrySpec` interop format for importing a\n// foreign owner's scope declarations.\n//\n// Design: design/scope-registry.md (in the patter repo). expr never depends on\n// this; this depends one-way on expr.\n// ---------------------------------------------------------------------------\n\nimport type {\n EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver,\n} from \"@wildwinter/expr\";\n\nexport type { EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\n\n// ---------------------------------------------------------------------------\n// Declarations + the scopeRegistrySpec interop format\n// ---------------------------------------------------------------------------\n\n/**\n * A property declaration. `default` is used by an *owned* scope to seed its bag\n * (foreign scopes ignore it - the host owns the value). `writable: false` makes\n * a property read-only; default is read/write. (`type`/`values` feed validation.)\n */\nexport interface ScopeDeclaration {\n name: string;\n type: PropertyType;\n values?: string[]; // for enum / flags\n /** A quality's ordered ladder of stage names (quality.md). */\n stages?: string[];\n default?: ScalarValue; // owned scopes: seed value\n writable?: boolean; // default true\n}\n\n/** One scope in a `scopeRegistrySpec`: a token + (optional) declarations. */\nexport interface ScopeSpec {\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: ScopeDeclaration[];\n}\n\n/**\n * The interop format an owner (Storylet Studio, a host game) exports so another\n * engine can validate references into its scopes. Carried under the well-known\n * `scopeRegistrySpec` JSON key (inside a `.storyworld`, or a standalone file).\n */\nexport interface ScopeRegistrySpec {\n version: number;\n scopes: ScopeSpec[];\n}\n\n/** The spec versions this build understands. */\nexport const SUPPORTED_SPEC_VERSIONS = [1] as const;\n\n/**\n * Extract + validate a `scopeRegistrySpec` from any JSON value (a parsed\n * `.storyworld` bundle, or a vanilla `{ scopeRegistrySpec: ... }` manifest).\n * Returns null when the key is absent (so callers can probe arbitrary files);\n * throws on a malformed or unsupported-version spec.\n */\nexport function readScopeRegistrySpec(source: unknown): ScopeRegistrySpec | null {\n if (!source || typeof source !== \"object\") return null;\n const raw = (source as Record<string, unknown>).scopeRegistrySpec;\n if (raw === undefined) return null;\n if (typeof raw !== \"object\" || raw === null) throw new Error(\"scopeRegistrySpec must be an object\");\n const spec = raw as Record<string, unknown>;\n if (typeof spec.version !== \"number\") throw new Error(\"scopeRegistrySpec.version must be a number\");\n if (!(SUPPORTED_SPEC_VERSIONS as readonly number[]).includes(spec.version)) {\n throw new Error(`unsupported scopeRegistrySpec version ${spec.version} (supported: ${SUPPORTED_SPEC_VERSIONS.join(\", \")})`);\n }\n if (!Array.isArray(spec.scopes)) throw new Error(\"scopeRegistrySpec.scopes must be an array\");\n for (const s of spec.scopes) {\n if (!s || typeof s !== \"object\" || typeof (s as ScopeSpec).token !== \"string\") {\n throw new Error(\"each scopeRegistrySpec scope needs a string token\");\n }\n }\n return spec as unknown as ScopeRegistrySpec;\n}\n\n// ---------------------------------------------------------------------------\n// PropertyBag - the state kernel's unit of state (added 0.2.0; design:\n// storylets-new/design/engine-runtimes.md 3.1). A typed, declared property\n// bag with defaults, the firing rule (engine writes notify subscribers;\n// host writes are silent but always auditable), examiner rows, one\n// sanctioned clone door, and bare-value save/load. Owned registry scopes\n// are bags; products may also hold bag families of their own (per-box,\n// per-scene) and mount the shared ones.\n// ---------------------------------------------------------------------------\n\n/** One property change. `silent` marks a host write (the firing rule: it\n * reaches the audit hook but not subscribers); `reason` is the host's own\n * note for its log. */\nexport interface BagChange {\n name: string;\n prev?: ScalarValue;\n next: ScalarValue;\n silent: boolean;\n reason?: string;\n}\n\n/** One examiner row: what a property examiner/editor needs to render and\n * edit a declared property. */\nexport interface PropertyRow {\n name: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n /** A quality's ordered stage ladder, so an inspector can offer the stages\n * instead of a free-text box. `quality` has been in PropertyType since the\n * ladder landed, and the evaluator compares stages by LADDER POSITION and\n * refuses an unknown one, so free text is not a soft failure: a typo breaks\n * play rather than being corrected. This row is the only thing an examiner\n * sees, so a ladder it cannot carry is a ladder no editor can offer. One\n * consumer forked this whole interface to add the field; the field belongs\n * here, beside the `values` it is the closed-set twin of. */\n stages?: string[];\n writable: boolean;\n}\n\nexport class PropertyBag {\n /** The live values record (stable identity across reseed, so an\n * EvalContext built over it stays valid). Read-path for evaluation;\n * writes go through `set` so the firing rule applies. */\n readonly values: Record<string, ScalarValue> = {};\n private decls = new Map<string, ScopeDeclaration>();\n private readonly subscribers = new Set<(change: BagChange) => void>();\n private readonly auditors = new Set<(change: BagChange) => void>();\n /** Name normalisation policy: lowercase by default (the registry's\n * long-standing contract); a product whose names are case-significant\n * passes identity. */\n private readonly norm: (name: string) => string;\n\n constructor(declarations: ScopeDeclaration[] = [], opts?: { normalise?: (name: string) => string }) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\n this.seed(declarations);\n }\n\n private seed(declarations: ScopeDeclaration[]): void {\n for (const d of declarations) {\n const name = this.norm(d.name);\n this.decls.set(name, d);\n // Cloned so bags seeded from one declaration set never share a\n // mutable default (flags arrays).\n this.values[name] = structuredClone(d.default ?? defaultFor(d));\n }\n }\n\n get(name: string): ScalarValue | undefined {\n return this.values[this.norm(name)];\n }\n\n /** Write a property. Engine writes (the default) notify subscribers;\n * pass `silent: true` for a host write, which reaches only the audit\n * hook. Throws on a read-only property. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string }): BagChange {\n const n = this.norm(name);\n if (this.decls.get(n)?.writable === false) throw new Error(`'${name}' is read-only`);\n const change: BagChange = {\n name: n,\n prev: this.values[n],\n next: value,\n silent: opts?.silent ?? false,\n reason: opts?.reason,\n };\n this.values[n] = value;\n for (const audit of this.auditors) audit(change);\n if (!change.silent) for (const fn of this.subscribers) fn(change);\n return change;\n }\n\n /** Notified of engine (non-silent) writes. Returns the unsubscribe. */\n subscribe(fn: (change: BagChange) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n /** Notified of EVERY write, silent or not. Returns the unsubscribe. */\n onAudit(fn: (change: BagChange) => void): () => void {\n this.auditors.add(fn);\n return () => this.auditors.delete(fn);\n }\n\n /** Examiner rows: the declared surface only (stray values are storage,\n * not surface). */\n rows(): PropertyRow[] {\n return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), undefined, name));\n }\n\n declarations(): ScopeDeclaration[] {\n return [...this.decls.values()];\n }\n\n /** The one sanctioned copy door: values deep-copied, declarations\n * duplicated, the normalisation policy carried, subscriptions NOT\n * carried. */\n clone(): PropertyBag {\n const c = new PropertyBag([], { normalise: this.norm });\n c.decls = new Map(this.decls);\n Object.assign(c.values, structuredClone(this.values));\n return c;\n }\n\n /** Clear and re-seed from new declarations, in place (the values record\n * keeps its identity, so contexts built over it stay valid). */\n reseed(declarations: ScopeDeclaration[]): void {\n for (const k of Object.keys(this.values)) delete this.values[k];\n this.decls.clear();\n this.seed(declarations);\n }\n\n /** Bare values, ready to embed in a product's save. */\n save(): Record<string, ScalarValue> {\n return structuredClone(this.values);\n }\n\n /** Lay saved values over the current ones (call after a fresh seed:\n * orphans land as strays, new declarations keep their defaults; the\n * product decides whether to prune). Does not fire events. */\n load(values: Record<string, ScalarValue>): void {\n for (const [k, v] of Object.entries(values)) this.values[this.norm(k)] = v;\n }\n}\n\nfunction rowFor(d: ScopeDeclaration, value: ScalarValue | undefined, writable?: boolean, name?: string): PropertyRow {\n return {\n name: name ?? d.name.toLowerCase(),\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n writable: writable ?? d.writable ?? true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The registry / state container\n// ---------------------------------------------------------------------------\n\ninterface OwnedScope {\n kind: \"owned\";\n bag: PropertyBag;\n}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n}\ntype Entry = OwnedScope | ForeignScope;\n\n/** The versioned owned-state fragment both product save envelopes embed\n * (design/engine-runtimes.md 3.1: one serialisation shape for bags). */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\n\n /**\n * Register a scope this registry **owns and stores**. Its bag is seeded from\n * each declaration's `default` (or a type default). Owned scopes are\n * type-checked (declarations) and serialized by `save`/`load`.\n */\n defineOwned(token: string, declarations: ScopeDeclaration[]): this {\n return this.mountOwned(token, new PropertyBag(declarations));\n }\n\n /**\n * Attach an EXISTING bag as an owned scope - the shared-container move: a\n * host (or the other product) holds the bag; this registry reads, writes\n * and lists it like its own, but the holder saves it.\n */\n mountOwned(token: string, bag: PropertyBag): this {\n this.assertFree(token);\n this.scopes.set(token, { kind: \"owned\", bag });\n return this;\n }\n\n /** An owned scope's bag (subscribe, audit, rows live there). */\n ownedBag(token: string): PropertyBag {\n const e = this.scopes.get(token);\n if (!e || e.kind !== \"owned\") throw new Error(`'@${token}' is not an owned scope`);\n return e.bag;\n }\n\n /**\n * Re-initialise an existing **owned** scope's bag from new declarations,\n * clearing its current values. For scope-local state that resets on a context\n * change (e.g. entering a new scene / site / deck) without disturbing other\n * scopes. Mutates the bag in place, so an `EvalContext` already built from this\n * registry stays valid.\n */\n reseedOwned(token: string, declarations: ScopeDeclaration[]): this {\n this.ownedBag(token).reseed(declarations);\n return this;\n }\n\n /**\n * Register a **foreign** scope backed by a host `{ get, set? }` resolver. The\n * values live in the host/other engine and are never stored or saved here.\n * `declarations` (optional, e.g. imported from a `scopeRegistrySpec`) are used\n * only for validation; omit them for an opaque scope.\n */\n defineForeign(\n token: string,\n resolver: ScopeResolver,\n declarations: ScopeDeclaration[] = [],\n scopeWritable = true,\n ): this {\n this.assertFree(token);\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(d.name.toLowerCase(), d);\n this.scopes.set(token, { kind: \"foreign\", resolver, decls, scopeWritable });\n return this;\n }\n\n has(token: string): boolean {\n return this.scopes.has(token);\n }\n\n /** Read a property; undefined if the scope or property is not present. */\n get(scope: string, name: string): ScalarValue | undefined {\n const e = this.scopes.get(scope);\n if (!e) return undefined;\n return e.kind === \"owned\" ? e.bag.get(name) : e.resolver.get(name.toLowerCase());\n }\n\n /** Write a property (an ENGINE write: the bag's subscribers fire; use\n * the bag directly for silent host writes). Throws on an unknown or\n * read-only scope/property. */\n set(scope: string, name: string, value: ScalarValue): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n try {\n e.bag.set(name, value);\n } catch {\n throw new Error(`'@${scope}.${name}' is read-only`);\n }\n return;\n }\n const n = name.toLowerCase();\n if (!this.foreignWritable(e, n)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.resolver.set!(n, value);\n }\n\n private foreignWritable(e: ForeignScope, name: string): boolean {\n if (!e.resolver.set) return false; // no setter => read-only scope\n return e.decls.get(name)?.writable ?? e.scopeWritable;\n }\n\n /** Examiner rows across every scope with a declared surface: owned bags\n * first, then declared foreign scopes (values read through, writability\n * reflecting the resolver). Opaque foreign scopes are not listed. */\n listProperties(): ({ scope: string } & PropertyRow)[] {\n const out: ({ scope: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...row });\n } else {\n for (const d of e.decls.values()) {\n out.push({\n scope: token,\n ...rowFor(d, e.resolver.get(d.name.toLowerCase()), this.foreignWritable(e, d.name.toLowerCase())),\n });\n }\n }\n }\n return out;\n }\n\n /**\n * Build the `EvalContext` expr's `evaluate` consumes: owned scopes as static\n * bags, foreign scopes as their resolvers. `host` carries dialect-function\n * callbacks (PRNG, tag lookups) and is passed through untouched.\n */\n toEvalContext(host?: Record<string, unknown>): EvalContext {\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of this.scopes) {\n scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n }\n // The quality channel (quality.md): declared here once, so a host that\n // registers a quality gets ordering comparisons and advance() with no\n // further wiring. Only added when a quality exists, so contexts stay\n // byte-identical for products that declare none.\n const qualities = this.qualityLadders();\n return qualities.size === 0 ? { scopes, host } : {\n scopes, host,\n qualities: (scope, name) => qualities.get(scope)?.get(name.toLowerCase()),\n };\n }\n\n /** Every quality declaration's ladder, keyed scope token then name. */\n private qualityLadders(): Map<string, Map<string, readonly string[]>> {\n const out = new Map<string, Map<string, readonly string[]>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n for (const d of decls) {\n if (d.type !== \"quality\" || d.stages === undefined) continue;\n let m = out.get(token);\n if (!m) { m = new Map(); out.set(token, m); }\n m.set(d.name.toLowerCase(), d.stages);\n }\n }\n return out;\n }\n\n /**\n * Build the `ExpressionSchema` expr's validator consumes. Scopes with no\n * declarations are **omitted** (opaque - references into them are not flagged);\n * declared scopes contribute their property types for validation.\n */\n toSchema(): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const d of decls) m.set(d.name.toLowerCase(), {\n type: d.type, enumValues: d.values,\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n });\n properties.set(token, m);\n }\n return { properties };\n }\n\n /** Serialize **owned** scopes only (foreign scopes are host-owned,\n * host-saved), as bare bags - the 0.1.x shape, kept stable so existing\n * consumers' save formats are untouched. A product embedding the\n * versioned cross-product shape uses `saveFragment`. */\n save(): Record<string, Record<string, ScalarValue>> {\n const out: Record<string, Record<string, ScalarValue>> = {};\n for (const [token, e] of this.scopes) if (e.kind === \"owned\") out[token] = e.bag.save();\n return out;\n }\n\n /** Restore owned-scope values from a `save` blob. Unknown/foreign scopes\n * are ignored. */\n load(blob: Record<string, Record<string, ScalarValue>>): void {\n for (const [token, vals] of Object.entries(blob)) {\n const e = this.scopes.get(token);\n if (e?.kind === \"owned\") e.bag.load(vals);\n }\n }\n\n /** The versioned owned-state fragment (the one serialisation shape both\n * product families' save envelopes embed when they adopt the kernel;\n * design/engine-runtimes.md 3.1). `save()` wrapped with a version stamp. */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /** Restore from a versioned fragment; an unsupported version throws. */\n loadFragment(fragment: OwnedStateFragment): void {\n if (fragment.version !== SAVE_FRAGMENT_VERSION) {\n throw new Error(`unsupported owned-state fragment version ${fragment.version} (supported: ${SAVE_FRAGMENT_VERSION})`);\n }\n this.load(fragment.scopes);\n }\n\n private assertFree(token: string): void {\n if (this.scopes.has(token)) throw new Error(`scope '@${token}' is already registered`);\n }\n}\n\n/** The seed value for a declared property: its own `default`, else the type's.\n *\n * Exported because it was being written again wherever a declaration needed seeding, and a\n * copy of a defaults table is a copy that stops agreeing. Patterplay carried three of them in\n * one file, for its shared decls, its host-scope decls and its scene decls - three declaration\n * TYPES, one behaviour, and nothing to notice if a case drifted. The parameter is structurally\n * typed for exactly that reason: anything with `type` and the optional `default` / `values` /\n * `stages` fits, whatever the caller calls its declaration.\n *\n * A quality seeds at the FIRST rung of its ladder: the ladder's start is the story's start. */\nexport function defaultFor(d: Pick<ScopeDeclaration, \"type\" | \"default\" | \"values\" | \"stages\">): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"enum\": return d.values?.[0] ?? \"\";\n case \"flags\": return [];\n // A quality starts at the first rung of its ladder.\n case \"quality\": return d.stages?.[0] ?? \"\";\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// @wildwinter/scoperegistry - the scope registry / runtime state container that\n// sits on top of @wildwinter/expr.\n//\n// expr is a stateless calculator: given an AST, an EvalContext (the state), and\n// a Dialect, it computes. This package is the *state* layer: it owns the world\n// state as a set of named scopes - each either an **owned** scope (a property\n// bag this registry stores and saves) or a **foreign** scope (host- or\n// other-engine-resolved at runtime, never stored here) - and produces the\n// `EvalContext` (for evaluation) and `ExpressionSchema` (for validation) that\n// expr consumes. Plus the `scopeRegistrySpec` interop format for importing a\n// foreign owner's scope declarations.\n//\n// Design: design/scope-registry.md (in the patter repo). expr never depends on\n// this; this depends one-way on expr.\n// ---------------------------------------------------------------------------\n\nimport type {\n EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver,\n} from \"@wildwinter/expr\";\n\nexport type { EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\n\n// ---------------------------------------------------------------------------\n// Declarations + the scopeRegistrySpec interop format\n// ---------------------------------------------------------------------------\n\n/**\n * A property declaration. `default` is used by an *owned* scope to seed its bag\n * (foreign scopes ignore it - the host owns the value). `writable: false` makes\n * a property read-only; default is read/write. (`type`/`values` feed validation.)\n */\nexport interface ScopeDeclaration {\n name: string;\n type: PropertyType;\n values?: string[]; // for enum / flags\n /** A quality's ordered ladder of stage names (quality.md). */\n stages?: string[];\n default?: ScalarValue; // owned scopes: seed value\n writable?: boolean; // default true\n}\n\n/** One scope in a `scopeRegistrySpec`: a token + (optional) declarations. */\nexport interface ScopeSpec {\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: ScopeDeclaration[];\n}\n\n/**\n * The interop format an owner (Storylet Studio, a host game) exports so another\n * engine can validate references into its scopes. Carried under the well-known\n * `scopeRegistrySpec` JSON key (inside a `.storyworld`, or a standalone file).\n */\nexport interface ScopeRegistrySpec {\n version: number;\n scopes: ScopeSpec[];\n}\n\n/** The spec versions this build understands. */\nexport const SUPPORTED_SPEC_VERSIONS = [1] as const;\n\n/**\n * Extract + validate a `scopeRegistrySpec` from any JSON value (a parsed\n * `.storyworld` bundle, or a vanilla `{ scopeRegistrySpec: ... }` manifest).\n * Returns null when the key is absent (so callers can probe arbitrary files);\n * throws on a malformed or unsupported-version spec.\n */\nexport function readScopeRegistrySpec(source: unknown): ScopeRegistrySpec | null {\n if (!source || typeof source !== \"object\") return null;\n const raw = (source as Record<string, unknown>).scopeRegistrySpec;\n if (raw === undefined) return null;\n if (typeof raw !== \"object\" || raw === null) throw new Error(\"scopeRegistrySpec must be an object\");\n const spec = raw as Record<string, unknown>;\n if (typeof spec.version !== \"number\") throw new Error(\"scopeRegistrySpec.version must be a number\");\n if (!(SUPPORTED_SPEC_VERSIONS as readonly number[]).includes(spec.version)) {\n throw new Error(`unsupported scopeRegistrySpec version ${spec.version} (supported: ${SUPPORTED_SPEC_VERSIONS.join(\", \")})`);\n }\n if (!Array.isArray(spec.scopes)) throw new Error(\"scopeRegistrySpec.scopes must be an array\");\n for (const s of spec.scopes) {\n if (!s || typeof s !== \"object\" || typeof (s as ScopeSpec).token !== \"string\") {\n throw new Error(\"each scopeRegistrySpec scope needs a string token\");\n }\n }\n return spec as unknown as ScopeRegistrySpec;\n}\n\n// ---------------------------------------------------------------------------\n// PropertyBag - the state kernel's unit of state (added 0.2.0; design:\n// storylets-new/design/engine-runtimes.md 3.1). A typed, declared property\n// bag with defaults, the firing rule (engine writes notify subscribers;\n// host writes are silent but always auditable), examiner rows, one\n// sanctioned clone door, and bare-value save/load. Owned registry scopes\n// are bags; products may also hold bag families of their own (per-box,\n// per-scene) and mount the shared ones.\n// ---------------------------------------------------------------------------\n\n/** One property change. `silent` marks a host write (the firing rule: it\n * reaches the audit hook but not subscribers); `reason` is the host's own\n * note for its log. */\nexport interface BagChange {\n name: string;\n prev?: ScalarValue;\n next: ScalarValue;\n silent: boolean;\n reason?: string;\n}\n\n/** One examiner row: what a property examiner/editor needs to render and\n * edit a declared property. */\nexport interface PropertyRow {\n name: string;\n /** The address this property answers to - what getProperty/setProperty take.\n * A bag composes it from its own `pathPrefix` and the name, so a row is\n * self-describing: an examiner can render and write a row without being told\n * separately where it came from.\n *\n * The PREFIX CARRIES ITS OWN SEPARATOR rather than the bag assuming a dot,\n * because a prefix is not always a bare scope token: the Storylet Engine\n * addresses a deck's properties as `deck.<id>.name`, so the prefix is already\n * a dotted path. Patterplay's `@patter.gold` and `@scene.mood` are the plain\n * case. (`@gold` also resolves - splitRef defaults an unqualified name to the\n * patter scope - but it is the shorthand, not the address a row reports.)\n *\n * With no prefix this is just the name. Both families forked this interface\n * to add exactly this field - once per runtime - which is the same reason\n * `stages` is here. */\n path: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n /** A quality's ordered stage ladder, so an inspector can offer the stages\n * instead of a free-text box. `quality` has been in PropertyType since the\n * ladder landed, and the evaluator compares stages by LADDER POSITION and\n * refuses an unknown one, so free text is not a soft failure: a typo breaks\n * play rather than being corrected. This row is the only thing an examiner\n * sees, so a ladder it cannot carry is a ladder no editor can offer. One\n * consumer forked this whole interface to add the field; the field belongs\n * here, beside the `values` it is the closed-set twin of. */\n stages?: string[];\n writable: boolean;\n}\n\nexport class PropertyBag {\n /** The live values record (stable identity across reseed, so an\n * EvalContext built over it stays valid). Read-path for evaluation;\n * writes go through `set` so the firing rule applies. */\n readonly values: Record<string, ScalarValue> = {};\n private decls = new Map<string, ScopeDeclaration>();\n private readonly subscribers = new Set<(change: BagChange) => void>();\n private readonly auditors = new Set<(change: BagChange) => void>();\n /** Name normalisation policy: lowercase by default (the registry's\n * long-standing contract); a product whose names are case-significant\n * passes identity. */\n private readonly norm: (name: string) => string;\n\n /** The address prefix this bag's rows carry, separator included (`@`,\n * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */\n readonly pathPrefix: string;\n\n constructor(\n declarations: ScopeDeclaration[] = [],\n opts?: { normalise?: (name: string) => string; pathPrefix?: string },\n ) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\n this.pathPrefix = opts?.pathPrefix ?? \"\";\n this.seed(declarations);\n }\n\n private seed(declarations: ScopeDeclaration[]): void {\n for (const d of declarations) {\n const name = this.norm(d.name);\n this.decls.set(name, d);\n // Cloned so bags seeded from one declaration set never share a\n // mutable default (flags arrays).\n this.values[name] = structuredClone(d.default ?? defaultFor(d));\n }\n }\n\n get(name: string): ScalarValue | undefined {\n return this.values[this.norm(name)];\n }\n\n /** Write a property. Engine writes (the default) notify subscribers;\n * pass `silent: true` for a host write, which reaches only the audit\n * hook. Throws on a read-only property. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string }): BagChange {\n const n = this.norm(name);\n if (this.decls.get(n)?.writable === false) throw new Error(`'${name}' is read-only`);\n const change: BagChange = {\n name: n,\n prev: this.values[n],\n next: value,\n silent: opts?.silent ?? false,\n reason: opts?.reason,\n };\n this.values[n] = value;\n for (const audit of this.auditors) audit(change);\n if (!change.silent) for (const fn of this.subscribers) fn(change);\n return change;\n }\n\n /** Notified of engine (non-silent) writes. Returns the unsubscribe. */\n subscribe(fn: (change: BagChange) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n /** Notified of EVERY write, silent or not. Returns the unsubscribe. */\n onAudit(fn: (change: BagChange) => void): () => void {\n this.auditors.add(fn);\n return () => this.auditors.delete(fn);\n }\n\n /** Examiner rows: the declared surface only (stray values are storage,\n * not surface). */\n rows(): PropertyRow[] {\n return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), undefined, name, this.pathPrefix));\n }\n\n declarations(): ScopeDeclaration[] {\n return [...this.decls.values()];\n }\n\n /** The one sanctioned copy door: values deep-copied, declarations\n * duplicated, the normalisation policy carried, subscriptions NOT\n * carried. */\n clone(): PropertyBag {\n const c = new PropertyBag([], { normalise: this.norm, pathPrefix: this.pathPrefix });\n c.decls = new Map(this.decls);\n Object.assign(c.values, structuredClone(this.values));\n return c;\n }\n\n /** Clear and re-seed from new declarations, in place (the values record\n * keeps its identity, so contexts built over it stay valid). */\n reseed(declarations: ScopeDeclaration[]): void {\n for (const k of Object.keys(this.values)) delete this.values[k];\n this.decls.clear();\n this.seed(declarations);\n }\n\n /** Bare values, ready to embed in a product's save. */\n save(): Record<string, ScalarValue> {\n return structuredClone(this.values);\n }\n\n /** Lay saved values over the current ones (call after a fresh seed:\n * orphans land as strays, new declarations keep their defaults; the\n * product decides whether to prune). Does not fire events. */\n load(values: Record<string, ScalarValue>): void {\n for (const [k, v] of Object.entries(values)) this.values[this.norm(k)] = v;\n }\n}\n\nfunction rowFor(\n d: ScopeDeclaration,\n value: ScalarValue | undefined,\n writable?: boolean,\n name?: string,\n pathPrefix = \"\",\n): PropertyRow {\n const rowName = name ?? d.name.toLowerCase();\n return {\n name: rowName,\n path: pathPrefix + rowName,\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n // `stages` was added to the row so an examiner could offer a quality's ladder\n // instead of a free-text box, and then never populated here: every quality row\n // this function built came out without one. Fixed 2026-09-02.\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n writable: writable ?? d.writable ?? true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The registry / state container\n// ---------------------------------------------------------------------------\n\ninterface OwnedScope {\n kind: \"owned\";\n bag: PropertyBag;\n}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n}\ntype Entry = OwnedScope | ForeignScope;\n\n/** The versioned owned-state fragment both product save envelopes embed\n * (design/engine-runtimes.md 3.1: one serialisation shape for bags). */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\n\n /**\n * Register a scope this registry **owns and stores**. Its bag is seeded from\n * each declaration's `default` (or a type default). Owned scopes are\n * type-checked (declarations) and serialized by `save`/`load`.\n */\n defineOwned(token: string, declarations: ScopeDeclaration[], pathPrefix?: string): this {\n // The scope knows its own token, so its rows can address themselves: `world.hp`.\n // The ADDRESS GRAMMAR is the product's, though, not the registry's - Patterplay\n // writes `@patter.gold` where the Storylet Engine writes `world.gold` - so a\n // caller may say how its addresses look. A bag MOUNTED here keeps whatever prefix\n // its holder gave it: the holder owns the addressing.\n return this.mountOwned(token, new PropertyBag(declarations, { pathPrefix: pathPrefix ?? `${token}.` }));\n }\n\n /**\n * Attach an EXISTING bag as an owned scope - the shared-container move: a\n * host (or the other product) holds the bag; this registry reads, writes\n * and lists it like its own, but the holder saves it.\n */\n mountOwned(token: string, bag: PropertyBag): this {\n this.assertFree(token);\n this.scopes.set(token, { kind: \"owned\", bag });\n return this;\n }\n\n /** An owned scope's bag (subscribe, audit, rows live there). */\n ownedBag(token: string): PropertyBag {\n const e = this.scopes.get(token);\n if (!e || e.kind !== \"owned\") throw new Error(`'@${token}' is not an owned scope`);\n return e.bag;\n }\n\n /**\n * Re-initialise an existing **owned** scope's bag from new declarations,\n * clearing its current values. For scope-local state that resets on a context\n * change (e.g. entering a new scene / site / deck) without disturbing other\n * scopes. Mutates the bag in place, so an `EvalContext` already built from this\n * registry stays valid.\n */\n reseedOwned(token: string, declarations: ScopeDeclaration[]): this {\n this.ownedBag(token).reseed(declarations);\n return this;\n }\n\n /**\n * Register a **foreign** scope backed by a host `{ get, set? }` resolver. The\n * values live in the host/other engine and are never stored or saved here.\n * `declarations` (optional, e.g. imported from a `scopeRegistrySpec`) are used\n * only for validation; omit them for an opaque scope.\n */\n defineForeign(\n token: string,\n resolver: ScopeResolver,\n declarations: ScopeDeclaration[] = [],\n scopeWritable = true,\n ): this {\n this.assertFree(token);\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(d.name.toLowerCase(), d);\n this.scopes.set(token, { kind: \"foreign\", resolver, decls, scopeWritable });\n return this;\n }\n\n has(token: string): boolean {\n return this.scopes.has(token);\n }\n\n /** Read a property; undefined if the scope or property is not present. */\n get(scope: string, name: string): ScalarValue | undefined {\n const e = this.scopes.get(scope);\n if (!e) return undefined;\n return e.kind === \"owned\" ? e.bag.get(name) : e.resolver.get(name.toLowerCase());\n }\n\n /** Write a property (an ENGINE write: the bag's subscribers fire; use\n * the bag directly for silent host writes). Throws on an unknown or\n * read-only scope/property. */\n set(scope: string, name: string, value: ScalarValue): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n try {\n e.bag.set(name, value);\n } catch {\n throw new Error(`'@${scope}.${name}' is read-only`);\n }\n return;\n }\n const n = name.toLowerCase();\n if (!this.foreignWritable(e, n)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.resolver.set!(n, value);\n }\n\n private foreignWritable(e: ForeignScope, name: string): boolean {\n if (!e.resolver.set) return false; // no setter => read-only scope\n return e.decls.get(name)?.writable ?? e.scopeWritable;\n }\n\n /** Examiner rows across every scope with a declared surface: owned bags\n * first, then declared foreign scopes (values read through, writability\n * reflecting the resolver). Opaque foreign scopes are not listed. */\n listProperties(): ({ scope: string } & PropertyRow)[] {\n const out: ({ scope: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...row });\n } else {\n for (const d of e.decls.values()) {\n out.push({\n scope: token,\n ...rowFor(d, e.resolver.get(d.name.toLowerCase()), this.foreignWritable(e, d.name.toLowerCase()),\n undefined, `${token}.`),\n });\n }\n }\n }\n return out;\n }\n\n /**\n * Build the `EvalContext` expr's `evaluate` consumes: owned scopes as static\n * bags, foreign scopes as their resolvers. `host` carries dialect-function\n * callbacks (PRNG, tag lookups) and is passed through untouched.\n */\n toEvalContext(host?: Record<string, unknown>): EvalContext {\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of this.scopes) {\n scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n }\n // The quality channel (quality.md): declared here once, so a host that\n // registers a quality gets ordering comparisons and advance() with no\n // further wiring. Only added when a quality exists, so contexts stay\n // byte-identical for products that declare none.\n const qualities = this.qualityLadders();\n return qualities.size === 0 ? { scopes, host } : {\n scopes, host,\n qualities: (scope, name) => qualities.get(scope)?.get(name.toLowerCase()),\n };\n }\n\n /** Every quality declaration's ladder, keyed scope token then name. */\n private qualityLadders(): Map<string, Map<string, readonly string[]>> {\n const out = new Map<string, Map<string, readonly string[]>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n for (const d of decls) {\n if (d.type !== \"quality\" || d.stages === undefined) continue;\n let m = out.get(token);\n if (!m) { m = new Map(); out.set(token, m); }\n m.set(d.name.toLowerCase(), d.stages);\n }\n }\n return out;\n }\n\n /**\n * Build the `ExpressionSchema` expr's validator consumes. Scopes with no\n * declarations are **omitted** (opaque - references into them are not flagged);\n * declared scopes contribute their property types for validation.\n */\n toSchema(): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const d of decls) m.set(d.name.toLowerCase(), {\n type: d.type, enumValues: d.values,\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n });\n properties.set(token, m);\n }\n return { properties };\n }\n\n /** Serialize **owned** scopes only (foreign scopes are host-owned,\n * host-saved), as bare bags - the 0.1.x shape, kept stable so existing\n * consumers' save formats are untouched. A product embedding the\n * versioned cross-product shape uses `saveFragment`. */\n save(): Record<string, Record<string, ScalarValue>> {\n const out: Record<string, Record<string, ScalarValue>> = {};\n for (const [token, e] of this.scopes) if (e.kind === \"owned\") out[token] = e.bag.save();\n return out;\n }\n\n /** Restore owned-scope values from a `save` blob. Unknown/foreign scopes\n * are ignored. */\n load(blob: Record<string, Record<string, ScalarValue>>): void {\n for (const [token, vals] of Object.entries(blob)) {\n const e = this.scopes.get(token);\n if (e?.kind === \"owned\") e.bag.load(vals);\n }\n }\n\n /** The versioned owned-state fragment (the one serialisation shape both\n * product families' save envelopes embed when they adopt the kernel;\n * design/engine-runtimes.md 3.1). `save()` wrapped with a version stamp. */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /** Restore from a versioned fragment; an unsupported version throws. */\n loadFragment(fragment: OwnedStateFragment): void {\n if (fragment.version !== SAVE_FRAGMENT_VERSION) {\n throw new Error(`unsupported owned-state fragment version ${fragment.version} (supported: ${SAVE_FRAGMENT_VERSION})`);\n }\n this.load(fragment.scopes);\n }\n\n private assertFree(token: string): void {\n if (this.scopes.has(token)) throw new Error(`scope '@${token}' is already registered`);\n }\n}\n\n/** The seed value for a declared property: its own `default`, else the type's.\n *\n * Exported because it was being written again wherever a declaration needed seeding, and a\n * copy of a defaults table is a copy that stops agreeing. Patterplay carried three of them in\n * one file, for its shared decls, its host-scope decls and its scene decls - three declaration\n * TYPES, one behaviour, and nothing to notice if a case drifted. The parameter is structurally\n * typed for exactly that reason: anything with `type` and the optional `default` / `values` /\n * `stages` fits, whatever the caller calls its declaration.\n *\n * A quality seeds at the FIRST rung of its ladder: the ladder's start is the story's start. */\nexport function defaultFor(d: Pick<ScopeDeclaration, \"type\" | \"default\" | \"values\" | \"stages\">): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"enum\": return d.values?.[0] ?? \"\";\n case \"flags\": return [];\n // A quality starts at the first rung of its ladder.\n case \"quality\": return d.stages?.[0] ?? \"\";\n // Unreachable for a well-typed declaration, and deliberately present anyway: a bundle\n // is DATA, and a hand-edited or newer-than-this-build one can carry a type string the\n // union does not have. Falling off the switch would seed `undefined`, which is not a\n // ScalarValue and travels a long way before it fails. Patterplay's copy of this had the\n // guard and this one did not, which is the drift you only find by removing a duplicate.\n default: return false;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wildwinter/scoperegistry",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Scope registry / runtime state container for @wildwinter/expr: owned property scopes + foreign (host-resolved) scopes, save/load, and the scopeRegistrySpec interop format. Produces the EvalContext and ExpressionSchema that expr consumes.",
5
5
  "type": "module",
6
6
  "license": "MIT",