@forgeax/engine-plugin 0.1.21 → 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.
- package/README.md +94 -10
- package/dist/__tests__/composition-contract.test-d.d.ts +2 -0
- package/dist/__tests__/composition-contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/composition.integration.test.d.ts +2 -0
- package/dist/__tests__/composition.integration.test.d.ts.map +1 -0
- package/dist/__tests__/public-api.test-d.d.ts +2 -0
- package/dist/__tests__/public-api.test-d.d.ts.map +1 -0
- package/dist/__tests__/realm-loader.integration.test.d.ts +2 -0
- package/dist/__tests__/realm-loader.integration.test.d.ts.map +1 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.mjs +548 -1
- package/dist/browser.mjs.map +1 -1
- package/dist/composition.d.ts +73 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +560 -8
- package/dist/index.mjs.map +1 -1
- package/dist/inspection.d.ts +26 -0
- package/dist/inspection.d.ts.map +1 -0
- package/dist/loader.d.ts +51 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.mjs +12 -7
- package/dist/loader.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/browser-entry.test.ts +1 -0
- package/src/__tests__/composition-contract.test-d.ts +74 -0
- package/src/__tests__/composition.integration.test.ts +1347 -0
- package/src/__tests__/public-api.test-d.ts +70 -0
- package/src/__tests__/realm-loader.integration.test.ts +56 -0
- package/src/browser.ts +18 -0
- package/src/composition.ts +642 -0
- package/src/index.ts +18 -0
- package/src/inspection.ts +160 -0
- package/src/loader.ts +82 -13
- package/dist/.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @forgeax/engine-plugin
|
|
2
2
|
|
|
3
|
-
`@forgeax/engine
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
106
|
-
import type { Plugin } from '@forgeax/engine
|
|
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
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"composition-contract.test-d.d.ts","sourceRoot":"","sources":["../../src/__tests__/composition-contract.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"public-api.test-d.d.ts","sourceRoot":"","sources":["../../src/__tests__/public-api.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -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
|
package/dist/browser.d.ts.map
CHANGED
|
@@ -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"}
|