@yagejs/core 0.4.0 → 0.6.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.js CHANGED
@@ -1136,6 +1136,13 @@ var Entity = class {
1136
1136
  name;
1137
1137
  /** Tags for group queries. */
1138
1138
  tags;
1139
+ /**
1140
+ * Stable identity key, scene-scoped. Set at spawn-time when
1141
+ * `options.key` is passed to `scene.spawn` / `entity.spawnChild`;
1142
+ * `undefined` otherwise. Used with `scene.findByKey` and as a stable
1143
+ * id in persistent stores (e.g. `defineSet<string>("world.opened")`).
1144
+ */
1145
+ key;
1139
1146
  components = /* @__PURE__ */ new Map();
1140
1147
  _destroyed = false;
1141
1148
  _scene = null;
@@ -1206,17 +1213,21 @@ var Entity = class {
1206
1213
  this._scene._addExistingEntity(child);
1207
1214
  }
1208
1215
  }
1209
- spawnChild(name, classOrBlueprint, params) {
1216
+ spawnChild(name, classOrBlueprintOrOptions, paramsOrOptions, maybeOptions) {
1210
1217
  const scene = this.scene;
1211
1218
  if (this._children?.has(name)) {
1212
1219
  throw new Error(
1213
1220
  `Entity "${this.name}" already has a child named "${name}".`
1214
1221
  );
1215
1222
  }
1216
- const child = classOrBlueprint === void 0 ? scene.spawn(name) : scene.spawn(
1217
- classOrBlueprint,
1218
- params
1219
- );
1223
+ let child;
1224
+ if (classOrBlueprintOrOptions === void 0) {
1225
+ child = scene.spawn(name);
1226
+ } else if (typeof classOrBlueprintOrOptions === "object" && !("build" in classOrBlueprintOrOptions)) {
1227
+ child = scene.spawn(name, classOrBlueprintOrOptions);
1228
+ } else {
1229
+ child = scene.spawn(classOrBlueprintOrOptions, paramsOrOptions, maybeOptions);
1230
+ }
1220
1231
  this.addChild(name, child);
1221
1232
  return child;
1222
1233
  }
@@ -1359,6 +1370,19 @@ var Entity = class {
1359
1370
  }
1360
1371
  return false;
1361
1372
  }
1373
+ /**
1374
+ * Return the stable key, or throw if this entity was spawned without one.
1375
+ * Use inside component `setup()` when the component depends on identity
1376
+ * (e.g. reading from a `defineSet` keyed by entity id).
1377
+ */
1378
+ requireKey() {
1379
+ if (this.key === void 0) {
1380
+ throw new Error(
1381
+ `Entity "${this.name}" (id=${this.id}) has no stable key. Pass { key: "..." } to scene.spawn(...) or entity.spawnChild(...).`
1382
+ );
1383
+ }
1384
+ return this.key;
1385
+ }
1362
1386
  /**
1363
1387
  * Internal: set the scene and callbacks. Called by Scene.spawn().
1364
1388
  * @internal
@@ -1367,6 +1391,20 @@ var Entity = class {
1367
1391
  this._scene = scene;
1368
1392
  this.callbacks = callbacks;
1369
1393
  }
1394
+ /**
1395
+ * Internal: assign the stable identity key. Called by `Scene._registerKey`
1396
+ * during spawn. Throws if the entity already has a key — keys are
1397
+ * immutable for an entity's lifetime.
1398
+ * @internal
1399
+ */
1400
+ _setKey(key) {
1401
+ if (this.key !== void 0) {
1402
+ throw new Error(
1403
+ `Entity "${this.name}" already has key "${this.key}".`
1404
+ );
1405
+ }
1406
+ this.key = key;
1407
+ }
1370
1408
  };
1371
1409
 
1372
1410
  // src/QueryCache.ts
