@elysiajs/openapi 1.4.15 → 1.4.16

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/dist/openapi.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type AnyElysia, type TSchema, type InputSchema } from 'elysia';
1
+ import { type AnyElysia, type InputSchema } from 'elysia';
2
2
  import type { OpenAPIV3 } from 'openapi-types';
3
- import { TAnySchema, type TProperties } from '@sinclair/typebox';
4
- import type { AdditionalReferences, ElysiaOpenAPIConfig, MapJsonSchema } from './types';
3
+ import { TAnySchema } from '@sinclair/typebox';
4
+ import type { AdditionalReferences, ElysiaOpenAPIConfig, MapJsonSchema, OpenAPIVersion } from './types';
5
5
  export declare const capitalize: (word: string) => string;
6
6
  /**
7
7
  * Get all possible paths of a path with optional parameters
@@ -10,24 +10,27 @@ export declare const capitalize: (word: string) => string;
10
10
  */
11
11
  export declare const getPossiblePath: (path: string) => string[];
12
12
  export declare const getLoosePath: (path: string) => string;
13
- export declare const unwrapSchema: (schema: InputSchema["body"], mapJsonSchema?: MapJsonSchema, io?: "input" | "output") => OpenAPIV3.SchemaObject | undefined;
13
+ export declare const unwrapSchema: (schema: InputSchema["body"], mapJsonSchema?: MapJsonSchema, io?: "input" | "output", openapiVersion?: OpenAPIVersion) => OpenAPIV3.SchemaObject | undefined;
14
14
  /**
15
15
  * Convert TypeBox enum-like Union schemas to OpenAPI enum schemas
16
16
  *
17
17
  * Otherwise, return the schema as is
18
18
  */
19
19
  export declare const enumToOpenApi: <T extends TAnySchema | OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined>(_schema: T) => T;
20
+ export declare const nullToOpenApi: <T>(schema: T, openapiVersion: OpenAPIVersion) => T;
20
21
  /**
21
- * Converts Elysia routes to OpenAPI 3.0.3 paths schema
22
+ * Converts Elysia routes to OpenAPI paths schema
22
23
  * @param routes Array of Elysia route objects
23
24
  * @returns OpenAPI paths object
24
25
  */
