@forgeax/engine-host 0.1.27 → 0.1.29
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 +9 -0
- package/dist/__tests__/startup.test.d.ts +2 -0
- package/dist/__tests__/startup.test.d.ts.map +1 -0
- package/dist/backend.d.ts +7 -1
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.mjs +56 -16
- 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 +70 -29
- package/dist/index.mjs.map +1 -1
- package/dist/protocol.d.ts +2 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.mjs.map +1 -1
- package/dist/startup.d.ts +18 -0
- package/dist/startup.d.ts.map +1 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.mjs +3 -1
- package/dist/transport.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/host.test.ts +109 -0
- package/src/__tests__/startup.test.ts +27 -0
- package/src/backend.ts +42 -17
- package/src/frontend.ts +11 -11
- package/src/index.ts +1 -0
- package/src/protocol.ts +6 -1
- package/src/startup.ts +43 -0
- package/src/transport.ts +2 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
createHostAssembly,
|
|
10
10
|
createHostTransport,
|
|
11
11
|
createHostWebSocketClient,
|
|
12
|
+
HOST_ACTIVATION_REPORT_SERVICE,
|
|
12
13
|
HostAssemblyError,
|
|
13
14
|
} from '../index';
|
|
14
15
|
|
|
@@ -26,6 +27,59 @@ function catalogFor(plugin: Plugin) {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
describe('Engine host pair', () => {
|
|
30
|
+
it('admits one explicit bootstrap revision and preserves the owner activation callback', async () => {
|
|
31
|
+
const ownerReports: string[] = [];
|
|
32
|
+
const subscriberReports: string[] = [];
|
|
33
|
+
const backend = await createBackendHost({
|
|
34
|
+
config: { phase: 'owner' },
|
|
35
|
+
onActivationReport: (report) => {
|
|
36
|
+
ownerReports.push(report.revision);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const client = backend.transport.connect();
|
|
40
|
+
const remove = backend.subscribeActivationReports((report) => {
|
|
41
|
+
subscriberReports.push(report.revision);
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
const original = backend.assembly.current;
|
|
45
|
+
const bootstrap = await backend.update(
|
|
46
|
+
{ config: { phase: 'bootstrap' } },
|
|
47
|
+
{ bootstrap: true },
|
|
48
|
+
);
|
|
49
|
+
const full = await backend.update({ config: { phase: 'full' } });
|
|
50
|
+
await client.request(HOST_ACTIVATION_REPORT_SERVICE, {
|
|
51
|
+
state: 'active',
|
|
52
|
+
revision: bootstrap.revision,
|
|
53
|
+
});
|
|
54
|
+
expect(ownerReports).toEqual([bootstrap.revision]);
|
|
55
|
+
expect(subscriberReports).toEqual([bootstrap.revision]);
|
|
56
|
+
await expect(
|
|
57
|
+
client.request(HOST_ACTIVATION_REPORT_SERVICE, {
|
|
58
|
+
state: 'active',
|
|
59
|
+
revision: original.revision,
|
|
60
|
+
}),
|
|
61
|
+
).rejects.toMatchObject({ code: 'host-assembly-revision-mismatch' });
|
|
62
|
+
remove();
|
|
63
|
+
await client.request(HOST_ACTIVATION_REPORT_SERVICE, {
|
|
64
|
+
state: 'active',
|
|
65
|
+
revision: full.revision,
|
|
66
|
+
});
|
|
67
|
+
expect(ownerReports).toEqual([bootstrap.revision, full.revision]);
|
|
68
|
+
expect(subscriberReports).toEqual([bootstrap.revision]);
|
|
69
|
+
await backend.update({ config: { phase: 'next-bootstrap' } }, { bootstrap: true });
|
|
70
|
+
await expect(
|
|
71
|
+
client.request(HOST_ACTIVATION_REPORT_SERVICE, {
|
|
72
|
+
state: 'active',
|
|
73
|
+
revision: bootstrap.revision,
|
|
74
|
+
}),
|
|
75
|
+
).rejects.toMatchObject({ code: 'host-assembly-revision-mismatch' });
|
|
76
|
+
} finally {
|
|
77
|
+
remove();
|
|
78
|
+
client.close();
|
|
79
|
+
await backend.dispose();
|
|
80
|
+
}
|
|
81
|
+
expect(() => backend.subscribeActivationReports(() => {})).toThrow(HostAssemblyError);
|
|
82
|
+
});
|
|
29
83
|
it('activates a static frontend assembly through native Cordis Loader and unloads it', async () => {
|
|
30
84
|
const events: string[] = [];
|
|
31
85
|
const plugin: Plugin = {
|
|
@@ -357,6 +411,61 @@ describe('Engine host pair', () => {
|
|
|
357
411
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
358
412
|
});
|
|
359
413
|
|
|
414
|
+
it('preserves unknown remote business codes across the real WebSocket boundary', async () => {
|
|
415
|
+
const server = new WebSocketServer({ port: 0 });
|
|
416
|
+
await once(server, 'listening');
|
|
417
|
+
const address = server.address();
|
|
418
|
+
if (address === null || typeof address === 'string') throw new Error('WebSocket port missing');
|
|
419
|
+
const failures = {
|
|
420
|
+
capability: {
|
|
421
|
+
code: 'view-engine-capability-unavailable',
|
|
422
|
+
expected: 'Engine workspace capability project.open to be installed',
|
|
423
|
+
hint: 'Build and install the matching Engine workspace plugin before opening a project.',
|
|
424
|
+
detail: { capability: 'project.open' },
|
|
425
|
+
},
|
|
426
|
+
asset: {
|
|
427
|
+
code: 'view-workspace-asset-unsupported',
|
|
428
|
+
expected: 'Engine asset.open to support asset kind material',
|
|
429
|
+
hint: 'Use an Engine type-preview capability for this asset kind.',
|
|
430
|
+
detail: { kind: 'material' },
|
|
431
|
+
},
|
|
432
|
+
} as const;
|
|
433
|
+
const transport = createHostTransport();
|
|
434
|
+
const unregister = transport.register('forgeax.view.workspace.call', ({ payload }) => {
|
|
435
|
+
const failure =
|
|
436
|
+
(payload as { readonly operation?: unknown }).operation === 'asset.open'
|
|
437
|
+
? failures.asset
|
|
438
|
+
: failures.capability;
|
|
439
|
+
throw Object.assign(new Error(failure.hint), failure);
|
|
440
|
+
});
|
|
441
|
+
server.on('connection', (socket) => attachHostWebSocketServer(socket, transport));
|
|
442
|
+
let client: Awaited<ReturnType<typeof createHostWebSocketClient>> | undefined;
|
|
443
|
+
try {
|
|
444
|
+
client = await createHostWebSocketClient(new WebSocket(`ws://127.0.0.1:${address.port}`));
|
|
445
|
+
for (const failure of [failures.capability, failures.asset]) {
|
|
446
|
+
await expect(
|
|
447
|
+
client.request('forgeax.view.workspace.call', {
|
|
448
|
+
operation: failure === failures.asset ? 'asset.open' : 'project.open',
|
|
449
|
+
}),
|
|
450
|
+
).rejects.toMatchObject({
|
|
451
|
+
code: 'host-transport-failure',
|
|
452
|
+
expected: failure.expected,
|
|
453
|
+
hint: failure.hint,
|
|
454
|
+
detail: {
|
|
455
|
+
service: 'host/socket',
|
|
456
|
+
remoteCode: failure.code,
|
|
457
|
+
...failure.detail,
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
} finally {
|
|
462
|
+
client?.close();
|
|
463
|
+
unregister();
|
|
464
|
+
transport.close();
|
|
465
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
360
469
|
it('withdraws the frontend capability when its backend connection closes', async () => {
|
|
361
470
|
const plugin: Plugin = { name: 'disconnect-fixture', apply() {} };
|
|
362
471
|
const pair = {
|
|
@@ -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,
|
|
@@ -59,7 +60,15 @@ export interface BackendHost {
|
|
|
59
60
|
readonly transport: HostTransportServer;
|
|
60
61
|
readonly ownedContext: boolean;
|
|
61
62
|
/** Reconcile backend Entries and publish the matching frontend assembly atomically. */
|
|
62
|
-
update(
|
|
63
|
+
update(
|
|
64
|
+
input: HostAssemblyInput | readonly GamePluginEntry[],
|
|
65
|
+
/** Replace the one admitted bootstrap revision for staged frontend reconnects. */
|
|
66
|
+
options?: { readonly bootstrap?: boolean },
|
|
67
|
+
): Promise<HostAssembly>;
|
|
68
|
+
/** Observe accepted reports without replacing the owning host's callback. */
|
|
69
|
+
subscribeActivationReports(
|
|
70
|
+
listener: (report: HostActivationReport) => void | Promise<void>,
|
|
71
|
+
): () => void;
|
|
63
72
|
dispose(): Promise<void>;
|
|
64
73
|
}
|
|
65
74
|
|
|
@@ -157,7 +166,6 @@ function assertBackendCatalogIdentity(
|
|
|
157
166
|
*/
|
|
158
167
|
export async function createBackendHost(options: BackendHostOptions = {}): Promise<BackendHost> {
|
|
159
168
|
const context = options.context ?? new Context();
|
|
160
|
-
const ownedContext = options.context === undefined;
|
|
161
169
|
const realm = options.realm ?? 'engine';
|
|
162
170
|
const catalog = options.catalog;
|
|
163
171
|
const pairedBackendEntries =
|
|
@@ -188,7 +196,7 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
188
196
|
});
|
|
189
197
|
const checkedInitial = validateHostAssembly(initialAssembly);
|
|
190
198
|
if (!checkedInitial.ok) {
|
|
191
|
-
if (
|
|
199
|
+
if (options.context === undefined) await context.fiber.dispose();
|
|
192
200
|
throw checkedInitial.error;
|
|
193
201
|
}
|
|
194
202
|
const backendModules =
|
|
@@ -203,23 +211,27 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
203
211
|
// at the promoted full assembly from an earlier client. Keep that one
|
|
204
212
|
// staged revision valid for activation reports; all other revisions must
|
|
205
213
|
// still match the current authority exactly.
|
|
206
|
-
|
|
214
|
+
let bootstrapRevision = checkedInitial.value.revision;
|
|
215
|
+
const activationListeners = new Set<(report: HostActivationReport) => void | Promise<void>>();
|
|
207
216
|
const transport = options.transport ?? createHostTransport();
|
|
208
|
-
let
|
|
217
|
+
let startup: HostStartup | undefined;
|
|
209
218
|
let loaderFiber: Fiber | undefined;
|
|
210
219
|
let loader: CatalogLoader | undefined;
|
|
211
220
|
let backendEntries =
|
|
212
221
|
options.pairs === undefined
|
|
213
222
|
? (options.entries ?? options.assembly?.entries ?? [])
|
|
214
223
|
: pairedBackendEntries;
|
|
215
|
-
const startupFibers: Fiber[] = [];
|
|
216
224
|
let unregisterAssemblyService: (() => void) | undefined;
|
|
217
225
|
let unregisterActivationService: (() => void) | undefined;
|
|
218
226
|
let unsubscribeAssembly: (() => void) | undefined;
|
|
219
227
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
startup = await createHostStartup({
|
|
229
|
+
context,
|
|
230
|
+
startupPlugins: [
|
|
231
|
+
hostFoundationPlugin(assembly, transport),
|
|
232
|
+
...(options.startupPlugins ?? []),
|
|
233
|
+
],
|
|
234
|
+
});
|
|
223
235
|
if (catalog !== undefined) {
|
|
224
236
|
const bootstrapped = await bootstrapCatalogLoader(context, catalog, realm, {
|
|
225
237
|
catalogDigest: checkedInitial.value.revision,
|
|
@@ -250,6 +262,7 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
250
262
|
);
|
|
251
263
|
}
|
|
252
264
|
await options.onActivationReport?.(report);
|
|
265
|
+
for (const listener of activationListeners) await listener(report);
|
|
253
266
|
return { accepted: true };
|
|
254
267
|
},
|
|
255
268
|
);
|
|
@@ -261,9 +274,7 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
261
274
|
unregisterAssemblyService?.();
|
|
262
275
|
unsubscribeAssembly?.();
|
|
263
276
|
await loaderFiber?.dispose();
|
|
264
|
-
|
|
265
|
-
await foundationFiber?.dispose();
|
|
266
|
-
if (ownedContext) await context.fiber.dispose();
|
|
277
|
+
await startup?.dispose();
|
|
267
278
|
throw error;
|
|
268
279
|
}
|
|
269
280
|
|
|
@@ -273,8 +284,22 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
273
284
|
...(loader === undefined ? {} : { loader }),
|
|
274
285
|
assembly,
|
|
275
286
|
transport,
|
|
276
|
-
ownedContext,
|
|
277
|
-
|
|
287
|
+
ownedContext: options.context === undefined,
|
|
288
|
+
subscribeActivationReports(listener) {
|
|
289
|
+
if (disposed) {
|
|
290
|
+
throw new HostAssemblyError(
|
|
291
|
+
'host-assembly-service-unavailable',
|
|
292
|
+
'the backend host to remain active while observing activation reports',
|
|
293
|
+
'Subscribe on an active host instance.',
|
|
294
|
+
{ service: HOST_ACTIVATION_REPORT_SERVICE },
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
activationListeners.add(listener);
|
|
298
|
+
return () => {
|
|
299
|
+
activationListeners.delete(listener);
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
async update(nextInput, updateOptions) {
|
|
278
303
|
if (disposed) {
|
|
279
304
|
throw new HostAssemblyError(
|
|
280
305
|
'host-assembly-service-unavailable',
|
|
@@ -320,19 +345,19 @@ export async function createBackendHost(options: BackendHostOptions = {}): Promi
|
|
|
320
345
|
await loader.await();
|
|
321
346
|
}
|
|
322
347
|
backendEntries = nextBackendEntries;
|
|
348
|
+
if (updateOptions?.bootstrap === true) bootstrapRevision = checkedCandidate.value.revision;
|
|
323
349
|
return authority.publish(effectiveInput);
|
|
324
350
|
},
|
|
325
351
|
async dispose() {
|
|
326
352
|
if (disposed) return;
|
|
327
353
|
disposed = true;
|
|
354
|
+
activationListeners.clear();
|
|
328
355
|
unsubscribeAssembly?.();
|
|
329
356
|
unregisterActivationService?.();
|
|
330
357
|
unregisterAssemblyService?.();
|
|
331
358
|
transport.close();
|
|
332
359
|
await loaderFiber?.dispose();
|
|
333
|
-
|
|
334
|
-
await foundationFiber?.dispose();
|
|
335
|
-
if (ownedContext) await context.fiber.dispose();
|
|
360
|
+
await startup?.dispose();
|
|
336
361
|
},
|
|
337
362
|
};
|
|
338
363
|
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/protocol.ts
CHANGED
|
@@ -116,7 +116,12 @@ export interface HostAssemblyErrorDetailByCode {
|
|
|
116
116
|
'host-assembly-service-unavailable': { readonly service: string };
|
|
117
117
|
'host-assembly-stale-request': { readonly service: string; readonly generation: number };
|
|
118
118
|
'host-assembly-request-aborted': { readonly service: string };
|
|
119
|
-
'host-transport-failure': {
|
|
119
|
+
'host-transport-failure': {
|
|
120
|
+
readonly service: string;
|
|
121
|
+
readonly reason: string;
|
|
122
|
+
/** Business error code received from a remote host service, if any. */
|
|
123
|
+
readonly remoteCode?: string;
|
|
124
|
+
};
|
|
120
125
|
'host-assembly-not-ready': {
|
|
121
126
|
readonly entryId: string;
|
|
122
127
|
readonly fiberState: string;
|
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
|
+
}
|
package/src/transport.ts
CHANGED
|
@@ -431,8 +431,10 @@ function errorFromSummary(service: string, summary: HostErrorSummary): HostAssem
|
|
|
431
431
|
);
|
|
432
432
|
}
|
|
433
433
|
return new HostAssemblyError('host-transport-failure', summary.expected, summary.hint, {
|
|
434
|
+
...detail,
|
|
434
435
|
service,
|
|
435
436
|
reason: summary.detail.reason ? String(summary.detail.reason) : summary.code,
|
|
437
|
+
remoteCode: summary.code,
|
|
436
438
|
});
|
|
437
439
|
}
|
|
438
440
|
|