@yagejs/core 0.5.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.cjs CHANGED
@@ -110,6 +110,8 @@ __export(index_exports, {
110
110
  Sequence: () => Sequence,
111
111
  SerializableRegistry: () => SerializableRegistry,
112
112
  ServiceKey: () => ServiceKey,
113
+ StoreMigrationMissingError: () => StoreMigrationMissingError,
114
+ StoreVersionTooNewError: () => StoreVersionTooNewError,
113
115
  System: () => System,
114
116
  SystemScheduler: () => SystemScheduler,
115
117
  SystemSchedulerKey: () => SystemSchedulerKey,
@@ -119,16 +121,25 @@ __export(index_exports, {
119
121
  Tween: () => Tween,
120
122
  VERSION: () => VERSION,
121
123
  Vec2: () => Vec2,
124
+ _clearStoreRegistryForTesting: () => _clearStoreRegistryForTesting,
125
+ _resetAllStoresForTesting: () => _resetAllStoresForTesting,
122
126
  _resetEntityIdCounter: () => _resetEntityIdCounter,
123
127
  advanceFrames: () => advanceFrames,
128
+ createAtom: () => createAtom,
124
129
  createDefaultRandomSeed: () => createDefaultRandomSeed,
125
130
  createKeyframeTrack: () => createKeyframeTrack,
126
131
  createMockEntity: () => createMockEntity,
127
132
  createMockScene: () => createMockScene,
128
133
  createRandomService: () => createRandomService,
134
+ createStore: () => createStore,
129
135
  createTestEngine: () => createTestEngine,
136
+ dateCodec: () => dateCodec,
130
137
  defineBlueprint: () => defineBlueprint,
138
+ defineCounter: () => defineCounter,
131
139
  defineEvent: () => defineEvent,
140
+ defineMap: () => defineMap,
141
+ defineSet: () => defineSet,
142
+ defineStore: () => defineStore,
132
143
  defineTrait: () => defineTrait,
133
144
  easeInOutQuad: () => easeInOutQuad,
134
145
  easeInQuad: () => easeInQuad,
@@ -141,13 +152,16 @@ __export(index_exports, {
141
152
  interpolate: () => interpolate,
142
153
  isPointerConsumeContainer: () => isPointerConsumeContainer,
143
154
  isSerializable: () => isSerializable,
155
+ jsonCodec: () => jsonCodec,
144
156
  makeEntityScopedQueue: () => makeEntityScopedQueue,
145
157
  makeGlobalScopedQueue: () => makeGlobalScopedQueue,
146
158
  makeSceneScopedQueue: () => makeSceneScopedQueue,
159
+ mapCodec: () => mapCodec,
147
160
  markPointerConsumeContainer: () => markPointerConsumeContainer,
148
161
  normalizeSeed: () => normalizeSeed,
149
162
  resolveTransition: () => resolveTransition,
150
163
  serializable: () => serializable,
164
+ setCodec: () => setCodec,
151
165
  trait: () => trait,
152
166
  unmarkPointerConsumeContainer: () => unmarkPointerConsumeContainer
153
167
  });
@@ -1243,6 +1257,13 @@ var Entity = class {
1243
1257
  name;
1244
1258
  /** Tags for group queries. */
1245
1259
  tags;
1260
+ /**
1261
+ * Stable identity key, scene-scoped. Set at spawn-time when
1262
+ * `options.key` is passed to `scene.spawn` / `entity.spawnChild`;
1263
+ * `undefined` otherwise. Used with `scene.findByKey` and as a stable
1264
+ * id in persistent stores (e.g. `defineSet<string>("world.opened")`).
1265
+ */
1266
+ key;
1246
1267
  components = /* @__PURE__ */ new Map();
1247
1268
  _destroyed = false;
1248
1269
  _scene = null;
@@ -1313,17 +1334,21 @@ var Entity = class {
1313
1334
  this._scene._addExistingEntity(child);
1314
1335
  }
1315
1336
  }
1316
- spawnChild(name, classOrBlueprint, params) {
1337
+ spawnChild(name, classOrBlueprintOrOptions, paramsOrOptions, maybeOptions) {
1317
1338
  const scene = this.scene;
1318
1339
  if (this._children?.has(name)) {
1319
1340
  throw new Error(
1320
1341
  `Entity "${this.name}" already has a child named "${name}".`
1321
1342
  );
1322
1343
  }
1323
- const child = classOrBlueprint === void 0 ? scene.spawn(name) : scene.spawn(
1324
- classOrBlueprint,
1325
- params
1326
- );
1344
+ let child;
1345
+ if (classOrBlueprintOrOptions === void 0) {
1346
+ child = scene.spawn(name);
1347
+ } else if (typeof classOrBlueprintOrOptions === "object" && !("build" in classOrBlueprintOrOptions)) {
1348
+ child = scene.spawn(name, classOrBlueprintOrOptions);
1349
+ } else {
1350
+ child = scene.spawn(classOrBlueprintOrOptions, paramsOrOptions, maybeOptions);
1351
+ }
1327
1352
  this.addChild(name, child);
1328
1353
  return child;
1329
1354
  }
@@ -1466,6 +1491,19 @@ var Entity = class {
1466
1491
  }
1467
1492
  return false;
1468
1493
  }
1494
+ /**
1495
+ * Return the stable key, or throw if this entity was spawned without one.
1496
+ * Use inside component `setup()` when the component depends on identity
1497
+ * (e.g. reading from a `defineSet` keyed by entity id).
1498
+ */
1499
+ requireKey() {
1500
+ if (this.key === void 0) {
1501
+ throw new Error(
1502
+ `Entity "${this.name}" (id=${this.id}) has no stable key. Pass { key: "..." } to scene.spawn(...) or entity.spawnChild(...).`
1503
+ );
1504
+ }
1505
+ return this.key;
1506
+ }
1469
1507
  /**
1470
1508
  * Internal: set the scene and callbacks. Called by Scene.spawn().
1471
1509
  * @internal
@@ -1474,6 +1512,20 @@ var Entity = class {
1474
1512
  this._scene = scene;
1475
1513
  this.callbacks = callbacks;
1476
1514
  }
1515
+ /**
1516
+ * Internal: assign the stable identity key. Called by `Scene._registerKey`
1517
+ * during spawn. Throws if the entity already has a key — keys are
1518
+ * immutable for an entity's lifetime.
1519
+ * @internal
1520
+ */
1521
+ _setKey(key) {
1522
+ if (this.key !== void 0) {
1523
+ throw new Error(
1524
+ `Entity "${this.name}" already has key "${this.key}".`
1525
+ );
1526
+ }
1527
+ this.key = key;
1528
+ }
1477
1529
  };
1478
1530
 
1479
1531
  // src/QueryCache.ts
@@ -2656,6 +2708,17 @@ function decodeBase64(base64) {
2656
2708
  __name(decodeBase64, "decodeBase64");
2657
2709
 
2658
2710
  // src/Scene.ts
2711
+ var _SPAWN_OPTION_KEYS = /* @__PURE__ */ new Set(["key"]);
2712
+ function _looksLikeSpawnOptions(v) {
2713
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
2714
+ const keys = Object.keys(v);
2715
+ if (keys.length === 0) return false;
2716
+ for (const k of keys) {
2717
+ if (!_SPAWN_OPTION_KEYS.has(k)) return false;
2718
+ }
2719
+ return true;
2720
+ }
2721
+ __name(_looksLikeSpawnOptions, "_looksLikeSpawnOptions");
2659
2722
  var Scene = class {
2660
2723
  static {
2661
2724
  __name(this, "Scene");
@@ -2681,6 +2744,7 @@ var Scene = class {
2681
2744
  _entityEventHandlers;
2682
2745
  _entityEventObserver;
2683
2746
  _scopedServices;
2747
+ _identityIndex;
2684
2748
  /** Access the EngineContext. */
2685
2749
  get context() {
2686
2750
  return this._context;
@@ -2729,10 +2793,26 @@ var Scene = class {
2729
2793
  }, "set")
2730
2794
  });
2731
2795
  }
2732
- spawn(nameOrBlueprintOrClass, params) {
2796
+ spawn(nameOrBlueprintOrClass, paramsOrOptions, maybeOptions) {
2733
2797
  if (typeof nameOrBlueprintOrClass === "function") {
2734
- const entity2 = new nameOrBlueprintOrClass();
2798
+ const Ctor = nameOrBlueprintOrClass;
2799
+ const hasSetup = typeof Ctor.prototype.setup === "function";
2800
+ let params;
2801
+ let options2;
2802
+ if (maybeOptions !== void 0) {
2803
+ params = paramsOrOptions;
2804
+ options2 = maybeOptions;
2805
+ } else if (paramsOrOptions === void 0) {
2806
+ } else if (!hasSetup) {
2807
+ options2 = paramsOrOptions;
2808
+ } else if (_looksLikeSpawnOptions(paramsOrOptions)) {
2809
+ options2 = paramsOrOptions;
2810
+ } else {
2811
+ params = paramsOrOptions;
2812
+ }
2813
+ const entity2 = new Ctor();
2735
2814
  entity2._setScene(this, this.entityCallbacks);
2815
+ if (options2?.key !== void 0) this._registerKey(entity2, options2.key);
2736
2816
  this.entities.add(entity2);
2737
2817
  this.bus?.emit("entity:created", { entity: entity2 });
2738
2818
  entity2.setup?.(params);
@@ -2740,15 +2820,59 @@ var Scene = class {
2740
2820
  }
2741
2821
  const isBlueprint = typeof nameOrBlueprintOrClass === "object" && nameOrBlueprintOrClass !== null && "build" in nameOrBlueprintOrClass;
2742
2822
  const name = isBlueprint ? nameOrBlueprintOrClass.name : nameOrBlueprintOrClass;
2823
+ let blueprintParams;
2824
+ let options;
2825
+ if (isBlueprint) {
2826
+ if (maybeOptions !== void 0) {
2827
+ blueprintParams = paramsOrOptions;
2828
+ options = maybeOptions;
2829
+ } else if (paramsOrOptions !== void 0 && _looksLikeSpawnOptions(paramsOrOptions)) {
2830
+ options = paramsOrOptions;
2831
+ } else {
2832
+ blueprintParams = paramsOrOptions;
2833
+ }
2834
+ } else {
2835
+ options = paramsOrOptions;
2836
+ }
2743
2837
  const entity = new Entity(name);
2744
2838
  entity._setScene(this, this.entityCallbacks);
2839
+ if (options?.key !== void 0) this._registerKey(entity, options.key);
2745
2840
  this.entities.add(entity);
2746
2841
  this.bus?.emit("entity:created", { entity });
2747
2842
  if (isBlueprint) {
2748
- nameOrBlueprintOrClass.build(entity, params);
2843
+ nameOrBlueprintOrClass.build(
2844
+ entity,
2845
+ blueprintParams
2846
+ );
2749
2847
  }
2750
2848
  return entity;
2751
2849
  }
2850
+ /**
2851
+ * Look up an entity by its stable identity key, scoped to this scene.
2852
+ * Returns `undefined` for unknown or already-destroyed entities.
2853
+ */
2854
+ findByKey(key) {
2855
+ const entity = this._identityIndex?.get(key);
2856
+ if (!entity || entity.isDestroyed) return void 0;
2857
+ return entity;
2858
+ }
2859
+ /**
2860
+ * Internal: register a key on a freshly spawned entity. Throws on
2861
+ * duplicate so callers (Scene.spawn) can abort before adding to
2862
+ * `this.entities` or emitting `entity:created`.
2863
+ * @internal
2864
+ */
2865
+ _registerKey(entity, key) {
2866
+ this._identityIndex ??= /* @__PURE__ */ new Map();
2867
+ const existing = this._identityIndex.get(key);
2868
+ if (existing && !existing.isDestroyed) {
2869
+ throw new Error(
2870
+ `Scene "${this.name}" already has an entity with key "${key}". Destroy it before spawning a duplicate.`
2871
+ );
2872
+ }
2873
+ entity._setKey(key);
2874
+ this._identityIndex.set(key, entity);
2875
+ }
2752
2876
  /**
2753
2877
  * Add an existing entity to this scene (used by Entity.addChild for auto-scene-membership).
2754
2878
  * @internal
@@ -2899,12 +3023,17 @@ var Scene = class {
2899
3023
  entity._performDestroy();
2900
3024
  this.queryCache?.onEntityDestroyed(entity);
2901
3025
  this.entities.delete(entity);
3026
+ if (entity.key !== void 0 && this._identityIndex?.get(entity.key) === entity) {
3027
+ this._identityIndex.delete(entity.key);
3028
+ }
2902
3029
  this.bus?.emit("entity:destroyed", { entity });
2903
3030
  }
2904
3031
  this.destroyQueue.length = 0;
2905
3032
  }
2906
3033
  /**
2907
- * Destroy all entities — used during scene exit.
3034
+ * Destroy all entities — used during scene exit. Clears the identity
3035
+ * index in bulk; per-entity key removal in `_flushDestroyQueue` is the
3036
+ * in-game path.
2908
3037
  * @internal
2909
3038
  */
2910
3039
  _destroyAllEntities() {
@@ -2915,6 +3044,7 @@ var Scene = class {
2915
3044
  this.entities.clear();
2916
3045
  this.destroyQueue.length = 0;
2917
3046
  this._entityEventHandlers?.clear();
3047
+ this._identityIndex?.clear();
2918
3048
  }
2919
3049
  };
2920
3050
 
@@ -4578,6 +4708,387 @@ function advanceFrames(engine, n, dtMs = 1e3 / 60) {
4578
4708
  }
4579
4709
  __name(advanceFrames, "advanceFrames");
4580
4710
 
4711
+ // src/state/Atom.ts
4712
+ function createAtom(initial) {
4713
+ let value = initial;
4714
+ const listeners = /* @__PURE__ */ new Set();
4715
+ return {
4716
+ get() {
4717
+ return value;
4718
+ },
4719
+ set(next) {
4720
+ if (Object.is(value, next)) return;
4721
+ value = next;
4722
+ for (const fn of listeners) fn(value);
4723
+ },
4724
+ subscribe(listener) {
4725
+ listeners.add(listener);
4726
+ return () => {
4727
+ listeners.delete(listener);
4728
+ };
4729
+ }
4730
+ };
4731
+ }
4732
+ __name(createAtom, "createAtom");
4733
+
4734
+ // src/state/Store.ts
4735
+ function createStore(initial) {
4736
+ let snapshot = { ...initial };
4737
+ const listeners = /* @__PURE__ */ new Set();
4738
+ return {
4739
+ get() {
4740
+ return snapshot;
4741
+ },
4742
+ set(partial) {
4743
+ let changed = false;
4744
+ for (const key of Object.keys(partial)) {
4745
+ if (!Object.is(snapshot[key], partial[key])) {
4746
+ changed = true;
4747
+ break;
4748
+ }
4749
+ }
4750
+ if (!changed) return;
4751
+ snapshot = { ...snapshot, ...partial };
4752
+ for (const fn of listeners) fn();
4753
+ },
4754
+ subscribe(listener) {
4755
+ listeners.add(listener);
4756
+ return () => {
4757
+ listeners.delete(listener);
4758
+ };
4759
+ }
4760
+ };
4761
+ }
4762
+ __name(createStore, "createStore");
4763
+
4764
+ // src/state/codecs.ts
4765
+ function jsonCodec() {
4766
+ return {
4767
+ encode: /* @__PURE__ */ __name((value) => value, "encode"),
4768
+ decode: /* @__PURE__ */ __name((raw) => raw, "decode")
4769
+ };
4770
+ }
4771
+ __name(jsonCodec, "jsonCodec");
4772
+ function setCodec() {
4773
+ return {
4774
+ encode: /* @__PURE__ */ __name((value) => Array.from(value), "encode"),
4775
+ decode: /* @__PURE__ */ __name((raw) => {
4776
+ if (!Array.isArray(raw)) {
4777
+ throw new Error("setCodec.decode: expected an array");
4778
+ }
4779
+ return new Set(raw);
4780
+ }, "decode")
4781
+ };
4782
+ }
4783
+ __name(setCodec, "setCodec");
4784
+ function mapCodec() {
4785
+ return {
4786
+ encode: /* @__PURE__ */ __name((value) => Array.from(value.entries()), "encode"),
4787
+ decode: /* @__PURE__ */ __name((raw) => {
4788
+ if (!Array.isArray(raw)) {
4789
+ throw new Error("mapCodec.decode: expected an array of entries");
4790
+ }
4791
+ return new Map(raw);
4792
+ }, "decode")
4793
+ };
4794
+ }
4795
+ __name(mapCodec, "mapCodec");
4796
+ function dateCodec() {
4797
+ return {
4798
+ encode: /* @__PURE__ */ __name((value) => value.toISOString(), "encode"),
4799
+ decode: /* @__PURE__ */ __name((raw) => {
4800
+ if (typeof raw !== "string") {
4801
+ throw new Error("dateCodec.decode: expected an ISO string");
4802
+ }
4803
+ const d = new Date(raw);
4804
+ if (Number.isNaN(d.getTime())) {
4805
+ throw new Error(`dateCodec.decode: invalid ISO string ${JSON.stringify(raw)}`);
4806
+ }
4807
+ return d;
4808
+ }, "decode")
4809
+ };
4810
+ }
4811
+ __name(dateCodec, "dateCodec");
4812
+
4813
+ // src/state/persistent.ts
4814
+ var registry3 = /* @__PURE__ */ new Map();
4815
+ function register(entry) {
4816
+ registry3.set(entry.id, entry);
4817
+ }
4818
+ __name(register, "register");
4819
+ function _resetAllStoresForTesting() {
4820
+ for (const entry of registry3.values()) entry.reset();
4821
+ }
4822
+ __name(_resetAllStoresForTesting, "_resetAllStoresForTesting");
4823
+ function _clearStoreRegistryForTesting() {
4824
+ registry3.clear();
4825
+ }
4826
+ __name(_clearStoreRegistryForTesting, "_clearStoreRegistryForTesting");
4827
+ var StoreVersionTooNewError = class extends Error {
4828
+ static {
4829
+ __name(this, "StoreVersionTooNewError");
4830
+ }
4831
+ storeId;
4832
+ storedVersion;
4833
+ currentVersion;
4834
+ constructor(storeId, storedVersion, currentVersion) {
4835
+ super(
4836
+ `Store "${storeId}" was saved at version ${storedVersion}, but this build is at version ${currentVersion}. Cannot downgrade.`
4837
+ );
4838
+ this.name = "StoreVersionTooNewError";
4839
+ this.storeId = storeId;
4840
+ this.storedVersion = storedVersion;
4841
+ this.currentVersion = currentVersion;
4842
+ }
4843
+ };
4844
+ var StoreMigrationMissingError = class extends Error {
4845
+ static {
4846
+ __name(this, "StoreMigrationMissingError");
4847
+ }
4848
+ storeId;
4849
+ storedVersion;
4850
+ currentVersion;
4851
+ constructor(storeId, storedVersion, currentVersion) {
4852
+ super(
4853
+ `Store "${storeId}" needs migration from version ${storedVersion} to ${currentVersion}, but no migrate() was provided.`
4854
+ );
4855
+ this.name = "StoreMigrationMissingError";
4856
+ this.storeId = storeId;
4857
+ this.storedVersion = storedVersion;
4858
+ this.currentVersion = currentVersion;
4859
+ }
4860
+ };
4861
+ function defineStore(id, opts) {
4862
+ const version = opts.version ?? 1;
4863
+ const codec = opts.codec ?? jsonCodec();
4864
+ const defaults = opts.defaults;
4865
+ const inner = createStore(defaults());
4866
+ const replaceAll = /* @__PURE__ */ __name((next) => {
4867
+ inner.set({ ...next });
4868
+ }, "replaceAll");
4869
+ const store = {
4870
+ id,
4871
+ version,
4872
+ get: inner.get,
4873
+ set: inner.set,
4874
+ subscribe: inner.subscribe,
4875
+ reset() {
4876
+ replaceAll(defaults());
4877
+ },
4878
+ serialize() {
4879
+ return { version, data: codec.encode(inner.get()) };
4880
+ },
4881
+ hydrate(payload) {
4882
+ if (payload.version > version) {
4883
+ throw new StoreVersionTooNewError(id, payload.version, version);
4884
+ }
4885
+ let next;
4886
+ if (payload.version < version) {
4887
+ if (!opts.migrate) {
4888
+ throw new StoreMigrationMissingError(id, payload.version, version);
4889
+ }
4890
+ next = opts.migrate(payload.data, payload.version);
4891
+ } else {
4892
+ next = codec.decode(payload.data);
4893
+ }
4894
+ replaceAll(next);
4895
+ }
4896
+ };
4897
+ register({ id, reset: /* @__PURE__ */ __name(() => store.reset(), "reset") });
4898
+ return store;
4899
+ }
4900
+ __name(defineStore, "defineStore");
4901
+ function defineSet(id, opts) {
4902
+ const version = opts?.version ?? 1;
4903
+ const defaults = /* @__PURE__ */ __name(() => new Set(opts?.defaults?.() ?? []), "defaults");
4904
+ const codec = setCodec();
4905
+ const atom = createAtom(defaults());
4906
+ const replace = /* @__PURE__ */ __name((next) => {
4907
+ atom.set(next);
4908
+ }, "replace");
4909
+ const store = {
4910
+ id,
4911
+ version,
4912
+ has(key) {
4913
+ return atom.get().has(key);
4914
+ },
4915
+ add(key) {
4916
+ const current = atom.get();
4917
+ if (current.has(key)) return;
4918
+ const next = new Set(current);
4919
+ next.add(key);
4920
+ replace(next);
4921
+ },
4922
+ remove(key) {
4923
+ const current = atom.get();
4924
+ if (!current.has(key)) return;
4925
+ const next = new Set(current);
4926
+ next.delete(key);
4927
+ replace(next);
4928
+ },
4929
+ clear() {
4930
+ if (atom.get().size === 0) return;
4931
+ replace(/* @__PURE__ */ new Set());
4932
+ },
4933
+ size() {
4934
+ return atom.get().size;
4935
+ },
4936
+ values() {
4937
+ return atom.get().values();
4938
+ },
4939
+ subscribe(listener) {
4940
+ return atom.subscribe(() => listener());
4941
+ },
4942
+ reset() {
4943
+ replace(defaults());
4944
+ },
4945
+ serialize() {
4946
+ return { version, data: codec.encode(atom.get()) };
4947
+ },
4948
+ hydrate(payload) {
4949
+ if (payload.version > version) {
4950
+ throw new StoreVersionTooNewError(id, payload.version, version);
4951
+ }
4952
+ if (payload.version < version) {
4953
+ if (!opts?.migrate) {
4954
+ throw new StoreMigrationMissingError(id, payload.version, version);
4955
+ }
4956
+ replace(opts.migrate(payload.data, payload.version));
4957
+ return;
4958
+ }
4959
+ replace(codec.decode(payload.data));
4960
+ }
4961
+ };
4962
+ register({ id, reset: /* @__PURE__ */ __name(() => store.reset(), "reset") });
4963
+ return store;
4964
+ }
4965
+ __name(defineSet, "defineSet");
4966
+ function defineMap(id, opts) {
4967
+ const version = opts?.version ?? 1;
4968
+ const defaults = /* @__PURE__ */ __name(() => new Map(opts?.defaults?.() ?? []), "defaults");
4969
+ const codec = mapCodec();
4970
+ const atom = createAtom(defaults());
4971
+ const replace = /* @__PURE__ */ __name((next) => {
4972
+ atom.set(next);
4973
+ }, "replace");
4974
+ const store = {
4975
+ id,
4976
+ version,
4977
+ has(key) {
4978
+ return atom.get().has(key);
4979
+ },
4980
+ get(key) {
4981
+ return atom.get().get(key);
4982
+ },
4983
+ set(key, value) {
4984
+ const current = atom.get();
4985
+ if (current.has(key) && Object.is(current.get(key), value)) return;
4986
+ const next = new Map(current);
4987
+ next.set(key, value);
4988
+ replace(next);
4989
+ },
4990
+ remove(key) {
4991
+ const current = atom.get();
4992
+ if (!current.has(key)) return;
4993
+ const next = new Map(current);
4994
+ next.delete(key);
4995
+ replace(next);
4996
+ },
4997
+ clear() {
4998
+ if (atom.get().size === 0) return;
4999
+ replace(/* @__PURE__ */ new Map());
5000
+ },
5001
+ size() {
5002
+ return atom.get().size;
5003
+ },
5004
+ entries() {
5005
+ return atom.get().entries();
5006
+ },
5007
+ subscribe(listener) {
5008
+ return atom.subscribe(() => listener());
5009
+ },
5010
+ reset() {
5011
+ replace(defaults());
5012
+ },
5013
+ serialize() {
5014
+ return { version, data: codec.encode(atom.get()) };
5015
+ },
5016
+ hydrate(payload) {
5017
+ if (payload.version > version) {
5018
+ throw new StoreVersionTooNewError(id, payload.version, version);
5019
+ }
5020
+ if (payload.version < version) {
5021
+ if (!opts?.migrate) {
5022
+ throw new StoreMigrationMissingError(id, payload.version, version);
5023
+ }
5024
+ replace(opts.migrate(payload.data, payload.version));
5025
+ return;
5026
+ }
5027
+ replace(codec.decode(payload.data));
5028
+ }
5029
+ };
5030
+ register({ id, reset: /* @__PURE__ */ __name(() => store.reset(), "reset") });
5031
+ return store;
5032
+ }
5033
+ __name(defineMap, "defineMap");
5034
+ function defineCounter(id, opts) {
5035
+ const version = opts?.version ?? 1;
5036
+ const defaults = /* @__PURE__ */ __name(() => opts?.defaults?.() ?? 0, "defaults");
5037
+ const atom = createAtom(defaults());
5038
+ const store = {
5039
+ id,
5040
+ version,
5041
+ value() {
5042
+ return atom.get();
5043
+ },
5044
+ set(n) {
5045
+ atom.set(n);
5046
+ },
5047
+ increment(by = 1) {
5048
+ atom.set(atom.get() + by);
5049
+ },
5050
+ decrement(by = 1) {
5051
+ atom.set(atom.get() - by);
5052
+ },
5053
+ subscribe(listener) {
5054
+ return atom.subscribe(() => listener());
5055
+ },
5056
+ reset() {
5057
+ atom.set(defaults());
5058
+ },
5059
+ serialize() {
5060
+ return { version, data: atom.get() };
5061
+ },
5062
+ hydrate(payload) {
5063
+ if (payload.version > version) {
5064
+ throw new StoreVersionTooNewError(id, payload.version, version);
5065
+ }
5066
+ if (payload.version < version) {
5067
+ if (!opts?.migrate) {
5068
+ throw new StoreMigrationMissingError(id, payload.version, version);
5069
+ }
5070
+ const migrated = opts.migrate(payload.data, payload.version);
5071
+ if (typeof migrated !== "number") {
5072
+ throw new Error(
5073
+ `defineCounter "${id}".hydrate: migrate returned non-number ${typeof migrated}`
5074
+ );
5075
+ }
5076
+ atom.set(migrated);
5077
+ return;
5078
+ }
5079
+ if (typeof payload.data !== "number") {
5080
+ throw new Error(
5081
+ `defineCounter "${id}".hydrate: expected number, got ${typeof payload.data}`
5082
+ );
5083
+ }
5084
+ atom.set(payload.data);
5085
+ }
5086
+ };
5087
+ register({ id, reset: /* @__PURE__ */ __name(() => store.reset(), "reset") });
5088
+ return store;
5089
+ }
5090
+ __name(defineCounter, "defineCounter");
5091
+
4581
5092
  // src/index.ts
4582
5093
  var VERSION = "0.0.0";
4583
5094
  // Annotate the CommonJS export names for ESM import in node:
@@ -4627,6 +5138,8 @@ var VERSION = "0.0.0";
4627
5138
  Sequence,
4628
5139
  SerializableRegistry,
4629
5140
  ServiceKey,
5141
+ StoreMigrationMissingError,
5142
+ StoreVersionTooNewError,
4630
5143
  System,
4631
5144
  SystemScheduler,
4632
5145
  SystemSchedulerKey,
@@ -4636,16 +5149,25 @@ var VERSION = "0.0.0";
4636
5149
  Tween,
4637
5150
  VERSION,
4638
5151
  Vec2,
5152
+ _clearStoreRegistryForTesting,
5153
+ _resetAllStoresForTesting,
4639
5154
  _resetEntityIdCounter,
4640
5155
  advanceFrames,
5156
+ createAtom,
4641
5157
  createDefaultRandomSeed,
4642
5158
  createKeyframeTrack,
4643
5159
  createMockEntity,
4644
5160
  createMockScene,
4645
5161
  createRandomService,
5162
+ createStore,
4646
5163
  createTestEngine,
5164
+ dateCodec,
4647
5165
  defineBlueprint,
5166
+ defineCounter,
4648
5167
  defineEvent,
5168
+ defineMap,
5169
+ defineSet,
5170
+ defineStore,
4649
5171
  defineTrait,
4650
5172
  easeInOutQuad,
4651
5173
  easeInQuad,
@@ -4658,13 +5180,16 @@ var VERSION = "0.0.0";
4658
5180
  interpolate,
4659
5181
  isPointerConsumeContainer,
4660
5182
  isSerializable,
5183
+ jsonCodec,
4661
5184
  makeEntityScopedQueue,
4662
5185
  makeGlobalScopedQueue,
4663
5186
  makeSceneScopedQueue,
5187
+ mapCodec,
4664
5188
  markPointerConsumeContainer,
4665
5189
  normalizeSeed,
4666
5190
  resolveTransition,
4667
5191
  serializable,
5192
+ setCodec,
4668
5193
  trait,
4669
5194
  unmarkPointerConsumeContainer
4670
5195
  });