Skip to content

Commit d5f547f

Browse files
authored
Merge pull request #41 from dev-five-git/allow-more-type
Fix type issue and gen interface property with optional issue
2 parents 5712642 + c167bc5 commit d5f547f

File tree

6 files changed

+42
-12
lines changed

6 files changed

+42
-12
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"packages/generator/package.json":"Patch","packages/core/package.json":"Patch"},"note":"Allow more type","date":"2026-01-19T12:09:16.172578500Z"}

packages/core/src/additional.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ export type DevupApiRequestInit = Omit<RequestInit, 'body'> & {
2121
params?: Record<string, string | number | boolean | null | undefined>
2222
query?:
2323
| ConstructorParameters<typeof URLSearchParams>[0]
24-
| Record<string, string | number | (number | string)[]>
24+
| Record<
25+
string,
26+
| string
27+
| number
28+
| boolean
29+
| null
30+
| undefined
31+
| (number | string | boolean)[]
32+
>
2533
middleware?: Middleware[]
2634
}
2735

packages/generator/src/__tests__/wrap-interface-key-guard.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,15 @@ test.each([
5555
] as const)('wrapInterfaceKeyGuard wraps key with forbidden characters: %s -> %s', (key, expected) => {
5656
expect(wrapInterfaceKeyGuard(key)).toBe(expected)
5757
})
58+
59+
test.each([
60+
['name?', 'name?'], // valid identifier with optional marker
61+
['email?', 'email?'], // valid identifier with optional marker
62+
['field_name?', 'field_name?'], // valid identifier with optional marker
63+
['field-name?', '[`field-name`]?'], // invalid identifier with optional marker
64+
['field name?', '[`field name`]?'], // invalid identifier with optional marker
65+
['/users?', '[`/users`]?'], // path with optional marker
66+
['123field?', '[`123field`]?'], // starts with number, with optional marker
67+
] as const)('wrapInterfaceKeyGuard handles optional keys (ending with ?): %s -> %s', (key, expected) => {
68+
expect(wrapInterfaceKeyGuard(key)).toBe(expected)
69+
})

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { OpenAPIV3_1 } from 'openapi-types'
22
import type { CrudConfig, CrudField } from './crud-types'
33
import { parseCrudConfigsFromMultiple } from './parse-crud-tags'
4+
import { wrapInterfaceKeyGuard } from './wrap-interface-key-guard'
45

56
/**
67
* Convert string to PascalCase for component names
@@ -286,7 +287,7 @@ export function generateCrudConfigTypes(
286287
lines.push("declare module '@devup-api/ui' {")
287288
lines.push(' interface DevupCrudApiNames {')
288289
for (const name of apiNames) {
289-
lines.push(` ${name}: true`)
290+
lines.push(` ${wrapInterfaceKeyGuard(name)}: true`)
290291
}
291292
lines.push(' }')
292293
lines.push('}')

packages/generator/src/generate-schema.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { OpenAPIV3_1 } from 'openapi-types'
22
import type { ParameterDefinition } from './generate-interface'
3+
import { wrapInterfaceKeyGuard } from './wrap-interface-key-guard'
34

45
/**
56
* Check if a schema is nullable (OpenAPI 3.0 or 3.1)
@@ -298,14 +299,15 @@ function formatType(obj: Record<string, unknown>, indent: number = 0): string {
298299
.map(([key, value]) => {
299300
// Handle string values (e.g., component references)
300301
if (typeof value === 'string') {
301-
return `${nextIndentStr}${key}: ${value}`
302+
return `${nextIndentStr}${wrapInterfaceKeyGuard(key)}: ${value}`
302303
}
303304

304305
// Handle ParameterDefinition for params and query
305306
if (isParameterDefinition(value)) {
306307
const typeStr = formatTypeValue(value.type, nextIndent)
307308
const isOptional = value.required === false
308-
const keyWithOptional = isOptional ? `${key}?` : key
309+
const wrappedKey = wrapInterfaceKeyGuard(key)
310+
const keyWithOptional = isOptional ? `${wrappedKey}?` : wrappedKey
309311
let description = ''
310312
if (value.description) {
311313
description += `${nextIndentStr}/**\n${nextIndentStr} * ${value.description}`
@@ -325,7 +327,7 @@ function formatType(obj: Record<string, unknown>, indent: number = 0): string {
325327
if (isTypeObject(value)) {
326328
const formattedValue = formatTypeValue(value.type, nextIndent)
327329
// Key already has '?' if it's optional (from getTypeFromSchema), keep it as is
328-
return `${nextIndentStr}${key}: ${formattedValue}`
330+
return `${nextIndentStr}${wrapInterfaceKeyGuard(key)}: ${formattedValue}`
329331
}
330332

331333
// Check if value is an object (like params, query) with all optional properties
@@ -337,7 +339,7 @@ function formatType(obj: Record<string, unknown>, indent: number = 0): string {
337339
const optionalMarker = valueAllOptional ? '?' : ''
338340

339341
const formattedValue = formatTypeValue(value, nextIndent)
340-
return `${nextIndentStr}${key}${optionalMarker}: ${formattedValue}`
342+
return `${nextIndentStr}${wrapInterfaceKeyGuard(key)}${optionalMarker}: ${formattedValue}`
341343
})
342344
.join(';\n')
343345

packages/generator/src/wrap-interface-key-guard.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@ export function wrapInterfaceKeyGuard(key: string): string {
44
return key
55
}
66

7-
// Check if key contains forbidden characters that require wrapping
7+
// Check if key ends with '?' (optional marker in TypeScript)
8+
// If so, process the base key and add '?' back at the end
9+
const isOptional = key.endsWith('?')
10+
const baseKey = isOptional ? key.slice(0, -1) : key
11+
12+
// Check if base key contains forbidden characters that require wrapping
813
// TypeScript identifier pattern: starts with letter/underscore/dollar, followed by letters/numbers/underscore/dollar
9-
const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)
14+
const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(baseKey)
1015

1116
if (
1217
!isValidIdentifier ||
13-
key.includes('"') ||
14-
key.includes("'") ||
15-
key.includes('`')
18+
baseKey.includes('"') ||
19+
baseKey.includes("'") ||
20+
baseKey.includes('`')
1621
) {
17-
return `[\`${key}\`]`
22+
const wrapped = `[\`${baseKey}\`]`
23+
return isOptional ? `${wrapped}?` : wrapped
1824
}
1925
return key
2026
}

0 commit comments

Comments
 (0)