@vworlds/vecs 1.0.45 → 1.0.47

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.
Files changed (136) hide show
  1. package/README.md +7 -1
  2. package/dist/component.d.ts +57 -60
  3. package/dist/component.js +24 -17
  4. package/dist/component.js.map +1 -1
  5. package/dist/component_meta.d.ts +67 -79
  6. package/dist/component_meta.js +331 -37
  7. package/dist/component_meta.js.map +1 -1
  8. package/dist/dsl.js +1 -1
  9. package/dist/dsl.js.map +1 -1
  10. package/dist/entity/entity.base.js +4 -0
  11. package/dist/entity/entity.base.js.map +1 -1
  12. package/dist/entity/entity.components.d.ts +39 -41
  13. package/dist/entity/entity.components.js +249 -82
  14. package/dist/entity/entity.components.js.map +1 -1
  15. package/dist/entity/entity.d.ts +1 -2
  16. package/dist/entity/entity.js +2 -3
  17. package/dist/entity/entity.js.map +1 -1
  18. package/dist/entity/entity.lifecycle.d.ts +7 -1
  19. package/dist/entity/entity.lifecycle.js +40 -50
  20. package/dist/entity/entity.lifecycle.js.map +1 -1
  21. package/dist/entity/entity.queries.js +30 -0
  22. package/dist/entity/entity.queries.js.map +1 -1
  23. package/dist/entity/entity.relationships.d.ts +4 -0
  24. package/dist/entity/entity.relationships.js +18 -10
  25. package/dist/entity/entity.relationships.js.map +1 -1
  26. package/dist/index.d.ts +7 -4
  27. package/dist/index.js +4 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/inject.d.ts +63 -14
  30. package/dist/inject.js +165 -89
  31. package/dist/inject.js.map +1 -1
  32. package/dist/modules/identity.js +2 -2
  33. package/dist/modules/identity.js.map +1 -1
  34. package/dist/modules/implements.js +2 -2
  35. package/dist/modules/implements.js.map +1 -1
  36. package/dist/modules/pipeline.js +5 -5
  37. package/dist/modules/pipeline.js.map +1 -1
  38. package/dist/modules/relationship_types.d.ts +70 -6
  39. package/dist/modules/relationship_types.js +70 -6
  40. package/dist/modules/relationship_types.js.map +1 -1
  41. package/dist/modules/relationships.d.ts +31 -9
  42. package/dist/modules/relationships.js +120 -44
  43. package/dist/modules/relationships.js.map +1 -1
  44. package/dist/modules/singleton.js +2 -2
  45. package/dist/modules/singleton.js.map +1 -1
  46. package/dist/modules/snapshot.d.ts +26 -0
  47. package/dist/modules/snapshot.js +45 -0
  48. package/dist/modules/snapshot.js.map +1 -0
  49. package/dist/modules/snapshot_types.d.ts +85 -0
  50. package/dist/modules/snapshot_types.js +130 -0
  51. package/dist/modules/snapshot_types.js.map +1 -0
  52. package/dist/package.json +2 -2
  53. package/dist/query/callbacks.d.ts +9 -7
  54. package/dist/query/callbacks.js +27 -41
  55. package/dist/query/callbacks.js.map +1 -1
  56. package/dist/query/grouped_query.js +7 -7
  57. package/dist/query/grouped_query.js.map +1 -1
  58. package/dist/query/grouping.d.ts +6 -2
  59. package/dist/query/grouping.js +84 -15
  60. package/dist/query/grouping.js.map +1 -1
  61. package/dist/query/query.d.ts +4 -4
  62. package/dist/query/query.js +37 -14
  63. package/dist/query/query.js.map +1 -1
  64. package/dist/snapshot/capture.d.ts +1 -0
  65. package/dist/snapshot/capture.js +212 -0
  66. package/dist/snapshot/capture.js.map +1 -0
  67. package/dist/snapshot/clone.d.ts +1 -0
  68. package/dist/snapshot/clone.js +107 -0
  69. package/dist/snapshot/clone.js.map +1 -0
  70. package/dist/snapshot/index.d.ts +5 -0
  71. package/dist/snapshot/index.js +21 -0
  72. package/dist/snapshot/index.js.map +1 -0
  73. package/dist/snapshot/preflight.d.ts +1 -0
  74. package/dist/snapshot/preflight.js +40 -0
  75. package/dist/snapshot/preflight.js.map +1 -0
  76. package/dist/snapshot/restore.d.ts +1 -0
  77. package/dist/snapshot/restore.js +111 -0
  78. package/dist/snapshot/restore.js.map +1 -0
  79. package/dist/snapshot/types.d.ts +135 -0
  80. package/dist/snapshot/types.js +26 -0
  81. package/dist/snapshot/types.js.map +1 -0
  82. package/dist/system.d.ts +3 -1
  83. package/dist/system.js +33 -23
  84. package/dist/system.js.map +1 -1
  85. package/dist/terms/all_term.js +19 -0
  86. package/dist/terms/all_term.js.map +1 -1
  87. package/dist/terms/any_term.js +19 -0
  88. package/dist/terms/any_term.js.map +1 -1
  89. package/dist/terms/not_term.js +4 -0
  90. package/dist/terms/not_term.js.map +1 -1
  91. package/dist/terms/target_term.d.ts +1 -2
  92. package/dist/terms/target_term.js +2 -4
  93. package/dist/terms/target_term.js.map +1 -1
  94. package/dist/terms/term.d.ts +2 -0
  95. package/dist/terms/term.js +59 -10
  96. package/dist/terms/term.js.map +1 -1
  97. package/dist/util/array_map.d.ts +43 -12
  98. package/dist/util/array_map.js +90 -41
  99. package/dist/util/array_map.js.map +1 -1
  100. package/dist/util/dense_set.js +26 -0
  101. package/dist/util/dense_set.js.map +1 -1
  102. package/dist/world/world.base.js +12 -0
  103. package/dist/world/world.base.js.map +1 -1
  104. package/dist/world/world.d.ts +7 -0
  105. package/dist/world/world.deferred.js +17 -0
  106. package/dist/world/world.deferred.js.map +1 -1
  107. package/dist/world/world.entities.d.ts +13 -2
  108. package/dist/world/world.entities.js +16 -2
  109. package/dist/world/world.entities.js.map +1 -1
  110. package/dist/world/world.js +15 -3
  111. package/dist/world/world.js.map +1 -1
  112. package/dist/world/world.pipeline.d.ts +2 -2
  113. package/dist/world/world.pipeline.js +4 -2
  114. package/dist/world/world.pipeline.js.map +1 -1
  115. package/dist/world/world.queries.d.ts +7 -0
  116. package/dist/world/world.queries.js +52 -0
  117. package/dist/world/world.queries.js.map +1 -1
  118. package/dist/world/world.transfers.d.ts +137 -0
  119. package/dist/world/world.transfers.js +188 -0
  120. package/dist/world/world.transfers.js.map +1 -0
  121. package/docs/README.md +18 -6
  122. package/docs/components.md +90 -53
  123. package/docs/concepts.md +21 -17
  124. package/docs/design-guide.md +125 -94
  125. package/docs/entities.md +62 -76
  126. package/docs/execution-model.md +26 -10
  127. package/docs/getting-started.md +6 -6
  128. package/docs/glossary.md +19 -10
  129. package/docs/modules.md +8 -8
  130. package/docs/queries-and-filters.md +15 -10
  131. package/docs/relationships.md +245 -124
  132. package/docs/snapshots.md +181 -0
  133. package/docs/systems.md +64 -21
  134. package/docs/utilities.md +10 -6
  135. package/package.json +2 -2
  136. package/skills/vecs/SKILL.md +5 -4
