Skip to content

Commit 1ee1402

Browse files
committed
Refactor
1 parent 4c35b82 commit 1ee1402

File tree

16 files changed

+756
-488
lines changed

16 files changed

+756
-488
lines changed

packages/core/src/api-struct.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,34 @@ export type DevupDeleteApiStructKey<O extends string> = ConditionalKeys<
7676
export type DevupPatchApiStructKey<O extends string> = ConditionalKeys<
7777
DevupPatchApiStructScope<O>
7878
>
79+
export type DevupApiMethodKeys =
80+
| 'get'
81+
| 'post'
82+
| 'put'
83+
| 'delete'
84+
| 'patch'
85+
| 'GET'
86+
| 'POST'
87+
| 'PUT'
88+
| 'DELETE'
89+
| 'PATCH'
90+
91+
export type DevupApiMethodScope<
92+
S extends string,
93+
M extends DevupApiMethodKeys,
94+
> = {
95+
get: DevupGetApiStructScope<S>
96+
post: DevupPostApiStructScope<S>
97+
put: DevupPutApiStructScope<S>
98+
delete: DevupDeleteApiStructScope<S>
99+
patch: DevupPatchApiStructScope<S>
100+
GET: DevupGetApiStructScope<S>
101+
POST: DevupPostApiStructScope<S>
102+
PUT: DevupPutApiStructScope<S>
103+
DELETE: DevupDeleteApiStructScope<S>
104+
PATCH: DevupPatchApiStructScope<S>
105+
}[M]
106+
79107
export type DevupApiStructScope<O extends string> = DevupGetApiStructScope<O> &
80108
DevupPostApiStructScope<O> &
81109
DevupPutApiStructScope<O> &

packages/generator/src/__tests__/index.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,8 @@ test('index.ts exports', () => {
3030
extractSchemaNameFromRef: expect.any(Function),
3131
normalizeServerName: expect.any(Function),
3232
isErrorStatusCode: expect.any(Function),
33+
isNullableSchema: expect.any(Function),
34+
getPrimaryType: expect.any(Function),
35+
collectSchemaNames: expect.any(Function),
3336
})
3437
})

packages/generator/src/generate-crud-config.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1+
import { toPascal } from '@devup-api/utils'
12
import type { OpenAPIV3_1 } from 'openapi-types'
23
import type { CrudConfig, CrudField } from './crud-types'
34
import { parseCrudConfigsFromMultiple } from './parse-crud-tags'
45
import { wrapInterfaceKeyGuard } from './wrap-interface-key-guard'
56

