@chidchanun/bcp 0.3.0 → 0.3.1

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.
@@ -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: normalizeName(
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: normalizeName(
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 = normalizeName(
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 normalizeName(value, field) {
1002
+ function normalizeName2(value, field) {
500
1003
  const normalized = String(value ?? "").trim();
501
1004
  if (!normalized) {
502
1005
  throw new TypeError(
@@ -606,7 +1109,7 @@ function defineModule(module) {
606
1109
  "BCP Plugins: module must be an object."
607
1110
  );
608
1111
  }
609
- normalizeName2(
1112
+ normalizeName3(
610
1113
  module.name,
611
1114
  "module name"
612
1115
  );
@@ -676,7 +1179,7 @@ function createPluginHookBus() {
676
1179
  const hooks = /* @__PURE__ */ new Map();
677
1180
  const bus = {
678
1181
  on(name, handler) {
679
- const normalized = normalizeName2(
1182
+ const normalized = normalizeName3(
680
1183
  name,
681
1184
  "hook name"
682
1185
  );
@@ -706,7 +1209,7 @@ function createPluginHookBus() {
706
1209
  };
707
1210
  },
708
1211
  async emit(name, payload) {
709
- const normalized = normalizeName2(
1212
+ const normalized = normalizeName3(
710
1213
  name,
711
1214
  "hook name"
712
1215
  );
@@ -722,7 +1225,7 @@ function createPluginHookBus() {
722
1225
  },
723
1226
  listenerCount(name) {
724
1227
  return hooks.get(
725
- normalizeName2(
1228
+ normalizeName3(
726
1229
  name,
727
1230
  "hook name"
728
1231
  )
@@ -734,7 +1237,7 @@ function createPluginHookBus() {
734
1237
  return;
735
1238
  }
736
1239
  hooks.delete(
737
- normalizeName2(
1240
+ normalizeName3(
738
1241
  name,
739
1242
  "hook name"
740
1243
  )
@@ -967,7 +1470,7 @@ function createPluginHost(options = {}) {
967
1470
  },
968
1471
  plugin(name) {
969
1472
  const entry = entries.get(
970
- normalizeName2(
1473
+ normalizeName3(
971
1474
  name,
972
1475
  "plugin name"
973
1476
  )
@@ -1001,7 +1504,7 @@ function createPluginHost(options = {}) {
1001
1504
  validatePluginDefinition(
1002
1505
  definition
1003
1506
  );
1004
- const name = normalizeName2(
1507
+ const name = normalizeName3(
1005
1508
  definition.name,
1006
1509
  "plugin name"
1007
1510
  );
@@ -1218,7 +1721,7 @@ function validatePluginDefinition(definition) {
1218
1721
  "BCP Plugins: plugin definition must be an object."
1219
1722
  );
1220
1723
  }
1221
- const name = normalizeName2(
1724
+ const name = normalizeName3(
1222
1725
  definition.name,
1223
1726
  "plugin name"
1224
1727
  );
@@ -1261,7 +1764,7 @@ function normalizeDependencyList(value, plugin, field) {
1261
1764
  );
1262
1765
  }
1263
1766
  const normalized = value.map(
1264
- (item) => normalizeName2(
1767
+ (item) => normalizeName3(
1265
1768
  item,
1266
1769
  `${field} dependency`
1267
1770
  )
@@ -1322,7 +1825,7 @@ function isPluginModule(value) {
1322
1825
  )
1323
1826
  );
1324
1827
  }
1325
- function normalizeName2(value, field) {
1828
+ function normalizeName3(value, field) {
1326
1829
  const text = String(value ?? "").trim();
1327
1830
  if (!text) {
1328
1831
  throw new TypeError(
@@ -1340,14 +1843,14 @@ function normalizeOptionalVersion(value) {
1340
1843
  if (value === void 0) {
1341
1844
  return void 0;
1342
1845
  }
1343
- return normalizeName2(
1846
+ return normalizeName3(
1344
1847
  value,
1345
1848
  "plugin version"
1346
1849
  );
1347
1850
  }
1348
1851
  function assertServiceKey(key) {
1349
1852
  if (typeof key === "string") {
1350
- normalizeName2(
1853
+ normalizeName3(
1351
1854
  key,
1352
1855
  "service key"
1353
1856
  );
@@ -1399,7 +1902,7 @@ function defineApp(definition) {
1399
1902
  }
1400
1903
  function createApp(definition) {
1401
1904
  defineApp(definition);
1402
- const name = normalizeName3(
1905
+ const name = normalizeName4(
1403
1906
  definition.name,
1404
1907
  "application name"
1405
1908
  );
@@ -1410,6 +1913,10 @@ function createApp(definition) {
1410
1913
  definition.schema,
1411
1914
  definition.config
1412
1915
  );
1916
+ const container = createServiceContainer({
1917
+ name: `${name}:container`,
1918
+ providers: definition.providers
1919
+ });
1413
1920
  const plugins = createPluginHost({
1414
1921
  plugins: definition.plugins,
1415
1922
  modules: definition.modules,
@@ -1436,6 +1943,7 @@ function createApp(definition) {
1436
1943
  version
1437
1944
  } : {},
1438
1945
  config,
1946
+ container,
1439
1947
  services: plugins.services,
1440
1948
  hooks: plugins.hooks,
1441
1949
  plugins,
@@ -1447,6 +1955,29 @@ function createApp(definition) {
1447
1955
  return state;
1448
1956
  }
1449
1957
  };
1958
+ deployment.addResource({
1959
+ name: "bcp:container",
1960
+ ready() {
1961
+ return {
1962
+ ok: container.state === "active",
1963
+ detail: `Service container is ${container.state}.`
1964
+ };
1965
+ },
1966
+ async stop() {
1967
+ await container.dispose();
1968
+ },
1969
+ diagnostics() {
1970
+ return {
1971
+ state: container.state,
1972
+ providers: container.graph().map(
1973
+ (node) => ({
1974
+ token: node.description,
1975
+ lifetime: node.lifetime
1976
+ })
1977
+ )
1978
+ };
1979
+ }
1980
+ });
1450
1981
  deployment.addResource({
1451
1982
  name: "bcp:plugins",
1452
1983
  async start() {
@@ -1478,6 +2009,7 @@ function createApp(definition) {
1478
2009
  } : {},
1479
2010
  config,
1480
2011
  context,
2012
+ container,
1481
2013
  services: plugins.services,
1482
2014
  hooks: plugins.hooks,
1483
2015
  plugins,
@@ -1499,6 +2031,19 @@ function createApp(definition) {
1499
2031
  );
1500
2032
  return app;
1501
2033
  },
2034
+ register(provider, options = {}) {
2035
+ assertMutable();
2036
+ container.register(
2037
+ provider,
2038
+ options
2039
+ );
2040
+ return app;
2041
+ },
2042
+ createScope(options = {}) {
2043
+ return container.createScope(
2044
+ options
2045
+ );
2046
+ },
1502
2047
  addResource(resource) {
1503
2048
  assertMutable();
1504
2049
  deployment.addResource(
@@ -1675,6 +2220,13 @@ function createApp(definition) {
1675
2220
  errors.push(error);
1676
2221
  }
1677
2222
  }
2223
+ if (container.state !== "disposed") {
2224
+ try {
2225
+ await container.dispose();
2226
+ } catch (error) {
2227
+ errors.push(error);
2228
+ }
2229
+ }
1678
2230
  try {
1679
2231
  await disposeApplication();
1680
2232
  } catch (error) {
@@ -1722,6 +2274,9 @@ function createApp(definition) {
1722
2274
  services: plugins.services.keys().map(
1723
2275
  formatServiceKey2
1724
2276
  ),
2277
+ containerProviders: container.graph().map(
2278
+ (node) => node.description
2279
+ ),
1725
2280
  pluginCount: plugins.plugins().length
1726
2281
  };
1727
2282
  }
@@ -1741,6 +2296,12 @@ function createApp(definition) {
1741
2296
  await plugins.close();
1742
2297
  } catch {
1743
2298
  }
2299
+ if (container.state !== "disposed") {
2300
+ try {
2301
+ await container.dispose();
2302
+ } catch {
2303
+ }
2304
+ }
1744
2305
  try {
1745
2306
  await disposeApplication();
1746
2307
  } catch {
@@ -1772,7 +2333,7 @@ function createApp(definition) {
1772
2333
  function assertMutable() {
1773
2334
  if (state !== "created") {
1774
2335
  throw new Error(
1775
- "BCP Application: plugins, services and resources must be registered before start()."
2336
+ "BCP Application: plugins, services, providers and resources must be registered before start()."
1776
2337
  );
1777
2338
  }
1778
2339
  }
@@ -1797,13 +2358,18 @@ function validateDefinition(definition) {
1797
2358
  "BCP Application: definition must be an object."
1798
2359
  );
1799
2360
  }
1800
- normalizeName3(
2361
+ normalizeName4(
1801
2362
  definition.name,
1802
2363
  "application name"
1803
2364
  );
1804
2365
  normalizeOptionalText2(
1805
2366
  definition.version
1806
2367
  );
2368
+ if (definition.providers !== void 0 && !Array.isArray(definition.providers)) {
2369
+ throw new TypeError(
2370
+ "BCP Application: providers must be an array."
2371
+ );
2372
+ }
1807
2373
  if (definition.plugins !== void 0 && !Array.isArray(definition.plugins)) {
1808
2374
  throw new TypeError(
1809
2375
  "BCP Application: plugins must be an array."
@@ -1832,7 +2398,7 @@ function validateDefinition(definition) {
1832
2398
  }
1833
2399
  }
1834
2400
  }
1835
- function normalizeName3(value, label) {
2401
+ function normalizeName4(value, label) {
1836
2402
  if (typeof value !== "string") {
1837
2403
  throw new TypeError(
1838
2404
  `BCP Application: ${label} must be a string.`