From 186f56a9fd08945a3878196e7564afbc67217acb Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Wed, 22 May 2024 18:10:43 +0200 Subject: [PATCH 01/10] fix: add examples for versioning --- .../README.md | 24 +++++++ .../api-definitions-v1.json | 66 +++++++++++++++++++ .../api-definitions-v2.json | 42 ++++++++++++ .../package.json | 29 ++++++++ .../server-without-versioning.js | 37 +++++++++++ .../server/v1/resolvers.js | 20 ++++++ .../server/v2/resolvers.js | 13 ++++ .../versioning-external-inherits.js | 60 +++++++++++++++++ .../versioning-only-parent.js | 49 ++++++++++++++ .../versioning-plugin-inherits.js | 60 +++++++++++++++++ 10 files changed, 400 insertions(+) create mode 100644 packages/bautajs-fastify-versioning-example/README.md create mode 100644 packages/bautajs-fastify-versioning-example/api-definitions-v1.json create mode 100644 packages/bautajs-fastify-versioning-example/api-definitions-v2.json create mode 100644 packages/bautajs-fastify-versioning-example/package.json create mode 100644 packages/bautajs-fastify-versioning-example/server-without-versioning.js create mode 100644 packages/bautajs-fastify-versioning-example/server/v1/resolvers.js create mode 100644 packages/bautajs-fastify-versioning-example/server/v2/resolvers.js create mode 100644 packages/bautajs-fastify-versioning-example/versioning-external-inherits.js create mode 100644 packages/bautajs-fastify-versioning-example/versioning-only-parent.js create mode 100644 packages/bautajs-fastify-versioning-example/versioning-plugin-inherits.js diff --git a/packages/bautajs-fastify-versioning-example/README.md b/packages/bautajs-fastify-versioning-example/README.md new file mode 100644 index 0000000..11eec14 --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/README.md @@ -0,0 +1,24 @@ +# bautajs-fastify-versioning-example + +- This API example intends to show how you can manage versioning with bautajs-fastify. +- This project example purpose is to showcase main features using simple examples. +- This project example **does not** intend to show good practices using Node.js or security practices at all. Please be sure you follow security good practices on your Node.js API (i.e. adding [helmet](@fastify/helmet)). + +## How to start + +- It is recommented that you are using node v18. +- `npm install` from the root project of the monorepo +- enter into `packages/bautajs-fastify-example` folder + +## Brief explanation of the different examples + +TODO + + +## Third party dependencies licenses + +### Production + - [@axa/bautajs-core@3.0.0](https://github.com/axa-group/bauta.js) - MIT* + - [@axa/bautajs-datasource-rest@3.0.0](https://github.com/axa-group/bauta.js) - MIT* + - [@axa/bautajs-fastify@3.0.0](https://github.com/axa-group/bauta.js) - MIT* + - [fastify@4.8.1](https://github.com/fastify/fastify) - MIT diff --git a/packages/bautajs-fastify-versioning-example/api-definitions-v1.json b/packages/bautajs-fastify-versioning-example/api-definitions-v1.json new file mode 100644 index 0000000..a50b014 --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/api-definitions-v1.json @@ -0,0 +1,66 @@ +{ + "openapi": "3.0.0", + "info": { + "description": "A new API", + "version": "v1", + "title": "Cats API" + }, + "servers": [ + { + "url": "/v1/api/" + } + ], + "paths": { + "/cats": { + "get": { + "operationId": "findCat", + "responses": { + "200": { + "description": "Miau!", + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "type": "string" + }, + "communication": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/dogs": { + "get": { + "operationId": "findDog", + "responses": { + "200": { + "description": "Woof!", + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "type": "string" + }, + "communication": { + "type": "string" + }, + "breed": { + "type": "string" + } + } + } + } + } + } + } + } + } + } +} diff --git a/packages/bautajs-fastify-versioning-example/api-definitions-v2.json b/packages/bautajs-fastify-versioning-example/api-definitions-v2.json new file mode 100644 index 0000000..6784ba7 --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/api-definitions-v2.json @@ -0,0 +1,42 @@ +{ + "openapi": "3.0.0", + "info": { + "description": "A new API", + "version": "v2", + "title": "Cats API" + }, + "servers": [ + { + "url": "/v2/api/" + } + ], + "paths": { + "/cats": { + "get": { + "operationId": "findCatV2", + "responses": { + "200": { + "description": "Miaw!", + "content": { + "application/json": { + "schema": { + "properties": { + "petName": { + "type": "string" + }, + "communication": { + "type": "string" + }, + "action:": { + "type": "string" + } + } + } + } + } + } + } + } + } + } +} diff --git a/packages/bautajs-fastify-versioning-example/package.json b/packages/bautajs-fastify-versioning-example/package.json new file mode 100644 index 0000000..f8f4751 --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/package.json @@ -0,0 +1,29 @@ +{ + "name": "@axa/bautajs-fastify-versioning-example", + "version": "1.0.0", + "description": "A bautaJS example of versioning in fastify", + "main": "./server-without-versioning.js", + "scripts": { + "start": "LOG_LEVEL=info DEBUG=bautajs* node ./server-without-versioning.js", + "start:parent": "LOG_LEVEL=info DEBUG=bautajs* node ./versioning-only-parent.js", + "start:plugin": "LOG_LEVEL=info DEBUG=bautajs* node ./versioning-plugin-inherits.js", + "start:external": "LOG_LEVEL=info DEBUG=bautajs* node ./versioning-external-inherits.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/axa-group/bauta.js" + }, + "private": true, + "keywords": [ + "bautajs", + "fastify", + "middleware" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "dependencies": { + "@axa/bautajs-core": "^3.1.0", + "@axa/bautajs-datasource-rest": "^3.1.0", + "@axa/bautajs-fastify": "^3.1.0", + "fastify": "^4.15.0" + } +} diff --git a/packages/bautajs-fastify-versioning-example/server-without-versioning.js b/packages/bautajs-fastify-versioning-example/server-without-versioning.js new file mode 100644 index 0000000..40e0356 --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/server-without-versioning.js @@ -0,0 +1,37 @@ +const fastify = require('fastify')({ logger: true }); + +const { bautajsFastify } = require('@axa/bautajs-fastify'); +const { BautaJS } = require('@axa/bautajs-core'); + +const apiDefinitionsV1 = require('./api-definitions-v1.json'); + +const bautaJsV1 = new BautaJS({ + apiDefinition: apiDefinitionsV1, + resolversPath: './server/**/resolvers*.js', + staticConfig: { + someVar: 2 + }, + strictResponseSerialization: false, + validatorOptions: { + coerceTypes: 'array' + } +}); + +(async () => { + fastify.register(bautajsFastify, { + bautajsInstance: bautaJsV1, + prefix: '/v1/', + apiBasePath: '/api/' + }); + + fastify.listen( + { + host: '0.0.0.0', + port: 8080 + }, + err => { + if (err) throw err; + fastify.log.info('Server listening on localhost:', fastify.server.address().port); + } + ); +})(); diff --git a/packages/bautajs-fastify-versioning-example/server/v1/resolvers.js b/packages/bautajs-fastify-versioning-example/server/v1/resolvers.js new file mode 100644 index 0000000..b332a6b --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/server/v1/resolvers.js @@ -0,0 +1,20 @@ +const { pipe, resolver, step } = require('@axa/bautajs-core'); + +const getCatStep = step(() => { + return { + name: 'Grey', + communication: 'Meow' + }; +}); + +const getDogStep = step(() => { + return { + name: 'Milu', + communication: 'Woof' + }; +}); + +module.exports = resolver(operations => { + operations.findCat.validateRequest(true).validateResponse(true).setup(pipe(getCatStep)); + operations.findDog.validateRequest(true).validateResponse(true).setup(pipe(getDogStep)); +}); diff --git a/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js b/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js new file mode 100644 index 0000000..d9b5861 --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js @@ -0,0 +1,13 @@ +const { pipe, resolver, step } = require('@axa/bautajs-core'); + +const getCatStep = step(() => { + return { + petName: 'Grey', + communication: 'Meow', + action: 'Purr' + }; +}); + +module.exports = resolver(operations => { + operations.findCatV2.validateRequest(true).validateResponse(true).setup(pipe(getCatStep)); +}); diff --git a/packages/bautajs-fastify-versioning-example/versioning-external-inherits.js b/packages/bautajs-fastify-versioning-example/versioning-external-inherits.js new file mode 100644 index 0000000..bf64e3e --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/versioning-external-inherits.js @@ -0,0 +1,60 @@ +const fastify = require('fastify')({ logger: true }); + +const { bautajsFastify } = require('@axa/bautajs-fastify'); +const { BautaJS } = require('@axa/bautajs-core'); + +const apiDefinitionsV1 = require('./api-definitions-v1.json'); + +const apiDefinitionsV2 = require('./api-definitions-v2.json'); + +const bautaJsV1 = new BautaJS({ + apiDefinition: apiDefinitionsV1, + resolversPath: './server/v1/resolvers*.js', + staticConfig: { + someVar: 2 + }, + strictResponseSerialization: false, + validatorOptions: { + coerceTypes: 'array' + } +}); + +const bautaJsV2 = new BautaJS({ + apiDefinition: apiDefinitionsV2, + resolversPath: './server/v2/resolvers*.js', + staticConfig: { + someVar: 2 + }, + strictResponseSerialization: false, + validatorOptions: { + coerceTypes: 'array' + } +}); + +(async () => { + fastify + .register(bautajsFastify, { + bautajsInstance: bautaJsV1, + prefix: '/v1/', + apiBasePath: '/api/' + }) + .after(() => { + bautaJsV2.inheritOperationsFrom(bautaJsV1); + fastify.register(bautajsFastify, { + bautajsInstance: bautaJsV2, + prefix: '/v2/', + apiBasePath: '/api/' + }); + }); + + fastify.listen( + { + host: '0.0.0.0', + port: 8181 + }, + err => { + if (err) throw err; + fastify.log.info('Server listening on localhost:', fastify.server.address().port); + } + ); +})(); diff --git a/packages/bautajs-fastify-versioning-example/versioning-only-parent.js b/packages/bautajs-fastify-versioning-example/versioning-only-parent.js new file mode 100644 index 0000000..9b43e73 --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/versioning-only-parent.js @@ -0,0 +1,49 @@ +const fastify = require('fastify')({ logger: true }); + +const { bautajsFastify } = require('@axa/bautajs-fastify'); +const { BautaJS } = require('@axa/bautajs-core'); + +const apiDefinitionsV1 = require('./api-definitions-v1.json'); + +const apiDefinitionsV2 = require('./api-definitions-v2.json'); + +const bautaJsV1 = new BautaJS({ + apiDefinition: apiDefinitionsV1, + resolversPath: './server/v1/resolvers*.js', + staticConfig: { + someVar: 2 + }, + strictResponseSerialization: false, + validatorOptions: { + coerceTypes: 'array' + } +}); + +(async () => { + fastify + .register(bautajsFastify, { + bautajsInstance: bautaJsV1, + prefix: '/v1/', + apiBasePath: '/api/' + }) + .after(() => { + fastify.register(bautajsFastify, { + resolversPath: './server/v2/resolvers*.js', + apiDefinition: apiDefinitionsV2, + prefix: '/v2/', + apiBasePath: '/api/', + inheritOperationsFrom: bautaJsV1 + }); + }); + + fastify.listen( + { + host: '0.0.0.0', + port: 8383 + }, + err => { + if (err) throw err; + fastify.log.info('Server listening on localhost:', fastify.server.address().port); + } + ); +})(); diff --git a/packages/bautajs-fastify-versioning-example/versioning-plugin-inherits.js b/packages/bautajs-fastify-versioning-example/versioning-plugin-inherits.js new file mode 100644 index 0000000..1aa29da --- /dev/null +++ b/packages/bautajs-fastify-versioning-example/versioning-plugin-inherits.js @@ -0,0 +1,60 @@ +const fastify = require('fastify')({ logger: true }); + +const { bautajsFastify } = require('@axa/bautajs-fastify'); +const { BautaJS } = require('@axa/bautajs-core'); + +const apiDefinitionsV1 = require('./api-definitions-v1.json'); + +const apiDefinitionsV2 = require('./api-definitions-v2.json'); + +const bautaJsV1 = new BautaJS({ + apiDefinition: apiDefinitionsV1, + resolversPath: './server/v1/resolvers*.js', + staticConfig: { + someVar: 2 + }, + strictResponseSerialization: false, + validatorOptions: { + coerceTypes: 'array' + } +}); + +const bautaJsV2 = new BautaJS({ + apiDefinition: apiDefinitionsV2, + resolversPath: './server/v2/resolvers*.js', + staticConfig: { + someVar: 2 + }, + strictResponseSerialization: false, + validatorOptions: { + coerceTypes: 'array' + } +}); + +(async () => { + fastify + .register(bautajsFastify, { + bautajsInstance: bautaJsV1, + prefix: '/v1/', + apiBasePath: '/api/' + }) + .after(() => { + fastify.register(bautajsFastify, { + prefix: '/v2/', + apiBasePath: '/api/', + inheritOperationsFrom: bautaJsV1, + bautajsInstance: bautaJsV2 + }); + }); + + fastify.listen( + { + host: '0.0.0.0', + port: 8282 + }, + err => { + if (err) throw err; + fastify.log.info('Server listening on localhost:', fastify.server.address().port); + } + ); +})(); From 34f2586193bcc6cb5241d992e2d90af0f67c723b Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Thu, 23 May 2024 10:05:51 +0200 Subject: [PATCH 02/10] fix: extra logs to show the issue clearly --- packages/bautajs-core/src/bauta.ts | 10 ++++++++++ packages/bautajs-fastify/src/index.ts | 3 +++ 2 files changed, 13 insertions(+) diff --git a/packages/bautajs-core/src/bauta.ts b/packages/bautajs-core/src/bauta.ts index 6d65193..9c7e76d 100644 --- a/packages/bautajs-core/src/bauta.ts +++ b/packages/bautajs-core/src/bauta.ts @@ -98,6 +98,7 @@ export class BautaJS implements BautaJSInstance { resolvers, validatorOptions }: BautaJSOptions = {}) { + console.log('constructor in bautajs-core started'); const api = apiDefinition ? prebuildApi(apiDefinition) : undefined; let responseValidation = false; let requestValidation = true; @@ -135,12 +136,16 @@ export class BautaJS implements BautaJSInstance { [this.operations] ); } + + console.log('constructor in bautajs-core finished', this.operations); } public async bootstrap(): Promise { + console.log('bootstrap in bautajs-core started'); if (this.bootstrapped === true) { throw new Error('The instance has already being bootstrapped.'); } + if (this.apiDefinition) { const parser = new Parser(this.logger); const parsedApiDefinition = await parser.asyncParse(this.apiDefinition); @@ -164,6 +169,8 @@ export class BautaJS implements BautaJSInstance { }, {}) ); this.bootstrapped = true; + + console.log('bootstrap in bautajs-core finished', this.operations); } public decorate(property: string | symbol, value: any, dependencies?: string[]) { @@ -263,6 +270,7 @@ export class BautaJS implements BautaJSInstance { } public inheritOperationsFrom(bautajsInstance: BautaJSInstance) { + console.log('inheritOperationsFrom in bautajs-core started'); if (!(bautajsInstance instanceof BautaJS)) { throw new Error('A bautaJS instance must be provided.'); } @@ -293,6 +301,8 @@ export class BautaJS implements BautaJSInstance { } } }); + + console.log('inheritOperationsFrom in bautajs-core finished', this.operations); return this; } } diff --git a/packages/bautajs-fastify/src/index.ts b/packages/bautajs-fastify/src/index.ts index ab20e47..2b0201c 100644 --- a/packages/bautajs-fastify/src/index.ts +++ b/packages/bautajs-fastify/src/index.ts @@ -26,16 +26,19 @@ export async function bautajsFastify( fastify: FastifyInstance, opts: BautaJSFastifyPluginOptions ): Promise; + export async function bautajsFastify( fastify: FastifyInstance, opts: BautaJSFastifyPluginOptions & bautaJS.BautaJSOptions ): Promise; + export async function bautajsFastify(fastify: FastifyInstance, opts: any) { const bautajs = opts.bautajsInstance ? opts.bautajsInstance : new bautaJS.BautaJS({ logger: fastify.log as bautaJS.Logger, ...opts }); if (opts.inheritOperationsFrom) { + console.log('habemus inheritOperationsFrom in bautajs-fastify'); bautajs.inheritOperationsFrom(opts.inheritOperationsFrom); } From bed922181dab5fed961f8de3c33a0e6b95b6f650 Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Thu, 23 May 2024 14:08:52 +0200 Subject: [PATCH 03/10] fix: experiment without freeze --- packages/bautajs-core/src/bauta.ts | 83 +++++++++++++++---- .../api-definitions-v1.json | 1 + .../api-definitions-v2.json | 2 +- .../server/v2/resolvers.js | 5 +- .../versioning-only-parent.js | 4 +- packages/bautajs-fastify/src/index.ts | 4 +- 6 files changed, 75 insertions(+), 24 deletions(-) diff --git a/packages/bautajs-core/src/bauta.ts b/packages/bautajs-core/src/bauta.ts index 9c7e76d..37a566b 100644 --- a/packages/bautajs-core/src/bauta.ts +++ b/packages/bautajs-core/src/bauta.ts @@ -10,7 +10,8 @@ import { Logger, Operations, BasicOperation, - Validator + Validator, + Route } from './types'; import { isLoggerValid } from './utils/logger-validator'; import Parser from './open-api/parser'; @@ -22,6 +23,23 @@ interface API { operations: BasicOperation[]; } +/** + * This utility function generates a identifier for a route that delimites its uniqueness + * at route level (regardless of the operationId). It is used to be sure if we should + * inherit or not a route from a parent instance. + * + * Note that its value has no meaning at routing level (it is not a valid url or method) + * + * @param route + * @returns + */ +function getRouteIdentifier(route: Route | undefined) { + if (!route) { + return ''; + } + return `${route.method}_${route.url}_${route.path}`; +} + function prebuildApi(apiDefinition: Document): API { try { return { @@ -98,7 +116,7 @@ export class BautaJS implements BautaJSInstance { resolvers, validatorOptions }: BautaJSOptions = {}) { - console.log('constructor in bautajs-core started'); + // console.log('constructor in bautajs-core started'); const api = apiDefinition ? prebuildApi(apiDefinition) : undefined; let responseValidation = false; let requestValidation = true; @@ -137,11 +155,11 @@ export class BautaJS implements BautaJSInstance { ); } - console.log('constructor in bautajs-core finished', this.operations); + // console.log('constructor in bautajs-core finished', this.operations); } public async bootstrap(): Promise { - console.log('bootstrap in bautajs-core started'); + // console.log('bootstrap in bautajs-core started'); if (this.bootstrapped === true) { throw new Error('The instance has already being bootstrapped.'); } @@ -161,16 +179,16 @@ export class BautaJS implements BautaJSInstance { } // This will prevent to create new operations after bootstrapping the bautajs instance. - this.operations = Object.freeze( - Object.entries(this.operations).reduce((acc: Operations, [key, val]) => { - acc[key] = val; + // this.operations = Object.freeze( + // Object.entries(this.operations).reduce((acc: Operations, [key, val]) => { + // acc[key] = val; - return acc; - }, {}) - ); - this.bootstrapped = true; + // return acc; + // }, {}) + // ); + // this.bootstrapped = true; - console.log('bootstrap in bautajs-core finished', this.operations); + // console.log('bootstrap in bautajs-core finished', this.operations); } public decorate(property: string | symbol, value: any, dependencies?: string[]) { @@ -270,24 +288,55 @@ export class BautaJS implements BautaJSInstance { } public inheritOperationsFrom(bautajsInstance: BautaJSInstance) { - console.log('inheritOperationsFrom in bautajs-core started'); + console.log('inheritOperationsFrom in bautajs-core started', this.operations); + + Object.values(this.operations).forEach(op => { + console.log( + 'MELON this.operation', + op.id, + 'route', + op.route, + 'op.identifier', + getRouteIdentifier(op.route) + ); + }); + if (!(bautajsInstance instanceof BautaJS)) { throw new Error('A bautaJS instance must be provided.'); } - if (this.bootstrapped === true) { - throw new Error('Operation inherit should be done before bootstrap the BautaJS instance.'); - } + // if (this.bootstrapped === true) { + // throw new Error('Operation inherit should be done before bootstrap the BautaJS instance.'); + // } if (bautajsInstance.bootstrapped === false) { this.logger.warn( 'The given instance is not bootstrapped, thus operation schema will be no inherited.' ); } + Object.keys(bautajsInstance.operations).forEach(operationId => { + // console.log('iterating parent operations', operationId); const operation = bautajsInstance.operations[operationId]; + + const routeIdentifier = getRouteIdentifier(operation.route); + + const routeAlreadyExists = !!Object.values(this.operations).find( + op => getRouteIdentifier(op.route) === routeIdentifier + ); + + console.log( + 'operationId', + operationId, + 'with route id', + routeIdentifier, + 'routeAlreadyExists', + routeAlreadyExists + ); if ( operation.deprecated !== true && - !Object.prototype.hasOwnProperty.call(this.operations, operationId) + !Object.prototype.hasOwnProperty.call(this.operations, operationId) && + !routeAlreadyExists ) { + console.log('XAVI INHERITING OPERATION', operationId); this.operations[operationId] = OperationBuilder.create(operation.id, this); this.operations[operationId].setup(operation.handler); this.operations[operationId].requestValidationEnabled = operation.requestValidationEnabled; diff --git a/packages/bautajs-fastify-versioning-example/api-definitions-v1.json b/packages/bautajs-fastify-versioning-example/api-definitions-v1.json index a50b014..10117be 100644 --- a/packages/bautajs-fastify-versioning-example/api-definitions-v1.json +++ b/packages/bautajs-fastify-versioning-example/api-definitions-v1.json @@ -14,6 +14,7 @@ "/cats": { "get": { "operationId": "findCat", + "deprecated": true, "responses": { "200": { "description": "Miau!", diff --git a/packages/bautajs-fastify-versioning-example/api-definitions-v2.json b/packages/bautajs-fastify-versioning-example/api-definitions-v2.json index 6784ba7..64a7fec 100644 --- a/packages/bautajs-fastify-versioning-example/api-definitions-v2.json +++ b/packages/bautajs-fastify-versioning-example/api-definitions-v2.json @@ -27,7 +27,7 @@ "communication": { "type": "string" }, - "action:": { + "favouriteAction:": { "type": "string" } } diff --git a/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js b/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js index d9b5861..9e6da2d 100644 --- a/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js +++ b/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js @@ -2,12 +2,13 @@ const { pipe, resolver, step } = require('@axa/bautajs-core'); const getCatStep = step(() => { return { - petName: 'Grey', + petName: 'Blondie', communication: 'Meow', - action: 'Purr' + favouriteAction: 'Purr' }; }); module.exports = resolver(operations => { operations.findCatV2.validateRequest(true).validateResponse(true).setup(pipe(getCatStep)); + // operations.findCat.validateRequest(true).validateResponse(true).setup(pipe(getCatStep)); }); diff --git a/packages/bautajs-fastify-versioning-example/versioning-only-parent.js b/packages/bautajs-fastify-versioning-example/versioning-only-parent.js index 9b43e73..a5a857c 100644 --- a/packages/bautajs-fastify-versioning-example/versioning-only-parent.js +++ b/packages/bautajs-fastify-versioning-example/versioning-only-parent.js @@ -9,7 +9,7 @@ const apiDefinitionsV2 = require('./api-definitions-v2.json'); const bautaJsV1 = new BautaJS({ apiDefinition: apiDefinitionsV1, - resolversPath: './server/v1/resolvers*.js', + resolversPath: './server/v1/resolvers.js', staticConfig: { someVar: 2 }, @@ -28,7 +28,7 @@ const bautaJsV1 = new BautaJS({ }) .after(() => { fastify.register(bautajsFastify, { - resolversPath: './server/v2/resolvers*.js', + resolversPath: './server/v2/resolvers.js', apiDefinition: apiDefinitionsV2, prefix: '/v2/', apiBasePath: '/api/', diff --git a/packages/bautajs-fastify/src/index.ts b/packages/bautajs-fastify/src/index.ts index 2b0201c..f3ba47b 100644 --- a/packages/bautajs-fastify/src/index.ts +++ b/packages/bautajs-fastify/src/index.ts @@ -37,13 +37,13 @@ export async function bautajsFastify(fastify: FastifyInstance, opts: any) { ? opts.bautajsInstance : new bautaJS.BautaJS({ logger: fastify.log as bautaJS.Logger, ...opts }); + await bautajs.bootstrap(); + if (opts.inheritOperationsFrom) { console.log('habemus inheritOperationsFrom in bautajs-fastify'); bautajs.inheritOperationsFrom(opts.inheritOperationsFrom); } - await bautajs.bootstrap(); - // Add x-request-id on the response await fastify.register(responseXRequestId, { prefix: opts.prefix }); From 053a9a6815f1d34dd90a9583e489f857d30f8488 Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Thu, 23 May 2024 14:22:29 +0200 Subject: [PATCH 04/10] fix: now it seems to be working as expected --- packages/bautajs-core/src/bauta.ts | 20 +++++++++---------- .../api-definitions-v1.json | 3 +++ .../api-definitions-v2.json | 2 +- .../server/v1/resolvers.js | 6 ++++-- .../server/v2/resolvers.js | 11 ++++++++-- packages/bautajs-fastify/src/index.ts | 4 ++-- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/packages/bautajs-core/src/bauta.ts b/packages/bautajs-core/src/bauta.ts index 37a566b..4edd1a9 100644 --- a/packages/bautajs-core/src/bauta.ts +++ b/packages/bautajs-core/src/bauta.ts @@ -179,14 +179,14 @@ export class BautaJS implements BautaJSInstance { } // This will prevent to create new operations after bootstrapping the bautajs instance. - // this.operations = Object.freeze( - // Object.entries(this.operations).reduce((acc: Operations, [key, val]) => { - // acc[key] = val; + this.operations = Object.freeze( + Object.entries(this.operations).reduce((acc: Operations, [key, val]) => { + acc[key] = val; - // return acc; - // }, {}) - // ); - // this.bootstrapped = true; + return acc; + }, {}) + ); + this.bootstrapped = true; // console.log('bootstrap in bautajs-core finished', this.operations); } @@ -304,9 +304,9 @@ export class BautaJS implements BautaJSInstance { if (!(bautajsInstance instanceof BautaJS)) { throw new Error('A bautaJS instance must be provided.'); } - // if (this.bootstrapped === true) { - // throw new Error('Operation inherit should be done before bootstrap the BautaJS instance.'); - // } + if (this.bootstrapped === true) { + throw new Error('Operation inherit should be done before bootstrap the BautaJS instance.'); + } if (bautajsInstance.bootstrapped === false) { this.logger.warn( 'The given instance is not bootstrapped, thus operation schema will be no inherited.' diff --git a/packages/bautajs-fastify-versioning-example/api-definitions-v1.json b/packages/bautajs-fastify-versioning-example/api-definitions-v1.json index 10117be..0c7f4fb 100644 --- a/packages/bautajs-fastify-versioning-example/api-definitions-v1.json +++ b/packages/bautajs-fastify-versioning-example/api-definitions-v1.json @@ -27,6 +27,9 @@ }, "communication": { "type": "string" + }, + "breed": { + "type": "string" } } } diff --git a/packages/bautajs-fastify-versioning-example/api-definitions-v2.json b/packages/bautajs-fastify-versioning-example/api-definitions-v2.json index 64a7fec..189203a 100644 --- a/packages/bautajs-fastify-versioning-example/api-definitions-v2.json +++ b/packages/bautajs-fastify-versioning-example/api-definitions-v2.json @@ -27,7 +27,7 @@ "communication": { "type": "string" }, - "favouriteAction:": { + "favouriteAction": { "type": "string" } } diff --git a/packages/bautajs-fastify-versioning-example/server/v1/resolvers.js b/packages/bautajs-fastify-versioning-example/server/v1/resolvers.js index b332a6b..354971c 100644 --- a/packages/bautajs-fastify-versioning-example/server/v1/resolvers.js +++ b/packages/bautajs-fastify-versioning-example/server/v1/resolvers.js @@ -3,14 +3,16 @@ const { pipe, resolver, step } = require('@axa/bautajs-core'); const getCatStep = step(() => { return { name: 'Grey', - communication: 'Meow' + communication: 'Meow', + breed: 'all cats are the same' }; }); const getDogStep = step(() => { return { name: 'Milu', - communication: 'Woof' + communication: 'Woof', + breed: 'fox-terrier' }; }); diff --git a/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js b/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js index 9e6da2d..116cf3b 100644 --- a/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js +++ b/packages/bautajs-fastify-versioning-example/server/v2/resolvers.js @@ -9,6 +9,13 @@ const getCatStep = step(() => { }); module.exports = resolver(operations => { - operations.findCatV2.validateRequest(true).validateResponse(true).setup(pipe(getCatStep)); - // operations.findCat.validateRequest(true).validateResponse(true).setup(pipe(getCatStep)); + operations.findCatV2 + .validateRequest(true) + .validateResponse(true) + .setup( + pipe(getCatStep, prev => { + console.log('mierda mia', prev); + return prev; + }) + ); }); diff --git a/packages/bautajs-fastify/src/index.ts b/packages/bautajs-fastify/src/index.ts index f3ba47b..2b0201c 100644 --- a/packages/bautajs-fastify/src/index.ts +++ b/packages/bautajs-fastify/src/index.ts @@ -37,13 +37,13 @@ export async function bautajsFastify(fastify: FastifyInstance, opts: any) { ? opts.bautajsInstance : new bautaJS.BautaJS({ logger: fastify.log as bautaJS.Logger, ...opts }); - await bautajs.bootstrap(); - if (opts.inheritOperationsFrom) { console.log('habemus inheritOperationsFrom in bautajs-fastify'); bautajs.inheritOperationsFrom(opts.inheritOperationsFrom); } + await bautajs.bootstrap(); + // Add x-request-id on the response await fastify.register(responseXRequestId, { prefix: opts.prefix }); From 6c0fd8b9232dabcf14f1c4132ea5f14ff14bb262 Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Thu, 23 May 2024 14:34:02 +0200 Subject: [PATCH 05/10] fix: remove extra logs --- packages/bautajs-core/src/bauta.ts | 59 +----------------------------- 1 file changed, 2 insertions(+), 57 deletions(-) diff --git a/packages/bautajs-core/src/bauta.ts b/packages/bautajs-core/src/bauta.ts index 4edd1a9..99852ee 100644 --- a/packages/bautajs-core/src/bauta.ts +++ b/packages/bautajs-core/src/bauta.ts @@ -10,8 +10,7 @@ import { Logger, Operations, BasicOperation, - Validator, - Route + Validator } from './types'; import { isLoggerValid } from './utils/logger-validator'; import Parser from './open-api/parser'; @@ -23,23 +22,6 @@ interface API { operations: BasicOperation[]; } -/** - * This utility function generates a identifier for a route that delimites its uniqueness - * at route level (regardless of the operationId). It is used to be sure if we should - * inherit or not a route from a parent instance. - * - * Note that its value has no meaning at routing level (it is not a valid url or method) - * - * @param route - * @returns - */ -function getRouteIdentifier(route: Route | undefined) { - if (!route) { - return ''; - } - return `${route.method}_${route.url}_${route.path}`; -} - function prebuildApi(apiDefinition: Document): API { try { return { @@ -116,7 +98,6 @@ export class BautaJS implements BautaJSInstance { resolvers, validatorOptions }: BautaJSOptions = {}) { - // console.log('constructor in bautajs-core started'); const api = apiDefinition ? prebuildApi(apiDefinition) : undefined; let responseValidation = false; let requestValidation = true; @@ -154,12 +135,9 @@ export class BautaJS implements BautaJSInstance { [this.operations] ); } - - // console.log('constructor in bautajs-core finished', this.operations); } public async bootstrap(): Promise { - // console.log('bootstrap in bautajs-core started'); if (this.bootstrapped === true) { throw new Error('The instance has already being bootstrapped.'); } @@ -187,8 +165,6 @@ export class BautaJS implements BautaJSInstance { }, {}) ); this.bootstrapped = true; - - // console.log('bootstrap in bautajs-core finished', this.operations); } public decorate(property: string | symbol, value: any, dependencies?: string[]) { @@ -288,19 +264,6 @@ export class BautaJS implements BautaJSInstance { } public inheritOperationsFrom(bautajsInstance: BautaJSInstance) { - console.log('inheritOperationsFrom in bautajs-core started', this.operations); - - Object.values(this.operations).forEach(op => { - console.log( - 'MELON this.operation', - op.id, - 'route', - op.route, - 'op.identifier', - getRouteIdentifier(op.route) - ); - }); - if (!(bautajsInstance instanceof BautaJS)) { throw new Error('A bautaJS instance must be provided.'); } @@ -314,29 +277,12 @@ export class BautaJS implements BautaJSInstance { } Object.keys(bautajsInstance.operations).forEach(operationId => { - // console.log('iterating parent operations', operationId); const operation = bautajsInstance.operations[operationId]; - const routeIdentifier = getRouteIdentifier(operation.route); - - const routeAlreadyExists = !!Object.values(this.operations).find( - op => getRouteIdentifier(op.route) === routeIdentifier - ); - - console.log( - 'operationId', - operationId, - 'with route id', - routeIdentifier, - 'routeAlreadyExists', - routeAlreadyExists - ); if ( operation.deprecated !== true && - !Object.prototype.hasOwnProperty.call(this.operations, operationId) && - !routeAlreadyExists + !Object.prototype.hasOwnProperty.call(this.operations, operationId) ) { - console.log('XAVI INHERITING OPERATION', operationId); this.operations[operationId] = OperationBuilder.create(operation.id, this); this.operations[operationId].setup(operation.handler); this.operations[operationId].requestValidationEnabled = operation.requestValidationEnabled; @@ -351,7 +297,6 @@ export class BautaJS implements BautaJSInstance { } }); - console.log('inheritOperationsFrom in bautajs-core finished', this.operations); return this; } } From 0c032ab12bcf38b73e81d683ba9ba487ad19c1a2 Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Thu, 23 May 2024 14:36:30 +0200 Subject: [PATCH 06/10] fix: remove logs --- packages/bautajs-fastify/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/bautajs-fastify/src/index.ts b/packages/bautajs-fastify/src/index.ts index 2b0201c..db9bc5b 100644 --- a/packages/bautajs-fastify/src/index.ts +++ b/packages/bautajs-fastify/src/index.ts @@ -38,7 +38,6 @@ export async function bautajsFastify(fastify: FastifyInstance, opts: any) { : new bautaJS.BautaJS({ logger: fastify.log as bautaJS.Logger, ...opts }); if (opts.inheritOperationsFrom) { - console.log('habemus inheritOperationsFrom in bautajs-fastify'); bautajs.inheritOperationsFrom(opts.inheritOperationsFrom); } From bbff965c42cb85b002da29ea30a1e81cc17e2e5b Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Thu, 23 May 2024 17:50:28 +0200 Subject: [PATCH 07/10] fix(bautajs): fix documentation for versioning --- docs/api-versioning.md | 287 +++++++++--------- .../README.md | 23 +- 2 files changed, 168 insertions(+), 142 deletions(-) diff --git a/docs/api-versioning.md b/docs/api-versioning.md index c813b53..49009bb 100644 --- a/docs/api-versioning.md +++ b/docs/api-versioning.md @@ -1,46 +1,42 @@ # API versioning -Bauta.js has API versioning out of the box to version your API operations. +## Context -As Bauta.js is loading all files under ./resolvers file by default, it's recommended to follow the next folder structure, remember you can change this default path by other one. +By API versioning we mean the fact that we can have a running server with two different versions of the same endpoint. -```console -- resolvers - -- v1 - -- cats-resolver.js - -- v2 - -- cats-resolver.js -``` +Let's assume that initially we have a service without any versioning whatsoever. This means that we have the following set of components: -```js -// v1/cats-resolver.js -const { resolver } = require('@axa/bautajs-core'); -module.exports = resolver((operations) => { - operations.findCat.setup((_, ctx) => { - return { - name: 'toto' - } - }); -}) -``` +- a swagger api definition with certain routes and operations defined +- a set of resolvers that implement those operations +- a bauta service that loads and exposes both swagger and resolvers. -```js -// v2/cats-resolver.js -const { resolver } = require('@axa/bautajs-core'); -module.exports = resolver((operations) => { - operations.findCatV2.setup((_, ctx) => { - return { - idV2: 'toto' - } - }); -}) -``` +In the context of bauta, for versioning we mean the capability to provide a subset of those api's in a different prefixed route with a different swagger. + +This means that after versioning is applied we have the following: + +- the original swagger api definition with certain routes and operations defined, some of them marked as deprecated +- the original set of resolvers that implement those operations +- a new swagger with certain new routes and operations defined in a different way +- a new set of resolvers that implement these new operations +- a bauta service that loads and exposes both swagger and resolvers, for the original and for the new services. + + +## Configuration for versioning -This is an example of API definitions for two API versions: +For versioning to work there are three parts that **must** be configured in order to work. + +### 1. Swagger API definition + +:exclamation: You **must** declare any route that you want to use in the new version as ``deprecated``. :exclamation: + +This is required to make sure that bautaJs does not overwrite the new version with the old implementation. + +Below there is an example of API definitions for two API versions (note: you may use two different swaggers and load each one of them separatedly): ```json // api-definitions.json - [ { +[ + { "openapi": "3.0.0", "info": { "description": "A new API", @@ -49,30 +45,31 @@ This is an example of API definitions for two API versions: }, "servers": [ { - "url":"/v1/api/" - } + "url": "/v1/api/" + } ], "paths": { "/cats": { - "get":{ - "operationId":"findCat", + "get": { + "operationId": "findCat", + "deprecated": true, // <-- MUST mark it as deprecated if you want to use new version of the same route "responses": { "200": { "description": "Miau!", "content": { "application/json": { "schema": { - "properties": { - "name": { - "type": "string" + "properties": { + "name": { + "type": "string" } } } } } + } } } - } } } }, @@ -85,35 +82,99 @@ This is an example of API definitions for two API versions: }, "servers": [ { - "url":"/v2/api/" - } + "url": "/v2/api/" + } ], "paths": { "/cats": { - "get":{ - "operationId":"findCatV2", + "get": { + "operationId": "findCatV2", "responses": { "200": { "description": "Miaw!", "content": { "application/json": { - "schema": { - "properties": { - "name": { - "type": "string" - } + "schema": { + "properties": { + "petName": { // Breaking change: petName instead of name in v1 + "type": "string" } } + } } } + } } } - } } } - }] + } +] +``` + + + +#### Deprecation programmaticaly + +Bautajs supports the deprecation by code with the resolver method `setAsDeprecated`. + +```js +const { resolver } = require('@axa/bautajs-core'); +// my-resolver.js +module.exports = resolver(operations) => { + operations.findCats.setAsDeprecated().setup(() => 'result'); +} +``` + +While you may use this in a pinch we strongly suggest that you favour the deprecation of services at swagger level whenever possible. + +In any case the deprecation of routes that you are versioning, either by swagger or programmatically, **is mandatory**. + + +### 2. Resolver resolution + + +Bauta.js has API versioning out of the box to version your API operations. + +As Bauta.js is loading all files under ./resolvers file by default, it's recommended to follow the next folder structure, remember you can change this default path by other one. + +```console +- resolvers + -- v1 + -- cats-resolver.js + -- v2 + -- cats-resolver.js ``` + +```js +// resolvers/v1/cats-resolver.js +const { resolver } = require('@axa/bautajs-core'); +module.exports = resolver((operations) => { + operations.findCat.setup((_, ctx) => { + return { + name: 'toto' + } + }); +}) +``` + +```js +// resolvers/v2/cats-resolver.js +const { resolver } = require('@axa/bautajs-core'); +module.exports = resolver((operations) => { + operations.findCatV2.setup((_, ctx) => { + return { + petName: 'toto' + } + }); +}) +``` + +### 3. BautaJS instantiation of both API versions + +When you want to apply versioning, you have to modify slightly the way that bautaJs starts up to clearly configure what is considered the old version of your service endpoints and what is considered the latest version. + ### Example using Bauta.js core without a Node.js framework plugin Now we need to specify a `bautaJS` instance per API swagger version. @@ -126,11 +187,13 @@ const apiDefinition = require('./api-definitions.json'); const bautajsV1 = new BautaJS({ apiDefinition: apiDefinitions[0], + resolversPath: './resolvers/v1/cats-resolvers.js', prefix: '/v1/', apiBasePath: '/api/' }); -const bautajsV2 = new BautaJS({ +const bautajsV2 = new BautaJS({ apiDefinition: apiDefinitions[1], + resolversPath: './resolvers/v2/cats-resolvers.js', prefix: '/v2/', apiBasePath: '/api/' }); @@ -146,7 +209,7 @@ const bautajsV2 = new BautaJS({ const res1 = await bautajsV2.operations.findCat.run(); // result: name: 'toto' const res2 = await bautajsV2.operations.findCatV2.run(); - // result: idV2: 'toto' + // result: petName: 'toto' console.log(res1, res2); })() @@ -158,6 +221,7 @@ In this example the `findCat` from v1 instance will be inherited to the v2 insta Calling `inheritOperationsFrom` after bootstrap the instance is not possible and will always throw an error. This behaviour is intended since api schema is resolved on bootstrap time so if the instance is already bootstrapped and `inheritOperationsFrom` is called after this could lead to unexpected behaviour of your API. + ### Example using Bauta.js with a @axa/bautajs-fastify plugin #### Creating the Bauta.js instances and setting up the inheritance outside the plugin @@ -170,24 +234,26 @@ const apiDefinition = require('./api-definitions.json'); const bautajsV1 = new BautaJS({ apiDefinition: apiDefinitions[0], - prefix: '/v1/', - apiBasePath: '/api/' + resolversPath: './resolvers/v1/cats-resolvers.js', }); const bautajsV2 = new BautaJS({ apiDefinition: apiDefinitions[1], - prefix: '/v2/', - apiBasePath: '/api/' + resolversPath: './resolvers/v2/cats-resolvers.js', }); (async () => { await fastifyInstance .register(bautajsFastify, { - bautajsInstance: bautajsV1 + bautajsInstance: bautajsV1, + prefix: '/v1/', + apiBasePath: '/api/' }) .after(() => { bautajsV2.inheritOperationsFrom(bautajsV1); fastifyInstance.register(bautajsFastify, { - bautajsInstance: bautajsV2 + bautajsInstance: bautajsV2, + prefix: '/v2/', + apiBasePath: '/api/' }); }); @@ -195,7 +261,7 @@ const bautajsV2 = new BautaJS({ const res1 = await bautajsV2.operations.findCat.run(); // result: name: 'toto' const res2 = await bautajsV2.operations.findCatV2.run(); - // result: idV2: 'toto' + // result: petName: 'toto' console.log(res1, res2); })() @@ -211,24 +277,27 @@ const apiDefinition = require('./api-definitions.json'); const bautajsV1 = new BautaJS({ apiDefinition: apiDefinitions[0], - prefix: '/v1/', - apiBasePath: '/api/' + resolversPath: './resolvers/v1/cats-resolvers.js' }); + const bautajsV2 = new BautaJS({ apiDefinition: apiDefinitions[1], - prefix: '/v2/', - apiBasePath: '/api/' + resolversPath: './resolvers/v2/cats-resolvers.js' }); (async () => { await fastifyInstance .register(bautajsFastify, { - bautajsInstance: bautajsV1 + bautajsInstance: bautajsV1, + prefix: '/v1/', + apiBasePath: '/api/' }) .after(() => { fastifyInstance.register(bautajsFastify, { bautajsInstance: bautajsV2, - inheritOperationsFrom: bautajsV1 + inheritOperationsFrom: bautajsV1, + prefix: '/v2/', + apiBasePath: '/api/' }); }); @@ -236,7 +305,7 @@ const bautajsV2 = new BautaJS({ const res1 = await bautajsV2.operations.findCat.run(); // result: name: 'toto' const res2 = await bautajsV2.operations.findCatV2.run(); - // result: idV2: 'toto' + // result: petName: 'toto' console.log(res1, res2); })() @@ -252,20 +321,22 @@ const apiDefinition = require('./api-definitions.json'); const bautajsV1 = new BautaJS({ apiDefinition: apiDefinitions[0], - prefix: '/v1/', - apiBasePath: '/api/' + resolversPath: './resolvers/v1/cats-resolvers.js' }); (async () => { await fastifyInstance .register(bautajsFastify, { - bautajsInstance: bautajsV1 + bautajsInstance: bautajsV1, + prefix: '/v1/', + apiBasePath: '/api/' }) .after(() => { - fastifyInstance.register(bautajsFastify, { + fastifyInstance.register(bautajsFastify, { apiDefinition: apiDefinitions[1], + resolversPath: './resolvers/v2/cats-resolvers.js', prefix: '/v2/', - apiBasePath: '/api/' + apiBasePath: '/api/', inheritOperationsFrom: bautajsV1 }); }); @@ -274,78 +345,14 @@ const bautajsV1 = new BautaJS({ const res1 = await bautajsV2.operations.findCat.run(); // result: name: 'toto' const res2 = await bautajsV2.operations.findCatV2.run(); - // result: idV2: 'toto' + // result: petName: 'toto' console.log(res1, res2); })() ``` -### Example using Bauta.js with a @axa/bautajs-express plugin - -TBD +Note about the examples: there are orientative examples, if you want to use them in a real case scenario, remember that you need +to start up the server accordingly (for instance, in `fastify` you have to listen to the plugin instance you have created and registered). -## Deprecate an operation -A deprecated operation means that on call `inheritOperationsFrom` method that operation won't be `cloned` to the new instance. - -#### Deprecation programmaticaly - -```js -const { resolver } = require('@axa/bautajs-core'); -// my-resolver.js -module.exports = resolver(operations) => { - operations.findCats.setAsDeprecated().setup(() => 'result'); -} -``` - -#### Deprecation by OpenAPI swagger schema - -```json -// api-definitions.json -[ - { - "openapi": "3.0.0", - "apiVersion": "1.0", - "info": { - "description": "A new API", - "version": "v1", - "title": "CORE API" - }, - "servers": [ - { - "url":"/v1/api/" - } - ], - "paths": { - "/cats": { - "get":{ - "operationId":"findCat", - "deprecated": true - } - } - } - }, - { - "openapi": "3.0.0", - "apiVersion": "2.0", - "info": { - "description": "A new API", - "version": "v2", - "title": "CORE API" - }, - "servers": [ - { - "url":"/v1/api/" - } - ], - "paths": { - "/cats": { - "get":{ - "operationId":"findCat" - } - } - } - } -] -``` diff --git a/packages/bautajs-fastify-versioning-example/README.md b/packages/bautajs-fastify-versioning-example/README.md index 11eec14..68bf23d 100644 --- a/packages/bautajs-fastify-versioning-example/README.md +++ b/packages/bautajs-fastify-versioning-example/README.md @@ -8,11 +8,30 @@ - It is recommented that you are using node v18. - `npm install` from the root project of the monorepo -- enter into `packages/bautajs-fastify-example` folder +- enter into `packages/bautajs-fastify-versioning-example` folder ## Brief explanation of the different examples -TODO +We have a set of three services: + +- `v1` + - `cats` --> endpoint marked as `deprecated`. This service returns name, communication and breed + - `dogs` --> This service returns name, communication and breed +- `v2` + - `cats` --> latest version of the endpoint. This service returns petName, communication and favouriteAction + + +## An example of each initialization method + +You can run npm start for each one of the scripts: + +- ``npm run start``: Uses default port 8080. Basic example without any versioning. Only v1 endpoints are exposed. +- ``start:external``: Configures the versioning inheritance outside the plugin. +- ``start:plugin``: Configures the versioning inheritance with the plugin option 'inheritOperationsFrom' and instantiates the two instances of bautaJs (the original and the new version). +- ``start:parent``: Configures the versioning inheritance instantiating only the parent bautaJs instance and uses the plugin option 'inheritOperationsFrom' to add the new version endpoints. + + +This is only a showcase of how the versioning works. For details you should check [versioning](https://github.com/axa-group/bauta.js/blob/main/docs/api-versioning.md). ## Third party dependencies licenses From bf0ea67146a4de0b8b39acbf8dd32e84676183e4 Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Thu, 23 May 2024 17:53:22 +0200 Subject: [PATCH 08/10] fix: minor correction --- docs/api-versioning.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-versioning.md b/docs/api-versioning.md index 49009bb..c10a151 100644 --- a/docs/api-versioning.md +++ b/docs/api-versioning.md @@ -27,9 +27,9 @@ For versioning to work there are three parts that **must** be configured in orde ### 1. Swagger API definition -:exclamation: You **must** declare any route that you want to use in the new version as ``deprecated``. :exclamation: +:exclamation: You **must** declare any existing route that you want to use as a new version as ``deprecated``. :exclamation: -This is required to make sure that bautaJs does not overwrite the new version with the old implementation. +This is required to make sure that bautaJs does not overwrite the new version with the original route implementation. Below there is an example of API definitions for two API versions (note: you may use two different swaggers and load each one of them separatedly): From 7464bd2693cf6d40ba84ac4b4d925d7cd5f1d0e7 Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Thu, 23 May 2024 18:06:23 +0200 Subject: [PATCH 09/10] fix: fix indentation --- docs/api-versioning.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api-versioning.md b/docs/api-versioning.md index c10a151..1b59321 100644 --- a/docs/api-versioning.md +++ b/docs/api-versioning.md @@ -175,7 +175,7 @@ module.exports = resolver((operations) => { When you want to apply versioning, you have to modify slightly the way that bautaJs starts up to clearly configure what is considered the old version of your service endpoints and what is considered the latest version. -### Example using Bauta.js core without a Node.js framework plugin +#### Example using Bauta.js core without a Node.js framework plugin Now we need to specify a `bautaJS` instance per API swagger version. @@ -222,9 +222,9 @@ In this example the `findCat` from v1 instance will be inherited to the v2 insta Calling `inheritOperationsFrom` after bootstrap the instance is not possible and will always throw an error. This behaviour is intended since api schema is resolved on bootstrap time so if the instance is already bootstrapped and `inheritOperationsFrom` is called after this could lead to unexpected behaviour of your API. -### Example using Bauta.js with a @axa/bautajs-fastify plugin +#### Example using Bauta.js with a @axa/bautajs-fastify plugin -#### Creating the Bauta.js instances and setting up the inheritance outside the plugin +##### Creating the Bauta.js instances and setting up the inheritance outside the plugin ```js const { BautaJS } = require('@axa/bautajs-core'); @@ -267,7 +267,7 @@ const bautajsV2 = new BautaJS({ })() ``` -#### Creating the Bauta.js instances and setting up the inheritance with the plugin option 'inheritOperationsFrom' +##### Creating the Bauta.js instances and setting up the inheritance with the plugin option 'inheritOperationsFrom' ```js const { BautaJS } = require('@axa/bautajs-core'); @@ -311,7 +311,7 @@ const bautajsV2 = new BautaJS({ })() ``` -#### Creating only the parent Bauta.js instance and setting up the inheritance with the plugin option 'inheritOperationsFrom' +##### Creating only the parent Bauta.js instance and setting up the inheritance with the plugin option 'inheritOperationsFrom' ```js const { BautaJS } = require('@axa/bautajs-core'); @@ -351,7 +351,7 @@ const bautajsV1 = new BautaJS({ })() ``` -Note about the examples: there are orientative examples, if you want to use them in a real case scenario, remember that you need +Note about the examples: these are orientative examples, if you want to use them in a real case scenario, remember that you need to start up the server accordingly (for instance, in `fastify` you have to listen to the plugin instance you have created and registered). From 17ea071678b3566c7de39e8b3d0b6a4baf911b22 Mon Sep 17 00:00:00 2001 From: Xavier Redondo Date: Fri, 24 May 2024 09:23:43 +0200 Subject: [PATCH 10/10] fix: unmake unnneded changes due to carriage returns --- packages/bautajs-core/src/bauta.ts | 4 ---- packages/bautajs-fastify/src/index.ts | 2 -- 2 files changed, 6 deletions(-) diff --git a/packages/bautajs-core/src/bauta.ts b/packages/bautajs-core/src/bauta.ts index 99852ee..6d65193 100644 --- a/packages/bautajs-core/src/bauta.ts +++ b/packages/bautajs-core/src/bauta.ts @@ -141,7 +141,6 @@ export class BautaJS implements BautaJSInstance { if (this.bootstrapped === true) { throw new Error('The instance has already being bootstrapped.'); } - if (this.apiDefinition) { const parser = new Parser(this.logger); const parsedApiDefinition = await parser.asyncParse(this.apiDefinition); @@ -275,10 +274,8 @@ export class BautaJS implements BautaJSInstance { 'The given instance is not bootstrapped, thus operation schema will be no inherited.' ); } - Object.keys(bautajsInstance.operations).forEach(operationId => { const operation = bautajsInstance.operations[operationId]; - if ( operation.deprecated !== true && !Object.prototype.hasOwnProperty.call(this.operations, operationId) @@ -296,7 +293,6 @@ export class BautaJS implements BautaJSInstance { } } }); - return this; } } diff --git a/packages/bautajs-fastify/src/index.ts b/packages/bautajs-fastify/src/index.ts index db9bc5b..ab20e47 100644 --- a/packages/bautajs-fastify/src/index.ts +++ b/packages/bautajs-fastify/src/index.ts @@ -26,12 +26,10 @@ export async function bautajsFastify( fastify: FastifyInstance, opts: BautaJSFastifyPluginOptions ): Promise; - export async function bautajsFastify( fastify: FastifyInstance, opts: BautaJSFastifyPluginOptions & bautaJS.BautaJSOptions ): Promise; - export async function bautajsFastify(fastify: FastifyInstance, opts: any) { const bautajs = opts.bautajsInstance ? opts.bautajsInstance