6-
/**
7-
* Convert string to PascalCase for component names
8-
*/
9-
function toPascalCase(str: string): string {
10-
return str
11-
.split(/[-_]/)
12-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
13-
.join('')
14-
}
15-
167
/**
178
* Convert string to title case for labels
189
*/
@@ -162,7 +153,7 @@ function generateFieldsComponent(
162153
*/
163154
function generateCrudComponent(name: string, config: CrudConfig): string[] {
164155
const lines: string[] = []
165-
const componentName = `${toPascalCase(name)}Crud`
156+
const componentName = `${toPascal(name)}Crud`
166157
const fieldsComponentName = `${componentName}Fields`
167158

168159
// Determine edit method and operationId
@@ -235,7 +226,7 @@ export function generateCrudConfigCode(
235226

236227
// Generate component for each CRUD group
237228
for (const [name, config] of Object.entries(configs)) {
238-
const componentName = `${toPascalCase(name)}Crud`
229+
const componentName = `${toPascal(name)}Crud`
239230

240231
// Get fields from create endpoint (primary form fields)
241232
const fields = config.create.fields ?? []
@@ -259,7 +250,7 @@ export function generateCrudConfigCode(
259250
// Default export
260251
lines.push('export default {')
261252
for (const name of Object.keys(configs)) {
262-
const componentName = `${toPascalCase(name)}Crud`
253+
const componentName = `${toPascal(name)}Crud`
263254
lines.push(` ${name}: ${componentName},`)
264255
}
265256
lines.push('};')

packages/generator/src/generate-interface.ts

Lines changed: 20 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getTypeFromSchema,
1212
} from './generate-schema'
1313
import {
14+
collectSchemaNames,
1415
extractSchemaNameFromRef,
1516
getRequestBodyContent,
1617
isErrorStatusCode,
@@ -145,73 +146,10 @@ function generateSchemaInterface(
145146
} as const
146147
const convertCaseType = options?.convertCase ?? 'camel'
147148

148-
// Helper function to collect schema names from a schema object
149-
// Recursively traverses into referenced schemas to find all nested $refs
150-
const collectSchemaNames = (
151-
schemaObj: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject,
152-
targetSet: Set<string>,
153-
visited: Set<string> = new Set(),
154-
): void => {
155-
if ('$ref' in schemaObj) {
156-
const schemaName = extractSchemaNameFromRef(schemaObj.$ref)
157-
if (schemaName) {
158-
// Avoid infinite recursion for circular references
159-
if (visited.has(schemaName)) {
160-
return
161-
}
162-
targetSet.add(schemaName)
163-
visited.add(schemaName)
164-
165-
// Recursively collect from the referenced schema
166-
const referencedSchema = schema.components?.schemas?.[schemaName]
167-
if (referencedSchema) {
168-
collectSchemaNames(
169-
referencedSchema as
170-
| OpenAPIV3_1.SchemaObject
171-
| OpenAPIV3_1.ReferenceObject,
172-
targetSet,
173-
visited,
174-
)
175-
}
176-
}
177-
return
178-
}
179-
180-
const schemaObjTyped = schemaObj as OpenAPIV3_1.SchemaObject
181-
182-
// Check allOf, anyOf, oneOf
183-
if (schemaObjTyped.allOf) {
184-
schemaObjTyped.allOf.forEach((s) => {
185-
collectSchemaNames(s, targetSet, visited)
186-
})
187-
}
188-
if (schemaObjTyped.anyOf) {
189-
schemaObjTyped.anyOf.forEach((s) => {
190-
collectSchemaNames(s, targetSet, visited)
191-
})
192-
}
193-
if (schemaObjTyped.oneOf) {
194-
schemaObjTyped.oneOf.forEach((s) => {
195-
collectSchemaNames(s, targetSet, visited)
196-
})
197-
}
198-
199-
// Check properties
200-
if (schemaObjTyped.properties) {
201-
Object.values(schemaObjTyped.properties).forEach((prop) => {
202-
collectSchemaNames(prop, targetSet, visited)
203-
})
204-
}
205-
206-
// Check items (for arrays)
207-
if (
208-
schemaObjTyped.type === 'array' &&
209-
'items' in schemaObjTyped &&
210-
schemaObjTyped.items
211-
) {
212-
collectSchemaNames(schemaObjTyped.items, targetSet, visited)
213-
}
214-
}
149+
const collectOpts = {
150+
followComponentRefs: true,
151+
document: schema,
152+
} as const
215153

216154
// Track which schemas are used in request body and responses
217155
const requestSchemaNames = new Set<string>()
@@ -242,7 +180,11 @@ function generateSchemaInterface(
242180
const content = operation.requestBody.content
243181
const bodyContent = getRequestBodyContent(content)
244182
if (bodyContent && 'schema' in bodyContent && bodyContent.schema) {
245-
collectSchemaNames(bodyContent.schema, requestSchemaNames)
183+
collectSchemaNames(
184+
bodyContent.schema,
185+
requestSchemaNames,
186+
collectOpts,
187+
)
246188
}
247189
}
248190
}
@@ -272,9 +214,17 @@ function generateSchemaInterface(
272214
jsonContent.schema
273215
) {
274216
if (isError) {
275-
collectSchemaNames(jsonContent.schema, errorSchemaNames)
217+
collectSchemaNames(
218+
jsonContent.schema,
219+
errorSchemaNames,
220+
collectOpts,
221+
)
276222
} else {
277-
collectSchemaNames(jsonContent.schema, responseSchemaNames)
223+
collectSchemaNames(
224+
jsonContent.schema,
225+
responseSchemaNames,
226+
collectOpts,
227+
)
278228
}
279229
}
280230
}

packages/generator/src/generate-schema.ts

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import type { OpenAPIV3_1 } from 'openapi-types'
22
import type { ParameterDefinition } from './generate-interface'
3-
import { getRequestBodyContent, resolveRef } from './openapi-utils'
3+
import {
4+
getPrimaryType,
5+
getRequestBodyContent,
6+
isNullableSchema,
7+
resolveRef,
8+
} from './openapi-utils'
49
import { wrapInterfaceKeyGuard } from './wrap-interface-key-guard'
510

611
/**
@@ -83,35 +88,6 @@ function generateEnumName(
8388
return name
8489
}
8590

86-
/**
87-
* Check if a schema is nullable (OpenAPI 3.0 or 3.1)
88-
* OpenAPI 3.0: uses `nullable: true`
89-
* OpenAPI 3.1: uses type array like `["string", "null"]`
90-
*/
91-
function isNullable(schema: OpenAPIV3_1.SchemaObject): boolean {
92-
// OpenAPI 3.0 style: nullable: true
93-
if ('nullable' in schema && schema.nullable === true) {
94-
return true
95-
}
96-
97-
// OpenAPI 3.1 style: type is an array containing "null"
98-
if (Array.isArray(schema.type) && schema.type.includes('null')) {
99-
return true
100-
}
101-
102-
return false
103-
}
104-
105-
/**
106-
* Get the non-null type from OpenAPI 3.1 type array
107-
* e.g., ["string", "null"] -> "string"
108-
*/
109-
function getNonNullType(
110-
types: (OpenAPIV3_1.NonArraySchemaObjectType | 'array')[],
111-
): OpenAPIV3_1.NonArraySchemaObjectType | 'array' | undefined {
112-
return types.find((t) => t !== 'null')
113-
}
114-
11591
/**
11692
* Options for component reference handling
11793
*/
@@ -241,7 +217,7 @@ export function getTypeFromSchema(
241217
}
242218

243219
// Check if schema is nullable
244-
const nullable = isNullable(schemaObj)
220+
const nullable = isNullableSchema(schemaObj)
245221

246222
// Handle enum
247223
if (schemaObj.enum) {
@@ -273,9 +249,7 @@ export function getTypeFromSchema(
273249
}
274250

275251
// Get the actual type (handle OpenAPI 3.1 type arrays)
276-
const actualType = Array.isArray(schemaObj.type)
277-
? getNonNullType(schemaObj.type)
278-
: schemaObj.type
252+
const actualType = getPrimaryType(schemaObj)
279253

280254
// Handle primitive types
281255
if (actualType === 'string') {
@@ -718,7 +692,10 @@ export function extractRequestBody(
718692
}
719693

720694
if ('$ref' in requestBody) {
721-
const resolved = resolveRef(requestBody.$ref, document)
695+
const resolved = resolveRef<OpenAPIV3_1.RequestBodyObject>(
696+
requestBody.$ref,
697+
document,
698+
)
722699
if (resolved && 'content' in resolved && resolved.content) {
723700
const content =
724701
resolved.content as OpenAPIV3_1.RequestBodyObject['content']

0 commit comments

Comments
 (0)