Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions generator/manage-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

const fs = require('fs')
const path = require('path')
const { factory, SyntaxKind } = require('typescript')
const { ESLint } = require('eslint')
const { generateAPIClass } = require('./generator.js')

Expand All @@ -22,14 +23,48 @@ function pascal(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}

function createProperty(name, type, optional = false) {
return factory.createPropertySignature(
undefined,
factory.createIdentifier(name),
optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
type,
)
}

function createOpType(...ops) {
return factory.createUnionTypeNode(
ops.map((op) => factory.createLiteralTypeNode(factory.createStringLiteral(op))),
)
}

function createPatchOperationTransform(_schemaObject, meta) {
if (meta.path !== '#/components/schemas/PatchOperation') {
return undefined
}

return factory.createUnionTypeNode([
factory.createTypeLiteralNode([
createProperty('op', createOpType('add', 'replace')),
createProperty('path', factory.createKeywordTypeNode(SyntaxKind.StringKeyword)),
createProperty('value', factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword)),
]),
factory.createTypeLiteralNode([
createProperty('op', createOpType('remove')),
createProperty('path', factory.createKeywordTypeNode(SyntaxKind.StringKeyword)),
createProperty('value', factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword), true),
]),
])
}

async function emitTypes(spec) {
console.log('generating ManageTypes.ts from spec')
// openapi-typescript v7 is ESM-only; use dynamic import from this CJS script.
const mod = await import('openapi-typescript')
const openapiTS = mod.default ?? mod
// v7 returns an AST (array of ts.Node); we must format via astToString.
// v6 returns a string. Handle both.
const result = await openapiTS(spec)
const result = await openapiTS(spec, { transform: createPatchOperationTransform })
let output
if (typeof result === 'string') {
output = result
Expand Down Expand Up @@ -100,17 +135,14 @@ import Manage from './Manage'
import { CWLogger, RetryOptions } from './types'
${imports}

import type { components } from './ManageTypes'
${typeExports}

/**
* @public
* Manage patch operation input object, usually passed in an array of operations
*/
export type PatchOperation = {
op: 'add' | 'replace' | 'remove'
path: string
value: unknown
}
export type PatchOperation = components['schemas']['PatchOperation']

/**
* @public
Expand Down
7 changes: 2 additions & 5 deletions src/ManageAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ServiceAPI } from './Manage/ServiceAPI'
import { SystemAPI } from './Manage/SystemAPI'
import { TimeAPI } from './Manage/TimeAPI'

import type { components } from './ManageTypes'
import type * as CompanyAPITypes from './Manage/CompanyAPI'
import type * as ConfigurationsAPITypes from './Manage/ConfigurationsAPI'
import type * as ExpenseAPITypes from './Manage/ExpenseAPI'
Expand All @@ -31,11 +32,7 @@ import type * as TimeAPITypes from './Manage/TimeAPI'
* @public
* Manage patch operation input object, usually passed in an array of operations
*/
export type PatchOperation = {
op: 'add' | 'replace' | 'remove'
path: string
value: unknown
}
export type PatchOperation = components['schemas']['PatchOperation']

/**
* @public
Expand Down
10 changes: 7 additions & 3 deletions src/ManageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41977,9 +41977,13 @@ export interface components {
};
};
PatchOperation: {
op?: string;
path?: string;
value?: Record<string, never>;
op: "add" | "replace";
path: string;
value: unknown;
} | {
op: "remove";
path: string;
value?: unknown;
};
PaymentMethodReference: {
/** Format: int32 */
Expand Down
19 changes: 19 additions & 0 deletions test/patch-operation-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expectTypeOf } from 'expect-type'
import type { PatchOperation } from '../src/types'

const addPatchOperation: PatchOperation = { op: 'add', path: 'summary', value: 'updated' }
const replacePatchOperation: PatchOperation = { op: 'replace', path: 'summary', value: 'updated' }
const removePatchOperation: PatchOperation = { op: 'remove', path: 'summary' }

expectTypeOf(addPatchOperation.value).toEqualTypeOf<unknown>()
expectTypeOf(replacePatchOperation.value).toEqualTypeOf<unknown>()
expectTypeOf(removePatchOperation.value).toEqualTypeOf<unknown | undefined>()

// @ts-expect-error add operations should require a value.
const addPatchOperationWithoutValue: PatchOperation = { op: 'add', path: 'summary' }

// @ts-expect-error replace operations should require a value.
const replacePatchOperationWithoutValue: PatchOperation = { op: 'replace', path: 'summary' }

// @ts-expect-error patch operation op should be constrained to JSON patch operations.
const invalidPatchOperation: PatchOperation = { op: 'copy', path: 'summary', value: 'updated' }
2 changes: 1 addition & 1 deletion test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"exclude": [],
"compilerOptions": {
"noEmit": true,
"rootDir": ".",
"rootDir": "..",
"allowJs": true,
"checkJs": false,
"baseUrl": "."
Expand Down