@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
|
@@ -1,3 +1,506 @@
|
|
|
1
|
+
// packages/server/src/container.ts
|
|
2
|
+
var ServiceNotFoundError = class extends Error {
|
|
3
|
+
token;
|
|
4
|
+
constructor(token) {
|
|
5
|
+
super(
|
|
6
|
+
`BCP Container: service "${token.description}" is not registered.`
|
|
7
|
+
);
|
|
8
|
+
this.name = "ServiceNotFoundError";
|
|
9
|
+
this.token = token;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var ServiceResolutionError = class extends Error {
|
|
13
|
+
token;
|
|
14
|
+
cause;
|
|
15
|
+
constructor(token, message, cause) {
|
|
16
|
+
super(
|
|
17
|
+
`BCP Container: could not resolve "${token.description}". ${message}`
|
|
18
|
+
);
|
|
19
|
+
this.name = "ServiceResolutionError";
|
|
20
|
+
this.token = token;
|
|
21
|
+
this.cause = cause;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var ServiceDisposalError = class extends AggregateError {
|
|
25
|
+
constructor(errors) {
|
|
26
|
+
super(
|
|
27
|
+
errors,
|
|
28
|
+
"BCP Container: one or more services failed to dispose."
|
|
29
|
+
);
|
|
30
|
+
this.name = "ServiceDisposalError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
function createServiceContainer(options = {}) {
|
|
34
|
+
const root = new ScopeImpl(
|
|
35
|
+
normalizeOptionalName(
|
|
36
|
+
options.name
|
|
37
|
+
) ?? "root",
|
|
38
|
+
void 0
|
|
39
|
+
);
|
|
40
|
+
root.root = root;
|
|
41
|
+
const container = {
|
|
42
|
+
get name() {
|
|
43
|
+
return root.name;
|
|
44
|
+
},
|
|
45
|
+
get state() {
|
|
46
|
+
return root.state;
|
|
47
|
+
},
|
|
48
|
+
get parent() {
|
|
49
|
+
return void 0;
|
|
50
|
+
},
|
|
51
|
+
has(token) {
|
|
52
|
+
return root.has(token);
|
|
53
|
+
},
|
|
54
|
+
resolve(token) {
|
|
55
|
+
return root.resolve(token);
|
|
56
|
+
},
|
|
57
|
+
optional(token) {
|
|
58
|
+
return root.optional(token);
|
|
59
|
+
},
|
|
60
|
+
createScope(scopeOptions = {}) {
|
|
61
|
+
return root.createScope(
|
|
62
|
+
scopeOptions
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
graph() {
|
|
66
|
+
return root.graph();
|
|
67
|
+
},
|
|
68
|
+
dispose() {
|
|
69
|
+
return root.dispose();
|
|
70
|
+
},
|
|
71
|
+
register(provider, registerOptions = {}) {
|
|
72
|
+
root.registerProvider(
|
|
73
|
+
provider,
|
|
74
|
+
registerOptions.replace === true
|
|
75
|
+
);
|
|
76
|
+
return container;
|
|
77
|
+
},
|
|
78
|
+
registerMany(providers) {
|
|
79
|
+
for (const provider of providers) {
|
|
80
|
+
root.registerProvider(
|
|
81
|
+
provider,
|
|
82
|
+
false
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return container;
|
|
86
|
+
},
|
|
87
|
+
providers() {
|
|
88
|
+
return Array.from(
|
|
89
|
+
root.providersMap.values()
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
for (const provider of options.providers ?? []) {
|
|
94
|
+
root.registerProvider(
|
|
95
|
+
provider,
|
|
96
|
+
false
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return container;
|
|
100
|
+
}
|
|
101
|
+
var ScopeImpl = class _ScopeImpl {
|
|
102
|
+
constructor(name, parent) {
|
|
103
|
+
this.name = name;
|
|
104
|
+
this.parent = parent;
|
|
105
|
+
if (parent) {
|
|
106
|
+
this.root = parent.root;
|
|
107
|
+
parent.children.add(this);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
name;
|
|
111
|
+
parent;
|
|
112
|
+
root;
|
|
113
|
+
providersMap = /* @__PURE__ */ new Map();
|
|
114
|
+
overrideProviders = /* @__PURE__ */ new Map();
|
|
115
|
+
singletonCache = /* @__PURE__ */ new Map();
|
|
116
|
+
scopedCache = /* @__PURE__ */ new Map();
|
|
117
|
+
disposals = [];
|
|
118
|
+
children = /* @__PURE__ */ new Set();
|
|
119
|
+
state = "active";
|
|
120
|
+
disposePromise;
|
|
121
|
+
has(token) {
|
|
122
|
+
assertToken(token);
|
|
123
|
+
return this.findProvider(token) !== void 0;
|
|
124
|
+
}
|
|
125
|
+
resolve(token) {
|
|
126
|
+
return this.resolveWithPath(
|
|
127
|
+
token,
|
|
128
|
+
[]
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
async optional(token) {
|
|
132
|
+
if (!this.has(token)) {
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
return this.resolve(token);
|
|
136
|
+
}
|
|
137
|
+
createScope(options = {}) {
|
|
138
|
+
this.assertActive();
|
|
139
|
+
const scope = new _ScopeImpl(
|
|
140
|
+
normalizeOptionalName(
|
|
141
|
+
options.name
|
|
142
|
+
) ?? `${this.name}:scope-${this.children.size + 1}`,
|
|
143
|
+
this
|
|
144
|
+
);
|
|
145
|
+
for (const provider of options.overrides ?? []) {
|
|
146
|
+
scope.registerOverride(
|
|
147
|
+
provider
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return scope;
|
|
151
|
+
}
|
|
152
|
+
graph() {
|
|
153
|
+
const effective = /* @__PURE__ */ new Map();
|
|
154
|
+
let current = this;
|
|
155
|
+
while (current) {
|
|
156
|
+
for (const [id, provider] of current.overrideProviders) {
|
|
157
|
+
if (!effective.has(id)) {
|
|
158
|
+
effective.set(
|
|
159
|
+
id,
|
|
160
|
+
{
|
|
161
|
+
provider,
|
|
162
|
+
owner: current,
|
|
163
|
+
overridden: true
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
current = current.parent;
|
|
169
|
+
}
|
|
170
|
+
for (const [id, provider] of this.root.providersMap) {
|
|
171
|
+
if (!effective.has(id)) {
|
|
172
|
+
effective.set(
|
|
173
|
+
id,
|
|
174
|
+
{
|
|
175
|
+
provider,
|
|
176
|
+
owner: this.root,
|
|
177
|
+
overridden: false
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return Array.from(
|
|
183
|
+
effective.values(),
|
|
184
|
+
(record) => ({
|
|
185
|
+
token: record.provider.token,
|
|
186
|
+
description: record.provider.token.description,
|
|
187
|
+
lifetime: normalizeLifetime(
|
|
188
|
+
record.provider.lifetime
|
|
189
|
+
),
|
|
190
|
+
dependencies: (record.provider.dependencies ?? []).map(
|
|
191
|
+
(dependency) => dependency.description
|
|
192
|
+
),
|
|
193
|
+
overridden: record.overridden
|
|
194
|
+
})
|
|
195
|
+
).sort(
|
|
196
|
+
(left, right) => left.description.localeCompare(
|
|
197
|
+
right.description
|
|
198
|
+
)
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
dispose() {
|
|
202
|
+
if (this.disposePromise) {
|
|
203
|
+
return this.disposePromise;
|
|
204
|
+
}
|
|
205
|
+
if (this.state === "disposed") {
|
|
206
|
+
return Promise.resolve();
|
|
207
|
+
}
|
|
208
|
+
this.disposePromise = this.disposeInternal();
|
|
209
|
+
return this.disposePromise;
|
|
210
|
+
}
|
|
211
|
+
registerProvider(provider, replace) {
|
|
212
|
+
this.assertActive();
|
|
213
|
+
if (this.parent) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
"BCP Container: providers can only be registered on the root container. Use scope overrides for child scopes."
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
validateProvider(provider);
|
|
219
|
+
const id = provider.token.id;
|
|
220
|
+
if (this.providersMap.has(id) && !replace) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`BCP Container: service "${provider.token.description}" is already registered.`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (replace && this.hasResolved(id)) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`BCP Container: service "${provider.token.description}" cannot be replaced after it has been resolved.`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
this.providersMap.set(
|
|
231
|
+
id,
|
|
232
|
+
provider
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
registerOverride(provider) {
|
|
236
|
+
validateProvider(provider);
|
|
237
|
+
const id = provider.token.id;
|
|
238
|
+
if (this.overrideProviders.has(id)) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`BCP Container: scope override for "${provider.token.description}" is already registered.`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
this.overrideProviders.set(
|
|
244
|
+
id,
|
|
245
|
+
provider
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
async resolveWithPath(token, path) {
|
|
249
|
+
this.assertActive();
|
|
250
|
+
assertToken(token);
|
|
251
|
+
if (path.some(
|
|
252
|
+
(item) => item.id === token.id
|
|
253
|
+
)) {
|
|
254
|
+
throw new ServiceResolutionError(
|
|
255
|
+
token,
|
|
256
|
+
`Circular dependency detected: ${[
|
|
257
|
+
...path,
|
|
258
|
+
token
|
|
259
|
+
].map(
|
|
260
|
+
(item) => item.description
|
|
261
|
+
).join(" -> ")}.`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
const record = this.findProvider(token);
|
|
265
|
+
if (!record) {
|
|
266
|
+
throw new ServiceNotFoundError(
|
|
267
|
+
token
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
const lifetime = normalizeLifetime(
|
|
271
|
+
record.provider.lifetime
|
|
272
|
+
);
|
|
273
|
+
const cache = lifetime === "singleton" ? record.owner.singletonCache : lifetime === "scoped" ? this.scopedCache : void 0;
|
|
274
|
+
const existing = cache?.get(token.id);
|
|
275
|
+
if (existing) {
|
|
276
|
+
return existing;
|
|
277
|
+
}
|
|
278
|
+
const resolution = this.instantiate(
|
|
279
|
+
record.provider,
|
|
280
|
+
[
|
|
281
|
+
...path,
|
|
282
|
+
token
|
|
283
|
+
],
|
|
284
|
+
lifetime,
|
|
285
|
+
record.owner
|
|
286
|
+
);
|
|
287
|
+
cache?.set(
|
|
288
|
+
token.id,
|
|
289
|
+
resolution
|
|
290
|
+
);
|
|
291
|
+
try {
|
|
292
|
+
return await resolution;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
cache?.delete(token.id);
|
|
295
|
+
if (error instanceof ServiceNotFoundError || error instanceof ServiceResolutionError) {
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
throw new ServiceResolutionError(
|
|
299
|
+
token,
|
|
300
|
+
error instanceof Error ? error.message : String(error),
|
|
301
|
+
error
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async instantiate(provider, path, lifetime, providerOwner) {
|
|
306
|
+
const dependencies = await Promise.all(
|
|
307
|
+
(provider.dependencies ?? []).map(
|
|
308
|
+
(dependency) => this.resolveWithPath(
|
|
309
|
+
dependency,
|
|
310
|
+
path
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
);
|
|
314
|
+
const context = {
|
|
315
|
+
scope: this,
|
|
316
|
+
resolve: (dependency) => this.resolveWithPath(
|
|
317
|
+
dependency,
|
|
318
|
+
path
|
|
319
|
+
),
|
|
320
|
+
optional: async (dependency) => {
|
|
321
|
+
if (!this.has(dependency)) {
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
return this.resolveWithPath(
|
|
325
|
+
dependency,
|
|
326
|
+
path
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
const value = await provider.factory(
|
|
331
|
+
context,
|
|
332
|
+
dependencies
|
|
333
|
+
);
|
|
334
|
+
if (provider.dispose) {
|
|
335
|
+
const disposalOwner = lifetime === "singleton" ? providerOwner : this;
|
|
336
|
+
disposalOwner.disposals.push({
|
|
337
|
+
provider,
|
|
338
|
+
value
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return value;
|
|
342
|
+
}
|
|
343
|
+
findProvider(token) {
|
|
344
|
+
let current = this;
|
|
345
|
+
while (current) {
|
|
346
|
+
const override = current.overrideProviders.get(
|
|
347
|
+
token.id
|
|
348
|
+
);
|
|
349
|
+
if (override) {
|
|
350
|
+
return {
|
|
351
|
+
provider: override,
|
|
352
|
+
owner: current,
|
|
353
|
+
overridden: true
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
current = current.parent;
|
|
357
|
+
}
|
|
358
|
+
const provider = this.root.providersMap.get(
|
|
359
|
+
token.id
|
|
360
|
+
);
|
|
361
|
+
if (!provider) {
|
|
362
|
+
return void 0;
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
provider,
|
|
366
|
+
owner: this.root,
|
|
367
|
+
overridden: false
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async disposeInternal() {
|
|
371
|
+
this.state = "disposing";
|
|
372
|
+
const errors = [];
|
|
373
|
+
for (const child of Array.from(
|
|
374
|
+
this.children
|
|
375
|
+
).reverse()) {
|
|
376
|
+
try {
|
|
377
|
+
await child.dispose();
|
|
378
|
+
} catch (error) {
|
|
379
|
+
errors.push(error);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
for (const record of [...this.disposals].reverse()) {
|
|
383
|
+
if (!record.provider.dispose) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
await record.provider.dispose(
|
|
388
|
+
record.value
|
|
389
|
+
);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
errors.push(error);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
this.disposals.length = 0;
|
|
395
|
+
this.singletonCache.clear();
|
|
396
|
+
this.scopedCache.clear();
|
|
397
|
+
this.children.clear();
|
|
398
|
+
this.parent?.children.delete(this);
|
|
399
|
+
this.state = "disposed";
|
|
400
|
+
if (errors.length > 0) {
|
|
401
|
+
throw new ServiceDisposalError(
|
|
402
|
+
errors
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
hasResolved(id) {
|
|
407
|
+
if (this.singletonCache.has(id) || this.scopedCache.has(id)) {
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
for (const child of this.children) {
|
|
411
|
+
if (child.hasResolved(id)) {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
assertActive() {
|
|
418
|
+
if (this.state !== "active") {
|
|
419
|
+
throw new Error(
|
|
420
|
+
`BCP Container: scope "${this.name}" is ${this.state}.`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
function validateProvider(provider) {
|
|
426
|
+
if (!provider || typeof provider !== "object") {
|
|
427
|
+
throw new TypeError(
|
|
428
|
+
"BCP Container: provider must be an object."
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
assertToken(provider.token);
|
|
432
|
+
if (typeof provider.factory !== "function") {
|
|
433
|
+
throw new TypeError(
|
|
434
|
+
`BCP Container: provider "${provider.token.description}" must define factory().`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
normalizeLifetime(
|
|
438
|
+
provider.lifetime
|
|
439
|
+
);
|
|
440
|
+
assertDependencies(
|
|
441
|
+
provider.dependencies ?? []
|
|
442
|
+
);
|
|
443
|
+
if (provider.dispose !== void 0 && typeof provider.dispose !== "function") {
|
|
444
|
+
throw new TypeError(
|
|
445
|
+
`BCP Container: provider "${provider.token.description}" dispose must be a function.`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function assertToken(token) {
|
|
450
|
+
if (!token || typeof token !== "object" || typeof token.id !== "symbol" || typeof token.description !== "string" || token.description.trim() === "") {
|
|
451
|
+
throw new TypeError(
|
|
452
|
+
"BCP Container: invalid service token. Use createServiceToken()."
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function assertDependencies(dependencies) {
|
|
457
|
+
if (!Array.isArray(dependencies)) {
|
|
458
|
+
throw new TypeError(
|
|
459
|
+
"BCP Container: provider dependencies must be an array."
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
for (const dependency of dependencies) {
|
|
463
|
+
assertToken(dependency);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function normalizeLifetime(lifetime) {
|
|
467
|
+
const normalized = lifetime ?? "singleton";
|
|
468
|
+
if (normalized !== "singleton" && normalized !== "scoped" && normalized !== "transient") {
|
|
469
|
+
throw new TypeError(
|
|
470
|
+
"BCP Container: service lifetime must be singleton, scoped or transient."
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
return normalized;
|
|
474
|
+
}
|
|
475
|
+
function normalizeName(value, label) {
|
|
476
|
+
if (typeof value !== "string") {
|
|
477
|
+
throw new TypeError(
|
|
478
|
+
`BCP Container: ${label} must be a string.`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
const normalized = value.trim();
|
|
482
|
+
if (!normalized) {
|
|
483
|
+
throw new TypeError(
|
|
484
|
+
`BCP Container: ${label} cannot be empty.`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
if (normalized.length > 256 || /[\r\n]/.test(normalized)) {
|
|
488
|
+
throw new TypeError(
|
|
489
|
+
`BCP Container: ${label} must be at most 256 characters without line breaks.`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
return normalized;
|
|
493
|
+
}
|
|
494
|
+
function normalizeOptionalName(value) {
|
|
495
|
+
if (value === void 0) {
|
|
496
|
+
return void 0;
|
|
497
|
+
}
|
|
498
|
+
return normalizeName(
|
|
499
|
+
value,
|
|
500
|
+
"scope name"
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
1
504
|
// packages/server/src/deployment.ts
|
|
2
505
|
import {
|
|
3
506
|
randomUUID
|
|
@@ -73,7 +576,7 @@ function createDeploymentRuntime(options) {
|
|
|
73
576
|
"runtime start timestamp"
|
|
74
577
|
);
|
|
75
578
|
const metadata = {
|
|
76
|
-
serviceName:
|
|
579
|
+
serviceName: normalizeName2(
|
|
77
580
|
options.serviceName,
|
|
78
581
|
"serviceName"
|
|
79
582
|
),
|
|
@@ -84,7 +587,7 @@ function createDeploymentRuntime(options) {
|
|
|
84
587
|
options.version
|
|
85
588
|
)
|
|
86
589
|
} : {},
|
|
87
|
-
deploymentId:
|
|
590
|
+
deploymentId: normalizeName2(
|
|
88
591
|
options.deploymentId ?? environment.BCP_DEPLOYMENT_ID ?? idFactory(),
|
|
89
592
|
"deploymentId"
|
|
90
593
|
),
|
|
@@ -132,7 +635,7 @@ function createDeploymentRuntime(options) {
|
|
|
132
635
|
"BCP Deployment: resources can only be registered before runtime start."
|
|
133
636
|
);
|
|
134
637
|
}
|
|
135
|
-
const name =
|
|
638
|
+
const name = normalizeName2(
|
|
136
639
|
resource.name,
|
|
137
640
|
"resource name"
|
|
138
641
|
);
|
|
@@ -496,7 +999,7 @@ function normalizeSignals(signals) {
|
|
|
496
999
|
new Set(signals)
|
|
497
1000
|
);
|
|
498
1001
|
}
|
|
499
|
-
function
|
|
1002
|
+
function normalizeName2(value, field) {
|
|
500
1003
|
const normalized = String(value ?? "").trim();
|
|
501
1004
|
if (!normalized) {
|
|
502
1005
|
throw new TypeError(
|
|
@@ -579,6 +1082,382 @@ function withTimeout(promise, timeoutMs, label) {
|
|
|
579
1082
|
);
|
|
580
1083
|
}
|
|
581
1084
|
|
|
1085
|
+
// packages/server/src/modules.ts
|
|
1086
|
+
var MODULE_V2_KIND = "bcp-module-v2";
|
|
1087
|
+
var ModuleDependencyError = class extends Error {
|
|
1088
|
+
constructor(message) {
|
|
1089
|
+
super(message);
|
|
1090
|
+
this.name = "ModuleDependencyError";
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
var ModuleLifecycleError = class extends Error {
|
|
1094
|
+
module;
|
|
1095
|
+
phase;
|
|
1096
|
+
cause;
|
|
1097
|
+
constructor(moduleName, phase, cause) {
|
|
1098
|
+
super(
|
|
1099
|
+
`BCP Modules: ${phase} failed for module "${moduleName}": ${formatError(cause)}`
|
|
1100
|
+
);
|
|
1101
|
+
this.name = "ModuleLifecycleError";
|
|
1102
|
+
this.module = moduleName;
|
|
1103
|
+
this.phase = phase;
|
|
1104
|
+
this.cause = cause;
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
function isModuleDefinition(value) {
|
|
1108
|
+
return Boolean(
|
|
1109
|
+
value && typeof value === "object" && value.kind === MODULE_V2_KIND
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
function composeModules(roots = []) {
|
|
1113
|
+
const ordered = resolveModuleOrder(roots);
|
|
1114
|
+
const providers = [];
|
|
1115
|
+
const plugins = [];
|
|
1116
|
+
const services = [];
|
|
1117
|
+
const resources = [];
|
|
1118
|
+
const serviceKeys = /* @__PURE__ */ new Set();
|
|
1119
|
+
for (const module of ordered) {
|
|
1120
|
+
providers.push(
|
|
1121
|
+
...module.providers ?? []
|
|
1122
|
+
);
|
|
1123
|
+
plugins.push(
|
|
1124
|
+
...module.plugins ?? []
|
|
1125
|
+
);
|
|
1126
|
+
resources.push(
|
|
1127
|
+
...module.resources ?? []
|
|
1128
|
+
);
|
|
1129
|
+
for (const entry of module.services ?? []) {
|
|
1130
|
+
const [key] = entry;
|
|
1131
|
+
if (serviceKeys.has(key)) {
|
|
1132
|
+
throw new ModuleDependencyError(
|
|
1133
|
+
`BCP Modules: duplicate shared service ${formatServiceKey(key)} while composing module "${module.name}".`
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
serviceKeys.add(key);
|
|
1137
|
+
services.push(entry);
|
|
1138
|
+
}
|
|
1139
|
+
validateModuleExports(
|
|
1140
|
+
module
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
return {
|
|
1144
|
+
modules: ordered,
|
|
1145
|
+
providers,
|
|
1146
|
+
plugins,
|
|
1147
|
+
services,
|
|
1148
|
+
resources,
|
|
1149
|
+
records() {
|
|
1150
|
+
return ordered.map(
|
|
1151
|
+
(module) => ({
|
|
1152
|
+
name: module.name,
|
|
1153
|
+
...module.version ? {
|
|
1154
|
+
version: module.version
|
|
1155
|
+
} : {},
|
|
1156
|
+
imports: (module.imports ?? []).map(
|
|
1157
|
+
(dependency) => dependency.name
|
|
1158
|
+
),
|
|
1159
|
+
providers: (module.providers ?? []).map(
|
|
1160
|
+
(provider) => provider.token.description
|
|
1161
|
+
),
|
|
1162
|
+
exports: (module.exports ?? []).map(
|
|
1163
|
+
(token) => token.description
|
|
1164
|
+
),
|
|
1165
|
+
plugins: (module.plugins ?? []).map(
|
|
1166
|
+
(plugin) => plugin.name
|
|
1167
|
+
),
|
|
1168
|
+
resources: (module.resources ?? []).map(
|
|
1169
|
+
(resource) => resource.name
|
|
1170
|
+
)
|
|
1171
|
+
})
|
|
1172
|
+
);
|
|
1173
|
+
},
|
|
1174
|
+
exportedTokens(moduleName) {
|
|
1175
|
+
const normalized = normalizeName3(
|
|
1176
|
+
moduleName,
|
|
1177
|
+
"module name"
|
|
1178
|
+
);
|
|
1179
|
+
const module = ordered.find(
|
|
1180
|
+
(entry) => entry.name === normalized
|
|
1181
|
+
);
|
|
1182
|
+
if (!module) {
|
|
1183
|
+
throw new ModuleDependencyError(
|
|
1184
|
+
`BCP Modules: module "${normalized}" is not part of this composition.`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
return [
|
|
1188
|
+
...module.exports ?? []
|
|
1189
|
+
];
|
|
1190
|
+
},
|
|
1191
|
+
createLifecycleResources(context) {
|
|
1192
|
+
return ordered.flatMap(
|
|
1193
|
+
(module) => [
|
|
1194
|
+
...module.resources ?? [],
|
|
1195
|
+
createModuleLifecycleResource(
|
|
1196
|
+
module,
|
|
1197
|
+
context
|
|
1198
|
+
)
|
|
1199
|
+
]
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
function resolveModuleOrder(roots) {
|
|
1205
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1206
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1207
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1208
|
+
const order = [];
|
|
1209
|
+
const visit = (module, path) => {
|
|
1210
|
+
validateModuleDefinition(module);
|
|
1211
|
+
const name = module.name;
|
|
1212
|
+
const existing = byName.get(name);
|
|
1213
|
+
if (existing && existing !== module) {
|
|
1214
|
+
throw new ModuleDependencyError(
|
|
1215
|
+
`BCP Modules: duplicate module name "${name}" refers to different definitions.`
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
byName.set(name, module);
|
|
1219
|
+
if (visited.has(name)) {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
if (visiting.has(name)) {
|
|
1223
|
+
throw new ModuleDependencyError(
|
|
1224
|
+
`BCP Modules: circular module dependency detected: ${[
|
|
1225
|
+
...path,
|
|
1226
|
+
name
|
|
1227
|
+
].join(" -> ")}.`
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
visiting.add(name);
|
|
1231
|
+
for (const dependency of module.imports ?? []) {
|
|
1232
|
+
visit(
|
|
1233
|
+
dependency,
|
|
1234
|
+
[
|
|
1235
|
+
...path,
|
|
1236
|
+
name
|
|
1237
|
+
]
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
visiting.delete(name);
|
|
1241
|
+
visited.add(name);
|
|
1242
|
+
order.push(module);
|
|
1243
|
+
};
|
|
1244
|
+
for (const root of roots) {
|
|
1245
|
+
visit(root, []);
|
|
1246
|
+
}
|
|
1247
|
+
return order;
|
|
1248
|
+
}
|
|
1249
|
+
function createModuleLifecycleResource(module, shared) {
|
|
1250
|
+
const config = parseModuleConfig(
|
|
1251
|
+
module.schema,
|
|
1252
|
+
module.config
|
|
1253
|
+
);
|
|
1254
|
+
const context = {
|
|
1255
|
+
name: module.name,
|
|
1256
|
+
config,
|
|
1257
|
+
container: shared.container,
|
|
1258
|
+
services: shared.services,
|
|
1259
|
+
hooks: shared.hooks
|
|
1260
|
+
};
|
|
1261
|
+
let setupComplete = false;
|
|
1262
|
+
let disposed = false;
|
|
1263
|
+
return {
|
|
1264
|
+
name: `bcp:module:${module.name}`,
|
|
1265
|
+
async start() {
|
|
1266
|
+
try {
|
|
1267
|
+
if (!setupComplete) {
|
|
1268
|
+
await runModuleHook(
|
|
1269
|
+
module,
|
|
1270
|
+
"setup",
|
|
1271
|
+
module.setup,
|
|
1272
|
+
context
|
|
1273
|
+
);
|
|
1274
|
+
setupComplete = true;
|
|
1275
|
+
}
|
|
1276
|
+
await runModuleHook(
|
|
1277
|
+
module,
|
|
1278
|
+
"start",
|
|
1279
|
+
module.start,
|
|
1280
|
+
context
|
|
1281
|
+
);
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
if (!disposed) {
|
|
1284
|
+
disposed = true;
|
|
1285
|
+
try {
|
|
1286
|
+
await runModuleHook(
|
|
1287
|
+
module,
|
|
1288
|
+
"dispose",
|
|
1289
|
+
module.dispose,
|
|
1290
|
+
context
|
|
1291
|
+
);
|
|
1292
|
+
} catch (disposeError) {
|
|
1293
|
+
throw new AggregateError(
|
|
1294
|
+
[
|
|
1295
|
+
error,
|
|
1296
|
+
disposeError
|
|
1297
|
+
],
|
|
1298
|
+
`BCP Modules: startup and cleanup failed for module "${module.name}".`
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
throw error;
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
ready() {
|
|
1306
|
+
return true;
|
|
1307
|
+
},
|
|
1308
|
+
async stop() {
|
|
1309
|
+
const errors = [];
|
|
1310
|
+
try {
|
|
1311
|
+
await runModuleHook(
|
|
1312
|
+
module,
|
|
1313
|
+
"stop",
|
|
1314
|
+
module.stop,
|
|
1315
|
+
context
|
|
1316
|
+
);
|
|
1317
|
+
} catch (error) {
|
|
1318
|
+
errors.push(error);
|
|
1319
|
+
}
|
|
1320
|
+
if (!disposed) {
|
|
1321
|
+
disposed = true;
|
|
1322
|
+
try {
|
|
1323
|
+
await runModuleHook(
|
|
1324
|
+
module,
|
|
1325
|
+
"dispose",
|
|
1326
|
+
module.dispose,
|
|
1327
|
+
context
|
|
1328
|
+
);
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
errors.push(error);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
if (errors.length > 0) {
|
|
1334
|
+
throw new AggregateError(
|
|
1335
|
+
errors,
|
|
1336
|
+
`BCP Modules: shutdown failed for module "${module.name}".`
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
diagnostics() {
|
|
1341
|
+
return {
|
|
1342
|
+
name: module.name,
|
|
1343
|
+
...module.version ? {
|
|
1344
|
+
version: module.version
|
|
1345
|
+
} : {},
|
|
1346
|
+
imports: (module.imports ?? []).map(
|
|
1347
|
+
(dependency) => dependency.name
|
|
1348
|
+
),
|
|
1349
|
+
exports: (module.exports ?? []).map(
|
|
1350
|
+
(token) => token.description
|
|
1351
|
+
)
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
async function runModuleHook(module, phase, hook, context) {
|
|
1357
|
+
if (!hook) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
try {
|
|
1361
|
+
await hook(context);
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
throw new ModuleLifecycleError(
|
|
1364
|
+
module.name,
|
|
1365
|
+
phase,
|
|
1366
|
+
error
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
function validateModuleDefinition(module) {
|
|
1371
|
+
if (!module || typeof module !== "object") {
|
|
1372
|
+
throw new TypeError(
|
|
1373
|
+
"BCP Modules: module must be an object."
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
if (module.kind !== MODULE_V2_KIND) {
|
|
1377
|
+
throw new TypeError(
|
|
1378
|
+
`BCP Modules: module kind must be "${MODULE_V2_KIND}". Use defineModule().`
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
normalizeName3(
|
|
1382
|
+
module.name,
|
|
1383
|
+
"module name"
|
|
1384
|
+
);
|
|
1385
|
+
for (const [label, value] of [
|
|
1386
|
+
["imports", module.imports],
|
|
1387
|
+
["providers", module.providers],
|
|
1388
|
+
["exports", module.exports],
|
|
1389
|
+
["plugins", module.plugins],
|
|
1390
|
+
["resources", module.resources]
|
|
1391
|
+
]) {
|
|
1392
|
+
if (value !== void 0 && !Array.isArray(value)) {
|
|
1393
|
+
throw new TypeError(
|
|
1394
|
+
`BCP Modules: ${label} must be an array.`
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
for (const [phase, hook] of [
|
|
1399
|
+
["setup", module.setup],
|
|
1400
|
+
["start", module.start],
|
|
1401
|
+
["stop", module.stop],
|
|
1402
|
+
["dispose", module.dispose]
|
|
1403
|
+
]) {
|
|
1404
|
+
if (hook !== void 0 && typeof hook !== "function") {
|
|
1405
|
+
throw new TypeError(
|
|
1406
|
+
`BCP Modules: ${phase} must be a function.`
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
function validateModuleExports(module) {
|
|
1412
|
+
const available = /* @__PURE__ */ new Set();
|
|
1413
|
+
for (const provider of module.providers ?? []) {
|
|
1414
|
+
available.add(
|
|
1415
|
+
provider.token.id
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
for (const dependency of module.imports ?? []) {
|
|
1419
|
+
for (const token of dependency.exports ?? []) {
|
|
1420
|
+
available.add(token.id);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
for (const token of module.exports ?? []) {
|
|
1424
|
+
if (!available.has(token.id)) {
|
|
1425
|
+
throw new ModuleDependencyError(
|
|
1426
|
+
`BCP Modules: module "${module.name}" exports "${token.description}" but does not provide or import it.`
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
function parseModuleConfig(parser, value) {
|
|
1432
|
+
if (!parser) {
|
|
1433
|
+
return value;
|
|
1434
|
+
}
|
|
1435
|
+
if (typeof parser === "function") {
|
|
1436
|
+
return parser(value);
|
|
1437
|
+
}
|
|
1438
|
+
return parser.parse(value);
|
|
1439
|
+
}
|
|
1440
|
+
function normalizeName3(value, label) {
|
|
1441
|
+
if (typeof value !== "string") {
|
|
1442
|
+
throw new TypeError(
|
|
1443
|
+
`BCP Modules: ${label} must be a string.`
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
const normalized = value.trim();
|
|
1447
|
+
if (!normalized) {
|
|
1448
|
+
throw new TypeError(
|
|
1449
|
+
`BCP Modules: ${label} cannot be empty.`
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
return normalized;
|
|
1453
|
+
}
|
|
1454
|
+
function formatServiceKey(key) {
|
|
1455
|
+
return typeof key === "symbol" ? key.description ? `Symbol(${key.description})` : key.toString() : key;
|
|
1456
|
+
}
|
|
1457
|
+
function formatError(value) {
|
|
1458
|
+
return value instanceof Error ? value.message : String(value);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
582
1461
|
// packages/server/src/plugins.ts
|
|
583
1462
|
var PluginDependencyError = class extends Error {
|
|
584
1463
|
constructor(message) {
|
|
@@ -592,7 +1471,7 @@ var PluginLifecycleError = class extends Error {
|
|
|
592
1471
|
cause;
|
|
593
1472
|
constructor(plugin, phase, cause) {
|
|
594
1473
|
super(
|
|
595
|
-
`BCP Plugins: ${phase} failed for plugin "${plugin}": ${
|
|
1474
|
+
`BCP Plugins: ${phase} failed for plugin "${plugin}": ${formatError2(cause)}`
|
|
596
1475
|
);
|
|
597
1476
|
this.name = "PluginLifecycleError";
|
|
598
1477
|
this.plugin = plugin;
|
|
@@ -606,7 +1485,7 @@ function defineModule(module) {
|
|
|
606
1485
|
"BCP Plugins: module must be an object."
|
|
607
1486
|
);
|
|
608
1487
|
}
|
|
609
|
-
|
|
1488
|
+
normalizeName4(
|
|
610
1489
|
module.name,
|
|
611
1490
|
"module name"
|
|
612
1491
|
);
|
|
@@ -627,7 +1506,7 @@ function createPluginServiceRegistry(initial) {
|
|
|
627
1506
|
assertServiceKey(key);
|
|
628
1507
|
if (values.has(key)) {
|
|
629
1508
|
throw new Error(
|
|
630
|
-
`BCP Plugins: duplicate initial service ${
|
|
1509
|
+
`BCP Plugins: duplicate initial service ${formatServiceKey2(key)}.`
|
|
631
1510
|
);
|
|
632
1511
|
}
|
|
633
1512
|
values.set(key, value);
|
|
@@ -638,7 +1517,7 @@ function createPluginServiceRegistry(initial) {
|
|
|
638
1517
|
assertServiceKey(key);
|
|
639
1518
|
if (values.has(key) && !options.replace) {
|
|
640
1519
|
throw new Error(
|
|
641
|
-
`BCP Plugins: service ${
|
|
1520
|
+
`BCP Plugins: service ${formatServiceKey2(key)} is already registered.`
|
|
642
1521
|
);
|
|
643
1522
|
}
|
|
644
1523
|
values.set(key, value);
|
|
@@ -647,7 +1526,7 @@ function createPluginServiceRegistry(initial) {
|
|
|
647
1526
|
assertServiceKey(key);
|
|
648
1527
|
if (!values.has(key)) {
|
|
649
1528
|
throw new Error(
|
|
650
|
-
`BCP Plugins: service ${
|
|
1529
|
+
`BCP Plugins: service ${formatServiceKey2(key)} is not registered.`
|
|
651
1530
|
);
|
|
652
1531
|
}
|
|
653
1532
|
return values.get(key);
|
|
@@ -676,7 +1555,7 @@ function createPluginHookBus() {
|
|
|
676
1555
|
const hooks = /* @__PURE__ */ new Map();
|
|
677
1556
|
const bus = {
|
|
678
1557
|
on(name, handler) {
|
|
679
|
-
const normalized =
|
|
1558
|
+
const normalized = normalizeName4(
|
|
680
1559
|
name,
|
|
681
1560
|
"hook name"
|
|
682
1561
|
);
|
|
@@ -706,7 +1585,7 @@ function createPluginHookBus() {
|
|
|
706
1585
|
};
|
|
707
1586
|
},
|
|
708
1587
|
async emit(name, payload) {
|
|
709
|
-
const normalized =
|
|
1588
|
+
const normalized = normalizeName4(
|
|
710
1589
|
name,
|
|
711
1590
|
"hook name"
|
|
712
1591
|
);
|
|
@@ -722,7 +1601,7 @@ function createPluginHookBus() {
|
|
|
722
1601
|
},
|
|
723
1602
|
listenerCount(name) {
|
|
724
1603
|
return hooks.get(
|
|
725
|
-
|
|
1604
|
+
normalizeName4(
|
|
726
1605
|
name,
|
|
727
1606
|
"hook name"
|
|
728
1607
|
)
|
|
@@ -734,7 +1613,7 @@ function createPluginHookBus() {
|
|
|
734
1613
|
return;
|
|
735
1614
|
}
|
|
736
1615
|
hooks.delete(
|
|
737
|
-
|
|
1616
|
+
normalizeName4(
|
|
738
1617
|
name,
|
|
739
1618
|
"hook name"
|
|
740
1619
|
)
|
|
@@ -967,7 +1846,7 @@ function createPluginHost(options = {}) {
|
|
|
967
1846
|
},
|
|
968
1847
|
plugin(name) {
|
|
969
1848
|
const entry = entries.get(
|
|
970
|
-
|
|
1849
|
+
normalizeName4(
|
|
971
1850
|
name,
|
|
972
1851
|
"plugin name"
|
|
973
1852
|
)
|
|
@@ -1001,7 +1880,7 @@ function createPluginHost(options = {}) {
|
|
|
1001
1880
|
validatePluginDefinition(
|
|
1002
1881
|
definition
|
|
1003
1882
|
);
|
|
1004
|
-
const name =
|
|
1883
|
+
const name = normalizeName4(
|
|
1005
1884
|
definition.name,
|
|
1006
1885
|
"plugin name"
|
|
1007
1886
|
);
|
|
@@ -1218,7 +2097,7 @@ function validatePluginDefinition(definition) {
|
|
|
1218
2097
|
"BCP Plugins: plugin definition must be an object."
|
|
1219
2098
|
);
|
|
1220
2099
|
}
|
|
1221
|
-
const name =
|
|
2100
|
+
const name = normalizeName4(
|
|
1222
2101
|
definition.name,
|
|
1223
2102
|
"plugin name"
|
|
1224
2103
|
);
|
|
@@ -1261,7 +2140,7 @@ function normalizeDependencyList(value, plugin, field) {
|
|
|
1261
2140
|
);
|
|
1262
2141
|
}
|
|
1263
2142
|
const normalized = value.map(
|
|
1264
|
-
(item) =>
|
|
2143
|
+
(item) => normalizeName4(
|
|
1265
2144
|
item,
|
|
1266
2145
|
`${field} dependency`
|
|
1267
2146
|
)
|
|
@@ -1302,7 +2181,7 @@ function requireContext(entry) {
|
|
|
1302
2181
|
}
|
|
1303
2182
|
function markFailed(entry, error) {
|
|
1304
2183
|
entry.record.state = "failed";
|
|
1305
|
-
entry.record.error =
|
|
2184
|
+
entry.record.error = formatError2(error);
|
|
1306
2185
|
}
|
|
1307
2186
|
function cloneRecord(record) {
|
|
1308
2187
|
return {
|
|
@@ -1322,7 +2201,7 @@ function isPluginModule(value) {
|
|
|
1322
2201
|
)
|
|
1323
2202
|
);
|
|
1324
2203
|
}
|
|
1325
|
-
function
|
|
2204
|
+
function normalizeName4(value, field) {
|
|
1326
2205
|
const text = String(value ?? "").trim();
|
|
1327
2206
|
if (!text) {
|
|
1328
2207
|
throw new TypeError(
|
|
@@ -1340,14 +2219,14 @@ function normalizeOptionalVersion(value) {
|
|
|
1340
2219
|
if (value === void 0) {
|
|
1341
2220
|
return void 0;
|
|
1342
2221
|
}
|
|
1343
|
-
return
|
|
2222
|
+
return normalizeName4(
|
|
1344
2223
|
value,
|
|
1345
2224
|
"plugin version"
|
|
1346
2225
|
);
|
|
1347
2226
|
}
|
|
1348
2227
|
function assertServiceKey(key) {
|
|
1349
2228
|
if (typeof key === "string") {
|
|
1350
|
-
|
|
2229
|
+
normalizeName4(
|
|
1351
2230
|
key,
|
|
1352
2231
|
"service key"
|
|
1353
2232
|
);
|
|
@@ -1359,10 +2238,10 @@ function assertServiceKey(key) {
|
|
|
1359
2238
|
);
|
|
1360
2239
|
}
|
|
1361
2240
|
}
|
|
1362
|
-
function
|
|
2241
|
+
function formatServiceKey2(key) {
|
|
1363
2242
|
return typeof key === "symbol" ? String(key) : `"${key}"`;
|
|
1364
2243
|
}
|
|
1365
|
-
function
|
|
2244
|
+
function formatError2(error) {
|
|
1366
2245
|
if (error instanceof Error) {
|
|
1367
2246
|
return error.message || error.name;
|
|
1368
2247
|
}
|
|
@@ -1386,7 +2265,7 @@ var ApplicationLifecycleError = class extends Error {
|
|
|
1386
2265
|
cause;
|
|
1387
2266
|
constructor(phase, cause) {
|
|
1388
2267
|
super(
|
|
1389
|
-
`BCP Application: ${phase} failed: ${
|
|
2268
|
+
`BCP Application: ${phase} failed: ${formatError3(cause)}`
|
|
1390
2269
|
);
|
|
1391
2270
|
this.name = "ApplicationLifecycleError";
|
|
1392
2271
|
this.phase = phase;
|
|
@@ -1399,7 +2278,7 @@ function defineApp(definition) {
|
|
|
1399
2278
|
}
|
|
1400
2279
|
function createApp(definition) {
|
|
1401
2280
|
defineApp(definition);
|
|
1402
|
-
const name =
|
|
2281
|
+
const name = normalizeName5(
|
|
1403
2282
|
definition.name,
|
|
1404
2283
|
"application name"
|
|
1405
2284
|
);
|
|
@@ -1410,11 +2289,33 @@ function createApp(definition) {
|
|
|
1410
2289
|
definition.schema,
|
|
1411
2290
|
definition.config
|
|
1412
2291
|
);
|
|
2292
|
+
const moduleInputs = definition.modules ?? [];
|
|
2293
|
+
const modules = composeModules(
|
|
2294
|
+
moduleInputs.filter(
|
|
2295
|
+
isModuleDefinition
|
|
2296
|
+
)
|
|
2297
|
+
);
|
|
2298
|
+
const legacyModules = moduleInputs.filter(
|
|
2299
|
+
(module) => !isModuleDefinition(module)
|
|
2300
|
+
);
|
|
2301
|
+
const container = createServiceContainer({
|
|
2302
|
+
name: `${name}:container`,
|
|
2303
|
+
providers: [
|
|
2304
|
+
...modules.providers,
|
|
2305
|
+
...definition.providers ?? []
|
|
2306
|
+
]
|
|
2307
|
+
});
|
|
1413
2308
|
const plugins = createPluginHost({
|
|
1414
|
-
plugins:
|
|
1415
|
-
|
|
2309
|
+
plugins: [
|
|
2310
|
+
...modules.plugins,
|
|
2311
|
+
...definition.plugins ?? []
|
|
2312
|
+
],
|
|
2313
|
+
modules: legacyModules,
|
|
1416
2314
|
configs: definition.pluginConfigs,
|
|
1417
|
-
services:
|
|
2315
|
+
services: [
|
|
2316
|
+
...modules.services,
|
|
2317
|
+
...definition.services ?? []
|
|
2318
|
+
]
|
|
1418
2319
|
});
|
|
1419
2320
|
const deployment = createDeploymentRuntime({
|
|
1420
2321
|
...definition.deployment ?? {},
|
|
@@ -1436,6 +2337,8 @@ function createApp(definition) {
|
|
|
1436
2337
|
version
|
|
1437
2338
|
} : {},
|
|
1438
2339
|
config,
|
|
2340
|
+
container,
|
|
2341
|
+
modules,
|
|
1439
2342
|
services: plugins.services,
|
|
1440
2343
|
hooks: plugins.hooks,
|
|
1441
2344
|
plugins,
|
|
@@ -1447,6 +2350,29 @@ function createApp(definition) {
|
|
|
1447
2350
|
return state;
|
|
1448
2351
|
}
|
|
1449
2352
|
};
|
|
2353
|
+
deployment.addResource({
|
|
2354
|
+
name: "bcp:container",
|
|
2355
|
+
ready() {
|
|
2356
|
+
return {
|
|
2357
|
+
ok: container.state === "active",
|
|
2358
|
+
detail: `Service container is ${container.state}.`
|
|
2359
|
+
};
|
|
2360
|
+
},
|
|
2361
|
+
async stop() {
|
|
2362
|
+
await container.dispose();
|
|
2363
|
+
},
|
|
2364
|
+
diagnostics() {
|
|
2365
|
+
return {
|
|
2366
|
+
state: container.state,
|
|
2367
|
+
providers: container.graph().map(
|
|
2368
|
+
(node) => ({
|
|
2369
|
+
token: node.description,
|
|
2370
|
+
lifetime: node.lifetime
|
|
2371
|
+
})
|
|
2372
|
+
)
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
1450
2376
|
deployment.addResource({
|
|
1451
2377
|
name: "bcp:plugins",
|
|
1452
2378
|
async start() {
|
|
@@ -1468,6 +2394,13 @@ function createApp(definition) {
|
|
|
1468
2394
|
};
|
|
1469
2395
|
}
|
|
1470
2396
|
});
|
|
2397
|
+
for (const resource of modules.createLifecycleResources({
|
|
2398
|
+
container,
|
|
2399
|
+
services: plugins.services,
|
|
2400
|
+
hooks: plugins.hooks
|
|
2401
|
+
})) {
|
|
2402
|
+
deployment.addResource(resource);
|
|
2403
|
+
}
|
|
1471
2404
|
for (const resource of definition.resources ?? []) {
|
|
1472
2405
|
deployment.addResource(resource);
|
|
1473
2406
|
}
|
|
@@ -1478,6 +2411,8 @@ function createApp(definition) {
|
|
|
1478
2411
|
} : {},
|
|
1479
2412
|
config,
|
|
1480
2413
|
context,
|
|
2414
|
+
container,
|
|
2415
|
+
modules,
|
|
1481
2416
|
services: plugins.services,
|
|
1482
2417
|
hooks: plugins.hooks,
|
|
1483
2418
|
plugins,
|
|
@@ -1499,6 +2434,19 @@ function createApp(definition) {
|
|
|
1499
2434
|
);
|
|
1500
2435
|
return app;
|
|
1501
2436
|
},
|
|
2437
|
+
register(provider, options = {}) {
|
|
2438
|
+
assertMutable();
|
|
2439
|
+
container.register(
|
|
2440
|
+
provider,
|
|
2441
|
+
options
|
|
2442
|
+
);
|
|
2443
|
+
return app;
|
|
2444
|
+
},
|
|
2445
|
+
createScope(options = {}) {
|
|
2446
|
+
return container.createScope(
|
|
2447
|
+
options
|
|
2448
|
+
);
|
|
2449
|
+
},
|
|
1502
2450
|
addResource(resource) {
|
|
1503
2451
|
assertMutable();
|
|
1504
2452
|
deployment.addResource(
|
|
@@ -1675,6 +2623,13 @@ function createApp(definition) {
|
|
|
1675
2623
|
errors.push(error);
|
|
1676
2624
|
}
|
|
1677
2625
|
}
|
|
2626
|
+
if (container.state !== "disposed") {
|
|
2627
|
+
try {
|
|
2628
|
+
await container.dispose();
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
errors.push(error);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
1678
2633
|
try {
|
|
1679
2634
|
await disposeApplication();
|
|
1680
2635
|
} catch (error) {
|
|
@@ -1720,8 +2675,12 @@ function createApp(definition) {
|
|
|
1720
2675
|
} : {},
|
|
1721
2676
|
state,
|
|
1722
2677
|
services: plugins.services.keys().map(
|
|
1723
|
-
|
|
2678
|
+
formatServiceKey3
|
|
1724
2679
|
),
|
|
2680
|
+
containerProviders: container.graph().map(
|
|
2681
|
+
(node) => node.description
|
|
2682
|
+
),
|
|
2683
|
+
modules: modules.records(),
|
|
1725
2684
|
pluginCount: plugins.plugins().length
|
|
1726
2685
|
};
|
|
1727
2686
|
}
|
|
@@ -1741,6 +2700,12 @@ function createApp(definition) {
|
|
|
1741
2700
|
await plugins.close();
|
|
1742
2701
|
} catch {
|
|
1743
2702
|
}
|
|
2703
|
+
if (container.state !== "disposed") {
|
|
2704
|
+
try {
|
|
2705
|
+
await container.dispose();
|
|
2706
|
+
} catch {
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
1744
2709
|
try {
|
|
1745
2710
|
await disposeApplication();
|
|
1746
2711
|
} catch {
|
|
@@ -1772,7 +2737,7 @@ function createApp(definition) {
|
|
|
1772
2737
|
function assertMutable() {
|
|
1773
2738
|
if (state !== "created") {
|
|
1774
2739
|
throw new Error(
|
|
1775
|
-
"BCP Application: plugins, services and resources must be registered before start()."
|
|
2740
|
+
"BCP Application: plugins, services, providers and resources must be registered before start()."
|
|
1776
2741
|
);
|
|
1777
2742
|
}
|
|
1778
2743
|
}
|
|
@@ -1797,13 +2762,18 @@ function validateDefinition(definition) {
|
|
|
1797
2762
|
"BCP Application: definition must be an object."
|
|
1798
2763
|
);
|
|
1799
2764
|
}
|
|
1800
|
-
|
|
2765
|
+
normalizeName5(
|
|
1801
2766
|
definition.name,
|
|
1802
2767
|
"application name"
|
|
1803
2768
|
);
|
|
1804
2769
|
normalizeOptionalText2(
|
|
1805
2770
|
definition.version
|
|
1806
2771
|
);
|
|
2772
|
+
if (definition.providers !== void 0 && !Array.isArray(definition.providers)) {
|
|
2773
|
+
throw new TypeError(
|
|
2774
|
+
"BCP Application: providers must be an array."
|
|
2775
|
+
);
|
|
2776
|
+
}
|
|
1807
2777
|
if (definition.plugins !== void 0 && !Array.isArray(definition.plugins)) {
|
|
1808
2778
|
throw new TypeError(
|
|
1809
2779
|
"BCP Application: plugins must be an array."
|
|
@@ -1832,7 +2802,7 @@ function validateDefinition(definition) {
|
|
|
1832
2802
|
}
|
|
1833
2803
|
}
|
|
1834
2804
|
}
|
|
1835
|
-
function
|
|
2805
|
+
function normalizeName5(value, label) {
|
|
1836
2806
|
if (typeof value !== "string") {
|
|
1837
2807
|
throw new TypeError(
|
|
1838
2808
|
`BCP Application: ${label} must be a string.`
|
|
@@ -1857,10 +2827,10 @@ function normalizeOptionalText2(value) {
|
|
|
1857
2827
|
}
|
|
1858
2828
|
return value.trim() || void 0;
|
|
1859
2829
|
}
|
|
1860
|
-
function
|
|
2830
|
+
function formatServiceKey3(key) {
|
|
1861
2831
|
return typeof key === "symbol" ? key.description ? `Symbol(${key.description})` : key.toString() : key;
|
|
1862
2832
|
}
|
|
1863
|
-
function
|
|
2833
|
+
function formatError3(value) {
|
|
1864
2834
|
if (value instanceof Error) {
|
|
1865
2835
|
return value.message;
|
|
1866
2836
|
}
|