@blamejs/core 0.14.18 → 0.14.19

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/lib/openapi.js CHANGED
@@ -5,24 +5,32 @@
5
5
  * @title Openapi
6
6
  *
7
7
  * @intro
8
- * OpenAPI 3.1 emitter from declarative route declarations + schemas
9
- * (composable with `b.safeSchema`); JSON / YAML output. Operators
10
- * describe their public HTTP surface as an OpenAPI 3.1 document the
11
- * framework serves at `/openapi.json` (or any path) for downstream
12
- * tooling: API consumers, Postman, code-generators, contract-test
13
- * rigs.
8
+ * OpenAPI 3.1 / 3.2 emitter from declarative route declarations +
9
+ * schemas (composable with `b.safeSchema`); JSON / YAML output.
10
+ * Operators describe their public HTTP surface as an OpenAPI document
11
+ * the framework serves at `/openapi.json` (or any path) for
12
+ * downstream tooling: API consumers, Postman, code-generators,
13
+ * contract-test rigs.
14
14
  *
15
- * The builder is FRAMEWORK-FACING: it produces a valid OpenAPI 3.1
15
+ * The builder is FRAMEWORK-FACING: it produces a valid OpenAPI
16
16
  * document, but the operator's hand-written contract is the source
17
17
  * of truth — it does NOT auto-walk `b.router` routes (operators
18
18
  * frequently want a smaller / different surface published than what
19
19
  * the router exposes internally).
20
20
  *