package/README.md CHANGED
@@ -66,13 +66,19 @@ The full documentation lives in [`docs/`](./docs/README.md). Recommended reading
66
66
  3. [Execution model](./docs/execution-model.md) — phases, deferred mutation, event routing.
67
67
  4. Subsystem guides: [Components and traits](./docs/components.md) · [Entities](./docs/entities.md) ·
68
68
  [Systems](./docs/systems.md) · [Queries and filters](./docs/queries-and-filters.md) ·
69
- [Relationships, targets, and ownership](./docs/relationships.md) · [Modules](./docs/modules.md)
69
+ [Relationships, edges, targets, and ownership](./docs/relationships.md) · [Modules](./docs/modules.md) ·
70
+ [Subtree snapshots](./docs/snapshots.md)
70
71
  5. [Design guide](./docs/design-guide.md) — patterns and anti-patterns from real designs.
71
72
 
72
73
  Plus the [Utilities](./docs/utilities.md) guide for helper classes and the
73
74
  [Glossary](./docs/glossary.md) for terminology. The exhaustive per-symbol reference is the JSDoc on
74
75
  the source under [`src/`](./src/) — read it in your IDE.
75
76
 
77
+ Networking is documented alongside the packages that own it:
78
+ [Remote procedure calls](../vecs-protocol/docs/rpc.md) ·
79
+ [Sessions and caller context](../vecs-server/docs/sessions.md) ·
80
+ [Client world](../vecs-client/docs/client-world.md).
81
+
76
82
  ## Build & Test
