@forgeax/engine-plugin 0.1.20 → 0.1.23

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 (37) hide show
  1. package/README.md +94 -10
  2. package/dist/__tests__/composition-contract.test-d.d.ts +2 -0
  3. package/dist/__tests__/composition-contract.test-d.d.ts.map +1 -0
  4. package/dist/__tests__/composition.integration.test.d.ts +2 -0
  5. package/dist/__tests__/composition.integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/public-api.test-d.d.ts +2 -0
  7. package/dist/__tests__/public-api.test-d.d.ts.map +1 -0
  8. package/dist/__tests__/realm-loader.integration.test.d.ts +2 -0
  9. package/dist/__tests__/realm-loader.integration.test.d.ts.map +1 -0
  10. package/dist/browser.d.ts +2 -0
  11. package/dist/browser.d.ts.map +1 -1
  12. package/dist/browser.mjs +548 -1
  13. package/dist/browser.mjs.map +1 -1
  14. package/dist/composition.d.ts +73 -0
  15. package/dist/composition.d.ts.map +1 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.mjs +560 -8
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/inspection.d.ts +26 -0
  21. package/dist/inspection.d.ts.map +1 -0
  22. package/dist/loader.d.ts +51 -3
  23. package/dist/loader.d.ts.map +1 -1
  24. package/dist/loader.mjs +12 -7
  25. package/dist/loader.mjs.map +1 -1
  26. package/package.json +2 -2
  27. package/src/__tests__/browser-entry.test.ts +1 -0
  28. package/src/__tests__/composition-contract.test-d.ts +74 -0
  29. package/src/__tests__/composition.integration.test.ts +1347 -0
  30. package/src/__tests__/public-api.test-d.ts +70 -0
  31. package/src/__tests__/realm-loader.integration.test.ts +56 -0
  32. package/src/browser.ts +18 -0
  33. package/src/composition.ts +642 -0
  34. package/src/index.ts +18 -0
  35. package/src/inspection.ts +160 -0
  36. package/src/loader.ts +82 -13
  37. package/dist/.tsbuildinfo +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @forgeax/engine-plugin
2
2
 