@@ -1803,11 +1841,30 @@ var Inspector = class {
1803
1841
  mouseUp: /* @__PURE__ */ __name((button = 0) => {
1804
1842
  this.requireInputManager().firePointerUp(button);
1805
1843
  }, "mouseUp"),
1806
- gamepadButton: /* @__PURE__ */ __name((idx, pressed) => {
1807
- this.requireInputManager().fireGamepadButton(idx, pressed);
1844
+ /**
1845
+ * Inject a synthetic pointer-move with full pointer addressing. Pass `opts`
1846
+ * with `id` / `type: "touch"` to drive a specific finger; defaults match
1847
+ * the primary mouse pointer (same as `mouseMove`).
1848
+ */
1849
+ pointerMove: /* @__PURE__ */ __name((x, y, opts) => {
1850
+ this.requireInputManager().firePointerMove(x, y, opts);
1851
+ }, "pointerMove"),
1852
+ /**
1853
+ * Inject a synthetic pointer-down. With `opts.id` and `opts.type: "touch"`
1854
+ * this drives a multi-touch contact, exercising `getPointers()`,
1855
+ * per-pointer event hooks, and the any-pointer aggregate for `MouseLeft`.
1856
+ */
1857
+ pointerDown: /* @__PURE__ */ __name((button = 0, opts) => {
1858
+ this.requireInputManager().firePointerDown(button, opts);
1859
+ }, "pointerDown"),
1860
+ pointerUp: /* @__PURE__ */ __name((button = 0, opts) => {
1861
+ this.requireInputManager().firePointerUp(button, opts);
1862
+ }, "pointerUp"),
1863
+ gamepadButton: /* @__PURE__ */ __name((code, pressed) => {
1864
+ this.requireInputManager().fireGamepadButton(code, pressed);
1808
1865
  }, "gamepadButton"),
1809
- gamepadAxis: /* @__PURE__ */ __name((idx, value) => {
1810
- this.requireInputManager().fireGamepadAxis(idx, value);
1866
+ gamepadAxis: /* @__PURE__ */ __name((side, value) => {
1867
+ this.requireInputManager().fireGamepadAxis(side, value);
1811
1868
  }, "gamepadAxis"),
1812
1869
  tap: /* @__PURE__ */ __name((code, frames = 1) => {
1813
1870
  this.assertNonNegativeInteger(frames, "Inspector.input.tap(frames)");
@@ -2366,6 +2423,7 @@ var Inspector = class {
2366
2423
  keys: [],
2367
2424
  actions: [],
2368
2425
  mouse: { x: 0, y: 0, buttons: [], down: false },
2426
+ pointers: [],
2369
2427
  gamepad: { buttons: [], axes: [] }
2370
2428
  };
2371
2429
  }
@@ -2529,6 +2587,17 @@ function decodeBase64(base64) {
2529
2587
  __name(decodeBase64, "decodeBase64");
2530
2588
 
2531
2589
  // src/Scene.ts
2590
+ var _SPAWN_OPTION_KEYS = /* @__PURE__ */ new Set(["key"]);
2591
+ function _looksLikeSpawnOptions(v) {
2592
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
2593
+ const keys = Object.keys(v);
2594
+ if (keys.length === 0) return false;
2595
+ for (const k of keys) {
2596
+ if (!_SPAWN_OPTION_KEYS.has(k)) return false;
2597
+ }
2598
+ return true;
2599
+ }
2600
+ __name(_looksLikeSpawnOptions, "_looksLikeSpawnOptions");
2532
2601
  var Scene = class {
2533
2602
  static {
2534
2603
  __name(this, "Scene");
@@ -2554,6 +2623,7 @@ var Scene = class {
2554
2623
  _entityEventHandlers;
2555
2624
  _entityEventObserver;
2556
2625
  _scopedServices;
2626
+ _identityIndex;
2557
2627
  /** Access the EngineContext. */
2558
2628
  get context() {
2559
2629
  return this._context;
@@ -2602,10 +2672,26 @@ var Scene = class {
2602
2672
  }, "set")
2603
2673
  });
2604
2674
  }
2605
- spawn(nameOrBlueprintOrClass, params) {
2675
+ spawn(nameOrBlueprintOrClass, paramsOrOptions, maybeOptions) {
2606
2676
  if (typeof nameOrBlueprintOrClass === "function") {
2607
- const entity2 = new nameOrBlueprintOrClass();
2677
+ const Ctor = nameOrBlueprintOrClass;
2678
+ const hasSetup = typeof Ctor.prototype.setup === "function";
2679
+ let params;
2680
+ let options2;
2681
+ if (maybeOptions !== void 0) {
2682
+ params = paramsOrOptions;
2683
+ options2 = maybeOptions;
2684
+ } else if (paramsOrOptions === void 0) {
2685
+ } else if (!hasSetup) {
2686
+ options2 = paramsOrOptions;
2687
+ } else if (_looksLikeSpawnOptions(paramsOrOptions)) {
2688
+ options2 = paramsOrOptions;
2689
+ } else {
2690
+ params = paramsOrOptions;
2691
+ }
2692
+ const entity2 = new Ctor();
2608
2693
  entity2._setScene(this, this.entityCallbacks);
2694
+ if (options2?.key !== void 0) this._registerKey(entity2, options2.key);
2609
2695
  this.entities.add(entity2);
2610
2696
  this.bus?.emit("entity:created", { entity: entity2 });
2611
2697
  entity2.setup?.(params);
@@ -2613,15 +2699,59 @@ var Scene = class {
2613
2699
  }
2614
2700
  const isBlueprint = typeof nameOrBlueprintOrClass === "object" && nameOrBlueprintOrClass !== null && "build" in nameOrBlueprintOrClass;
2615
2701
  const name = isBlueprint ? nameOrBlueprintOrClass.name : nameOrBlueprintOrClass;
2702
+ let blueprintParams;
2703
+ let options;
2704
+ if (isBlueprint) {
2705
+ if (maybeOptions !== void 0) {
2706
+ blueprintParams = paramsOrOptions;
2707
+ options = maybeOptions;
2708
+ } else if (paramsOrOptions !== void 0 && _looksLikeSpawnOptions(paramsOrOptions)) {
2709
+ options = paramsOrOptions;
2710
+ } else {
2711
+ blueprintParams = paramsOrOptions;
2712
+ }
2713
+ } else {
2714
+ options = paramsOrOptions;
2715
+ }
2616
2716
  const entity = new Entity(name);
2617
2717
  entity._setScene(this, this.entityCallbacks);
2718
+ if (options?.key !== void 0) this._registerKey(entity, options.key);
2618
2719
  this.entities.add(entity);
2619
2720
  this.bus?.emit("entity:created", { entity });
2620
2721
  if (isBlueprint) {
2621
- nameOrBlueprintOrClass.build(entity, params);
2722
+ nameOrBlueprintOrClass.build(
2723
+ entity,
2724
+ blueprintParams
2725
+ );
2622
2726
  }
2623
2727
  return entity;
2624
2728
  }
2729
+ /**
2730
+ * Look up an entity by its stable identity key, scoped to this scene.
2731
+ * Returns `undefined` for unknown or already-destroyed entities.
2732
+ */
2733
+ findByKey(key) {
2734
+ const entity = this._identityIndex?.get(key);
2735
+ if (!entity || entity.isDestroyed) return void 0;
2736
+ return entity;
2737
+ }
2738
+ /**
2739
+ * Internal: register a key on a freshly spawned entity. Throws on
2740
+ * duplicate so callers (Scene.spawn) can abort before adding to
2741
+ * `this.entities` or emitting `entity:created`.
2742
+ * @internal
2743
+ */
2744
+ _registerKey(entity, key) {
2745
+ this._identityIndex ??= /* @__PURE__ */ new Map();
2746
+ const existing = this._identityIndex.get(key);
2747
+ if (existing && !existing.isDestroyed) {
2748
+ throw new Error(
2749
+ `Scene "${this.name}" already has an entity with key "${key}". Destroy it before spawning a duplicate.`
2750
+ );
2751
+ }
2752
+ entity._setKey(key);
2753
+ this._identityIndex.set(key, entity);
2754
+ }
2625
2755
  /**
2626
2756
  * Add an existing entity to this scene (used by Entity.addChild for auto-scene-membership).
2627
2757
  * @internal
@@ -2772,12 +2902,17 @@ var Scene = class {
2772
2902
  entity._performDestroy();
2773
2903
  this.queryCache?.onEntityDestroyed(entity);
2774
2904
  this.entities.delete(entity);
2905
+ if (entity.key !== void 0 && this._identityIndex?.get(entity.key) === entity) {
2906
+ this._identityIndex.delete(entity.key);
2907
+ }
2775
2908
  this.bus?.emit("entity:destroyed", { entity });
2776
2909
  }
2777
2910
  this.destroyQueue.length = 0;
2778
2911
  }
2779
2912
  /**
2780
- * Destroy all entities — used during scene exit.
2913
+ * Destroy all entities — used during scene exit. Clears the identity
2914
+ * index in bulk; per-entity key removal in `_flushDestroyQueue` is the
2915
+ * in-game path.
2781
2916
  * @internal
2782
2917
  */
2783
2918
  _destroyAllEntities() {
@@ -2788,6 +2923,7 @@ var Scene = class {
2788
2923
  this.entities.clear();
2789
2924
  this.destroyQueue.length = 0;
2790
2925
  this._entityEventHandlers?.clear();
2926
+ this._identityIndex?.clear();
2791
2927
  }
2792
2928
  };
2793
2929
 
@@ -4389,6 +4525,21 @@ var RendererAdapterKey = new ServiceKey(
4389
4525
  "rendererAdapter"
4390
4526
  );
4391
4527
 
4528
+ // src/ui-consume-registry.ts
4529
+ var registry2 = /* @__PURE__ */ new WeakSet();
4530
+ function markPointerConsumeContainer(container) {
4531
+ registry2.add(container);
4532
+ }
4533
+ __name(markPointerConsumeContainer, "markPointerConsumeContainer");
4534
+ function unmarkPointerConsumeContainer(container) {
4535
+ registry2.delete(container);
4536
+ }
4537
+ __name(unmarkPointerConsumeContainer, "unmarkPointerConsumeContainer");
4538
+ function isPointerConsumeContainer(container) {
4539
+ return registry2.has(container);
4540
+ }
4541
+ __name(isPointerConsumeContainer, "isPointerConsumeContainer");
4542
+
4392
4543
  // src/test-utils.ts
4393
4544
  var _TestScene = class extends Scene {
4394
4545
  static {
@@ -4436,6 +4587,387 @@ function advanceFrames(engine, n, dtMs = 1e3 / 60) {
4436
4587
  }
4437
4588
  __name(advanceFrames, "advanceFrames");
4438
4589
 
4590
+ // src/state/Atom.ts
4591
+ function createAtom(initial) {
4592
+ let value = initial;
4593
+ const listeners = /* @__PURE__ */ new Set();
4594
+ return {
4595
+ get() {
4596
+ return value;
4597
+ },
4598
+ set(next) {
4599
+ if (Object.is(value, next)) return;
4600
+ value = next;
4601
+ for (const fn of listeners) fn(value);
4602
+ },
4603
+ subscribe(listener) {
4604
+ listeners.add(listener);
4605
+ return () => {
4606
+ listeners.delete(listener);
4607
+ };
4608
+ }
4609
+ };
4610
+ }
4611
+ __name(createAtom, "createAtom");
4612
+
4613
+ // src/state/Store.ts
4614
+ function createStore(initial) {
4615
+ let snapshot = { ...initial };
4616
+ const listeners = /* @__PURE__ */ new Set();
4617
+ return {
4618
+ get() {
4619
+ return snapshot;
4620
+ },
4621
+ set(partial) {
4622
+ let changed = false;
4623
+ for (const key of Object.keys(partial)) {
4624
+ if (!Object.is(snapshot[key], partial[key])) {
4625
+ changed = true;
4626
+ break;
4627
+ }
4628
+ }
4629
+ if (!changed) return;
4630
+ snapshot = { ...snapshot, ...partial };
4631
+ for (const fn of listeners) fn();
4632
+ },
4633
+ subscribe(listener) {
4634
+ listeners.add(listener);
4635
+ return () => {
4636
+ listeners.delete(listener);
4637
+ };
4638
+ }
4639
+ };
4640
+ }
4641
+ __name(createStore, "createStore");
4642
+
4643
+ // src/state/codecs.ts
4644
+ function jsonCodec() {
4645
+ return {
4646
+ encode: /* @__PURE__ */ __name((value) => value, "encode"),
4647
+ decode: /* @__PURE__ */ __name((raw) => raw, "decode")
4648
+ };
4649
+ }
4650
+ __name(jsonCodec, "jsonCodec");
4651
+ function setCodec() {
4652
+ return {
4653
+ encode: /* @__PURE__ */ __name((value) => Array.from(value), "encode"),
4654
+ decode: /* @__PURE__ */ __name((raw) => {
4655
+ if (!Array.isArray(raw)) {
4656
+ throw new Error("setCodec.decode: expected an array");
4657
+ }
4658
+ return new Set(raw);
4659
+ }, "decode")
4660
+ };
4661
+ }
4662
+ __name(setCodec, "setCodec");
4663
+ function mapCodec() {
4664
+ return {
4665
+ encode: /* @__PURE__ */ __name((value) => Array.from(value.entries()), "encode"),
4666
+ decode: /* @__PURE__ */ __name((raw) => {
4667
+ if (!Array.isArray(raw)) {
4668
+ throw new Error("mapCodec.decode: expected an array of entries");
4669
+ }
4670
+ return new Map(raw);
4671
+ }, "decode")
4672
+ };
4673
+ }
4674
+ __name(mapCodec, "mapCodec");
4675
+ function dateCodec() {
4676
+ return {
4677
+ encode: /* @__PURE__ */ __name((value) => value.toISOString(), "encode"),
4678
+ decode: /* @__PURE__ */ __name((raw) => {
4679
+ if (typeof raw !== "string") {
4680
+ throw new Error("dateCodec.decode: expected an ISO string");
4681
+ }
4682
+ const d = new Date(raw);
4683
+ if (Number.isNaN(d.getTime())) {
4684
+ throw new Error(`dateCodec.decode: invalid ISO string ${JSON.stringify(raw)}`);
4685
+ }
4686
+ return d;
4687
+ }, "decode")
4688
+ };
4689
+ }
4690
+ __name(dateCodec, "dateCodec");
4691
+
4692
+ // src/state/persistent.ts
4693
+ var registry3 = /* @__PURE__ */ new Map();
4694
+ function register(entry) {
4695
+ registry3.set(entry.id, entry);
4696
+ }
4697
+ __name(register, "register");
4698
+ function _resetAllStoresForTesting() {
4699
+ for (const entry of registry3.values()) entry.reset();
4700
+ }
4701
+ __name(_resetAllStoresForTesting, "_resetAllStoresForTesting");
4702
+ function _clearStoreRegistryForTesting() {
4703
+ registry3.clear();
4704
+ }
4705
+ __name(_clearStoreRegistryForTesting, "_clearStoreRegistryForTesting");
4706
+ var StoreVersionTooNewError = class extends Error {
4707
+ static {
4708
+ __name(this, "StoreVersionTooNewError");
4709
+ }
4710
+ storeId;
4711
+ storedVersion;
4712
+ currentVersion;
4713
+ constructor(storeId, storedVersion, currentVersion) {
4714
+ super(
4715
+ `Store "${storeId}" was saved at version ${storedVersion}, but this build is at version ${currentVersion}. Cannot downgrade.`
4716
+ );
4717
+ this.name = "StoreVersionTooNewError";
4718
+ this.storeId = storeId;
4719
+ this.storedVersion = storedVersion;
4720
+ this.currentVersion = currentVersion;
4721
+ }
4722
+ };
4723
+ var StoreMigrationMissingError = class extends Error {
4724
+ static {
4725
+ __name(this, "StoreMigrationMissingError");
4726
+ }
4727
+ storeId;
4728
+ storedVersion;
4729
+ currentVersion;
4730
+ constructor(storeId, storedVersion, currentVersion) {
4731
+ super(
4732
+ `Store "${storeId}" needs migration from version ${storedVersion} to ${currentVersion}, but no migrate() was provided.`
4733
+ );
4734
+ this.name = "StoreMigrationMissingError";
4735
+ this.storeId = storeId;
4736
+ this.storedVersion = storedVersion;
4737
+ this.currentVersion = currentVersion;
4738
+ }
4739
+ };
4740
+ function defineStore(id, opts) {
4741
+ const version = opts.version ?? 1;
4742
+ const codec = opts.codec ?? jsonCodec();
4743
+ const defaults = opts.defaults;
4744
+ const inner = createStore(defaults());
4745
+ const replaceAll = /* @__PURE__ */ __name((next) => {
4746
+ inner.set({ ...next });
4747
+ }, "replaceAll");
4748
+ const store = {
4749
+ id,
4750
+ version,
4751
+ get: inner.get,
4752
+ set: inner.set,
4753
+ subscribe: inner.subscribe,
4754
+ reset() {
4755
+ replaceAll(defaults());
4756
+ },
4757
+ serialize() {
4758
+ return { version, data: codec.encode(inner.get()) };
4759
+ },
4760
+ hydrate(payload) {
4761
+ if (payload.version > version) {
4762
+ throw new StoreVersionTooNewError(id, payload.version, version);
4763
+ }
4764
+ let next;
4765
+ if (payload.version < version) {
4766
+ if (!opts.migrate) {
4767
+ throw new StoreMigrationMissingError(id, payload.version, version);
4768
+ }
4769
+ next = opts.migrate(payload.data, payload.version);
4770
+ } else {
4771
+ next = codec.decode(payload.data);
4772
+ }
4773
+ replaceAll(next);
4774
+ }
4775
+ };
4776
+ register({ id, reset: /* @__PURE__ */ __name(() => store.reset(), "reset") });
4777
+ return store;
4778
+ }
4779
+ __name(defineStore, "defineStore");
4780
+ function defineSet(id, opts) {
4781
+ const version = opts?.version ?? 1;
4782
+ const defaults = /* @__PURE__ */ __name(() => new Set(opts?.defaults?.() ?? []), "defaults");
4783
+ const codec = setCodec();
4784
+ const atom = createAtom(defaults());
4785
+ const replace = /* @__PURE__ */ __name((next) => {
4786
+ atom.set(next);
4787
+ }, "replace");
4788
+ const store = {
4789
+ id,
4790
+ version,
4791
+ has(key) {
4792
+ return atom.get().has(key);
4793
+ },
4794
+ add(key) {
4795
+ const current = atom.get();
4796
+ if (current.has(key)) return;
4797
+ const next = new Set(current);
4798
+ next.add(key);
4799
+ replace(next);
4800
+ },
4801
+ remove(key) {
4802
+ const current = atom.get();
4803
+ if (!current.has(key)) return;
4804
+ const next = new Set(current);
4805
+ next.delete(key);
4806
+ replace(next);
4807
+ },
4808
+ clear() {
4809
+ if (atom.get().size === 0) return;
4810
+ replace(/* @__PURE__ */ new Set());
4811
+ },
4812
+ size() {
4813
+ return atom.get().size;
4814
+ },
4815
+ values() {
4816
+ return atom.get().values();
4817
+ },
4818
+ subscribe(listener) {
4819
+ return atom.subscribe(() => listener());
4820
+ },
4821
+ reset() {
4822
+ replace(defaults());
4823
+ },
4824
+ serialize() {
4825
+ return { version, data: codec.encode(atom.get()) };
4826
+ },
4827
+ hydrate(payload) {
4828
+ if (payload.version > version) {
4829
+ throw new StoreVersionTooNewError(id, payload.version, version);
4830
+ }
4831
+ if (payload.version < version) {
4832
+ if (!opts?.migrate) {
4833
+ throw new StoreMigrationMissingError(id, payload.version, version);
4834
+ }
4835
+ replace(opts.migrate(payload.data, payload.version));
4836
+ return;
4837
+ }
4838
+ replace(codec.decode(payload.data));
4839
+ }
4840
+ };
4841
+ register({ id, reset: /* @__PURE__ */ __name(() => store.reset(), "reset") });
4842
+ return store;
4843
+ }
4844
+ __name(defineSet, "defineSet");
4845
+ function defineMap(id, opts) {
4846
+ const version = opts?.version ?? 1;
4847
+ const defaults = /* @__PURE__ */ __name(() => new Map(opts?.defaults?.() ?? []), "defaults");
4848
+ const codec = mapCodec();
4849
+ const atom = createAtom(defaults());
4850
+ const replace = /* @__PURE__ */ __name((next) => {
4851
+ atom.set(next);
4852
+ }, "replace");
4853
+ const store = {
4854
+ id,
4855
+ version,
4856
+ has(key) {
4857
+ return atom.get().has(key);
4858
+ },
4859
+ get(key) {
4860
+ return atom.get().get(key);
4861
+ },
4862
+ set(key, value) {
4863
+ const current = atom.get();
4864
+ if (current.has(key) && Object.is(current.get(key), value)) return;
4865
+ const next = new Map(current);
4866
+ next.set(key, value);
4867
+ replace(next);
4868
+ },
4869
+ remove(key) {
4870
+ const current = atom.get();
4871
+ if (!current.has(key)) return;
4872
+ const next = new Map(current);
4873
+ next.delete(key);
4874
+ replace(next);
4875
+ },
4876
+ clear() {
4877
+ if (atom.get().size === 0) return;
4878
+ replace(/* @__PURE__ */ new Map());
4879
+ },
4880
+ size() {
4881
+ return atom.get().size;
4882
+ },
4883
+ entries() {
4884
+ return atom.get().entries();
4885
+ },
4886
+ subscribe(listener) {
4887
+ return atom.subscribe(() => listener());
4888
+ },
4889
+ reset() {
4890
+ replace(defaults());
4891
+ },
4892
+ serialize() {
4893
+ return { version, data: codec.encode(atom.get()) };
4894
+ },
4895
+ hydrate(payload) {
4896
+ if (payload.version > version) {
4897
+ throw new StoreVersionTooNewError(id, payload.version, version);
4898
+ }
4899
+ if (payload.version < version) {
4900
+ if (!opts?.migrate) {
4901
+ throw new StoreMigrationMissingError(id, payload.version, version);
4902
+ }
4903
+ replace(opts.migrate(payload.data, payload.version));
4904
+ return;
4905
+ }
4906
+ replace(codec.decode(payload.data));
4907
+ }
4908
+ };
4909
+ register({ id, reset: /* @__PURE__ */ __name(() => store.reset(), "reset") });
4910
+ return store;
4911
+ }
4912
+ __name(defineMap, "defineMap");
4913
+ function defineCounter(id, opts) {
4914
+ const version = opts?.version ?? 1;
4915
+ const defaults = /* @__PURE__ */ __name(() => opts?.defaults?.() ?? 0, "defaults");
4916
+ const atom = createAtom(defaults());
4917
+ const store = {
4918
+ id,
4919
+ version,
4920
+ value() {
4921
+ return atom.get();
4922
+ },
4923
+ set(n) {
4924
+ atom.set(n);
4925
+ },
4926
+ increment(by = 1) {
4927
+ atom.set(atom.get() + by);
4928
+ },
4929
+ decrement(by = 1) {
4930
+ atom.set(atom.get() - by);
4931
+ },
4932
+ subscribe(listener) {
4933
+ return atom.subscribe(() => listener());
4934
+ },
4935
+ reset() {
4936
+ atom.set(defaults());
4937
+ },
4938
+ serialize() {
4939
+ return { version, data: atom.get() };
4940
+ },
4941
+ hydrate(payload) {
4942
+ if (payload.version > version) {
4943
+ throw new StoreVersionTooNewError(id, payload.version, version);
4944
+ }
4945
+ if (payload.version < version) {
4946
+ if (!opts?.migrate) {
4947
+ throw new StoreMigrationMissingError(id, payload.version, version);
4948
+ }
4949
+ const migrated = opts.migrate(payload.data, payload.version);
4950
+ if (typeof migrated !== "number") {
4951
+ throw new Error(
4952
+ `defineCounter "${id}".hydrate: migrate returned non-number ${typeof migrated}`
4953
+ );
4954
+ }
4955
+ atom.set(migrated);
4956
+ return;
4957
+ }
4958
+ if (typeof payload.data !== "number") {
4959
+ throw new Error(
4960
+ `defineCounter "${id}".hydrate: expected number, got ${typeof payload.data}`
4961
+ );
4962
+ }
4963
+ atom.set(payload.data);
4964
+ }
4965
+ };
4966
+ register({ id, reset: /* @__PURE__ */ __name(() => store.reset(), "reset") });
4967
+ return store;
4968
+ }
4969
+ __name(defineCounter, "defineCounter");
4970
+
4439
4971
  // src/index.ts
4440
4972
  var VERSION = "0.0.0";
4441
4973
  export {
@@ -4484,6 +5016,8 @@ export {
4484
5016
  Sequence,
4485
5017
  SerializableRegistry,
4486
5018
  ServiceKey,
5019
+ StoreMigrationMissingError,
5020
+ StoreVersionTooNewError,
4487
5021
  System,
4488
5022
  SystemScheduler,
4489
5023
  SystemSchedulerKey,
@@ -4493,16 +5027,25 @@ export {
4493
5027
  Tween,
4494
5028
  VERSION,
4495
5029
  Vec2,
5030
+ _clearStoreRegistryForTesting,
5031
+ _resetAllStoresForTesting,
4496
5032
  _resetEntityIdCounter,
4497
5033
  advanceFrames,
5034
+ createAtom,
4498
5035
  createDefaultRandomSeed,
4499
5036
  createKeyframeTrack,
4500
5037
  createMockEntity,
4501
5038
  createMockScene,
4502
5039
  createRandomService,
5040
+ createStore,
4503
5041
  createTestEngine,
5042
+ dateCodec,
4504
5043
  defineBlueprint,
5044
+ defineCounter,
4505
5045
  defineEvent,
5046
+ defineMap,
5047
+ defineSet,
5048
+ defineStore,
4506
5049
  defineTrait,
4507
5050
  easeInOutQuad,
4508
5051
  easeInQuad,
@@ -4513,13 +5056,19 @@ export {
4513
5056
  getSerializableType,
4514
5057
  globalRandom,
4515
5058
  interpolate,
5059
+ isPointerConsumeContainer,
4516
5060
  isSerializable,
5061
+ jsonCodec,
4517
5062
  makeEntityScopedQueue,
4518
5063
  makeGlobalScopedQueue,
4519
5064
  makeSceneScopedQueue,
5065
+ mapCodec,
5066
+ markPointerConsumeContainer,
4520
5067
  normalizeSeed,
4521
5068
  resolveTransition,
4522
5069
  serializable,
4523
- trait
5070
+ setCodec,
5071
+ trait,
5072
+ unmarkPointerConsumeContainer
4524
5073
  };
4525
5074
  //# sourceMappingURL=index.js.map