@bymax-one/nest-core 1.6.0 → 1.6.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.
@@ -134,6 +134,82 @@ function collectContributions(discovery, reflector, handlers) {
134
134
  });
135
135
  }
136
136
 
137
+ // src/openapi/openapi.shape.ts
138
+ var OPERATION_METHODS = [
139
+ "get",
140
+ "post",
141
+ "put",
142
+ "patch",
143
+ "delete",
144
+ "head",
145
+ "options",
146
+ "trace"
147
+ ];
148
+ function asRecord(value) {
149
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
150
+ return {};
151
+ }
152
+ return value;
153
+ }
154
+ function operationsOf(item) {
155
+ return Object.entries(asRecord(item)).filter(([key]) => OPERATION_METHODS.includes(key));
156
+ }
157
+ function operationKey(method, path) {
158
+ return `${method.toUpperCase()} ${path}`;
159
+ }
160
+
161
+ // src/openapi/openapi.components.ts
162
+ function isEmptyRequired(key, value) {
163
+ return key === "required" && Array.isArray(value) && value.length === 0;
164
+ }
165
+ var IGNORABLE_KEYS = /* @__PURE__ */ new Set(["type", "properties", "description"]);
166
+ function describesNoShape(entry) {
167
+ const type = entry["type"];
168
+ if (type !== void 0 && type !== "object") {
169
+ return false;
170
+ }
171
+ const properties = entry["properties"];
172
+ if (!Object.hasOwn(entry, "properties") || asRecord(properties) !== properties) {
173
+ return false;
174
+ }
175
+ if (Object.keys(asRecord(properties)).length > 0) {
176
+ return false;
177
+ }
178
+ return Object.entries(entry).every(
179
+ ([key, value]) => IGNORABLE_KEYS.has(key) || isEmptyRequired(key, value)
180
+ );
181
+ }
182
+ function placeComponentEntries(existing, member, offered, onKept) {
183
+ const next = new Map(Object.entries(asRecord(existing)));
184
+ for (const [name, contributed] of Object.entries(asRecord(offered))) {
185
+ const declared = next.get(name);
186
+ const held = asRecord(declared);
187
+ const replaceable = member === "schemas" && describesNoShape(held);
188
+ if (declared !== void 0 && !replaceable) {
189
+ onKept(name);
190
+ continue;
191
+ }
192
+ const described = held["description"];
193
+ next.set(
194
+ name,
195
+ described === void 0 || described === "" ? contributed : { ...asRecord(contributed), description: described }
196
+ );
197
+ }
198
+ return Object.fromEntries(next);
199
+ }
200
+ function mergeContributedComponents(merged, contributions) {
201
+ const discarded = [];
202
+ for (const contribution of contributions) {
203
+ for (const [member, entries] of Object.entries(contribution.components)) {
204
+ const placed = placeComponentEntries(merged.get(member), member, entries, (name) => {
205
+ discarded.push({ contributor: contribution.label, member, name });
206
+ });
207
+ merged.set(member, placed);
208
+ }
209
+ }
210
+ return discarded;
211
+ }
212
+
137
213
  // src/route-defaults.ts
138
214
  var DEFAULT_HEALTH_PATH = "health";
139
215
  var DEFAULT_METRICS_PATH = "metrics";
@@ -326,30 +402,6 @@ var CORE_PARAMETERS = {
326
402
  }
327
403
  };
328
404
 
329
- // src/openapi/openapi.shape.ts
330
- var OPERATION_METHODS = [
331
- "get",
332
- "post",
333
- "put",
334
- "patch",
335
- "delete",
336
- "head",
337
- "options",
338
- "trace"
339
- ];
340
- function asRecord(value) {
341
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
342
- return {};
343
- }
344
- return value;
345
- }
346
- function operationsOf(item) {
347
- return Object.entries(asRecord(item)).filter(([key]) => OPERATION_METHODS.includes(key));
348
- }
349
- function operationKey(method, path) {
350
- return `${method.toUpperCase()} ${path}`;
351
- }
352
-
353
405
  // src/openapi/openapi.document.ts
354
406
  var METRICS_SCHEME_NAME = "BymaxMetricsAuth";
355
407
  var ERROR_ENVELOPE_SCHEMA = "BymaxErrorEnvelope";
