From 366ccdc1eb3e670da7ae28d94f6c2be72e238ee3 Mon Sep 17 00:00:00 2001 From: Miron Balcerzak Date: Sun, 10 May 2026 22:56:26 +0200 Subject: [PATCH 1/2] feat(core): added factoryMethods --- .../docs/reference/configuration/output.mdx | 66 ++ packages/angular/src/http-client.test.ts | 7 + packages/angular/src/http-resource.test.ts | 7 + packages/core/src/generators/factory.test.ts | 588 ++++++++++++++++++ packages/core/src/generators/factory.ts | 499 +++++++++++++++ packages/core/src/generators/index.ts | 1 + .../src/generators/schema-definition.test.ts | 17 + .../core/src/generators/schema-definition.ts | 26 + packages/core/src/test-utils/context.ts | 7 + packages/core/src/types.ts | 27 + packages/core/src/utils/path.ts | 8 + packages/core/src/writers/schemas.ts | 177 +++++- .../mock/src/faker/getters/combine.test.ts | 7 + .../orval/src/utils/options.factory.test.ts | 86 +++ packages/orval/src/utils/options.ts | 34 + packages/orval/src/write-specs.ts | 6 + packages/solid-start/src/index.test.ts | 7 + .../factory-methods/combined/endpoints.ts | 25 + .../combined/model/circularChild.ts | 11 + .../combined/model/circularParent.ts | 11 + .../combined/model/deepCircularA.ts | 11 + .../combined/model/deepCircularB.ts | 11 + .../combined/model/deepCircularC.ts | 11 + .../combined/model/factoryMethods.ts | 61 ++ .../factory-methods/combined/model/index.ts | 15 + .../factory-methods/combined/model/status.ts | 15 + .../factory-methods/combined/model/user.ts | 19 + .../factory-methods/inline/endpoints.ts | 25 + .../inline/model/circularChild.ts | 17 + .../inline/model/circularParent.ts | 17 + .../inline/model/deepCircularA.ts | 17 + .../inline/model/deepCircularB.ts | 17 + .../inline/model/deepCircularC.ts | 17 + .../factory-methods/inline/model/index.ts | 14 + .../factory-methods/inline/model/status.ts | 19 + .../factory-methods/inline/model/user.ts | 33 + .../factory-methods/separate/endpoints.ts | 25 + .../separate/model/circularChild.factory.ts | 15 + .../separate/model/circularChild.ts | 11 + .../separate/model/circularParent.factory.ts | 15 + .../separate/model/circularParent.ts | 11 + .../separate/model/deepCircularA.factory.ts | 15 + .../separate/model/deepCircularA.ts | 11 + .../separate/model/deepCircularB.factory.ts | 15 + .../separate/model/deepCircularB.ts | 11 + .../separate/model/deepCircularC.factory.ts | 15 + .../separate/model/deepCircularC.ts | 11 + .../factory-methods/separate/model/index.ts | 21 + .../separate/model/status.factory.ts | 12 + .../factory-methods/separate/model/status.ts | 15 + .../separate/model/user.factory.ts | 24 + .../factory-methods/separate/model/user.ts | 19 + tests/api-generation.spec.ts | 1 + tests/configs/factory-methods.config.ts | 43 ++ tests/package.json | 1 + tests/specifications/factory-methods.yaml | 68 ++ 56 files changed, 2291 insertions(+), 4 deletions(-) create mode 100644 packages/core/src/generators/factory.test.ts create mode 100644 packages/core/src/generators/factory.ts create mode 100644 packages/orval/src/utils/options.factory.test.ts create mode 100644 tests/__snapshots__/factory-methods/combined/endpoints.ts create mode 100644 tests/__snapshots__/factory-methods/combined/model/circularChild.ts create mode 100644 tests/__snapshots__/factory-methods/combined/model/circularParent.ts create mode 100644 tests/__snapshots__/factory-methods/combined/model/deepCircularA.ts create mode 100644 tests/__snapshots__/factory-methods/combined/model/deepCircularB.ts create mode 100644 tests/__snapshots__/factory-methods/combined/model/deepCircularC.ts create mode 100644 tests/__snapshots__/factory-methods/combined/model/factoryMethods.ts create mode 100644 tests/__snapshots__/factory-methods/combined/model/index.ts create mode 100644 tests/__snapshots__/factory-methods/combined/model/status.ts create mode 100644 tests/__snapshots__/factory-methods/combined/model/user.ts create mode 100644 tests/__snapshots__/factory-methods/inline/endpoints.ts create mode 100644 tests/__snapshots__/factory-methods/inline/model/circularChild.ts create mode 100644 tests/__snapshots__/factory-methods/inline/model/circularParent.ts create mode 100644 tests/__snapshots__/factory-methods/inline/model/deepCircularA.ts create mode 100644 tests/__snapshots__/factory-methods/inline/model/deepCircularB.ts create mode 100644 tests/__snapshots__/factory-methods/inline/model/deepCircularC.ts create mode 100644 tests/__snapshots__/factory-methods/inline/model/index.ts create mode 100644 tests/__snapshots__/factory-methods/inline/model/status.ts create mode 100644 tests/__snapshots__/factory-methods/inline/model/user.ts create mode 100644 tests/__snapshots__/factory-methods/separate/endpoints.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/circularChild.factory.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/circularChild.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/circularParent.factory.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/circularParent.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/deepCircularA.factory.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/deepCircularA.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/deepCircularB.factory.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/deepCircularB.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/deepCircularC.factory.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/deepCircularC.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/index.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/status.factory.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/status.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/user.factory.ts create mode 100644 tests/__snapshots__/factory-methods/separate/model/user.ts create mode 100644 tests/configs/factory-methods.config.ts create mode 100644 tests/specifications/factory-methods.yaml diff --git a/docs/content/docs/reference/configuration/output.mdx b/docs/content/docs/reference/configuration/output.mdx index 358de15cf7..545f3d1127 100644 --- a/docs/content/docs/reference/configuration/output.mdx +++ b/docs/content/docs/reference/configuration/output.mdx @@ -1697,6 +1697,72 @@ export interface Pet { } ``` +## factoryMethods + +**Type:** `Object` +**Default:** `{ generate: false }` + +Generate factory methods for DTOs (Data Transfer Objects) initialized with safe default values. Useful for testing and initializing empty state. +Functionality handles OpenAPI `readOnly` and `writeOnly` flags to generate appropriate payload structures: +- **Required properties:** Always included in the factory output, regardless of their visibility flags. +- **Optional `readOnly` properties:** Always omitted from the factory output, as they would be dropped by the server. +- **Optional `writeOnly` properties:** Always included in the factory output (even if `optionalPropertyStrategy` is set to `'omit'`). + +```ts title="orval.config.ts" +export default defineConfig({ + petstore: { + output: { + factoryMethods: { + generate: true, + functionNamePrefix: 'create', + mode: 'separate-file', + optionalPropertyStrategy: 'include', + outputDirectory: `#output.workspace.schemas`, + }, + }, + }, +}); +``` + +### generate + +**Type:** `Boolean` +**Default:** `false` + +Enables or disables factory generation. + +### functionNamePrefix + +**Type:** `String` +**Default:** `'create'` + +Prefix for the generated factory function names. + +### mode + +**Type:** `'inline-with-schema' | 'separate-file' | 'combined-separate-file'` +**Default:** `'separate-file'` + +Where to generate the factory methods: +- `inline-with-schema`: Appends the factory function to the schema file. +- `separate-file`: Creates a `{schema}.factory.ts` with factory method. By default it is placed next to schema file. +- `combined-separate-file`: Aggregates all factory methods into a single `factoryMethods.ts` file. + +### optionalPropertyStrategy + +**Type:** `'include' | 'omit'` +**Default:** `'include'` + +Determines whether optional schema properties are included in the default factory output. + +### outputDirectory + +**Type:** `String` +**Default:** `#output.workspace.schemas` + +Defaults to the value configured in `#output.workspace.schemas`. +Determines where factory methods will be generated (can be used to generated methods away from schema directory). +Takes effect only when used `mode` is `separate-file` or `combined-separate-file`. --- ## Other Options diff --git a/packages/angular/src/http-client.test.ts b/packages/angular/src/http-client.test.ts index e00689043a..516027ecff 100644 --- a/packages/angular/src/http-client.test.ts +++ b/packages/angular/src/http-client.test.ts @@ -125,6 +125,13 @@ const createOutput = ( optionsParamRequired: false, unionAddMissingProperties: false, propertySortOrder: 'Specification', + factoryMethods: { + generate: false, + functionNamePrefix: 'create', + mode: 'inline-with-schema', + outputDirectory: '', + optionalPropertyStrategy: 'omit', + }, ...overrides, } satisfies NormalizedOutputOptions; diff --git a/packages/angular/src/http-resource.test.ts b/packages/angular/src/http-resource.test.ts index e30cd85be8..4d7bef44d1 100644 --- a/packages/angular/src/http-resource.test.ts +++ b/packages/angular/src/http-resource.test.ts @@ -135,6 +135,13 @@ const createOutput = ( optionsParamRequired: false, unionAddMissingProperties: false, propertySortOrder: 'Specification', + factoryMethods: { + generate: false, + functionNamePrefix: 'create', + mode: 'inline-with-schema', + outputDirectory: '', + optionalPropertyStrategy: 'omit', + }, ...overrides, } satisfies NormalizedOutputOptions; diff --git a/packages/core/src/generators/factory.test.ts b/packages/core/src/generators/factory.test.ts new file mode 100644 index 0000000000..13ffca4a60 --- /dev/null +++ b/packages/core/src/generators/factory.test.ts @@ -0,0 +1,588 @@ +import { describe, expect, it } from 'vitest'; + +import type { + ContextSpec, + OpenApiDocument, + OpenApiSchemaObject, +} from '../types'; +import { NamingConvention } from '../types'; +import { generateFactory } from './factory'; + +const baseFactoryMethods = { + generate: true, + functionNamePrefix: 'create', + mode: 'inline-with-schema', + outputDirectory: '', + optionalPropertyStrategy: 'omit', +}; + +const baseOverride = { + useDates: false, + namingConvention: {}, + components: { + schemas: { suffix: '', itemSuffix: '' }, + responses: { suffix: '' }, + parameters: { suffix: '' }, + requestBodies: { suffix: '' }, + }, +}; + +const createMockContext = ( + overrides: Record = {}, +): ContextSpec => { + const { override: overrideOverride, ...rest } = overrides; + return { + target: 'test', + workspace: 'test', + spec: { + components: { + schemas: { + RefTarget: { + type: 'object', + properties: { id: { type: 'string' } }, + }, + CircularChild: { + type: 'object', + properties: { + parent: { $ref: '#/components/schemas/CircularParent' }, + }, + }, + CircularParent: { + type: 'object', + properties: { + child: { $ref: '#/components/schemas/CircularChild' }, + }, + }, + DeepCircularA: { + type: 'object', + properties: { b: { $ref: '#/components/schemas/DeepCircularB' } }, + }, + DeepCircularB: { + type: 'object', + properties: { c: { $ref: '#/components/schemas/DeepCircularC' } }, + }, + DeepCircularC: { + type: 'object', + properties: { a: { $ref: '#/components/schemas/DeepCircularA' } }, + }, + }, + }, + } as unknown as OpenApiDocument, + output: { + target: '', + namingConvention: NamingConvention.CAMEL_CASE, + fileExtension: '.ts', + mode: 'single' as unknown, + client: 'axios' as unknown, + httpClient: 'axios' as unknown, + clean: false, + docs: false, + prettier: false, + biome: false, + headers: false, + indexFiles: false, + allParamsOptional: false, + urlEncodeParameters: false, + unionAddMissingProperties: false, + optionsParamRequired: false, + propertySortOrder: 'Alphabetical' as unknown, + factoryMethods: baseFactoryMethods, + override: { + ...baseOverride, + ...(overrideOverride as object | undefined), + }, + ...rest, + } as unknown, + } as unknown as ContextSpec; +}; + +describe('generateFactory', () => { + it('returns undefined if schema is not an object/combination', () => { + const schema: OpenApiSchemaObject = { type: 'string' }; + expect( + generateFactory(schema, 'StringSchema', createMockContext()), + ).toBeUndefined(); + }); + + it('generates factory for basic object with omitted optionals', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['id', 'name'], + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + isActive: { type: 'boolean' }, + tags: { type: 'array', items: { type: 'string' } }, + }, + }; + + const result = generateFactory(schema, 'User', createMockContext()); + expect(result).toBeDefined(); + expect(result?.model).toContain('export function createUser(): User {'); + expect(result?.model).toContain('id: 0'); + expect(result?.model).toContain("name: ''"); + expect(result?.model).not.toContain('isActive'); + expect(result?.model).not.toContain('tags'); + }); + + it('includes optional properties when strategy is include', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + properties: { + isActive: { type: 'boolean' }, + tags: { type: 'array', items: { type: 'string' } }, + score: { type: 'number' }, + }, + }; + + const result = generateFactory( + schema, + 'User', + createMockContext({ + factoryMethods: { + ...baseFactoryMethods, + optionalPropertyStrategy: 'include', + }, + }), + ); + expect(result?.model).toContain('isActive: false'); + expect(result?.model).toContain('tags: []'); + expect(result?.model).toContain('score: 0'); + }); + + it('omits optional readOnly properties but forcefully keeps optional writeOnly properties', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['reqReadOnly'], + properties: { + normalOpt: { type: 'string' }, + readOnlyOpt: { type: 'string', readOnly: true }, + writeOnlyOpt: { type: 'string', writeOnly: true }, + reqReadOnly: { type: 'string', readOnly: true }, + }, + }; + + const result = generateFactory(schema, 'Obj', createMockContext()); + expect(result?.model).not.toContain("normalOpt: ''"); + expect(result?.model).not.toContain("readOnlyOpt: ''"); + expect(result?.model).toContain("writeOnlyOpt: ''"); + expect(result?.model).toContain("reqReadOnly: ''"); + }); + + it('handles nested objects', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['nested'], + properties: { + nested: { + type: 'object', + required: ['val'], + properties: { val: { type: 'number' } }, + }, + }, + }; + + const result = generateFactory(schema, 'NestedObj', createMockContext()); + expect(result?.model).toContain('nested: {\n val: 0\n }'); + }); + + it('handles $ref and imports correctly', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['target'], + properties: { + target: { $ref: '#/components/schemas/RefTarget' }, + }, + }; + + const result = generateFactory(schema, 'WithRef', createMockContext()); + expect(result?.model).toContain('target: createRefTarget()'); + expect(result?.imports).toContainEqual({ + name: 'createRefTarget', + importPath: './refTarget', + isConstant: true, + }); + expect(result?.imports).toContainEqual({ name: 'RefTarget' }); + }); + + it('handles mode: separate-file import paths', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['target'], + properties: { + target: { $ref: '#/components/schemas/RefTarget' }, + }, + }; + + const result = generateFactory( + schema, + 'WithRef', + createMockContext({ + factoryMethods: { ...baseFactoryMethods, mode: 'separate-file' }, + }), + ); + expect(result?.imports).toContainEqual({ + name: 'createRefTarget', + importPath: './refTarget.factory', + isConstant: true, + }); + }); + + it('handles mode: combined-separate-file import paths', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['target'], + properties: { + target: { $ref: '#/components/schemas/RefTarget' }, + }, + }; + + const result = generateFactory( + schema, + 'WithRefCombined', + createMockContext({ + factoryMethods: { + ...baseFactoryMethods, + mode: 'combined-separate-file', + }, + }), + ); + expect(result?.imports).not.toContainEqual({ + name: 'createRefTarget', + importPath: './factoryMethods', + isConstant: true, + }); + }); + + it('breaks cycles automatically (direct parent child)', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['child'], + properties: { + child: { $ref: '#/components/schemas/CircularChild' }, + }, + }; + + const result = generateFactory( + schema, + 'CircularParent', + createMockContext(), + ); + expect(result?.model).toContain('child: {} as CircularChild'); + }); + + it('breaks deep cycles automatically', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['b'], + properties: { + b: { $ref: '#/components/schemas/DeepCircularB' }, + }, + }; + + const result = generateFactory( + schema, + 'DeepCircularA', + createMockContext(), + ); + expect(result?.model).toContain('b: {} as DeepCircularB'); + }); + + it('uses string literal for date/date-time format when useDates is false', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['createdAt'], + properties: { + createdAt: { type: 'string', format: 'date-time' }, + }, + }; + + const result = generateFactory(schema, 'Dates', createMockContext()); + expect(result?.model).toContain("createdAt: '"); + }); + + it('uses Date object for date/date-time format when useDates is true', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['createdAt'], + properties: { + createdAt: { type: 'string', format: 'date-time' }, + }, + }; + + const result = generateFactory( + schema, + 'Dates', + createMockContext({ override: { useDates: true } }), + ); + expect(result?.model).toContain('createdAt: new Date(0)'); + }); + + it('handles date/date-time default values with useDates = true', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['dateField'], + properties: { + dateField: { type: 'string', format: 'date', default: '2026-01-01' }, + }, + }; + + const result = generateFactory( + schema, + 'DateDefaultObj', + createMockContext({ override: { useDates: true } }), + ); + expect(result?.model).toContain("dateField: new Date('2026-01-01')"); + }); + + it('handles enums with strings and numbers', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['status', 'count'], + properties: { + status: { type: 'string', enum: ['ACTIVE', 'INACTIVE'] }, + count: { type: 'number', enum: [100, 200] }, + }, + }; + + const result = generateFactory(schema, 'WithEnum', createMockContext()); + expect(result?.model).toContain('status: "ACTIVE"'); + expect(result?.model).toContain('count: 100'); + }); + + it('handles allOf appropriately', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + allOf: [ + { + type: 'object', + required: ['a'], + properties: { a: { type: 'string' } }, + }, + { + type: 'object', + required: ['b'], + properties: { b: { type: 'number' } }, + }, + ], + }; + + const result = generateFactory(schema, 'Combined', createMockContext()); + expect(result?.model).toContain( + "Object.assign({}, {\n a: ''\n }, {\n b: 0\n })", + ); + }); + + it('sorts properties alphabetically when configured', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['z', 'a', 'm'], + properties: { + z: { type: 'number' }, + a: { type: 'string' }, + m: { type: 'boolean' }, + }, + }; + + const result = generateFactory( + schema, + 'SortedObj', + createMockContext({ propertySortOrder: 'Alphabetical' }), + ); + expect(result?.model).toMatch(/a: '',[\s\S]*m: false,[\s\S]*z: 0/); + }); + + it('handles const, default, null, array type arrays, and array constraints', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: [ + 'withConst', + 'withDefault', + 'withDefaultObj', + 'nullField', + 'multiType', + 'arrayConstraints', + 'tupleArray', + ], + properties: { + withConst: { type: 'string', const: 'STATIC_VALUE' }, + withDefault: { type: 'number', default: 42 }, + withDefaultObj: { type: 'object', default: { key: 'val' } }, + nullField: { type: 'null' as unknown as string }, + multiType: { type: ['string', 'null'] as unknown as string[] }, + arrayConstraints: { + type: 'array', + minItems: 2, + items: { type: 'string' }, + }, + tupleArray: { + type: 'array', + prefixItems: [{ type: 'number' }, { type: 'boolean' }], + }, + }, + }; + + const result = generateFactory(schema, 'AdvancedObj', createMockContext()); + expect(result?.model).toContain('withConst: "STATIC_VALUE"'); + expect(result?.model).toContain('withDefault: 42'); + expect(result?.model).toContain('withDefaultObj: {"key":"val"}'); + expect(result?.model).toContain('nullField: null'); + expect(result?.model).toContain("multiType: ''"); + expect(result?.model).toContain("arrayConstraints: ['', '']"); + expect(result?.model).toContain('tupleArray: [0, false]'); + }); + + it('handles anyOf and oneOf by picking the first option', () => { + const schemaOneOf: OpenApiSchemaObject = { + type: 'object', + oneOf: [ + { + type: 'object', + required: ['a'], + properties: { a: { type: 'string' } }, + }, + { + type: 'object', + required: ['b'], + properties: { b: { type: 'number' } }, + }, + ], + }; + + const resultOneOf = generateFactory( + schemaOneOf, + 'Combined', + createMockContext(), + ); + expect(resultOneOf?.model).toContain("{\n a: ''\n }"); + + const schemaAnyOf: OpenApiSchemaObject = { + type: 'object', + anyOf: [ + { + type: 'object', + required: ['x'], + properties: { x: { type: 'boolean' } }, + }, + { + type: 'object', + required: ['y'], + properties: { y: { type: 'string' } }, + }, + ], + }; + + const resultAnyOf = generateFactory( + schemaAnyOf, + 'CombinedAny', + createMockContext(), + ); + expect(resultAnyOf?.model).toContain('{\n x: false\n }'); + }); + + it('handles root array schemas', () => { + const schema: OpenApiSchemaObject = { + type: 'array', + items: { type: 'string' }, + }; + + const result = generateFactory(schema, 'StringArray', createMockContext()); + expect(result?.model).toContain( + 'export function createStringArray(): StringArray {', + ); + expect(result?.model).toContain('return []'); + }); + + it('handles root enum schemas', () => { + const schema: OpenApiSchemaObject = { + type: 'string', + enum: ['FIRST', 'SECOND'], + }; + + const result = generateFactory(schema, 'StringEnum', createMockContext()); + expect(result?.model).toContain('return "FIRST"'); + }); + + it('infers implicit types for arrays and enums', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: [ + 'implicitArray', + 'implicitEnum', + 'implicitNumEnum', + 'implicitBoolEnum', + ], + properties: { + implicitArray: { items: { type: 'number' } }, + implicitEnum: { enum: ['A', 'B'] }, + implicitNumEnum: { enum: [1, 2] }, + implicitBoolEnum: { enum: [true, false] }, + }, + }; + + const result = generateFactory(schema, 'ImplicitObj', createMockContext()); + expect(result?.model).toContain('implicitArray: []'); + expect(result?.model).toContain('implicitEnum: "A"'); + expect(result?.model).toContain('implicitNumEnum: 1'); + expect(result?.model).toContain('implicitBoolEnum: true'); + }); + + it('returns undefined as unknown for unsupported types', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + required: ['weirdField'], + properties: { + weirdField: { type: 'weird' as unknown }, + }, + }; + + const result = generateFactory(schema, 'WeirdObj', createMockContext()); + expect(result?.model).toContain('weirdField: undefined as unknown'); + }); + + it('escapes quotes in string enums', () => { + const schema: OpenApiSchemaObject = { + type: 'string', + enum: ["O'Reilly"], + }; + + const result = generateFactory(schema, 'EscapedEnum', createMockContext()); + expect(result?.model).toContain(`return "O'Reilly"`); + }); + + it('escapes backslashes and quotes properly', () => { + const schema: OpenApiSchemaObject = { + type: 'string', + enum: [String.raw`C:\temp\folder\file's`], + }; + + const result = generateFactory( + schema, + 'EscapedBackslashEnum', + createMockContext(), + ); + expect(result?.model).toContain( + String.raw`return "C:\\temp\\folder\\file's"`, + ); + }); + + it('omits optional $ref properties when readOnly is set on the reference itself', () => { + const schema: OpenApiSchemaObject = { + type: 'object', + properties: { + user: { + $ref: '#/components/schemas/RefTarget', + readOnly: true, + } as unknown as OpenApiSchemaObject, + token: { + $ref: '#/components/schemas/RefTarget', + writeOnly: true, + } as unknown as OpenApiSchemaObject, + }, + }; + + const result = generateFactory(schema, 'AuthPayload', createMockContext()); + expect(result?.model).not.toContain('user: createRefTarget()'); + expect(result?.model).toContain('token: createRefTarget()'); + }); +}); diff --git a/packages/core/src/generators/factory.ts b/packages/core/src/generators/factory.ts new file mode 100644 index 0000000000..f235db7d78 --- /dev/null +++ b/packages/core/src/generators/factory.ts @@ -0,0 +1,499 @@ +import { resolveRef } from '../resolvers/ref'; +import type { + ContextSpec, + GeneratorImport, + OpenApiReferenceObject, + OpenApiSchemaObject, +} from '../types'; +import { PropertySortOrder } from '../types'; +import { + conventionName, + getFileInfo, + isString, + logWarning, + pascal, + upath, +} from '../utils'; + +const circularRefCache = new WeakMap>(); + +function getSchemasPath(context: ContextSpec): string { + const { schemas, target } = context.output; + if (schemas) { + const schemasPath = isString(schemas) ? schemas : schemas.path; + return upath.normalizeSafe(schemasPath); + } + const { dirname, filename } = getFileInfo(target); + return upath.joinSafe(dirname, filename + '.schemas'); +} + +function getSchemaImportPath( + refName: string, + context: ContextSpec, +): string | undefined { + if (context.output.factoryMethods.mode === 'inline-with-schema') { + return undefined; + } + let outputDir = context.output.factoryMethods.outputDirectory; + let schemasPath = getSchemasPath(context); + + if (context.output.workspace) { + if (outputDir && !upath.isAbsolute(outputDir)) { + outputDir = upath.resolve(context.output.workspace, outputDir); + } + if (schemasPath && !upath.isAbsolute(schemasPath)) { + schemasPath = upath.resolve(context.output.workspace, schemasPath); + } + } + + const relativePath = outputDir + ? upath.relativeSafe(outputDir, schemasPath) + : './'; + const baseName = conventionName(refName, context.output.namingConvention); + return upath.joinSafe(relativePath, baseName); +} + +type SchemaArray = (OpenApiSchemaObject | OpenApiReferenceObject)[]; + +interface ResolvedRef { + imports: GeneratorImport[]; + schema: OpenApiSchemaObject; +} + +function isReference( + schema: OpenApiSchemaObject | OpenApiReferenceObject, +): schema is OpenApiReferenceObject { + return '$ref' in schema; +} + +function getResolvedRef( + schema: OpenApiReferenceObject, + context: ContextSpec, +): ResolvedRef { + return resolveRef(schema, context) as ResolvedRef; +} + +function getProperties( + schema: OpenApiSchemaObject, +): Record { + return ( + (schema.properties as + | Record + | undefined) ?? {} + ); +} + +function getItems( + schema: OpenApiSchemaObject, +): OpenApiSchemaObject | OpenApiReferenceObject | undefined { + return schema.items as + | OpenApiSchemaObject + | OpenApiReferenceObject + | undefined; +} + +function getAdditionalProperties( + schema: OpenApiSchemaObject, +): OpenApiSchemaObject | OpenApiReferenceObject | boolean | undefined { + return schema.additionalProperties as + | OpenApiSchemaObject + | OpenApiReferenceObject + | boolean + | undefined; +} + +function getSchemas(schemas: unknown): SchemaArray | undefined { + return schemas as SchemaArray | undefined; +} + +function getExtendedProps(schema: OpenApiSchemaObject): { + constValue: unknown; + prefixItems: SchemaArray | undefined; + minItems: number | undefined; +} { + const extended = schema as OpenApiSchemaObject & { + const?: unknown; + prefixItems?: SchemaArray; + minItems?: number; + }; + return { + constValue: extended.const, + prefixItems: extended.prefixItems, + minItems: extended.minItems, + }; +} + +export function generateFactory( + schema: OpenApiSchemaObject, + name: string, + context: ContextSpec, +): { model: string; imports: GeneratorImport[] } | undefined { + if (!canGenerateSchema(schema)) return undefined; + + const { functionNamePrefix, mode } = context.output.factoryMethods; + const factoryName = `${functionNamePrefix}${pascal(name)}`; + const imports: GeneratorImport[] = []; + const payload = buildPayload(schema, context, [name], imports); + + if (mode !== 'inline-with-schema') { + const schemaImportPath = getSchemaImportPath(name, context); + imports.push({ name, importPath: schemaImportPath }); + } + + return { + model: `export function ${factoryName}(): ${name} {\n return ${payload};\n}\n`, + imports, + }; +} + +function canGenerateSchema(schema: OpenApiSchemaObject): boolean { + return ( + schema.type === 'object' || + schema.type === 'array' || + !!schema.properties || + !!schema.allOf || + !!schema.oneOf || + !!schema.anyOf || + !!schema.items || + !!schema.enum + ); +} + +function hasCircularReference( + target: OpenApiSchemaObject | OpenApiReferenceObject, + sourceName: string, + context: ContextSpec, + visited = new Set(), +): boolean { + if (isReference(target)) { + const { imports, schema } = getResolvedRef(target, context); + const refName = imports[0]?.name; + if (refName === sourceName) return true; + if (refName && visited.has(refName)) return false; + if (refName) visited.add(refName); + + let cache = circularRefCache.get(context); + if (!cache) { + cache = new Map(); + circularRefCache.set(context, cache); + } + const cacheKey = refName ? `${sourceName}::${refName}` : undefined; + if (cacheKey) { + const cached = cache.get(cacheKey); + if (cached !== undefined) { + return cached; + } + } + + const result = hasCircularReference(schema, sourceName, context, visited); + + if (cacheKey) { + cache.set(cacheKey, result); + } + return result; + } + + const check = (schemas?: SchemaArray): boolean => + schemas?.some((s) => + hasCircularReference(s, sourceName, context, visited), + ) ?? false; + + const items = getItems(target); + const additionalProperties = getAdditionalProperties(target); + + return ( + check(getSchemas(target.allOf)) || + check(getSchemas(target.oneOf)) || + check(getSchemas(target.anyOf)) || + Object.values(getProperties(target)).some((s) => + hasCircularReference(s, sourceName, context, visited), + ) || + (!!items && hasCircularReference(items, sourceName, context, visited)) || + (typeof additionalProperties === 'object' && + hasCircularReference(additionalProperties, sourceName, context, visited)) + ); +} + +function buildPayload( + target: OpenApiSchemaObject | OpenApiReferenceObject, + context: ContextSpec, + parents: string[], + imports: GeneratorImport[], +): string { + if (isReference(target)) { + return buildRefPayload(target, context, parents, imports); + } + + const schema = target; + + if (schema.allOf) + return buildAllOfPayload( + getSchemas(schema.allOf) ?? [], + context, + parents, + imports, + ); + if (schema.oneOf) + return buildFirstOfPayload( + getSchemas(schema.oneOf) ?? [], + context, + parents, + imports, + ); + if (schema.anyOf) + return buildFirstOfPayload( + getSchemas(schema.anyOf) ?? [], + context, + parents, + imports, + ); + + const { constValue } = getExtendedProps(schema); + if (constValue !== undefined) return formatValue(constValue); + if (schema.default !== undefined) return buildDefaultPayload(schema, context); + + const schemaType = inferSchemaType(schema); + + if (schemaType === 'object' || schema.properties) + return buildObjectPayload(schema, context, parents, imports); + if (schemaType === 'array') + return buildArrayPayload(schema, context, parents, imports); + + return buildPrimitivePayload(schema, schemaType, context); +} + +function buildRefPayload( + schema: OpenApiReferenceObject, + context: ContextSpec, + parents: string[], + imports: GeneratorImport[], +): string { + const { schema: resolved, imports: refImports } = getResolvedRef( + schema, + context, + ); + const refName = refImports[0]?.name; + + if (!refName) return '{}'; + + if ( + parents.includes(refName) || + hasCircularReference(resolved, parents[0], context) + ) { + imports.push({ + name: refName, + importPath: getSchemaImportPath(refName, context), + }); + return `{} as ${refName}`; + } + + const { functionNamePrefix, mode } = context.output.factoryMethods; + const refFactoryName = `${functionNamePrefix}${pascal(refName)}`; + + if (mode !== 'combined-separate-file') { + const importPath = resolveImportPath(mode, refName, context); + imports.push({ name: refFactoryName, importPath, isConstant: true }); + } + + imports.push({ + name: refName, + importPath: getSchemaImportPath(refName, context), + }); + + return `${refFactoryName}()`; +} + +function resolveImportPath( + mode: string, + refName: string, + context: ContextSpec, +): string | undefined { + const baseName = conventionName(refName, context.output.namingConvention); + switch (mode) { + case 'separate-file': { + return `./${baseName}.factory`; + } + case 'combined-separate-file': { + return `./${conventionName('factoryMethods', context.output.namingConvention)}`; + } + case 'inline-with-schema': { + return `./${baseName}`; + } + } +} + +function buildAllOfPayload( + allOf: SchemaArray, + context: ContextSpec, + parents: string[], + imports: GeneratorImport[], +): string { + const payloads = allOf.map((s) => buildPayload(s, context, parents, imports)); + return payloads.length > 0 + ? `Object.assign({}, ${payloads.join(', ')})` + : '{}'; +} + +function buildFirstOfPayload( + schemas: SchemaArray, + context: ContextSpec, + parents: string[], + imports: GeneratorImport[], +): string { + const first = schemas[0]; + return first ? buildPayload(first, context, parents, imports) : '{}'; +} + +function buildObjectPayload( + schema: OpenApiSchemaObject, + context: ContextSpec, + parents: string[], + imports: GeneratorImport[], +): string { + const { optionalPropertyStrategy } = context.output.factoryMethods; + const props = getProperties(schema); + const requiredProps: string[] = + (schema.required as string[] | undefined) ?? []; + const entries = Object.entries(props); + + if (context.output.propertySortOrder === PropertySortOrder.ALPHABETICAL) { + entries.sort(([a], [b]) => a.localeCompare(b)); + } + + const includeOptional = optionalPropertyStrategy === 'include'; + const lines: string[] = []; + + for (const [key, prop] of entries) { + const isRequired = requiredProps.includes(key); + const resolved = isReference(prop) + ? getResolvedRef(prop, context).schema + : prop; + + const isReadOnly = + !!(prop as OpenApiSchemaObject).readOnly || !!resolved.readOnly; + const isWriteOnly = + !!(prop as OpenApiSchemaObject).writeOnly || !!resolved.writeOnly; + + if (!isRequired) { + if (isReadOnly) continue; + if (!isWriteOnly && !includeOptional) continue; + } + + const payload = buildPayload(prop, context, parents, imports); + const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) + ? key + : JSON.stringify(key); + lines.push(`${safeKey}: ${payload}`); + } + + return `{\n ${lines.join(',\n ')}\n }`; +} + +function buildArrayPayload( + schema: OpenApiSchemaObject, + context: ContextSpec, + parents: string[], + imports: GeneratorImport[], +): string { + const { prefixItems, minItems } = getExtendedProps(schema); + const items = getItems(schema); + + if (prefixItems && prefixItems.length > 0) { + const payloads = prefixItems.map((item) => + buildPayload(item, context, parents, imports), + ); + return `[${payloads.join(', ')}]`; + } + + if (minItems && items) { + const MAX_MIN_ITEMS = 50; + if (minItems > MAX_MIN_ITEMS) { + logWarning( + `Warning: minItems is ${minItems}, capping at ${MAX_MIN_ITEMS} to prevent massive payload.`, + ); + } + const count = Math.min(minItems, MAX_MIN_ITEMS); + const itemPayload = buildPayload(items, context, parents, imports); + return `[${Array.from({ length: count }) + .fill(itemPayload) + .join(', ')}]`; + } + + return '[]'; +} + +function inferSchemaType(schema: OpenApiSchemaObject): string | undefined { + let type = schema.type as string | string[] | undefined; + + if (Array.isArray(type)) { + const nonNull = type.filter((t) => t !== 'null'); + type = nonNull.length > 0 ? nonNull[0] : 'null'; + } + + if (!type && schema.items) return 'array'; + + if (!type && schema.enum) { + const first = (schema.enum as unknown[])[0]; + if (typeof first === 'number') return 'number'; + if (typeof first === 'boolean') return 'boolean'; + return 'string'; + } + + return type; +} + +function buildDefaultPayload( + schema: OpenApiSchemaObject, + context: ContextSpec, +): string { + if ( + context.output.override.useDates && + typeof schema.default === 'string' && + (schema.format === 'date' || schema.format === 'date-time') + ) { + return `new Date('${schema.default}')`; + } + return formatValue(schema.default); +} + +function buildPrimitivePayload( + schema: OpenApiSchemaObject, + schemaType: string | undefined, + context: ContextSpec, +): string { + if (schemaType === 'null') return 'null'; + + const enumValues = schema.enum as unknown[] | undefined; + + if (schemaType === 'boolean') { + return enumValues && enumValues.length > 0 + ? String(enumValues[0]) + : 'false'; + } + + if (schemaType === 'number' || schemaType === 'integer') { + return enumValues && enumValues.length > 0 ? String(enumValues[0]) : '0'; + } + + if (schemaType === 'string') { + if (enumValues && enumValues.length > 0) { + const first = enumValues[0]; + return typeof first === 'string' ? JSON.stringify(first) : String(first); + } + if (schema.format === 'date' || schema.format === 'date-time') { + return context.output.override.useDates + ? 'new Date(0)' + : `'${new Date(0).toISOString()}'`; + } + return "''"; + } + + return 'undefined as unknown'; +} + +function formatValue(val: unknown): string { + if (val === null) return 'null'; + if (typeof val === 'string') return JSON.stringify(val); + if (typeof val === 'object') return JSON.stringify(val); + return String(val as number | boolean); +} diff --git a/packages/core/src/generators/index.ts b/packages/core/src/generators/index.ts index 9cc5a65c38..c50a623b54 100644 --- a/packages/core/src/generators/index.ts +++ b/packages/core/src/generators/index.ts @@ -1,4 +1,5 @@ export * from './component-definition'; +export * from './factory'; export * from './imports'; export * from './input-filters'; export * from './models-inline'; diff --git a/packages/core/src/generators/schema-definition.test.ts b/packages/core/src/generators/schema-definition.test.ts index 7f3af5931f..5fb07b5c8e 100644 --- a/packages/core/src/generators/schema-definition.test.ts +++ b/packages/core/src/generators/schema-definition.test.ts @@ -11,8 +11,15 @@ describe('generateSchemasDefinition', () => { const context = { output: { override: { namingConvention: {} }, + factoryMethods: { + generate: false, + functionNamePrefix: 'create', + mode: 'separate-file', + optionalPropertyStrategy: 'include', + }, }, target: 'typescript', + workspace: '', spec: {}, } as unknown as ContextSpec; @@ -138,8 +145,15 @@ describe('generateSchemasDefinition', () => { enum: 'PascalCase', }, }, + factoryMethods: { + generate: false, + functionNamePrefix: 'create', + mode: 'separate-file', + optionalPropertyStrategy: 'include', + }, }, target: 'typescript', + workspace: '', spec: {}, } as unknown as ContextSpec; @@ -235,6 +249,7 @@ describe('generateSchemasDefinition', () => { const specContext = { ...context, output: { + ...context.output, override: { enumGenerationType: 'const', namingConvention: {} }, }, spec: { @@ -280,6 +295,7 @@ describe('generateSchemasDefinition', () => { const specContext = { ...context, output: { + ...context.output, override: { enumGenerationType: 'const', namingConvention: {} }, }, spec: { @@ -337,6 +353,7 @@ describe('generateSchemasDefinition', () => { const specContext = { ...context, output: { + ...context.output, override: { enumGenerationType: 'const', namingConvention: {} }, }, spec: { diff --git a/packages/core/src/generators/schema-definition.ts b/packages/core/src/generators/schema-definition.ts index b971b60beb..81f34fa916 100644 --- a/packages/core/src/generators/schema-definition.ts +++ b/packages/core/src/generators/schema-definition.ts @@ -23,6 +23,7 @@ import { pascal, sanitize, } from '../utils'; +import { generateFactory } from './factory'; import { generateInterface } from './interface'; /** @@ -69,6 +70,31 @@ export function generateSchemasDefinition( ); if (!seenNames.has(normalizedName)) { seenNames.add(normalizedName); + + if (context.output.factoryMethods.generate && schema.schema) { + const factoryData = generateFactory( + schema.schema, + schema.name, + context, + ); + if (factoryData) { + if (context.output.factoryMethods.mode === 'inline-with-schema') { + schema.model += `\n${factoryData.model}`; + for (const imp of factoryData.imports) { + if ( + !schema.imports.some((existing) => existing.name === imp.name) + ) { + schema.imports.push(imp); + } + } + } else { + schema.factory = factoryData.model; + schema.factoryImports = factoryData.imports; + schema.factoryMode = context.output.factoryMethods.mode; + } + } + } + deduplicatedModels.push(schema); } } diff --git a/packages/core/src/test-utils/context.ts b/packages/core/src/test-utils/context.ts index 136ca9912d..5d05b90477 100644 --- a/packages/core/src/test-utils/context.ts +++ b/packages/core/src/test-utils/context.ts @@ -42,6 +42,13 @@ export function createTestContextSpec({ unionAddMissingProperties: false, optionsParamRequired: false, propertySortOrder: PropertySortOrder.SPECIFICATION, + factoryMethods: { + generate: false, + functionNamePrefix: 'create', + mode: 'separate-file', + outputDirectory: '', + optionalPropertyStrategy: 'include', + }, override: { title: undefined, transformer: undefined, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 886b84890f..a0d4c24f62 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -60,6 +60,7 @@ export interface NormalizedOutputOptions { unionAddMissingProperties: boolean; optionsParamRequired: boolean; propertySortOrder: PropertySortOrder; + factoryMethods: NormalizedFactoryMethodsOptions; } export interface NormalizedParamsSerializerOptions { @@ -279,6 +280,28 @@ export type EnumGeneration = export type SchemaGenerationType = 'typescript' | 'zod'; +export type FactoryMethodsOptionalPropertyStrategy = 'include' | 'omit'; +export type FactoryMethodsMode = + | 'inline-with-schema' + | 'separate-file' + | 'combined-separate-file'; + +export interface FactoryMethodsOptions { + generate?: boolean; + functionNamePrefix?: string; + mode?: FactoryMethodsMode; + outputDirectory?: string; + optionalPropertyStrategy?: FactoryMethodsOptionalPropertyStrategy; +} + +export interface NormalizedFactoryMethodsOptions { + generate: boolean; + functionNamePrefix: string; + mode: FactoryMethodsMode; + outputDirectory: string; + optionalPropertyStrategy: FactoryMethodsOptionalPropertyStrategy; +} + export interface SchemaOptions { path: string; type: SchemaGenerationType; @@ -323,6 +346,7 @@ export interface OutputOptions { unionAddMissingProperties?: boolean; optionsParamRequired?: boolean; propertySortOrder?: PropertySortOrder; + factoryMethods?: FactoryMethodsOptions; } export interface InputFiltersOptions { @@ -1129,6 +1153,9 @@ export interface GeneratorSchema { imports: GeneratorImport[]; dependencies?: string[]; schema?: OpenApiSchemaObject; + factory?: string; + factoryImports?: GeneratorImport[]; + factoryMode?: FactoryMethodsMode; } export interface GeneratorImport { diff --git a/packages/core/src/utils/path.ts b/packages/core/src/utils/path.ts index c5f894aa84..5902fa96ed 100644 --- a/packages/core/src/utils/path.ts +++ b/packages/core/src/utils/path.ts @@ -2,6 +2,14 @@ import basepath from 'node:path'; import { getExtension } from './extension'; +export function isAbsolute(value: string): boolean { + return basepath.isAbsolute(value); +} + +export function resolve(...args: string[]): string { + return toUnix(basepath.resolve(...args)); +} + export function toUnix(value: string): string { value = value.replaceAll('\\', '/'); value = value.replaceAll(/(? { const canonicalByName = canonicalNameMap.get(imp.name); @@ -233,6 +236,32 @@ function normalizeCanonicalImportPaths( return { ...imp, importPath }; }); + + if (schema.factoryImports) { + schema.factoryImports = schema.factoryImports.map((imp) => { + const canonicalByName = canonicalNameMap.get(imp.name); + + const resolvedImportKey = resolveImportKey( + factoryDir, + imp.importPath ?? `./${conventionName(imp.name, namingConvention)}`, + fileExtension, + ); + const canonicalByPath = canonicalPathMap.get(resolvedImportKey); + const canonical = canonicalByName ?? canonicalByPath; + if (!canonical?.importPath) return imp; + + const relative = upath.relativeSafe( + factoryDir, + canonical.importPath.replaceAll('\\', '/'), + ); + const withoutFileExtension = relative.endsWith(fileExtension) + ? relative.slice(0, -fileExtension.length) + : relative.replace(/\.ts$/, ''); + const importPath = `${withoutFileExtension}${importExtension}`; + + return { ...imp, importPath }; + }); + } } } @@ -249,12 +278,28 @@ function mergeSchemaGroup(schemas: GeneratorSchema[]): GeneratorSchema { const mergedDependencies = [ ...new Set(schemas.flatMap((schema) => schema.dependencies ?? [])), ]; + + const mergedFactory = schemas + .map((s) => s.factory) + .filter(Boolean) + .join('\n'); + const mergedFactoryImports = [ + ...new Map( + schemas + .flatMap((schema) => schema.factoryImports ?? []) + .map((imp) => [JSON.stringify(imp), imp] as [string, GeneratorImport]), + ).values(), + ]; + return { name: baseSchemaName, schema: baseSchema, model: schemas.map((schema) => schema.model).join('\n'), imports: mergedImports, dependencies: mergedDependencies, + factory: mergedFactory || undefined, + factoryImports: mergedFactoryImports, + factoryMode: schemas[0].factoryMode, }; } @@ -363,6 +408,43 @@ interface WriteSchemasOptions { header: string; indexFiles: boolean; tsconfig?: Tsconfig; + factoryOutputDirectory?: string; +} + +async function emitFactoryForSchema( + schema: GeneratorSchema, + namingConvention: NamingConvention, + header: string, + factoryDir: string, + fileExtension: string, + helpers: { + separateFactoryNames: string[]; + combinedFactoryContent: { value: string }; + combinedFactoryImports: GeneratorImport[]; + isCombined: { value: boolean }; + }, +) { + if (schema.factory && schema.factoryMode) { + const mode = schema.factoryMode; + if (mode === 'separate-file') { + const baseName = conventionName(schema.name, namingConvention); + const factoryName = `${baseName}.factory`; + helpers.separateFactoryNames.push(factoryName); + const factoryImportsStr = generateImports({ + imports: schema.factoryImports ?? [], + namingConvention, + }); + const factoryFile = `${header}\n${factoryImportsStr}\n\n${schema.factory}`; + await writeGeneratedFile( + getPath(factoryDir, factoryName, fileExtension), + factoryFile, + ); + } else if (mode === 'combined-separate-file') { + helpers.isCombined.value = true; + helpers.combinedFactoryContent.value += `${schema.factory}\n`; + helpers.combinedFactoryImports.push(...(schema.factoryImports ?? [])); + } + } } export async function writeSchemas({ @@ -374,6 +456,7 @@ export async function writeSchemas({ header, indexFiles, tsconfig, + factoryOutputDirectory, }: WriteSchemasOptions) { const schemaGroups = getSchemaGroups( schemaPath, @@ -397,8 +480,23 @@ export async function writeSchemas({ namingConvention, fileExtension, tsconfig, + factoryOutputDirectory, ); + const factoryDir = factoryOutputDirectory ?? schemaPath; + + const combinedFactoryContent = { value: '' }; + const combinedFactoryImports: GeneratorImport[] = []; + const isCombined = { value: false }; + const separateFactoryNames: string[] = []; + + const factoryHelpers = { + separateFactoryNames, + combinedFactoryContent, + combinedFactoryImports, + isCombined, + }; + for (const groupSchemas of Object.values(schemaGroups)) { if (groupSchemas.length === 1) { await writeSchema({ @@ -410,6 +508,16 @@ export async function writeSchemas({ header, tsconfig, }); + + const singleSchema = groupSchemas[0]; + await emitFactoryForSchema( + singleSchema, + namingConvention, + header, + factoryDir, + fileExtension, + factoryHelpers, + ); continue; } @@ -424,6 +532,28 @@ export async function writeSchemas({ header, tsconfig, }); + + await emitFactoryForSchema( + mergedSchema, + namingConvention, + header, + factoryDir, + fileExtension, + factoryHelpers, + ); + } + + if (isCombined.value) { + const factoryFileName = conventionName('factoryMethods', namingConvention); + const factoryFileImports = generateImports({ + imports: combinedFactoryImports, + namingConvention, + }); + const factoryFile = `${header}\n${factoryFileImports}\n\n${combinedFactoryContent.value}`; + await writeGeneratedFile( + getPath(factoryDir, factoryFileName, fileExtension), + factoryFile, + ); } if (indexFiles) { @@ -444,11 +574,50 @@ export async function writeSchemas({ const uniqueSchemaNames = [...conventionNamesSet]; // Create export statements - const currentExports = uniqueSchemaNames - .map((schemaName) => `export * from './${schemaName}${ext}';`) - .toSorted((a, b) => a.localeCompare(b, 'en', { numeric: true })); + const currentExports = uniqueSchemaNames.map( + (schemaName) => `export * from './${schemaName}${ext}';`, + ); + + if ( + factoryOutputDirectory && + upath.normalizeSafe(factoryOutputDirectory) !== + upath.normalizeSafe(schemaPath) && + (isCombined.value || separateFactoryNames.length > 0) + ) { + const factoryIndexFilePath = nodePath.join( + factoryOutputDirectory, + `index.ts`, + ); + await fs.ensureFile(factoryIndexFilePath); + const factoryExports: string[] = []; + if (isCombined.value) { + const factoryFileName = conventionName( + 'factoryMethods', + namingConvention, + ); + factoryExports.push(`export * from './${factoryFileName}${ext}';`); + } + for (const fName of separateFactoryNames) { + factoryExports.push(`export * from './${fName}${ext}';`); + } + const content = `${header}\n${factoryExports.join('\n')}\n`; + await writeGeneratedFile(factoryIndexFilePath, content); + } else { + if (isCombined.value) { + const factoryFileName = conventionName( + 'factoryMethods', + namingConvention, + ); + currentExports.push(`export * from './${factoryFileName}${ext}';`); + } + for (const fName of separateFactoryNames) { + currentExports.push(`export * from './${fName}${ext}';`); + } + } - const exports = currentExports.join('\n'); + const exports = [...new Set(currentExports)] + .toSorted((a, b) => a.localeCompare(b, 'en', { numeric: true })) + .join('\n'); const fileContent = `${header}\n${exports}\n`; diff --git a/packages/mock/src/faker/getters/combine.test.ts b/packages/mock/src/faker/getters/combine.test.ts index 129c666408..58f128c735 100644 --- a/packages/mock/src/faker/getters/combine.test.ts +++ b/packages/mock/src/faker/getters/combine.test.ts @@ -37,6 +37,13 @@ function createMockContext(): ContextSpec { unionAddMissingProperties: false, optionsParamRequired: false, propertySortOrder: PropertySortOrder.ALPHABETICAL, + factoryMethods: { + generate: false, + functionNamePrefix: 'create', + mode: 'inline-with-schema', + outputDirectory: '', + optionalPropertyStrategy: 'omit', + }, override: { title: undefined, transformer: undefined, diff --git a/packages/orval/src/utils/options.factory.test.ts b/packages/orval/src/utils/options.factory.test.ts new file mode 100644 index 0000000000..dbeb46b3d5 --- /dev/null +++ b/packages/orval/src/utils/options.factory.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from 'vitest'; + +import { normalizeOptions } from './options'; + +describe('normalizeOptions factoryMethods', () => { + const baseInput = { target: 'petstore.yaml' }; + + it('should use defaults when factoryMethods has generate: true', async () => { + const options = await normalizeOptions({ + input: baseInput, + output: { + target: 'api.ts', + factoryMethods: { generate: true }, + }, + }); + + expect(options.output.factoryMethods).toEqual({ + generate: true, + functionNamePrefix: 'create', + mode: 'separate-file', + optionalPropertyStrategy: 'include', + outputDirectory: process.cwd(), + }); + }); + + it('should override specific properties', async () => { + const options = await normalizeOptions({ + input: baseInput, + output: { + target: 'api.ts', + factoryMethods: { + generate: true, + functionNamePrefix: 'build', + mode: 'inline-with-schema', + optionalPropertyStrategy: 'omit', + }, + }, + }); + + expect(options.output.factoryMethods).toEqual({ + generate: true, + functionNamePrefix: 'build', + mode: 'inline-with-schema', + optionalPropertyStrategy: 'omit', + outputDirectory: process.cwd(), + }); + }); + + it('should use default values when properties are omitted', async () => { + const options = await normalizeOptions({ + input: baseInput, + output: { + target: 'api.ts', + factoryMethods: { + generate: true, + functionNamePrefix: 'make', + }, + }, + }); + + expect(options.output.factoryMethods).toEqual({ + generate: true, + functionNamePrefix: 'make', + mode: 'separate-file', + optionalPropertyStrategy: 'include', + outputDirectory: process.cwd(), + }); + }); + + it('should default to generate: false if factoryMethods is not provided', async () => { + const options = await normalizeOptions({ + input: baseInput, + output: { + target: 'api.ts', + }, + }); + + expect(options.output.factoryMethods).toEqual({ + generate: false, + functionNamePrefix: 'create', + mode: 'separate-file', + optionalPropertyStrategy: 'include', + outputDirectory: process.cwd(), + }); + }); +}); diff --git a/packages/orval/src/utils/options.ts b/packages/orval/src/utils/options.ts index 7a13ace16a..730e38258a 100644 --- a/packages/orval/src/utils/options.ts +++ b/packages/orval/src/utils/options.ts @@ -26,6 +26,7 @@ import { type McpServerOptions, type Mutator, NamingConvention, + type NormalizedFactoryMethodsOptions, type NormalizedHonoOptions, type NormalizedHookOptions, type NormalizedJsDocOptions, @@ -219,6 +220,38 @@ export async function normalizeOptions( const defaultFileExtension = '.ts'; + const factoryMethodsConfig = outputOptions.factoryMethods; + let factoryMethods: NormalizedFactoryMethodsOptions = { + generate: false, + functionNamePrefix: 'create', + mode: 'separate-file', + optionalPropertyStrategy: 'include', + outputDirectory: outputOptions.schemas + ? normalizePath( + isString(outputOptions.schemas) + ? outputOptions.schemas + : outputOptions.schemas.path, + outputWorkspace, + ) + : normalizePath(outputWorkspace, outputWorkspace), // default to workspace + }; + + if (factoryMethodsConfig) { + factoryMethods = { + generate: factoryMethodsConfig.generate ?? factoryMethods.generate, + functionNamePrefix: + factoryMethodsConfig.functionNamePrefix ?? + factoryMethods.functionNamePrefix, + mode: factoryMethodsConfig.mode ?? factoryMethods.mode, + outputDirectory: factoryMethodsConfig.outputDirectory + ? normalizePath(factoryMethodsConfig.outputDirectory, outputWorkspace) + : factoryMethods.outputDirectory, + optionalPropertyStrategy: + factoryMethodsConfig.optionalPropertyStrategy ?? + factoryMethods.optionalPropertyStrategy, + }; + } + // `useQuery` / `useMutation` defaults are applied per-verb in // `query-generator.ts` so we can tell "unset" from "explicit true" (#2376). const globalQueryOptions: NormalizedQueryOptions = { @@ -289,6 +322,7 @@ export async function normalizeOptions( baseUrl: outputOptions.baseUrl, unionAddMissingProperties: outputOptions.unionAddMissingProperties ?? false, + factoryMethods, override: { ...outputOptions.override, mock: { diff --git a/packages/orval/src/write-specs.ts b/packages/orval/src/write-specs.ts index 977c16fd52..7d192482ec 100644 --- a/packages/orval/src/write-specs.ts +++ b/packages/orval/src/write-specs.ts @@ -319,6 +319,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, + factoryOutputDirectory: output.factoryMethods.outputDirectory, }); } @@ -333,6 +334,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, + factoryOutputDirectory: output.factoryMethods.outputDirectory, }); // Add re-export from operations in the main schemas index @@ -354,6 +356,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, + factoryOutputDirectory: output.factoryMethods.outputDirectory, }); } } else { @@ -399,6 +402,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, + factoryOutputDirectory: output.factoryMethods.outputDirectory, }); } @@ -412,6 +416,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, + factoryOutputDirectory: output.factoryMethods.outputDirectory, }); // Add re-export from operations in the main schemas index @@ -433,6 +438,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, + factoryOutputDirectory: output.factoryMethods.outputDirectory, }); } } else { diff --git a/packages/solid-start/src/index.test.ts b/packages/solid-start/src/index.test.ts index 65375299bc..5918407d72 100644 --- a/packages/solid-start/src/index.test.ts +++ b/packages/solid-start/src/index.test.ts @@ -42,6 +42,13 @@ function makeOutput(useDates = false): ContextSpec['output'] { unionAddMissingProperties: false, optionsParamRequired: false, propertySortOrder: PropertySortOrder.ALPHABETICAL, + factoryMethods: { + generate: false, + functionNamePrefix: 'create', + mode: 'inline-with-schema', + outputDirectory: '', + optionalPropertyStrategy: 'omit', + }, override: { title: undefined, transformer: undefined, diff --git a/tests/__snapshots__/factory-methods/combined/endpoints.ts b/tests/__snapshots__/factory-methods/combined/endpoints.ts new file mode 100644 index 0000000000..06f24bbc84 --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/endpoints.ts @@ -0,0 +1,25 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import axios from 'axios'; +import type { + AxiosRequestConfig, + AxiosResponse +} from 'axios'; + +import type { + User +} from './model'; + +export const getTest = ( + options?: AxiosRequestConfig + ): Promise> => { + return axios.get( + `/test`,options + ); + } + +export type GetTestResult = AxiosResponse diff --git a/tests/__snapshots__/factory-methods/combined/model/circularChild.ts b/tests/__snapshots__/factory-methods/combined/model/circularChild.ts new file mode 100644 index 0000000000..e8bbc220a4 --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/model/circularChild.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { CircularParent } from './circularParent'; + +export interface CircularChild { + parent: CircularParent; +} diff --git a/tests/__snapshots__/factory-methods/combined/model/circularParent.ts b/tests/__snapshots__/factory-methods/combined/model/circularParent.ts new file mode 100644 index 0000000000..7fedcb36d2 --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/model/circularParent.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { CircularChild } from './circularChild'; + +export interface CircularParent { + child: CircularChild; +} diff --git a/tests/__snapshots__/factory-methods/combined/model/deepCircularA.ts b/tests/__snapshots__/factory-methods/combined/model/deepCircularA.ts new file mode 100644 index 0000000000..69951182b3 --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/model/deepCircularA.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { DeepCircularB } from './deepCircularB'; + +export interface DeepCircularA { + b?: DeepCircularB; +} diff --git a/tests/__snapshots__/factory-methods/combined/model/deepCircularB.ts b/tests/__snapshots__/factory-methods/combined/model/deepCircularB.ts new file mode 100644 index 0000000000..a2c11a2c1c --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/model/deepCircularB.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { DeepCircularC } from './deepCircularC'; + +export interface DeepCircularB { + c?: DeepCircularC; +} diff --git a/tests/__snapshots__/factory-methods/combined/model/deepCircularC.ts b/tests/__snapshots__/factory-methods/combined/model/deepCircularC.ts new file mode 100644 index 0000000000..52e2bfc581 --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/model/deepCircularC.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { DeepCircularA } from './deepCircularA'; + +export interface DeepCircularC { + a?: DeepCircularA; +} diff --git a/tests/__snapshots__/factory-methods/combined/model/factoryMethods.ts b/tests/__snapshots__/factory-methods/combined/model/factoryMethods.ts new file mode 100644 index 0000000000..652ab87bfb --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/model/factoryMethods.ts @@ -0,0 +1,61 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +import type { CircularChild } from './circularChild'; +import type { CircularParent } from './circularParent'; +import type { DeepCircularA } from './deepCircularA'; +import type { DeepCircularB } from './deepCircularB'; +import type { DeepCircularC } from './deepCircularC'; +import type { Status } from './status'; +import type { User } from './user'; + +export function createStatus(): Status { + return "ACTIVE"; +} + +export function createCircularChild(): CircularChild { + return { + parent: {} as CircularParent + }; +} + +export function createCircularParent(): CircularParent { + return { + child: {} as CircularChild + }; +} + +export function createUser(): User { + return { + id: 0, + name: '', + status: createStatus(), + avatar: '', + createdAt: '1970-01-01T00:00:00.000Z', + tags: [], + circular: createCircularParent() + }; +} + +export function createDeepCircularC(): DeepCircularC { + return { + a: {} as DeepCircularA + }; +} + +export function createDeepCircularB(): DeepCircularB { + return { + c: {} as DeepCircularC + }; +} + +export function createDeepCircularA(): DeepCircularA { + return { + b: {} as DeepCircularB + }; +} + diff --git a/tests/__snapshots__/factory-methods/combined/model/index.ts b/tests/__snapshots__/factory-methods/combined/model/index.ts new file mode 100644 index 0000000000..b833ca953f --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/model/index.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +export * from './circularChild'; +export * from './circularParent'; +export * from './deepCircularA'; +export * from './deepCircularB'; +export * from './deepCircularC'; +export * from './factoryMethods'; +export * from './status'; +export * from './user'; diff --git a/tests/__snapshots__/factory-methods/combined/model/status.ts b/tests/__snapshots__/factory-methods/combined/model/status.ts new file mode 100644 index 0000000000..dfa75b0ba1 --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/model/status.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +export type Status = typeof Status[keyof typeof Status]; + + +export const Status = { + ACTIVE: 'ACTIVE', + INACTIVE: 'INACTIVE', + PENDING: 'PENDING', +} as const; diff --git a/tests/__snapshots__/factory-methods/combined/model/user.ts b/tests/__snapshots__/factory-methods/combined/model/user.ts new file mode 100644 index 0000000000..660673bf79 --- /dev/null +++ b/tests/__snapshots__/factory-methods/combined/model/user.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { CircularParent } from './circularParent'; +import type { Status } from './status'; + +export interface User { + id: number; + name: string; + status: Status; + /** @nullable */ + avatar?: string | null; + createdAt?: string; + tags?: string[]; + circular?: CircularParent; +} diff --git a/tests/__snapshots__/factory-methods/inline/endpoints.ts b/tests/__snapshots__/factory-methods/inline/endpoints.ts new file mode 100644 index 0000000000..06f24bbc84 --- /dev/null +++ b/tests/__snapshots__/factory-methods/inline/endpoints.ts @@ -0,0 +1,25 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import axios from 'axios'; +import type { + AxiosRequestConfig, + AxiosResponse +} from 'axios'; + +import type { + User +} from './model'; + +export const getTest = ( + options?: AxiosRequestConfig + ): Promise> => { + return axios.get( + `/test`,options + ); + } + +export type GetTestResult = AxiosResponse diff --git a/tests/__snapshots__/factory-methods/inline/model/circularChild.ts b/tests/__snapshots__/factory-methods/inline/model/circularChild.ts new file mode 100644 index 0000000000..13f0f7bf41 --- /dev/null +++ b/tests/__snapshots__/factory-methods/inline/model/circularChild.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { CircularParent } from './circularParent'; + +export interface CircularChild { + parent: CircularParent; +} + +export function createCircularChild(): CircularChild { + return { + parent: {} as CircularParent + }; +} diff --git a/tests/__snapshots__/factory-methods/inline/model/circularParent.ts b/tests/__snapshots__/factory-methods/inline/model/circularParent.ts new file mode 100644 index 0000000000..329a3376bc --- /dev/null +++ b/tests/__snapshots__/factory-methods/inline/model/circularParent.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { CircularChild } from './circularChild'; + +export interface CircularParent { + child: CircularChild; +} + +export function createCircularParent(): CircularParent { + return { + child: {} as CircularChild + }; +} diff --git a/tests/__snapshots__/factory-methods/inline/model/deepCircularA.ts b/tests/__snapshots__/factory-methods/inline/model/deepCircularA.ts new file mode 100644 index 0000000000..65e82d9b39 --- /dev/null +++ b/tests/__snapshots__/factory-methods/inline/model/deepCircularA.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { DeepCircularB } from './deepCircularB'; + +export interface DeepCircularA { + b?: DeepCircularB; +} + +export function createDeepCircularA(): DeepCircularA { + return { + b: {} as DeepCircularB + }; +} diff --git a/tests/__snapshots__/factory-methods/inline/model/deepCircularB.ts b/tests/__snapshots__/factory-methods/inline/model/deepCircularB.ts new file mode 100644 index 0000000000..3a80b721a1 --- /dev/null +++ b/tests/__snapshots__/factory-methods/inline/model/deepCircularB.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { DeepCircularC } from './deepCircularC'; + +export interface DeepCircularB { + c?: DeepCircularC; +} + +export function createDeepCircularB(): DeepCircularB { + return { + c: {} as DeepCircularC + }; +} diff --git a/tests/__snapshots__/factory-methods/inline/model/deepCircularC.ts b/tests/__snapshots__/factory-methods/inline/model/deepCircularC.ts new file mode 100644 index 0000000000..aafb484883 --- /dev/null +++ b/tests/__snapshots__/factory-methods/inline/model/deepCircularC.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { DeepCircularA } from './deepCircularA'; + +export interface DeepCircularC { + a?: DeepCircularA; +} + +export function createDeepCircularC(): DeepCircularC { + return { + a: {} as DeepCircularA + }; +} diff --git a/tests/__snapshots__/factory-methods/inline/model/index.ts b/tests/__snapshots__/factory-methods/inline/model/index.ts new file mode 100644 index 0000000000..a7f69e09d4 --- /dev/null +++ b/tests/__snapshots__/factory-methods/inline/model/index.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +export * from './circularChild'; +export * from './circularParent'; +export * from './deepCircularA'; +export * from './deepCircularB'; +export * from './deepCircularC'; +export * from './status'; +export * from './user'; diff --git a/tests/__snapshots__/factory-methods/inline/model/status.ts b/tests/__snapshots__/factory-methods/inline/model/status.ts new file mode 100644 index 0000000000..1d4e3206d1 --- /dev/null +++ b/tests/__snapshots__/factory-methods/inline/model/status.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +export type Status = typeof Status[keyof typeof Status]; + + +export const Status = { + ACTIVE: 'ACTIVE', + INACTIVE: 'INACTIVE', + PENDING: 'PENDING', +} as const; + +export function createStatus(): Status { + return "ACTIVE"; +} diff --git a/tests/__snapshots__/factory-methods/inline/model/user.ts b/tests/__snapshots__/factory-methods/inline/model/user.ts new file mode 100644 index 0000000000..078838913e --- /dev/null +++ b/tests/__snapshots__/factory-methods/inline/model/user.ts @@ -0,0 +1,33 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { CircularParent } from './circularParent'; +import type { Status } from './status'; +import { createCircularParent } from './circularParent'; +import { createStatus } from './status'; + +export interface User { + id: number; + name: string; + status: Status; + /** @nullable */ + avatar?: string | null; + createdAt?: string; + tags?: string[]; + circular?: CircularParent; +} + +export function createUser(): User { + return { + id: 0, + name: '', + status: createStatus(), + avatar: '', + createdAt: '1970-01-01T00:00:00.000Z', + tags: [], + circular: createCircularParent() + }; +} diff --git a/tests/__snapshots__/factory-methods/separate/endpoints.ts b/tests/__snapshots__/factory-methods/separate/endpoints.ts new file mode 100644 index 0000000000..06f24bbc84 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/endpoints.ts @@ -0,0 +1,25 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import axios from 'axios'; +import type { + AxiosRequestConfig, + AxiosResponse +} from 'axios'; + +import type { + User +} from './model'; + +export const getTest = ( + options?: AxiosRequestConfig + ): Promise> => { + return axios.get( + `/test`,options + ); + } + +export type GetTestResult = AxiosResponse diff --git a/tests/__snapshots__/factory-methods/separate/model/circularChild.factory.ts b/tests/__snapshots__/factory-methods/separate/model/circularChild.factory.ts new file mode 100644 index 0000000000..730c86574e --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/circularChild.factory.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +import type { CircularChild } from './circularChild'; +import type { CircularParent } from './circularParent'; + +export function createCircularChild(): CircularChild { + return { + parent: {} as CircularParent + }; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/circularChild.ts b/tests/__snapshots__/factory-methods/separate/model/circularChild.ts new file mode 100644 index 0000000000..e8bbc220a4 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/circularChild.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { CircularParent } from './circularParent'; + +export interface CircularChild { + parent: CircularParent; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/circularParent.factory.ts b/tests/__snapshots__/factory-methods/separate/model/circularParent.factory.ts new file mode 100644 index 0000000000..760295a9f8 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/circularParent.factory.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +import type { CircularChild } from './circularChild'; +import type { CircularParent } from './circularParent'; + +export function createCircularParent(): CircularParent { + return { + child: {} as CircularChild + }; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/circularParent.ts b/tests/__snapshots__/factory-methods/separate/model/circularParent.ts new file mode 100644 index 0000000000..7fedcb36d2 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/circularParent.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { CircularChild } from './circularChild'; + +export interface CircularParent { + child: CircularChild; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularA.factory.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularA.factory.ts new file mode 100644 index 0000000000..c3552e73fb --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularA.factory.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +import type { DeepCircularA } from './deepCircularA'; +import type { DeepCircularB } from './deepCircularB'; + +export function createDeepCircularA(): DeepCircularA { + return { + b: {} as DeepCircularB + }; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularA.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularA.ts new file mode 100644 index 0000000000..69951182b3 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularA.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { DeepCircularB } from './deepCircularB'; + +export interface DeepCircularA { + b?: DeepCircularB; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularB.factory.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularB.factory.ts new file mode 100644 index 0000000000..b7d2c7f745 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularB.factory.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +import type { DeepCircularB } from './deepCircularB'; +import type { DeepCircularC } from './deepCircularC'; + +export function createDeepCircularB(): DeepCircularB { + return { + c: {} as DeepCircularC + }; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularB.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularB.ts new file mode 100644 index 0000000000..a2c11a2c1c --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularB.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { DeepCircularC } from './deepCircularC'; + +export interface DeepCircularB { + c?: DeepCircularC; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularC.factory.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularC.factory.ts new file mode 100644 index 0000000000..668b1ebdcb --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularC.factory.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +import type { DeepCircularA } from './deepCircularA'; +import type { DeepCircularC } from './deepCircularC'; + +export function createDeepCircularC(): DeepCircularC { + return { + a: {} as DeepCircularA + }; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularC.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularC.ts new file mode 100644 index 0000000000..52e2bfc581 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularC.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { DeepCircularA } from './deepCircularA'; + +export interface DeepCircularC { + a?: DeepCircularA; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/index.ts b/tests/__snapshots__/factory-methods/separate/model/index.ts new file mode 100644 index 0000000000..870395a2ef --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/index.ts @@ -0,0 +1,21 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +export * from './circularChild.factory'; +export * from './circularChild'; +export * from './circularParent.factory'; +export * from './circularParent'; +export * from './deepCircularA.factory'; +export * from './deepCircularA'; +export * from './deepCircularB.factory'; +export * from './deepCircularB'; +export * from './deepCircularC.factory'; +export * from './deepCircularC'; +export * from './status.factory'; +export * from './status'; +export * from './user.factory'; +export * from './user'; diff --git a/tests/__snapshots__/factory-methods/separate/model/status.factory.ts b/tests/__snapshots__/factory-methods/separate/model/status.factory.ts new file mode 100644 index 0000000000..a188c7b890 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/status.factory.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +import type { Status } from './status'; + +export function createStatus(): Status { + return "ACTIVE"; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/status.ts b/tests/__snapshots__/factory-methods/separate/model/status.ts new file mode 100644 index 0000000000..dfa75b0ba1 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/status.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +export type Status = typeof Status[keyof typeof Status]; + + +export const Status = { + ACTIVE: 'ACTIVE', + INACTIVE: 'INACTIVE', + PENDING: 'PENDING', +} as const; diff --git a/tests/__snapshots__/factory-methods/separate/model/user.factory.ts b/tests/__snapshots__/factory-methods/separate/model/user.factory.ts new file mode 100644 index 0000000000..e98037dd93 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/user.factory.ts @@ -0,0 +1,24 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ + +import type { CircularParent } from './circularParent'; +import type { Status } from './status'; +import type { User } from './user'; +import { createCircularParent } from './circularParent.factory'; +import { createStatus } from './status.factory'; + +export function createUser(): User { + return { + id: 0, + name: '', + status: createStatus(), + avatar: '', + createdAt: '1970-01-01T00:00:00.000Z', + tags: [], + circular: createCircularParent() + }; +} diff --git a/tests/__snapshots__/factory-methods/separate/model/user.ts b/tests/__snapshots__/factory-methods/separate/model/user.ts new file mode 100644 index 0000000000..660673bf79 --- /dev/null +++ b/tests/__snapshots__/factory-methods/separate/model/user.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.10.0 🍺 + * Do not edit manually. + * Factory Methods Test Spec + * OpenAPI spec version: 1.0.0 + */ +import type { CircularParent } from './circularParent'; +import type { Status } from './status'; + +export interface User { + id: number; + name: string; + status: Status; + /** @nullable */ + avatar?: string | null; + createdAt?: string; + tags?: string[]; + circular?: CircularParent; +} diff --git a/tests/api-generation.spec.ts b/tests/api-generation.spec.ts index cc595f641b..8306444d86 100644 --- a/tests/api-generation.spec.ts +++ b/tests/api-generation.spec.ts @@ -13,6 +13,7 @@ await describeApiGenerationSnapshots({ generated('angular'), generated('angular-query'), generated('axios'), + generated('factory-methods'), generated('cli'), generated('default'), generated('fetch'), diff --git a/tests/configs/factory-methods.config.ts b/tests/configs/factory-methods.config.ts new file mode 100644 index 0000000000..30878e9f70 --- /dev/null +++ b/tests/configs/factory-methods.config.ts @@ -0,0 +1,43 @@ +import { defineConfig } from 'orval'; + +export default defineConfig({ + inline: { + input: '../specifications/factory-methods.yaml', + output: { + target: '../generated/factory-methods/inline/endpoints.ts', + schemas: '../generated/factory-methods/inline/model', + factoryMethods: { + generate: true, + mode: 'inline-with-schema', + }, + clean: true, + prettier: true, + }, + }, + separate: { + input: '../specifications/factory-methods.yaml', + output: { + target: '../generated/factory-methods/separate/endpoints.ts', + schemas: '../generated/factory-methods/separate/model', + factoryMethods: { + generate: true, + mode: 'separate-file', + }, + clean: true, + prettier: true, + }, + }, + combined: { + input: '../specifications/factory-methods.yaml', + output: { + target: '../generated/factory-methods/combined/endpoints.ts', + schemas: '../generated/factory-methods/combined/model', + factoryMethods: { + generate: true, + mode: 'combined-separate-file', + }, + clean: true, + prettier: true, + }, + }, +}); diff --git a/tests/package.json b/tests/package.json index 27ef63d380..8d48879761 100644 --- a/tests/package.json +++ b/tests/package.json @@ -23,6 +23,7 @@ "generate:fetch": "orval --config ./configs/fetch.config.ts", "generate:mcp": "orval --config ./configs/mcp.config.ts", "generate:hono": "orval --config ./configs/hono.config.ts", + "generate:factory-methods": "orval --config ./configs/factory-methods.config.ts", "test:snapshots": "vitest run --config vitest.snapshots.ts", "test:snapshots:update": "bun run test:snapshots --update", "build": "node ./scripts/typecheck-generated.mjs && bun run verify:mock-generated", diff --git a/tests/specifications/factory-methods.yaml b/tests/specifications/factory-methods.yaml new file mode 100644 index 0000000000..ac0fe45d56 --- /dev/null +++ b/tests/specifications/factory-methods.yaml @@ -0,0 +1,68 @@ +openapi: '3.0.0' +info: + version: 1.0.0 + title: Factory Methods Test Spec +paths: + /test: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/User' +components: + schemas: + User: + type: object + required: [id, name, status] + properties: + id: + type: integer + name: + type: string + status: + $ref: '#/components/schemas/Status' + avatar: + type: string + nullable: true + createdAt: + type: string + format: date-time + tags: + type: array + items: + type: string + circular: + $ref: '#/components/schemas/CircularParent' + Status: + type: string + enum: [ACTIVE, INACTIVE, PENDING] + CircularParent: + type: object + required: [child] + properties: + child: + $ref: '#/components/schemas/CircularChild' + CircularChild: + type: object + required: [parent] + properties: + parent: + $ref: '#/components/schemas/CircularParent' + DeepCircularA: + type: object + properties: + b: + $ref: '#/components/schemas/DeepCircularB' + DeepCircularB: + type: object + properties: + c: + $ref: '#/components/schemas/DeepCircularC' + DeepCircularC: + type: object + properties: + a: + $ref: '#/components/schemas/DeepCircularA' From d48911c0d6baeb9bdfc6c2cc935965a1bf87dfbd Mon Sep 17 00:00:00 2001 From: Miron Balcerzak Date: Sun, 17 May 2026 19:14:27 +0200 Subject: [PATCH 2/2] feat(core): added factoryMethods - code review --- .../docs/reference/configuration/output.mdx | 32 +++++++---------- packages/angular/src/http-client.test.ts | 5 ++- packages/angular/src/http-resource.test.ts | 5 ++- packages/core/src/generators/factory.test.ts | 15 ++++---- packages/core/src/generators/factory.ts | 25 +++++++------ .../src/generators/schema-definition.test.ts | 16 ++------- .../core/src/generators/schema-definition.ts | 4 +-- packages/core/src/test-utils/context.ts | 8 +---- packages/core/src/types.ts | 14 +++----- packages/core/src/writers/schemas.ts | 4 +-- .../mock/src/faker/getters/combine.test.ts | 5 ++- .../orval/src/utils/options.factory.test.ts | 35 +++++++----------- packages/orval/src/utils/options.ts | 36 +++++++------------ packages/orval/src/write-specs.ts | 12 +++---- packages/solid-start/src/index.test.ts | 5 ++- .../factory-methods/combined/endpoints.ts | 2 +- .../combined/model/circularChild.ts | 2 +- .../combined/model/circularParent.ts | 2 +- .../combined/model/deepCircularA.ts | 2 +- .../combined/model/deepCircularB.ts | 2 +- .../combined/model/deepCircularC.ts | 2 +- .../combined/model/factoryMethods.ts | 2 +- .../factory-methods/combined/model/index.ts | 2 +- .../factory-methods/combined/model/status.ts | 2 +- .../factory-methods/combined/model/user.ts | 2 +- .../factory-methods/inline/endpoints.ts | 2 +- .../inline/model/circularChild.ts | 2 +- .../inline/model/circularParent.ts | 2 +- .../inline/model/deepCircularA.ts | 2 +- .../inline/model/deepCircularB.ts | 2 +- .../inline/model/deepCircularC.ts | 2 +- .../factory-methods/inline/model/index.ts | 2 +- .../factory-methods/inline/model/status.ts | 2 +- .../factory-methods/inline/model/user.ts | 2 +- .../factory-methods/separate/endpoints.ts | 2 +- .../separate/model/circularChild.factory.ts | 2 +- .../separate/model/circularChild.ts | 2 +- .../separate/model/circularParent.factory.ts | 2 +- .../separate/model/circularParent.ts | 2 +- .../separate/model/deepCircularA.factory.ts | 2 +- .../separate/model/deepCircularA.ts | 2 +- .../separate/model/deepCircularB.factory.ts | 2 +- .../separate/model/deepCircularB.ts | 2 +- .../separate/model/deepCircularC.factory.ts | 2 +- .../separate/model/deepCircularC.ts | 2 +- .../factory-methods/separate/model/index.ts | 2 +- .../separate/model/status.factory.ts | 2 +- .../factory-methods/separate/model/status.ts | 2 +- .../separate/model/user.factory.ts | 2 +- .../factory-methods/separate/model/user.ts | 2 +- tests/configs/factory-methods.config.ts | 9 ++--- 51 files changed, 121 insertions(+), 179 deletions(-) diff --git a/docs/content/docs/reference/configuration/output.mdx b/docs/content/docs/reference/configuration/output.mdx index 545f3d1127..a6fe7211e7 100644 --- a/docs/content/docs/reference/configuration/output.mdx +++ b/docs/content/docs/reference/configuration/output.mdx @@ -1706,17 +1706,16 @@ Generate factory methods for DTOs (Data Transfer Objects) initialized with safe Functionality handles OpenAPI `readOnly` and `writeOnly` flags to generate appropriate payload structures: - **Required properties:** Always included in the factory output, regardless of their visibility flags. - **Optional `readOnly` properties:** Always omitted from the factory output, as they would be dropped by the server. -- **Optional `writeOnly` properties:** Always included in the factory output (even if `optionalPropertyStrategy` is set to `'omit'`). +- **Optional `writeOnly` properties:** Always included in the factory output (even if `includeOptionalProperty` is set to `false`). ```ts title="orval.config.ts" export default defineConfig({ petstore: { output: { factoryMethods: { - generate: true, functionNamePrefix: 'create', - mode: 'separate-file', - optionalPropertyStrategy: 'include', + mode: 'split', + includeOptionalProperty: true, outputDirectory: `#output.workspace.schemas`, }, }, @@ -1724,13 +1723,6 @@ export default defineConfig({ }); ``` -### generate - -**Type:** `Boolean` -**Default:** `false` - -Enables or disables factory generation. - ### functionNamePrefix **Type:** `String` @@ -1740,18 +1732,18 @@ Prefix for the generated factory function names. ### mode -**Type:** `'inline-with-schema' | 'separate-file' | 'combined-separate-file'` -**Default:** `'separate-file'` +**Type:** `'single' | 'split' | 'single-split'` +**Default:** `'split'` Where to generate the factory methods: -- `inline-with-schema`: Appends the factory function to the schema file. -- `separate-file`: Creates a `{schema}.factory.ts` with factory method. By default it is placed next to schema file. -- `combined-separate-file`: Aggregates all factory methods into a single `factoryMethods.ts` file. +- `single`: Appends the factory function to the schema file. +- `split`: Creates a `{schema}.factory.ts` with factory method. By default it is placed next to schema file. +- `single-split`: Aggregates all factory methods into a single `factoryMethods.ts` file. -### optionalPropertyStrategy +### includeOptionalProperty -**Type:** `'include' | 'omit'` -**Default:** `'include'` +**Type:** `boolean` +**Default:** `true` Determines whether optional schema properties are included in the default factory output. @@ -1762,7 +1754,7 @@ Determines whether optional schema properties are included in the default factor Defaults to the value configured in `#output.workspace.schemas`. Determines where factory methods will be generated (can be used to generated methods away from schema directory). -Takes effect only when used `mode` is `separate-file` or `combined-separate-file`. +Takes effect only when used `mode` is `split` or `single-split`. --- ## Other Options diff --git a/packages/angular/src/http-client.test.ts b/packages/angular/src/http-client.test.ts index 516027ecff..e444694e14 100644 --- a/packages/angular/src/http-client.test.ts +++ b/packages/angular/src/http-client.test.ts @@ -126,11 +126,10 @@ const createOutput = ( unionAddMissingProperties: false, propertySortOrder: 'Specification', factoryMethods: { - generate: false, functionNamePrefix: 'create', - mode: 'inline-with-schema', + mode: 'single', outputDirectory: '', - optionalPropertyStrategy: 'omit', + includeOptionalProperty: false, }, ...overrides, } satisfies NormalizedOutputOptions; diff --git a/packages/angular/src/http-resource.test.ts b/packages/angular/src/http-resource.test.ts index 4d7bef44d1..71922f07ba 100644 --- a/packages/angular/src/http-resource.test.ts +++ b/packages/angular/src/http-resource.test.ts @@ -136,11 +136,10 @@ const createOutput = ( unionAddMissingProperties: false, propertySortOrder: 'Specification', factoryMethods: { - generate: false, functionNamePrefix: 'create', - mode: 'inline-with-schema', + mode: 'single', outputDirectory: '', - optionalPropertyStrategy: 'omit', + includeOptionalProperty: false, }, ...overrides, } satisfies NormalizedOutputOptions; diff --git a/packages/core/src/generators/factory.test.ts b/packages/core/src/generators/factory.test.ts index 13ffca4a60..909de49131 100644 --- a/packages/core/src/generators/factory.test.ts +++ b/packages/core/src/generators/factory.test.ts @@ -9,11 +9,10 @@ import { NamingConvention } from '../types'; import { generateFactory } from './factory'; const baseFactoryMethods = { - generate: true, functionNamePrefix: 'create', - mode: 'inline-with-schema', + mode: 'single', outputDirectory: '', - optionalPropertyStrategy: 'omit', + includeOptionalProperty: false, }; const baseOverride = { @@ -141,7 +140,7 @@ describe('generateFactory', () => { createMockContext({ factoryMethods: { ...baseFactoryMethods, - optionalPropertyStrategy: 'include', + includeOptionalProperty: true, }, }), ); @@ -205,7 +204,7 @@ describe('generateFactory', () => { expect(result?.imports).toContainEqual({ name: 'RefTarget' }); }); - it('handles mode: separate-file import paths', () => { + it('handles mode: split import paths', () => { const schema: OpenApiSchemaObject = { type: 'object', required: ['target'], @@ -218,7 +217,7 @@ describe('generateFactory', () => { schema, 'WithRef', createMockContext({ - factoryMethods: { ...baseFactoryMethods, mode: 'separate-file' }, + factoryMethods: { ...baseFactoryMethods, mode: 'split' }, }), ); expect(result?.imports).toContainEqual({ @@ -228,7 +227,7 @@ describe('generateFactory', () => { }); }); - it('handles mode: combined-separate-file import paths', () => { + it('handles mode: single-split import paths', () => { const schema: OpenApiSchemaObject = { type: 'object', required: ['target'], @@ -243,7 +242,7 @@ describe('generateFactory', () => { createMockContext({ factoryMethods: { ...baseFactoryMethods, - mode: 'combined-separate-file', + mode: 'single-split', }, }), ); diff --git a/packages/core/src/generators/factory.ts b/packages/core/src/generators/factory.ts index f235db7d78..8b1f329cd8 100644 --- a/packages/core/src/generators/factory.ts +++ b/packages/core/src/generators/factory.ts @@ -31,10 +31,10 @@ function getSchemaImportPath( refName: string, context: ContextSpec, ): string | undefined { - if (context.output.factoryMethods.mode === 'inline-with-schema') { + if (context.output.factoryMethods?.mode === 'single') { return undefined; } - let outputDir = context.output.factoryMethods.outputDirectory; + let outputDir = context.output.factoryMethods?.outputDirectory; let schemasPath = getSchemasPath(context); if (context.output.workspace) { @@ -128,14 +128,15 @@ export function generateFactory( name: string, context: ContextSpec, ): { model: string; imports: GeneratorImport[] } | undefined { - if (!canGenerateSchema(schema)) return undefined; + if (!canGenerateSchema(schema) || !context.output.factoryMethods) + return undefined; const { functionNamePrefix, mode } = context.output.factoryMethods; const factoryName = `${functionNamePrefix}${pascal(name)}`; const imports: GeneratorImport[] = []; const payload = buildPayload(schema, context, [name], imports); - if (mode !== 'inline-with-schema') { + if (mode !== 'single') { const schemaImportPath = getSchemaImportPath(name, context); imports.push({ name, importPath: schemaImportPath }); } @@ -287,10 +288,11 @@ function buildRefPayload( return `{} as ${refName}`; } - const { functionNamePrefix, mode } = context.output.factoryMethods; + const { functionNamePrefix = 'create', mode = 'single' } = + context.output.factoryMethods ?? {}; const refFactoryName = `${functionNamePrefix}${pascal(refName)}`; - if (mode !== 'combined-separate-file') { + if (mode !== 'single-split') { const importPath = resolveImportPath(mode, refName, context); imports.push({ name: refFactoryName, importPath, isConstant: true }); } @@ -310,13 +312,13 @@ function resolveImportPath( ): string | undefined { const baseName = conventionName(refName, context.output.namingConvention); switch (mode) { - case 'separate-file': { + case 'split': { return `./${baseName}.factory`; } - case 'combined-separate-file': { + case 'single-split': { return `./${conventionName('factoryMethods', context.output.namingConvention)}`; } - case 'inline-with-schema': { + case 'single': { return `./${baseName}`; } } @@ -350,7 +352,8 @@ function buildObjectPayload( parents: string[], imports: GeneratorImport[], ): string { - const { optionalPropertyStrategy } = context.output.factoryMethods; + const { includeOptionalProperty = false } = + context.output.factoryMethods ?? {}; const props = getProperties(schema); const requiredProps: string[] = (schema.required as string[] | undefined) ?? []; @@ -360,7 +363,7 @@ function buildObjectPayload( entries.sort(([a], [b]) => a.localeCompare(b)); } - const includeOptional = optionalPropertyStrategy === 'include'; + const includeOptional = includeOptionalProperty; const lines: string[] = []; for (const [key, prop] of entries) { diff --git a/packages/core/src/generators/schema-definition.test.ts b/packages/core/src/generators/schema-definition.test.ts index 5fb07b5c8e..6f55448d78 100644 --- a/packages/core/src/generators/schema-definition.test.ts +++ b/packages/core/src/generators/schema-definition.test.ts @@ -11,15 +11,9 @@ describe('generateSchemasDefinition', () => { const context = { output: { override: { namingConvention: {} }, - factoryMethods: { - generate: false, - functionNamePrefix: 'create', - mode: 'separate-file', - optionalPropertyStrategy: 'include', - }, + factoryMethods: undefined, }, target: 'typescript', - workspace: '', spec: {}, } as unknown as ContextSpec; @@ -145,15 +139,9 @@ describe('generateSchemasDefinition', () => { enum: 'PascalCase', }, }, - factoryMethods: { - generate: false, - functionNamePrefix: 'create', - mode: 'separate-file', - optionalPropertyStrategy: 'include', - }, + factoryMethods: undefined, }, target: 'typescript', - workspace: '', spec: {}, } as unknown as ContextSpec; diff --git a/packages/core/src/generators/schema-definition.ts b/packages/core/src/generators/schema-definition.ts index 81f34fa916..3963b438d9 100644 --- a/packages/core/src/generators/schema-definition.ts +++ b/packages/core/src/generators/schema-definition.ts @@ -71,14 +71,14 @@ export function generateSchemasDefinition( if (!seenNames.has(normalizedName)) { seenNames.add(normalizedName); - if (context.output.factoryMethods.generate && schema.schema) { + if (context.output.factoryMethods && schema.schema) { const factoryData = generateFactory( schema.schema, schema.name, context, ); if (factoryData) { - if (context.output.factoryMethods.mode === 'inline-with-schema') { + if (context.output.factoryMethods.mode === 'single') { schema.model += `\n${factoryData.model}`; for (const imp of factoryData.imports) { if ( diff --git a/packages/core/src/test-utils/context.ts b/packages/core/src/test-utils/context.ts index 5d05b90477..f447506737 100644 --- a/packages/core/src/test-utils/context.ts +++ b/packages/core/src/test-utils/context.ts @@ -42,13 +42,7 @@ export function createTestContextSpec({ unionAddMissingProperties: false, optionsParamRequired: false, propertySortOrder: PropertySortOrder.SPECIFICATION, - factoryMethods: { - generate: false, - functionNamePrefix: 'create', - mode: 'separate-file', - outputDirectory: '', - optionalPropertyStrategy: 'include', - }, + factoryMethods: undefined, override: { title: undefined, transformer: undefined, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index a0d4c24f62..9362bd40bf 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -60,7 +60,7 @@ export interface NormalizedOutputOptions { unionAddMissingProperties: boolean; optionsParamRequired: boolean; propertySortOrder: PropertySortOrder; - factoryMethods: NormalizedFactoryMethodsOptions; + factoryMethods?: NormalizedFactoryMethodsOptions; } export interface NormalizedParamsSerializerOptions { @@ -280,26 +280,20 @@ export type EnumGeneration = export type SchemaGenerationType = 'typescript' | 'zod'; -export type FactoryMethodsOptionalPropertyStrategy = 'include' | 'omit'; -export type FactoryMethodsMode = - | 'inline-with-schema' - | 'separate-file' - | 'combined-separate-file'; +export type FactoryMethodsMode = 'single' | 'split' | 'single-split'; export interface FactoryMethodsOptions { - generate?: boolean; functionNamePrefix?: string; mode?: FactoryMethodsMode; outputDirectory?: string; - optionalPropertyStrategy?: FactoryMethodsOptionalPropertyStrategy; + includeOptionalProperty?: boolean; } export interface NormalizedFactoryMethodsOptions { - generate: boolean; functionNamePrefix: string; mode: FactoryMethodsMode; outputDirectory: string; - optionalPropertyStrategy: FactoryMethodsOptionalPropertyStrategy; + includeOptionalProperty: boolean; } export interface SchemaOptions { diff --git a/packages/core/src/writers/schemas.ts b/packages/core/src/writers/schemas.ts index 8234a20380..7a6316cef6 100644 --- a/packages/core/src/writers/schemas.ts +++ b/packages/core/src/writers/schemas.ts @@ -426,7 +426,7 @@ async function emitFactoryForSchema( ) { if (schema.factory && schema.factoryMode) { const mode = schema.factoryMode; - if (mode === 'separate-file') { + if (mode === 'split') { const baseName = conventionName(schema.name, namingConvention); const factoryName = `${baseName}.factory`; helpers.separateFactoryNames.push(factoryName); @@ -439,7 +439,7 @@ async function emitFactoryForSchema( getPath(factoryDir, factoryName, fileExtension), factoryFile, ); - } else if (mode === 'combined-separate-file') { + } else if (mode === 'single-split') { helpers.isCombined.value = true; helpers.combinedFactoryContent.value += `${schema.factory}\n`; helpers.combinedFactoryImports.push(...(schema.factoryImports ?? [])); diff --git a/packages/mock/src/faker/getters/combine.test.ts b/packages/mock/src/faker/getters/combine.test.ts index 58f128c735..4ad6ffddd2 100644 --- a/packages/mock/src/faker/getters/combine.test.ts +++ b/packages/mock/src/faker/getters/combine.test.ts @@ -38,11 +38,10 @@ function createMockContext(): ContextSpec { optionsParamRequired: false, propertySortOrder: PropertySortOrder.ALPHABETICAL, factoryMethods: { - generate: false, functionNamePrefix: 'create', - mode: 'inline-with-schema', + mode: 'single', outputDirectory: '', - optionalPropertyStrategy: 'omit', + includeOptionalProperty: false, }, override: { title: undefined, diff --git a/packages/orval/src/utils/options.factory.test.ts b/packages/orval/src/utils/options.factory.test.ts index dbeb46b3d5..cf0885cf51 100644 --- a/packages/orval/src/utils/options.factory.test.ts +++ b/packages/orval/src/utils/options.factory.test.ts @@ -5,20 +5,19 @@ import { normalizeOptions } from './options'; describe('normalizeOptions factoryMethods', () => { const baseInput = { target: 'petstore.yaml' }; - it('should use defaults when factoryMethods has generate: true', async () => { + it('should use defaults when factoryMethods is an empty object', async () => { const options = await normalizeOptions({ input: baseInput, output: { target: 'api.ts', - factoryMethods: { generate: true }, + factoryMethods: {}, }, }); expect(options.output.factoryMethods).toEqual({ - generate: true, functionNamePrefix: 'create', - mode: 'separate-file', - optionalPropertyStrategy: 'include', + mode: 'split', + includeOptionalProperty: true, outputDirectory: process.cwd(), }); }); @@ -29,19 +28,17 @@ describe('normalizeOptions factoryMethods', () => { output: { target: 'api.ts', factoryMethods: { - generate: true, functionNamePrefix: 'build', - mode: 'inline-with-schema', - optionalPropertyStrategy: 'omit', + mode: 'single', + includeOptionalProperty: false, }, }, }); expect(options.output.factoryMethods).toEqual({ - generate: true, functionNamePrefix: 'build', - mode: 'inline-with-schema', - optionalPropertyStrategy: 'omit', + mode: 'single', + includeOptionalProperty: false, outputDirectory: process.cwd(), }); }); @@ -52,22 +49,20 @@ describe('normalizeOptions factoryMethods', () => { output: { target: 'api.ts', factoryMethods: { - generate: true, functionNamePrefix: 'make', }, }, }); expect(options.output.factoryMethods).toEqual({ - generate: true, functionNamePrefix: 'make', - mode: 'separate-file', - optionalPropertyStrategy: 'include', + mode: 'split', + includeOptionalProperty: true, outputDirectory: process.cwd(), }); }); - it('should default to generate: false if factoryMethods is not provided', async () => { + it('should default to undefined if factoryMethods is not provided', async () => { const options = await normalizeOptions({ input: baseInput, output: { @@ -75,12 +70,6 @@ describe('normalizeOptions factoryMethods', () => { }, }); - expect(options.output.factoryMethods).toEqual({ - generate: false, - functionNamePrefix: 'create', - mode: 'separate-file', - optionalPropertyStrategy: 'include', - outputDirectory: process.cwd(), - }); + expect(options.output.factoryMethods).toBeUndefined(); }); }); diff --git a/packages/orval/src/utils/options.ts b/packages/orval/src/utils/options.ts index 730e38258a..cbf12c6562 100644 --- a/packages/orval/src/utils/options.ts +++ b/packages/orval/src/utils/options.ts @@ -221,34 +221,24 @@ export async function normalizeOptions( const defaultFileExtension = '.ts'; const factoryMethodsConfig = outputOptions.factoryMethods; - let factoryMethods: NormalizedFactoryMethodsOptions = { - generate: false, - functionNamePrefix: 'create', - mode: 'separate-file', - optionalPropertyStrategy: 'include', - outputDirectory: outputOptions.schemas - ? normalizePath( - isString(outputOptions.schemas) - ? outputOptions.schemas - : outputOptions.schemas.path, - outputWorkspace, - ) - : normalizePath(outputWorkspace, outputWorkspace), // default to workspace - }; + let factoryMethods: NormalizedFactoryMethodsOptions | undefined = undefined; if (factoryMethodsConfig) { factoryMethods = { - generate: factoryMethodsConfig.generate ?? factoryMethods.generate, - functionNamePrefix: - factoryMethodsConfig.functionNamePrefix ?? - factoryMethods.functionNamePrefix, - mode: factoryMethodsConfig.mode ?? factoryMethods.mode, + functionNamePrefix: factoryMethodsConfig.functionNamePrefix ?? 'create', + mode: factoryMethodsConfig.mode ?? 'split', outputDirectory: factoryMethodsConfig.outputDirectory ? normalizePath(factoryMethodsConfig.outputDirectory, outputWorkspace) - : factoryMethods.outputDirectory, - optionalPropertyStrategy: - factoryMethodsConfig.optionalPropertyStrategy ?? - factoryMethods.optionalPropertyStrategy, + : outputOptions.schemas + ? normalizePath( + isString(outputOptions.schemas) + ? outputOptions.schemas + : outputOptions.schemas.path, + outputWorkspace, + ) + : normalizePath(outputWorkspace, outputWorkspace), + includeOptionalProperty: + factoryMethodsConfig.includeOptionalProperty ?? true, }; } diff --git a/packages/orval/src/write-specs.ts b/packages/orval/src/write-specs.ts index 7d192482ec..358bf6d5e6 100644 --- a/packages/orval/src/write-specs.ts +++ b/packages/orval/src/write-specs.ts @@ -319,7 +319,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, - factoryOutputDirectory: output.factoryMethods.outputDirectory, + factoryOutputDirectory: output.factoryMethods?.outputDirectory, }); } @@ -334,7 +334,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, - factoryOutputDirectory: output.factoryMethods.outputDirectory, + factoryOutputDirectory: output.factoryMethods?.outputDirectory, }); // Add re-export from operations in the main schemas index @@ -356,7 +356,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, - factoryOutputDirectory: output.factoryMethods.outputDirectory, + factoryOutputDirectory: output.factoryMethods?.outputDirectory, }); } } else { @@ -402,7 +402,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, - factoryOutputDirectory: output.factoryMethods.outputDirectory, + factoryOutputDirectory: output.factoryMethods?.outputDirectory, }); } @@ -416,7 +416,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, - factoryOutputDirectory: output.factoryMethods.outputDirectory, + factoryOutputDirectory: output.factoryMethods?.outputDirectory, }); // Add re-export from operations in the main schemas index @@ -438,7 +438,7 @@ export async function writeSpecs( header, indexFiles: output.indexFiles, tsconfig: output.tsconfig, - factoryOutputDirectory: output.factoryMethods.outputDirectory, + factoryOutputDirectory: output.factoryMethods?.outputDirectory, }); } } else { diff --git a/packages/solid-start/src/index.test.ts b/packages/solid-start/src/index.test.ts index 5918407d72..8d53cc877d 100644 --- a/packages/solid-start/src/index.test.ts +++ b/packages/solid-start/src/index.test.ts @@ -43,11 +43,10 @@ function makeOutput(useDates = false): ContextSpec['output'] { optionsParamRequired: false, propertySortOrder: PropertySortOrder.ALPHABETICAL, factoryMethods: { - generate: false, functionNamePrefix: 'create', - mode: 'inline-with-schema', + mode: 'single', outputDirectory: '', - optionalPropertyStrategy: 'omit', + includeOptionalProperty: false, }, override: { title: undefined, diff --git a/tests/__snapshots__/factory-methods/combined/endpoints.ts b/tests/__snapshots__/factory-methods/combined/endpoints.ts index 06f24bbc84..2053b43a8d 100644 --- a/tests/__snapshots__/factory-methods/combined/endpoints.ts +++ b/tests/__snapshots__/factory-methods/combined/endpoints.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/combined/model/circularChild.ts b/tests/__snapshots__/factory-methods/combined/model/circularChild.ts index e8bbc220a4..f001787610 100644 --- a/tests/__snapshots__/factory-methods/combined/model/circularChild.ts +++ b/tests/__snapshots__/factory-methods/combined/model/circularChild.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/combined/model/circularParent.ts b/tests/__snapshots__/factory-methods/combined/model/circularParent.ts index 7fedcb36d2..bfa68e87ca 100644 --- a/tests/__snapshots__/factory-methods/combined/model/circularParent.ts +++ b/tests/__snapshots__/factory-methods/combined/model/circularParent.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/combined/model/deepCircularA.ts b/tests/__snapshots__/factory-methods/combined/model/deepCircularA.ts index 69951182b3..efef21128a 100644 --- a/tests/__snapshots__/factory-methods/combined/model/deepCircularA.ts +++ b/tests/__snapshots__/factory-methods/combined/model/deepCircularA.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/combined/model/deepCircularB.ts b/tests/__snapshots__/factory-methods/combined/model/deepCircularB.ts index a2c11a2c1c..a7d2c61ba9 100644 --- a/tests/__snapshots__/factory-methods/combined/model/deepCircularB.ts +++ b/tests/__snapshots__/factory-methods/combined/model/deepCircularB.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/combined/model/deepCircularC.ts b/tests/__snapshots__/factory-methods/combined/model/deepCircularC.ts index 52e2bfc581..9321b3d22a 100644 --- a/tests/__snapshots__/factory-methods/combined/model/deepCircularC.ts +++ b/tests/__snapshots__/factory-methods/combined/model/deepCircularC.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/combined/model/factoryMethods.ts b/tests/__snapshots__/factory-methods/combined/model/factoryMethods.ts index 652ab87bfb..38a491c6f4 100644 --- a/tests/__snapshots__/factory-methods/combined/model/factoryMethods.ts +++ b/tests/__snapshots__/factory-methods/combined/model/factoryMethods.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/combined/model/index.ts b/tests/__snapshots__/factory-methods/combined/model/index.ts index b833ca953f..547e9d917f 100644 --- a/tests/__snapshots__/factory-methods/combined/model/index.ts +++ b/tests/__snapshots__/factory-methods/combined/model/index.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/combined/model/status.ts b/tests/__snapshots__/factory-methods/combined/model/status.ts index dfa75b0ba1..6694ec6f8b 100644 --- a/tests/__snapshots__/factory-methods/combined/model/status.ts +++ b/tests/__snapshots__/factory-methods/combined/model/status.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/combined/model/user.ts b/tests/__snapshots__/factory-methods/combined/model/user.ts index 660673bf79..712ff1a956 100644 --- a/tests/__snapshots__/factory-methods/combined/model/user.ts +++ b/tests/__snapshots__/factory-methods/combined/model/user.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/inline/endpoints.ts b/tests/__snapshots__/factory-methods/inline/endpoints.ts index 06f24bbc84..2053b43a8d 100644 --- a/tests/__snapshots__/factory-methods/inline/endpoints.ts +++ b/tests/__snapshots__/factory-methods/inline/endpoints.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/inline/model/circularChild.ts b/tests/__snapshots__/factory-methods/inline/model/circularChild.ts index 13f0f7bf41..c10a2ca35e 100644 --- a/tests/__snapshots__/factory-methods/inline/model/circularChild.ts +++ b/tests/__snapshots__/factory-methods/inline/model/circularChild.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/inline/model/circularParent.ts b/tests/__snapshots__/factory-methods/inline/model/circularParent.ts index 329a3376bc..6977e615b9 100644 --- a/tests/__snapshots__/factory-methods/inline/model/circularParent.ts +++ b/tests/__snapshots__/factory-methods/inline/model/circularParent.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/inline/model/deepCircularA.ts b/tests/__snapshots__/factory-methods/inline/model/deepCircularA.ts index 65e82d9b39..0a0cb4b7ea 100644 --- a/tests/__snapshots__/factory-methods/inline/model/deepCircularA.ts +++ b/tests/__snapshots__/factory-methods/inline/model/deepCircularA.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/inline/model/deepCircularB.ts b/tests/__snapshots__/factory-methods/inline/model/deepCircularB.ts index 3a80b721a1..13f593897f 100644 --- a/tests/__snapshots__/factory-methods/inline/model/deepCircularB.ts +++ b/tests/__snapshots__/factory-methods/inline/model/deepCircularB.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/inline/model/deepCircularC.ts b/tests/__snapshots__/factory-methods/inline/model/deepCircularC.ts index aafb484883..8f82d01439 100644 --- a/tests/__snapshots__/factory-methods/inline/model/deepCircularC.ts +++ b/tests/__snapshots__/factory-methods/inline/model/deepCircularC.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/inline/model/index.ts b/tests/__snapshots__/factory-methods/inline/model/index.ts index a7f69e09d4..8f63d15b4a 100644 --- a/tests/__snapshots__/factory-methods/inline/model/index.ts +++ b/tests/__snapshots__/factory-methods/inline/model/index.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/inline/model/status.ts b/tests/__snapshots__/factory-methods/inline/model/status.ts index 1d4e3206d1..984d6a0cb1 100644 --- a/tests/__snapshots__/factory-methods/inline/model/status.ts +++ b/tests/__snapshots__/factory-methods/inline/model/status.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/inline/model/user.ts b/tests/__snapshots__/factory-methods/inline/model/user.ts index 078838913e..97eb9ff861 100644 --- a/tests/__snapshots__/factory-methods/inline/model/user.ts +++ b/tests/__snapshots__/factory-methods/inline/model/user.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/endpoints.ts b/tests/__snapshots__/factory-methods/separate/endpoints.ts index 06f24bbc84..2053b43a8d 100644 --- a/tests/__snapshots__/factory-methods/separate/endpoints.ts +++ b/tests/__snapshots__/factory-methods/separate/endpoints.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/circularChild.factory.ts b/tests/__snapshots__/factory-methods/separate/model/circularChild.factory.ts index 730c86574e..7e94da800e 100644 --- a/tests/__snapshots__/factory-methods/separate/model/circularChild.factory.ts +++ b/tests/__snapshots__/factory-methods/separate/model/circularChild.factory.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/circularChild.ts b/tests/__snapshots__/factory-methods/separate/model/circularChild.ts index e8bbc220a4..f001787610 100644 --- a/tests/__snapshots__/factory-methods/separate/model/circularChild.ts +++ b/tests/__snapshots__/factory-methods/separate/model/circularChild.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/circularParent.factory.ts b/tests/__snapshots__/factory-methods/separate/model/circularParent.factory.ts index 760295a9f8..415c1a1bf0 100644 --- a/tests/__snapshots__/factory-methods/separate/model/circularParent.factory.ts +++ b/tests/__snapshots__/factory-methods/separate/model/circularParent.factory.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/circularParent.ts b/tests/__snapshots__/factory-methods/separate/model/circularParent.ts index 7fedcb36d2..bfa68e87ca 100644 --- a/tests/__snapshots__/factory-methods/separate/model/circularParent.ts +++ b/tests/__snapshots__/factory-methods/separate/model/circularParent.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularA.factory.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularA.factory.ts index c3552e73fb..3d4b995b28 100644 --- a/tests/__snapshots__/factory-methods/separate/model/deepCircularA.factory.ts +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularA.factory.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularA.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularA.ts index 69951182b3..efef21128a 100644 --- a/tests/__snapshots__/factory-methods/separate/model/deepCircularA.ts +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularA.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularB.factory.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularB.factory.ts index b7d2c7f745..42021e8c28 100644 --- a/tests/__snapshots__/factory-methods/separate/model/deepCircularB.factory.ts +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularB.factory.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularB.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularB.ts index a2c11a2c1c..a7d2c61ba9 100644 --- a/tests/__snapshots__/factory-methods/separate/model/deepCircularB.ts +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularB.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularC.factory.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularC.factory.ts index 668b1ebdcb..341a911531 100644 --- a/tests/__snapshots__/factory-methods/separate/model/deepCircularC.factory.ts +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularC.factory.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/deepCircularC.ts b/tests/__snapshots__/factory-methods/separate/model/deepCircularC.ts index 52e2bfc581..9321b3d22a 100644 --- a/tests/__snapshots__/factory-methods/separate/model/deepCircularC.ts +++ b/tests/__snapshots__/factory-methods/separate/model/deepCircularC.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/index.ts b/tests/__snapshots__/factory-methods/separate/model/index.ts index 870395a2ef..e3970c2c12 100644 --- a/tests/__snapshots__/factory-methods/separate/model/index.ts +++ b/tests/__snapshots__/factory-methods/separate/model/index.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/status.factory.ts b/tests/__snapshots__/factory-methods/separate/model/status.factory.ts index a188c7b890..2dc31a76a5 100644 --- a/tests/__snapshots__/factory-methods/separate/model/status.factory.ts +++ b/tests/__snapshots__/factory-methods/separate/model/status.factory.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/status.ts b/tests/__snapshots__/factory-methods/separate/model/status.ts index dfa75b0ba1..6694ec6f8b 100644 --- a/tests/__snapshots__/factory-methods/separate/model/status.ts +++ b/tests/__snapshots__/factory-methods/separate/model/status.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/user.factory.ts b/tests/__snapshots__/factory-methods/separate/model/user.factory.ts index e98037dd93..fbc884062a 100644 --- a/tests/__snapshots__/factory-methods/separate/model/user.factory.ts +++ b/tests/__snapshots__/factory-methods/separate/model/user.factory.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/__snapshots__/factory-methods/separate/model/user.ts b/tests/__snapshots__/factory-methods/separate/model/user.ts index 660673bf79..712ff1a956 100644 --- a/tests/__snapshots__/factory-methods/separate/model/user.ts +++ b/tests/__snapshots__/factory-methods/separate/model/user.ts @@ -1,5 +1,5 @@ /** - * Generated by orval v8.10.0 🍺 + * Generated by orval v8.12.3 🍺 * Do not edit manually. * Factory Methods Test Spec * OpenAPI spec version: 1.0.0 diff --git a/tests/configs/factory-methods.config.ts b/tests/configs/factory-methods.config.ts index 30878e9f70..0b226850bb 100644 --- a/tests/configs/factory-methods.config.ts +++ b/tests/configs/factory-methods.config.ts @@ -7,8 +7,7 @@ export default defineConfig({ target: '../generated/factory-methods/inline/endpoints.ts', schemas: '../generated/factory-methods/inline/model', factoryMethods: { - generate: true, - mode: 'inline-with-schema', + mode: 'single', }, clean: true, prettier: true, @@ -20,8 +19,7 @@ export default defineConfig({ target: '../generated/factory-methods/separate/endpoints.ts', schemas: '../generated/factory-methods/separate/model', factoryMethods: { - generate: true, - mode: 'separate-file', + mode: 'split', }, clean: true, prettier: true, @@ -33,8 +31,7 @@ export default defineConfig({ target: '../generated/factory-methods/combined/endpoints.ts', schemas: '../generated/factory-methods/combined/model', factoryMethods: { - generate: true, - mode: 'combined-separate-file', + mode: 'single-split', }, clean: true, prettier: true,