@vibecook/ice 0.8.0 → 0.9.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.
@@ -16,7 +16,7 @@ import {
16
16
  createSiblingOrderIndex,
17
17
  defineQuery,
18
18
  widgets
19
- } from "./chunk-BE4F4X3Q.js";
19
+ } from "./chunk-LBRHND4G.js";
20
20
  import {
21
21
  planeCssTransform,
22
22
  worldToScreen
@@ -1160,4 +1160,4 @@ export {
1160
1160
  wireMeasurement,
1161
1161
  DOM_VERSION
1162
1162
  };
1163
- //# sourceMappingURL=chunk-CBCSIEXW.js.map
1163
+ //# sourceMappingURL=chunk-E33I7R2Z.js.map
@@ -12,7 +12,7 @@ import {
12
12
  keyboardClaimOf,
13
13
  startRafLoop,
14
14
  wireMeasurement
15
- } from "./chunk-CBCSIEXW.js";
15
+ } from "./chunk-E33I7R2Z.js";
16
16
  import {
17
17
  ActiveTool,
18
18
  DurableUndoStatus,
@@ -33,7 +33,7 @@ import {
33
33
  tools,
34
34
  widgets,
35
35
  writeRuntimeResource
36
- } from "./chunk-BE4F4X3Q.js";
36
+ } from "./chunk-LBRHND4G.js";
37
37
 
38
38
  // ../react/src/hooks.ts
39
39
  import { createContext, useCallback, useContext, useMemo, useRef, useSyncExternalStore } from "react";
@@ -673,4 +673,4 @@ export {
673
673
  InfiniteCanvas,
674
674
  REACT_VERSION
675
675
  };
676
- //# sourceMappingURL=chunk-3SUDKHUF.js.map
676
+ //# sourceMappingURL=chunk-HM25ULBP.js.map
@@ -1623,6 +1623,10 @@ function defaultsOf(schema) {
1623
1623
  for (const [name, spec] of Object.entries(schema)) out[name] = defaultValueOf(spec);
1624
1624
  return out;
1625
1625
  }
