diff --git a/CHANGELOG.md b/CHANGELOG.md index 98e326d..8983113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## 0.4.0 (2026-08-10) + +### Features + +* **oas**: support OAS 3.1 and 3.2 in getConfiguration (OAS31/OAS32 configurations) +* **amf**: bump amf-client-js from 5.10.2 to 5.11.12531 (exposes OAS31/OAS32, AMF team fixes) + ## 0.3.0 (2026-04-07) ### Features diff --git a/index.js b/index.js index 7392032..8a692fe 100644 --- a/index.js +++ b/index.js @@ -75,6 +75,10 @@ function getConfiguration(type) { case 'OAS 3.0': case 'OAS 3': return OASConfiguration.OAS30(); + case 'OAS 3.1': + return OASConfiguration.OAS31(); + case 'OAS 3.2': + return OASConfiguration.OAS32(); case 'ASYNC 2.0': return AsyncAPIConfiguration.Async20(); case 'GRPC': return GRPCConfiguration.GRPC(); default: throw new Error(`Unknown API type: ${type}`); diff --git a/package-lock.json b/package-lock.json index bfbfa6f..81d4234 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@api-components/api-model-generator", - "version": "0.3.1", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@api-components/api-model-generator", - "version": "0.3.1", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { - "amf-client-js": "^5.10.2", + "amf-client-js": "5.11.12531", "fs-extra": "^10.0.0" }, "devDependencies": { @@ -25,9 +25,9 @@ } }, "node_modules/@aml-org/amf-antlr-parsers": { - "version": "0.8.35", - "resolved": "https://registry.npmjs.org/@aml-org/amf-antlr-parsers/-/amf-antlr-parsers-0.8.35.tgz", - "integrity": "sha512-M7rqnyRy71Jj9I7pepmEpxLSfBYV/EeaMshGrUdocMtc252cijBmyWcLDt4jdLYXumuXSCyDokz/derq/ZEJmg==", + "version": "0.9.154", + "resolved": "https://registry.npmjs.org/@aml-org/amf-antlr-parsers/-/amf-antlr-parsers-0.9.154.tgz", + "integrity": "sha512-eXptBQVW/EuCiRAXl1qtOeMBQS9fFJKqMOH9b/PdvSAnIIFmrg/itqKsUdpnFRxFd0o9NYthbM/8Vc8sViBFQw==", "license": "ISC" }, "node_modules/@eslint-community/eslint-utils": { @@ -351,6 +351,7 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -363,20 +364,82 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/amf-client-js": { - "version": "5.10.2", - "resolved": "https://registry.npmjs.org/amf-client-js/-/amf-client-js-5.10.2.tgz", - "integrity": "sha512-g8QARWMl94WsO55YHtowoEJKWjIfxgk6QZH9+CGuCxLLzrCWpC4YVZh6hb03EqBfAcCXfYu/8Q+cB8tx1WGkrg==", + "version": "5.11.12531", + "resolved": "https://registry.npmjs.org/amf-client-js/-/amf-client-js-5.11.12531.tgz", + "integrity": "sha512-zAc/PwsOfsNzgii7LAsuJrYx+qXUBlOkd3Mwz3YD9r+jZjpJkEeDmxtb+N4F+Z5dy/g3i6J0kMfgeVYWCUOwtA==", "license": "Apache-2.0", "dependencies": { - "@aml-org/amf-antlr-parsers": "0.8.35", - "ajv": "6.14.0", - "avro-js": "1.11.3" + "@aml-org/amf-antlr-parsers": "0.9.154", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "avro-js": "1.11.5" }, "bin": { "amf": "bin/amf" } }, + "node_modules/amf-client-js/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/amf-client-js/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -421,9 +484,9 @@ } }, "node_modules/avro-js": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/avro-js/-/avro-js-1.11.3.tgz", - "integrity": "sha512-B1b0wI5iwSkVwj3RQWRzW99/LGoYl6df9j1kWime8r8b0dXCdKU7t7mEOFkOpQFaT/I9JXaL7KAIxM+/3TUk1A==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/avro-js/-/avro-js-1.11.5.tgz", + "integrity": "sha512-oevfVBGmLFF8CgUXnrw50Y7NKQZtsTtJIVOfa8b7cbQVmNcXSOzZLwjhM7a3EHdExU2sMP3WRiq2XD9kMLre4g==", "license": "Apache-2.0", "dependencies": { "underscore": "^1.13.2" @@ -871,6 +934,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -880,6 +944,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -1263,6 +1343,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -1661,6 +1742,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1721,6 +1803,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2003,6 +2094,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" diff --git a/package.json b/package.json index b05b5a0..1913c67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@api-components/api-model-generator", - "version": "0.3.1", + "version": "0.4.0", "description": "AMF model generator for API components", "main": "index.js", "scripts": { @@ -16,7 +16,7 @@ "Francisco Di Giandomenico" ], "dependencies": { - "amf-client-js": "^5.10.2", + "amf-client-js": "5.11.12531", "fs-extra": "^10.0.0" }, "devDependencies": { diff --git a/test/api-generation.test.js b/test/api-generation.test.js index 97f910e..20c7fb7 100644 --- a/test/api-generation.test.js +++ b/test/api-generation.test.js @@ -321,6 +321,95 @@ describe('API generation', () => { }); }); + describe('OAS 3.1 data model generation', () => { + let files; + let opts; + + const modelFile = path.join(dest, 'oas31-webhooks.json'); + const compactModelFile = path.join(dest, 'oas31-webhooks-compact.json'); + const config = { 'type': 'OAS 3.1', 'mime': 'application/yaml' }; + + beforeEach(() => { + files = new Map(); + opts = { + src: srcDir, + dest, + }; + }); + + afterEach(() => fs.remove(dest)); + + it('Generates data model for regular model', async () => { + files.set('apis/oas31-webhooks.yaml', config); + await generator(files, opts); + const exists = await fs.pathExists(modelFile); + assert.isTrue(exists, 'model file exists'); + const data = await fs.readJson(modelFile); + assertValidAmfModel(data); + }); + + it('Generates data model for compact model', async () => { + files.set('apis/oas31-webhooks.yaml', config); + await generator(files, opts); + const exists = await fs.pathExists(compactModelFile); + assert.isTrue(exists, 'model file exists'); + const data = await fs.readJson(compactModelFile); + assertValidAmfModel(data); + }); + + it('Emits webhooks as first-class operations', async () => { + files.set('apis/oas31-webhooks.yaml', config); + await generator(files, opts); + const raw = await fs.readFile(compactModelFile, 'utf8'); + assert.include(raw, 'Webhook', 'compact model references a Webhook'); + }); + }); + + describe('OAS 3.2 data model generation', () => { + let files; + let opts; + + const modelFile = path.join(dest, 'oas32-query-sse.json'); + const compactModelFile = path.join(dest, 'oas32-query-sse-compact.json'); + const config = { 'type': 'OAS 3.2', 'mime': 'application/yaml' }; + + beforeEach(() => { + files = new Map(); + opts = { + src: srcDir, + dest, + }; + }); + + afterEach(() => fs.remove(dest)); + + it('Generates data model for regular model', async () => { + files.set('apis/oas32-query-sse.yaml', config); + await generator(files, opts); + const exists = await fs.pathExists(modelFile); + assert.isTrue(exists, 'model file exists'); + const data = await fs.readJson(modelFile); + assertValidAmfModel(data); + }); + + it('Generates data model for compact model', async () => { + files.set('apis/oas32-query-sse.yaml', config); + await generator(files, opts); + const exists = await fs.pathExists(compactModelFile); + assert.isTrue(exists, 'model file exists'); + const data = await fs.readJson(compactModelFile); + assertValidAmfModel(data); + }); + + it('Parses the QUERY method and SSE media type', async () => { + files.set('apis/oas32-query-sse.yaml', config); + await generator(files, opts); + const raw = await fs.readFile(compactModelFile, 'utf8'); + assert.include(raw, 'QUERY', 'compact model references the QUERY method'); + assert.include(raw, 'event-stream', 'compact model references the SSE media type'); + }); + }); + describe('generator.generate()', () => { let opts; diff --git a/test/apis/oas31-webhooks.yaml b/test/apis/oas31-webhooks.yaml new file mode 100644 index 0000000..6e19b25 --- /dev/null +++ b/test/apis/oas31-webhooks.yaml @@ -0,0 +1,70 @@ +openapi: 3.1.0 +info: + title: OAS 3.1 Webhooks + Schema Composition + version: 1.0.0 +paths: + /pets: + get: + operationId: listPets + summary: List pets + responses: + "200": + description: A list of pets + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" + +# Top-level `webhooks` map of Path Item Objects (OAS 3.1.0, sibling to paths). +webhooks: + newPet: + post: + operationId: newPetWebhook + summary: Notify subscriber of a newly created pet + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + "200": + description: Webhook received successfully + +components: + schemas: + Pet: + allOf: + - $ref: "#/components/schemas/PetBase" + - $ref: "#/components/schemas/PetKindFields" + + PetBase: + type: object + required: [id, name] + properties: + id: + type: integer + name: + type: string + kind: + type: string + enum: [dog, cat] + + # if/then/else conditional (JSON Schema 2020-12 dialect used by OAS 3.1) + PetKindFields: + if: + properties: + kind: + const: dog + then: + properties: + breed: + type: string + required: [breed] + else: + properties: + indoor: + type: boolean + required: [indoor] diff --git a/test/apis/oas32-query-sse.yaml b/test/apis/oas32-query-sse.yaml new file mode 100644 index 0000000..b67de5a --- /dev/null +++ b/test/apis/oas32-query-sse.yaml @@ -0,0 +1,63 @@ +openapi: 3.2.0 +info: + title: OAS 3.2 QUERY Method + SSE Streaming + version: 1.0.0 +paths: + /pets: + # `query` is a Path Item Object fixed field (OAS 3.2.0): a QUERY operation + # that is safe/idempotent but carries a request body (unlike GET). + query: + operationId: queryPets + summary: Search pets using a structured filter body + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + kind: + type: string + maxAge: + type: integer + responses: + "200": + description: Matching pets + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + + /pets/events: + get: + operationId: streamPetEvents + summary: Stream pet lifecycle events via Server-Sent Events + responses: + "200": + description: A live stream of pet events + content: + # `itemSchema` on the Media Type Object (OAS 3.2.0) describes each + # event in a streaming media type; text/event-stream is the SSE case. + text/event-stream: + itemSchema: + type: object + required: [event] + properties: + event: + type: string + enum: [petCreated, petUpdated, petDeleted] + data: + contentMediaType: application/json + contentSchema: + type: object + required: [id] + properties: + id: + type: integer diff --git a/types.d.ts b/types.d.ts index cd573f9..1bd7e5a 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,4 +1,4 @@ -export declare type ApiType = 'RAML 1.0' | 'RAML 0.8' | 'OAS 2.0' | 'OAS 3.0' | 'ASYNC 2.0' | 'GRPC'; +export declare type ApiType = 'RAML 1.0' | 'RAML 0.8' | 'OAS 2.0' | 'OAS 3.0' | 'OAS 3.1' | 'OAS 3.2' | 'ASYNC 2.0' | 'GRPC'; export declare interface ApiConfiguration { /**