21
- * The builder fluent surface is `path()` / `schema()` / `response()`
22
- * / `parameter()` / `requestBody()` / `header()` / `example()` /
23
- * `security.add()` / `security.require()` / `tag()` / `server()`,
24
- * each returning the builder for chaining. Terminal calls are
25
- * `toJson()` (3.1 JSON document with referential integrity checked
21
+ * `3.1.0` is the default emitted version. Pass
22
+ * `create({ openapi: "3.2.0", ... })` to opt into OpenAPI 3.2; both
23
+ * 3.1.x and 3.2.x parse and emit. The 3.2 additions wired here are
24
+ * the top-level `webhooks` map (named out-of-band Path Item Objects
25
+ * the API initiates — OpenAPI 3.2 §4.8.2) and the `jsonSchemaDialect`
26
+ * field (declares the default JSON Schema dialect for the document —
27
+ * OpenAPI 3.2 §4.8.1).
28
+ *
29
+ * The builder fluent surface is `path()` / `webhook()` / `schema()` /
30
+ * `response()` / `parameter()` / `requestBody()` / `header()` /
31
+ * `example()` / `security.add()` / `security.require()` / `tag()` /
32
+ * `server()`, each returning the builder for chaining. Terminal calls
33
+ * are `toJson()` (JSON document with referential integrity checked
26
34
  * — every security-scheme reference must resolve), `toJsonString()`,
27
35
  * `toYaml()`, and `middleware(opts)` which mounts the cached
28
36
  * document at request-time. Security-scheme builders for bearer /
@@ -30,7 +38,7 @@
30
38
  * `b.openapi.security`.
31
39
  *
32
40
  * @card
33
- * OpenAPI 3.1 emitter from declarative route declarations + schemas (composable with `b.safeSchema`); JSON / YAML output.
41
+ * OpenAPI 3.1 / 3.2 emitter from declarative route declarations + schemas (composable with `b.safeSchema`); JSON / YAML output.
34
42
  */
35
43
 
36
44
  var validateOpts = require("./validate-opts");
@@ -44,7 +52,33 @@ var audit = lazyRequire(function () { return require("./audit");
44
52
 
45
53
  var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
46
54
 
47
- var OPENAPI_VERSION = "3.1.0";
55
+ // Default emitted version. `create({ openapi: "3.2.0" })` opts into 3.2.
56
+ // Both 3.1.x and 3.2.x are accepted by create() and parse() (OpenAPI
57
+ // 3.2 is a backward-compatible superset of 3.1).
58
+ var OPENAPI_VERSION = "3.1.0";
59
+ var SUPPORTED_MAJOR_MINOR = ["3.1", "3.2"];
60
+
61
+ // _resolveVersion — validate an operator-supplied `opts.openapi` version
62
+ // string against the accepted major.minor set and return it; default to
63
+ // OPENAPI_VERSION when omitted. THROWS on an unsupported version
64
+ // (config-time / entry-point tier — operator catches the typo at boot).
65
+ function _resolveVersion(version, label) {
66
+ if (version === undefined || version === null) return OPENAPI_VERSION;
67
+ if (typeof version !== "string" || version.length === 0) {
68
+ throw new OpenApiError("openapi/bad-version",
69
+ label + ": openapi must be a version string (e.g. \"3.1.0\" or \"3.2.0\")");
70
+ }
71
+ for (var i = 0; i < SUPPORTED_MAJOR_MINOR.length; i += 1) {
72
+ if (version.indexOf(SUPPORTED_MAJOR_MINOR[i] + ".") === 0 ||
73
+ version === SUPPORTED_MAJOR_MINOR[i]) {
74
+ return version;
75
+ }
76
+ }
77
+ throw new OpenApiError("openapi/bad-version",
78
+ label + ": openapi version must be one of " +
79
+ SUPPORTED_MAJOR_MINOR.map(function (v) { return v + ".x"; }).join(" / ") +
80
+ " — got " + JSON.stringify(version));
81
+ }
48
82
 
49
83
  /**
50
84
  * @primitive b.openapi.create
@@ -52,23 +86,33 @@ var OPENAPI_VERSION = "3.1.0";
52
86
  * @since 0.6.30
53
87
  * @related b.openapi.parse, b.asyncapi.create, b.safeSchema
54
88
  *
55
- * Build a fluent OpenAPI 3.1 document builder. `opts.info` is required
56
- * (`title` + `version`). Returns a chainable builder; terminal calls
57
- * are `toJson()`, `toJsonString(indent)`, `toYaml()`, and
89
+ * Build a fluent OpenAPI 3.1 / 3.2 document builder. `opts.info` is
90
+ * required (`title` + `version`). Returns a chainable builder; terminal
91
+ * calls are `toJson()`, `toJsonString(indent)`, `toYaml()`, and
58
92
  * `middleware(opts)`. `toJson()` cross-checks every doc-level and
59
93
  * per-operation security requirement against
60
94
  * `components.securitySchemes` and throws
61
95
  * `OpenApiError("openapi/dangling-security")` on a missing scheme.
62
96
  *
97
+ * `3.1.0` is emitted by default. Pass `openapi: "3.2.0"` to opt into
98
+ * OpenAPI 3.2; an unsupported version (e.g. `"4.0.0"`) throws
99
+ * `OpenApiError("openapi/bad-version")`. `webhook(name, method, opts)`
100
+ * registers a top-level webhook (OpenAPI 3.2 §4.8.2) and
101
+ * `jsonSchemaDialect` declares the document's default JSON Schema
102
+ * dialect (OpenAPI 3.2 §4.8.1) — both valid in 3.1.x and 3.2.x.
103
+ *
63
104
  * @opts
64
- * info: { title, version, description?, contact?, license? }, // REQUIRED — title + version are non-empty strings
65
- * servers: array, // [{ url, description?, variables? }, ...]
66
- * externalDocs: { url, description? },
67
- * tags: array, // [{ name, description? }, ...] — seed; builder.tag() appends more
68
- * security: array, // doc-level security requirements [{ schemeName: ["scope"] }, ...]
105
+ * info: { title, version, description?, contact?, license? }, // REQUIRED — title + version are non-empty strings
106
+ * openapi: string, // emitted version "3.1.x" (default) or "3.2.x"
107
+ * jsonSchemaDialect: string, // default JSON Schema dialect URI for the document
108
+ * servers: array, // [{ url, description?, variables? }, ...]
109
+ * externalDocs: { url, description? },
110
+ * tags: array, // [{ name, description? }, ...] — seed; builder.tag() appends more
111
+ * security: array, // doc-level security requirements [{ schemeName: ["scope"] }, ...]
69
112
  *
70
113
  * @example
71
114
  * var doc = b.openapi.create({
115
+ * openapi: "3.2.0",
72
116
  * info: { title: "Acme API", version: "1.0.0" },
73
117
  * servers: [{ url: "https://api.acme.example.com" }],
74
118
  * });
@@ -79,14 +123,19 @@ var OPENAPI_VERSION = "3.1.0";
79
123
  * responses: { "200": { description: "ok" }, "404": { description: "not found" } },
80
124
  * security: [{ bearerAuth: [] }],
81
125
  * });
126
+ * doc.webhook("newPet", "post", {
127
+ * requestBody: { content: { "application/json": { schema: { type: "object" } } } },
128
+ * responses: { "200": { description: "ack" } },
129
+ * });
82
130
  * var json = doc.toJson();
83
- * json.openapi; // → "3.1.0"
84
- * json.paths["/users/{id}"].get.summary; // → "Fetch a user"
131
+ * json.openapi; // → "3.2.0"
132
+ * json.webhooks.newPet.post.responses["200"].description; // → "ack"
85
133
  */
86
134
  function create(opts) {
87
135
  opts = opts || {};
88
136
  validateOpts(opts, [
89
- "info", "servers", "externalDocs", "tags", "security",
137
+ "info", "openapi", "jsonSchemaDialect",
138
+ "servers", "externalDocs", "tags", "security",
90
139
  ], "openapi.create");
91
140
  if (!opts.info || typeof opts.info !== "object") {
92
141
  throw new OpenApiError("openapi/bad-info",
@@ -97,7 +146,13 @@ function create(opts) {
97
146
  validateOpts.requireNonEmptyString(opts.info.version,
98
147
  "openapi.create: info.version", OpenApiError, "openapi/bad-info");
99
148
 
149
+ var openapiVersion = _resolveVersion(opts.openapi, "openapi.create");
150
+ var jsonSchemaDialect = validateOpts.optionalNonEmptyString(
151
+ opts.jsonSchemaDialect, "openapi.create: jsonSchemaDialect",
152
+ OpenApiError, "openapi/bad-json-schema-dialect");
153
+
100
154
  var paths = new pathsBuilderMod.PathsBuilder();
155
+ var webhooks = new pathsBuilderMod.WebhooksBuilder();
101
156
  var components = {
102
157
  schemas: {},
103
158
  responses: {},
@@ -124,6 +179,32 @@ function create(opts) {
124
179
  _validateServerEntry(docServers[s], "openapi.create: servers[" + s + "]");
125
180
  }
126
181
 
182
+ // _checkOperationSecurity — every per-operation security requirement
183
+ // key must resolve to a registered security scheme. `labelPrefix`
184
+ // distinguishes path operations ("") from webhook operations
185
+ // ("webhook ") in the error message.
186
+ function _checkOperationSecurity(itemMap, labelPrefix) {
187
+ for (var itemKey in itemMap) {
188
+ if (!Object.prototype.hasOwnProperty.call(itemMap, itemKey)) continue;
189
+ var item = itemMap[itemKey];
190
+ for (var methodKey in item) {
191
+ if (!Object.prototype.hasOwnProperty.call(item, methodKey)) continue;
192
+ var op = item[methodKey];
193
+ if (!Array.isArray(op.security)) continue;
194
+ for (var os = 0; os < op.security.length; os += 1) {
195
+ for (var sn in op.security[os]) {
196
+ if (!Object.prototype.hasOwnProperty.call(op.security[os], sn)) continue;
197
+ if (!components.securitySchemes[sn]) {
198
+ throw new OpenApiError("openapi/dangling-security",
199
+ "toJson: " + labelPrefix + methodKey.toUpperCase() + " " + itemKey +
200
+ " references undefined security scheme " + JSON.stringify(sn));
201
+ }
202
+ }
203
+ }
204
+ }
205
+ }
206
+ }
207
+
127
208
  var builder = {
128
209
  info: Object.assign({}, opts.info),
129
210
 
@@ -132,6 +213,11 @@ function create(opts) {
132
213
  return builder;
133
214
  },
134
215
 
216
+ webhook: function (name, method, webhookOpts) {
217
+ webhooks.add(name, method, webhookOpts || {});
218
+ return builder;
219
+ },
220
+
135
221
  schema: function (name, schemaSpec) {
136
222
  validateOpts.requireNonEmptyString(name, "schema: name",
137
223
  OpenApiError, "openapi/bad-component");
@@ -235,11 +321,13 @@ function create(opts) {
235
321
 
236
322
  toJson: function () {
237
323
  var doc = {
238
- openapi: OPENAPI_VERSION,
324
+ openapi: openapiVersion,
239
325
  info: builder.info,
240
326
  };
327
+ if (jsonSchemaDialect) doc.jsonSchemaDialect = jsonSchemaDialect;
241
328
  if (docServers.length > 0) doc.servers = docServers.slice();
242
329
  doc.paths = paths.toMap();
330
+ if (webhooks.count() > 0) doc.webhooks = webhooks.toMap();
243
331
  var anyComponent = false;
244
332
  var componentsOut = {};
245
333
  var keys = ["schemas", "responses", "parameters", "requestBodies",
@@ -267,27 +355,10 @@ function create(opts) {
267
355
  }
268
356
  }
269
357
  }
270
- // Same check on per-operation security.
271
- for (var pathKey in doc.paths) {
272
- if (!Object.prototype.hasOwnProperty.call(doc.paths, pathKey)) continue;
273
- var pathItem = doc.paths[pathKey];
274
- for (var methodKey in pathItem) {
275
- if (!Object.prototype.hasOwnProperty.call(pathItem, methodKey)) continue;
276
- var op = pathItem[methodKey];
277
- if (Array.isArray(op.security)) {
278
- for (var os = 0; os < op.security.length; os += 1) {
279
- for (var sn in op.security[os]) {
280
- if (!Object.prototype.hasOwnProperty.call(op.security[os], sn)) continue;
281
- if (!components.securitySchemes[sn]) {
282
- throw new OpenApiError("openapi/dangling-security",
283
- "toJson: " + methodKey.toUpperCase() + " " + pathKey +
284
- " references undefined security scheme " + JSON.stringify(sn));
285
- }
286
- }
287
- }
288
- }
289
- }
290
- }
358
+ // Same check on per-operation security — for both `paths` and
359
+ // `webhooks` operations (webhook operations carry `security` too).
360
+ _checkOperationSecurity(doc.paths, "");
361
+ if (doc.webhooks) _checkOperationSecurity(doc.webhooks, "webhook ");
291
362
  try {
292
363
  audit().safeEmit({
293
364
  action: "openapi.document.built",
@@ -347,25 +418,100 @@ function create(opts) {
347
418
  return builder;
348
419
  }
349
420
 
421
+ var PARSE_METHODS = ["get", "put", "post", "delete",
422
+ "options", "head", "patch", "trace"];
423
+
424
+ // _validateItemOperations — validate the Operation Objects inside a
425
+ // single Path Item (used for both `paths` entries and `webhooks`
426
+ // entries). `label` is the operator-facing prefix (a path key like
427
+ // "/x" or a webhook label like "webhook newPet"). Pushes shape errors
428
+ // into `errors`; non-method fields (parameters / summary / $ref) are
429
+ // skipped exactly as the paths loop did before this was extracted.
430
+ function _validateItemOperations(item, label, errors, securitySchemes) {
431
+ for (var methodKey in item) {
432
+ if (!Object.prototype.hasOwnProperty.call(item, methodKey)) continue;
433
+ if (PARSE_METHODS.indexOf(methodKey) === -1) continue; // allow non-method fields like 'parameters', 'summary', '$ref'
434
+ var op = item[methodKey];
435
+ if (!op || typeof op !== "object") {
436
+ errors.push(methodKey.toUpperCase() + " " + label + ": operation must be an object");
437
+ continue;
438
+ }
439
+ if (!op.responses || typeof op.responses !== "object" ||
440
+ Object.keys(op.responses).length === 0) {
441
+ errors.push(methodKey.toUpperCase() + " " + label +
442
+ ": responses object required (per OpenAPI 3.1 §4.8.5)");
443
+ } else {
444
+ for (var statusKey in op.responses) {
445
+ if (!Object.prototype.hasOwnProperty.call(op.responses, statusKey)) continue;
446
+ var resp = op.responses[statusKey];
447
+ if (!resp || typeof resp !== "object") {
448
+ errors.push(methodKey.toUpperCase() + " " + label +
449
+ " response " + statusKey + ": must be an object");
450
+ continue;
451
+ }
452
+ if (resp["$ref"]) continue; // $ref short-circuit
453
+ if (typeof resp.description !== "string" || resp.description.length === 0) {
454
+ errors.push(methodKey.toUpperCase() + " " + label +
455
+ " response " + statusKey +
456
+ ": description is required (per OpenAPI 3.1 §4.8.16)");
457
+ }
458
+ }
459
+ }
460
+ if (Array.isArray(op.parameters)) {
461
+ for (var pi = 0; pi < op.parameters.length; pi += 1) {
462
+ var p = op.parameters[pi];
463
+ if (!p || typeof p !== "object") continue;
464
+ if (p["$ref"]) continue;
465
+ if (p.in === "path" && p.required !== true) {
466
+ errors.push(methodKey.toUpperCase() + " " + label +
467
+ " parameters[" + pi + "]: path parameter " +
468
+ JSON.stringify(p.name) + " must have required=true");
469
+ }
470
+ }
471
+ }
472
+ // Operation-level security requirement keys must resolve to a
473
+ // registered scheme — the builder's toJson() enforces this, so parse()
474
+ // must too, for both path and webhook operations.
475
+ if (securitySchemes && Array.isArray(op.security)) {
476
+ for (var rqi = 0; rqi < op.security.length; rqi += 1) {
477
+ var req = op.security[rqi];
478
+ if (!req || typeof req !== "object") continue;
479
+ for (var schemeKey in req) {
480
+ if (!Object.prototype.hasOwnProperty.call(req, schemeKey)) continue;
481
+ if (!securitySchemes[schemeKey]) {
482
+ errors.push(methodKey.toUpperCase() + " " + label +
483
+ ": security references undefined scheme " +
484
+ JSON.stringify(schemeKey));
485
+ }
486
+ }
487
+ }
488
+ }
489
+ }
490
+ }
491
+
350
492
  /**
351
493
  * @primitive b.openapi.parse
352
494
  * @signature b.openapi.parse(jsonStringOrObject)
353
495
  * @since 0.6.30
354
496
  * @related b.openapi.create
355
497
  *
356
- * Parse + validate an external OpenAPI 3.1 document. Operators hand a
357
- * doc that arrived from a downstream integration (consumer hand-
498
+ * Parse + validate an external OpenAPI 3.1 / 3.2 document. Operators
499
+ * hand a doc that arrived from a downstream integration (consumer hand-
358
500
  * edited, contract-test fixture, third-party publish) and want the
359
501
  * framework's gate to enforce the same shape rules `toJson()`
360
502
  * enforces on builder output. Throws on invalid JSON or non-object
361
503
  * input; otherwise returns `{ doc, errors, valid }`. `errors` is an
362
- * array of strings — empty on a valid document. Path keys must start
363
- * with `/`, every operation must declare `responses` with a
364
- * `description`, path parameters must carry `required: true`, and
365
- * doc-level security must reference declared schemes.
504
+ * array of strings — empty on a valid document. The `openapi` version
505
+ * must be `3.1.x` or `3.2.x`. Path keys must start with `/`, every
506
+ * operation must declare `responses` with a `description`, path
507
+ * parameters must carry `required: true`, and doc-level security must
508
+ * reference declared schemes. Top-level `webhooks` (OpenAPI 3.2
509
+ * §4.8.2) are validated with the same operation rules but free-form
510
+ * names instead of `/`-prefixed URL keys; `jsonSchemaDialect` (OpenAPI
511
+ * 3.2 §4.8.1) must be a string when present.
366
512
  *
367
513
  * @example
368
- * var result = b.openapi.parse('{"openapi":"3.1.0","info":{"title":"x","version":"1.0.0"}}');
514
+ * var result = b.openapi.parse('{"openapi":"3.2.0","info":{"title":"x","version":"1.0.0"}}');
369
515
  * result.valid; // → true
370
516
  * result.errors; // → []
371
517
  *
@@ -389,9 +535,12 @@ function parse(jsonStringOrObject) {
389
535
  }
390
536
  var errors = [];
391
537
  if (typeof doc.openapi !== "string") {
392
- errors.push("missing or non-string `openapi` version field (must be 3.1.x)");
393
- } else if (doc.openapi.indexOf("3.1") !== 0) {
394
- errors.push("`openapi` version must be 3.1.x — got " + JSON.stringify(doc.openapi));
538
+ errors.push("missing or non-string `openapi` version field (must be 3.1.x or 3.2.x)");
539
+ } else if (doc.openapi.indexOf("3.1") !== 0 && doc.openapi.indexOf("3.2") !== 0) {
540
+ errors.push("`openapi` version must be 3.1.x or 3.2.x — got " + JSON.stringify(doc.openapi));
541
+ }
542
+ if (doc.jsonSchemaDialect != null && typeof doc.jsonSchemaDialect !== "string") {
543
+ errors.push("`jsonSchemaDialect` must be a string when present (per OpenAPI 3.2 §4.8.1)");
395
544
  }
396
545
  if (!doc.info || typeof doc.info !== "object") {
397
546
  errors.push("missing or non-object `info`");
@@ -403,6 +552,9 @@ function parse(jsonStringOrObject) {
403
552
  errors.push("info.version must be a non-empty string");
404
553
  }
405
554
  }
555
+ // Resolved once so operation-level security requirements on both paths
556
+ // and webhooks can be checked against it during shape validation.
557
+ var securitySchemes = (doc.components && doc.components.securitySchemes) || {};
406
558
  if (doc.paths != null && typeof doc.paths !== "object") {
407
559
  errors.push("`paths` must be an object when present");
408
560
  } else if (doc.paths) {
@@ -416,54 +568,27 @@ function parse(jsonStringOrObject) {
416
568
  errors.push("paths[" + JSON.stringify(pathKey) + "] must be an object");
417
569
  continue;
418
570
  }
419
- var validMethods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
420
- for (var methodKey in pathItem) {
421
- if (!Object.prototype.hasOwnProperty.call(pathItem, methodKey)) continue;
422
- if (validMethods.indexOf(methodKey) === -1) continue; // allow non-method fields like 'parameters', 'summary', '$ref'
423
- var op = pathItem[methodKey];
424
- if (!op || typeof op !== "object") {
425
- errors.push(methodKey.toUpperCase() + " " + pathKey + ": operation must be an object");
426
- continue;
427
- }
428
- if (!op.responses || typeof op.responses !== "object" ||
429
- Object.keys(op.responses).length === 0) {
430
- errors.push(methodKey.toUpperCase() + " " + pathKey +
431
- ": responses object required (per OpenAPI 3.1 §4.8.5)");
432
- } else {
433
- for (var statusKey in op.responses) {
434
- if (!Object.prototype.hasOwnProperty.call(op.responses, statusKey)) continue;
435
- var resp = op.responses[statusKey];
436
- if (!resp || typeof resp !== "object") {
437
- errors.push(methodKey.toUpperCase() + " " + pathKey +
438
- " response " + statusKey + ": must be an object");
439
- continue;
440
- }
441
- if (resp["$ref"]) continue; // $ref short-circuit
442
- if (typeof resp.description !== "string" || resp.description.length === 0) {
443
- errors.push(methodKey.toUpperCase() + " " + pathKey +
444
- " response " + statusKey +
445
- ": description is required (per OpenAPI 3.1 §4.8.16)");
446
- }
447
- }
448
- }
449
- if (Array.isArray(op.parameters)) {
450
- for (var pi = 0; pi < op.parameters.length; pi += 1) {
451
- var p = op.parameters[pi];
452
- if (!p || typeof p !== "object") continue;
453
- if (p["$ref"]) continue;
454
- if (p.in === "path" && p.required !== true) {
455
- errors.push(methodKey.toUpperCase() + " " + pathKey +
456
- " parameters[" + pi + "]: path parameter " +
457
- JSON.stringify(p.name) + " must have required=true");
458
- }
459
- }
460
- }
571
+ _validateItemOperations(pathItem, pathKey, errors, securitySchemes);
572
+ }
573
+ }
574
+ // Webhooks same operation rules as paths, but keys are free-form
575
+ // webhook names (not `/`-prefixed URL templates), per OpenAPI 3.2
576
+ // §4.8.2.
577
+ if (doc.webhooks != null && typeof doc.webhooks !== "object") {
578
+ errors.push("`webhooks` must be an object when present");
579
+ } else if (doc.webhooks) {
580
+ for (var webhookKey in doc.webhooks) {
581
+ if (!Object.prototype.hasOwnProperty.call(doc.webhooks, webhookKey)) continue;
582
+ var webhookItem = doc.webhooks[webhookKey];
583
+ if (!webhookItem || typeof webhookItem !== "object") {
584
+ errors.push("webhooks[" + JSON.stringify(webhookKey) + "] must be an object");
585
+ continue;
461
586
  }
587
+ _validateItemOperations(webhookItem, "webhook " + webhookKey, errors, securitySchemes);
462
588
  }
463
589
  }
464
590
  // Dangling security references — every requirement key must resolve
465
591
  // to a registered security scheme.
466
- var securitySchemes = (doc.components && doc.components.securitySchemes) || {};
467
592
  if (Array.isArray(doc.security)) {
468
593
  for (var s = 0; s < doc.security.length; s += 1) {
469
594
  for (var schemeName in doc.security[s]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.14.18",
3
+ "version": "0.14.19",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:b96f0679-8967-43f9-b412-aa598ea52508",
5
+ "serialNumber": "urn:uuid:c7c9f488-0e96-4295-af9d-359b64bbfce6",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-06-02T15:25:35.604Z",
8
+ "timestamp": "2026-06-02T19:28:34.781Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.14.18",
22
+ "bom-ref": "@blamejs/core@0.14.19",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.14.18",
25
+ "version": "0.14.19",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.14.18",
29
+ "purl": "pkg:npm/%40blamejs/core@0.14.19",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.14.18",
57
+ "ref": "@blamejs/core@0.14.19",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]