@chidchanun/bcp 0.3.0 → 0.3.2
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 +135 -111
- package/docs/README.md +53 -78
- package/docs/api-freeze-snapshot.json +24 -2
- package/docs/api-manifest.json +29 -13
- package/docs/api-reference.md +195 -197
- package/docs/application-platform.md +141 -171
- package/docs/docs-web-manifest.json +9 -5
- package/docs/migration-0.3.md +189 -68
- package/docs/module-system-v2.md +386 -0
- package/docs/platform-contract.md +53 -37
- package/docs/platform-manifest.json +36 -4
- package/docs/releases/0.3.1.md +116 -0
- package/docs/releases/0.3.2.md +125 -0
- package/docs/service-container.md +333 -0
- package/package.json +13 -1
- package/packages/bundler/src/client-boundary.ts +2 -0
- package/packages/client/src/application.mjs +1004 -34
- package/packages/client/src/container.mjs +573 -0
- package/packages/client/src/container.ts +24 -0
- package/packages/client/src/modules.mjs +392 -0
- package/packages/client/src/modules.ts +17 -0
- package/packages/server/src/application.ts +171 -11
- package/packages/server/src/container.ts +970 -0
- package/packages/server/src/modules.ts +682 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// packages/server/src/modules.ts
|
|
2
|
+
var MODULE_V2_KIND = "bcp-module-v2";
|
|
3
|
+
var ModuleDependencyError = class extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ModuleDependencyError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var ModuleLifecycleError = class extends Error {
|
|
10
|
+
module;
|
|
11
|
+
phase;
|
|
12
|
+
cause;
|
|
13
|
+
constructor(moduleName, phase, cause) {
|
|
14
|
+
super(
|
|
15
|
+
`BCP Modules: ${phase} failed for module "${moduleName}": ${formatError(cause)}`
|
|
16
|
+
);
|
|
17
|
+
this.name = "ModuleLifecycleError";
|
|
18
|
+
this.module = moduleName;
|
|
19
|
+
this.phase = phase;
|
|
20
|
+
this.cause = cause;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function defineModule(input) {
|
|
24
|
+
const definition = {
|
|
25
|
+
...input,
|
|
26
|
+
kind: MODULE_V2_KIND
|
|
27
|
+
};
|
|
28
|
+
validateModuleDefinition(definition);
|
|
29
|
+
return definition;
|
|
30
|
+
}
|
|
31
|
+
function isModuleDefinition(value) {
|
|
32
|
+
return Boolean(
|
|
33
|
+
value && typeof value === "object" && value.kind === MODULE_V2_KIND
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function composeModules(roots = []) {
|
|
37
|
+
const ordered = resolveModuleOrder(roots);
|
|
38
|
+
const providers = [];
|
|
39
|
+
const plugins = [];
|
|
40
|
+
const services = [];
|
|
41
|
+
const resources = [];
|
|
42
|
+
const serviceKeys = /* @__PURE__ */ new Set();
|
|
43
|
+
for (const module of ordered) {
|
|
44
|
+
providers.push(
|
|
45
|
+
...module.providers ?? []
|
|
46
|
+
);
|
|
47
|
+
plugins.push(
|
|
48
|
+
...module.plugins ?? []
|
|
49
|
+
);
|
|
50
|
+
resources.push(
|
|
51
|
+
...module.resources ?? []
|
|
52
|
+
);
|
|
53
|
+
for (const entry of module.services ?? []) {
|
|
54
|
+
const [key] = entry;
|
|
55
|
+
if (serviceKeys.has(key)) {
|
|
56
|
+
throw new ModuleDependencyError(
|
|
57
|
+
`BCP Modules: duplicate shared service ${formatServiceKey(key)} while composing module "${module.name}".`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
serviceKeys.add(key);
|
|
61
|
+
services.push(entry);
|
|
62
|
+
}
|
|
63
|
+
validateModuleExports(
|
|
64
|
+
module
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
modules: ordered,
|
|
69
|
+
providers,
|
|
70
|
+
plugins,
|
|
71
|
+
services,
|
|
72
|
+
resources,
|
|
73
|
+
records() {
|
|
74
|
+
return ordered.map(
|
|
75
|
+
(module) => ({
|
|
76
|
+
name: module.name,
|
|
77
|
+
...module.version ? {
|
|
78
|
+
version: module.version
|
|
79
|
+
} : {},
|
|
80
|
+
imports: (module.imports ?? []).map(
|
|
81
|
+
(dependency) => dependency.name
|
|
82
|
+
),
|
|
83
|
+
providers: (module.providers ?? []).map(
|
|
84
|
+
(provider) => provider.token.description
|
|
85
|
+
),
|
|
86
|
+
exports: (module.exports ?? []).map(
|
|
87
|
+
(token) => token.description
|
|
88
|
+
),
|
|
89
|
+
plugins: (module.plugins ?? []).map(
|
|
90
|
+
(plugin) => plugin.name
|
|
91
|
+
),
|
|
92
|
+
resources: (module.resources ?? []).map(
|
|
93
|
+
(resource) => resource.name
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
exportedTokens(moduleName) {
|
|
99
|
+
const normalized = normalizeName(
|
|
100
|
+
moduleName,
|
|
101
|
+
"module name"
|
|
102
|
+
);
|
|
103
|
+
const module = ordered.find(
|
|
104
|
+
(entry) => entry.name === normalized
|
|
105
|
+
);
|
|
106
|
+
if (!module) {
|
|
107
|
+
throw new ModuleDependencyError(
|
|
108
|
+
`BCP Modules: module "${normalized}" is not part of this composition.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return [
|
|
112
|
+
...module.exports ?? []
|
|
113
|
+
];
|
|
114
|
+
},
|
|
115
|
+
createLifecycleResources(context) {
|
|
116
|
+
return ordered.flatMap(
|
|
117
|
+
(module) => [
|
|
118
|
+
...module.resources ?? [],
|
|
119
|
+
createModuleLifecycleResource(
|
|
120
|
+
module,
|
|
121
|
+
context
|
|
122
|
+
)
|
|
123
|
+
]
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function resolveModuleOrder(roots) {
|
|
129
|
+
const byName = /* @__PURE__ */ new Map();
|
|
130
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
131
|
+
const visited = /* @__PURE__ */ new Set();
|
|
132
|
+
const order = [];
|
|
133
|
+
const visit = (module, path) => {
|
|
134
|
+
validateModuleDefinition(module);
|
|
135
|
+
const name = module.name;
|
|
136
|
+
const existing = byName.get(name);
|
|
137
|
+
if (existing && existing !== module) {
|
|
138
|
+
throw new ModuleDependencyError(
|
|
139
|
+
`BCP Modules: duplicate module name "${name}" refers to different definitions.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
byName.set(name, module);
|
|
143
|
+
if (visited.has(name)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (visiting.has(name)) {
|
|
147
|
+
throw new ModuleDependencyError(
|
|
148
|
+
`BCP Modules: circular module dependency detected: ${[
|
|
149
|
+
...path,
|
|
150
|
+
name
|
|
151
|
+
].join(" -> ")}.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
visiting.add(name);
|
|
155
|
+
for (const dependency of module.imports ?? []) {
|
|
156
|
+
visit(
|
|
157
|
+
dependency,
|
|
158
|
+
[
|
|
159
|
+
...path,
|
|
160
|
+
name
|
|
161
|
+
]
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
visiting.delete(name);
|
|
165
|
+
visited.add(name);
|
|
166
|
+
order.push(module);
|
|
167
|
+
};
|
|
168
|
+
for (const root of roots) {
|
|
169
|
+
visit(root, []);
|
|
170
|
+
}
|
|
171
|
+
return order;
|
|
172
|
+
}
|
|
173
|
+
function createModuleLifecycleResource(module, shared) {
|
|
174
|
+
const config = parseModuleConfig(
|
|
175
|
+
module.schema,
|
|
176
|
+
module.config
|
|
177
|
+
);
|
|
178
|
+
const context = {
|
|
179
|
+
name: module.name,
|
|
180
|
+
config,
|
|
181
|
+
container: shared.container,
|
|
182
|
+
services: shared.services,
|
|
183
|
+
hooks: shared.hooks
|
|
184
|
+
};
|
|
185
|
+
let setupComplete = false;
|
|
186
|
+
let disposed = false;
|
|
187
|
+
return {
|
|
188
|
+
name: `bcp:module:${module.name}`,
|
|
189
|
+
async start() {
|
|
190
|
+
try {
|
|
191
|
+
if (!setupComplete) {
|
|
192
|
+
await runModuleHook(
|
|
193
|
+
module,
|
|
194
|
+
"setup",
|
|
195
|
+
module.setup,
|
|
196
|
+
context
|
|
197
|
+
);
|
|
198
|
+
setupComplete = true;
|
|
199
|
+
}
|
|
200
|
+
await runModuleHook(
|
|
201
|
+
module,
|
|
202
|
+
"start",
|
|
203
|
+
module.start,
|
|
204
|
+
context
|
|
205
|
+
);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (!disposed) {
|
|
208
|
+
disposed = true;
|
|
209
|
+
try {
|
|
210
|
+
await runModuleHook(
|
|
211
|
+
module,
|
|
212
|
+
"dispose",
|
|
213
|
+
module.dispose,
|
|
214
|
+
context
|
|
215
|
+
);
|
|
216
|
+
} catch (disposeError) {
|
|
217
|
+
throw new AggregateError(
|
|
218
|
+
[
|
|
219
|
+
error,
|
|
220
|
+
disposeError
|
|
221
|
+
],
|
|
222
|
+
`BCP Modules: startup and cleanup failed for module "${module.name}".`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
ready() {
|
|
230
|
+
return true;
|
|
231
|
+
},
|
|
232
|
+
async stop() {
|
|
233
|
+
const errors = [];
|
|
234
|
+
try {
|
|
235
|
+
await runModuleHook(
|
|
236
|
+
module,
|
|
237
|
+
"stop",
|
|
238
|
+
module.stop,
|
|
239
|
+
context
|
|
240
|
+
);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
errors.push(error);
|
|
243
|
+
}
|
|
244
|
+
if (!disposed) {
|
|
245
|
+
disposed = true;
|
|
246
|
+
try {
|
|
247
|
+
await runModuleHook(
|
|
248
|
+
module,
|
|
249
|
+
"dispose",
|
|
250
|
+
module.dispose,
|
|
251
|
+
context
|
|
252
|
+
);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
errors.push(error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (errors.length > 0) {
|
|
258
|
+
throw new AggregateError(
|
|
259
|
+
errors,
|
|
260
|
+
`BCP Modules: shutdown failed for module "${module.name}".`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
diagnostics() {
|
|
265
|
+
return {
|
|
266
|
+
name: module.name,
|
|
267
|
+
...module.version ? {
|
|
268
|
+
version: module.version
|
|
269
|
+
} : {},
|
|
270
|
+
imports: (module.imports ?? []).map(
|
|
271
|
+
(dependency) => dependency.name
|
|
272
|
+
),
|
|
273
|
+
exports: (module.exports ?? []).map(
|
|
274
|
+
(token) => token.description
|
|
275
|
+
)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
async function runModuleHook(module, phase, hook, context) {
|
|
281
|
+
if (!hook) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
await hook(context);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
throw new ModuleLifecycleError(
|
|
288
|
+
module.name,
|
|
289
|
+
phase,
|
|
290
|
+
error
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function validateModuleDefinition(module) {
|
|
295
|
+
if (!module || typeof module !== "object") {
|
|
296
|
+
throw new TypeError(
|
|
297
|
+
"BCP Modules: module must be an object."
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (module.kind !== MODULE_V2_KIND) {
|
|
301
|
+
throw new TypeError(
|
|
302
|
+
`BCP Modules: module kind must be "${MODULE_V2_KIND}". Use defineModule().`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
normalizeName(
|
|
306
|
+
module.name,
|
|
307
|
+
"module name"
|
|
308
|
+
);
|
|
309
|
+
for (const [label, value] of [
|
|
310
|
+
["imports", module.imports],
|
|
311
|
+
["providers", module.providers],
|
|
312
|
+
["exports", module.exports],
|
|
313
|
+
["plugins", module.plugins],
|
|
314
|
+
["resources", module.resources]
|
|
315
|
+
]) {
|
|
316
|
+
if (value !== void 0 && !Array.isArray(value)) {
|
|
317
|
+
throw new TypeError(
|
|
318
|
+
`BCP Modules: ${label} must be an array.`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const [phase, hook] of [
|
|
323
|
+
["setup", module.setup],
|
|
324
|
+
["start", module.start],
|
|
325
|
+
["stop", module.stop],
|
|
326
|
+
["dispose", module.dispose]
|
|
327
|
+
]) {
|
|
328
|
+
if (hook !== void 0 && typeof hook !== "function") {
|
|
329
|
+
throw new TypeError(
|
|
330
|
+
`BCP Modules: ${phase} must be a function.`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function validateModuleExports(module) {
|
|
336
|
+
const available = /* @__PURE__ */ new Set();
|
|
337
|
+
for (const provider of module.providers ?? []) {
|
|
338
|
+
available.add(
|
|
339
|
+
provider.token.id
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
for (const dependency of module.imports ?? []) {
|
|
343
|
+
for (const token of dependency.exports ?? []) {
|
|
344
|
+
available.add(token.id);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
for (const token of module.exports ?? []) {
|
|
348
|
+
if (!available.has(token.id)) {
|
|
349
|
+
throw new ModuleDependencyError(
|
|
350
|
+
`BCP Modules: module "${module.name}" exports "${token.description}" but does not provide or import it.`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function parseModuleConfig(parser, value) {
|
|
356
|
+
if (!parser) {
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
if (typeof parser === "function") {
|
|
360
|
+
return parser(value);
|
|
361
|
+
}
|
|
362
|
+
return parser.parse(value);
|
|
363
|
+
}
|
|
364
|
+
function normalizeName(value, label) {
|
|
365
|
+
if (typeof value !== "string") {
|
|
366
|
+
throw new TypeError(
|
|
367
|
+
`BCP Modules: ${label} must be a string.`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const normalized = value.trim();
|
|
371
|
+
if (!normalized) {
|
|
372
|
+
throw new TypeError(
|
|
373
|
+
`BCP Modules: ${label} cannot be empty.`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
return normalized;
|
|
377
|
+
}
|
|
378
|
+
function formatServiceKey(key) {
|
|
379
|
+
return typeof key === "symbol" ? key.description ? `Symbol(${key.description})` : key.toString() : key;
|
|
380
|
+
}
|
|
381
|
+
function formatError(value) {
|
|
382
|
+
return value instanceof Error ? value.message : String(value);
|
|
383
|
+
}
|
|
384
|
+
export {
|
|
385
|
+
MODULE_V2_KIND,
|
|
386
|
+
ModuleDependencyError,
|
|
387
|
+
ModuleLifecycleError,
|
|
388
|
+
composeModules,
|
|
389
|
+
defineModule,
|
|
390
|
+
isModuleDefinition,
|
|
391
|
+
resolveModuleOrder
|
|
392
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
MODULE_V2_KIND,
|
|
3
|
+
ModuleDependencyError,
|
|
4
|
+
ModuleLifecycleError,
|
|
5
|
+
composeModules,
|
|
6
|
+
defineModule,
|
|
7
|
+
isModuleDefinition,
|
|
8
|
+
resolveModuleOrder,
|
|
9
|
+
|
|
10
|
+
type ModuleComposition,
|
|
11
|
+
type ModuleConfigParser,
|
|
12
|
+
type ModuleConfigSchema,
|
|
13
|
+
type ModuleContext,
|
|
14
|
+
type ModuleDefinition,
|
|
15
|
+
type ModuleInput,
|
|
16
|
+
type ModuleRecord,
|
|
17
|
+
} from "../../server/src/modules.js";
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createServiceContainer,
|
|
3
|
+
|
|
4
|
+
type ServiceContainer,
|
|
5
|
+
type ServiceProvider,
|
|
6
|
+
type ServiceScope,
|
|
7
|
+
type ServiceScopeOptions,
|
|
8
|
+
} from "./container.js";
|
|
9
|
+
|
|
1
10
|
import {
|
|
2
11
|
createDeploymentRuntime,
|
|
3
12
|
|
|
@@ -11,6 +20,14 @@ import {
|
|
|
11
20
|
type DeploymentSignalOptions,
|
|
12
21
|
} from "./deployment.js";
|
|
13
22
|
|
|
23
|
+
import {
|
|
24
|
+
composeModules,
|
|
25
|
+
isModuleDefinition,
|
|
26
|
+
|
|
27
|
+
type ModuleComposition,
|
|
28
|
+
type ModuleDefinition,
|
|
29
|
+
} from "./modules.js";
|
|
30
|
+
|
|
14
31
|
import {
|
|
15
32
|
createPluginHost,
|
|
16
33
|
|
|
@@ -52,6 +69,8 @@ export interface ApplicationContext<TConfig = unknown> {
|
|
|
52
69
|
readonly name: string;
|
|
53
70
|
readonly version?: string;
|
|
54
71
|
readonly config: TConfig;
|
|
72
|
+
readonly container: ServiceContainer;
|
|
73
|
+
readonly modules: ModuleComposition;
|
|
55
74
|
readonly services: PluginServiceRegistry;
|
|
56
75
|
readonly hooks: PluginHookBus;
|
|
57
76
|
readonly plugins: PluginHost;
|
|
@@ -65,8 +84,12 @@ export interface ApplicationDefinition<TConfig = unknown> {
|
|
|
65
84
|
version?: string;
|
|
66
85
|
config?: unknown;
|
|
67
86
|
schema?: ApplicationConfigParser<TConfig>;
|
|
87
|
+
providers?: readonly ServiceProvider<any>[];
|
|
68
88
|
plugins?: readonly PluginDefinition<any>[];
|
|
69
|
-
modules?: readonly
|
|
89
|
+
modules?: readonly (
|
|
90
|
+
| PluginModule
|
|
91
|
+
| ModuleDefinition<any>
|
|
92
|
+
)[];
|
|
70
93
|
pluginConfigs?: Record<string, unknown>;
|
|
71
94
|
services?: Iterable<
|
|
72
95
|
readonly [PluginServiceKey, unknown]
|
|
@@ -96,6 +119,8 @@ export interface Application<TConfig = unknown> {
|
|
|
96
119
|
readonly state: ApplicationState;
|
|
97
120
|
readonly config: TConfig;
|
|
98
121
|
readonly context: ApplicationContext<TConfig>;
|
|
122
|
+
readonly container: ServiceContainer;
|
|
123
|
+
readonly modules: ModuleComposition;
|
|
99
124
|
readonly services: PluginServiceRegistry;
|
|
100
125
|
readonly hooks: PluginHookBus;
|
|
101
126
|
readonly plugins: PluginHost;
|
|
@@ -112,6 +137,15 @@ export interface Application<TConfig = unknown> {
|
|
|
112
137
|
replace?: boolean;
|
|
113
138
|
}
|
|
114
139
|
): Application<TConfig>;
|
|
140
|
+
register<T>(
|
|
141
|
+
provider: ServiceProvider<T>,
|
|
142
|
+
options?: {
|
|
143
|
+
replace?: boolean;
|
|
144
|
+
}
|
|
145
|
+
): Application<TConfig>;
|
|
146
|
+
createScope(
|
|
147
|
+
options?: ServiceScopeOptions
|
|
148
|
+
): ServiceScope;
|
|
115
149
|
addResource(
|
|
116
150
|
resource: DeploymentResource
|
|
117
151
|
): Application<TConfig>;
|
|
@@ -180,16 +214,42 @@ export function createApp<TConfig = unknown>(
|
|
|
180
214
|
definition.schema,
|
|
181
215
|
definition.config
|
|
182
216
|
);
|
|
217
|
+
const moduleInputs =
|
|
218
|
+
definition.modules ?? [];
|
|
219
|
+
const modules =
|
|
220
|
+
composeModules(
|
|
221
|
+
moduleInputs.filter(
|
|
222
|
+
isModuleDefinition
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
const legacyModules =
|
|
226
|
+
moduleInputs.filter(
|
|
227
|
+
module =>
|
|
228
|
+
!isModuleDefinition(module)
|
|
229
|
+
) as PluginModule[];
|
|
230
|
+
const container =
|
|
231
|
+
createServiceContainer({
|
|
232
|
+
name:
|
|
233
|
+
`${name}:container`,
|
|
234
|
+
providers: [
|
|
235
|
+
...modules.providers,
|
|
236
|
+
...(definition.providers ?? []),
|
|
237
|
+
],
|
|
238
|
+
});
|
|
183
239
|
const plugins =
|
|
184
240
|
createPluginHost({
|
|
185
|
-
plugins:
|
|
186
|
-
|
|
241
|
+
plugins: [
|
|
242
|
+
...modules.plugins,
|
|
243
|
+
...(definition.plugins ?? []),
|
|
244
|
+
],
|
|
187
245
|
modules:
|
|
188
|
-
|
|
246
|
+
legacyModules,
|
|
189
247
|
configs:
|
|
190
248
|
definition.pluginConfigs,
|
|
191
|
-
services:
|
|
192
|
-
|
|
249
|
+
services: [
|
|
250
|
+
...modules.services,
|
|
251
|
+
...(definition.services ?? []),
|
|
252
|
+
],
|
|
193
253
|
});
|
|
194
254
|
const deployment =
|
|
195
255
|
createDeploymentRuntime({
|
|
@@ -229,6 +289,8 @@ export function createApp<TConfig = unknown>(
|
|
|
229
289
|
}
|
|
230
290
|
: {}),
|
|
231
291
|
config,
|
|
292
|
+
container,
|
|
293
|
+
modules,
|
|
232
294
|
services:
|
|
233
295
|
plugins.services,
|
|
234
296
|
hooks:
|
|
@@ -243,6 +305,38 @@ export function createApp<TConfig = unknown>(
|
|
|
243
305
|
},
|
|
244
306
|
};
|
|
245
307
|
|
|
308
|
+
deployment.addResource({
|
|
309
|
+
name:
|
|
310
|
+
"bcp:container",
|
|
311
|
+
ready() {
|
|
312
|
+
return {
|
|
313
|
+
ok:
|
|
314
|
+
container.state ===
|
|
315
|
+
"active",
|
|
316
|
+
detail:
|
|
317
|
+
`Service container is ${container.state}.`,
|
|
318
|
+
};
|
|
319
|
+
},
|
|
320
|
+
async stop() {
|
|
321
|
+
await container.dispose();
|
|
322
|
+
},
|
|
323
|
+
diagnostics() {
|
|
324
|
+
return {
|
|
325
|
+
state:
|
|
326
|
+
container.state,
|
|
327
|
+
providers:
|
|
328
|
+
container.graph().map(
|
|
329
|
+
node => ({
|
|
330
|
+
token:
|
|
331
|
+
node.description,
|
|
332
|
+
lifetime:
|
|
333
|
+
node.lifetime,
|
|
334
|
+
})
|
|
335
|
+
),
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
246
340
|
deployment.addResource({
|
|
247
341
|
name:
|
|
248
342
|
"bcp:plugins",
|
|
@@ -272,6 +366,19 @@ export function createApp<TConfig = unknown>(
|
|
|
272
366
|
},
|
|
273
367
|
});
|
|
274
368
|
|
|
369
|
+
for (
|
|
370
|
+
const resource
|
|
371
|
+
of modules.createLifecycleResources({
|
|
372
|
+
container,
|
|
373
|
+
services:
|
|
374
|
+
plugins.services,
|
|
375
|
+
hooks:
|
|
376
|
+
plugins.hooks,
|
|
377
|
+
})
|
|
378
|
+
) {
|
|
379
|
+
deployment.addResource(resource);
|
|
380
|
+
}
|
|
381
|
+
|
|
275
382
|
for (
|
|
276
383
|
const resource
|
|
277
384
|
of definition.resources ?? []
|
|
@@ -284,11 +391,13 @@ export function createApp<TConfig = unknown>(
|
|
|
284
391
|
name,
|
|
285
392
|
...(version
|
|
286
393
|
? {
|
|
287
|
-
|
|
288
|
-
|
|
394
|
+
version,
|
|
395
|
+
}
|
|
289
396
|
: {}),
|
|
290
397
|
config,
|
|
291
398
|
context,
|
|
399
|
+
container,
|
|
400
|
+
modules,
|
|
292
401
|
services:
|
|
293
402
|
plugins.services,
|
|
294
403
|
hooks:
|
|
@@ -322,6 +431,26 @@ export function createApp<TConfig = unknown>(
|
|
|
322
431
|
return app;
|
|
323
432
|
},
|
|
324
433
|
|
|
434
|
+
register<T>(
|
|
435
|
+
provider: ServiceProvider<T>,
|
|
436
|
+
options: {
|
|
437
|
+
replace?: boolean;
|
|
438
|
+
} = {}
|
|
439
|
+
) {
|
|
440
|
+
assertMutable();
|
|
441
|
+
container.register(
|
|
442
|
+
provider,
|
|
443
|
+
options
|
|
444
|
+
);
|
|
445
|
+
return app;
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
createScope(options = {}) {
|
|
449
|
+
return container.createScope(
|
|
450
|
+
options
|
|
451
|
+
);
|
|
452
|
+
},
|
|
453
|
+
|
|
325
454
|
addResource(resource) {
|
|
326
455
|
assertMutable();
|
|
327
456
|
deployment.addResource(
|
|
@@ -562,6 +691,14 @@ export function createApp<TConfig = unknown>(
|
|
|
562
691
|
}
|
|
563
692
|
}
|
|
564
693
|
|
|
694
|
+
if (container.state !== "disposed") {
|
|
695
|
+
try {
|
|
696
|
+
await container.dispose();
|
|
697
|
+
} catch (error) {
|
|
698
|
+
errors.push(error);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
565
702
|
try {
|
|
566
703
|
await disposeApplication();
|
|
567
704
|
} catch (error) {
|
|
@@ -612,8 +749,8 @@ export function createApp<TConfig = unknown>(
|
|
|
612
749
|
name,
|
|
613
750
|
...(version
|
|
614
751
|
? {
|
|
615
|
-
|
|
616
|
-
|
|
752
|
+
version,
|
|
753
|
+
}
|
|
617
754
|
: {}),
|
|
618
755
|
state,
|
|
619
756
|
services:
|
|
@@ -622,6 +759,13 @@ export function createApp<TConfig = unknown>(
|
|
|
622
759
|
.map(
|
|
623
760
|
formatServiceKey
|
|
624
761
|
),
|
|
762
|
+
containerProviders:
|
|
763
|
+
container.graph().map(
|
|
764
|
+
node =>
|
|
765
|
+
node.description
|
|
766
|
+
),
|
|
767
|
+
modules:
|
|
768
|
+
modules.records(),
|
|
625
769
|
pluginCount:
|
|
626
770
|
plugins.plugins().length,
|
|
627
771
|
};
|
|
@@ -654,6 +798,14 @@ export function createApp<TConfig = unknown>(
|
|
|
654
798
|
// Preserve the original startup failure.
|
|
655
799
|
}
|
|
656
800
|
|
|
801
|
+
if (container.state !== "disposed") {
|
|
802
|
+
try {
|
|
803
|
+
await container.dispose();
|
|
804
|
+
} catch {
|
|
805
|
+
// Preserve the original startup failure.
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
657
809
|
try {
|
|
658
810
|
await disposeApplication();
|
|
659
811
|
} catch {
|
|
@@ -697,7 +849,7 @@ export function createApp<TConfig = unknown>(
|
|
|
697
849
|
function assertMutable(): void {
|
|
698
850
|
if (state !== "created") {
|
|
699
851
|
throw new Error(
|
|
700
|
-
"BCP Application: plugins, services and resources must be registered before start()."
|
|
852
|
+
"BCP Application: plugins, services, providers and resources must be registered before start()."
|
|
701
853
|
);
|
|
702
854
|
}
|
|
703
855
|
}
|
|
@@ -747,6 +899,14 @@ function validateDefinition<TConfig>(
|
|
|
747
899
|
definition.version
|
|
748
900
|
);
|
|
749
901
|
|
|
902
|
+
if (
|
|
903
|
+
definition.providers !== undefined &&
|
|
904
|
+
!Array.isArray(definition.providers)
|
|
905
|
+
) {
|
|
906
|
+
throw new TypeError(
|
|
907
|
+
"BCP Application: providers must be an array."
|
|
908
|
+
);
|
|
909
|
+
}
|
|
750
910
|
if (
|
|
751
911
|
definition.plugins !== undefined &&
|
|
752
912
|
!Array.isArray(definition.plugins)
|