@devup-api/generator 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,482 +0,0 @@
1
- import type { OpenAPIV3_1 } from 'openapi-types'
2
- import type { ParameterDefinition } from './generate-interface'
3
-
4
- /**
5
- * Resolve $ref reference in OpenAPI parameter
6
- */
7
- export function resolveParameterRef(
8
- ref: string,
9
- document: OpenAPIV3_1.Document,
10
- ): OpenAPIV3_1.ParameterObject | null {
11
- if (!ref.startsWith('#/')) {
12
- return null
13
- }
14
-
15
- const parts = ref.slice(2).split('/')
16
- let current: unknown = document
17
-
18
- for (const part of parts) {
19
- if (current && typeof current === 'object' && part in current) {
20
- current = (current as Record<string, unknown>)[part]
21
- } else {
22
- return null
23
- }
24
- }
25
-
26
- if (current && typeof current === 'object' && !('$ref' in current)) {
27
- return current as OpenAPIV3_1.ParameterObject
28
- }
29
-
30
- return null
31
- }
32
-
33
- /**
34
- * Resolve $ref reference in OpenAPI schema
35
- */
36
- export function resolveSchemaRef(
37
- ref: string,
38
- document: OpenAPIV3_1.Document,
39
- ): OpenAPIV3_1.SchemaObject | null {
40
- if (!ref.startsWith('#/')) {
41
- return null
42
- }
43
-
44
- const parts = ref.slice(2).split('/')
45
- let current: unknown = document
46
-
47
- for (const part of parts) {
48
- if (current && typeof current === 'object' && part in current) {
49
- current = (current as Record<string, unknown>)[part]
50
- } else {
51
- return null
52
- }
53
- }
54
-
55
- if (current && typeof current === 'object' && !('$ref' in current)) {
56
- return current as OpenAPIV3_1.SchemaObject
57
- }
58
-
59
- return null
60
- }
61
-
62
- /**
63
- * Convert OpenAPI schema to TypeScript type representation
64
- */
65
- export function getTypeFromSchema(
66
- schema: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject,
67
- document: OpenAPIV3_1.Document,
68
- options?: {
69
- defaultNonNullable?: boolean
70
- },
71
- ): { type: unknown; default?: unknown } {
72
- const defaultNonNullable = options?.defaultNonNullable ?? false
73
- // Handle $ref
74
- if ('$ref' in schema) {
75
- const resolved = resolveSchemaRef(schema.$ref, document)
76
- if (resolved) {
77
- return getTypeFromSchema(resolved, document, options)
78
- }
79
- return { type: 'unknown', default: undefined }
80
- }
81
-
82
- const schemaObj = schema as OpenAPIV3_1.SchemaObject
83
-
84
- // Handle allOf, anyOf, oneOf
85
- if (schemaObj.allOf) {
86
- const types = schemaObj.allOf.map((s) =>
87
- getTypeFromSchema(s, document, options),
88
- )
89
- return {
90
- type:
91
- types.length > 0
92
- ? types.map((t) => formatTypeValue(t.type)).join(' & ')
93
- : 'unknown',
94
- default: schemaObj.default,
95
- }
96
- }
97
-
98
- if (schemaObj.anyOf || schemaObj.oneOf) {
99
- const types = (schemaObj.anyOf || schemaObj.oneOf || []).map((s) =>
100
- getTypeFromSchema(s, document, options),
101
- )
102
- return {
103
- type:
104
- types.length > 0
105
- ? `(${types.map((t) => formatTypeValue(t.type)).join(' | ')})`
106
- : 'unknown',
107
- default: schemaObj.default,
108
- }
109
- }
110
-
111
- // Handle enum
112
- if (schemaObj.enum) {
113
- return {
114
- type: schemaObj.enum.map((v) => `"${String(v)}"`).join(' | '),
115
- default: schemaObj.default,
116
- }
117
- }
118
-
119
- // Handle primitive types
120
- if (schemaObj.type === 'string') {
121
- if (schemaObj.format === 'date' || schemaObj.format === 'date-time') {
122
- return { type: 'string', default: schemaObj.default }
123
- }
124
- return { type: 'string', default: schemaObj.default }
125
- }
126
-
127
- if (schemaObj.type === 'number' || schemaObj.type === 'integer') {
128
- return { type: 'number', default: schemaObj.default }
129
- }
130
-
131
- if (schemaObj.type === 'boolean') {
132
- return { type: 'boolean', default: schemaObj.default }
133
- }
134
-
135
- // Handle array
136
- if (schemaObj.type === 'array') {
137
- const items = schemaObj.items
138
- if (items) {
139
- const itemType = getTypeFromSchema(items, document, options)
140
- return {
141
- type: `Array<${formatTypeValue(itemType.type)}>`,
142
- default: schemaObj.default,
143
- }
144
- }
145
- return { type: 'unknown[]', default: schemaObj.default }
146
- }
147
-
148
- // Handle object
149
- if (schemaObj.type === 'object' || schemaObj.properties) {
150
- const props: Record<string, { type: unknown; default?: unknown }> = {}
151
- const required = schemaObj.required || []
152
-
153
- if (schemaObj.properties) {
154
- for (const [key, value] of Object.entries(schemaObj.properties)) {
155
- const propType = getTypeFromSchema(value, document, options)
156
- // Check if property has default value
157
- // Need to resolve $ref if present to check for default
158
- let hasDefault = false
159
- if ('$ref' in value) {
160
- const resolved = resolveSchemaRef(value.$ref, document)
161
- if (resolved) {
162
- hasDefault = resolved.default !== undefined
163
- }
164
- } else {
165
- const propSchema = value as OpenAPIV3_1.SchemaObject
166
- hasDefault = propSchema.default !== undefined
167
- }
168
- const isInRequired = required.includes(key)
169
-
170
- // If defaultNonNullable is true and has default, treat as required
171
- // Otherwise, mark as optional if not in required array
172
- if (defaultNonNullable && hasDefault && !isInRequired) {
173
- props[key] = propType
174
- } else if (!isInRequired) {
175
- props[`${key}?`] = propType
176
- } else {
177
- props[key] = propType
178
- }
179
- }
180
- }
181
-
182
- // Handle additionalProperties
183
- if (schemaObj.additionalProperties) {
184
- if (schemaObj.additionalProperties === true) {
185
- props['[key: string]'] = { type: 'unknown', default: undefined }
186
- } else if (typeof schemaObj.additionalProperties === 'object') {
187
- const additionalType = getTypeFromSchema(
188
- schemaObj.additionalProperties,
189
- document,
190
- options,
191
- )
192
- props['[key: string]'] = {
193
- type: additionalType.type,
194
- default: additionalType.default,
195
- }
196
- }
197
- }
198
-
199
- return { type: { ...props }, default: schemaObj.default }
200
- }
201
-
202
- // Handle oneOf/anyOf already handled above, but check again for safety
203
- return { type: 'unknown', default: undefined }
204
- }
205
-
206
- /**
207
- * Check if a value is a ParameterDefinition
208
- */
209
- function isParameterDefinition(value: unknown): value is ParameterDefinition {
210
- return (
211
- typeof value === 'object' &&
212
- value !== null &&
213
- 'type' in value &&
214
- 'in' in value &&
215
- 'name' in value
216
- )
217
- }
218
-
219
- /**
220
- * Check if all properties in an object are optional
221
- */
222
- export function areAllPropertiesOptional(
223
- obj: Record<string, unknown>,
224
- ): boolean {
225
- const entries = Object.entries(obj)
226
- if (entries.length === 0) {
227
- return true
228
- }
229
-
230
- return entries.every(([key, value]) => {
231
- // If key ends with '?', it's optional (from getTypeFromSchema)
232
- if (key.endsWith('?')) {
233
- return true
234
- }
235
-
236
- // If it's a ParameterDefinition, check required field
237
- if (isParameterDefinition(value)) {
238
- return value.required === false
239
- }
240
-
241
- // If it's a type object, check if the type itself is optional
242
- if (isTypeObject(value)) {
243
- // For type objects, check if the type is an object with all optional properties
244
- if (
245
- typeof value.type === 'object' &&
246
- value.type !== null &&
247
- !Array.isArray(value.type)
248
- ) {
249
- return areAllPropertiesOptional(value.type as Record<string, unknown>)
250
- }
251
- return false
252
- }
253
-
254
- // For nested objects, recursively check
255
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
256
- return areAllPropertiesOptional(value as Record<string, unknown>)
257
- }
258
-
259
- return false
260
- })
261
- }
262
-
263
- /**
264
- * Format a type object to TypeScript interface/type string
265
- */
266
- export function formatType(
267
- obj: Record<string, unknown>,
268
- indent: number = 0,
269
- ): string {
270
- const indentStr = ' '.repeat(indent)
271
- const nextIndent = indent + 1
272
- const nextIndentStr = ' '.repeat(nextIndent)
273
-
274
- const entries = Object.entries(obj)
275
- .map(([key, value]) => {
276
- // Handle string values (e.g., component references)
277
- if (typeof value === 'string') {
278
- return `${nextIndentStr}${key}: ${value}`
279
- }
280
-
281
- // Handle ParameterDefinition for params and query
282
- if (isParameterDefinition(value)) {
283
- const typeStr = formatTypeValue(value.type, nextIndent)
284
- const isOptional = value.required === false
285
- const keyWithOptional = isOptional ? `${key}?` : key
286
- let description = ''
287
- if (value.description) {
288
- description += `${nextIndentStr}/**\n${nextIndentStr} * ${value.description}`
289
- if (typeof value.default !== 'undefined') {
290
- description += `\n${nextIndentStr} * @default {${value.default}}`
291
- }
292
- description = `${description}\n${nextIndentStr} */\n${nextIndentStr}`
293
- } else if (typeof value.default !== 'undefined') {
294
- description += `${nextIndentStr}/** @default {${value.default}} */\n${nextIndentStr}`
295
- } else {
296
- description = nextIndentStr
297
- }
298
- return `${description}${keyWithOptional}: ${typeStr}`
299
- }
300
-
301
- // Handle { type: unknown, default?: unknown } structure (from getTypeFromSchema)
302
- if (isTypeObject(value)) {
303
- const formattedValue = formatTypeValue(value.type, nextIndent)
304
- // Key already has '?' if it's optional (from getTypeFromSchema), keep it as is
305
- return `${nextIndentStr}${key}: ${formattedValue}`
306
- }
307
-
308
- // Check if value is an object (like params, query) with all optional properties
309
- const valueAllOptional =
310
- typeof value === 'object' &&
311
- value !== null &&
312
- !Array.isArray(value) &&
313
- areAllPropertiesOptional(value as Record<string, unknown>)
314
- const optionalMarker = valueAllOptional ? '?' : ''
315
-
316
- const formattedValue = formatTypeValue(value, nextIndent)
317
- return `${nextIndentStr}${key}${optionalMarker}: ${formattedValue}`
318
- })
319
- .join(';\n')
320
-
321
- if (entries.length === 0) {
322
- return '{}'
323
- }
324
-
325
- return `{\n${entries};\n${indentStr}}`
326
- }
327
-
328
- /**
329
- * Check if a value is a type object with { type, default? } structure
330
- */
331
- function isTypeObject(
332
- value: unknown,
333
- ): value is { type: unknown; default?: unknown } {
334
- return (
335
- typeof value === 'object' &&
336
- value !== null &&
337
- 'type' in value &&
338
- Object.keys(value).length <= 2 &&
339
- (!('default' in value) || Object.keys(value).length === 2)
340
- )
341
- }
342
-
343
- /**
344
- * Format a type value to TypeScript type string
345
- */
346
- export function formatTypeValue(value: unknown, indent: number = 0): string {
347
- if (typeof value === 'string') {
348
- return value
349
- }
350
-
351
- // Handle { type: unknown, default?: unknown } structure
352
- if (isTypeObject(value)) {
353
- return formatTypeValue(value.type, indent)
354
- }
355
-
356
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
357
- return formatType(value as Record<string, unknown>, indent)
358
- }
359
-
360
- return String(value)
361
- }
362
-
363
- /**
364
- * Extract parameters from OpenAPI operation
365
- */
366
- export function extractParameters(
367
- pathItem: OpenAPIV3_1.PathItemObject | undefined,
368
- operation: OpenAPIV3_1.OperationObject | undefined,
369
- document: OpenAPIV3_1.Document,
370
- ): {
371
- pathParams: Record<string, ParameterDefinition>
372
- queryParams: Record<string, ParameterDefinition>
373
- headerParams: Record<string, ParameterDefinition>
374
- } {
375
- const pathParams: Record<string, ParameterDefinition> = {}
376
- const queryParams: Record<string, ParameterDefinition> = {}
377
- const headerParams: Record<string, ParameterDefinition> = {}
378
-
379
- const allParams = [
380
- ...(pathItem?.parameters || []),
381
- ...(operation?.parameters || []),
382
- ]
383
-
384
- for (const param of allParams) {
385
- if ('$ref' in param) {
386
- // Resolve $ref parameter
387
- const resolved = resolveParameterRef(param.$ref, document)
388
- if (
389
- resolved &&
390
- 'in' in resolved &&
391
- 'name' in resolved &&
392
- typeof resolved.in === 'string' &&
393
- typeof resolved.name === 'string'
394
- ) {
395
- const paramSchema =
396
- 'schema' in resolved && resolved.schema ? resolved.schema : {}
397
- const { type: paramType, default: paramDefault } = getTypeFromSchema(
398
- paramSchema,
399
- document,
400
- { defaultNonNullable: false },
401
- )
402
- const result = {
403
- ...resolved,
404
- type: paramType,
405
- default: paramDefault,
406
- }
407
- if (resolved.in === 'path') {
408
- pathParams[resolved.name] = result
409
- } else if (resolved.in === 'query') {
410
- queryParams[resolved.name] = result
411
- } else if (resolved.in === 'header') {
412
- headerParams[resolved.name] = result
413
- }
414
- }
415
- continue
416
- }
417
-
418
- const paramSchema = param.schema || {}
419
- const { type: paramType, default: paramDefault } = getTypeFromSchema(
420
- paramSchema,
421
- document,
422
- { defaultNonNullable: false },
423
- )
424
- const result = {
425
- ...param,
426
- type: paramType,
427
- default: paramDefault,
428
- }
429
-
430
- if (param.in === 'path') {
431
- pathParams[param.name] = result
432
- } else if (param.in === 'query') {
433
- queryParams[param.name] = result
434
- } else if (param.in === 'header') {
435
- headerParams[param.name] = result
436
- }
437
- }
438
-
439
- return { pathParams, queryParams, headerParams }
440
- }
441
-
442
- /**
443
- * Extract request body from OpenAPI operation
444
- */
445
- export function extractRequestBody(
446
- requestBody:
447
- | OpenAPIV3_1.RequestBodyObject
448
- | OpenAPIV3_1.ReferenceObject
449
- | undefined,
450
- document: OpenAPIV3_1.Document,
451
- ): unknown {
452
- if (!requestBody) {
453
- return undefined
454
- }
455
-
456
- if ('$ref' in requestBody) {
457
- const resolved = resolveSchemaRef(requestBody.$ref, document)
458
- if (resolved && 'content' in resolved && resolved.content) {
459
- const content =
460
- resolved.content as OpenAPIV3_1.RequestBodyObject['content']
461
- const jsonContent = content['application/json']
462
- if (jsonContent && 'schema' in jsonContent && jsonContent.schema) {
463
- return getTypeFromSchema(jsonContent.schema, document, {
464
- defaultNonNullable: false,
465
- }).type
466
- }
467
- }
468
- return 'unknown'
469
- }
470
-
471
- const content = requestBody.content
472
- if (content) {
473
- const jsonContent = content['application/json']
474
- if (jsonContent && 'schema' in jsonContent && jsonContent.schema) {
475
- return getTypeFromSchema(jsonContent.schema, document, {
476
- defaultNonNullable: false,
477
- }).type
478
- }
479
- }
480
-
481
- return undefined
482
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './create-url-map'
2
- export * from './generate-interface'
@@ -1,6 +0,0 @@
1
- export function wrapInterfaceKeyGuard(key: string): string {
2
- if (key.includes('/')) {
3
- return `[\`${key}\`]`
4
- }
5
- return key
6
- }
package/tsconfig.json DELETED
@@ -1,34 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "jsx": "react-jsx",
9
- "allowJs": true,
10
-
11
- // Bundler mode
12
- "moduleResolution": "bundler",
13
- "verbatimModuleSyntax": true,
14
- "emitDeclarationOnly": true,
15
-
16
- // Best practices
17
- "strict": true,
18
- "skipLibCheck": true,
19
- "noFallthroughCasesInSwitch": true,
20
- "noUncheckedIndexedAccess": true,
21
- "noImplicitOverride": true,
22
-
23
- // Some stricter flags (disabled by default)
24
- "noUnusedLocals": false,
25
- "noUnusedParameters": false,
26
- "noPropertyAccessFromIndexSignature": false,
27
- "declaration": true,
28
- "declarationMap": true,
29
- "outDir": "dist",
30
- "rootDir": "src"
31
- },
32
- "include": ["src/**/*.ts"],
33
- "exclude": ["dist", "node_modules"]
34
- }