@chidchanun/bcp 0.2.0 → 0.2.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 +222 -239
- package/docs/README.md +153 -240
- package/docs/api-manifest.json +130 -0
- package/docs/api-reference.md +224 -0
- package/docs/configuration.md +96 -1
- package/docs/docs-web-manifest.json +23 -3
- package/docs/documentation-platform.md +136 -0
- package/docs/environment-validation.md +224 -0
- package/docs/platform-manifest.json +15 -4
- package/docs/releases/0.2.1.md +132 -0
- package/docs/releases/0.2.2.md +118 -0
- package/package.json +1 -1
- package/packages/cli/src/args.ts +50 -9
- package/packages/cli/src/bootstrap.ts +22 -7
- package/packages/cli/src/configuration.ts +235 -0
- package/packages/cli/src/index.ts +101 -1
- package/packages/client/src/config.ts +26 -0
- package/packages/config/src/diagnostics.ts +226 -0
- package/packages/config/src/environment-loader.ts +101 -0
- package/packages/config/src/environment-schema.ts +677 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
export type BcpEnvironmentValueType =
|
|
2
|
+
| "string"
|
|
3
|
+
| "number"
|
|
4
|
+
| "boolean"
|
|
5
|
+
| "url";
|
|
6
|
+
|
|
7
|
+
export interface BcpEnvironmentVariableRule {
|
|
8
|
+
type: BcpEnvironmentValueType;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
secret?: boolean;
|
|
11
|
+
minLength?: number;
|
|
12
|
+
maxLength?: number;
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
default?: string | number | boolean;
|
|
16
|
+
description?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type BcpEnvironmentSchema =
|
|
20
|
+
Record<
|
|
21
|
+
string,
|
|
22
|
+
BcpEnvironmentVariableRule
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
export interface BcpEnvironmentValidationIssue {
|
|
26
|
+
key: string;
|
|
27
|
+
severity: "error" | "warning";
|
|
28
|
+
code:
|
|
29
|
+
| "missing"
|
|
30
|
+
| "invalid_type"
|
|
31
|
+
| "too_short"
|
|
32
|
+
| "too_long"
|
|
33
|
+
| "too_small"
|
|
34
|
+
| "too_large"
|
|
35
|
+
| "public_secret"
|
|
36
|
+
| "invalid_default";
|
|
37
|
+
message: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface BcpEnvironmentValidationResult {
|
|
41
|
+
ok: boolean;
|
|
42
|
+
checked: number;
|
|
43
|
+
present: number;
|
|
44
|
+
defaults: number;
|
|
45
|
+
issues: BcpEnvironmentValidationIssue[];
|
|
46
|
+
values: Record<
|
|
47
|
+
string,
|
|
48
|
+
string | number | boolean
|
|
49
|
+
>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function defineEnvironment<
|
|
53
|
+
const TSchema extends BcpEnvironmentSchema
|
|
54
|
+
>(
|
|
55
|
+
schema: TSchema
|
|
56
|
+
): TSchema {
|
|
57
|
+
assertEnvironmentSchema(
|
|
58
|
+
schema
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return schema;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function applyEnvironmentDefaults(
|
|
65
|
+
schema: BcpEnvironmentSchema,
|
|
66
|
+
target:
|
|
67
|
+
NodeJS.ProcessEnv |
|
|
68
|
+
Record<string, string | undefined> =
|
|
69
|
+
process.env
|
|
70
|
+
): number {
|
|
71
|
+
assertEnvironmentSchema(
|
|
72
|
+
schema
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
let applied = 0;
|
|
76
|
+
|
|
77
|
+
for (
|
|
78
|
+
const [
|
|
79
|
+
key,
|
|
80
|
+
rule,
|
|
81
|
+
]
|
|
82
|
+
of Object.entries(
|
|
83
|
+
schema
|
|
84
|
+
)
|
|
85
|
+
) {
|
|
86
|
+
if (
|
|
87
|
+
rule.default === undefined ||
|
|
88
|
+
(
|
|
89
|
+
target[key] !== undefined &&
|
|
90
|
+
target[key] !== ""
|
|
91
|
+
)
|
|
92
|
+
) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
target[key] =
|
|
97
|
+
serializeEnvironmentValue(
|
|
98
|
+
rule.default
|
|
99
|
+
);
|
|
100
|
+
applied++;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return applied;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function validateEnvironment(
|
|
107
|
+
schema: BcpEnvironmentSchema,
|
|
108
|
+
source:
|
|
109
|
+
NodeJS.ProcessEnv |
|
|
110
|
+
Record<string, string | undefined> =
|
|
111
|
+
process.env
|
|
112
|
+
): BcpEnvironmentValidationResult {
|
|
113
|
+
assertEnvironmentSchema(
|
|
114
|
+
schema
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const issues:
|
|
118
|
+
BcpEnvironmentValidationIssue[] = [];
|
|
119
|
+
const values:
|
|
120
|
+
Record<
|
|
121
|
+
string,
|
|
122
|
+
string | number | boolean
|
|
123
|
+
> = {};
|
|
124
|
+
|
|
125
|
+
let present = 0;
|
|
126
|
+
let defaults = 0;
|
|
127
|
+
|
|
128
|
+
for (
|
|
129
|
+
const [
|
|
130
|
+
key,
|
|
131
|
+
rule,
|
|
132
|
+
]
|
|
133
|
+
of Object.entries(
|
|
134
|
+
schema
|
|
135
|
+
)
|
|
136
|
+
) {
|
|
137
|
+
if (
|
|
138
|
+
rule.secret &&
|
|
139
|
+
key.startsWith(
|
|
140
|
+
"BCP_PUBLIC_"
|
|
141
|
+
)
|
|
142
|
+
) {
|
|
143
|
+
issues.push({
|
|
144
|
+
key,
|
|
145
|
+
severity: "error",
|
|
146
|
+
code: "public_secret",
|
|
147
|
+
message:
|
|
148
|
+
`${key} is marked secret but uses the BCP_PUBLIC_ prefix. Public variables are embedded in client bundles.`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const raw =
|
|
153
|
+
source[key];
|
|
154
|
+
const hasSourceValue =
|
|
155
|
+
raw !== undefined &&
|
|
156
|
+
raw !== "";
|
|
157
|
+
|
|
158
|
+
if (hasSourceValue) {
|
|
159
|
+
present++;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const input:
|
|
163
|
+
string | number | boolean | undefined =
|
|
164
|
+
hasSourceValue
|
|
165
|
+
? raw
|
|
166
|
+
: rule.default;
|
|
167
|
+
|
|
168
|
+
if (
|
|
169
|
+
input === undefined
|
|
170
|
+
) {
|
|
171
|
+
if (
|
|
172
|
+
rule.required
|
|
173
|
+
) {
|
|
174
|
+
issues.push({
|
|
175
|
+
key,
|
|
176
|
+
severity: "error",
|
|
177
|
+
code: "missing",
|
|
178
|
+
message:
|
|
179
|
+
`${key} is required but was not provided.`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
!hasSourceValue
|
|
188
|
+
) {
|
|
189
|
+
defaults++;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const parsed =
|
|
193
|
+
parseEnvironmentRuleValue(
|
|
194
|
+
key,
|
|
195
|
+
input,
|
|
196
|
+
rule,
|
|
197
|
+
issues
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (
|
|
201
|
+
parsed !== undefined
|
|
202
|
+
) {
|
|
203
|
+
values[key] =
|
|
204
|
+
parsed;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
ok:
|
|
210
|
+
!issues.some(
|
|
211
|
+
(issue) =>
|
|
212
|
+
issue.severity ===
|
|
213
|
+
"error"
|
|
214
|
+
),
|
|
215
|
+
checked:
|
|
216
|
+
Object.keys(
|
|
217
|
+
schema
|
|
218
|
+
).length,
|
|
219
|
+
present,
|
|
220
|
+
defaults,
|
|
221
|
+
issues,
|
|
222
|
+
values,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function assertEnvironmentSchema(
|
|
227
|
+
schema: unknown
|
|
228
|
+
): asserts schema is BcpEnvironmentSchema {
|
|
229
|
+
if (
|
|
230
|
+
!isPlainObject(
|
|
231
|
+
schema
|
|
232
|
+
)
|
|
233
|
+
) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
"BCP Framework: environment config must be an object."
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (
|
|
240
|
+
const [
|
|
241
|
+
key,
|
|
242
|
+
value,
|
|
243
|
+
]
|
|
244
|
+
of Object.entries(
|
|
245
|
+
schema
|
|
246
|
+
)
|
|
247
|
+
) {
|
|
248
|
+
if (
|
|
249
|
+
!/^[A-Za-z_][A-Za-z0-9_]*$/.test(
|
|
250
|
+
key
|
|
251
|
+
)
|
|
252
|
+
) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`BCP Framework: invalid environment schema key "${key}".`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
assertEnvironmentRule(
|
|
259
|
+
key,
|
|
260
|
+
value
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function assertEnvironmentRule(
|
|
266
|
+
key: string,
|
|
267
|
+
value: unknown
|
|
268
|
+
): asserts value is BcpEnvironmentVariableRule {
|
|
269
|
+
if (
|
|
270
|
+
!isPlainObject(
|
|
271
|
+
value
|
|
272
|
+
)
|
|
273
|
+
) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`BCP Framework: environment.${key} must be an object.`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const allowed =
|
|
280
|
+
new Set([
|
|
281
|
+
"type",
|
|
282
|
+
"required",
|
|
283
|
+
"secret",
|
|
284
|
+
"minLength",
|
|
285
|
+
"maxLength",
|
|
286
|
+
"min",
|
|
287
|
+
"max",
|
|
288
|
+
"default",
|
|
289
|
+
"description",
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
for (
|
|
293
|
+
const property
|
|
294
|
+
of Object.keys(
|
|
295
|
+
value
|
|
296
|
+
)
|
|
297
|
+
) {
|
|
298
|
+
if (
|
|
299
|
+
!allowed.has(
|
|
300
|
+
property
|
|
301
|
+
)
|
|
302
|
+
) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
`BCP Framework: unknown config option "environment.${key}.${property}".`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (
|
|
310
|
+
value.type !== "string" &&
|
|
311
|
+
value.type !== "number" &&
|
|
312
|
+
value.type !== "boolean" &&
|
|
313
|
+
value.type !== "url"
|
|
314
|
+
) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
`BCP Framework: environment.${key}.type must be string, number, boolean, or url.`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
assertOptionalBoolean(
|
|
321
|
+
value.required,
|
|
322
|
+
`environment.${key}.required`
|
|
323
|
+
);
|
|
324
|
+
assertOptionalBoolean(
|
|
325
|
+
value.secret,
|
|
326
|
+
`environment.${key}.secret`
|
|
327
|
+
);
|
|
328
|
+
assertOptionalNonNegativeInteger(
|
|
329
|
+
value.minLength,
|
|
330
|
+
`environment.${key}.minLength`
|
|
331
|
+
);
|
|
332
|
+
assertOptionalNonNegativeInteger(
|
|
333
|
+
value.maxLength,
|
|
334
|
+
`environment.${key}.maxLength`
|
|
335
|
+
);
|
|
336
|
+
assertOptionalFiniteNumber(
|
|
337
|
+
value.min,
|
|
338
|
+
`environment.${key}.min`
|
|
339
|
+
);
|
|
340
|
+
assertOptionalFiniteNumber(
|
|
341
|
+
value.max,
|
|
342
|
+
`environment.${key}.max`
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
if (
|
|
346
|
+
value.description !== undefined &&
|
|
347
|
+
(
|
|
348
|
+
typeof value.description !== "string" ||
|
|
349
|
+
value.description.trim() === ""
|
|
350
|
+
)
|
|
351
|
+
) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`BCP Framework: environment.${key}.description must be a non-empty string.`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (
|
|
358
|
+
value.minLength !== undefined &&
|
|
359
|
+
value.maxLength !== undefined &&
|
|
360
|
+
value.minLength > value.maxLength
|
|
361
|
+
) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`BCP Framework: environment.${key}.minLength cannot be greater than maxLength.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (
|
|
368
|
+
value.min !== undefined &&
|
|
369
|
+
value.max !== undefined &&
|
|
370
|
+
value.min > value.max
|
|
371
|
+
) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`BCP Framework: environment.${key}.min cannot be greater than max.`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (
|
|
378
|
+
value.default !== undefined
|
|
379
|
+
) {
|
|
380
|
+
const issues:
|
|
381
|
+
BcpEnvironmentValidationIssue[] = [];
|
|
382
|
+
|
|
383
|
+
const parsed =
|
|
384
|
+
parseEnvironmentRuleValue(
|
|
385
|
+
key,
|
|
386
|
+
value.default,
|
|
387
|
+
value as BcpEnvironmentVariableRule,
|
|
388
|
+
issues
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
if (
|
|
392
|
+
parsed === undefined ||
|
|
393
|
+
issues.some(
|
|
394
|
+
(issue) =>
|
|
395
|
+
issue.severity ===
|
|
396
|
+
"error"
|
|
397
|
+
)
|
|
398
|
+
) {
|
|
399
|
+
throw new Error(
|
|
400
|
+
`BCP Framework: environment.${key}.default does not satisfy its declared rule.`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function parseEnvironmentRuleValue(
|
|
407
|
+
key: string,
|
|
408
|
+
input: string | number | boolean,
|
|
409
|
+
rule: BcpEnvironmentVariableRule,
|
|
410
|
+
issues: BcpEnvironmentValidationIssue[]
|
|
411
|
+
): string | number | boolean | undefined {
|
|
412
|
+
if (
|
|
413
|
+
rule.type === "string" ||
|
|
414
|
+
rule.type === "url"
|
|
415
|
+
) {
|
|
416
|
+
if (
|
|
417
|
+
typeof input !==
|
|
418
|
+
"string"
|
|
419
|
+
) {
|
|
420
|
+
issues.push({
|
|
421
|
+
key,
|
|
422
|
+
severity: "error",
|
|
423
|
+
code: "invalid_type",
|
|
424
|
+
message:
|
|
425
|
+
`${key} must be a ${rule.type}.`,
|
|
426
|
+
});
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (
|
|
431
|
+
rule.minLength !== undefined &&
|
|
432
|
+
input.length <
|
|
433
|
+
rule.minLength
|
|
434
|
+
) {
|
|
435
|
+
issues.push({
|
|
436
|
+
key,
|
|
437
|
+
severity: "error",
|
|
438
|
+
code: "too_short",
|
|
439
|
+
message:
|
|
440
|
+
`${key} must contain at least ${rule.minLength} character(s).`,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (
|
|
445
|
+
rule.maxLength !== undefined &&
|
|
446
|
+
input.length >
|
|
447
|
+
rule.maxLength
|
|
448
|
+
) {
|
|
449
|
+
issues.push({
|
|
450
|
+
key,
|
|
451
|
+
severity: "error",
|
|
452
|
+
code: "too_long",
|
|
453
|
+
message:
|
|
454
|
+
`${key} must contain at most ${rule.maxLength} character(s).`,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (
|
|
459
|
+
rule.type === "url"
|
|
460
|
+
) {
|
|
461
|
+
try {
|
|
462
|
+
const url =
|
|
463
|
+
new URL(
|
|
464
|
+
input
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
if (
|
|
468
|
+
url.protocol !== "http:" &&
|
|
469
|
+
url.protocol !== "https:"
|
|
470
|
+
) {
|
|
471
|
+
throw new Error(
|
|
472
|
+
"unsupported protocol"
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
} catch {
|
|
476
|
+
issues.push({
|
|
477
|
+
key,
|
|
478
|
+
severity: "error",
|
|
479
|
+
code: "invalid_type",
|
|
480
|
+
message:
|
|
481
|
+
`${key} must be an absolute http(s) URL.`,
|
|
482
|
+
});
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return input;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (
|
|
491
|
+
rule.type === "number"
|
|
492
|
+
) {
|
|
493
|
+
const parsed =
|
|
494
|
+
typeof input === "number"
|
|
495
|
+
? input
|
|
496
|
+
: typeof input === "string" &&
|
|
497
|
+
input.trim() !== ""
|
|
498
|
+
? Number(
|
|
499
|
+
input
|
|
500
|
+
)
|
|
501
|
+
: Number.NaN;
|
|
502
|
+
|
|
503
|
+
if (
|
|
504
|
+
!Number.isFinite(
|
|
505
|
+
parsed
|
|
506
|
+
)
|
|
507
|
+
) {
|
|
508
|
+
issues.push({
|
|
509
|
+
key,
|
|
510
|
+
severity: "error",
|
|
511
|
+
code: "invalid_type",
|
|
512
|
+
message:
|
|
513
|
+
`${key} must be a finite number.`,
|
|
514
|
+
});
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (
|
|
519
|
+
rule.min !== undefined &&
|
|
520
|
+
parsed < rule.min
|
|
521
|
+
) {
|
|
522
|
+
issues.push({
|
|
523
|
+
key,
|
|
524
|
+
severity: "error",
|
|
525
|
+
code: "too_small",
|
|
526
|
+
message:
|
|
527
|
+
`${key} must be greater than or equal to ${rule.min}.`,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (
|
|
532
|
+
rule.max !== undefined &&
|
|
533
|
+
parsed > rule.max
|
|
534
|
+
) {
|
|
535
|
+
issues.push({
|
|
536
|
+
key,
|
|
537
|
+
severity: "error",
|
|
538
|
+
code: "too_large",
|
|
539
|
+
message:
|
|
540
|
+
`${key} must be less than or equal to ${rule.max}.`,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return parsed;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (
|
|
548
|
+
typeof input === "boolean"
|
|
549
|
+
) {
|
|
550
|
+
return input;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (
|
|
554
|
+
typeof input === "string"
|
|
555
|
+
) {
|
|
556
|
+
const normalized =
|
|
557
|
+
input
|
|
558
|
+
.trim()
|
|
559
|
+
.toLowerCase();
|
|
560
|
+
|
|
561
|
+
if (
|
|
562
|
+
[
|
|
563
|
+
"1",
|
|
564
|
+
"true",
|
|
565
|
+
"yes",
|
|
566
|
+
"on",
|
|
567
|
+
].includes(
|
|
568
|
+
normalized
|
|
569
|
+
)
|
|
570
|
+
) {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (
|
|
575
|
+
[
|
|
576
|
+
"0",
|
|
577
|
+
"false",
|
|
578
|
+
"no",
|
|
579
|
+
"off",
|
|
580
|
+
].includes(
|
|
581
|
+
normalized
|
|
582
|
+
)
|
|
583
|
+
) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
issues.push({
|
|
589
|
+
key,
|
|
590
|
+
severity: "error",
|
|
591
|
+
code: "invalid_type",
|
|
592
|
+
message:
|
|
593
|
+
`${key} must be a boolean value (true/false, 1/0, yes/no, on/off).`,
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
return undefined;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function serializeEnvironmentValue(
|
|
600
|
+
value: string | number | boolean
|
|
601
|
+
): string {
|
|
602
|
+
if (
|
|
603
|
+
typeof value === "boolean"
|
|
604
|
+
) {
|
|
605
|
+
return value
|
|
606
|
+
? "true"
|
|
607
|
+
: "false";
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return String(
|
|
611
|
+
value
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function assertOptionalBoolean(
|
|
616
|
+
value: unknown,
|
|
617
|
+
label: string
|
|
618
|
+
): void {
|
|
619
|
+
if (
|
|
620
|
+
value !== undefined &&
|
|
621
|
+
typeof value !== "boolean"
|
|
622
|
+
) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
`BCP Framework: ${label} must be boolean.`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function assertOptionalNonNegativeInteger(
|
|
630
|
+
value: unknown,
|
|
631
|
+
label: string
|
|
632
|
+
): void {
|
|
633
|
+
if (
|
|
634
|
+
value !== undefined &&
|
|
635
|
+
(
|
|
636
|
+
!Number.isInteger(
|
|
637
|
+
value
|
|
638
|
+
) ||
|
|
639
|
+
Number(value) < 0
|
|
640
|
+
)
|
|
641
|
+
) {
|
|
642
|
+
throw new Error(
|
|
643
|
+
`BCP Framework: ${label} must be a non-negative integer.`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function assertOptionalFiniteNumber(
|
|
649
|
+
value: unknown,
|
|
650
|
+
label: string
|
|
651
|
+
): void {
|
|
652
|
+
if (
|
|
653
|
+
value !== undefined &&
|
|
654
|
+
(
|
|
655
|
+
typeof value !== "number" ||
|
|
656
|
+
!Number.isFinite(
|
|
657
|
+
value
|
|
658
|
+
)
|
|
659
|
+
)
|
|
660
|
+
) {
|
|
661
|
+
throw new Error(
|
|
662
|
+
`BCP Framework: ${label} must be a finite number.`
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function isPlainObject(
|
|
668
|
+
value: unknown
|
|
669
|
+
): value is Record<string, any> {
|
|
670
|
+
return (
|
|
671
|
+
typeof value === "object" &&
|
|
672
|
+
value !== null &&
|
|
673
|
+
!Array.isArray(
|
|
674
|
+
value
|
|
675
|
+
)
|
|
676
|
+
);
|
|
677
|
+
}
|