@ebarahona/loopback-openapi-v3 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -1,8 +1,32 @@
1
1
  # @ebarahona/loopback-openapi-v3
2
2
 
3
- OpenAPI version compatibility enhancer for LoopBack 4. Supports emitting OpenAPI Specification (OAS) 3.0.x, 3.1.x, and 3.2.x specs from LoopBack-generated OpenAPI output, with nullable conversion and lossy downgrade diagnostics.
3
+ OpenAPI version compatibility enhancer for LoopBack 4. Transforms LoopBack-generated OpenAPI 3.0 specs into OAS 3.1 or 3.2 at boot, handles nullable conversion both ways, and emits structured warnings for lossy downgrades.
4
4
 
5
- LoopBack 4 generates OpenAPI 3.0 specs by default. This component transforms the output to OpenAPI 3.1 or 3.2, enabling JSON Schema compatibility (3.1) and SSE streaming support (3.2). It also handles downgrading from higher versions to lower ones for backward compatibility.
5
+ ```bash
6
+ npm install @ebarahona/loopback-openapi-v3
7
+ ```
8
+
9
+ > Part of the [`@ebarahona/loopback-*` plugin portfolio](https://github.com/ebarahona/loopback-plugins). See the [portfolio roadmap](https://github.com/ebarahona/loopback-plugins/blob/main/ROADMAP.md) for sibling plugins (`loopback-connector-mongodb`, `loopback-transport-core`, planned `loopback-graphql`) and the shared infrastructure they use.
10
+
11
+ LoopBack 4 emits OpenAPI 3.0 by default. This component registers an OAS Enhancer that runs during spec generation and rewrites the document to the target version. Upgrades convert `{type, nullable}` to `{type: [type, 'null']}` and emit 3.1+ idioms. Downgrades strip fields that have no 3.0 equivalent and emit a `TransformWarning` for each one. The enhancer is pure and deterministic, so LoopBack's spec cache reuses the result on subsequent requests.
12
+
13
+ ## What This Provides
14
+
15
+ | Export | Purpose |
16
+ | ------------------------------------- | ---------------------------------------------------------------------- |
17
+ | `OpenApiVersionComponent` | LoopBack 4 component that registers the enhancer |
18
+ | `OpenApiVersionEnhancer` | OAS Enhancer wired into `@loopback/openapi-v3` spec generation |
19
+ | `OpenApiVersionBindings` | Typed `BindingKey` namespace (`CONFIG`) |
20
+ | `OpenApiVersion` | Version literal type (`'3.0.0' \| '3.1.0' \| '3.2.0'`) |
21
+ | `OpenApiVersionConfig` | Component config type (target version, nullable transform flag) |
22
+ | `transformOpenApiSpec(spec, config?)` | Pure transform function, usable outside LoopBack |
23
+ | `parseVersion(version)` | Parses `'3.1.0'` into `{major, minor, patch}` |
24
+ | `TransformResult` | `{spec, warnings}` returned by `transformOpenApiSpec` |
25
+ | `TransformWarning` | Structured warning describing a stripped or rewritten field |
26
+ | `OpenApiVersionError` | Base class for every error thrown by this package |
27
+ | `OpenApiVersionConfigError` | Thrown when component config is invalid |
28
+ | `OpenApiTransformError` | Thrown when the input spec cannot be transformed |
29
+ | `OpenApiDowngradeError` | Thrown when a downgrade target cannot represent required source fields |
6
30
 
7
31
  ## Install
8
32
 
@@ -12,111 +36,121 @@ npm install @ebarahona/loopback-openapi-v3
12
36
 
13
37
  ## Usage
14
38
 
39
+ ### Component path (recommended)
40
+
15
41
  ```typescript
16
- import {OpenApiVersionComponent, OpenApiVersionBindings} from '@ebarahona/loopback-openapi-v3';
42
+ import {RestApplication} from '@loopback/rest';
43
+ import {
44
+ OpenApiVersionComponent,
45
+ OpenApiVersionBindings,
46
+ } from '@ebarahona/loopback-openapi-v3';
17
47
 
18
48
  const app = new RestApplication();
19
49
  app.component(OpenApiVersionComponent);
20
- app.bind(OpenApiVersionBindings.CONFIG).to({version: '3.1.0'});
50
+ app.bind(OpenApiVersionBindings.CONFIG).to({
51
+ version: '3.1.0',
52
+ transformNullable: true,
53
+ });
21
54
  ```
22
55
 
23
- The spec served at `/openapi.json` will output `"openapi": "3.1.0"`.
56
+ The spec served at `/openapi.json` is rewritten to `"openapi": "3.1.0"`, with nullable fields converted to type arrays. LoopBack caches the transformed spec; the enhancer does not re-run per request.
24
57
 
25
- ## Configuration
58
+ ### Pure-function path (standalone)
59
+
60
+ `transformOpenApiSpec` is exported for use outside a LoopBack application (build-time tooling, tests, fixture generation):
26
61
 
27
62
  ```typescript
28
- app.bind(OpenApiVersionBindings.CONFIG).to({
29
- // Target version: '3.0.0' (default, no-op), '3.1.0', or '3.2.0'
30
- version: '3.1.0',
63
+ import {transformOpenApiSpec} from '@ebarahona/loopback-openapi-v3';
31
64
 
32
- // Transform nullable fields from 3.0 format to 3.1+ format
33
- // { type: 'string', nullable: true } -> { type: ['string', 'null'] }
34
- // Defaults to true for 3.1+
35
- transformNullable: true,
65
+ const {spec, warnings} = transformOpenApiSpec(inputSpec, {
66
+ version: '3.1.0',
36
67
  });
68
+
69
+ for (const w of warnings) {
70
+ console.warn(`[${w.field}] ${w.message}`);
71
+ }
37
72
  ```
38
73
 
39
- ## Supported Versions
74
+ The input spec is deep-cloned with `structuredClone`; the original is never mutated.
75
+
76
+ ## Configuration
77
+
78
+ `OpenApiVersionBindings.CONFIG` accepts an `OpenApiVersionConfig`:
79
+
80
+ | Field | Type | Default | Purpose |
81
+ | ------------------- | ------------------------------- | --------- | ------------------------------------------------------------------------------------------------------ |
82
+ | `version` | `'3.0.0' \| '3.1.0' \| '3.2.0'` | `'3.0.0'` | Target OAS version. `'3.0.0'` is a no-op pass-through. |
83
+ | `transformNullable` | `boolean` | `true` | Rewrite `{type, nullable: true}` to `{type: [type, 'null']}` on upgrade, and the reverse on downgrade. |
40
84
 
41
- | Version | Status | Spec |
42
- |---|---|---|
43
- | 3.0.0 | Default (no-op) | [spec.openapis.org/oas/v3.0.3](https://spec.openapis.org/oas/v3.0.3.html) |
44
- | 3.1.0 | Supported | [spec.openapis.org/oas/v3.1.0](https://spec.openapis.org/oas/v3.1.0.html) |
45
- | 3.2.0 | Supported | [spec.openapis.org/oas/v3.2.0](https://spec.openapis.org/oas/v3.2.0.html) |
85
+ Invalid configurations (unrecognized version, non-boolean flag) throw `OpenApiVersionConfigError` synchronously when the component binds.
46
86
 
47
- ## How It Works
87
+ ## Supported versions
48
88
 
49
- The component registers an OAS Enhancer that runs once at boot after LoopBack assembles the spec. The original spec is deep-cloned to prevent mutation. The transformed spec is cached by LoopBack and served on subsequent requests without re-processing.
89
+ | Version | Status | Spec |
90
+ | ------- | ------------- | ------------------------------------------------------------------------- |
91
+ | 3.0.0 | Default no-op | [spec.openapis.org/oas/v3.0.3](https://spec.openapis.org/oas/v3.0.3.html) |
92
+ | 3.1.0 | Supported | [spec.openapis.org/oas/v3.1.0](https://spec.openapis.org/oas/v3.1.0.html) |
93
+ | 3.2.0 | Supported | [spec.openapis.org/oas/v3.2.0](https://spec.openapis.org/oas/v3.2.0.html) |
50
94
 
51
- ### Upgrades (3.0 -> 3.1/3.2)
95
+ ### Upgrades (3.0 -> 3.1 / 3.2)
52
96
 
53
- - `nullable: true` transformed to type arrays (`{ type: ['string', 'null'] }`)
54
- - Handles nullable with `oneOf`, `anyOf`, `allOf`, and no type
97
+ - `nullable: true` rewritten to type arrays (`{type: ['string', 'null']}`).
98
+ - Handles nullable combined with `oneOf`, `anyOf`, `allOf`, and schemas without an explicit `type`.
55
99
 
56
- ### Downgrades (3.2 -> 3.0/3.1)
100
+ ### Downgrades (3.2 -> 3.0 / 3.1)
57
101
 
58
- Downgrades are lossy. Features that have no equivalent in lower versions are stripped with diagnostic warnings.
102
+ Downgrades are lossy. Features that have no equivalent in lower versions are stripped, and a `TransformWarning` is emitted for each one.
59
103
 
60
104
  **3.1 features stripped when targeting 3.0:**
61
105
 
62
- | Feature | Field |
63
- |---|---|
64
- | JSON Schema dialect | `jsonSchemaDialect` |
65
- | Webhooks | `webhooks` |
66
- | Reusable path items | `components.pathItems` |
67
- | SPDX license identifier | `info.license.identifier` |
68
- | Type arrays | `type: ['string', 'null']` reverted to `nullable: true` |
106
+ | Feature | Field |
107
+ | ----------------------- | ---------------------------------------------- |
108
+ | JSON Schema dialect | `jsonSchemaDialect` |
109
+ | Webhooks | `webhooks` |
110
+ | Reusable path items | `components.pathItems` |
111
+ | SPDX license identifier | `info.license.identifier` |
112
+ | Type arrays | `type: ['string', 'null']` -> `nullable: true` |
69
113
 
70
114
  **3.2 features stripped when targeting 3.0 or 3.1:**
71
115
 
72
- | Feature | Field |
73
- |---|---|
74
- | Document identity | `$self` |
75
- | Server name | `servers[].name` |
76
- | QUERY HTTP method | `paths.*. query` |
77
- | Additional HTTP methods | `paths.*.additionalOperations` |
78
- | Tag nesting | `tags[].summary`, `.parent`, `.kind` |
79
- | Querystring parameter | `parameters[].in: 'querystring'` converted to `'query'` |
80
- | OAuth2 device flow | `securitySchemes.*.flows.device` |
81
- | SSE streaming schema | `content.*.itemSchema` |
82
- | SSE streaming encoding | `content.*.itemEncoding`, `.prefixEncoding` |
83
- | Example formats | `examples.*.dataValue`, `.serializedValue` |
84
- | Reusable media types | `components.mediaTypes` |
85
- | XML text nodes | `xml.text` |
86
-
87
- ### Diagnostics
88
-
89
- The transformer emits warnings for every stripped feature:
116
+ | Feature | Field |
117
+ | ----------------------- | --------------------------------------------- |
118
+ | Document identity | `$self` |
119
+ | Server name | `servers[].name` |
120
+ | QUERY HTTP method | `paths.*.query` |
121
+ | Additional HTTP methods | `paths.*.additionalOperations` |
122
+ | Tag nesting | `tags[].summary`, `.parent`, `.kind` |
123
+ | Querystring parameter | `parameters[].in: 'querystring'` -> `'query'` |
124
+ | OAuth2 device flow | `securitySchemes.*.flows.device` |
125
+ | SSE streaming schema | `content.*.itemSchema` |
126
+ | SSE streaming encoding | `content.*.itemEncoding`, `.prefixEncoding` |
127
+ | Example formats | `examples.*.dataValue`, `.serializedValue` |
128
+ | Reusable media types | `components.mediaTypes` |
129
+ | XML text nodes | `xml.text` |
90
130
 
91
- ```typescript
92
- import {transformOpenApiSpec} from '@ebarahona/loopback-openapi-v3';
131
+ When a required source field cannot be expressed in the target version (for example a 3.2 spec that depends on `$self` referenced elsewhere in the document), the transform raises `OpenApiDowngradeError` instead of silently dropping the reference.
93
132
 
94
- const {spec, warnings} = transformOpenApiSpec(inputSpec, {version: '3.0.0'});
95
- for (const w of warnings) {
96
- console.log(`[${w.field}] ${w.message}`);
97
- }
98
- // [webhooks] Removed webhooks because target version is 3.0.x (webhooks require 3.1+)
99
- // [$self] Removed $self because target version is 3.0.x
100
- ```
133
+ ## Typed errors
101
134
 
102
- ### Pure Function API
135
+ This package throws four typed error classes, all derived from `OpenApiVersionError`. Catch the base class for a category check, or the specific subclass for fine-grained handling.
103
136
 
104
- The transformer is available as a standalone pure function for use outside LoopBack:
137
+ - `OpenApiVersionError`: abstract base. Every error thrown by this package extends it.
138
+ - `OpenApiVersionConfigError`: invalid component configuration. Thrown synchronously when the component binds or when `transformOpenApiSpec` is called with an unsupported target version.
139
+ - `OpenApiTransformError`: input spec is structurally invalid (missing `openapi` field, unparsable version string, cyclic schema references).
140
+ - `OpenApiDowngradeError`: a downgrade target cannot represent a load-bearing source field. The error includes the offending field path and the source/target version pair.
105
141
 
106
- ```typescript
107
- import {transformOpenApiSpec, parseVersion} from '@ebarahona/loopback-openapi-v3';
142
+ ## Stability
108
143
 
109
- const {spec, warnings} = transformOpenApiSpec(inputSpec, {version: '3.1.0'});
110
- ```
144
+ Every export listed in [What This Provides](#what-this-provides) is tagged `@public` from v1.0 onward. The public-API surface is audited by the `/lb4-public-api-audit` skill before every release; experimental additions are tagged `@experimental` until at least one real consumer has exercised the surface.
111
145
 
112
146
  ## Requirements
113
147
 
114
- - Node.js >= 18 (for `structuredClone`)
115
- - LoopBack 4 (`@loopback/core` >= 7.0.0)
116
- - `@loopback/openapi-v3` >= 11.0.0
117
- - `@loopback/rest` >= 15.0.0
148
+ - Node.js >= 20.19.0 (for `structuredClone` and the package's engines field)
149
+ - LoopBack 4 application
150
+
151
+ Peer dependencies: `@loopback/core` (>=7.0.0 <8.0.0), `@loopback/openapi-v3` (>=11.0.0 <12.0.0), `@loopback/rest` (>=15.0.0 <16.0.0).
118
152
 
119
- ## Spec References
153
+ ## Spec references
120
154
 
121
155
  - [OpenAPI 3.0.3](https://spec.openapis.org/oas/v3.0.3.html)
122
156
  - [OpenAPI 3.1.0](https://spec.openapis.org/oas/v3.1.0.html)
@@ -125,7 +159,7 @@ const {spec, warnings} = transformOpenApiSpec(inputSpec, {version: '3.1.0'});
125
159
 
126
160
  ## Contributing
127
161
 
128
- See [CONTRIBUTING.md](CONTRIBUTING.md).
162
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Help-wanted topics with measurable acceptance criteria are listed in [HELP_WANTED.md](HELP_WANTED.md).
129
163
 
130
164
  ## License
131
165
 
@@ -0,0 +1,19 @@
1
+ export interface OpenApiErrorOptions {
2
+ cause?: unknown;
3
+ }
4
+ export declare class OpenApiVersionError extends Error {
5
+ readonly name: string;
6
+ readonly cause?: unknown;
7
+ constructor(message: string, options?: OpenApiErrorOptions);
8
+ }
9
+ export declare class OpenApiVersionConfigError extends OpenApiVersionError {
10
+ readonly name = "OpenApiVersionConfigError";
11
+ }
12
+ export declare class OpenApiTransformError extends OpenApiVersionError {
13
+ readonly name = "OpenApiTransformError";
14
+ }
15
+ export declare class OpenApiDowngradeError extends OpenApiVersionError {
16
+ readonly unsupportedFeatures: readonly string[];
17
+ readonly name = "OpenApiDowngradeError";
18
+ constructor(message: string, unsupportedFeatures: readonly string[], options?: OpenApiErrorOptions);
19
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenApiDowngradeError = exports.OpenApiTransformError = exports.OpenApiVersionConfigError = exports.OpenApiVersionError = void 0;
4
+ class OpenApiVersionError extends Error {
5
+ constructor(message, options) {
6
+ super(message);
7
+ this.name = 'OpenApiVersionError';
8
+ if (options && 'cause' in options) {
9
+ this.cause = options.cause;
10
+ }
11
+ Object.setPrototypeOf(this, new.target.prototype);
12
+ }
13
+ }
14
+ exports.OpenApiVersionError = OpenApiVersionError;
15
+ class OpenApiVersionConfigError extends OpenApiVersionError {
16
+ constructor() {
17
+ super(...arguments);
18
+ this.name = 'OpenApiVersionConfigError';
19
+ }
20
+ }
21
+ exports.OpenApiVersionConfigError = OpenApiVersionConfigError;
22
+ class OpenApiTransformError extends OpenApiVersionError {
23
+ constructor() {
24
+ super(...arguments);
25
+ this.name = 'OpenApiTransformError';
26
+ }
27
+ }
28
+ exports.OpenApiTransformError = OpenApiTransformError;
29
+ class OpenApiDowngradeError extends OpenApiVersionError {
30
+ constructor(message, unsupportedFeatures, options) {
31
+ super(message, options);
32
+ this.unsupportedFeatures = unsupportedFeatures;
33
+ this.name = 'OpenApiDowngradeError';
34
+ }
35
+ }
36
+ exports.OpenApiDowngradeError = OpenApiDowngradeError;
37
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";;;AA8BA,MAAa,mBAAoB,SAAQ,KAAK;IAG5C,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAW,qBAAqB,CAAC;QAOrD,IAAI,OAAO,IAAI,OAAO,IAAI,OAAO,EAAE;YACjC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;SAC5B;QACD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAbD,kDAaC;AAQD,MAAa,yBAA0B,SAAQ,mBAAmB;IAAlE;;QACoB,SAAI,GAAG,2BAA2B,CAAC;IACvD,CAAC;CAAA;AAFD,8DAEC;AAQD,MAAa,qBAAsB,SAAQ,mBAAmB;IAA9D;;QACoB,SAAI,GAAG,uBAAuB,CAAC;IACnD,CAAC;CAAA;AAFD,sDAEC;AAUD,MAAa,qBAAsB,SAAQ,mBAAmB;IAE5D,YACE,OAAe,EACC,mBAAsC,EACtD,OAA6B;QAE7B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAHR,wBAAmB,GAAnB,mBAAmB,CAAmB;QAHtC,SAAI,GAAG,uBAAuB,CAAC;IAOjD,CAAC;CACF;AATD,sDASC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { OpenApiVersionComponent } from './openapi-version.component';
2
2
  export { OpenApiVersionEnhancer } from './openapi-version.enhancer';
3
3
  export { OpenApiVersionBindings } from './keys';
4
- export { OpenApiVersion, OpenApiVersionConfig } from './types';
5
- export { transformOpenApiSpec, parseVersion, TransformResult, TransformWarning, } from './transform';
4
+ export type { OpenApiVersion, OpenApiVersionConfig } from './types';
5
+ export { transformOpenApiSpec, parseVersion } from './transform';
6
+ export type { TransformResult, TransformWarning } from './transform';
7
+ export { OpenApiVersionError, OpenApiVersionConfigError, OpenApiTransformError, OpenApiDowngradeError, } from './errors';
8
+ export type { OpenApiErrorOptions } from './errors';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseVersion = exports.transformOpenApiSpec = exports.OpenApiVersionBindings = exports.OpenApiVersionEnhancer = exports.OpenApiVersionComponent = void 0;
3
+ exports.OpenApiDowngradeError = exports.OpenApiTransformError = exports.OpenApiVersionConfigError = exports.OpenApiVersionError = exports.parseVersion = exports.transformOpenApiSpec = exports.OpenApiVersionBindings = exports.OpenApiVersionEnhancer = exports.OpenApiVersionComponent = void 0;
4
4
  var openapi_version_component_1 = require("./openapi-version.component");
5
5
  Object.defineProperty(exports, "OpenApiVersionComponent", { enumerable: true, get: function () { return openapi_version_component_1.OpenApiVersionComponent; } });
6
6
  var openapi_version_enhancer_1 = require("./openapi-version.enhancer");
@@ -10,4 +10,9 @@ Object.defineProperty(exports, "OpenApiVersionBindings", { enumerable: true, get
10
10
  var transform_1 = require("./transform");
11
11
  Object.defineProperty(exports, "transformOpenApiSpec", { enumerable: true, get: function () { return transform_1.transformOpenApiSpec; } });
12
12
  Object.defineProperty(exports, "parseVersion", { enumerable: true, get: function () { return transform_1.parseVersion; } });
13
+ var errors_1 = require("./errors");
14
+ Object.defineProperty(exports, "OpenApiVersionError", { enumerable: true, get: function () { return errors_1.OpenApiVersionError; } });
15
+ Object.defineProperty(exports, "OpenApiVersionConfigError", { enumerable: true, get: function () { return errors_1.OpenApiVersionConfigError; } });
16
+ Object.defineProperty(exports, "OpenApiTransformError", { enumerable: true, get: function () { return errors_1.OpenApiTransformError; } });
17
+ Object.defineProperty(exports, "OpenApiDowngradeError", { enumerable: true, get: function () { return errors_1.OpenApiDowngradeError; } });
13
18
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yEAAoE;AAA5D,oIAAA,uBAAuB,OAAA;AAC/B,uEAAkE;AAA1D,kIAAA,sBAAsB,OAAA;AAC9B,+BAA8C;AAAtC,8GAAA,sBAAsB,OAAA;AAE9B,yCAKqB;AAJnB,iHAAA,oBAAoB,OAAA;AACpB,yGAAA,YAAY,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAUA,yEAAoE;AAA5D,oIAAA,uBAAuB,OAAA;AAE/B,uEAAkE;AAA1D,kIAAA,sBAAsB,OAAA;AAE9B,+BAA8C;AAAtC,8GAAA,sBAAsB,OAAA;AAI9B,yCAA+D;AAAvD,iHAAA,oBAAoB,OAAA;AAAE,yGAAA,YAAY,OAAA;AAI1C,mCAKkB;AAJhB,6GAAA,mBAAmB,OAAA;AACnB,mHAAA,yBAAyB,OAAA;AACzB,+GAAA,qBAAqB,OAAA;AACrB,+GAAA,qBAAqB,OAAA"}
package/dist/keys.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BindingKey } from '@loopback/core';
2
- import { OpenApiVersionConfig } from './types';
2
+ import type { OpenApiVersionConfig } from './types';
3
3
  export declare namespace OpenApiVersionBindings {
4
4
  const CONFIG: BindingKey<OpenApiVersionConfig>;
5
5
  const COMPONENT = "openapi-version.component";
package/dist/keys.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":";;;AAAA,yCAA0C;AAG1C,IAAiB,sBAAsB,CAKtC;AALD,WAAiB,sBAAsB;IACxB,6BAAM,GAAG,iBAAU,CAAC,MAAM,CACrC,wBAAwB,CACzB,CAAC;IACW,gCAAS,GAAG,2BAA2B,CAAC;AACvD,CAAC,EALgB,sBAAsB,sCAAtB,sBAAsB,QAKtC"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":";;;AAAA,yCAA0C;AAQ1C,IAAiB,sBAAsB,CAgBtC;AAhBD,WAAiB,sBAAsB;IAMxB,6BAAM,GAAG,iBAAU,CAAC,MAAM,CACrC,wBAAwB,CACzB,CAAC;IAOW,gCAAS,GAAG,2BAA2B,CAAC;AACvD,CAAC,EAhBgB,sBAAsB,sCAAtB,sBAAsB,QAgBtC"}
@@ -1,4 +1,5 @@
1
- import { Binding, Component } from '@loopback/core';
1
+ import { Binding } from '@loopback/core';
2
+ import type { Component } from '@loopback/core';
2
3
  export declare class OpenApiVersionComponent implements Component {
3
4
  bindings: Binding[];
4
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openapi-version.component.js","sourceRoot":"","sources":["../src/openapi-version.component.ts"],"names":[],"mappings":";;;AAAA,yCAIwB;AACxB,qDAAoD;AACpD,iCAA8C;AAC9C,yEAAkE;AAClE,mCAAuC;AAavC,MAAa,uBAAuB;IAApC;QACE,aAAQ,GAAc;YACpB,cAAO,CAAC,IAAI,CAAC,6BAAsB,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,sBAAc,CAAC;YAC9D,IAAA,6BAAsB,EAAC,iDAAsB,CAAC,CAAC,KAAK,CAAC,2BAAc,CAAC;SACrE,CAAC;IACJ,CAAC;CAAA;AALD,0DAKC"}
1
+ {"version":3,"file":"openapi-version.component.js","sourceRoot":"","sources":["../src/openapi-version.component.ts"],"names":[],"mappings":";;;AAAA,yCAA+D;AAE/D,qDAAoD;AACpD,iCAA8C;AAC9C,yEAAkE;AAClE,mCAAuC;AAevC,MAAa,uBAAuB;IAApC;QACE,aAAQ,GAAc;YACpB,cAAO,CAAC,IAAI,CAAC,6BAAsB,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,sBAAc,CAAC;YAC9D,IAAA,6BAAsB,EAAC,iDAAsB,CAAC,CAAC,KAAK,CAAC,2BAAc,CAAC;SACrE,CAAC;IACJ,CAAC;CAAA;AALD,0DAKC"}
@@ -1 +1 @@
1
- {"version":3,"file":"openapi-version.enhancer.js","sourceRoot":"","sources":["../src/openapi-version.enhancer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,yCAAkD;AAKlD,iCAAiC;AACjC,iCAA8C;AAC9C,2CAAiD;AACjD,mCAA6D;AAE7D,MAAM,KAAK,GAAG,IAAA,eAAY,EAAC,0BAA0B,CAAC,CAAC;AAqBhD,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAGjC,YAEE,UAAwC,sBAAc;QAA9C,YAAO,GAAP,OAAO,CAAuC;QAJxD,SAAI,GAAG,iBAAiB,CAAC;IAKtB,CAAC;IAEJ,UAAU,CAAC,IAAiB;QAC1B,MAAM,IAAI,GAAG,EAAC,GAAG,sBAAc,EAAE,GAAG,IAAI,CAAC,OAAO,EAAC,CAAC;QAClD,KAAK,CACH,8DAA8D,EAC9D,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,iBAAiB,KAAK,KAAK,CACjC,CAAC;QACF,MAAM,MAAM,GAAG,IAAA,gCAAoB,EACjC,IAA+B,EAC/B,IAAI,CACL,CAAC;QAEF,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;YAC9B,KAAK,CAAC,2CAA2C,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC3E,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE;gBAC/B,KAAK,CAAC,kBAAkB,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;aAC/C;SACF;QAED,OAAO,MAAM,CAAC,IAAmB,CAAC;IACpC,CAAC;CACF,CAAA;AA7BY,wDAAsB;iCAAtB,sBAAsB;IADlC,IAAA,iBAAU,GAAE;IAKR,WAAA,IAAA,aAAM,EAAC,EAAC,WAAW,EAAE,6BAAsB,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAC,CAAC,CAAA;;GAJ5D,sBAAsB,CA6BlC"}
1
+ {"version":3,"file":"openapi-version.enhancer.js","sourceRoot":"","sources":["../src/openapi-version.enhancer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,yCAAkD;AAElD,iCAAiC;AACjC,iCAA8C;AAC9C,2CAAiD;AACjD,mCAA6D;AAE7D,MAAM,KAAK,GAAG,IAAA,eAAY,EAAC,0BAA0B,CAAC,CAAC;AAuBhD,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAGjC,YAEE,UAAwC,sBAAc;QAA9C,YAAO,GAAP,OAAO,CAAuC;QAJxD,SAAI,GAAG,iBAAiB,CAAC;IAKtB,CAAC;IAEJ,UAAU,CAAC,IAAiB;QAC1B,MAAM,IAAI,GAAG,EAAC,GAAG,sBAAc,EAAE,GAAG,IAAI,CAAC,OAAO,EAAC,CAAC;QAClD,KAAK,CACH,8DAA8D,EAC9D,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,iBAAiB,KAAK,KAAK,CACjC,CAAC;QACF,MAAM,MAAM,GAAG,IAAA,gCAAoB,EAAC,IAA+B,EAAE,IAAI,CAAC,CAAC;QAE3E,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;YAC9B,KAAK,CACH,2CAA2C,EAC3C,MAAM,CAAC,QAAQ,CAAC,MAAM,CACvB,CAAC;YACF,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE;gBAC/B,KAAK,CAAC,kBAAkB,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;aAC/C;SACF;QAED,OAAO,MAAM,CAAC,IAAmB,CAAC;IACpC,CAAC;CACF,CAAA;AA7BY,wDAAsB;iCAAtB,sBAAsB;IADlC,IAAA,iBAAU,GAAE;IAKR,WAAA,IAAA,aAAM,EAAC,EAAC,WAAW,EAAE,6BAAsB,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAC,CAAC,CAAA;;GAJ5D,sBAAsB,CA6BlC"}
@@ -1,4 +1,4 @@
1
- import { OpenApiVersionConfig } from './types';
1
+ import type { OpenApiVersionConfig } from './types';
2
2
  interface Obj {
3
3
  [key: string]: unknown;
4
4
  }
package/dist/transform.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transformOpenApiSpec = exports.parseVersion = void 0;
4
+ const errors_1 = require("./errors");
4
5
  const types_1 = require("./types");
5
6
  const SUPPORTED_MINORS = [0, 1, 2];
6
7
  function isSupportedMinor(minor) {
@@ -16,23 +17,23 @@ function warnOnce(warnings, seen, field, message) {
16
17
  function parseVersion(version) {
17
18
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
18
19
  if (!match) {
19
- throw new Error(`Invalid OpenAPI version: "${version}". Expected format: "3.x.x"`);
20
+ throw new errors_1.OpenApiVersionConfigError(`Invalid OpenAPI version: "${version}". Expected format: "3.x.x"`);
20
21
  }
21
22
  const major = Number(match[1]);
22
23
  const minor = Number(match[2]);
23
24
  const patch = Number(match[3]);
24
25
  if (major !== 3) {
25
- throw new Error(`Unsupported OpenAPI major version: ${major}. Only version 3.x is supported.`);
26
+ throw new errors_1.OpenApiVersionConfigError(`Unsupported OpenAPI major version: ${major}. Only version 3.x is supported.`);
26
27
  }
27
28
  if (!isSupportedMinor(minor)) {
28
- throw new Error(`Unsupported OpenAPI minor version: 3.${minor}. Supported: 3.0.x, 3.1.x, 3.2.x`);
29
+ throw new errors_1.OpenApiVersionConfigError(`Unsupported OpenAPI minor version: 3.${minor}. Supported: 3.0.x, 3.1.x, 3.2.x`);
29
30
  }
30
31
  return { major, minor, patch };
31
32
  }
32
33
  exports.parseVersion = parseVersion;
33
34
  function transformOpenApiSpec(spec, config = types_1.DEFAULT_CONFIG) {
34
35
  if (typeof spec.openapi !== 'string') {
35
- throw new Error('Invalid OpenAPI spec: missing required string field "openapi".');
36
+ throw new errors_1.OpenApiTransformError('Invalid OpenAPI spec: missing required string field "openapi".');
36
37
  }
37
38
  const sourceVersion = parseVersion(spec.openapi);
38
39
  const targetVersion = parseVersion(config.version);
@@ -45,7 +46,9 @@ function transformOpenApiSpec(spec, config = types_1.DEFAULT_CONFIG) {
45
46
  if (sourceMinor === targetMinor) {
46
47
  return { spec: out, warnings };
47
48
  }
48
- if (config.transformNullable !== false && targetMinor >= 1 && sourceMinor < 1) {
49
+ if (config.transformNullable !== false &&
50
+ targetMinor >= 1 &&
51
+ sourceMinor < 1) {
49
52
  upgradeNullable(out);
50
53
  }
51
54
  if (targetMinor < 2 && sourceMinor >= 2) {
@@ -98,8 +101,10 @@ function downgradeNullable(spec, warnings, seen) {
98
101
  if (!Array.isArray(s[key]))
99
102
  continue;
100
103
  const arr = s[key];
101
- const nullIdx = arr.findIndex(item => typeof item === 'object' && item !== null &&
102
- Object.keys(item).length === 1 && item.type === 'null');
104
+ const nullIdx = arr.findIndex(item => typeof item === 'object' &&
105
+ item !== null &&
106
+ Object.keys(item).length === 1 &&
107
+ item.type === 'null');
103
108
  if (nullIdx !== -1) {
104
109
  arr.splice(nullIdx, 1);
105
110
  s.nullable = true;
@@ -189,7 +194,9 @@ function strip32Features(spec, targetMinor, warnings, seen) {
189
194
  for (const tag of spec.tags) {
190
195
  if (tag && typeof tag === 'object') {
191
196
  const t = tag;
192
- if (t.summary !== undefined || t.parent !== undefined || t.kind !== undefined) {
197
+ if (t.summary !== undefined ||
198
+ t.parent !== undefined ||
199
+ t.kind !== undefined) {
193
200
  delete t.summary;
194
201
  delete t.parent;
195
202
  delete t.kind;
@@ -307,7 +314,9 @@ function walkAllSchemas(spec, visitor) {
307
314
  visit(p);
308
315
  }
309
316
  }
310
- if (schema.items && typeof schema.items === 'object' && !('$ref' in schema.items)) {
317
+ if (schema.items &&
318
+ typeof schema.items === 'object' &&
319
+ !('$ref' in schema.items)) {
311
320
  visit(schema.items);
312
321
  }
313
322
  for (const key of ['allOf', 'oneOf', 'anyOf', 'prefixItems']) {
@@ -318,7 +327,9 @@ function walkAllSchemas(spec, visitor) {
318
327
  }
319
328
  }
320
329
  }
321
- if (schema.not && typeof schema.not === 'object' && !('$ref' in schema.not)) {
330
+ if (schema.not &&
331
+ typeof schema.not === 'object' &&
332
+ !('$ref' in schema.not)) {
322
333
  visit(schema.not);
323
334
  }
324
335
  if (schema.additionalProperties &&
@@ -332,10 +343,14 @@ function walkAllSchemas(spec, visitor) {
332
343
  const media = content[mt];
333
344
  if (!media || typeof media !== 'object')
334
345
  continue;
335
- if (media.schema && typeof media.schema === 'object' && !('$ref' in media.schema)) {
346
+ if (media.schema &&
347
+ typeof media.schema === 'object' &&
348
+ !('$ref' in media.schema)) {
336
349
  visit(media.schema);
337
350
  }
338
- if (media.itemSchema && typeof media.itemSchema === 'object' && !('$ref' in media.itemSchema)) {
351
+ if (media.itemSchema &&
352
+ typeof media.itemSchema === 'object' &&
353
+ !('$ref' in media.itemSchema)) {
339
354
  visit(media.itemSchema);
340
355
  }
341
356
  }
@@ -345,7 +360,9 @@ function walkAllSchemas(spec, visitor) {
345
360
  if (!p || typeof p !== 'object' || '$ref' in p)
346
361
  continue;
347
362
  const param = p;
348
- if (param.schema && typeof param.schema === 'object' && !('$ref' in param.schema)) {
363
+ if (param.schema &&
364
+ typeof param.schema === 'object' &&
365
+ !('$ref' in param.schema)) {
349
366
  visit(param.schema);
350
367
  }
351
368
  if (param.content && typeof param.content === 'object') {
@@ -369,7 +386,9 @@ function walkAllSchemas(spec, visitor) {
369
386
  const p = params[name];
370
387
  if (p && typeof p === 'object' && !('$ref' in p)) {
371
388
  const param = p;
372
- if (param.schema && typeof param.schema === 'object' && !('$ref' in param.schema)) {
389
+ if (param.schema &&
390
+ typeof param.schema === 'object' &&
391
+ !('$ref' in param.schema)) {
373
392
  visit(param.schema);
374
393
  }
375
394
  }
@@ -404,7 +423,9 @@ function walkAllSchemas(spec, visitor) {
404
423
  if (!h || typeof h !== 'object' || '$ref' in h)
405
424
  continue;
406
425
  const header = h;
407
- if (header.schema && typeof header.schema === 'object' && !('$ref' in header.schema)) {
426
+ if (header.schema &&
427
+ typeof header.schema === 'object' &&
428
+ !('$ref' in header.schema)) {
408
429
  visit(header.schema);
409
430
  }
410
431
  }
@@ -419,7 +440,17 @@ function walkAllSchemas(spec, visitor) {
419
440
  if (Array.isArray(pi.parameters)) {
420
441
  visitParams(pi.parameters);
421
442
  }
422
- const verbs = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace', 'query'];
443
+ const verbs = [
444
+ 'get',
445
+ 'post',
446
+ 'put',
447
+ 'patch',
448
+ 'delete',
449
+ 'options',
450
+ 'head',
451
+ 'trace',
452
+ 'query',
453
+ ];
423
454
  for (const verb of verbs) {
424
455
  const op = pi[verb];
425
456
  if (!op || typeof op !== 'object')
@@ -465,7 +496,17 @@ function walkAllSchemas(spec, visitor) {
465
496
  }
466
497
  }
467
498
  function forEachOperation(spec, visitor) {
468
- const verbs = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace', 'query'];
499
+ const verbs = [
500
+ 'get',
501
+ 'post',
502
+ 'put',
503
+ 'patch',
504
+ 'delete',
505
+ 'options',
506
+ 'head',
507
+ 'trace',
508
+ 'query',
509
+ ];
469
510
  function visitPathItems(pathsObj, prefix) {
470
511
  for (const path in pathsObj) {
471
512
  const item = pathsObj[path];