@@ -541,8 +593,14 @@ function augmentDocument(document, options, pathPrefixes = [""], contributions =
541
593
  const components = asRecord(document.components);
542
594
  const merged = new Map(Object.entries(components));
543
595
  if (openapi.includeCoreSchemas) {
544
- merged.set("schemas", mergeAbsent(asRecord(components["schemas"]), CORE_SCHEMAS));
545
- merged.set("parameters", mergeAbsent(asRecord(components["parameters"]), CORE_PARAMETERS));
596
+ const keep = () => void 0;
597
+ const catalogue = [
598
+ ["schemas", CORE_SCHEMAS],
599
+ ["parameters", CORE_PARAMETERS]
600
+ ];
601
+ for (const [member, entries] of catalogue) {
602
+ merged.set(member, placeComponentEntries(merged.get(member), member, entries, keep));
603
+ }
546
604
  }
547
605
  const scrapeScheme = options.metrics.authToken === void 0 ? {} : {
548
606
  [METRICS_SCHEME_NAME]: {
@@ -559,11 +617,7 @@ function augmentDocument(document, options, pathPrefixes = [""], contributions =
559
617
  if (Object.keys(declaredSchemes).length > 0) {
560
618
  merged.set("securitySchemes", declaredSchemes);
561
619
  }
562
- for (const contribution of contributions) {
563
- for (const [member, entries] of Object.entries(contribution.components)) {
564
- merged.set(member, mergeAbsent(asRecord(merged.get(member)), entries));
565
- }
566
- }
620
+ const discarded = mergeContributedComponents(merged, contributions);
567
621
  assertSchemesDeclared(openapi, asRecord(merged.get("securitySchemes")));
568
622
  const routes = indexOwnRoutes(options, pathPrefixes);
569
623
  const served = withoutDisabledRoutes(asRecord(document.paths), options, routes);
@@ -578,7 +632,10 @@ function augmentDocument(document, options, pathPrefixes = [""], contributions =
578
632
  )
579
633
  };
580
634
  const security = openapi.security.length > 0 && document.security === void 0 ? { security: openapi.security } : {};
581
- return { ...document, components: Object.fromEntries(merged), ...paths, ...security };
635
+ return {
636
+ document: { ...document, components: Object.fromEntries(merged), ...paths, ...security },
637
+ discarded
638
+ };
582
639
  }
583
640
 
584
641
  // src/optional-peer.ts
@@ -673,6 +730,16 @@ function warnUnsecuredOperations(logger, keys) {
673
730
  `a client generated from the OpenAPI document will send no credentials to ${keys.length} operation(s): ${listed}${elided > 0 ? `, and ${elided} more` : ""}. They state no security requirement, the document declares no default, and other operations in it do state one \u2014 so this is more often a missing openapi.security default than a public API. Set openapi.security, or state the intent per operation with an explicit [] in openapi.operationSecurity.`
674
731
  );
675
732
  }
733
+ function warnDiscardedComponents(logger, discarded) {
734
+ if (discarded.length === 0) {
735
+ return;
736
+ }
737
+ const elided = discarded.length - MAX_WARNED_OPERATIONS;
738
+ const listed = discarded.slice(0, MAX_WARNED_OPERATIONS).map(({ contributor, member, name }) => `${contributor} -> ${member}.${name}`).join(", ");
739
+ logger.warn(
740
+ `the served OpenAPI document already defines ${discarded.length} component entry(ies) a library also contributes: ${listed}${elided > 0 ? `, and ${elided} more` : ""}. The contributed version is not served, so a client generated from this document will not match that library's own types. What holds the name may be the application's own declaration, a core schema under openapi.includeCoreSchemas, or a contributor that sorted earlier \u2014 read the document before deciding what to remove.`
741
+ );
742
+ }
676
743
  function buildConfig(builder, options) {
677
744
  builder.setTitle(options.title).setDescription(options.description).setVersion(options.version);
678
745
  for (const server of options.servers) {
@@ -702,13 +769,19 @@ async function applyBymaxOpenApi(app) {
702
769
  operationIdFactory: recordingOperationIdFactory(handlers, options.operationIdFactory)
703
770
  });
704
771
  const prefixes = readPathPrefixes(app);
705
- const document = augmentDocument(generated, resolved, prefixes, readContributions(app, handlers));
772
+ const { document, discarded } = augmentDocument(
773
+ generated,
774
+ resolved,
775
+ prefixes,
776
+ readContributions(app, handlers)
777
+ );
706
778
  warnUnsecuredOperations(logger, unsecuredOperations(document, resolved, prefixes));
779
+ warnDiscardedComponents(logger, discarded);
707
780
  swagger.SwaggerModule.setup(options.path, app, document, {
708
781
  jsonDocumentUrl: options.jsonPath
709
782
  });
710
783
  logger.log(`OpenAPI document served at "/${options.path}" (JSON at "/${options.jsonPath}")`);
711
- return { mounted: true, path: options.path };
784
+ return { mounted: true, path: options.path, discarded };
712
785
  }
713
786
 
714
787
  exports.BYMAX_OPENAPI_CONTRACT_VERSION = BYMAX_OPENAPI_CONTRACT_VERSION;
@@ -1,55 +1,4 @@
1
- import { INestApplication, CustomDecorator } from '@nestjs/common';
2
-
3
- /** Why the document was not mounted. */
4
- type OpenApiSkipReason =
5
- /** The consumer never enabled the feature. */
6
- 'disabled'
7
- /** The runtime is production, where the document is never served. */
8
- | 'production';
9
- /** What {@link applyBymaxOpenApi} did, so a caller can assert on it. */
10
- interface OpenApiMountOutcome {
11
- /** Whether the document and its UI were mounted. */
12
- mounted: boolean;
13
- /** Present only when `mounted` is `false`. */
14
- reason?: OpenApiSkipReason;
15
- /** The route the UI was mounted at. Present only when `mounted` is `true`. */
16
- path?: string;
17
- }
18
- /**
19
- * Build and mount the OpenAPI document and its interactive UI, when the
20
- * configuration and the runtime both allow it.
21
- *
22
- * Call it once, after `NestFactory.create` and BEFORE the application starts
23
- * listening. The ordering is not a style preference: mounting the document
24
- * re-registers routes on the HTTP adapter, and on Express 5 doing that against
25
- * an already-initialized application replaces the router — every route the
26
- * application had, including its own controllers and this package's health
27
- * endpoints, stops resolving. `app.listen()` performs that initialization, so
28
- * "before listening" is the whole rule.
29
- *
30
- * It is safe to call unconditionally: with the feature disabled, or in
31
- * production, it mounts nothing, loads no optional peer, and returns why.
32
- *
33
- * Testing this under Jest needs one flag. `@nestjs/swagger` is loaded through a
34
- * dynamic `import()`, which is what keeps the peer optional for everyone who
35
- * never enables the document — and Jest's module registry cannot service a
36
- * dynamic import without `NODE_OPTIONS=--experimental-vm-modules`. Without it,
37
- * only the *enabled* path fails, with `dynamic import callback invoked without
38
- * --experimental-vm-modules`; the disabled and production paths never reach the
39
- * loader and pass either way, which is what makes the omission confusing.
40
- *
41
- * @param app - The created Nest application, not yet listening.
42
- * @returns What happened: mounted, or skipped with a reason.
43
- * @throws Error When `BymaxCoreModule` is not registered, when the feature is
44
- * enabled and the optional peer `@nestjs/swagger` is not installed, or when
45
- * `openapi.operationSecurity` addresses an operation the generated document
46
- * does not contain.
47
- * @example
48
- * const app = await NestFactory.create(AppModule)
49
- * await applyBymaxOpenApi(app)
50
- * await app.listen(3000)
51
- */
52
- declare function applyBymaxOpenApi(app: INestApplication): Promise<OpenApiMountOutcome>;
1
+ import { CustomDecorator, INestApplication } from '@nestjs/common';
53
2
 
54
3
  /**
55
4
  * Addresses one route handler, as `'<ControllerClassName>.<methodName>'`.
@@ -196,4 +145,99 @@ declare const BYMAX_OPENAPI_CONTRIBUTOR_METADATA = "bymax-one:openapi-contributo
196
145
  */
197
146
  declare function BymaxOpenApiContributor(): CustomDecorator<string>;
198
147
 
199
- export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, type IOpenApiContributor, type OpenApiFragment, type OpenApiFragmentObject, type OpenApiHandlerKey, type OpenApiMountOutcome, type OpenApiSkipReason, applyBymaxOpenApi };
148
+ /**
149
+ * @fileoverview Placing offered entries — this package's catalogue and every
150
+ * library contribution — into a document's `components`, and reporting what a
151
+ * contributor could not place.
152
+ *
153
+ * A module of its own because the rule here is not the document's rule: everywhere else the merge is "the existing side wins", and this is the
154
+ * one place that asks whether the existing side says anything at all. Keeping
155
+ * the question and its narrow carve-out together makes the exception legible as
156
+ * an exception.
157
+ * @layer internal
158
+ */
159
+
160
+ /**
161
+ * One component entry a contributor offered and the document already declared.
162
+ *
163
+ * Carries the contributor's label because a collision is only actionable if you
164
+ * know who lost it: an application with several contributing libraries would
165
+ * otherwise be told a name was dropped without being told whose it was.
166
+ */
167
+ interface DiscardedComponent {
168
+ /** The contributing class, as {@link ResolvedContribution.label} names it. */
169
+ readonly contributor: string;
170
+ /** The component member, such as `schemas` or `parameters`. */
171
+ readonly member: string;
172
+ /** The entry's name within that member. */
173
+ readonly name: string;
174
+ }
175
+
176
+ /** Why the document was not mounted. */
177
+ type OpenApiSkipReason =
178
+ /** The consumer never enabled the feature. */
179
+ 'disabled'
180
+ /** The runtime is production, where the document is never served. */
181
+ | 'production';
182
+ /** What {@link applyBymaxOpenApi} did, so a caller can assert on it. */
183
+ interface OpenApiMountOutcome {
184
+ /** Whether the document and its UI were mounted. */
185
+ mounted: boolean;
186
+ /** Present only when `mounted` is `false`. */
187
+ reason?: OpenApiSkipReason;
188
+ /** The route the UI was mounted at. Present only when `mounted` is `true`. */
189
+ path?: string;
190
+ /**
191
+ * Component entries a contributor offered that the served document already
192
+ * defined with a shape of its own. Present only when `mounted` is `true`;
193
+ * empty when every contribution was placed.
194
+ *
195
+ * The merge prefers whatever holds the name already, so a collision is not an
196
+ * error and does not stop the boot. The holder is usually the application's
197
+ * own declaration, but it may be a core schema this package contributes under
198
+ * `openapi.includeCoreSchemas` or an earlier contributor's entry — the report
199
+ * names what was dropped and by whom, not who owns the winning definition.
200
+ *
201
+ * It is reported because it is otherwise unobservable from either side: the
202
+ * contributing library cannot tell its entry was dropped, and the application
203
+ * cannot tell one was offered.
204
+ */
205
+ discarded?: readonly DiscardedComponent[];
206
+ }
207
+ /**
208
+ * Build and mount the OpenAPI document and its interactive UI, when the
209
+ * configuration and the runtime both allow it.
210
+ *
211
+ * Call it once, after `NestFactory.create` and BEFORE the application starts
212
+ * listening. The ordering is not a style preference: mounting the document
213
+ * re-registers routes on the HTTP adapter, and on Express 5 doing that against
214
+ * an already-initialized application replaces the router — every route the
215
+ * application had, including its own controllers and this package's health
216
+ * endpoints, stops resolving. `app.listen()` performs that initialization, so
217
+ * "before listening" is the whole rule.
218
+ *
219
+ * It is safe to call unconditionally: with the feature disabled, or in
220
+ * production, it mounts nothing, loads no optional peer, and returns why.
221
+ *
222
+ * Testing this under Jest needs one flag. `@nestjs/swagger` is loaded through a
223
+ * dynamic `import()`, which is what keeps the peer optional for everyone who
224
+ * never enables the document — and Jest's module registry cannot service a
225
+ * dynamic import without `NODE_OPTIONS=--experimental-vm-modules`. Without it,
226
+ * only the *enabled* path fails, with `dynamic import callback invoked without
227
+ * --experimental-vm-modules`; the disabled and production paths never reach the
228
+ * loader and pass either way, which is what makes the omission confusing.
229
+ *
230
+ * @param app - The created Nest application, not yet listening.
231
+ * @returns What happened: mounted, or skipped with a reason.
232
+ * @throws Error When `BymaxCoreModule` is not registered, when the feature is
233
+ * enabled and the optional peer `@nestjs/swagger` is not installed, or when
234
+ * `openapi.operationSecurity` addresses an operation the generated document
235
+ * does not contain.
236
+ * @example
237
+ * const app = await NestFactory.create(AppModule)
238
+ * await applyBymaxOpenApi(app)
239
+ * await app.listen(3000)
240
+ */
241
+ declare function applyBymaxOpenApi(app: INestApplication): Promise<OpenApiMountOutcome>;
242
+
243
+ export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, type DiscardedComponent, type IOpenApiContributor, type OpenApiFragment, type OpenApiFragmentObject, type OpenApiHandlerKey, type OpenApiMountOutcome, type OpenApiSkipReason, applyBymaxOpenApi };
@@ -1,55 +1,4 @@
1
- import { INestApplication, CustomDecorator } from '@nestjs/common';
2
-
3
- /** Why the document was not mounted. */
4
- type OpenApiSkipReason =
5
- /** The consumer never enabled the feature. */
6
- 'disabled'
7
- /** The runtime is production, where the document is never served. */
8
- | 'production';
9
- /** What {@link applyBymaxOpenApi} did, so a caller can assert on it. */
10
- interface OpenApiMountOutcome {
11
- /** Whether the document and its UI were mounted. */
12
- mounted: boolean;
13
- /** Present only when `mounted` is `false`. */
14
- reason?: OpenApiSkipReason;
15
- /** The route the UI was mounted at. Present only when `mounted` is `true`. */
16
- path?: string;
17
- }
18
- /**
19
- * Build and mount the OpenAPI document and its interactive UI, when the
20
- * configuration and the runtime both allow it.
21
- *
22
- * Call it once, after `NestFactory.create` and BEFORE the application starts
23
- * listening. The ordering is not a style preference: mounting the document
24
- * re-registers routes on the HTTP adapter, and on Express 5 doing that against
25
- * an already-initialized application replaces the router — every route the
26
- * application had, including its own controllers and this package's health
27
- * endpoints, stops resolving. `app.listen()` performs that initialization, so
28
- * "before listening" is the whole rule.
29
- *
30
- * It is safe to call unconditionally: with the feature disabled, or in
31
- * production, it mounts nothing, loads no optional peer, and returns why.
32
- *
33
- * Testing this under Jest needs one flag. `@nestjs/swagger` is loaded through a
34
- * dynamic `import()`, which is what keeps the peer optional for everyone who
35
- * never enables the document — and Jest's module registry cannot service a
36
- * dynamic import without `NODE_OPTIONS=--experimental-vm-modules`. Without it,
37
- * only the *enabled* path fails, with `dynamic import callback invoked without
38
- * --experimental-vm-modules`; the disabled and production paths never reach the
39
- * loader and pass either way, which is what makes the omission confusing.
40
- *
41
- * @param app - The created Nest application, not yet listening.
42
- * @returns What happened: mounted, or skipped with a reason.
43
- * @throws Error When `BymaxCoreModule` is not registered, when the feature is
44
- * enabled and the optional peer `@nestjs/swagger` is not installed, or when
45
- * `openapi.operationSecurity` addresses an operation the generated document
46
- * does not contain.
47
- * @example
48
- * const app = await NestFactory.create(AppModule)
49
- * await applyBymaxOpenApi(app)
50
- * await app.listen(3000)
51
- */
52
- declare function applyBymaxOpenApi(app: INestApplication): Promise<OpenApiMountOutcome>;
1
+ import { CustomDecorator, INestApplication } from '@nestjs/common';
53
2
 