1626
+ var utf8 = new TextEncoder();
1627
+ function facetBytesOf(cell) {
1628
+ return utf8.encode(JSON.stringify(cell)).length;
1629
+ }
1626
1630
  function signatureOf(spec, phase2) {
1627
1631
  const schema = spec.schema ?? {};
1628
1632
  return JSON.stringify({
@@ -1632,6 +1636,11 @@ function signatureOf(spec, phase2) {
1632
1636
  version: spec.version ?? 1,
1633
1637
  phase: phase2,
1634
1638
  tickWhile: spec.tick?.while ?? "all",
1639
+ // The I19 byte claim is IDENTITY-BEARING: a redefinition that changes only
1640
+ // the claim is a different declaration (hosts compare descriptors; the
1641
+ // ensure-cache must agree). `?? null` because JSON.stringify drops
1642
+ // undefined-valued keys, which would alias "no claim" with "absent field".
1643
+ maxFacetBytes: spec.maxFacetBytes ?? null,
1635
1644
  // Field ORDER is part of the shape: it is the generated component's column
1636
1645
  // order, which the durable cell layout depends on.
1637
1646
  schema: Object.entries(schema).map(([k, s]) => [k, s.kind, defaultValueOf(s)]),
@@ -1679,6 +1688,17 @@ function validate(name, spec, phase2) {
1679
1688
  if (spec.budgetMs !== void 0 && (!Number.isFinite(spec.budgetMs) || spec.budgetMs <= 0)) {
1680
1689
  fail2(name, `budgetMs must be a positive number (got ${String(spec.budgetMs)}).`);
1681
1690
  }
1691
+ if (spec.maxFacetBytes !== void 0) {
1692
+ if (spec.store !== "ephemeral") {
1693
+ fail2(
1694
+ name,
1695
+ `"maxFacetBytes" is ephemeral-only \u2014 it bounds the published presence facet, and only ephemeral behaviors publish one (petition I19).`
1696
+ );
1697
+ }
1698
+ if (!Number.isSafeInteger(spec.maxFacetBytes) || spec.maxFacetBytes <= 0) {
1699
+ fail2(name, `maxFacetBytes must be a positive integer byte count (got ${String(spec.maxFacetBytes)}).`);
1700
+ }
1701
+ }
1682
1702
  for (const [fieldName, s] of Object.entries(spec.schema ?? {})) {
1683
1703
  if (fieldName.length === 0) fail2(name, "schema field names must be non-empty.");
1684
1704
  if (typeof s !== "object" || s === null || typeof s.kind !== "string") {
@@ -1817,6 +1837,7 @@ function describeBehavior(behavior) {
1817
1837
  version: behavior.version,
1818
1838
  phase: behavior.phase,
1819
1839
  ...behavior.budgetMs === void 0 ? {} : { budgetMs: behavior.budgetMs },
1840
+ ...behavior.maxFacetBytes === void 0 ? {} : { maxFacetBytes: behavior.maxFacetBytes },
1820
1841
  tickWhile: behavior.tickWhile,
1821
1842
  schema: Object.entries(behavior.schema).map(([name, spec]) => ({
1822
1843
  name,
@@ -1845,6 +1866,15 @@ function defineBehavior(name, spec) {
1845
1866
  return existing;
1846
1867
  }
1847
1868
  const schema = spec.schema ?? {};
1869
+ const defaults = defaultsOf(schema);
1870
+ if (spec.maxFacetBytes !== void 0 && spec.store === "ephemeral") {
1871
+ const bytes = facetBytesOf(defaults);
1872
+ if (bytes > spec.maxFacetBytes) {
1873
+ throw new Error(
1874
+ `ice: defineBehavior("${name}") \u2014 the default facet is ${bytes} bytes, over the behavior's own maxFacetBytes=${spec.maxFacetBytes} claim. Nothing was registered.`
1875
+ );
1876
+ }
1877
+ }
1848
1878
  const raw = {};
1849
1879
  for (const [fieldName, s] of Object.entries(schema)) raw[fieldName] = strataFieldOf(name, fieldName, s);
1850
1880
  const component = ensureComponent(behaviorComponentName(name), raw);
@@ -1857,9 +1887,10 @@ function defineBehavior(name, spec) {
1857
1887
  version: spec.version ?? 1,
1858
1888
  phase: phase2,
1859
1889
  budgetMs: spec.budgetMs,
1890
+ maxFacetBytes: spec.maxFacetBytes,
1860
1891
  schema,
1861
1892
  component,
1862
- defaults: defaultsOf(schema),
1893
+ defaults,
1863
1894
  reads: spec.reads ?? [],
1864
1895
  writes: spec.writes ?? [],
1865
1896
  migrate: spec.migrate ?? {},
@@ -7920,7 +7951,7 @@ function createBehaviorRuntime(opts) {
7920
7951
  if (presence === void 0) return;
7921
7952
  const peer = presence.localPeer;
7922
7953
  if (!(world.isAlive(peer) && world.has(peer, this.own))) return;
7923
- const midTick = world.inImmediateProjectionUnsafeContext ?? false;
7954
+ const midTick = world.inImmediateProjectionUnsafeContext;
7924
7955
  if (midTick) {
7925
7956
  const behaviorName = this.b.name;
7926
7957
  const own = this.own;
@@ -8486,6 +8517,15 @@ function createBehaviorRuntime(opts) {
8486
8517
  if (!world.isAlive(peer)) return;
8487
8518
  if (this.hookEntity !== void 0 && this.hookEntity !== peer) return;
8488
8519
  const cell = cellForBehavior(world, this.b, peer, patch);
8520
+ const bound = this.b.maxFacetBytes;
8521
+ if (bound !== void 0) {
8522
+ const bytes = facetBytesOf(cell);
8523
+ if (bytes > bound) {
8524
+ throw new Error(
8525
+ `ice: behavior "${this.b.name}" ctx.write produced a ${bytes}-byte facet, over its declared maxFacetBytes=${bound} \u2014 the write was refused and the prior facet left intact.`
8526
+ );
8527
+ }
8528
+ }
8489
8529
  if (world.has(peer, this.own)) presence.eph.edit(peer).set(this.own, cell);
8490
8530
  else presence.eph.addComponent(peer, this.own, cell);
8491
8531
  }
@@ -8627,6 +8667,11 @@ function createBehaviorRuntime(opts) {
8627
8667
  },
8628
8668
  attach(e, behavior, data) {
8629
8669
  const other = behavior;
8670
+ if (other.store === "ephemeral") {
8671
+ throw new Error(
8672
+ `ice: behavior "${node.b.name}" called tx.attach("${other.name}") \u2014 an ephemeral behavior's facet is presence data and cannot be attached into the document.`
8673
+ );
8674
+ }
8630
8675
  const cell = cellForBehavior(world, other, e, data ?? {});
8631
8676
  if (world.has(e, other.component)) {
8632
8677
  tx.edit(e).set(other.component, cell);
@@ -8635,6 +8680,11 @@ function createBehaviorRuntime(opts) {
8635
8680
  }
8636
8681
  },
8637
8682
  detach(e, behavior) {
8683
+ if (behavior.store === "ephemeral") {
8684
+ throw new Error(
8685
+ `ice: behavior "${node.b.name}" called tx.detach("${behavior.name}") \u2014 ephemeral facets are withdrawn by the runtime (I17) or the presence layer's tombstones, never through the document.`
8686
+ );
8687
+ }
8638
8688
  tx.removeComponent(e, behavior.component);
8639
8689
  }
8640
8690
  };
@@ -8710,6 +8760,11 @@ function createBehaviorRuntime(opts) {
8710
8760
  `ice: engine.behaviors.attach("${b.name}") \u2014 durable attachment is a DOCUMENT op: use tx.attach inside a transaction so it syncs and undoes.`
8711
8761
  );
8712
8762
  }
8763
+ if (b.store === "ephemeral") {
8764
+ throw new Error(
8765
+ `ice: engine.behaviors.attach("${b.name}") \u2014 an ephemeral behavior's facet is minted by the runtime on the local peer; it cannot be attached to an entity.`
8766
+ );
8767
+ }
8713
8768
  const cell = cellForBehavior(world, b, e, data ?? {});
8714
8769
  if (world.has(e, b.component)) world.edit(e).set(b.component, cell);
8715
8770
  else world.addComponent(e, b.component, cell);
@@ -8720,6 +8775,11 @@ function createBehaviorRuntime(opts) {
8720
8775
  `ice: engine.behaviors.detach("${b.name}") \u2014 durable detachment is a DOCUMENT op: use tx.detach inside a transaction.`
8721
8776
  );
8722
8777
  }
8778
+ if (b.store === "ephemeral") {
8779
+ throw new Error(
8780
+ `ice: engine.behaviors.detach("${b.name}") \u2014 ephemeral facets are withdrawn by the runtime (I17) or the presence layer's tombstones, never detached through the world.`
8781
+ );
8782
+ }
8723
8783
  if (world.has(e, b.component)) world.removeComponent(e, b.component);
8724
8784
  },
8725
8785
  has: (e, b) => world.isAlive(e) && world.has(e, b.component),
@@ -9986,7 +10046,7 @@ var K_SNAPSHOT_OFFER = 2;
9986
10046
  var K_SNAPSHOT = 3;
9987
10047
  var K_UPDATE = 4;
9988
10048
  var K_PRESENCE = 5;
9989
- var utf8 = new TextEncoder();
10049
+ var utf82 = new TextEncoder();
9990
10050
  var fromUtf8 = new TextDecoder();
9991
10051
  var defaultClock = {
9992
10052
  setTimeout: (fn, ms) => setTimeout(fn, ms),
@@ -10000,7 +10060,7 @@ function frame(kind, payload) {
10000
10060
  return out;
10001
10061
  }
10002
10062
  function frameSnapshot(targetId, snapshot) {
10003
- const id = utf8.encode(targetId);
10063
+ const id = utf82.encode(targetId);
10004
10064
  const out = new Uint8Array(1 + 2 + id.length + snapshot.length);
10005
10065
  out[0] = K_SNAPSHOT;
10006
10066
  out[1] = id.length & 255;
@@ -10174,7 +10234,7 @@ function joinDoc(world, channel, opts = {}) {
10174
10234
  active = false;
10175
10235
  buffered.length = 0;
10176
10236
  opts.onSession?.(void 0);
10177
- send(frame(K_HELLO, utf8.encode(joinerId)));
10237
+ send(frame(K_HELLO, utf82.encode(joinerId)));
10178
10238
  armTimeout();
10179
10239
  };
10180
10240
  const onFrame = (bytes) => {
@@ -10222,7 +10282,7 @@ function joinDoc(world, channel, opts = {}) {
10222
10282
  if (unsubPresence !== void 0) roomSubs.push(unsubPresence);
10223
10283
  const begin = () => {
10224
10284
  if (left) return;
10225
- send(frame(K_HELLO, utf8.encode(joinerId)));
10285
+ send(frame(K_HELLO, utf82.encode(joinerId)));
10226
10286
  if (left) return;
10227
10287
  armTimeout();
10228
10288
  };
@@ -10844,7 +10904,7 @@ import {
10844
10904
  Local as Local3
10845
10905
  } from "@vibecook/strata-ecs";
10846
10906
  import { DurableUndoStatus } from "@vibecook/strata-ecs/durable";
10847
- var CORE_VERSION = "0.8.0";
10907
+ var CORE_VERSION = "0.9.0";
10848
10908
 
10849
10909
  export {
10850
10910
  defineSchemaOnce,
@@ -11129,4 +11189,4 @@ export {
11129
11189
  Local3 as Local,
11130
11190
  DurableUndoStatus
11131
11191
  };
11132
- //# sourceMappingURL=chunk-BE4F4X3Q.js.map
11192
+ //# sourceMappingURL=chunk-LBRHND4G.js.map