77
83
 
78
84
  ```sh
@@ -1,75 +1,72 @@
1
1
  import { Entity } from "./entity/index.js";
2
- import type { ComponentType, HookHandler } from "./component_meta.js";
3
- export { CleanupPolicy, ComponentMeta, Implements, Relationship, type ComponentClass, type ComponentInstance, type ComponentRef, type ComponentRefArray, type ComponentType, type Hook, type HookHandler, type RetargetHandler, } from "./component_meta.js";
4
- /** A component entity created by {@link World.component}. */
2
+ import { Relationship, type ComponentInstance, type ComponentType, type HookEvent, type HookHandler, type TargetHandler } from "./component_meta.js";
3
+ import { type SnapshotCapture, type SnapshotMaterialize, ClonePublic, CustomSnapshot, DontSnapshot } from "./modules/snapshot_types.js";
4
+ export { CleanupPolicy, ComponentMeta, Implements, Relationship, type ComponentClass, type ComponentInstance, type ComponentRef, type ComponentRefArray, type ComponentType, type HookEvent, type HookEventMap, type HookHandler, type TargetHandler, } from "./component_meta.js";
5
+ /**
6
+ * A per-component event map that narrows `add` / `remove` / `set` handlers to
7
+ * the instance type `T` produces, and narrows `targetAdd` / `targetRemove` /
8
+ * `targetPolicyAdd` / `targetPolicyRemove` handlers to `T`'s relationship
9
+ * instance type when `T` produces a relationship.
10
+ */
11
+ type ComponentEventMap<T extends ComponentType> = {
12
+ add: HookHandler<InstanceType<T>>;
13
+ remove: HookHandler<InstanceType<T>>;
14
+ set: HookHandler<InstanceType<T>>;
15
+ targetAdd: TargetHandler<InstanceType<T> & Relationship>;
16
+ targetRemove: TargetHandler<InstanceType<T> & Relationship>;
17
+ targetPolicyAdd: TargetHandler<InstanceType<T> & Relationship>;
18
+ targetPolicyRemove: TargetHandler<InstanceType<T> & Relationship>;
19
+ policyAdd: HookHandler;
20
+ policyRemove: HookHandler;
21
+ };
22
+ /** Callbacks declared alongside the {@link CustomSnapshot} policy value. */
23
+ export interface CustomSnapshotOptions<T extends ComponentInstance = ComponentInstance> {
24
+ /** Convert a live component instance into detached, world-agnostic data. */
25
+ capture: SnapshotCapture;
26
+ /** Construct a fresh, destination-owned instance from previously captured data. */
27
+ materialize: SnapshotMaterialize;
28
+ }
29
+ /**
30
+ * A component entity created by {@link World.component}.
31
+ */
5
32
  export declare class Component<T extends ComponentType = ComponentType> extends Entity {
6
33
  /**
7
- * Get or set the callback fired each time this component is **added** to an entity.
8
- *
9
- * The handler is called with the owning entity and the freshly created
10
- * component instance. Use it to initialise external state (e.g. scene objects)
11
- * that should exist for the lifetime of the component.
12
- *
13
- * Call without an argument to return the current handler. Pass `undefined` to remove it.
14
- *
15
- * @param handler - Callback invoked with `(entity, componentInstance)`, or `undefined`.
16
- * @returns The current handler when called without an argument; otherwise this component.
17
- *
18
- * @example
19
- * ```ts
20
- * world.component(Sprite).onAdd((entity, sprite) => sprite.initialize(scene));
21
- * ```
34
+ * Register a handler for a hook event, with the handler signature narrowed
35
+ * to this component's instance type (for `add` / `remove` / `set`) or its
36
+ * relationship instance type (for the `target*` events). See
37
+ * {@link ComponentMeta.on} for full semantics.
22
38
  */
23
- onAdd(): HookHandler | undefined;
24
- onAdd(handler: HookHandler<InstanceType<T>> | undefined): this;
25
- onAdd(handler: HookHandler | undefined): this;
39
+ on<E extends HookEvent>(event: E, handler: ComponentEventMap<T>[E], insert?: boolean): this;
40
+ /** Remove a previously-registered handler by reference. See {@link ComponentMeta.off}. */
41
+ off<E extends HookEvent>(event: E, handler: ComponentEventMap<T>[E]): this;
26
42
  /**
27
- * Get or set the callback fired each time this component is **removed** from an
28
- * entity, or when the owning entity is destroyed.
29
- *
30
- * The handler is called with the owning entity and the component instance that
31
- * is about to be detached. Use it to clean up any external state associated
32
- * with the component.
43
+ * Declare how cross-world subtree snapshots capture this component type.
33
44
  *
34
- * Call without an argument to return the current handler. Pass `undefined` to remove it.
45
+ * Callable at any time. The declaration affects future captures only:
46
+ * snapshots already captured retain the materializer their policy resolved to
47
+ * at capture time, so changing or revoking this policy cannot alter how they
48
+ * restore.
35
49
  *
36
- * @param handler - Callback invoked with `(entity, componentInstance)`, or `undefined`.
37
- * @returns The current handler when called without an argument; otherwise this component.
50
+ * `ClonePublic` — the default — captures a fresh instance with the source's
51
+ * own enumerable fields cloned. `DontSnapshot` never captures the type.
38
52
  *
39
- * @example
40
- * ```ts
41
- * world.component(Sprite).onRemove((entity, sprite) => sprite.destroy(scene));
42
- * ```
53
+ * @param policy - `ClonePublic` or `DontSnapshot`.
54
+ * @returns This component entity, for chaining.
43
55
  */
44
- onRemove(): HookHandler | undefined;
45
- onRemove(handler: HookHandler<InstanceType<T>> | undefined): this;
46
- onRemove(handler: HookHandler | undefined): this;
56
+ snapshotPolicy(policy: typeof ClonePublic | typeof DontSnapshot): this;
47
57
  /**
48
- * Get or set the callback fired each time this component's data is **set or
49
- * modified**.
50
- *
51
- * Fires when:
52
- * - `entity.set(C, props)` applies data to the component.
53
- * - `entity.attach(instance)` stores an existing instance.
54
- * - `entity.modified(C)` is called after in-place mutation.
55
- * - A system's `getMut(C)` marks the component dirty.
56
- * - `entity.ensure(C)` creates or marks the component dirty.
57
- *
58
- * The handler receives the owning entity and the current component instance.
59
- * Component instances do not carry entity references, which is why the entity
60
- * is passed explicitly.
58
+ * Declare a bespoke capture/materialize policy for this component type.
61
59
  *
62
- * Call without an argument to return the current handler. Pass `undefined` to remove it.
60
+ * Use it when a component mixes declarative state with runtime state that must
61
+ * not cross worlds — a physics handle, a socket, a compiled shader.
63
62
  *
64
- * @param handler - Callback invoked with `(entity, componentInstance)`, or `undefined`.
65
- * @returns The current handler when called without an argument; otherwise this component.
63
+ * Callable at any time. It affects future captures only; existing snapshots
64
+ * restore through the materializer bound when they were captured.
66
65
  *
67
- * @example
68
- * ```ts
69
- * world.component(Transform).onSet((entity, t) => renderer.syncTransform(entity.eid, t));
70
- * ```
66
+ * @param CustomSnapshot - Selects the owner-supplied policy.
67
+ * @param options - The `capture` and `materialize` callbacks, stored on this
68
+ * entity's own `OnSnapshot` edge so each component type keeps its policy.
69
+ * @returns This component entity, for chaining.
71
70
  */
72
- onSet(): HookHandler | undefined;
73
- onSet(handler: HookHandler<InstanceType<T>> | undefined): this;
74
- onSet(handler: HookHandler | undefined): this;
71
+ snapshotPolicy(policy: typeof CustomSnapshot, options: CustomSnapshotOptions<InstanceType<T>>): this;
75
72
  }
package/dist/component.js CHANGED
@@ -1,26 +1,33 @@
1
1
  import { Entity } from "./entity/index.js";
2
+ import { CustomSnapshot, _declareSnapshotPolicy, } from "./modules/snapshot_types.js";
2
3
  export { CleanupPolicy, ComponentMeta, Implements, Relationship, } from "./component_meta.js";
3
- /** A component entity created by {@link World.component}. */
4
+ /**
5
+ * A component entity created by {@link World.component}.
6
+ */
4
7
  export class Component extends Entity {
5
- onAdd(handler) {
6
- if (arguments.length === 0) {
7
- return super.onAdd();
8
- }
9
- super.onAdd(handler);
10
- return this;
8
+ /**
9
+ * Register a handler for a hook event, with the handler signature narrowed
10
+ * to this component's instance type (for `add` / `remove` / `set`) or its
11
+ * relationship instance type (for the `target*` events). See
12
+ * {@link ComponentMeta.on} for full semantics.
13
+ */
14
+ on(event, handler, insert = false) {
15
+ return super.on(event, handler, insert);
11
16
  }
12
- onRemove(handler) {
13
- if (arguments.length === 0) {
14
- return super.onRemove();
15
- }
16
- super.onRemove(handler);
17
- return this;
17
+ /** Remove a previously-registered handler by reference. See {@link ComponentMeta.off}. */
18
+ off(event, handler) {
19
+ return super.off(event, handler);
18
20
  }
19
- onSet(handler) {
20
- if (arguments.length === 0) {
21
- return super.onSet();
21
+ snapshotPolicy(policy, options) {
22
+ if (policy === CustomSnapshot) {
23
+ if (options?.capture === undefined || options.materialize === undefined) {
24
+ throw new Error(`snapshotPolicy(CustomSnapshot) requires both capture and materialize callbacks for ${this.meta.Class.name}`);
25
+ }
26
+ _declareSnapshotPolicy(this, policy, options.capture, options.materialize);
27
+ }
28
+ else {
29
+ _declareSnapshotPolicy(this, policy);
22
30
  }
23
- super.onSet(handler);
24
31
  return this;
25
32
  }
26
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"component.js","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EACL,aAAa,EACb,aAAa,EACb,UAAU,EACV,YAAY,GASb,MAAM,qBAAqB,CAAC;AAE7B,6DAA6D;AAC7D,MAAM,OAAO,SAAmD,SAAQ,MAAM;IAqB5D,KAAK,CAAC,OAAa;QACjC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAuBe,QAAQ,CAAC,OAAa;QACpC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1B,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IA8Be,KAAK,CAAC,OAAa;QACjC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
1
+ {"version":3,"file":"component.js","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAU3C,OAAO,EAKL,cAAc,EAEd,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EACL,aAAa,EACb,aAAa,EACb,UAAU,EACV,YAAY,GAUb,MAAM,qBAAqB,CAAC;AA4B7B;;GAEG;AACH,MAAM,OAAO,SAAmD,SAAQ,MAAM;IAC5E;;;;;OAKG;IACa,EAAE,CAChB,KAAQ,EACR,OAAgC,EAChC,MAAM,GAAG,KAAK;QAEd,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAA0B,EAAE,MAAM,CAAC,CAAC;IAC7D,CAAC;IAED,0FAA0F;IAC1E,GAAG,CAAsB,KAAQ,EAAE,OAAgC;QACjF,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,OAA0B,CAAC,CAAC;IACtD,CAAC;IAmCM,cAAc,CACnB,MAA0B,EAC1B,OAAgD;QAEhD,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;YAC9B,IAAI,OAAO,EAAE,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;gBACxE,MAAM,IAAI,KAAK,CACb,sFAAsF,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAC7G,CAAC;YACJ,CAAC;YACD,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QAC7E,CAAC;aAAM,CAAC;YACN,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -7,14 +7,36 @@ export declare enum CleanupPolicy {
7
7
  }
8
8
  /** Base class for relationship components that point at another entity. */
9
9
  export declare abstract class Relationship {
10
- target: Entity;
10
+ private _targets;
11
11
  }
12
- /** Callback fired after a relationship moves from one existing target to another. */
13
- export type RetargetHandler = (source: Entity, oldTarget: Entity, newTarget: Entity) => void;
12
+ /** Callback fired after one relationship edge is added or removed. */
13
+ export type TargetHandler<C extends Relationship = Relationship> = (source: Entity, relationship: C, target: Entity) => void;
14
14
  /** Callback fired for a component lifecycle event. */
15
15
  export type HookHandler<C extends ComponentInstance = ComponentInstance> = (entity: Entity, component: C) => void;
16
16
  /** A component instance. Components are plain objects created with a no-arg constructor. */
17
17
  export type ComponentInstance = object;
18
+ /**
19
+ * Event → handler signature map for the unified `on` / `off` hook API.
20
+ *
21
+ * Each key is a hook event name; its value is the handler signature accepted
22
+ * by {@link ComponentMeta.on} and {@link ComponentMeta.off} for that event.
23
+ * The `target*` events require a relationship meta; registering one on a
24
+ * non-relationship meta throws at registration time.
25
+ */
26
+ export interface HookEventMap {
27
+ /** Fired when a component of this type is first attached to an entity. */
28
+ add: HookHandler;
29
+ /** Fired when a component of this type is removed from an entity. */
30
+ remove: HookHandler;
31
+ /** Fired when a component's data is set or modified. */
32
+ set: HookHandler;
33
+ /** Fired after a relationship edge is added. Relationship metas only. */
34
+ targetAdd: TargetHandler;
35
+ /** Fired after a relationship edge is removed. Relationship metas only. */
36
+ targetRemove: TargetHandler;
37
+ }
38
+ /** Event names accepted by {@link ComponentMeta.on} / {@link ComponentMeta.off}. */
39
+ export type HookEvent = keyof HookEventMap;
18
40
  /** A component constructor used as a component key. */
19
41
  export type ComponentType<T extends ComponentInstance = ComponentInstance> = abstract new (...args: any[]) => T;
20
42
  /** A component class constructor that can be instantiated by entity.add/set. */
@@ -26,76 +48,33 @@ export declare class Implements {
26
48
  interfaces: ComponentType[];
27
49
  }
28
50
  /**
29
- * Lifecycle hook for a registered component class. Exposed by
30
- * {@link World.component}.
51
+ * Lifecycle hook registration for a registered component class. Exposed by
52
+ * {@link World.component} and on every entity.
31
53
  *
32
54
  * Hooks are a lightweight alternative to building a {@link System} when all
33
55
  * you need is a callback on add / remove / set for a single component type.
34
- * Component entities expose the same registration methods, so calls chain:
56
+ * Registration chains because `on` / `off` return the receiver:
35
57
  *
36
58
  * ```ts
