@elysiajs/openapi 1.4.2 → 1.4.4

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/bun.lock CHANGED
@@ -8,7 +8,8 @@
8
8
  "@scalar/types": "^0.2.13",
9
9
  "@sinclair/typemap": "^0.10.1",
10
10
  "@types/bun": "1.2.20",
11
- "elysia": "1.4.0",
11
+ "effect": "^3.17.13",
12
+ "elysia": "1.4.6",
12
13
  "eslint": "9.6.0",
13
14
  "openapi-types": "^12.1.3",
14
15
  "tsup": "^8.5.0",
@@ -167,6 +168,8 @@
167
168
 
168
169
  "@sinclair/typemap": ["@sinclair/typemap@0.10.1", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.30", "valibot": "^1.0.0", "zod": "^3.24.1" } }, "sha512-UXR0fhu/n3c9B6lB+SLI5t1eVpt9i9CdDrp2TajRe3LbKiUhCTZN2kSfJhjPnpc3I59jMRIhgew7+0HlMi08mg=="],
169
170
 
171
+ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
172
+
170
173
  "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="],
171
174
 
172
175
  "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
@@ -239,7 +242,9 @@
239
242
 
240
243
  "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
241
244
 
242
- "elysia": ["elysia@1.4.0", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "openapi-types": ">= 12.0.0" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-AVEq8cWo7+fdkbUtqVXDZ7uhjjv8K6NE6bt9oSzdocaawsW5VhJP2ArPoRYkWdJPYmS4f2FI5xWF+Tvs98yXVw=="],
245
+ "effect": ["effect@3.17.13", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-JMz5oBxs/6mu4FP9Csjub4jYMUwMLrp+IzUmSDVIzn2NoeoyOXMl7x1lghfr3dLKWffWrdnv/d8nFFdgrHXPqw=="],
246
+
247
+ "elysia": ["elysia@1.4.6", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "openapi-types": ">= 12.0.0" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-u2CorXLPs5ZXyWP+tQR+bgka/lJA4vNpB8lDE2w/sTmdaIwoPQmHEL4J3ai6OAlluWR1kfG7T9gO3EYT9D8viQ=="],
243
248
 
244
249
  "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
245
250
 
@@ -265,6 +270,8 @@
265
270
 
266
271
  "exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="],
267
272
 
273
+ "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
274
+
268
275
  "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
269
276
 
270
277
  "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
@@ -405,6 +412,8 @@
405
412
 
406
413
  "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
407
414
 
415
+ "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
416
+
408
417
  "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
409
418
 
410
419
  "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
@@ -8315,6 +8315,35 @@ var import_child_process = require("child_process");
8315
8315
  var matchRoute = /: Elysia<(.*)>/gs;
8316
8316
  var matchStatus = /(\d{3}):/g;
8317
8317
  var wrapStatusInQuote = (value) => value.replace(matchStatus, '"$1":');
8318
+ function extractRootObjects(code) {
8319
+ const results = [];
8320
+ let i = 0;
8321
+ while (i < code.length) {
8322
+ const colonIdx = code.indexOf(":", i);
8323
+ if (colonIdx === -1) break;
8324
+ let keyEnd = colonIdx - 1;
8325
+ while (keyEnd >= 0 && /\s/.test(code[keyEnd])) keyEnd--;
8326
+ let keyStart = keyEnd;
8327
+ while (keyStart >= 0 && /\w/.test(code[keyStart])) keyStart--;
8328
+ const braceIdx = code.indexOf("{", colonIdx);
8329
+ if (braceIdx === -1) break;
8330
+ let depth = 0;
8331
+ let end = braceIdx;
8332
+ for (; end < code.length; end++) {
8333
+ if (code[end] === "{") depth++;
8334
+ else if (code[end] === "}") {
8335
+ depth--;
8336
+ if (depth === 0) {
8337
+ end++;
8338
+ break;
8339
+ }
8340
+ }
8341
+ }
8342
+ results.push(`{${code.slice(keyStart + 1, end)};}`);
8343
+ i = end;
8344
+ }
8345
+ return results;
8346
+ }
8318
8347
  var fromTypes = (targetFilePath, {
8319
8348
  tsconfigPath = "tsconfig.json",
8320
8349
  instanceName,
@@ -8322,7 +8351,8 @@ var fromTypes = (targetFilePath, {
8322
8351
  overrideOutputPath,
8323
8352
  debug = false,
8324
8353
  compilerOptions,
8325
- tmpRoot = (0, import_path2.join)((0, import_os.tmpdir)(), ".ElysiaAutoOpenAPI")
8354
+ tmpRoot = (0, import_path2.join)((0, import_os.tmpdir)(), ".ElysiaAutoOpenAPI"),
8355
+ silent = false
8326
8356
  } = {}) => () => {
8327
8357
  try {
8328
8358
  if (!targetFilePath.endsWith(".ts") && !targetFilePath.endsWith(".tsx"))
@@ -8369,7 +8399,7 @@ var fromTypes = (targetFilePath, {
8369
8399
  (0, import_child_process.spawnSync)(`tsc`, {
8370
8400
  shell: true,
8371
8401
  cwd: tmpRoot,
8372
- stdio: debug ? "inherit" : void 0
8402
+ stdio: silent ? void 0 : "inherit"
8373
8403
  });
8374
8404
  const fileName = targetFilePath.replace(/.tsx$/, ".ts").replace(/.ts$/, ".d.ts");
8375
8405
  targetFile = (overrideOutputPath ? typeof overrideOutputPath === "string" ? overrideOutputPath.startsWith("/") ? overrideOutputPath : (0, import_path2.join)(tmpRoot, "dist", overrideOutputPath) : overrideOutputPath(tmpRoot) : void 0) ?? (0, import_path2.join)(
@@ -8405,7 +8435,7 @@ var fromTypes = (targetFilePath, {
8405
8435
  console.warn(tempFiles);
8406
8436
  }
8407
8437
  } else {
8408
- console.log(
8438
+ console.warn(
8409
8439
  "reason: root folder doesn't exists",
8410
8440
  (0, import_path2.join)(tmpRoot, "dist")
8411
8441
  );
@@ -8414,7 +8444,7 @@ var fromTypes = (targetFilePath, {
8414
8444
  }
8415
8445
  }
8416
8446
  const declaration = (0, import_fs.readFileSync)(targetFile, "utf8");
8417
- if ((0, import_fs.existsSync)(tmpRoot))
8447
+ if (!debug && (0, import_fs.existsSync)(tmpRoot))
8418
8448
  (0, import_fs.rmSync)(tmpRoot, { recursive: true, force: true });
8419
8449
  let instance = declaration.match(
8420
8450
  instanceName ? new RegExp(`${instanceName}: Elysia<(.*)`, "gs") : matchRoute
@@ -8434,8 +8464,8 @@ var fromTypes = (targetFilePath, {
8434
8464
  instance.slice(3, instance.indexOf("}, {", 4))
8435
8465
  );
8436
8466
  const routes = {};
8437
- for (const route of routesString.slice(1).split("} & {")) {
8438
- let schema = TypeBox(`{${route}}`);
8467
+ for (const route of extractRootObjects(routesString)) {
8468
+ let schema = TypeBox(route);
8439
8469
  if (schema.type !== "object") continue;
8440
8470
  const paths = [];
8441
8471
  while (true) {
@@ -8446,6 +8476,7 @@ var fromTypes = (targetFilePath, {
8446
8476
  if (!schema?.properties) break;
8447
8477
  }
8448
8478
  const method = paths.pop();
8479
+ if (!method) continue;
8449
8480
  const path = "/" + paths.join("/");
8450
8481
  schema = schema.properties;
8451
8482
  if (schema?.response?.type === "object") {
@@ -1,11 +1,11 @@
1
1
  import { Elysia } from 'elysia';
2
- import type { ElysiaOpenAPIConfig, OpenAPIProvider } from './types';
2
+ import type { ElysiaOpenAPIConfig } from './types';
3
3
  /**
4
4
  * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate OpenAPI documentation page.
5
5
  *
6
6
  * @see https://github.com/elysiajs/elysia-swagger
7
7
  */
8
- export declare const openapi: <const Enabled extends boolean = true, const Path extends string = "/openapi", const Provider extends OpenAPIProvider = "scalar">({ enabled, path, provider, specPath, documentation, exclude, swagger, scalar, references, mapJsonSchema }?: ElysiaOpenAPIConfig<Enabled, Path, Provider>) => Elysia<"", {
8
+ export declare const openapi: <const Enabled extends boolean = true, const Path extends string = "/openapi">({ enabled, path, provider, specPath, documentation, exclude, swagger, scalar, references, mapJsonSchema }?: ElysiaOpenAPIConfig<Enabled, Path>) => Elysia<"", {
9
9
  decorator: {};
10
10
  store: {};
11
11
  derive: {};
package/dist/cjs/index.js CHANGED
@@ -317,12 +317,52 @@ var getPossiblePath = (path) => {
317
317
  }
318
318
  return paths;
319
319
  };
320
- var isValidSchema = (schema) => typeof schema === "object" && (Kind in schema && schema[Kind] !== "Unknown" || schema.type || schema.properties || schema.items);
320
+ var isValidSchema = (schema) => schema && typeof schema === "object" && (Kind in schema && schema[Kind] !== "Unknown" || schema.type || schema.properties || schema.items);
321
321
  var getLoosePath = (path) => {
322
322
  if (path.charCodeAt(path.length - 1) === 47)
323
323
  return path.slice(0, path.length - 1);
324
324
  return path + "/";
325
325
  };
326
+ var warnings = {
327
+ zod4: `import openapi from '@elysiajs/openapi'
328
+ import * as z from 'zod'
329
+
330
+ openapi({
331
+ mapJsonSchema: {
332
+ zod: z.toJSONSchema
333
+ }
334
+ })`,
335
+ zod3: `import openapi from '@elysiajs/openapi'
336
+ import { zodToJsonSchema } from 'zod-to-json-schema'
337
+
338
+ openapi({
339
+ mapJsonSchema: {
340
+ zod: zodToJsonSchema
341
+ }
342
+ })`,
343
+ valibot: `import openapi from '@elysiajs/openapi'
344
+ import { toJsonSchema } from '@valibot/to-json-schema'
345
+
346
+ openapi({
347
+ mapJsonSchema: {
348
+ valibot: toJsonSchema
349
+ }
350
+ })`,
351
+ effect: `import { JSONSchema } from 'effect'
352
+
353
+ openapi({
354
+ mapJsonSchema: {
355
+ effect: JSONSchema.make
356
+ }
357
+ })`
358
+ };
359
+ var warned = {};
360
+ var unwrapReference = (schema, definitions) => {
361
+ if (!schema?.$ref) return schema;
362
+ const name = schema.$ref.slice(schema.$ref.lastIndexOf("/") + 1);
363
+ if (schema.$ref && definitions[name]) schema = definitions[name];
364
+ return schema;
365
+ };
326
366
  var unwrapSchema = (schema, mapJsonSchema) => {
327
367
  if (!schema) return;
328
368
  if (typeof schema === "string") schema = toRef(schema);
@@ -331,20 +371,63 @@ var unwrapSchema = (schema, mapJsonSchema) => {
331
371
  const vendor = schema["~standard"].vendor;
332
372
  if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === "function")
333
373
  return mapJsonSchema[vendor](schema);
334
- if (vendor === "zod" || vendor === "sury")
335
- return schema.toJSONSchema?.();
374
+ switch (vendor) {
375
+ case "zod":
376
+ if (warned.zod4 || warned.zod3) break;
377
+ console.warn(
378
+ "[@elysiajs/openapi] Zod doesn't provide JSON Schema method on the schema"
379
+ );
380
+ if ("_zod" in schema) {
381
+ warned.zod4 = true;
382
+ console.warn(
383
+ "For Zod v4, please provide z.toJSONSchema as follows:\n"
384
+ );
385
+ console.warn(warnings.zod4);
386
+ } else {
387
+ warned.zod3 = true;
388
+ console.warn(
389
+ "For Zod v3, please install zod-to-json-schema package and use it like this:\n"
390
+ );
391
+ console.warn(warnings.zod3);
392
+ }
393
+ break;
394
+ case "valibot":
395
+ if (warned.valibot) break;
396
+ warned.valibot = true;
397
+ console.warn(
398
+ "[@elysiajs/openapi] Valibot require a separate package for JSON Schema conversion"
399
+ );
400
+ console.warn(
401
+ "Please install @valibot/to-json-schema package and use it like this:\n"
402
+ );
403
+ console.warn(warnings.valibot);
404
+ break;
405
+ case "effect":
406
+ if (warned.effect) break;
407
+ warned.effect = true;
408
+ console.warn(
409
+ "[@elysiajs/openapi] Effect Schema doesn't provide JSON Schema method on the schema"
410
+ );
411
+ console.warn(
412
+ "please provide JSONSchema from 'effect' package as follows:\n"
413
+ );
414
+ console.warn(warnings.effect);
415
+ break;
416
+ }
336
417
  if (vendor === "arktype")
337
418
  return schema?.toJsonSchema?.();
338
419
  return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.();
339
420
  };
340
421
  function toOpenAPISchema(app, exclude, references, vendors) {
341
- const {
342
- methods: excludeMethods = ["OPTIONS"],
422
+ let {
423
+ methods: excludeMethods = ["options"],
343
424
  staticFile: excludeStaticFile = true,
344
425
  tags: excludeTags
345
426
  } = exclude ?? {};
427
+ excludeMethods = excludeMethods.map((method) => method.toLowerCase());
346
428
  const excludePaths = Array.isArray(exclude?.paths) ? exclude.paths : typeof exclude?.paths !== "undefined" ? [exclude.paths] : [];
347
429
  const paths = /* @__PURE__ */ Object.create(null);
430
+ const definitions = app.getGlobalDefinitions?.().type;
348
431
  const routes = app.getGlobalRoutes();
349
432
  if (references) {
350
433
  if (!Array.isArray(references)) references = [references];
@@ -359,7 +442,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
359
442
  if (excludeStaticFile && route.path.includes(".") || excludePaths.includes(route.path) || excludeMethods.includes(method))
360
443
  continue;
361
444
  const hooks = route.hooks ?? {};
362
- if (references)
445
+ if (route.path === "/a") {
446
+ console.log("H");
447
+ }
448
+ if (references?.length)
363
449
  for (const reference of references) {
364
450
  if (!reference) continue;
365
451
  const refer = reference[route.path]?.[method] ?? reference[getLoosePath(route.path)]?.[method];
@@ -390,7 +476,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
390
476
  };
391
477
  const parameters = [];
392
478
  if (hooks.params) {
393
- const params = unwrapSchema(hooks.params, vendors);
479
+ const params = unwrapReference(
480
+ unwrapSchema(hooks.params, vendors),
481
+ definitions
482
+ );
394
483
  if (params && params.type === "object" && params.properties)
395
484
  for (const [paramName, paramSchema] of Object.entries(
396
485
  params.properties
@@ -404,7 +493,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
404
493
  });
405
494
  }
406
495
  if (hooks.query) {
407
- let query = unwrapSchema(hooks.query, vendors);
496
+ const query = unwrapReference(
497
+ unwrapSchema(hooks.query, vendors),
498
+ definitions
499
+ );
408
500
  if (query && query.type === "object" && query.properties) {
409
501
  const required = query.required || [];
410
502
  for (const [queryName, querySchema] of Object.entries(
@@ -419,7 +511,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
419
511
  }
420
512
  }
421
513
  if (hooks.headers) {
422
- const headers = unwrapSchema(hooks.query, vendors);
514
+ const headers = unwrapReference(
515
+ unwrapSchema(hooks.query, vendors),
516
+ definitions
517
+ );
423
518
  if (headers && headers.type === "object" && headers.properties) {
424
519
  const required = headers.required || [];
425
520
  for (const [headerName, headerSchema] of Object.entries(
@@ -434,7 +529,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
434
529
  }
435
530
  }
436
531
  if (hooks.cookie) {
437
- const cookie = unwrapSchema(hooks.cookie, vendors);
532
+ const cookie = unwrapReference(
533
+ unwrapSchema(hooks.cookie, vendors),
534
+ definitions
535
+ );
438
536
  if (cookie && cookie.type === "object" && cookie.properties) {
439
537
  const required = cookie.required || [];
440
538
  for (const [cookieName, cookieSchema] of Object.entries(
@@ -578,10 +676,9 @@ function toOpenAPISchema(app, exclude, references, vendors) {
578
676
  };
579
677
  }
580
678
  }
581
- const _schemas = app.getGlobalDefinitions?.().type;
582
679
  const schemas = /* @__PURE__ */ Object.create(null);
583
- if (_schemas)
584
- for (const [name, schema] of Object.entries(_schemas)) {
680
+ if (definitions)
681
+ for (const [name, schema] of Object.entries(definitions)) {
585
682
  const jsonSchema = unwrapSchema(schema, vendors);
586
683
  if (jsonSchema) schemas[name] = jsonSchema;
587
684
  }
@@ -684,6 +781,10 @@ var openapi = ({
684
781
  };
685
782
  },
686
783
  {
784
+ error({ error }) {
785
+ console.log("[@elysiajs/openapi] error at specPath");
786
+ console.warn(error);
787
+ },
687
788
  detail: {
688
789
  hide: true
689
790
  }
@@ -60,12 +60,52 @@ var getPossiblePath = (path) => {
60
60
  }
61
61
  return paths;
62
62
  };
63
- var isValidSchema = (schema) => typeof schema === "object" && (Kind in schema && schema[Kind] !== "Unknown" || schema.type || schema.properties || schema.items);
63
+ var isValidSchema = (schema) => schema && typeof schema === "object" && (Kind in schema && schema[Kind] !== "Unknown" || schema.type || schema.properties || schema.items);
64
64
  var getLoosePath = (path) => {
65
65
  if (path.charCodeAt(path.length - 1) === 47)
66
66
  return path.slice(0, path.length - 1);
67
67
  return path + "/";
68
68
  };
69
+ var warnings = {
70
+ zod4: `import openapi from '@elysiajs/openapi'
71
+ import * as z from 'zod'
72
+
73
+ openapi({
74
+ mapJsonSchema: {
75
+ zod: z.toJSONSchema
76
+ }
77
+ })`,
78
+ zod3: `import openapi from '@elysiajs/openapi'
79
+ import { zodToJsonSchema } from 'zod-to-json-schema'
80
+
81
+ openapi({
82
+ mapJsonSchema: {
83
+ zod: zodToJsonSchema
84
+ }
85
+ })`,
86
+ valibot: `import openapi from '@elysiajs/openapi'
87
+ import { toJsonSchema } from '@valibot/to-json-schema'
88
+
89
+ openapi({
90
+ mapJsonSchema: {
91
+ valibot: toJsonSchema
92
+ }
93
+ })`,
94
+ effect: `import { JSONSchema } from 'effect'
95
+
96
+ openapi({
97
+ mapJsonSchema: {
98
+ effect: JSONSchema.make
99
+ }
100
+ })`
101
+ };
102
+ var warned = {};
103
+ var unwrapReference = (schema, definitions) => {
104
+ if (!schema?.$ref) return schema;
105
+ const name = schema.$ref.slice(schema.$ref.lastIndexOf("/") + 1);
106
+ if (schema.$ref && definitions[name]) schema = definitions[name];
107
+ return schema;
108
+ };
69
109
  var unwrapSchema = (schema, mapJsonSchema) => {
70
110
  if (!schema) return;
71
111
  if (typeof schema === "string") schema = toRef(schema);
@@ -74,20 +114,63 @@ var unwrapSchema = (schema, mapJsonSchema) => {
74
114
  const vendor = schema["~standard"].vendor;
75
115
  if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === "function")
76
116
  return mapJsonSchema[vendor](schema);
77
- if (vendor === "zod" || vendor === "sury")
78
- return schema.toJSONSchema?.();
117
+ switch (vendor) {
118
+ case "zod":
119
+ if (warned.zod4 || warned.zod3) break;
120
+ console.warn(
121
+ "[@elysiajs/openapi] Zod doesn't provide JSON Schema method on the schema"
122
+ );
123
+ if ("_zod" in schema) {
124
+ warned.zod4 = true;
125
+ console.warn(
126
+ "For Zod v4, please provide z.toJSONSchema as follows:\n"
127
+ );
128
+ console.warn(warnings.zod4);
129
+ } else {
130
+ warned.zod3 = true;
131
+ console.warn(
132
+ "For Zod v3, please install zod-to-json-schema package and use it like this:\n"
133
+ );
134
+ console.warn(warnings.zod3);
135
+ }
136
+ break;
137
+ case "valibot":
138
+ if (warned.valibot) break;
139
+ warned.valibot = true;
140
+ console.warn(
141
+ "[@elysiajs/openapi] Valibot require a separate package for JSON Schema conversion"
142
+ );
143
+ console.warn(
144
+ "Please install @valibot/to-json-schema package and use it like this:\n"
145
+ );
146
+ console.warn(warnings.valibot);
147
+ break;
148
+ case "effect":
149
+ if (warned.effect) break;
150
+ warned.effect = true;
151
+ console.warn(
152
+ "[@elysiajs/openapi] Effect Schema doesn't provide JSON Schema method on the schema"
153
+ );
154
+ console.warn(
155
+ "please provide JSONSchema from 'effect' package as follows:\n"
156
+ );
157
+ console.warn(warnings.effect);
158
+ break;
159
+ }
79
160
  if (vendor === "arktype")
80
161
  return schema?.toJsonSchema?.();
81
162
  return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.();
82
163
  };
83
164
  function toOpenAPISchema(app, exclude, references, vendors) {
84
- const {
85
- methods: excludeMethods = ["OPTIONS"],
165
+ let {
166
+ methods: excludeMethods = ["options"],
86
167
  staticFile: excludeStaticFile = true,
87
168
  tags: excludeTags
88
169
  } = exclude ?? {};
170
+ excludeMethods = excludeMethods.map((method) => method.toLowerCase());
89
171
  const excludePaths = Array.isArray(exclude?.paths) ? exclude.paths : typeof exclude?.paths !== "undefined" ? [exclude.paths] : [];
90
172
  const paths = /* @__PURE__ */ Object.create(null);
173
+ const definitions = app.getGlobalDefinitions?.().type;
91
174
  const routes = app.getGlobalRoutes();
92
175
  if (references) {
93
176
  if (!Array.isArray(references)) references = [references];
@@ -102,7 +185,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
102
185
  if (excludeStaticFile && route.path.includes(".") || excludePaths.includes(route.path) || excludeMethods.includes(method))
103
186
  continue;
104
187
  const hooks = route.hooks ?? {};
105
- if (references)
188
+ if (route.path === "/a") {
189
+ console.log("H");
190
+ }
191
+ if (references?.length)
106
192
  for (const reference of references) {
107
193
  if (!reference) continue;
108
194
  const refer = reference[route.path]?.[method] ?? reference[getLoosePath(route.path)]?.[method];
@@ -133,7 +219,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
133
219
  };
134
220
  const parameters = [];
135
221
  if (hooks.params) {
136
- const params = unwrapSchema(hooks.params, vendors);
222
+ const params = unwrapReference(
223
+ unwrapSchema(hooks.params, vendors),
224
+ definitions
225
+ );
137
226
  if (params && params.type === "object" && params.properties)
138
227
  for (const [paramName, paramSchema] of Object.entries(
139
228
  params.properties
@@ -147,7 +236,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
147
236
  });
148
237
  }
149
238
  if (hooks.query) {
150
- let query = unwrapSchema(hooks.query, vendors);
239
+ const query = unwrapReference(
240
+ unwrapSchema(hooks.query, vendors),
241
+ definitions
242
+ );
151
243
  if (query && query.type === "object" && query.properties) {
152
244
  const required = query.required || [];
153
245
  for (const [queryName, querySchema] of Object.entries(
@@ -162,7 +254,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
162
254
  }
163
255
  }
164
256
  if (hooks.headers) {
165
- const headers = unwrapSchema(hooks.query, vendors);
257
+ const headers = unwrapReference(
258
+ unwrapSchema(hooks.query, vendors),
259
+ definitions
260
+ );
166
261
  if (headers && headers.type === "object" && headers.properties) {
167
262
  const required = headers.required || [];
168
263
  for (const [headerName, headerSchema] of Object.entries(
@@ -177,7 +272,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
177
272
  }
178
273
  }
179
274
  if (hooks.cookie) {
180
- const cookie = unwrapSchema(hooks.cookie, vendors);
275
+ const cookie = unwrapReference(
276
+ unwrapSchema(hooks.cookie, vendors),
277
+ definitions
278
+ );
181
279
  if (cookie && cookie.type === "object" && cookie.properties) {
182
280
  const required = cookie.required || [];
183
281
  for (const [cookieName, cookieSchema] of Object.entries(
@@ -321,10 +419,9 @@ function toOpenAPISchema(app, exclude, references, vendors) {
321
419
  };
322
420
  }
323
421
  }
324
- const _schemas = app.getGlobalDefinitions?.().type;
325
422
  const schemas = /* @__PURE__ */ Object.create(null);
326
- if (_schemas)
327
- for (const [name, schema] of Object.entries(_schemas)) {
423
+ if (definitions)
424
+ for (const [name, schema] of Object.entries(definitions)) {
328
425
  const jsonSchema = unwrapSchema(schema, vendors);
329
426
  if (jsonSchema) schemas[name] = jsonSchema;
330
427
  }
@@ -6,6 +6,8 @@ export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null;
6
6
  type MaybeArray<T> = T | T[];
7
7
  export type MapJsonSchema = {
8
8
  [vendor: string]: Function;
9
+ } & {
10
+ [vendor in 'zod' | 'effect' | 'valibot' | 'arktype' | 'typemap' | 'yup' | 'joi']?: Function;
9
11
  };
10
12
  export type AdditionalReference = {
11
13
  [path in string]: {
@@ -21,7 +23,7 @@ export type AdditionalReference = {
21
23
  };
22
24
  };
23
25
  export type AdditionalReferences = MaybeArray<AdditionalReference | undefined | (() => AdditionalReference | undefined)>;
24
- export interface ElysiaOpenAPIConfig<Enabled extends boolean = true, Path extends string = '/swagger', Provider extends OpenAPIProvider = 'scalar'> {
26
+ export interface ElysiaOpenAPIConfig<Enabled extends boolean = true, Path extends string = '/swagger'> {
25
27
  /**
26
28
  * @default true
27
29
  */
@@ -67,7 +69,7 @@ export interface ElysiaOpenAPIConfig<Enabled extends boolean = true, Path extend
67
69
  * @see https://github.com/scalar/scalar
68
70
  * @see https://github.com/swagger-api/swagger-ui
69
71
  */
70
- provider?: Provider;
72
+ provider?: OpenAPIProvider;
71
73
  /**
72
74
  * Additional reference for each endpoint
73
75
  */
@@ -81,7 +83,7 @@ export interface ElysiaOpenAPIConfig<Enabled extends boolean = true, Path extend
81
83
  * import { toJsonSchema } from '@valibot/to-json-schema'
82
84
  *
83
85
  * openapi({
84
- * vendors: {
86
+ * mapJsonSchema: {
85
87
  * valibot: toJsonSchema
86
88
  * }
87
89
  * })
@@ -46,6 +46,11 @@ interface OpenAPIGeneratorOptions {
46
46
  * ! be careful that the folder will be removed after the process ends
47
47
  */
48
48
  tmpRoot?: string;
49
+ /**
50
+ * disable log
51
+ * @default false
52
+ */
53
+ silent?: boolean;
49
54
  }
50
55
  /**
51
56
  * Auto generate OpenAPI schema from Elysia instance
@@ -60,5 +65,5 @@ export declare const fromTypes: (
60
65
  *
61
66
  * The path must export an Elysia instance
62
67
  */
63
- targetFilePath: string, { tsconfigPath, instanceName, projectRoot, overrideOutputPath, debug, compilerOptions, tmpRoot }?: OpenAPIGeneratorOptions) => () => AdditionalReference | undefined;
68
+ targetFilePath: string, { tsconfigPath, instanceName, projectRoot, overrideOutputPath, debug, compilerOptions, tmpRoot, silent }?: OpenAPIGeneratorOptions) => () => AdditionalReference | undefined;
64
69
  export {};
@@ -8304,6 +8304,35 @@ import { spawnSync } from "child_process";
8304
8304
  var matchRoute = /: Elysia<(.*)>/gs;
8305
8305
  var matchStatus = /(\d{3}):/g;
8306
8306
  var wrapStatusInQuote = (value) => value.replace(matchStatus, '"$1":');
8307
+ function extractRootObjects(code) {
8308
+ const results = [];
8309
+ let i = 0;
8310
+ while (i < code.length) {
8311
+ const colonIdx = code.indexOf(":", i);
8312
+ if (colonIdx === -1) break;
8313
+ let keyEnd = colonIdx - 1;
8314
+ while (keyEnd >= 0 && /\s/.test(code[keyEnd])) keyEnd--;
8315
+ let keyStart = keyEnd;
8316
+ while (keyStart >= 0 && /\w/.test(code[keyStart])) keyStart--;
8317
+ const braceIdx = code.indexOf("{", colonIdx);
8318
+ if (braceIdx === -1) break;
8319
+ let depth = 0;
8320
+ let end = braceIdx;
8321
+ for (; end < code.length; end++) {
8322
+ if (code[end] === "{") depth++;
8323
+ else if (code[end] === "}") {
8324
+ depth--;
8325
+ if (depth === 0) {
8326
+ end++;
8327
+ break;
8328
+ }
8329
+ }
8330
+ }
8331
+ results.push(`{${code.slice(keyStart + 1, end)};}`);
8332
+ i = end;
8333
+ }
8334
+ return results;
8335
+ }
8307
8336
  var fromTypes = (targetFilePath, {
8308
8337
  tsconfigPath = "tsconfig.json",
8309
8338
  instanceName,
@@ -8311,7 +8340,8 @@ var fromTypes = (targetFilePath, {
8311
8340
  overrideOutputPath,
8312
8341
  debug = false,
8313
8342
  compilerOptions,
8314
- tmpRoot = join(tmpdir(), ".ElysiaAutoOpenAPI")
8343
+ tmpRoot = join(tmpdir(), ".ElysiaAutoOpenAPI"),
8344
+ silent = false
8315
8345
  } = {}) => () => {
8316
8346
  try {
8317
8347
  if (!targetFilePath.endsWith(".ts") && !targetFilePath.endsWith(".tsx"))
@@ -8358,7 +8388,7 @@ var fromTypes = (targetFilePath, {
8358
8388
  spawnSync(`tsc`, {
8359
8389
  shell: true,
8360
8390
  cwd: tmpRoot,
8361
- stdio: debug ? "inherit" : void 0
8391
+ stdio: silent ? void 0 : "inherit"
8362
8392
  });
8363
8393
  const fileName = targetFilePath.replace(/.tsx$/, ".ts").replace(/.ts$/, ".d.ts");
8364
8394
  targetFile = (overrideOutputPath ? typeof overrideOutputPath === "string" ? overrideOutputPath.startsWith("/") ? overrideOutputPath : join(tmpRoot, "dist", overrideOutputPath) : overrideOutputPath(tmpRoot) : void 0) ?? join(
@@ -8394,7 +8424,7 @@ var fromTypes = (targetFilePath, {
8394
8424
  console.warn(tempFiles);
8395
8425
  }
8396
8426
  } else {
8397
- console.log(
8427
+ console.warn(
8398
8428
  "reason: root folder doesn't exists",
8399
8429
  join(tmpRoot, "dist")
8400
8430
  );
@@ -8403,7 +8433,7 @@ var fromTypes = (targetFilePath, {
8403
8433
  }
8404
8434
  }
8405
8435
  const declaration = readFileSync(targetFile, "utf8");
8406
- if (existsSync(tmpRoot))
8436
+ if (!debug && existsSync(tmpRoot))
8407
8437
  rmSync(tmpRoot, { recursive: true, force: true });
8408
8438
  let instance = declaration.match(
8409
8439
  instanceName ? new RegExp(`${instanceName}: Elysia<(.*)`, "gs") : matchRoute
@@ -8423,8 +8453,8 @@ var fromTypes = (targetFilePath, {
8423
8453
  instance.slice(3, instance.indexOf("}, {", 4))
8424
8454
  );
8425
8455
  const routes = {};
8426
- for (const route of routesString.slice(1).split("} & {")) {
8427
- let schema = TypeBox(`{${route}}`);
8456
+ for (const route of extractRootObjects(routesString)) {
8457
+ let schema = TypeBox(route);
8428
8458
  if (schema.type !== "object") continue;
8429
8459
  const paths = [];
8430
8460
  while (true) {
@@ -8435,6 +8465,7 @@ var fromTypes = (targetFilePath, {
8435
8465
  if (!schema?.properties) break;
8436
8466
  }
8437
8467
  const method = paths.pop();
8468
+ if (!method) continue;
8438
8469
  const path = "/" + paths.join("/");
8439
8470
  schema = schema.properties;
8440
8471
  if (schema?.response?.type === "object") {
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { Elysia } from 'elysia';
2
- import type { ElysiaOpenAPIConfig, OpenAPIProvider } from './types';
2
+ import type { ElysiaOpenAPIConfig } from './types';
3
3
  /**
4
4
  * Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate OpenAPI documentation page.
5
5
  *
6
6
  * @see https://github.com/elysiajs/elysia-swagger
7
7
  */
8
- export declare const openapi: <const Enabled extends boolean = true, const Path extends string = "/openapi", const Provider extends OpenAPIProvider = "scalar">({ enabled, path, provider, specPath, documentation, exclude, swagger, scalar, references, mapJsonSchema }?: ElysiaOpenAPIConfig<Enabled, Path, Provider>) => Elysia<"", {
8
+ export declare const openapi: <const Enabled extends boolean = true, const Path extends string = "/openapi">({ enabled, path, provider, specPath, documentation, exclude, swagger, scalar, references, mapJsonSchema }?: ElysiaOpenAPIConfig<Enabled, Path>) => Elysia<"", {
9
9
  decorator: {};
10
10
  store: {};
11
11
  derive: {};
package/dist/index.mjs CHANGED
@@ -290,12 +290,52 @@ var getPossiblePath = (path) => {
290
290
  }
291
291
  return paths;
292
292
  };
293
- var isValidSchema = (schema) => typeof schema === "object" && (Kind in schema && schema[Kind] !== "Unknown" || schema.type || schema.properties || schema.items);
293
+ var isValidSchema = (schema) => schema && typeof schema === "object" && (Kind in schema && schema[Kind] !== "Unknown" || schema.type || schema.properties || schema.items);
294
294
  var getLoosePath = (path) => {
295
295
  if (path.charCodeAt(path.length - 1) === 47)
296
296
  return path.slice(0, path.length - 1);
297
297
  return path + "/";
298
298
  };
299
+ var warnings = {
300
+ zod4: `import openapi from '@elysiajs/openapi'
301
+ import * as z from 'zod'
302
+
303
+ openapi({
304
+ mapJsonSchema: {
305
+ zod: z.toJSONSchema
306
+ }
307
+ })`,
308
+ zod3: `import openapi from '@elysiajs/openapi'
309
+ import { zodToJsonSchema } from 'zod-to-json-schema'
310
+
311
+ openapi({
312
+ mapJsonSchema: {
313
+ zod: zodToJsonSchema
314
+ }
315
+ })`,
316
+ valibot: `import openapi from '@elysiajs/openapi'
317
+ import { toJsonSchema } from '@valibot/to-json-schema'
318
+
319
+ openapi({
320
+ mapJsonSchema: {
321
+ valibot: toJsonSchema
322
+ }
323
+ })`,
324
+ effect: `import { JSONSchema } from 'effect'
325
+
326
+ openapi({
327
+ mapJsonSchema: {
328
+ effect: JSONSchema.make
329
+ }
330
+ })`
331
+ };
332
+ var warned = {};
333
+ var unwrapReference = (schema, definitions) => {
334
+ if (!schema?.$ref) return schema;
335
+ const name = schema.$ref.slice(schema.$ref.lastIndexOf("/") + 1);
336
+ if (schema.$ref && definitions[name]) schema = definitions[name];
337
+ return schema;
338
+ };
299
339
  var unwrapSchema = (schema, mapJsonSchema) => {
300
340
  if (!schema) return;
301
341
  if (typeof schema === "string") schema = toRef(schema);
@@ -304,20 +344,63 @@ var unwrapSchema = (schema, mapJsonSchema) => {
304
344
  const vendor = schema["~standard"].vendor;
305
345
  if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === "function")
306
346
  return mapJsonSchema[vendor](schema);
307
- if (vendor === "zod" || vendor === "sury")
308
- return schema.toJSONSchema?.();
347
+ switch (vendor) {
348
+ case "zod":
349
+ if (warned.zod4 || warned.zod3) break;
350
+ console.warn(
351
+ "[@elysiajs/openapi] Zod doesn't provide JSON Schema method on the schema"
352
+ );
353
+ if ("_zod" in schema) {
354
+ warned.zod4 = true;
355
+ console.warn(
356
+ "For Zod v4, please provide z.toJSONSchema as follows:\n"
357
+ );
358
+ console.warn(warnings.zod4);
359
+ } else {
360
+ warned.zod3 = true;
361
+ console.warn(
362
+ "For Zod v3, please install zod-to-json-schema package and use it like this:\n"
363
+ );
364
+ console.warn(warnings.zod3);
365
+ }
366
+ break;
367
+ case "valibot":
368
+ if (warned.valibot) break;
369
+ warned.valibot = true;
370
+ console.warn(
371
+ "[@elysiajs/openapi] Valibot require a separate package for JSON Schema conversion"
372
+ );
373
+ console.warn(
374
+ "Please install @valibot/to-json-schema package and use it like this:\n"
375
+ );
376
+ console.warn(warnings.valibot);
377
+ break;
378
+ case "effect":
379
+ if (warned.effect) break;
380
+ warned.effect = true;
381
+ console.warn(
382
+ "[@elysiajs/openapi] Effect Schema doesn't provide JSON Schema method on the schema"
383
+ );
384
+ console.warn(
385
+ "please provide JSONSchema from 'effect' package as follows:\n"
386
+ );
387
+ console.warn(warnings.effect);
388
+ break;
389
+ }
309
390
  if (vendor === "arktype")
310
391
  return schema?.toJsonSchema?.();
311
392
  return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.();
312
393
  };
313
394
  function toOpenAPISchema(app, exclude, references, vendors) {
314
- const {
315
- methods: excludeMethods = ["OPTIONS"],
395
+ let {
396
+ methods: excludeMethods = ["options"],
316
397
  staticFile: excludeStaticFile = true,
317
398
  tags: excludeTags
318
399
  } = exclude ?? {};
400
+ excludeMethods = excludeMethods.map((method) => method.toLowerCase());
319
401
  const excludePaths = Array.isArray(exclude?.paths) ? exclude.paths : typeof exclude?.paths !== "undefined" ? [exclude.paths] : [];
320
402
  const paths = /* @__PURE__ */ Object.create(null);
403
+ const definitions = app.getGlobalDefinitions?.().type;
321
404
  const routes = app.getGlobalRoutes();
322
405
  if (references) {
323
406
  if (!Array.isArray(references)) references = [references];
@@ -332,7 +415,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
332
415
  if (excludeStaticFile && route.path.includes(".") || excludePaths.includes(route.path) || excludeMethods.includes(method))
333
416
  continue;
334
417
  const hooks = route.hooks ?? {};
335
- if (references)
418
+ if (route.path === "/a") {
419
+ console.log("H");
420
+ }
421
+ if (references?.length)
336
422
  for (const reference of references) {
337
423
  if (!reference) continue;
338
424
  const refer = reference[route.path]?.[method] ?? reference[getLoosePath(route.path)]?.[method];
@@ -363,7 +449,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
363
449
  };
364
450
  const parameters = [];
365
451
  if (hooks.params) {
366
- const params = unwrapSchema(hooks.params, vendors);
452
+ const params = unwrapReference(
453
+ unwrapSchema(hooks.params, vendors),
454
+ definitions
455
+ );
367
456
  if (params && params.type === "object" && params.properties)
368
457
  for (const [paramName, paramSchema] of Object.entries(
369
458
  params.properties
@@ -377,7 +466,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
377
466
  });
378
467
  }
379
468
  if (hooks.query) {
380
- let query = unwrapSchema(hooks.query, vendors);
469
+ const query = unwrapReference(
470
+ unwrapSchema(hooks.query, vendors),
471
+ definitions
472
+ );
381
473
  if (query && query.type === "object" && query.properties) {
382
474
  const required = query.required || [];
383
475
  for (const [queryName, querySchema] of Object.entries(
@@ -392,7 +484,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
392
484
  }
393
485
  }
394
486
  if (hooks.headers) {
395
- const headers = unwrapSchema(hooks.query, vendors);
487
+ const headers = unwrapReference(
488
+ unwrapSchema(hooks.query, vendors),
489
+ definitions
490
+ );
396
491
  if (headers && headers.type === "object" && headers.properties) {
397
492
  const required = headers.required || [];
398
493
  for (const [headerName, headerSchema] of Object.entries(
@@ -407,7 +502,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
407
502
  }
408
503
  }
409
504
  if (hooks.cookie) {
410
- const cookie = unwrapSchema(hooks.cookie, vendors);
505
+ const cookie = unwrapReference(
506
+ unwrapSchema(hooks.cookie, vendors),
507
+ definitions
508
+ );
411
509
  if (cookie && cookie.type === "object" && cookie.properties) {
412
510
  const required = cookie.required || [];
413
511
  for (const [cookieName, cookieSchema] of Object.entries(
@@ -551,10 +649,9 @@ function toOpenAPISchema(app, exclude, references, vendors) {
551
649
  };
552
650
  }
553
651
  }
554
- const _schemas = app.getGlobalDefinitions?.().type;
555
652
  const schemas = /* @__PURE__ */ Object.create(null);
556
- if (_schemas)
557
- for (const [name, schema] of Object.entries(_schemas)) {
653
+ if (definitions)
654
+ for (const [name, schema] of Object.entries(definitions)) {
558
655
  const jsonSchema = unwrapSchema(schema, vendors);
559
656
  if (jsonSchema) schemas[name] = jsonSchema;
560
657
  }
@@ -657,6 +754,10 @@ var openapi = ({
657
754
  };
658
755
  },
659
756
  {
757
+ error({ error }) {
758
+ console.log("[@elysiajs/openapi] error at specPath");
759
+ console.warn(error);
760
+ },
660
761
  detail: {
661
762
  hide: true
662
763
  }
package/dist/openapi.mjs CHANGED
@@ -31,12 +31,52 @@ var getPossiblePath = (path) => {
31
31
  }
32
32
  return paths;
33
33
  };
34
- var isValidSchema = (schema) => typeof schema === "object" && (Kind in schema && schema[Kind] !== "Unknown" || schema.type || schema.properties || schema.items);
34
+ var isValidSchema = (schema) => schema && typeof schema === "object" && (Kind in schema && schema[Kind] !== "Unknown" || schema.type || schema.properties || schema.items);
35
35
  var getLoosePath = (path) => {
36
36
  if (path.charCodeAt(path.length - 1) === 47)
37
37
  return path.slice(0, path.length - 1);
38
38
  return path + "/";
39
39
  };
40
+ var warnings = {
41
+ zod4: `import openapi from '@elysiajs/openapi'
42
+ import * as z from 'zod'
43
+
44
+ openapi({
45
+ mapJsonSchema: {
46
+ zod: z.toJSONSchema
47
+ }
48
+ })`,
49
+ zod3: `import openapi from '@elysiajs/openapi'
50
+ import { zodToJsonSchema } from 'zod-to-json-schema'
51
+
52
+ openapi({
53
+ mapJsonSchema: {
54
+ zod: zodToJsonSchema
55
+ }
56
+ })`,
57
+ valibot: `import openapi from '@elysiajs/openapi'
58
+ import { toJsonSchema } from '@valibot/to-json-schema'
59
+
60
+ openapi({
61
+ mapJsonSchema: {
62
+ valibot: toJsonSchema
63
+ }
64
+ })`,
65
+ effect: `import { JSONSchema } from 'effect'
66
+
67
+ openapi({
68
+ mapJsonSchema: {
69
+ effect: JSONSchema.make
70
+ }
71
+ })`
72
+ };
73
+ var warned = {};
74
+ var unwrapReference = (schema, definitions) => {
75
+ if (!schema?.$ref) return schema;
76
+ const name = schema.$ref.slice(schema.$ref.lastIndexOf("/") + 1);
77
+ if (schema.$ref && definitions[name]) schema = definitions[name];
78
+ return schema;
79
+ };
40
80
  var unwrapSchema = (schema, mapJsonSchema) => {
41
81
  if (!schema) return;
42
82
  if (typeof schema === "string") schema = toRef(schema);
@@ -45,20 +85,63 @@ var unwrapSchema = (schema, mapJsonSchema) => {
45
85
  const vendor = schema["~standard"].vendor;
46
86
  if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === "function")
47
87
  return mapJsonSchema[vendor](schema);
48
- if (vendor === "zod" || vendor === "sury")
49
- return schema.toJSONSchema?.();
88
+ switch (vendor) {
89
+ case "zod":
90
+ if (warned.zod4 || warned.zod3) break;
91
+ console.warn(
92
+ "[@elysiajs/openapi] Zod doesn't provide JSON Schema method on the schema"
93
+ );
94
+ if ("_zod" in schema) {
95
+ warned.zod4 = true;
96
+ console.warn(
97
+ "For Zod v4, please provide z.toJSONSchema as follows:\n"
98
+ );
99
+ console.warn(warnings.zod4);
100
+ } else {
101
+ warned.zod3 = true;
102
+ console.warn(
103
+ "For Zod v3, please install zod-to-json-schema package and use it like this:\n"
104
+ );
105
+ console.warn(warnings.zod3);
106
+ }
107
+ break;
108
+ case "valibot":
109
+ if (warned.valibot) break;
110
+ warned.valibot = true;
111
+ console.warn(
112
+ "[@elysiajs/openapi] Valibot require a separate package for JSON Schema conversion"
113
+ );
114
+ console.warn(
115
+ "Please install @valibot/to-json-schema package and use it like this:\n"
116
+ );
117
+ console.warn(warnings.valibot);
118
+ break;
119
+ case "effect":
120
+ if (warned.effect) break;
121
+ warned.effect = true;
122
+ console.warn(
123
+ "[@elysiajs/openapi] Effect Schema doesn't provide JSON Schema method on the schema"
124
+ );
125
+ console.warn(
126
+ "please provide JSONSchema from 'effect' package as follows:\n"
127
+ );
128
+ console.warn(warnings.effect);
129
+ break;
130
+ }
50
131
  if (vendor === "arktype")
51
132
  return schema?.toJsonSchema?.();
52
133
  return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.();
53
134
  };
54
135
  function toOpenAPISchema(app, exclude, references, vendors) {
55
- const {
56
- methods: excludeMethods = ["OPTIONS"],
136
+ let {
137
+ methods: excludeMethods = ["options"],
57
138
  staticFile: excludeStaticFile = true,
58
139
  tags: excludeTags
59
140
  } = exclude ?? {};
141
+ excludeMethods = excludeMethods.map((method) => method.toLowerCase());
60
142
  const excludePaths = Array.isArray(exclude?.paths) ? exclude.paths : typeof exclude?.paths !== "undefined" ? [exclude.paths] : [];
61
143
  const paths = /* @__PURE__ */ Object.create(null);
144
+ const definitions = app.getGlobalDefinitions?.().type;
62
145
  const routes = app.getGlobalRoutes();
63
146
  if (references) {
64
147
  if (!Array.isArray(references)) references = [references];
@@ -73,7 +156,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
73
156
  if (excludeStaticFile && route.path.includes(".") || excludePaths.includes(route.path) || excludeMethods.includes(method))
74
157
  continue;
75
158
  const hooks = route.hooks ?? {};
76
- if (references)
159
+ if (route.path === "/a") {
160
+ console.log("H");
161
+ }
162
+ if (references?.length)
77
163
  for (const reference of references) {
78
164
  if (!reference) continue;
79
165
  const refer = reference[route.path]?.[method] ?? reference[getLoosePath(route.path)]?.[method];
@@ -104,7 +190,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
104
190
  };
105
191
  const parameters = [];
106
192
  if (hooks.params) {
107
- const params = unwrapSchema(hooks.params, vendors);
193
+ const params = unwrapReference(
194
+ unwrapSchema(hooks.params, vendors),
195
+ definitions
196
+ );
108
197
  if (params && params.type === "object" && params.properties)
109
198
  for (const [paramName, paramSchema] of Object.entries(
110
199
  params.properties
@@ -118,7 +207,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
118
207
  });
119
208
  }
120
209
  if (hooks.query) {
121
- let query = unwrapSchema(hooks.query, vendors);
210
+ const query = unwrapReference(
211
+ unwrapSchema(hooks.query, vendors),
212
+ definitions
213
+ );
122
214
  if (query && query.type === "object" && query.properties) {
123
215
  const required = query.required || [];
124
216
  for (const [queryName, querySchema] of Object.entries(
@@ -133,7 +225,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
133
225
  }
134
226
  }
135
227
  if (hooks.headers) {
136
- const headers = unwrapSchema(hooks.query, vendors);
228
+ const headers = unwrapReference(
229
+ unwrapSchema(hooks.query, vendors),
230
+ definitions
231
+ );
137
232
  if (headers && headers.type === "object" && headers.properties) {
138
233
  const required = headers.required || [];
139
234
  for (const [headerName, headerSchema] of Object.entries(
@@ -148,7 +243,10 @@ function toOpenAPISchema(app, exclude, references, vendors) {
148
243
  }
149
244
  }
150
245
  if (hooks.cookie) {
151
- const cookie = unwrapSchema(hooks.cookie, vendors);
246
+ const cookie = unwrapReference(
247
+ unwrapSchema(hooks.cookie, vendors),
248
+ definitions
249
+ );
152
250
  if (cookie && cookie.type === "object" && cookie.properties) {
153
251
  const required = cookie.required || [];
154
252
  for (const [cookieName, cookieSchema] of Object.entries(
@@ -292,10 +390,9 @@ function toOpenAPISchema(app, exclude, references, vendors) {
292
390
  };
293
391
  }
294
392
  }
295
- const _schemas = app.getGlobalDefinitions?.().type;
296
393
  const schemas = /* @__PURE__ */ Object.create(null);
297
- if (_schemas)
298
- for (const [name, schema] of Object.entries(_schemas)) {
394
+ if (definitions)
395
+ for (const [name, schema] of Object.entries(definitions)) {
299
396
  const jsonSchema = unwrapSchema(schema, vendors);
300
397
  if (jsonSchema) schemas[name] = jsonSchema;
301
398
  }
package/dist/types.d.ts CHANGED
@@ -6,6 +6,8 @@ export type OpenAPIProvider = 'scalar' | 'swagger-ui' | null;
6
6
  type MaybeArray<T> = T | T[];
7
7
  export type MapJsonSchema = {
8
8
  [vendor: string]: Function;
9
+ } & {
10
+ [vendor in 'zod' | 'effect' | 'valibot' | 'arktype' | 'typemap' | 'yup' | 'joi']?: Function;
9
11
  };
10
12
  export type AdditionalReference = {
11
13
  [path in string]: {
@@ -21,7 +23,7 @@ export type AdditionalReference = {
21
23
  };
22
24
  };
23
25
  export type AdditionalReferences = MaybeArray<AdditionalReference | undefined | (() => AdditionalReference | undefined)>;
24
- export interface ElysiaOpenAPIConfig<Enabled extends boolean = true, Path extends string = '/swagger', Provider extends OpenAPIProvider = 'scalar'> {
26
+ export interface ElysiaOpenAPIConfig<Enabled extends boolean = true, Path extends string = '/swagger'> {
25
27
  /**
26
28
  * @default true
27
29
  */
@@ -67,7 +69,7 @@ export interface ElysiaOpenAPIConfig<Enabled extends boolean = true, Path extend
67
69
  * @see https://github.com/scalar/scalar
68
70
  * @see https://github.com/swagger-api/swagger-ui
69
71
  */
70
- provider?: Provider;
72
+ provider?: OpenAPIProvider;
71
73
  /**
72
74
  * Additional reference for each endpoint
73
75
  */
@@ -81,7 +83,7 @@ export interface ElysiaOpenAPIConfig<Enabled extends boolean = true, Path extend
81
83
  * import { toJsonSchema } from '@valibot/to-json-schema'
82
84
  *
83
85
  * openapi({
84
- * vendors: {
86
+ * mapJsonSchema: {
85
87
  * valibot: toJsonSchema
86
88
  * }
87
89
  * })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elysiajs/openapi",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "Plugin for Elysia to auto-generate API documentation",
5
5
  "author": {
6
6
  "name": "saltyAom",
@@ -79,7 +79,8 @@
79
79
  "@scalar/types": "^0.2.13",
80
80
  "@sinclair/typemap": "^0.10.1",
81
81
  "@types/bun": "1.2.20",
82
- "elysia": "1.4.0",
82
+ "effect": "^3.17.13",
83
+ "elysia": "1.4.6",
83
84
  "eslint": "9.6.0",
84
85
  "openapi-types": "^12.1.3",
85
86
  "tsup": "^8.5.0",