54
3
  /**
55
4
  * Addresses one route handler, as `'<ControllerClassName>.<methodName>'`.
@@ -196,4 +145,99 @@ declare const BYMAX_OPENAPI_CONTRIBUTOR_METADATA = "bymax-one:openapi-contributo
196
145
  */
197
146
  declare function BymaxOpenApiContributor(): CustomDecorator<string>;
198
147
 
199
- export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, type IOpenApiContributor, type OpenApiFragment, type OpenApiFragmentObject, type OpenApiHandlerKey, type OpenApiMountOutcome, type OpenApiSkipReason, applyBymaxOpenApi };
148
+ /**
149
+ * @fileoverview Placing offered entries — this package's catalogue and every
150
+ * library contribution — into a document's `components`, and reporting what a
151
+ * contributor could not place.
152
+ *
153
+ * A module of its own because the rule here is not the document's rule: everywhere else the merge is "the existing side wins", and this is the
154
+ * one place that asks whether the existing side says anything at all. Keeping
155
+ * the question and its narrow carve-out together makes the exception legible as
156
+ * an exception.
157
+ * @layer internal
158
+ */
159
+
160
+ /**
161
+ * One component entry a contributor offered and the document already declared.
162
+ *
163
+ * Carries the contributor's label because a collision is only actionable if you
164
+ * know who lost it: an application with several contributing libraries would
165
+ * otherwise be told a name was dropped without being told whose it was.
166
+ */
167
+ interface DiscardedComponent {
168
+ /** The contributing class, as {@link ResolvedContribution.label} names it. */
169
+ readonly contributor: string;
170
+ /** The component member, such as `schemas` or `parameters`. */
171
+ readonly member: string;
172
+ /** The entry's name within that member. */
173
+ readonly name: string;
174
+ }
175
+
176
+ /** Why the document was not mounted. */
177
+ type OpenApiSkipReason =
178
+ /** The consumer never enabled the feature. */
179
+ 'disabled'
180
+ /** The runtime is production, where the document is never served. */
181
+ | 'production';
182
+ /** What {@link applyBymaxOpenApi} did, so a caller can assert on it. */
183
+ interface OpenApiMountOutcome {
184
+ /** Whether the document and its UI were mounted. */
185
+ mounted: boolean;
186
+ /** Present only when `mounted` is `false`. */
187
+ reason?: OpenApiSkipReason;
188
+ /** The route the UI was mounted at. Present only when `mounted` is `true`. */
189
+ path?: string;
190
+ /**
191
+ * Component entries a contributor offered that the served document already
192
+ * defined with a shape of its own. Present only when `mounted` is `true`;
193
+ * empty when every contribution was placed.
194
+ *
195
+ * The merge prefers whatever holds the name already, so a collision is not an
196
+ * error and does not stop the boot. The holder is usually the application's
197
+ * own declaration, but it may be a core schema this package contributes under
198
+ * `openapi.includeCoreSchemas` or an earlier contributor's entry — the report
199
+ * names what was dropped and by whom, not who owns the winning definition.
200
+ *
201
+ * It is reported because it is otherwise unobservable from either side: the
202
+ * contributing library cannot tell its entry was dropped, and the application
203
+ * cannot tell one was offered.
204
+ */
205
+ discarded?: readonly DiscardedComponent[];
206
+ }
207
+ /**
208
+ * Build and mount the OpenAPI document and its interactive UI, when the
209
+ * configuration and the runtime both allow it.
210
+ *
211
+ * Call it once, after `NestFactory.create` and BEFORE the application starts
212
+ * listening. The ordering is not a style preference: mounting the document
213
+ * re-registers routes on the HTTP adapter, and on Express 5 doing that against
214
+ * an already-initialized application replaces the router — every route the
215
+ * application had, including its own controllers and this package's health
216
+ * endpoints, stops resolving. `app.listen()` performs that initialization, so
217
+ * "before listening" is the whole rule.
218
+ *
219
+ * It is safe to call unconditionally: with the feature disabled, or in
220
+ * production, it mounts nothing, loads no optional peer, and returns why.
221
+ *
222
+ * Testing this under Jest needs one flag. `@nestjs/swagger` is loaded through a
223
+ * dynamic `import()`, which is what keeps the peer optional for everyone who
224
+ * never enables the document — and Jest's module registry cannot service a
225
+ * dynamic import without `NODE_OPTIONS=--experimental-vm-modules`. Without it,
226
+ * only the *enabled* path fails, with `dynamic import callback invoked without
227
+ * --experimental-vm-modules`; the disabled and production paths never reach the
228
+ * loader and pass either way, which is what makes the omission confusing.
229
+ *
230
+ * @param app - The created Nest application, not yet listening.
231
+ * @returns What happened: mounted, or skipped with a reason.
232
+ * @throws Error When `BymaxCoreModule` is not registered, when the feature is
233
+ * enabled and the optional peer `@nestjs/swagger` is not installed, or when
234
+ * `openapi.operationSecurity` addresses an operation the generated document
235
+ * does not contain.
236
+ * @example
237
+ * const app = await NestFactory.create(AppModule)
238
+ * await applyBymaxOpenApi(app)
239
+ * await app.listen(3000)
240
+ */
241
+ declare function applyBymaxOpenApi(app: INestApplication): Promise<OpenApiMountOutcome>;
242
+
243
+ export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, type DiscardedComponent, type IOpenApiContributor, type OpenApiFragment, type OpenApiFragmentObject, type OpenApiHandlerKey, type OpenApiMountOutcome, type OpenApiSkipReason, applyBymaxOpenApi };