@orpc/openapi 0.0.4 → 0.0.5

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.
package/src/generator.ts CHANGED
@@ -1,20 +1,20 @@
1
+ import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
1
2
  import { type ContractRouter, eachContractRouterLeaf } from '@orpc/contract'
2
3
  import { type Router, toContractRouter } from '@orpc/server'
3
4
  import { findDeepMatches, isPlainObject, omit } from '@orpc/shared'
4
5
  import { preSerialize } from '@orpc/transformer'
5
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
6
6
  import {
7
7
  type MediaTypeObject,
8
- type OpenAPIObject,
9
8
  OpenApiBuilder,
9
+ type OpenAPIObject,
10
10
  type OperationObject,
11
11
  type ParameterObject,
12
12
  type RequestBodyObject,
13
13
  type ResponseObject,
14
14
  } from 'openapi3-ts/oas31'
15
15
  import {
16
- UNSUPPORTED_JSON_SCHEMA,
17
16
  extractJSONSchema,
17
+ UNSUPPORTED_JSON_SCHEMA,
18
18
  zodToJsonSchema,
19
19
  } from './zod-to-json-schema'
20
20
 
@@ -45,17 +45,17 @@ export function generateOpenAPI(
45
45
  } & Omit<OpenAPIObject, 'openapi'>,
46
46
  options?: GenerateOpenAPIOptions,
