@forgeax/engine-plugin 0.1.21 → 0.1.24

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
@@ -0,0 +1,70 @@
1
+ // Public consumer imports validated by scripts/check-public-import-examples.mjs:
2
+ // import { definePluginGroup, usePlugin } from '@forgeax/engine/plugin';
3
+ // import { projectPluginEntries } from '@forgeax/engine/plugin/loader';
4
+ // import { GameProjectSchema } from '@forgeax/engine/project';
5
+
6
+ import type { Plugin as ForgePlugin, PluginCompositionError } from '../index.js';
7
+ import { definePluginGroup, usePlugin } from '../index.js';
8
+ import type { CatalogLoader, CatalogLoaderError, GamePluginEntry } from '../loader.js';
9
+ import { projectPluginEntries } from '../loader.js';
10
+
11
+ const child: ForgePlugin = { name: 'child', apply: () => undefined };
12
+ const configuredChild: ForgePlugin.Object<{ readonly speed: number }> = {
13
+ name: 'configured-child',
14
+ apply: (_ctx, config) => config.speed,
15
+ };
16
+ // @ts-expect-error Plugin.Object config must preserve the declared field type.
17
+ usePlugin(configuredChild, { speed: 'not-a-number' }, { key: 'invalid' });
18
+ const group = definePluginGroup({
19
+ name: 'gameplay',
20
+ children: () => [usePlugin(child, undefined, { key: 'child' })],
21
+ });
22
+ const groupUse = usePlugin(group, undefined, { key: 'gameplay' });
23
+ const entry: GamePluginEntry = { id: 'gameplay', name: './assets/gameplay.plugin.ts' };
24
+ const projectedEntries = projectPluginEntries([entry], 'engine');
25
+ async function retryLastKnownGood(
26
+ loader: CatalogLoader,
27
+ entries: Parameters<CatalogLoader['root']['update']>[0],
28
+ ): Promise<void> {
29
+ await loader.root.update(entries);
30
+ await loader.await();
31
+ }
32
+ type Assert<T extends true> = T;
33
+ type PublicApiAssertions = [
34
+ Assert<typeof group extends ForgePlugin ? true : false>,
35
+ Assert<typeof groupUse.key extends string | undefined ? true : false>,
36
+ Assert<ReturnType<typeof projectPluginEntries> extends readonly object[] ? true : false>,
37
+ ];
38
+ declare const publicApiAssertions: PublicApiAssertions;
39
+ void publicApiAssertions;
40
+ void projectedEntries;
41
+ void retryLastKnownGood;
42
+
43
+ function recoverPlugin(error: PluginCompositionError | CatalogLoaderError): string {
44
+ switch (error.code) {
45
+ case 'plugin-config-invalid':
46
+ return error.detail.plugin;
47
+ case 'plugin-group-child-failed':
48
+ return error.detail.child;
49
+ case 'plugin-group-dependency-cycle':
50
+ return error.detail.path.join(' > ');
51
+ case 'plugin-group-key-duplicate':
52
+ return error.detail.key;
53
+ case 'plugin-group-key-required':
54
+ return error.detail.plugin;
55
+ case 'plugin-group-provider-missing':
56
+ return error.detail.service;
57
+ case 'plugin-catalog-missing':
58
+ return error.detail.name;
59
+ case 'plugin-realm-mismatch':
60
+ return error.detail.actual;
61
+ case 'plugin-entry-realm-mixed':
62
+ return error.detail.group;
63
+ case 'plugin-realm-unsupported':
64
+ return error.detail.realm;
65
+ case 'plugin-catalog-digest-mismatch':
66
+ return error.detail.actual;
67
+ }
68
+ }
69
+
70
+ void recoverPlugin;
@@ -0,0 +1,56 @@
1
+ import { Context, type Plugin } from '@forgeax/engine-plugin';
2
+ import { describe, expect, it } from 'vitest';
3
+ import {
4
+ installCatalogLoader,
5
+ type PluginCatalog,
6
+ type PluginRealm,
7
+ projectPluginEntries,
8
+ } from '../loader.js';
9
+
10
+ function catalogOf(realm: PluginRealm, plugin: Plugin): PluginCatalog {
11
+ return new Map([[`@game/${realm}`, { realm, load: async () => ({ default: plugin }) }]]);
12
+ }
13
+
14
+ describe('realm loader integration', () => {
15
+ it('installs and cleans each physical realm from the same Catalog shape', async () => {
16
+ const events: string[] = [];
17
+ const plugin: Plugin = {
18
+ apply(ctx) {
19
+ events.push('apply');
20
+ ctx.effect(() => () => events.push('dispose'));
21
+ },
22
+ };
23
+
24
+ for (const realm of ['host', 'engine', 'build'] as const) {
25
+ const context = new Context();
26
+ const { loader } = await installCatalogLoader(context, catalogOf(realm, plugin), realm);
27
+ await loader.root.update([{ id: `${realm}-plugin`, name: `@game/${realm}` }]);
28
+ await loader.await();
29
+ expect([...loader.entries()]).toHaveLength(1);
30
+ await context.fiber.dispose();
31
+ }
32
+
33
+ expect(events).toEqual(['apply', 'dispose', 'apply', 'dispose', 'apply', 'dispose']);
34
+ });
35
+
36
+ it('rejects an entry that crosses a physical realm before activation', async () => {
37
+ expect(() =>
38
+ projectPluginEntries(
39
+ [
40
+ {
41
+ id: 'root',
42
+ name: 'cordis:group',
43
+ group: true,
44
+ realm: 'engine',
45
+ config: [{ id: 'host', name: '@game/host', realm: 'host' }],
46
+ },
47
+ ],
48
+ 'engine',
49
+ ),
50
+ ).toThrowError(
51
+ expect.objectContaining({
52
+ code: 'plugin-entry-realm-mixed',
53
+ }),
54
+ );
55
+ });
56
+ });
package/src/browser.ts CHANGED
@@ -9,6 +9,24 @@
9
9
  */
10
10
  export * from '@deepseek-ai/cordis';
11
11
  export { createContextCapabilityResolver } from './capability.js';
12
+ export {
13
+ definePluginGroup,
14
+ PluginCompositionError,
15
+ type PluginCompositionErrorArgs,
16
+ type PluginCompositionErrorCode,
17
+ type PluginCompositionErrorDetailByCode,
18
+ type PluginGroupOptions,
19
+ type PluginUse,
20
+ type PluginUseOptions,
21
+ usePlugin,
22
+ } from './composition.js';
23
+ export {
24
+ type CatalogPluginFiberState,
25
+ type CatalogPluginInspection,
26
+ type CatalogPluginInspectionEntry,
27
+ type CatalogPluginInspectionFailure,
28
+ inspectCatalogPlugins,
29
+ } from './inspection.js';
12
30
  export {
13
31
  defineToolPlugin,
14
32
  isToolPlugin,