@shipfox/api-server 13.1.0 → 15.0.0
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +80 -0
- package/README.md +20 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules.d.ts +12 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +26 -4
- package/dist/modules.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +26 -26
- package/src/index.ts +1 -0
- package/src/modules.test.ts +137 -3
- package/src/modules.ts +62 -3
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@shipfox/api-server",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "15.0.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -22,36 +22,36 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@shipfox/annotations": "
|
|
25
|
+
"@shipfox/annotations": "15.0.0",
|
|
26
26
|
"@shipfox/annotations-dto": "12.3.0",
|
|
27
|
-
"@shipfox/api-agent": "
|
|
28
|
-
"@shipfox/api-agent-dto": "
|
|
29
|
-
"@shipfox/api-auth": "
|
|
30
|
-
"@shipfox/api-auth-dto": "
|
|
31
|
-
"@shipfox/api-definitions": "
|
|
32
|
-
"@shipfox/api-definitions-dto": "
|
|
33
|
-
"@shipfox/api-dispatcher": "
|
|
34
|
-
"@shipfox/api-email-challenges": "1.1.
|
|
35
|
-
"@shipfox/api-integration-core": "
|
|
36
|
-
"@shipfox/api-integration-core-dto": "
|
|
37
|
-
"@shipfox/api-logs": "
|
|
38
|
-
"@shipfox/api-projects": "
|
|
39
|
-
"@shipfox/api-projects-dto": "
|
|
40
|
-
"@shipfox/api-runners": "
|
|
41
|
-
"@shipfox/api-runners-dto": "
|
|
42
|
-
"@shipfox/api-secrets": "
|
|
27
|
+
"@shipfox/api-agent": "15.0.0",
|
|
28
|
+
"@shipfox/api-agent-dto": "15.0.0",
|
|
29
|
+
"@shipfox/api-auth": "15.0.0",
|
|
30
|
+
"@shipfox/api-auth-dto": "15.0.0",
|
|
31
|
+
"@shipfox/api-definitions": "15.0.0",
|
|
32
|
+
"@shipfox/api-definitions-dto": "15.0.0",
|
|
33
|
+
"@shipfox/api-dispatcher": "15.0.0",
|
|
34
|
+
"@shipfox/api-email-challenges": "1.1.10",
|
|
35
|
+
"@shipfox/api-integration-core": "15.0.0",
|
|
36
|
+
"@shipfox/api-integration-core-dto": "15.0.0",
|
|
37
|
+
"@shipfox/api-logs": "15.0.0",
|
|
38
|
+
"@shipfox/api-projects": "15.0.0",
|
|
39
|
+
"@shipfox/api-projects-dto": "15.0.0",
|
|
40
|
+
"@shipfox/api-runners": "15.0.0",
|
|
41
|
+
"@shipfox/api-runners-dto": "14.0.0",
|
|
42
|
+
"@shipfox/api-secrets": "15.0.0",
|
|
43
43
|
"@shipfox/api-secrets-dto": "12.0.0",
|
|
44
|
-
"@shipfox/api-triggers": "
|
|
45
|
-
"@shipfox/api-workflows": "
|
|
46
|
-
"@shipfox/api-workflows-dto": "
|
|
47
|
-
"@shipfox/api-workspaces-dto": "
|
|
48
|
-
"@shipfox/api-workspaces": "
|
|
44
|
+
"@shipfox/api-triggers": "15.0.0",
|
|
45
|
+
"@shipfox/api-workflows": "15.0.0",
|
|
46
|
+
"@shipfox/api-workflows-dto": "15.0.0",
|
|
47
|
+
"@shipfox/api-workspaces-dto": "15.0.0",
|
|
48
|
+
"@shipfox/api-workspaces": "15.0.0",
|
|
49
49
|
"@shipfox/config": "1.2.4",
|
|
50
50
|
"@shipfox/node-error-monitoring": "0.3.0",
|
|
51
|
-
"@shipfox/node-fastify": "0.4.
|
|
51
|
+
"@shipfox/node-fastify": "0.4.3",
|
|
52
52
|
"@shipfox/node-jwt": "0.4.0",
|
|
53
|
-
"@shipfox/node-module": "1.0.
|
|
54
|
-
"@shipfox/node-opentelemetry": "0.6.
|
|
53
|
+
"@shipfox/node-module": "1.0.7",
|
|
54
|
+
"@shipfox/node-opentelemetry": "0.6.5",
|
|
55
55
|
"@shipfox/node-postgres": "0.5.0"
|
|
56
56
|
},
|
|
57
57
|
"imports": {
|
package/src/index.ts
CHANGED
package/src/modules.test.ts
CHANGED
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
type WorkspacesInterModuleClient,
|
|
15
15
|
workspacesInterModuleContract,
|
|
16
16
|
} from '@shipfox/api-workspaces-dto/inter-module';
|
|
17
|
-
import {defineInterModulePresentation} from '@shipfox/inter-module';
|
|
17
|
+
import {defineInterModuleContract, defineInterModulePresentation} from '@shipfox/inter-module';
|
|
18
|
+
import type {DefaultAgentModuleFactory} from './modules.js';
|
|
18
19
|
import {defaultModules} from './modules.js';
|
|
19
20
|
|
|
20
21
|
const mocks = vi.hoisted(() => ({
|
|
@@ -35,6 +36,7 @@ const mocks = vi.hoisted(() => ({
|
|
|
35
36
|
deleteSecrets: vi.fn(),
|
|
36
37
|
getIntegrationConnectionById: vi.fn(),
|
|
37
38
|
getSecret: vi.fn(),
|
|
39
|
+
getSecretsByNamespace: vi.fn(),
|
|
38
40
|
getWorkspaceCreator: vi.fn(),
|
|
39
41
|
getWorkspaceOperatingState: vi.fn(),
|
|
40
42
|
listMembershipsForTokenClaims: vi.fn(),
|
|
@@ -109,6 +111,7 @@ describe('defaultModules', () => {
|
|
|
109
111
|
mocks.deleteSecrets.mockReset();
|
|
110
112
|
mocks.getIntegrationConnectionById.mockReset();
|
|
111
113
|
mocks.getSecret.mockReset();
|
|
114
|
+
mocks.getSecretsByNamespace.mockReset();
|
|
112
115
|
mocks.getWorkspaceCreator.mockReset();
|
|
113
116
|
mocks.listMembershipsForTokenClaims.mockReset();
|
|
114
117
|
mocks.setSecrets.mockReset();
|
|
@@ -123,6 +126,7 @@ describe('defaultModules', () => {
|
|
|
123
126
|
getAgentToolsContext: vi.fn(),
|
|
124
127
|
listSourceFiles: vi.fn(),
|
|
125
128
|
resolveConnection: vi.fn(),
|
|
129
|
+
resolveSourceRef: vi.fn(),
|
|
126
130
|
resolveSourceRepository: vi.fn(),
|
|
127
131
|
resolveTriggerReference: vi.fn(),
|
|
128
132
|
}),
|
|
@@ -151,6 +155,7 @@ describe('defaultModules', () => {
|
|
|
151
155
|
});
|
|
152
156
|
mocks.deleteSecrets.mockResolvedValue({deleted: 1});
|
|
153
157
|
mocks.getSecret.mockResolvedValue({value: 'secret'});
|
|
158
|
+
mocks.getSecretsByNamespace.mockResolvedValue({values: {}});
|
|
154
159
|
mocks.listMembershipsForTokenClaims.mockResolvedValue({memberships: []});
|
|
155
160
|
mocks.getWorkspaceCreator.mockResolvedValue({creatorUserId: null});
|
|
156
161
|
mocks.setSecrets.mockResolvedValue({});
|
|
@@ -185,7 +190,7 @@ describe('defaultModules', () => {
|
|
|
185
190
|
defineInterModulePresentation(secretsInterModuleContract, {
|
|
186
191
|
deleteSecrets: mocks.deleteSecrets,
|
|
187
192
|
getSecret: mocks.getSecret,
|
|
188
|
-
getSecretsByNamespace:
|
|
193
|
+
getSecretsByNamespace: mocks.getSecretsByNamespace,
|
|
189
194
|
getVariablesByNamespace: vi.fn(),
|
|
190
195
|
setSecrets: mocks.setSecrets,
|
|
191
196
|
}),
|
|
@@ -193,6 +198,7 @@ describe('defaultModules', () => {
|
|
|
193
198
|
});
|
|
194
199
|
mocks.createAgentModule.mockReturnValue({
|
|
195
200
|
name: 'agent',
|
|
201
|
+
database: {db: () => undefined as never, migrationsPath: 'test', databaseNamespace: 'agent'},
|
|
196
202
|
interModulePresentations: [
|
|
197
203
|
{
|
|
198
204
|
contract: agentInterModuleContract,
|
|
@@ -222,7 +228,11 @@ describe('defaultModules', () => {
|
|
|
222
228
|
interModulePresentations: [
|
|
223
229
|
{
|
|
224
230
|
contract: definitionsInterModuleContract,
|
|
225
|
-
handlers: {
|
|
231
|
+
handlers: {
|
|
232
|
+
getDefinitionForWorkflowRun: vi.fn(),
|
|
233
|
+
listDefinitionsAtRef: vi.fn(),
|
|
234
|
+
resolveDefinitionAtRef: vi.fn(),
|
|
235
|
+
},
|
|
226
236
|
},
|
|
227
237
|
],
|
|
228
238
|
});
|
|
@@ -236,6 +246,7 @@ describe('defaultModules', () => {
|
|
|
236
246
|
getLeasedAgentToolContext: vi.fn(),
|
|
237
247
|
getStepLogContext: vi.fn(),
|
|
238
248
|
resolveWorkflowRunTriggerReference: vi.fn(),
|
|
249
|
+
startDevRun: vi.fn(),
|
|
239
250
|
startRunFromTrigger: vi.fn(),
|
|
240
251
|
},
|
|
241
252
|
},
|
|
@@ -314,6 +325,129 @@ describe('defaultModules', () => {
|
|
|
314
325
|
expect(mocks.createAuthModule).toHaveBeenCalledWith({workspaces: expect.any(Object)});
|
|
315
326
|
});
|
|
316
327
|
|
|
328
|
+
it('uses the default Agent module factory when none is supplied', async () => {
|
|
329
|
+
await defaultModules();
|
|
330
|
+
|
|
331
|
+
expect(mocks.createAgentModule).toHaveBeenCalledWith({secrets: expect.any(Object)});
|
|
332
|
+
expect(mocks.createAgentModule.mock.calls[0]?.[0].secrets).not.toHaveProperty('getSecret');
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('composes Agent with the shared Secrets client and registers the supplied module', async () => {
|
|
336
|
+
const customAgentModule = mocks.createAgentModule();
|
|
337
|
+
mocks.createAgentModule.mockClear();
|
|
338
|
+
const agentModule = vi.fn<DefaultAgentModuleFactory>(({secrets}) =>
|
|
339
|
+
mocks.createAgentModule({secrets}),
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const modules = await defaultModules({agentModule});
|
|
343
|
+
const agentFactoryCall = agentModule.mock.calls[0];
|
|
344
|
+
expect(agentFactoryCall).toBeDefined();
|
|
345
|
+
if (!agentFactoryCall) throw new Error('Agent module factory was not called.');
|
|
346
|
+
const agentSecrets = agentFactoryCall[0].secrets;
|
|
347
|
+
const scope = {
|
|
348
|
+
workspaceId: crypto.randomUUID(),
|
|
349
|
+
projectId: null,
|
|
350
|
+
namespace: 'agent',
|
|
351
|
+
};
|
|
352
|
+
const secretValues = await agentSecrets.getSecretsByNamespace(scope);
|
|
353
|
+
|
|
354
|
+
expect(agentModule).toHaveBeenCalledWith({secrets: expect.any(Object)});
|
|
355
|
+
expect(secretValues).toEqual({values: {}});
|
|
356
|
+
expect(mocks.getSecretsByNamespace).toHaveBeenCalledWith(
|
|
357
|
+
scope,
|
|
358
|
+
expect.objectContaining({signal: expect.any(AbortSignal)}),
|
|
359
|
+
);
|
|
360
|
+
expect(agentSecrets).not.toHaveProperty('getSecret');
|
|
361
|
+
expect(agentSecrets).not.toHaveProperty('getVariablesByNamespace');
|
|
362
|
+
expect(mocks.createAgentModule).toHaveBeenCalledWith({secrets: agentSecrets});
|
|
363
|
+
expect(mocks.createAgentModule).toHaveBeenCalledTimes(1);
|
|
364
|
+
expect(modules.filter((module) => module.name === 'agent')).toEqual([customAgentModule]);
|
|
365
|
+
expect(
|
|
366
|
+
modules
|
|
367
|
+
.flatMap((module) =>
|
|
368
|
+
(module.interModulePresentations ?? []).map(({contract}) => contract.module),
|
|
369
|
+
)
|
|
370
|
+
.filter((module) => module === agentInterModuleContract.module),
|
|
371
|
+
).toEqual([agentInterModuleContract.module]);
|
|
372
|
+
expect(modules.map((module) => module.name)).toEqual([
|
|
373
|
+
'email-challenges',
|
|
374
|
+
'auth',
|
|
375
|
+
'workspaces',
|
|
376
|
+
'secrets',
|
|
377
|
+
'agent',
|
|
378
|
+
'integrations',
|
|
379
|
+
'projects',
|
|
380
|
+
'definitions',
|
|
381
|
+
'workflows',
|
|
382
|
+
'annotations',
|
|
383
|
+
'runners',
|
|
384
|
+
'logs',
|
|
385
|
+
'triggers',
|
|
386
|
+
'dispatcher',
|
|
387
|
+
]);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('rejects a custom Agent module without the Agent database namespace', async () => {
|
|
391
|
+
const customAgentModule = {
|
|
392
|
+
...mocks.createAgentModule(),
|
|
393
|
+
database: {
|
|
394
|
+
db: () => undefined as never,
|
|
395
|
+
migrationsPath: 'test',
|
|
396
|
+
databaseNamespace: 'custom_agent',
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
await expect(defaultModules({agentModule: () => customAgentModule})).rejects.toThrow(
|
|
401
|
+
'Custom agentModule must declare database namespace "agent"',
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('rejects a custom Agent module without the canonical Agent presentation', async () => {
|
|
406
|
+
const customAgentModule = {
|
|
407
|
+
...mocks.createAgentModule(),
|
|
408
|
+
interModulePresentations: [],
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
await expect(defaultModules({agentModule: () => customAgentModule})).rejects.toThrow(
|
|
412
|
+
'Custom agentModule must present the canonical "agent" inter-module contract',
|
|
413
|
+
);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('rejects a custom Agent module with a mismatched Agent contract', async () => {
|
|
417
|
+
const defaultAgentModule = mocks.createAgentModule();
|
|
418
|
+
const defaultPresentation = defaultAgentModule.interModulePresentations?.[0];
|
|
419
|
+
if (!defaultPresentation) throw new Error('Default Agent presentation is not configured.');
|
|
420
|
+
const mismatchedAgentContract = defineInterModuleContract({
|
|
421
|
+
module: agentInterModuleContract.module,
|
|
422
|
+
methods: Object.fromEntries(
|
|
423
|
+
Object.entries(agentInterModuleContract.methods).map(([method, contract]) => [
|
|
424
|
+
method,
|
|
425
|
+
{input: contract.input, output: contract.output, errors: contract.errors},
|
|
426
|
+
]),
|
|
427
|
+
),
|
|
428
|
+
});
|
|
429
|
+
const customAgentModule = {
|
|
430
|
+
...defaultAgentModule,
|
|
431
|
+
interModulePresentations: [{...defaultPresentation, contract: mismatchedAgentContract}],
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
await expect(defaultModules({agentModule: () => customAgentModule})).rejects.toThrow(
|
|
435
|
+
'Custom agentModule must present the canonical "agent" inter-module contract',
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('rejects an extension that duplicates the Agent presentation', async () => {
|
|
440
|
+
const defaultAgentModule = mocks.createAgentModule();
|
|
441
|
+
const agentPresentation = defaultAgentModule.interModulePresentations?.[0];
|
|
442
|
+
if (!agentPresentation) throw new Error('Default Agent presentation is not configured.');
|
|
443
|
+
|
|
444
|
+
await expect(
|
|
445
|
+
defaultModules({
|
|
446
|
+
extension: () => [{name: 'duplicate-agent', interModulePresentations: [agentPresentation]}],
|
|
447
|
+
}),
|
|
448
|
+
).rejects.toThrow('Module "agent" already has a registered presentation');
|
|
449
|
+
});
|
|
450
|
+
|
|
317
451
|
it('composes Auth with the shared Workspaces client and registers the supplied module', async () => {
|
|
318
452
|
const signupPolicy = {isSignupAllowed: vi.fn().mockResolvedValue({allowed: true})};
|
|
319
453
|
const customAuthModule = mocks.createAuthModule();
|
package/src/modules.ts
CHANGED
|
@@ -20,7 +20,10 @@ import {projectsInterModuleContract} from '@shipfox/api-projects-dto/inter-modul
|
|
|
20
20
|
import {createRunnersModule} from '@shipfox/api-runners';
|
|
21
21
|
import {runnersInterModuleContract} from '@shipfox/api-runners-dto/inter-module';
|
|
22
22
|
import {createSecretsModule} from '@shipfox/api-secrets';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
type SecretsInterModuleClient,
|
|
25
|
+
secretsInterModuleContract,
|
|
26
|
+
} from '@shipfox/api-secrets-dto/inter-module';
|
|
24
27
|
import {createTriggersModule} from '@shipfox/api-triggers';
|
|
25
28
|
import {createWorkflowsModule} from '@shipfox/api-workflows';
|
|
26
29
|
import {workflowsInterModuleContract} from '@shipfox/api-workflows-dto/inter-module';
|
|
@@ -41,6 +44,7 @@ import {logger} from '@shipfox/node-opentelemetry';
|
|
|
41
44
|
export interface DefaultModulesOptions {
|
|
42
45
|
webhookDeliverySource?: WebhookDeliverySource | undefined;
|
|
43
46
|
authModule?: DefaultAuthModuleFactory | undefined;
|
|
47
|
+
agentModule?: DefaultAgentModuleFactory | undefined;
|
|
44
48
|
runnersModule?: DefaultRunnersModuleFactory | undefined;
|
|
45
49
|
extension?: DefaultModulesExtension | undefined;
|
|
46
50
|
}
|
|
@@ -48,6 +52,17 @@ export interface DefaultModulesOptions {
|
|
|
48
52
|
export type DefaultAuthModuleFactory = (options: {
|
|
49
53
|
workspaces: WorkspacesInterModuleClient;
|
|
50
54
|
}) => ShipfoxModule;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Builds the Agent module in the standard composition slot. The default
|
|
58
|
+
* factory validates Agent configuration through `createAgentModule`; a custom
|
|
59
|
+
* factory owns equivalent validation when it does not delegate to that
|
|
60
|
+
* factory. Custom modules must preserve the `agent` database namespace and
|
|
61
|
+
* canonical Agent presentation required by the composed clients.
|
|
62
|
+
*/
|
|
63
|
+
export type DefaultAgentModuleFactory = (options: {
|
|
64
|
+
secrets: Pick<SecretsInterModuleClient, 'deleteSecrets' | 'getSecretsByNamespace' | 'setSecrets'>;
|
|
65
|
+
}) => ShipfoxModule;
|
|
51
66
|
export type DefaultRunnersModuleFactory = (options: {auth: AuthInterModuleClient}) => ShipfoxModule;
|
|
52
67
|
export type DefaultModulesExtension = (options: {
|
|
53
68
|
workspaces: WorkspacesInterModuleClient;
|
|
@@ -166,13 +181,17 @@ export async function defaultModules(
|
|
|
166
181
|
integrations: integrationsClient,
|
|
167
182
|
});
|
|
168
183
|
const extensionModules = options.extension?.({workspaces: workspacesClient}) ?? [];
|
|
184
|
+
const agentModule = (options.agentModule ?? createAgentModule)({
|
|
185
|
+
secrets: createAgentSecretsClient(secretsClient),
|
|
186
|
+
});
|
|
187
|
+
if (options.agentModule) validateCustomAgentModule(agentModule);
|
|
169
188
|
|
|
170
189
|
const modules = [
|
|
171
190
|
emailChallengesModule,
|
|
172
191
|
(options.authModule ?? createAuthModule)({workspaces: workspacesClient}),
|
|
173
192
|
createWorkspacesModule({auth: authClient, projects: projectsClient, runners: runnersClient}),
|
|
174
193
|
createSecretsModule(projectsClient),
|
|
175
|
-
|
|
194
|
+
agentModule,
|
|
176
195
|
integrations.module,
|
|
177
196
|
projectsModule,
|
|
178
197
|
definitionsModule,
|
|
@@ -193,7 +212,11 @@ export async function defaultModules(
|
|
|
193
212
|
workflows: workflowsClient,
|
|
194
213
|
jobLeaseTokenTtlSeconds: durationToSeconds(authConfig.AUTH_JOB_LEASE_TOKEN_EXPIRES_IN),
|
|
195
214
|
}),
|
|
196
|
-
createTriggersModule({
|
|
215
|
+
createTriggersModule({
|
|
216
|
+
workflows: workflowsClient,
|
|
217
|
+
definitions: definitionsClient,
|
|
218
|
+
projects: projectsClient,
|
|
219
|
+
}),
|
|
197
220
|
dispatcherModule,
|
|
198
221
|
...extensionModules,
|
|
199
222
|
];
|
|
@@ -201,3 +224,39 @@ export async function defaultModules(
|
|
|
201
224
|
interModuleTransport.seal();
|
|
202
225
|
return modules;
|
|
203
226
|
}
|
|
227
|
+
|
|
228
|
+
type AgentModuleSecretsClient = Pick<
|
|
229
|
+
SecretsInterModuleClient,
|
|
230
|
+
'deleteSecrets' | 'getSecretsByNamespace' | 'setSecrets'
|
|
231
|
+
>;
|
|
232
|
+
|
|
233
|
+
function createAgentSecretsClient(
|
|
234
|
+
secretsClient: SecretsInterModuleClient,
|
|
235
|
+
): AgentModuleSecretsClient {
|
|
236
|
+
return {
|
|
237
|
+
deleteSecrets: (input, options) => secretsClient.deleteSecrets(input, options),
|
|
238
|
+
getSecretsByNamespace: (input, options) => secretsClient.getSecretsByNamespace(input, options),
|
|
239
|
+
setSecrets: (input, options) => secretsClient.setSecrets(input, options),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function validateCustomAgentModule(module: ShipfoxModule): void {
|
|
244
|
+
const databases = module.database
|
|
245
|
+
? Array.isArray(module.database)
|
|
246
|
+
? module.database
|
|
247
|
+
: [module.database]
|
|
248
|
+
: [];
|
|
249
|
+
if (
|
|
250
|
+
!databases.some(({databaseNamespace}) => databaseNamespace === agentInterModuleContract.module)
|
|
251
|
+
) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
'Custom agentModule must declare database namespace "agent" for the Agent module.',
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (
|
|
258
|
+
!module.interModulePresentations?.some(({contract}) => contract === agentInterModuleContract)
|
|
259
|
+
) {
|
|
260
|
+
throw new Error('Custom agentModule must present the canonical "agent" inter-module contract.');
|
|
261
|
+
}
|
|
262
|
+
}
|