25
- export declare function toOpenAPISchema(app: AnyElysia, exclude?: ElysiaOpenAPIConfig['exclude'], references?: AdditionalReferences, vendors?: MapJsonSchema): {
26
+ export declare function toOpenAPISchema(app: AnyElysia, exclude?: ElysiaOpenAPIConfig['exclude'], references?: AdditionalReferences, vendors?: MapJsonSchema, openapiVersion?: OpenAPIVersion): {
26
27
  components: {
27
28
  schemas: any;
28
29
  };
29
30
  paths: OpenAPIV3.PathsObject<{}, {}>;
30
31
  };
31
- export declare const withHeaders: (schema: TSchema, headers: TProperties) => TSchema & {
32
- headers: TProperties;
32
+ type ResponseHeaderSchemas = Record<string, Exclude<InputSchema['headers'], undefined>>;
33
+ export declare const withHeaders: <S extends Exclude<InputSchema["body"], string | undefined>, H extends ResponseHeaderSchemas>(schema: S, headers: H) => S & {
34
+ headers: H;
33
35
  };
36
+ export {};
package/dist/openapi.mjs CHANGED
@@ -1,14 +1,8 @@
1
1
  // src/openapi.ts
2
2
  import { t } from "elysia";
3
-
4
- // node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
5
- var Kind = /* @__PURE__ */ Symbol.for("TypeBox.Kind");
6
-
7
- // src/openapi.ts
3
+ import { Kind } from "@sinclair/typebox";
8
4
  var capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
9
- var toRef = (name) => t.Ref(
10
- name.startsWith("#/") ? name : `#/components/schemas/${name}`
11
- );
5
+ var toRef = (name) => t.Ref(name.startsWith("#/") ? name : `#/components/schemas/${name}`);
12
6
  var toOperationId = (method, paths) => {
13
7
  let operationId = method.toLowerCase();
14
8
  if (!paths || paths === "/") return operationId + "Index";
@@ -142,7 +136,7 @@ var normalizeSchemaReference = (schema) => {
142
136
  if (typeof schema !== "string") return schema;
143
137
  return toRef(schema);
144
138
  };
145
- var mergeSchemaProperty = (existing, incoming, vendors) => {
139
+ var mergeSchemaProperty = (existing, incoming, vendors, openapiVersion = "3.1.2") => {
146
140
  if (!existing) return incoming;
147
141
  if (!incoming) return existing;
148
142
  let existingSchema = normalizeSchemaReference(existing);
@@ -150,9 +144,19 @@ var mergeSchemaProperty = (existing, incoming, vendors) => {
150
144
  if (!existingSchema) return incoming;
151
145
  if (!incomingSchema) return existing;
152
146
  if (!isTSchema(incomingSchema) && incomingSchema["~standard"])
153
- incomingSchema = unwrapSchema(incomingSchema, vendors);
147
+ incomingSchema = unwrapSchema(
148
+ incomingSchema,
149
+ vendors,
150
+ "input",
151
+ openapiVersion
152
+ );
154
153
  if (!isTSchema(existingSchema) && existingSchema["~standard"])
155
- existingSchema = unwrapSchema(existingSchema, vendors);
154
+ existingSchema = unwrapSchema(
155
+ existingSchema,
156
+ vendors,
157
+ "input",
158
+ openapiVersion
159
+ );
156
160
  if (!incomingSchema) return existingSchema;
157
161
  if (!existingSchema) return incomingSchema;
158
162
  const { schema: mergedSchema, notObjects } = mergeObjectSchemas([
@@ -165,24 +169,30 @@ var mergeSchemaProperty = (existing, incoming, vendors) => {
165
169
  }
166
170
  return mergedSchema;
167
171
  };
168
- var unwrapResponseSchema = (schema, vendors) => typeof schema === "string" ? normalizeSchemaReference(schema) : !schema ? void 0 : isTSchema(schema) ? schema : (
172
+ var unwrapResponseSchema = (schema, vendors, openapiVersion = "3.1.2") => typeof schema === "string" ? normalizeSchemaReference(schema) : !schema ? void 0 : isTSchema(schema) ? schema : (
169
173
  // @ts-ignore
170
- schema["~standard"] ? unwrapSchema(schema, vendors, "output") : Object.fromEntries(
174
+ schema["~standard"] ? unwrapSchema(
175
+ schema,
176
+ vendors,
177
+ "output",
178
+ openapiVersion
179
+ ) : Object.fromEntries(
171
180
  Object.entries(schema).map(([status, schema2]) => [
172
181
  status,
173
182
  typeof schema2 === "string" ? normalizeSchemaReference(schema2) : isTSchema(schema2) ? schema2 : unwrapSchema(
174
183
  schema2,
175
184
  vendors,
176
- "output"
185
+ "output",
186
+ openapiVersion
177
187
  )
178
188
  ])
179
189
  )
180
190
  );
181
- var mergeResponseSchema = (_existing, _incoming, vendors) => {
191
+ var mergeResponseSchema = (_existing, _incoming, vendors, openapiVersion = "3.1.2") => {
182
192
  if (!_existing) return _incoming;
183
193
  if (!_incoming) return _existing;
184
- let existing = unwrapResponseSchema(_existing, vendors);
185
- let incoming = unwrapResponseSchema(_incoming, vendors);
194
+ let existing = unwrapResponseSchema(_existing, vendors, openapiVersion);
195
+ let incoming = unwrapResponseSchema(_incoming, vendors, openapiVersion);
186
196
  if (!existing && !incoming) return void 0;
187
197
  if (incoming && !existing) return incoming;
188
198
  if (existing && !incoming) return existing;
@@ -204,14 +214,15 @@ var mergeResponseSchema = (_existing, _incoming, vendors) => {
204
214
  schema[status] = mergeSchemaProperty(
205
215
  existingSchema,
206
216
  incomingSchema,
207
- vendors
217
+ vendors,
218
+ openapiVersion
208
219
  );
209
220
  else if (existingSchema) schema[status] = existingSchema;
210
221
  else if (incomingSchema) schema[status] = incomingSchema;
211
222
  }
212
223
  return schema;
213
224
  };
214
- var mergeStandaloneValidators = (hooks, vendors) => {
225
+ var mergeStandaloneValidators = (hooks, vendors, openapiVersion = "3.1.2") => {
215
226
  const merged = { ...hooks };
216
227
  if (!hooks.standaloneValidator?.length) return merged;
217
228
  for (const validator of hooks.standaloneValidator) {
@@ -219,37 +230,43 @@ var mergeStandaloneValidators = (hooks, vendors) => {
219
230
  merged.body = mergeSchemaProperty(
220
231
  merged.body,
221
232
  validator.body,
222
- vendors
233
+ vendors,
234
+ openapiVersion
223
235
  );
224
236
  if (validator.headers)
225
237
  merged.headers = mergeSchemaProperty(
226
238
  merged.headers,
227
239
  validator.headers,
228
- vendors
240
+ vendors,
241
+ openapiVersion
229
242
  );
230
243
  if (validator.query)
231
244
  merged.query = mergeSchemaProperty(
232
245
  merged.query,
233
246
  validator.query,
234
- vendors
247
+ vendors,
248
+ openapiVersion
235
249
  );
236
250
  if (validator.params)
237
251
  merged.params = mergeSchemaProperty(
238
252
  merged.params,
239
253
  validator.params,
240
- vendors
254
+ vendors,
255
+ openapiVersion
241
256
  );
242
257
  if (validator.cookie)
243
258
  merged.cookie = mergeSchemaProperty(
244
259
  merged.cookie,
245
260
  validator.cookie,
246
- vendors
261
+ vendors,
262
+ openapiVersion
247
263
  );
248
264
  if (validator.response)
249
265
  merged.response = mergeResponseSchema(
250
266
  merged.response,
251
267
  validator.response,
252
- vendors
268
+ vendors,
269
+ openapiVersion
253
270
  );
254
271
  }
255
272
  if (typeof merged.body === "string")
@@ -275,36 +292,51 @@ var mergeStandaloneValidators = (hooks, vendors) => {
275
292
  }
276
293
  return merged;
277
294
  };
278
- var flattenRoutes = (routes, vendors) => routes.map((route) => {
295
+ var flattenRoutes = (routes, vendors, openapiVersion = "3.1.2") => routes.map((route) => {
279
296
  if (!route.hooks?.standaloneValidator?.length) return route;
280
297
  return {
281
298
  ...route,
282
- hooks: mergeStandaloneValidators(route.hooks, vendors)
299
+ hooks: mergeStandaloneValidators(
300
+ route.hooks,
301
+ vendors,
302
+ openapiVersion
303
+ )
283
304
  };
284
305
  });
285
- var unwrapReference = (schema, definitions) => {
306
+ var unwrapReference = (schema, definitions, openapiVersion = "3.1.2") => {
286
307
  const ref = schema?.$ref;
287
308
  if (!ref) return schema;
288
309
  const name = ref.slice(ref.lastIndexOf("/") + 1);
289
310
  if (ref && definitions[name]) schema = definitions[name];
290
- return enumToOpenApi(schema);
311
+ return nullToOpenApi(enumToOpenApi(schema), openapiVersion);
291
312
  };
292
- var unwrapSchema = (schema, mapJsonSchema, io = "input") => {
313
+ var unwrapSchema = (schema, mapJsonSchema, io = "input", openapiVersion = "3.1.2") => {
293
314
  if (!schema) return;
294
315
  if (typeof schema === "string") schema = toRef(schema);
295
- if (Kind in schema) return enumToOpenApi(schema);
316
+ if (Kind in schema)
317
+ return nullToOpenApi(enumToOpenApi(schema), openapiVersion);
296
318
  if (!schema?.["~standard"] && // @ts-ignore
297
319
  (schema.$schema || schema.type || schema.properties || schema.items))
298
- return schema;
320
+ return nullToOpenApi(schema, openapiVersion);
299
321
  if (!schema?.["~standard"]) return;
300
- const vendor = schema["~standard"].vendor;
322
+ const standard = schema["~standard"];
323
+ const vendor = standard.vendor;
301
324
  try {
325
+ const jsonSchemaTarget = openapiVersion.startsWith("3.0.") ? "draft-07" : "draft-2020-12";
302
326
  if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === "function")
303
- return enumToOpenApi(mapJsonSchema[vendor](schema));
304
- if (schema["~standard"]?.jsonSchema?.[io])
305
- return enumToOpenApi(schema["~standard"].jsonSchema[io]({
306
- target: "draft-2020-12"
307
- }));
327
+ return nullToOpenApi(
328
+ enumToOpenApi(mapJsonSchema[vendor](schema)),
329
+ openapiVersion
330
+ );
331
+ if (standard.jsonSchema?.[io])
332
+ return nullToOpenApi(
333
+ enumToOpenApi(
334
+ standard.jsonSchema[io]({
335
+ target: jsonSchemaTarget
336
+ })
337
+ ),
338
+ openapiVersion
339
+ );
308
340
  switch (vendor) {
309
341
  case "zod":
310
342
  if (warned.zod4 || warned.zod3) break;
@@ -349,10 +381,16 @@ var unwrapSchema = (schema, mapJsonSchema, io = "input") => {
349
381
  break;
350
382
  }
351
383
  if (vendor === "arktype")
352
- return enumToOpenApi(schema?.toJsonSchema?.());
353
- return enumToOpenApi(
354
- // @ts-ignore
355
- schema.toJSONSchema?.() ?? schema?.toJsonSchema?.()
384
+ return nullToOpenApi(
385
+ enumToOpenApi(schema.toJsonSchema?.()),
386
+ openapiVersion
387
+ );
388
+ return nullToOpenApi(
389
+ enumToOpenApi(
390
+ // @ts-ignore
391
+ schema.toJSONSchema?.() ?? schema?.toJsonSchema?.()
392
+ ),
393
+ openapiVersion
356
394
  );
357
395
  } catch (error) {
358
396
  console.warn(error);
@@ -385,9 +423,154 @@ var enumToOpenApi = (_schema) => {
385
423
  ...schema,
386
424
  items: enumToOpenApi(schema.items)
387
425
  };
426
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
427
+ const mapped = schema.anyOf.map(
428
+ (item) => item && typeof item === "object" && item.type === "Date" ? { type: "string", format: "date-time" } : enumToOpenApi(item)
429
+ );
430
+ const seen = /* @__PURE__ */ new Set();
431
+ const deduped = mapped.filter((item) => {
432
+ if (!item || typeof item !== "object") return true;
433
+ const key = JSON.stringify(
434
+ Object.fromEntries(
435
+ Object.entries(item).sort(
436
+ ([a], [b]) => a < b ? -1 : a > b ? 1 : 0
437
+ )
438
+ )
439
+ );
440
+ if (seen.has(key)) return false;
441
+ seen.add(key);
442
+ return true;
443
+ });
444
+ if (deduped.length === 1) return deduped[0];
445
+ return { ...schema, anyOf: deduped };
446
+ }
388
447
  return schema;
389
448
  };
390
- function toOpenAPISchema(app, exclude, references, vendors) {
449
+ var SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set([
450
+ "properties",
451
+ "patternProperties",
452
+ "$defs",
453
+ "definitions",
454
+ "dependentSchemas"
455
+ ]);
456
+ var SCHEMA_ARRAY_KEYS = /* @__PURE__ */ new Set(["allOf", "anyOf", "oneOf", "prefixItems"]);
457
+ var SCHEMA_VALUE_KEYS = /* @__PURE__ */ new Set([
458
+ "items",
459
+ "additionalProperties",
460
+ "unevaluatedProperties",
461
+ "contains",
462
+ "not",
463
+ "if",
464
+ "then",
465
+ "else",
466
+ "propertyNames"
467
+ ]);
468
+ var isSchemaObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
469
+ var nullToOpenApi = (schema, openapiVersion) => {
470
+ const normalize = (value) => {
471
+ if (Array.isArray(value)) return value.map(normalize);
472
+ if (!isSchemaObject(value)) return value;
473
+ const normalized = { ...value };
474
+ const isOpenAPI30 = openapiVersion.startsWith("3.0.");
475
+ for (const unionKey of ["anyOf", "oneOf"]) {
476
+ const union = normalized[unionKey];
477
+ if (!Array.isArray(union)) continue;
478
+ const nonNull = union.filter(
479
+ (item) => !isSchemaObject(item) || item.type !== "null"
480
+ );
481
+ if (nonNull.length === union.length) continue;
482
+ const normalizedNonNull = nonNull.map(normalize);
483
+ if (isOpenAPI30) {
484
+ delete normalized[unionKey];
485
+ if (normalizedNonNull.length === 1 && isSchemaObject(normalizedNonNull[0]))
486
+ Object.assign(normalized, normalizedNonNull[0]);
487
+ else if (normalizedNonNull.length)
488
+ normalized[unionKey] = normalizedNonNull;
489
+ normalized.nullable = true;
490
+ } else if (normalizedNonNull.length === 1 && isSchemaObject(normalizedNonNull[0]) && typeof normalizedNonNull[0].type === "string") {
491
+ delete normalized[unionKey];
492
+ Object.assign(normalized, normalizedNonNull[0]);
493
+ normalized.type = [normalizedNonNull[0].type, "null"];
494
+ } else normalized[unionKey] = union.map(normalize);
495
+ }
496
+ if (isOpenAPI30 && normalized.type === "null") {
497
+ delete normalized.type;
498
+ normalized.nullable = true;
499
+ } else if (isOpenAPI30 && Array.isArray(normalized.type) && normalized.type.includes("null")) {
500
+ const types = normalized.type.filter((type) => type !== "null");
501
+ normalized.nullable = true;
502
+ if (types.length === 1) normalized.type = types[0];
503
+ else if (types.length) normalized.type = types;
504
+ else delete normalized.type;
505
+ }
506
+ for (const [key, nested] of Object.entries(normalized)) {
507
+ if (SCHEMA_MAP_KEYS.has(key) && isSchemaObject(nested))
508
+ normalized[key] = Object.fromEntries(
509
+ Object.entries(nested).map(([name, child]) => [
510
+ name,
511
+ normalize(child)
512
+ ])
513
+ );
514
+ else if (SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(nested))
515
+ normalized[key] = nested.map(normalize);
516
+ else if (SCHEMA_VALUE_KEYS.has(key) && isSchemaObject(nested))
517
+ normalized[key] = normalize(nested);
518
+ else if (key === "dependencies" && isSchemaObject(nested))
519
+ normalized[key] = Object.fromEntries(
520
+ Object.entries(nested).map(([name, child]) => [
521
+ name,
522
+ isSchemaObject(child) ? normalize(child) : child
523
+ ])
524
+ );
525
+ }
526
+ return normalized;
527
+ };
528
+ return normalize(schema);
529
+ };
530
+ var toResponseHeaders = (schema, vendors, openapiVersion = "3.1.2") => {
531
+ if (!schema || typeof schema === "string" || !("headers" in schema) || !schema.headers)
532
+ return;
533
+ const entries = Object.entries(
534
+ schema.headers
535
+ ).map(
536
+ ([name, hs]) => [
537
+ name,
538
+ {
539
+ schema: unwrapSchema(
540
+ hs,
541
+ vendors,
542
+ "output",
543
+ openapiVersion
544
+ )
545
+ }
546
+ ]
547
+ ).filter(([, v]) => v.schema);
548
+ return entries.length ? Object.fromEntries(entries) : void 0;
549
+ };
550
+ var stripHeaders = (schema) => {
551
+ const { headers, ...rest } = schema;
552
+ return rest;
553
+ };
554
+ var VOID_TYPES = /* @__PURE__ */ new Set(["void", "null", "undefined"]);
555
+ var PLAIN_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean"]);
556
+ var toResponseContent = (schema, type, description) => VOID_TYPES.has(type) ? { type, description } : PLAIN_TYPES.has(type) ? { "text/plain": { schema } } : { "application/json": { schema } };
557
+ var toResponseObject = (schema, status, definitions, vendors, openapiVersion = "3.1.2") => {
558
+ const response = unwrapSchema(schema, vendors, "output", openapiVersion);
559
+ if (!response) return;
560
+ const responseSchema = stripHeaders(response);
561
+ const { type, description } = unwrapReference(
562
+ responseSchema,
563
+ definitions,
564
+ openapiVersion
565
+ );
566
+ const headers = toResponseHeaders(schema, vendors, openapiVersion);
567
+ return {
568
+ description: description ?? `Response for status ${status}`,
569
+ ...headers ? { headers } : {},
570
+ content: toResponseContent(responseSchema, type, description)
571
+ };
572
+ };
573
+ function toOpenAPISchema(app, exclude, references, vendors, openapiVersion = "3.1.2") {
391
574
  let {
392
575
  methods: excludeMethods = ["options"],
393
576
  staticFile: excludeStaticFile = true,
@@ -404,11 +587,19 @@ function toOpenAPISchema(app, exclude, references, vendors) {
404
587
  if (typeof reference === "function") references[i] = reference();
405
588
  }
406
589
  }
407
- const routes = flattenRoutes(app.getGlobalRoutes(), vendors);
590
+ const routes = flattenRoutes(app.getGlobalRoutes(), vendors, openapiVersion);
408
591
  for (const route of routes) {
409
592
  if (route.hooks?.detail?.hide) continue;
410
593
  const method = route.method.toLowerCase();
411
- if (excludeStaticFile && route.path.includes(".") || excludePaths.includes(route.path) || excludeMethods.includes(method))
594
+ if (excludeStaticFile && route.path.includes(".") || excludePaths.some((exclusion) => {
595
+ if (exclusion instanceof RegExp) {
596
+ exclusion.lastIndex = 0;
597
+ return exclusion.test(route.path);
598
+ }
599
+ if (typeof exclusion === "string")
600
+ return exclusion === route.path;
601
+ return false;
602
+ }) || excludeMethods.includes(method))
412
603
  continue;
413
604
  const hooks = route.hooks ?? {};
414
605
  if (references?.length)
@@ -454,8 +645,9 @@ function toOpenAPISchema(app, exclude, references, vendors) {
454
645
  const parameters = [];
455
646
  if (hooks.params) {
456
647
  const params = unwrapReference(
457
- unwrapSchema(hooks.params, vendors),
458
- definitions
648
+ unwrapSchema(hooks.params, vendors, "input", openapiVersion),
649
+ definitions,
650
+ openapiVersion
459
651
  );
460
652
  if (params && params.type === "object" && params.properties)
461
653
  for (const [name, schema] of Object.entries(params.properties))
@@ -479,8 +671,9 @@ function toOpenAPISchema(app, exclude, references, vendors) {
479
671
  }
480
672
  if (hooks.query) {
481
673
  const query = unwrapReference(
482
- unwrapSchema(hooks.query, vendors),
483
- definitions
674
+ unwrapSchema(hooks.query, vendors, "input", openapiVersion),
675
+ definitions,
676
+ openapiVersion
484
677
  );
485
678
  if (query && query.type === "object" && query.properties) {
486
679
  const required = query.required || [];
@@ -495,8 +688,9 @@ function toOpenAPISchema(app, exclude, references, vendors) {
495
688
  }
496
689
  if (hooks.headers) {
497
690
  const headers = unwrapReference(
498
- unwrapSchema(hooks.headers, vendors),
499
- definitions
691
+ unwrapSchema(hooks.headers, vendors, "input", openapiVersion),
692
+ definitions,
693
+ openapiVersion
500
694
  );
501
695
  if (headers && headers.type === "object" && headers.properties) {
502
696
  const required = headers.required || [];
@@ -511,8 +705,9 @@ function toOpenAPISchema(app, exclude, references, vendors) {
511
705
  }
512
706
  if (hooks.cookie) {
513
707
  const cookie = unwrapReference(
514
- unwrapSchema(hooks.cookie, vendors),
515
- definitions
708
+ unwrapSchema(hooks.cookie, vendors, "input", openapiVersion),
709
+ definitions,
710
+ openapiVersion
516
711
  );
517
712
  if (cookie && cookie.type === "object" && cookie.properties) {
518
713
  const required = cookie.required || [];
@@ -527,11 +722,17 @@ function toOpenAPISchema(app, exclude, references, vendors) {
527
722
  }
528
723
  if (parameters.length > 0) operation.parameters = parameters;
529
724
  if (hooks.body && method !== "get" && method !== "head") {
530
- const body = unwrapSchema(hooks.body, vendors);
725
+ const body = unwrapSchema(
726
+ hooks.body,
727
+ vendors,
728
+ "input",
729
+ openapiVersion
730
+ );
531
731
  if (body) {
532
732
  const { type, description, $ref, ...options } = unwrapReference(
533
733
  body,
534
- definitions
734
+ definitions,
735
+ openapiVersion
535
736
  );
536
737
  if (hooks.parse) {
537
738
  const content = {};
@@ -559,6 +760,12 @@ function toOpenAPISchema(app, exclude, references, vendors) {
559
760
  schema: body
560
761
  };
561
762
  continue;
763
+ case "arrayBuffer":
764
+ case "application/octet-stream":
765
+ content["application/octet-stream"] = {
766
+ schema: body
767
+ };
768
+ continue;
562
769
  }
563
770
  }
564
771
  operation.requestBody = {
@@ -592,50 +799,26 @@ function toOpenAPISchema(app, exclude, references, vendors) {
592
799
  if (hooks.response) {
593
800
  operation.responses = {};
594
801
  if (typeof hooks.response === "object" && // TypeBox
595
- !hooks.response.type && !hooks.response.$ref && !hooks.response["~standard"]) {
802
+ !(Kind in hooks.response) && !hooks.response.type && !hooks.response.$ref && !hooks.response["~standard"]) {
596
803
  for (let [status, schema] of Object.entries(hooks.response)) {
597
- const response = unwrapSchema(schema, vendors, "output");
598
- if (!response) continue;
599
- const { type, description, $ref, ..._options } = unwrapReference(response, definitions);
600
- operation.responses[status] = {
601
- description: description ?? `Response for status ${status}`,
602
- content: type === "void" || type === "null" || type === "undefined" ? { type, description } : type === "string" || type === "number" || type === "integer" || type === "boolean" ? {
603
- "text/plain": {
604
- schema: response
605
- }
606
- } : {
607
- "application/json": {
608
- schema: response
609
- }
610
- }
611
- };
804
+ const response = toResponseObject(
805
+ schema,
806
+ status,
807
+ definitions,
808
+ vendors,
809
+ openapiVersion
810
+ );
811
+ if (response) operation.responses[status] = response;
612
812
  }
613
813
  } else {
614
- const response = unwrapSchema(
814
+ const response = toResponseObject(
615
815
  hooks.response,
816
+ "200",
817
+ definitions,
616
818
  vendors,
617
- "output"
819
+ openapiVersion
618
820
  );
619
- if (response) {
620
- const {
621
- type: _type,
622
- description,
623
- ...options
624
- } = unwrapReference(response, definitions);
625
- const type = _type;
626
- operation.responses["200"] = {
627
- description: description ?? `Response for status 200`,
628
- content: type === "void" || type === "null" || type === "undefined" ? { type, description } : type === "string" || type === "number" || type === "integer" || type === "boolean" ? {
629
- "text/plain": {
630
- schema: response
631
- }
632
- } : {
633
- "application/json": {
634
- schema: response
635
- }
636
- }
637
- };
638
- }
821
+ if (response) operation.responses["200"] = response;
639
822
  }
640
823
  }
641
824
  for (let path of getPossiblePath(route.path)) {
@@ -669,7 +852,12 @@ function toOpenAPISchema(app, exclude, references, vendors) {
669
852
  const schemas = /* @__PURE__ */ Object.create(null);
670
853
  if (definitions)
671
854
  for (const [name, schema] of Object.entries(definitions)) {
672
- const jsonSchema = unwrapSchema(schema, vendors);
855
+ const jsonSchema = unwrapSchema(
856
+ schema,
857
+ vendors,
858
+ "input",
859
+ openapiVersion
860
+ );
673
861
  if (jsonSchema) schemas[name] = jsonSchema;
674
862
  }
675
863
  return {
@@ -679,14 +867,25 @@ function toOpenAPISchema(app, exclude, references, vendors) {
679
867
  paths
680
868
  };
681
869
  }
682
- var withHeaders = (schema, headers) => Object.assign(schema, {
683
- headers
684
- });
870
+ var withHeaders = (schema, headers) => {
871
+ const clone = Object.create(
872
+ Object.getPrototypeOf(schema),
873
+ Object.getOwnPropertyDescriptors(schema)
874
+ );
875
+ Object.defineProperty(clone, "headers", {
876
+ value: headers,
877
+ enumerable: true,
878
+ configurable: true,
879
+ writable: true
880
+ });
881
+ return clone;
882
+ };
685
883
  export {
686
884
  capitalize,
687
885
  enumToOpenApi,
688
886
  getLoosePath,
689
887
  getPossiblePath,
888
+ nullToOpenApi,
690
889
  toOpenAPISchema,
691
890
  unwrapSchema,
692
891
  withHeaders
package/dist/types.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import type { TSchema } from 'elysia';
2
- import type { OpenAPIV3 } from 'openapi-types';
2
+ import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
3
3
  import type { ApiReferenceConfiguration } from '@scalar/types';
4
4
  import type { SwaggerUIOptions } from './swagger/types';
5
5
  export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null;
6
+ export type OpenAPIVersion = `3.0.${number}` | `3.1.${number}`;
6
7
  type MaybeArray<T> = T | T[];
8
+ type OpenAPIDocumentation = Omit<Partial<OpenAPIV3.Document>, 'x-express-openapi-additional-middleware' | 'x-express-openapi-validation-strict'> | Omit<Partial<OpenAPIV3_1.Document>, 'x-express-openapi-additional-middleware' | 'x-express-openapi-validation-strict'>;
7
9
  export type MapJsonSchema = {
8
10
  [vendor: string]: Function;
9
11
  } & {
@@ -28,12 +30,18 @@ export interface ElysiaOpenAPIConfig<Enabled extends boolean = true, Path extend
28
30
  * @default true
29
31
  */
30
32
  enabled?: Enabled;
33
+ /**
34
+ * OpenAPI document version to emit
35
+ *
36
+ * @default '3.1.2'
37
+ */
38
+ openapiVersion?: OpenAPIVersion;
31
39
  /**
32
40
  * OpenAPI config
33
41
  *
34
- * @see https://spec.openapis.org/oas/v3.0.3.html
42
+ * @see https://spec.openapis.org/oas/latest.html
35
43
  */
36
- documentation?: Omit<Partial<OpenAPIV3.Document>, 'x-express-openapi-additional-middleware' | 'x-express-openapi-validation-strict'>;
44
+ documentation?: OpenAPIDocumentation;
37
45
  exclude?: {
38
46
  /**
39
47
  * Exclude methods from OpenAPI