{"_id":"@backpock/fastify-zod","_rev":"1-a3139e25d80d4803e96f5a4a61a669e9","name":"@backpock/fastify-zod","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.1":{"name":"@backpock/fastify-zod","version":"1.0.1","description":"Zod integration with Fastify","main":"build/index.js","scripts":{"check:types":"tsc -p . --noEmit","check:lint":"eslint src","check":"npm run check:types && npm run check:lint","clean":"rm -rf build","build:types":"tsc -p . --emitDeclarationOnly","build:babel":"babel src --out-dir build --extensions '.ts' --source-maps","build:openapi-spec":"node build/__tests__/generate-spec.fixtures.js","build:openapi-client":"rm -rf test-openapi-client && openapi-generator-cli generate && cd test-openapi-client && npm i && cd .. && npm i file:./test-openapi-client --save-dev","build":"npm run clean && npm run build:babel && npm run build:openapi-spec && npm run build:openapi-client && npm run build:types","test":"jest","prepublishOnly":"npm run clean && npm run build:babel"},"repository":{"type":"git","url":"git+https://github.com/elierotenberg/fastify-zod.git"},"keywords":["zod","fastify","openapi"],"author":{"name":"Elie Rotenberg","email":"elie@rotenberg.io"},"license":"MIT","bugs":{"url":"https://github.com/elierotenberg/fastify-zod/issues"},"homepage":"https://github.com/elierotenberg/fastify-zod#readme","devDependencies":{"@babel/cli":"^7.17.10","@babel/core":"^7.18.2","@babel/preset-env":"^7.18.2","@babel/preset-typescript":"^7.17.12","@types/http-errors":"^1.8.2","@types/jest":"^28.1.1","@types/node":"^17.0.41","@typescript-eslint/eslint-plugin":"^5.27.1","@typescript-eslint/parser":"^5.27.1","eslint":"^8.17.0","eslint-config-prettier":"^8.5.0","eslint-plugin-import":"^2.26.0","eslint-plugin-prettier":"^4.0.0","fastify":"^3.29.0","fastify-zod-test-openapi-client":"file:test-openapi-client","http-errors":"^2.0.0","jest":"^28.1.1","node-fetch":"^2.6.7","pino-pretty":"^8.0.0","prettier":"^2.6.2","typescript":"^4.7.3"},"peerDependencies":{"fastify":"^3.29.0"},"dependencies":{"@fastify/swagger":"^6.1.0","@openapitools/openapi-generator-cli":"^2.5.1","@types/js-yaml":"^4.0.5","change-case":"^4.1.2","fast-deep-equal":"^3.1.3","js-yaml":"^4.1.0","tslib":"^2.4.0","typed-jest-expect":"^1.0.0","zod":"^3.17.3","zod-to-json-schema":"^3.17.0"},"gitHead":"05a494b08766b4a6e2ae7587ffb88ff91c588d1c","_id":"@backpock/fastify-zod@1.0.1","_nodeVersion":"16.15.0","_npmVersion":"8.5.5","dist":{"integrity":"sha512-NK3pbxpGjiU8IqDCsN2q0VBTUd2OHVXP3bdGbjsrBWzklo8b3CUqcShEv+xqbtVE5IbFbUjLXjnYunN2EIOGQg==","shasum":"bc27921ca74c77e1f49178e706e8e1b3d8a9e4a5","tarball":"https://registry.npmjs.org/@backpock/fastify-zod/-/fastify-zod-1.0.1.tgz","fileCount":70,"unpackedSize":489719,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCEMhT8Zwl1iyAEpKdS+WH18Hj10xPedOxkRxqpsXIGOwIgcY0Phtu6oMoe9RvX3VS0m+b4Ib5u8G3gsGpO6iV03ao="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJioS7yACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqjtBAAn+Ev/aq9iHlMrapKPERRUqiywg5DAozXXvs3duOOCtVEke7k\r\nnb3Z09CVg54llV3uIufMKjZs9JbBwBEv3orEoH/mAU8kOksVUSw0PQ6zm6xj\r\nS+qGwlgEEsg2ERsMVUwhveyvFc1e57c1vm+5v2d43Fms36xAvlC8GilBjUVk\r\njsRzcVRsWeDYZEFn/A6qzEWJIVHDA7wbn4CyQzI7437s0drHNraDpo2gns89\r\nFOC87rTNXJJBqXEb+DI+IUTuzGq7wrk3ozAAVdWGQm3C2YG9M4Xds0ksxop5\r\nn2FenZGpncqqchekl9+Rg0zYAXYcbNdi9+FDjqTfmQHSDOdFd8GH/BSYkWDC\r\nsv8zSJ8+kzQ5Ypp/DVH+jlcfOytaFUaNF1TYENePWhG1k7XHSbkqw7nYu9Zu\r\ny3j4KNTsVdV/DUDBq8aXCA/fdnAUazJJJar/Rho44LgQ0MlrXFJaQ49VphHG\r\nAQgxTr/s0JVJAyfLRlux6ptkIPV2XP8zAzd7Zs4Z9gYnyo9XjC2Ef4CwpLwp\r\nyFMeG7IDNVEiD55FHx80V66atGKnf845W/DFJob7jDZFwV2gcW8FmMomHZzE\r\nR4UaHCb21RG2PX3ywon1mTRbE53RIWJnFVzjXfRoNoTkZHUq3bhiPN4BuMnX\r\nlYaHp0pRZSH5J8i2VMxPYdH/zY6wnpulFOs=\r\n=fANf\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"gomah","email":"hi@gomah.fr"},"directories":{},"maintainers":[{"name":"gomah","email":"hi@gomah.fr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/fastify-zod_1.0.1_1654730482695_0.4476787659474193"},"_hasShrinkwrap":false},"1.0.2":{"name":"@backpock/fastify-zod","version":"1.0.2","description":"Zod integration with Fastify","main":"build/index.js","scripts":{"check:types":"tsc -p . --noEmit","check:lint":"eslint src","check":"npm run check:types && npm run check:lint","clean":"rm -rf build","build:types":"tsc -p . --emitDeclarationOnly","build:babel":"babel src --out-dir build --extensions '.ts' --source-maps","build:openapi-spec":"node build/__tests__/generate-spec.fixtures.js","build:openapi-client":"rm -rf test-openapi-client && openapi-generator-cli generate && cd test-openapi-client && npm i && cd .. && npm i file:./test-openapi-client --save-dev","build":"npm run clean && npm run build:babel && npm run build:openapi-spec && npm run build:openapi-client && npm run build:types","test":"jest","prepublishOnly":"npm run clean && tsup src/index.ts --format esm,cjs --dts -d build/"},"repository":{"type":"git","url":"git+https://github.com/elierotenberg/fastify-zod.git"},"keywords":["zod","fastify","openapi"],"author":{"name":"Elie Rotenberg","email":"elie@rotenberg.io"},"license":"MIT","bugs":{"url":"https://github.com/elierotenberg/fastify-zod/issues"},"homepage":"https://github.com/elierotenberg/fastify-zod#readme","devDependencies":{"@babel/cli":"^7.17.10","@babel/core":"^7.18.2","@babel/preset-env":"^7.18.2","@babel/preset-typescript":"^7.17.12","@types/http-errors":"^1.8.2","@types/jest":"^28.1.1","@types/node":"^17.0.41","@typescript-eslint/eslint-plugin":"^5.27.1","@typescript-eslint/parser":"^5.27.1","eslint":"^8.17.0","eslint-config-prettier":"^8.5.0","eslint-plugin-import":"^2.26.0","eslint-plugin-prettier":"^4.0.0","fastify":"^3.29.0","fastify-zod-test-openapi-client":"file:test-openapi-client","http-errors":"^2.0.0","jest":"^28.1.1","node-fetch":"^2.6.7","pino-pretty":"^8.0.0","prettier":"^2.6.2","typescript":"^4.7.3"},"peerDependencies":{"fastify":"^3.29.0"},"dependencies":{"@fastify/swagger":"^6.1.0","@openapitools/openapi-generator-cli":"^2.5.1","@types/js-yaml":"^4.0.5","change-case":"^4.1.2","fast-deep-equal":"^3.1.3","js-yaml":"^4.1.0","tslib":"^2.4.0","tsup":"^6.1.0","typed-jest-expect":"^1.0.0","zod":"^3.17.3","zod-to-json-schema":"^3.17.0"},"types":"./build/index.d.ts","gitHead":"05a494b08766b4a6e2ae7587ffb88ff91c588d1c","_id":"@backpock/fastify-zod@1.0.2","_nodeVersion":"16.15.0","_npmVersion":"8.5.5","dist":{"integrity":"sha512-QhmdpixSZ0LP6apyM3N9HrPIsE1sB15AyZkUOHrr+Yqn71mU9K0Sym6MvU4dH6Za9P7W8OEOZBDRGM8yg4iHig==","shasum":"fd4fa7f3399aab7b37d3826233c82bfdd8889c74","tarball":"https://registry.npmjs.org/@backpock/fastify-zod/-/fastify-zod-1.0.2.tgz","fileCount":37,"unpackedSize":323822,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCraZy1Cx0SY+pkKJpWxo1EypA34x0Nr3tIkL1a57B38AIgQa0UBkLnDcjSZft2eU8IdN5Ah7bSgngBXaXWbLt5b5Q="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJioTNKACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp1CxAAn8bpJqEOqo2DMdxRYwrJwpmZwl+4QSNApsNN13AZ6mtQ1EiO\r\nsqMzU2AoB6kIZ4qACmJOl8MtkUeu/jXH1ux9BUxMdnASF8kt2HK6PlqDZ9qQ\r\nuG34E3vIgkXYVSgV8uHPzKm81Zl9++0gRI+zaAemkU8EIfqgaXS/lX9P5MpJ\r\nSTBJKi/2i8ceDJ9OQTBp+ZrbggvHJFA3W/TcTfeejNH53/n8NbZomQq+yzks\r\nUca19X3HlAW2PDrLINr+a4TRfnIBzAkqzEmAMHsM+FZ/pZTp8NZ5EKGXyoOH\r\nydeQmrMvYLUSMqUpHdE8t9328H5O8qOKW1y7Rv28hr4JMl1l7yfV33T6acMp\r\nJsoSnwb6F8yk0wBY2nkwVvLEaYk502vvXyiU8u4FAzOpk/a1MRuoBzuMFyn/\r\nDz614PtgeZrV2CeMhFPQ6SxVet0CmwRKYP+bXZakONBvzwcGBU/vnqwlUTqs\r\nvPJYHWua88dlHOkQUziIJYgwL5NkavaGfI0tVYlwh5Y0sQjmcsbRu2geEcP0\r\nhiPHHtG8A71+GtzoBqAtHA+eGt62Eg4dXsxwXwcWBw++yuaZp2afbpamyQ4U\r\notHaQLPZvgoumtH8BToZSXyIj35Utlj9eVKlfpam08OjtgfeWKcJIqnKEvwd\r\nCa7Lh0/2J3Bd/YLWmhK9tkzmbwv6UwUOvAw=\r\n=jDQm\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"gomah","email":"hi@gomah.fr"},"directories":{},"maintainers":[{"name":"gomah","email":"hi@gomah.fr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/fastify-zod_1.0.2_1654731594333_0.3371717550040849"},"_hasShrinkwrap":false}},"time":{"created":"2022-06-08T23:21:22.630Z","1.0.1":"2022-06-08T23:21:22.874Z","modified":"2022-06-08T23:39:54.652Z","1.0.2":"2022-06-08T23:39:54.569Z"},"maintainers":[{"name":"gomah","email":"hi@gomah.fr"}],"description":"Zod integration with Fastify","homepage":"https://github.com/elierotenberg/fastify-zod#readme","keywords":["zod","fastify","openapi"],"repository":{"type":"git","url":"git+https://github.com/elierotenberg/fastify-zod.git"},"author":{"name":"Elie Rotenberg","email":"elie@rotenberg.io"},"bugs":{"url":"https://github.com/elierotenberg/fastify-zod/issues"},"license":"MIT","readme":"# fastify-zod\n\n## Why?\n\n`fastify` is awesome and arguably the best Node http server around.\n\n`zod` is awesome and arguably the best TypeScript modeling / validation library around.\n\nUnfortunately, `fastify` and `zod` don't work together very well. [`fastify` suggests using `@sinclair/typebox`](https://www.fastify.io/docs/latest/TypeScript/#typebox), which is nice but is nowhere close to `zod`. This library allows you to use `zod` as your primary source of truth for models with nice integration with `fastify`, `fastify-swagger` and OpenAPI `typescript-fetch` generator.\n\n## Features\n\n- Define your models using `zod` in a single place, without redundancy / conflicting sources of truth\n- Use your models in busines logic code and get out of the box type-safety in `fastify`\n- First-class support for `fastify-swagger` and `openapitools-generator/typescrip-fetch`\n- Referential transparency, including for `enum`s\n- Deduplication of structurally equivalent models\n- Internal generated JSON Schemas available for reuse\n\n## Setup\n\n- Install `fastify-zod`\n\n```\nnpm i fastify-zod`\n```\n\n- Define your models using `zod`\n\n```ts\nconst TodoItemId = z.object({\n  id: z.string().uuid(),\n});\n\nenum TodoStateEnum {\n  Todo = `todo`,\n  InProgress = `in progress`,\n  Done = `done`,\n}\n\nconst TodoState = z.nativeEnum(TodoStateEnum);\n\nconst TodoItem = TodoItemId.extend({\n  label: z.string(),\n  dueDate: z.date().optional(),\n  state: TodoState,\n});\n\nconst TodoItems = z.object({\n  todoItems: z.array(TodoItem),\n});\n\nconst TodoItemsGroupedByStatus = z.object({\n  todo: z.array(TodoItem),\n  inProgress: z.array(TodoItem),\n  done: z.array(TodoItem),\n});\n\nconst models = {\n  TodoItemId,\n  TodoItem,\n  TodoItems,\n  TodoItemsGroupedByStatus,\n};\n```\n\n- Register `fastify` types\n\n```ts\nimport type { FastifyZod } from \"fastify-zod\";\n\n// Global augmentation, as suggested by\n// https://www.fastify.io/docs/latest/Reference/TypeScript/#creating-a-typescript-fastify-plugin\ndeclare module \"fastify\" {\n  interface FastifyInstance {\n    readonly zod: FastifyZod<typeof models>;\n  }\n}\n\n// Local augmentation\n// See below for register()\nconst f = register(fastify(), { jsonSchemas });\n```\n\n- Register `fastify-zod` with optional config for `fastify-swagger`\n\n```ts\nimport { buildJsonSchemas, register } from \"fastify-zod\";\n\nconst f = fastify();\n\nregister(f, {\n  jsonSchemas: buildJsonSchemas(models),\n  // optional, see below\n  swagger: {\n    openapi: {\n      /* ... */\n    },\n    exposeRoute: true,\n    transformSpec: {}, // optional, see below\n  },\n});\n```\n\n- Define fastify routes using simplified syntax and get automatic type inference\n\n```ts\nf.zod.post(\n  `/item`,\n  {\n    operationId: `postTodoItem`,\n    body: `TodoItem`,\n    reply: `TodoItems`,\n  },\n  async ({ body: nextItem }) => {\n    /* body is correctly inferred as TodoItem */\n    if (state.todoItems.some((prevItem) => prevItem.id === nextItem.id)) {\n      throw new BadRequest(`item already exists`);\n    }\n    state.todoItems = [...state.todoItems, nextItem];\n    /* reply is typechecked against TodoItems */\n    return state;\n  }\n);\n```\n\n- Generate transformed spec with first-class support for downstream `openapitools-generator`\n\n```ts\nconst transformedSpecJson = await f\n  .inject({\n    method: `get`,\n    url: `/documentation_transformed/json`,\n  })\n  .then((res) => res.body);\n\nawait writeFile(\n  join(__dirname, `..`, `..`, `openapi.transformed.json`),\n  transformedSpecJson,\n  { encoding: `utf-8` }\n);\n```\n\n- Generate OpenAPI Client with `openapitools-generator`\n\n`openapi-generator-cli generate`\n\n## API\n\n### `buildJsonSchemas(models: Models, options: BuildJsonSchemasOptions = {}): BuildJonSchemaResult<typeof models>`\n\nBuild JSON Schemas and `$ref` function from Zod models.\n\nThe result can be used either with `register` (recommended, see [example in tests](./src/__tests__/server.fixtures.ts)) or directly with `fastify.addSchema` using the `$ref` function (legacy, see [example in tests](./src/__tests__/server.legacy.fixtures.ts)).\n\n#### `Models`\n\nRecord mapping model keys to Zod types. Keys will be used to reference models in routes definitions.\n\nExample:\n\n```ts\nconst TodoItem = z.object({\n  /* ... */\n});\nconst TodoList = z.object({\n  todoItems: z.array(TodoItem),\n});\n\nconst models = {\n  TodoItem,\n  TodoList,\n};\n```\n\n#### `BuildJsonSchemasOptions = {}`\n\n##### `BuildJsonSchemasOptions.$id: string = \"Schemas\"`: `$id` of the generated schema (defaults to \"Schemas\")\n\n##### `BuildJsonSchemasOptions.target: `jsonSchema7`|`openApi3` = \"jsonSchema7\"`: _jsonSchema7_ (default) or _openApi3_\n\nGenerates either `jsonSchema7` or `openApi3` schema. See [`zod-to-json-schema`](https://github.com/StefanTerdell/zod-to-json-schema#options-object).\n\n#### `BuildJsonSchemasResult<typeof models> = { schemas: JsonSchema[], $ref: $ref<typeof models> }`\n\nThe result of `buildJsonSchemas` has 2 components: an array of schemas that can be added directly to fastify using `fastify.addSchema`, and a `$ref` function that returns a `{ $ref: string }` object that can be used directly.\n\nIf you simply pass the result to `register`, you won't have to care about this however.\n\n```ts\nconst { schemas, $ref } = buildJsonSchemas(models, { $id: \"MySchema\" });\n\nfor (const schema of schemas) {\n  fastify.addSchema(schema);\n}\n\nequals($ref(\"TodoItem\"), {\n  $ref: \"MySchema#/properties/TodoItem\",\n});\n```\n\n### `buildJsonSchema($id: string, Type: ZodType)` (_deprecated_)\n\nShorthand to `buildJsonSchema({ [$id]: Type }).schemas[0]`.\n\n### `register(f: FastifyInstance, { jsonSchemas, swaggerOptions?: = {} }: RegisterOptions`\n\nAdd schemas to `fastify` and decorate instance with `zod` property to add strongly-typed routes (see `fastify.zod` below).\n\n### `RegisterOptions<typeof models>`\n\n#### `RegisterOptions<typeof models>.jsonSchema`\n\nThe result of `buildJsonSchemas(models)` (see above).\n\n##### `RegisterOptions<typeof models>.swaggerOptions = FastifyDynamicSwaggerOptions & { transformSpec: TransformSpecOptions }`\n\nIf present, this options will automatically register `fastify-swagger` in addition to `fastify.zod`.\n\nAny options will be passed directly to `fastify-swagger` so you may refer to [their documentation](https://github.com/fastify/fastify-swagger).\n\nIn addition to `fastify-swagger` options, you can pass an additional property, `transformSpec`, to expose a transformed version of the original spec (see below).\n\n```ts\nregister(f, {\n  jsonSchemas: buildJsonSchemas(models),\n  swaggerOptions: {\n    routePrefix: `/swagger`,\n    swagger: {\n      info: {\n        title: `Fastify Zod Test Server`,\n        description: `Test Server for Fastify Zod`,\n        version: `0.0.0`,\n      },\n    },\n    staticCSP: true,\n    exposeRoute: true,\n    transformSpec: {\n      /* see below */\n    },\n  },\n});\n```\n\n##### `TransformSpecOptions = { cache: boolean = false, routePrefix?: string, options?: TransformOptions }`\n\nIf this property is present on the `swaggerOptions`, then in addition to routes added to `fastify` by `fastify-swagger`, a transformed version of the spec is also exposed. The transformed version is semantically equivalent but benefits from several improvements, notably first-class support for `openapitools-generator-cli` (see below).\n\n`cache` caches the transformed spec. As `SpecTransformer` can be computationally expensive, this may be useful if used in production. Defaults to `false`.\n\n`routePrefix` is the route used to expose the transformed spec, similar to the `routePrefix` option of `fastify-swagger`. Defaults to `${swaggerOptions.routePrefix}_transformed`. Since `swaggerOptions.routePrefix` defaults to `/documentation`, then the default if no `routePrefix` is provided in either options is `/documentation_transformed`.\nThe exposed routes are `/${routePrefix}/json` and `/${routePrefix}/yaml` for JSON and YAML respectively versions of the transformed spec.\n\n`options` are options passed to `SpecTransformer.transform` (see below). By default all transforms are applied.\n\n## `fastify.zod.(delete|get|head|options|patch|post|put)(url: string, config: RouteConfig, handler)`\n\nAdd route with strong typing.\n\nExample:\n\n```ts\nf.zod.put(\n  \"/:id\",\n  {\n    operationId: \"putTodoItem\",\n    params: \"TodoItemId\", // this is a key of \"models\" object above\n    body: \"TodoItem\",\n    reply: {\n      description: \"The updated todo item\",\n      key: \"TodoItem\",\n    },\n  },\n  async ({ params: { id }, body: item }) => {\n    /* ... */\n  }\n);\n```\n\n### withRefResolver: (options: FastifyDynamicSwaggerOptions) => FastifyDynamicSwaggerOptions\n\nWraps `fastify-swagger` options providing a sensible default [`refResolver` function](https://github.com/fastify/fastify-swagger#managing-your-refs) compatible with using the `$ref` function returned by buildJsonSchemas`.\n\n`register` automatically uses this under the hood so this is only required if you are using the result of `buildJsonSchemas` directly without using `register`.\n\n### SpecTransformer(spec: ApiSpec)\n\n`SpecTransformer` takes an API spec (typically the output of `/openapi/json` when using `fastify-swagger`) and applies various transforms. This class is used under the hood by `register` when `swaggerOptions.transformSpec` is set so you probably don't need to use it directly.\n\nThe transforms should typically be semantically transparent (no semantic difference) but applies some spec-level optimization and most importantly works around the many quirks of the `typescript-fetch` generator of `openapitools-generator-cli`.\n\n`SpecTransformer` is a stateful object that mutates itself internally, but the original spec object is not modified.\n\nAvailable transforms:\n\n- `rewriteSchemasAbsoluteRefs` transform\n\nTransforms `$ref`s relative to a schema to refs relative to the global spec.\n\nExample input:\n\n```json\n{\n  \"components\": {\n    \"schemas\": {\n      \"Schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"Item\": {\n            /* ... */\n          },\n          \"Items\": {\n            \"type\": \"array\",\n            \"items\": {\n              // \"#\" refers to \"Schema\" scope\n              \"$ref\": \"#/properties/Item\"\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\nOutput:\n\n```json\n{\n  \"components\": {\n    \"schemas\": {\n      \"Schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"Item\": {\n            /* ... */\n          },\n          \"Items\": {\n            \"type\": \"array\",\n            \"items\": {\n              // \"#\" refers to global scope\n              \"$ref\": \"#/components/schemas/Schema/properties/Item\"\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n- `extractSchemasProperties` transform\n\nExtract `properties` of schemas into new schemas and rewrite all `$ref`s to point to the new schema.\n\nExample input:\n\n```json\n{\n  \"components\": {\n    \"schemas\": {\n      \"Schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"Item\": {\n            /* ... */\n          },\n          \"Items\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"$ref\": \"#/components/schemas/Schema/properties/Item\"\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\nOutput:\n\n```json\n{\n  \"components\": {\n    \"schemas\": {\n      \"Schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"Item\": {\n            \"$ref\": \"#/components/schemas/Schema_TodoItem\"\n          },\n          \"Items\": {\n            \"$ref\": \"#/components/schemas/Schema_TodoItems\"\n          }\n        }\n      },\n      \"Schema_TodoItem\": {\n        /* ... */\n      },\n      \"Schema_TodoItems\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"$ref\": \"#/components/schemas/Schema_TodoItem\"\n        }\n      }\n    }\n  }\n}\n```\n\n- `mergeRefs` transform\n\nFinds deeply nested structures equivalent to existing schemas and replace them with `$ref`s to this schema. In practice this means deduplication and more importantly, referential equivalence in addition to structrural equivalence. This is especially useful for `enum`s since in TypeScript to equivalent enums are not assignable to each other.\n\nExample input:\n\n```json\n{\n  \"components\": {\n    \"schemas\": {\n      \"TodoItemState\": {\n        \"type\": \"string\",\n        \"enum\": [\"todo\", \"in progress\", \"done\"]\n      },\n      \"TodoItem\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"state\": {\n            \"type\": \"string\",\n            \"enum\": [\"todo\", \"in progress\", \"done\"]\n          }\n        }\n      }\n    }\n  }\n}\n{\n  \"mergeRefs\": [{\n    \"$ref\": \"TodoItemState#\"\n  }]\n}\n```\n\nOutput:\n\n```json\n{\n  \"components\": {\n    \"schemas\": {\n      \"TodoItemState\": {\n        \"type\": \"string\",\n        \"enum\": [\"todo\", \"in progress\", \"done\"]\n      },\n      \"TodoItem\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"state\": {\n            \"$ref\": \"#/components/schemas/TodoItemState\"\n          }\n        }\n      }\n    }\n  }\n}\n```\n\nIn the typical case, you will not create each ref explicitly, but rather use the `$ref` function provided by `buildJsonSchemas`:\n\n```ts\n{\n  mergeRefs: [$ref(\"TodoItemState\")];\n}\n```\n\n- `deleteUnusedSchemas` transform\n\nDelete all schemas that are not referenced anywhere, including in `paths`. This is useful to remove leftovers of the previous transforms.\n\nExample input:\n\n```json\n{\n  \"components\": {\n    \"schemas\": {\n      // Schema_TodoItem has been extracted,\n      // there are no references to this anymore\n      \"Schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"TodoItem\": {\n            \"$ref\": \"#/components/schemas/Schema_TodoItem\"\n          }\n        }\n      },\n      \"Schema_TodoItem\": {\n        /* ... */\n      }\n    }\n  },\n  \"paths\": {\n    \"/item\": {\n      \"get\": {\n        \"responses\": {\n          \"200\": {\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  // This used to be #/components/Schema/properties/TodoItem\n                  // but has been transformed by extractSchemasProperties\n                  \"$ref\": \"#/components/schemas/Schema_TodoItem\"\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\nOutput:\n\n```json\n{\n  \"components\": {\n    \"schemas\": {\n      // \"Schema\" has been deleted\n      \"Schema_TodoItem\": {\n        /* ... */\n      }\n    }\n  },\n  \"paths\": {\n    /* ... */\n  }\n}\n```\n\n- `schemaKeys` option\n\nThis option controls the behavior of newly created schemas (e.g. during `extractSchemasProperties` transform).\n\nAvailable configurations:\n\n- `schemaKeys.removeInitialSchemasPrefix`: remove `schemaKey` prefix of initial schemas to create less verbose schema names, e.g. `TodoState` instead of `MySchema_TodoState`\n\n- `schemaKeys.changeCase`: change case of generated schema keys. Defaults to `preserve`. In this case, original schema key and property key prefixes are preserved, and segments are underscore-separated.\n\nIn case of schema key conflict, an error will be thrown during `transform`.\n\n#### SpecTransformer#transform(options: TransformOptions)\n\nApplies the given transforms.\n\nDefault options:\n\n```ts\n{\n  rewriteAbsoluteRefs?: boolean = true,\n  extractSchemasProperties?: boolean = true,\n  mergeRefs?: { $ref: string }[] = [],\n  deleteUnusedSchemas?: boolean = true,\n  schemaKeys?: {\n    removeInitialSchemasPrefix: boolean = false,\n    changeCase: \"preserve\" | \"camelCase\" | \"PascalCase\" | \"snake_case\" | \"param-case\" = \"preserve\"\n  } = {}\n}\n```\n\nAll transforms default to `true` except `mergeRefs` that you must explicitly configure.\n\n#### SpecTransformer#getSpec(): Spec\n\nReturn the current state of the spec. This is typically called after `transform` to use the transformed spec.\n\n## Usage with `openapitools`\n\nTogether with `fastify-swagger`, and `SpecTransformer` this library supports downstream client code generation using `openapitools-generator-cli`.\n\nRecommended use is with `register` and `fastify.inject`.\n\nFor this you need to first generate the spec file, then run `openapitools-generator`:\n\n```ts\nconst jsonSchemas = buildJsonSchemas(models);\n\nregister(f, {\n  jsonSchemas,\n  swaggerOptions: {\n    openapi: {\n      /* ... */\n    },\n    exposeRoute: true,\n    transformSpec: {\n      routePrefix: \"/openapi_transformed\",\n      options: {\n        mergeRefs: [$ref(\"TodoItemState\")],\n      },\n    },\n  },\n});\n\nconst spec = await f\n  .inject({\n    method: \"get\",\n    url: \"/openapi_transformed/json\",\n  })\n  .then((spec) => spec.json());\n\nwriteFileSync(\"openapi-spec.json\", JSON.stringify(spec), { encoding: \"utf-8\" });\n```\n\n`openapi-generator-cli generate`\n\nWe recommend running this as part as the build step of your app, see [package.json](./package.json).\n\n## Caveats\n\nUnfortunately and despite best efforts by `SpecTransformer`, the OpenAPI generator has many quirks and limited support for some features. Complex nested arrays are sometimes not validated / parsed correctly, discriminated unions have limited support, etc.\n\n## License\n\nMIT License Copyright (c) 2022 Elie Rotenberg\n","readmeFilename":"README.md"}