3
- `@forgeax/engine-plugin` is the sole ForgeaX runtime entry to [DeepSeek Cordis](https://www.npmjs.com/package/@deepseek-ai/cordis). The main export remains a thin re-export of `@deepseek-ai/cordis@4.0.1`; the opt-in `@forgeax/engine-plugin/loader` subpath adds the exact-pinned DeepSeek Harness Entry/Loader control plane plus a browser-safe static Catalog boundary.
3
+ `@forgeax/engine/plugin` is the sole ForgeaX runtime entry to [DeepSeek Cordis](https://www.npmjs.com/package/@deepseek-ai/cordis). The main export remains a thin re-export of `@deepseek-ai/cordis@4.0.1`; the opt-in `@forgeax/engine/plugin/loader` subpath adds the exact-pinned Entry/Loader control plane plus a browser-safe static Catalog boundary.
4
4
 
5
5
  > [!IMPORTANT]
6
6
  > Cordis owns when a capability exists, what it depends on, and how it is reverted. ECS, Renderer, Assets, and Host packages own the direct data structures that execute it. Entry and Fiber work never enters per-entity, per-draw, or per-particle loops.
@@ -19,7 +19,7 @@
19
19
  The Catalog is not a package scanner. Devkit emits only modules already admitted by `forge.json.plugins[]`; production does not inspect `node_modules`, evaluate YAML, or construct arbitrary dynamic import strings.
20
20
 
21
21
  ```ts
22
- import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine-plugin/loader';
22
+ import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine/plugin/loader';
23
23
 
24
24
  const catalog = new Map([
25
25
  ['./movement.ts', { realm: 'engine', load: () => import('./movement.ts') }],
@@ -33,12 +33,99 @@ await loader.await();
33
33
 
34
34
  `loader.update(id, { disabled: true })`, `loader.update(id, { config })`, and `loader.remove(id)` use native DeepSeek Harness Entry reconciliation. A failed config update rolls back to the last working Fiber and its contributions.
35
35
 
36
+ ## Root Group authoring
37
+
38
+ Use one public Group as the project root. Each `usePlugin` declaration carries its
39
+ configuration. A stable key is optional: when omitted, the native Plugin object is
40
+ the child's identity; an explicit key preserves identity across reorder, config
41
+ update, replacement, and retry. Reusing one Plugin object without distinct keys
42
+ is rejected when it would create ambiguous instances.
43
+
44
+ ```ts
45
+ import { definePluginGroup, usePlugin } from '@forgeax/engine/plugin';
46
+ import type { Plugin } from '@forgeax/engine/plugin';
47
+
48
+ const movement: Plugin.Object<{ speed: number }> = {
49
+ name: 'movement',
50
+ provide: 'movementSpeed',
51
+ apply(ctx, config: { speed: number }) {
52
+ ctx.provide('movementSpeed', config.speed);
53
+ },
54
+ };
55
+
56
+ const gameRoot = definePluginGroup({
57
+ name: 'game-root',
58
+ children: (config: { speed: number }) => [
59
+ usePlugin(movement, { speed: config.speed }, { key: 'movement' }),
60
+ ],
61
+ });
62
+
63
+ const rootFiber = await context.plugin(gameRoot, { speed: 6 });
64
+ await rootFiber.await();
65
+
66
+ // @ts-expect-error speed must be a number
67
+ usePlugin(movement, { speed: 'not-a-number' }, { key: 'invalid' });
68
+ ```
69
+
70
+ The Group owns reconciliation while each child Fiber owns its own effect and
71
+ disposer. A rejected update leaves the prior Fiber and configuration as the
72
+ last-known-good state; repair the child owner, then retry the same Entry update.
73
+ The retry must await both the update and the Loader barrier before publishing a
74
+ new state.
75
+
76
+ ```ts
77
+ import type {
78
+ CatalogLoaderError,
79
+ CatalogLoader,
80
+ PluginCompositionError,
81
+ } from '@forgeax/engine/plugin';
82
+
83
+ function recoveryOwner(error: PluginCompositionError | CatalogLoaderError): string {
84
+ switch (error.code) {
85
+ case 'plugin-config-invalid':
86
+ return `Group config for ${error.detail.plugin}`;
87
+ case 'plugin-group-child-failed':
88
+ return `child ${error.detail.child}`;
89
+ case 'plugin-group-dependency-cycle':
90
+ return `dependency path ${error.detail.path.join(' > ')}`;
91
+ case 'plugin-group-key-duplicate':
92
+ return `stable key ${error.detail.key}`;
93
+ case 'plugin-group-key-required':
94
+ return `stable key for ${error.detail.plugin}`;
95
+ case 'plugin-group-provider-missing':
96
+ return `provider ${error.detail.service}`;
97
+ case 'plugin-catalog-missing':
98
+ return `Catalog entry ${error.detail.name}`;
99
+ case 'plugin-realm-mismatch':
100
+ return `realm ${error.detail.actual} for ${error.detail.name}`;
101
+ case 'plugin-entry-realm-mixed':
102
+ return `Group ${error.detail.group}`;
103
+ case 'plugin-realm-unsupported':
104
+ return `realm ${error.detail.realm}`;
105
+ case 'plugin-catalog-digest-mismatch':
106
+ return `Catalog digest ${error.detail.actual}`;
107
+ }
108
+ }
109
+
110
+ async function retryLastKnownGood(
111
+ loader: CatalogLoader,
112
+ entries: Parameters<CatalogLoader['root']['update']>[0],
113
+ ): Promise<void> {
114
+ await loader.root.update(entries);
115
+ await loader.await();
116
+ }
117
+ ```
118
+
119
+ `recoveryOwner` deliberately has no `default`: every closed error arm names its
120
+ owner and detail. `retryLastKnownGood` is the owner-level recovery barrier; it
121
+ does not dispose a working Fiber or fabricate a second registry.
122
+
36
123
  ## Native Cordis foundation
37
124
 
38
125
  The Loader does not replace direct Cordis composition. A host that already owns the plugin set can still create one World context and dispose an individual Fiber:
39
126
 
40
127
  ```ts
41
- import { createWorldContext } from '@forgeax/engine-ecs';
128
+ import { createWorldContext } from '@forgeax/engine/ecs';
42
129
  import optionalGameFeature from './optional-game-feature';
43
130
  import projectPlugin from './project-plugin';
44
131
 
@@ -77,7 +164,7 @@ publishing the replacement.
77
164
 
78
165
  A plugin owns reversible runtime contributions, not JavaScript module evaluation. Component and system tokens are vocabulary; installing them into one World creates lifecycle state.
79
166
 
80
- `defineComponent` and `defineSystem` still publish tokens to legacy process-wide discovery. Unloading removes the World-local lease, schedule membership, and owned data; it does not delete imported token objects or invalidate archetypes in another World.
167
+ `defineComponent` and `defineSystem` publish tokens as vocabulary. Unloading removes the World-local lease, schedule membership, and owned data; it does not delete imported token objects or invalidate archetypes in another World.
81
168
 
82
169
  ```mermaid
83
170
  flowchart LR
@@ -102,8 +189,8 @@ flowchart LR
102
189
  Register World vocabulary and its consumers in one generator effect. Yield each inverse immediately so partial activation and normal disposal share the same reverse order:
103
190
 
104
191
  ```ts
105
- import { defineComponent, defineSystem, Update } from '@forgeax/engine-ecs';
106
- import type { Plugin } from '@forgeax/engine-plugin';
192
+ import { defineComponent, defineSystem, Update } from '@forgeax/engine/ecs';
193
+ import type { Plugin } from '@forgeax/engine/plugin';
107
194
 
108
195
  const Position = defineComponent('Position', { x: 'f32' });
109
196
  const movement = defineSystem({
@@ -131,9 +218,6 @@ export default plugin;
131
218
 
132
219
  Disposal removes the system before releasing the component registration. The final component lease returns `component-in-use` while a live entity or registered system still references the token, preventing a half-unloaded World. The same token can be registered in two Worlds; each World owns an independent lease.
133
220
 
134
- > [!NOTE]
135
- > The legacy process-wide ECS discovery maps still exist for consumers not yet migrated to a target World. They are compatibility discovery only, not proof that a plugin is active. New plugin lifecycle code must use `world.components`; removing the legacy maps is a later migration after every name-based scene, restore, inspector, and external consumer has moved.
136
-
137
221
  ## Ownership rule
138
222
 
139
223
  Cleanup follows ownership, not access. A plugin removes an entity or component value only if it created or exclusively owns it. Shared game state needs an explicit owner; it must not be inferred from which systems happened to read it.
@@ -149,7 +233,7 @@ rebuilds the World instead of maintaining a second ownership graph for every ent
149
233
 
150
234
  A clean disposal leaves no active schedule entry, World lease, resource, listener, Host object, or
151
235
  plugin-owned ECS value from that Fiber. Imported component and system tokens may remain in the ESM realm
152
- and legacy discovery maps: they are inert vocabulary, not a live capability or unmanaged side effect.
236
+ and token vocabulary: they are inert declarations, not a live capability or unmanaged side effect.
153
237
 
154
238
  ## Failure and performance boundaries
155
239
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=composition-contract.test-d.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composition-contract.test-d.d.ts","sourceRoot":"","sources":["../../src/__tests__/composition-contract.test-d.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=composition.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composition.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/composition.integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=public-api.test-d.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-api.test-d.d.ts","sourceRoot":"","sources":["../../src/__tests__/public-api.test-d.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=realm-loader.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realm-loader.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realm-loader.integration.test.ts"],"names":[],"mappings":""}
package/dist/browser.d.ts CHANGED
@@ -9,5 +9,7 @@
9
9
  */
10
10
  export * from '@deepseek-ai/cordis';
11
11
  export { createContextCapabilityResolver } from './capability.js';
12
+ export { definePluginGroup, PluginCompositionError, type PluginCompositionErrorArgs, type PluginCompositionErrorCode, type PluginCompositionErrorDetailByCode, type PluginGroupOptions, type PluginUse, type PluginUseOptions, usePlugin, } from './composition.js';
13
+ export { type CatalogPluginFiberState, type CatalogPluginInspection, type CatalogPluginInspectionEntry, type CatalogPluginInspectionFailure, inspectCatalogPlugins, } from './inspection.js';
12
14
  export { defineToolPlugin, isToolPlugin, type ToolPlugin, } from './tool-plugin.js';
13
15
  //# sourceMappingURL=browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,kCAAkC,EACvC,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,EACjC,KAAK,8BAA8B,EACnC,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC"}