@forgeax/engine-host 0.1.27 → 0.1.28
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/dist/__tests__/startup.test.d.ts +2 -0
- package/dist/__tests__/startup.test.d.ts.map +1 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.mjs +36 -14
- package/dist/backend.mjs.map +1 -1
- package/dist/frontend.d.ts.map +1 -1
- package/dist/frontend.mjs +34 -11
- package/dist/frontend.mjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +47 -26
- package/dist/index.mjs.map +1 -1
- package/dist/startup.d.ts +18 -0
- package/dist/startup.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/startup.test.ts +27 -0
- package/src/backend.ts +13 -14
- package/src/frontend.ts +11 -11
- package/src/index.ts +1 -0
- package/src/startup.ts +43 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Context, type Fiber, type Plugin } from '@forgeax/engine-plugin';
|
|
2
|
+
/**
|
|
3
|
+
* The product-neutral host bootstrap. It owns only a Cordis Context and the
|
|
4
|
+
* supplied startup entries; assembly, transport, Project, and App are all
|
|
5
|
+
* ordinary plugins layered above this seam.
|
|
6
|
+
*/
|
|
7
|
+
export interface HostStartupOptions {
|
|
8
|
+
readonly context?: Context;
|
|
9
|
+
readonly startupPlugins?: readonly Plugin[];
|
|
10
|
+
}
|
|
11
|
+
export interface HostStartup {
|
|
12
|
+
readonly context: Context;
|
|
13
|
+
readonly ownedContext: boolean;
|
|
14
|
+
readonly fibers: readonly Fiber[];
|
|
15
|
+
dispose(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare function createHostStartup(options?: HostStartupOptions): Promise<HostStartup>;
|
|
18
|
+
//# sourceMappingURL=startup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAE1E;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CAuB9F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-host",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@deepseek-ai/cordis-plugin-loader": "1.0.2",
|
|
48
|
-
"@forgeax/engine-plugin": "0.1.
|
|
48
|
+
"@forgeax/engine-plugin": "0.1.28"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^20.14.0",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createHostStartup } from '../startup.js';
|
|
3
|
+
|
|
4
|
+
describe('generic host startup', () => {
|
|
5
|
+
it('owns only supplied Cordis startup entries and releases them', async () => {
|
|
6
|
+
let disposed = false;
|
|
7
|
+
const startup = await createHostStartup({
|
|
8
|
+
startupPlugins: [
|
|
9
|
+
{
|
|
10
|
+
name: 'fixture-startup',
|
|
11
|
+
apply(ctx) {
|
|
12
|
+
ctx.effect(
|
|
13
|
+
() => () => {
|
|
14
|
+
disposed = true;
|
|
15
|
+
},
|
|
16
|
+
'fixture-startup',
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(startup.ownedContext).toBe(true);
|
|
24
|
+
await startup.dispose();
|
|
25
|
+
expect(disposed).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
});
|
package/src/backend.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
modulesFromCatalog,
|
|
21
21
|
validateHostAssembly,
|
|
22
22
|
} from './protocol.js';
|
|
23
|
+
import { createHostStartup, type HostStartup } from './startup.js';
|
|
23
24
|
import {
|
|
24
25
|
createHostTransport,
|
|
25
26
|
HOST_ACTIVATION_REPORT_SERVICE,
|
|
@@ -157,7 +158,6 @@ function assertBackendCatalogIdentity(
|
|
|
157
158
|
*/
|
|
158
159
|
export async function createBackendHost(options: BackendHostOptions = {}): Promise<BackendHost> {
|
|
159
160
|
const context = options.context ?? new Context();
|
|
160
|
-
const ownedContext = options.context === undefined;
|
|
161
161
|
const realm = options.realm ?? 'engine';
|
|
162
162
|
const catalog = options.catalog;
|
|
163
163
|
const pairedBackendEntries =
|
|
@@ -188,7 +188,7 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
188
188
|
});
|
|
189
189
|
const checkedInitial = validateHostAssembly(initialAssembly);
|
|
190
190
|
if (!checkedInitial.ok) {
|
|
191
|
-
if (
|
|
191
|
+
if (options.context === undefined) await context.fiber.dispose();
|
|
192
192
|
throw checkedInitial.error;
|
|
193
193
|
}
|
|
194
194
|
const backendModules =
|
|
@@ -205,21 +205,24 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
205
205
|
// still match the current authority exactly.
|
|
206
206
|
const bootstrapRevision = checkedInitial.value.revision;
|
|
207
207
|
const transport = options.transport ?? createHostTransport();
|
|
208
|
-
let
|
|
208
|
+
let startup: HostStartup | undefined;
|
|
209
209
|
let loaderFiber: Fiber | undefined;
|
|
210
210
|
let loader: CatalogLoader | undefined;
|
|
211
211
|
let backendEntries =
|
|
212
212
|
options.pairs === undefined
|
|
213
213
|
? (options.entries ?? options.assembly?.entries ?? [])
|
|
214
214
|
: pairedBackendEntries;
|
|
215
|
-
const startupFibers: Fiber[] = [];
|
|
216
215
|
let unregisterAssemblyService: (() => void) | undefined;
|
|
217
216
|
let unregisterActivationService: (() => void) | undefined;
|
|
218
217
|
let unsubscribeAssembly: (() => void) | undefined;
|
|
219
218
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
startup = await createHostStartup({
|
|
220
|
+
context,
|
|
221
|
+
startupPlugins: [
|
|
222
|
+
hostFoundationPlugin(assembly, transport),
|
|
223
|
+
...(options.startupPlugins ?? []),
|
|
224
|
+
],
|
|
225
|
+
});
|
|
223
226
|
if (catalog !== undefined) {
|
|
224
227
|
const bootstrapped = await bootstrapCatalogLoader(context, catalog, realm, {
|
|
225
228
|
catalogDigest: checkedInitial.value.revision,
|
|
@@ -261,9 +264,7 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
261
264
|
unregisterAssemblyService?.();
|
|
262
265
|
unsubscribeAssembly?.();
|
|
263
266
|
await loaderFiber?.dispose();
|
|
264
|
-
|
|
265
|
-
await foundationFiber?.dispose();
|
|
266
|
-
if (ownedContext) await context.fiber.dispose();
|
|
267
|
+
await startup?.dispose();
|
|
267
268
|
throw error;
|
|
268
269
|
}
|
|
269
270
|
|
|
@@ -273,7 +274,7 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
273
274
|
...(loader === undefined ? {} : { loader }),
|
|
274
275
|
assembly,
|
|
275
276
|
transport,
|
|
276
|
-
ownedContext,
|
|
277
|
+
ownedContext: options.context === undefined,
|
|
277
278
|
async update(nextInput) {
|
|
278
279
|
if (disposed) {
|
|
279
280
|
throw new HostAssemblyError(
|
|
@@ -330,9 +331,7 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
330
331
|
unregisterAssemblyService?.();
|
|
331
332
|
transport.close();
|
|
332
333
|
await loaderFiber?.dispose();
|
|
333
|
-
|
|
334
|
-
await foundationFiber?.dispose();
|
|
335
|
-
if (ownedContext) await context.fiber.dispose();
|
|
334
|
+
await startup?.dispose();
|
|
336
335
|
},
|
|
337
336
|
};
|
|
338
337
|
return host;
|
package/src/frontend.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
modulesFromCatalog,
|
|
20
20
|
validateHostAssembly,
|
|
21
21
|
} from './protocol.js';
|
|
22
|
+
import { createHostStartup, type HostStartup } from './startup.js';
|
|
22
23
|
import {
|
|
23
24
|
HOST_ACTIVATION_REPORT_SERVICE,
|
|
24
25
|
HOST_ASSEMBLY_SERVICE,
|
|
@@ -306,8 +307,7 @@ export async function createFrontendHost(options: FrontendHostOptions = {}): Pro
|
|
|
306
307
|
return status;
|
|
307
308
|
},
|
|
308
309
|
};
|
|
309
|
-
|
|
310
|
-
let foundationFiber: Fiber | undefined;
|
|
310
|
+
let startup: HostStartup | undefined;
|
|
311
311
|
let loaderFiber: Fiber | undefined;
|
|
312
312
|
let loader: CatalogLoader | undefined;
|
|
313
313
|
let disposed = false;
|
|
@@ -356,9 +356,13 @@ export async function createFrontendHost(options: FrontendHostOptions = {}): Pro
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
try {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
startup = await createHostStartup({
|
|
360
|
+
context,
|
|
361
|
+
startupPlugins: [
|
|
362
|
+
hostFoundationPlugin(state, options.transport),
|
|
363
|
+
...(options.startupPlugins ?? []),
|
|
364
|
+
],
|
|
365
|
+
});
|
|
362
366
|
if (options.autoActivate !== false) {
|
|
363
367
|
// Activation is performed below through the returned host so its status
|
|
364
368
|
// callback and failure rollback cover the first assembly as well.
|
|
@@ -393,9 +397,7 @@ export async function createFrontendHost(options: FrontendHostOptions = {}): Pro
|
|
|
393
397
|
} catch (error) {
|
|
394
398
|
removeTransportDisconnect?.();
|
|
395
399
|
await loaderFiber?.dispose();
|
|
396
|
-
|
|
397
|
-
await foundationFiber?.dispose();
|
|
398
|
-
if (ownedContext) await context.fiber.dispose();
|
|
400
|
+
await startup?.dispose();
|
|
399
401
|
throw error;
|
|
400
402
|
}
|
|
401
403
|
|
|
@@ -434,9 +436,7 @@ export async function createFrontendHost(options: FrontendHostOptions = {}): Pro
|
|
|
434
436
|
disposed = true;
|
|
435
437
|
removeTransportDisconnect?.();
|
|
436
438
|
await loaderFiber?.dispose();
|
|
437
|
-
|
|
438
|
-
await foundationFiber?.dispose();
|
|
439
|
-
if (ownedContext) await context.fiber.dispose();
|
|
439
|
+
await startup?.dispose();
|
|
440
440
|
status = { state: 'disposed', revision: current.revision };
|
|
441
441
|
},
|
|
442
442
|
};
|
package/src/index.ts
CHANGED
package/src/startup.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Context, type Fiber, type Plugin } from '@forgeax/engine-plugin';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The product-neutral host bootstrap. It owns only a Cordis Context and the
|
|
5
|
+
* supplied startup entries; assembly, transport, Project, and App are all
|
|
6
|
+
* ordinary plugins layered above this seam.
|
|
7
|
+
*/
|
|
8
|
+
export interface HostStartupOptions {
|
|
9
|
+
readonly context?: Context;
|
|
10
|
+
readonly startupPlugins?: readonly Plugin[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HostStartup {
|
|
14
|
+
readonly context: Context;
|
|
15
|
+
readonly ownedContext: boolean;
|
|
16
|
+
readonly fibers: readonly Fiber[];
|
|
17
|
+
dispose(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function createHostStartup(options: HostStartupOptions = {}): Promise<HostStartup> {
|
|
21
|
+
const context = options.context ?? new Context();
|
|
22
|
+
const ownedContext = options.context === undefined;
|
|
23
|
+
const fibers: Fiber[] = [];
|
|
24
|
+
let disposed = false;
|
|
25
|
+
try {
|
|
26
|
+
for (const plugin of options.startupPlugins ?? []) fibers.push(await context.plugin(plugin));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
for (const fiber of fibers.reverse()) await fiber.dispose();
|
|
29
|
+
if (ownedContext) await context.fiber.dispose();
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
context,
|
|
34
|
+
ownedContext,
|
|
35
|
+
fibers,
|
|
36
|
+
async dispose(): Promise<void> {
|
|
37
|
+
if (disposed) return;
|
|
38
|
+
disposed = true;
|
|
39
|
+
for (const fiber of [...fibers].reverse()) await fiber.dispose();
|
|
40
|
+
if (ownedContext) await context.fiber.dispose();
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|