37
59
  * world.component(Sprite)
38
- * .onAdd((e, c) => initSprite(e, c))
39
- * .onRemove((e, c) => destroySprite(e, c))
40
- * .onSet((e, c) => syncSprite(e, c));
60
+ * .on("add", (e, c) => initSprite(e, c))
61
+ * .on("remove", (e, c) => destroySprite(e, c))
62
+ * .on("set", (e, c) => syncSprite(e, c));
41
63
  * ```
42
64
  *
65
+ * Multiple handlers may be registered for the same event: they fire in
66
+ * registration order (or in reverse registration order when `insert` is `true`).
67
+ * Remove a handler with {@link ComponentMeta.off} by the same reference used
68
+ * to register it. Callers rarely need to remove handlers.
69
+ *
43
70
  * Callbacks fire synchronously when the corresponding entity command is
44
71
  * applied: inline outside deferred mode, or while the world drains its command
45
- * queue inside a system / `forEach` / `defer` block.
46
- *
47
- * @typeParam C - Component class this hook is bound to.
72
+ * queue inside a system / `forEach` / `defer` block. An invariant violation
73
+ * thrown from a handler propagates out of the applying call (or out of
74
+ * `progress()` / `flush()` in deferred mode) and leaves the world partially
75
+ * mutated — treat it as a programming error and discard the world.
48
76
  */
49
- export interface Hook<C extends ComponentInstance = ComponentInstance> {
50
- /** Return the current add handler, or `undefined` when none is set. */
51
- onAdd(): HookHandler<C> | undefined;
52
- /**
53
- * Set the handler invoked when a component of this type is first attached to
54
- * an entity (`entity.add(C)` or `entity.set(C, ...)` on an entity that does
55
- * not yet have the component). Pass `undefined` to remove the current handler.
56
- *
57
- * @param handler - Receives the entity and freshly created component instance.
58
- * @returns This hook, for chaining.
59
- */
60
- onAdd(handler: HookHandler<C> | undefined): Hook<C>;
61
- /** Return the current remove handler, or `undefined` when none is set. */
62
- onRemove(): HookHandler<C> | undefined;
63
- /**
64
- * Set the handler invoked when a component of this type is removed from an
65
- * entity (explicit `entity.remove(C)` or implicit removal during
66
- * `entity.destroy()`). Pass `undefined` to remove the current handler.
67
- *
68
- * @param handler - Receives the entity and component instance that was removed.
69
- * @returns This hook, for chaining.
70
- */
71
- onRemove(handler: HookHandler<C> | undefined): Hook<C>;
72
- /** Return the current set handler, or `undefined` when none is set. */
73
- onSet(): HookHandler<C> | undefined;
74
- /**
75
- * Set the handler invoked when a component's data is set or modified. Pass
76
- * `undefined` to remove the current handler.
77
- *
78
- * Fires when:
79
- * - `entity.set(C, props)` applies data to the component.
80
- * - `entity.attach(instance)` stores an existing instance.
81
- * - `entity.modified(C)` is called after in-place mutation.
82
- * - A system's `getMut(C)` marks the component dirty.
83
- * - `entity.ensure(C)` creates or marks the component dirty.
84
- *
85
- * @param handler - Receives the entity and component instance whose data changed.
86
- * @returns This hook, for chaining.
87
- */
88
- onSet(handler: HookHandler<C> | undefined): Hook<C>;
89
- }
90
- /**
91
- * Bookkeeping record produced for each component eid.
92
- *
93
- * Holds the constructor, component eid, and pre-computed
94
- * {@link BitPtr} used by archetype checks. Implements {@link Hook}, so the
95
- * lifecycle handlers attached via `world.component(C)` are stored directly on
96
- * the meta object.
97
- */
98
- export declare class ComponentMeta implements Hook<ComponentInstance> {
77
+ export declare class ComponentMeta {
99
78
  /** The component class constructor this meta represents. */
100
79
  readonly Class: ComponentType;
101
80
  /** Component entity id assigned at registration time. */
@@ -104,33 +83,42 @@ export declare class ComponentMeta implements Hook<ComponentInstance> {
104
83
  readonly bitPtr: BitPtr;
105
84
  /** Whether this component is a relationship pointing at another entity. */
106
85
  readonly isRelationship: boolean;
86
+ /** Whether this relationship permits only one target. */
87
+ relationshipExclusive: boolean;
107
88
  /** Applied to entities carrying this component when this component entity is deleted. */
108
89
  onDelete: CleanupPolicy;
109
90
  /** Applied to entities targeting a deleted entity through this relationship. */
110
91
  onDeleteTarget: CleanupPolicy;
111
- private _usageCount;
112
92
  constructor(Class: ComponentType, ceid: EID);
113
93
  /** Number of entities currently carrying this component eid. */
114
94
  get usageCount(): number;
115
- /** @inheritdoc */
116
- onAdd(): HookHandler | undefined;
117
- onAdd(handler: HookHandler | undefined): ComponentMeta;
118
- /** @inheritdoc */
119
- onRemove(): HookHandler | undefined;
120
- onRemove(handler: HookHandler | undefined): ComponentMeta;
121
95
  /**
122
- * Return the current retarget handler, or `undefined` when none is set.
96
+ * Register a handler for a hook event.
97
+ *
98
+ * Multiple handlers may be registered for the same event; they fire in
99
+ * registration order. Pass `insert: true` to prepend the handler ahead of
100
+ * any already-registered handlers. Removing a handler is via {@link off} with
101
+ * the same reference.
102
+ *
103
+ * The `targetAdd`, `targetRemove`, `targetPolicyAdd`, and `targetPolicyRemove`
104
+ * events require this meta to be a relationship; registering one on a
105
+ * non-relationship meta throws at registration time.
106
+ *
107
+ * @param event - One of `add`, `remove`, `set`, `targetAdd`, `targetRemove`,
108
+ * `policyAdd`, `policyRemove`, `targetPolicyAdd`, `targetPolicyRemove`.
109
+ * @param handler - Handler whose signature is constrained by `event`.
110
+ * @param insert - When `true`, prepend the handler; when `false` (default), append.
111
+ * @returns This meta, for chaining.
123
112
  */
124
- onRetarget(): RetargetHandler | undefined;
113
+ on<E extends HookEvent>(event: E, handler: HookEventMap[E], insert?: boolean): this;
125
114
  /**
126
- * Set the handler fired after this relationship changes between two existing targets. Pass
127
- * `undefined` to remove the current handler.
115
+ * Remove a previously-registered handler by reference. Idempotent: removing
116
+ * an unregistered handler is a no-op.
128
117
  *
129
- * @param handler - Receives the source entity, previous target, and current target.
130
- * @returns This metadata object, for chaining.
118
+ * @param event - The event the handler was registered for.
119
+ * @param handler - The exact handler reference passed to {@link on}.
120
+ * @returns This meta, for chaining.
131
121
  */
132
- onRetarget(handler: RetargetHandler | undefined): ComponentMeta;
133
- /** @inheritdoc */
134
- onSet(): HookHandler | undefined;
135
- onSet(handler: HookHandler | undefined): ComponentMeta;
122
+ off<E extends HookEvent>(event: E, handler: HookEventMap[E]): this;
123
+ private _requireRelationship;
136
124
  }