47
47
  ): OpenAPIObject {
48
- const throwOnMissingTagDefinition =
49
- options?.throwOnMissingTagDefinition ?? false
50
- const ignoreUndefinedPathProcedures =
51
- options?.ignoreUndefinedPathProcedures ?? false
48
+ const throwOnMissingTagDefinition
49
+ = options?.throwOnMissingTagDefinition ?? false
50
+ const ignoreUndefinedPathProcedures
51
+ = options?.ignoreUndefinedPathProcedures ?? false
52
52
 
53
53
  const builder = new OpenApiBuilder({
54
54
  ...omit(opts, ['router']),
55
55
  openapi: '3.1.0',
56
56
  })
57
57
 
58
- const rootTags = opts.tags?.map((tag) => tag.name) ?? []
58
+ const rootTags = opts.tags?.map(tag => tag.name) ?? []
59
59
  const router = toContractRouter(opts.router)
60
60
 
61
61
  eachContractRouterLeaf(router, (procedure, path_) => {
@@ -76,7 +76,7 @@ export function generateOpenAPI(
76
76
  : {}
77
77
 
78
78
  const params: ParameterObject[] | undefined = (() => {
79
- const names = path.match(/{([^}]+)}/g)
79
+ const names = path.match(/\{([^}]+)\}/g)
80
80
 
81
81
  if (!names || !names.length) {
82
82
  return undefined
@@ -89,7 +89,7 @@ export function generateOpenAPI(
89
89
  }
90
90
 
91
91
  return names
92
- .map((raw) => raw.slice(1, -1))
92
+ .map(raw => raw.slice(1, -1))
93
93
  .map((name) => {
94
94
  let schema = inputSchema.properties?.[name]
95
95
  const required = inputSchema.required?.includes(name)
@@ -127,19 +127,20 @@ export function generateOpenAPI(
127
127
  ...inputSchema,
128
128
  properties: inputSchema.properties
129
129
  ? Object.entries(inputSchema.properties).reduce(
130
- (acc, [key, value]) => {
131
- if (key !== name) {
132
- acc[key] = value
133
- }
134
-
135
- return acc
136
- },
137
- {} as Record<string, JSONSchema>,
138
- )
130
+ (acc, [key, value]) => {
131
+ if (key !== name) {
132
+ acc[key] = value
133
+ }
134
+
135
+ return acc
136
+ },
137
+ {} as Record<string, JSONSchema>,
138
+ )
139
139
  : undefined,
140
- required: inputSchema.required?.filter((v) => v !== name),
140
+ required: inputSchema.required?.filter(v => v !== name),
141
141
  examples: inputSchema.examples?.map((example) => {
142
- if (!isPlainObject(example)) return example
142
+ if (!isPlainObject(example))
143
+ return example
143
144
 
144
145
  return Object.entries(example).reduce(
145
146
  (acc, [key, value]) => {
@@ -220,8 +221,8 @@ export function generateOpenAPI(
220
221
  contentMediaType: string
221
222
  })[]
222
223
 
223
- const isStillHasFileSchema =
224
- findDeepMatches(isFileSchema, schema).values.length > 0
224
+ const isStillHasFileSchema
225
+ = findDeepMatches(isFileSchema, schema).values.length > 0
225
226
 
226
227
  if (files.length) {
227
228
  parameters.push({
@@ -268,8 +269,8 @@ export function generateOpenAPI(
268
269
  contentMediaType: string
269
270
  })[]
270
271
 
271
- const isStillHasFileSchema =
272
- findDeepMatches(isFileSchema, schema).values.length > 0
272
+ const isStillHasFileSchema
273
+ = findDeepMatches(isFileSchema, schema).values.length > 0
273
274
 
274
275
  const content: Record<string, MediaTypeObject> = {}
275
276
 
@@ -295,7 +296,7 @@ export function generateOpenAPI(
295
296
  })()
296
297
 
297
298
  if (throwOnMissingTagDefinition && internal.tags) {
298
- const missingTag = internal.tags.find((tag) => !rootTags.includes(tag))
299
+ const missingTag = internal.tags.find(tag => !rootTags.includes(tag))
299
300
 
300
301
  if (missingTag !== undefined) {
301
302
  throw new Error(
@@ -313,7 +314,7 @@ export function generateOpenAPI(
313
314
  parameters: parameters.length ? parameters : undefined,
314
315
  requestBody,
315
316
  responses: {
316
- '200': successResponse,
317
+ 200: successResponse,
317
318
  },
318
319
  }
319
320
 
@@ -326,11 +327,12 @@ export function generateOpenAPI(
326
327
  }
327
328
 
328
329
  function isFileSchema(schema: unknown) {
329
- if (typeof schema !== 'object' || schema === null) return false
330
+ if (typeof schema !== 'object' || schema === null)
331
+ return false
330
332
  return (
331
- 'type' in schema &&
332
- 'contentMediaType' in schema &&
333
- typeof schema.type === 'string' &&
334
- typeof schema.contentMediaType === 'string'
333
+ 'type' in schema
334
+ && 'contentMediaType' in schema
335
+ && typeof schema.type === 'string'
336
+ && typeof schema.contentMediaType === 'string'
335
337
  )
336
338
  }
@@ -244,7 +244,7 @@ describe('special types', () => {
244
244
 
245
245
  describe('transform and effects', () => {
246
246
  it('should handle transform effects based on mode', () => {
247
- const schema = z.string().transform((val) => val.length)
247
+ const schema = z.string().transform(val => val.length)
248
248
 
249
249
  expect(zodToJsonSchema(schema, { mode: 'input' })).toEqual({
250
250
  type: 'string',
@@ -14,11 +14,8 @@ import {
14
14
  type KeySchema,
15
15
  type ZodAny,
16
16
  type ZodArray,
17
- type ZodBigInt,
18
- type ZodBoolean,
19
17
  type ZodBranded,
20
18
  type ZodCatch,
21
- type ZodDate,
22
19
  type ZodDefault,
23
20
  type ZodDiscriminatedUnion,
24
21
  type ZodEffects,
@@ -28,7 +25,6 @@ import {
28
25
  type ZodLazy,
29
26
  type ZodLiteral,
30
27
  type ZodMap,
31
- type ZodNaN,
32
28
  type ZodNativeEnum,
33
29
  type ZodNullable,
34
30
  type ZodNumber,
@@ -213,8 +209,8 @@ export function zodToJsonSchema(
213
209
  json.pattern = `${escapeStringRegexp(check.value)}$`
214
210
  break
215
211
  case 'emoji':
216
- json.pattern =
217
- '^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$'
212
+ json.pattern
213
+ = '^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$'
218
214
  break
219
215
  case 'nanoid':
220
216
  json.pattern = '^[a-zA-Z0-9_-]{21}$'
@@ -278,14 +274,10 @@ export function zodToJsonSchema(
278
274
  }
279
275
 
280
276
  case ZodFirstPartyTypeKind.ZodNaN: {
281
- const schema_ = schema as ZodNaN
282
-
283
277
  return { const: 'NaN' }
284
278
  }
285
279
 
286
280
  case ZodFirstPartyTypeKind.ZodBigInt: {
287
- const schema_ = schema as ZodBigInt
288
-
289
281
  const json: JSONSchema = { type: 'string', pattern: '^-?[0-9]+$' }
290
282
 
291
283
  // WARN: ignore checks
@@ -294,14 +286,10 @@ export function zodToJsonSchema(
294
286
  }
295
287
 
296
288
  case ZodFirstPartyTypeKind.ZodBoolean: {
297
- const schema_ = schema as ZodBoolean
298
-
299
289
  return { type: 'boolean' }
300
290
  }
301
291
 
302
292
  case ZodFirstPartyTypeKind.ZodDate: {
303
- const schema_ = schema as ZodDate
304
-
305
293
  const jsonSchema: JSONSchema = { type: 'string', format: Format.Date }
306
294
 
307
295
  // WARN: ignore checks
@@ -398,7 +386,7 @@ export function zodToJsonSchema(
398
386
  for (const [key, value] of Object.entries(schema_.shape)) {
399
387
  const { schema, matches } = extractJSONSchema(
400
388
  zodToJsonSchema(value, childOptions),
401
- (schema) => schema === UNDEFINED_JSON_SCHEMA,
389
+ schema => schema === UNDEFINED_JSON_SCHEMA,
402
390
  )
403
391
 
404
392
  if (schema) {
@@ -423,14 +411,15 @@ export function zodToJsonSchema(
423
411
  childOptions,
424
412
  )
425
413
  if (schema_._def.unknownKeys === 'strict') {
426
- json.additionalProperties =
427
- additionalProperties === UNSUPPORTED_JSON_SCHEMA
414
+ json.additionalProperties
415
+ = additionalProperties === UNSUPPORTED_JSON_SCHEMA
428
416
  ? false
429
417
  : additionalProperties
430
- } else {
418
+ }
419
+ else {
431
420
  if (
432
- additionalProperties &&
433
- additionalProperties !== UNSUPPORTED_JSON_SCHEMA
421
+ additionalProperties
422
+ && additionalProperties !== UNSUPPORTED_JSON_SCHEMA
434
423
  ) {
435
424
  json.additionalProperties = additionalProperties
436
425
  }
@@ -551,8 +540,8 @@ export function zodToJsonSchema(
551
540
  const schema_ = schema as ZodEffects<ZodTypeAny>
552
541
 
553
542
  if (
554
- schema_._def.effect.type === 'transform' &&
555
- childOptions?.mode === 'output'
543
+ schema_._def.effect.type === 'transform'
544
+ && childOptions?.mode === 'output'
556
545
  ) {
557
546
  return {}
558
547
  }
@@ -602,7 +591,7 @@ export function extractJSONSchema(
602
591
  schema: JSONSchema,
603
592
  check: (schema: JSONSchema) => boolean,
604
593
  matches: JSONSchema[] = [],
605
- ): { schema: JSONSchema | undefined; matches: JSONSchema[] } {
594
+ ): { schema: JSONSchema | undefined, matches: JSONSchema[] } {
606
595
  if (check(schema)) {
607
596
  matches.push(schema)
608
597
  return { schema: undefined, matches }
@@ -615,14 +604,14 @@ export function extractJSONSchema(
615
604
  // TODO: $ref
616
605
 
617
606
  if (
618
- schema.anyOf &&
619
- Object.keys(schema).every(
620
- (k) => k === 'anyOf' || NON_LOGIC_KEYWORDS.includes(k as any),
607
+ schema.anyOf
608
+ && Object.keys(schema).every(
609
+ k => k === 'anyOf' || NON_LOGIC_KEYWORDS.includes(k as any),
621
610
  )
622
611
  ) {
623
612
  const anyOf = schema.anyOf
624
- .map((s) => extractJSONSchema(s, check, matches).schema)
625
- .filter((v) => !!v)
613
+ .map(s => extractJSONSchema(s, check, matches).schema)
614
+ .filter(v => !!v)
626
615
 
627
616
  if (anyOf.length === 1 && typeof anyOf[0] === 'object') {
628
617
  return { schema: { ...schema, anyOf: undefined, ...anyOf[0] }, matches }
@@ -640,14 +629,14 @@ export function extractJSONSchema(
640
629
  // TODO: $ref
641
630
 
642
631
  if (
643
- schema.oneOf &&
644
- Object.keys(schema).every(
645
- (k) => k === 'oneOf' || NON_LOGIC_KEYWORDS.includes(k as any),
632
+ schema.oneOf
633
+ && Object.keys(schema).every(
634
+ k => k === 'oneOf' || NON_LOGIC_KEYWORDS.includes(k as any),
646
635
  )
647
636
  ) {
648
637
  const oneOf = schema.oneOf
649
- .map((s) => extractJSONSchema(s, check, matches).schema)
650
- .filter((v) => !!v)
638
+ .map(s => extractJSONSchema(s, check, matches).schema)
639
+ .filter(v => !!v)
651
640
 
652
641
  if (oneOf.length === 1 && typeof oneOf[0] === 'object') {
653
642
  return { schema: { ...schema, oneOf: undefined, ...oneOf[0] }, matches }