@warlock.js/core 5.26.0 → 5.28.0

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.
Files changed (129) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/esm/cli/commands/openapi-generator.action.mjs +43 -0
  3. package/esm/cli/commands/openapi-generator.action.mjs.map +1 -0
  4. package/esm/cli/commands/openapi-generator.command.mjs +44 -0
  5. package/esm/cli/commands/openapi-generator.command.mjs.map +1 -0
  6. package/esm/cli/commands/postman-generator.action.mjs +47 -0
  7. package/esm/cli/commands/postman-generator.action.mjs.map +1 -0
  8. package/esm/cli/commands/postman-generator.command.mjs +43 -0
  9. package/esm/cli/commands/postman-generator.command.mjs.map +1 -0
  10. package/esm/cli/framework-cli-commands.mjs +4 -0
  11. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  12. package/esm/dev-server/build-development-openapi-document.mjs +36 -0
  13. package/esm/dev-server/build-development-openapi-document.mjs.map +1 -0
  14. package/esm/dev-server/collect-development-api-routes.mjs +27 -0
  15. package/esm/dev-server/collect-development-api-routes.mjs.map +1 -0
  16. package/esm/dev-server/dev-logger.mjs +9 -1
  17. package/esm/dev-server/dev-logger.mjs.map +1 -1
  18. package/esm/dev-server/development-server.mjs +6 -3
  19. package/esm/dev-server/development-server.mjs.map +1 -1
  20. package/esm/dev-server/files-orchestrator.mjs +17 -1
  21. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  22. package/esm/dev-server/model-resource-typings.mjs +246 -0
  23. package/esm/dev-server/model-resource-typings.mjs.map +1 -0
  24. package/esm/dev-server/type-generator.mjs +46 -2
  25. package/esm/dev-server/type-generator.mjs.map +1 -1
  26. package/esm/http/index.d.mts +2 -1
  27. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  28. package/esm/http/middleware/inject-request-context.mjs +29 -3
  29. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  30. package/esm/http/serialized.d.mts +114 -0
  31. package/esm/http/serialized.d.mts.map +1 -0
  32. package/esm/index.d.mts +11 -3
  33. package/esm/index.mjs +6 -1
  34. package/esm/openapi/build-openapi-document.d.mts +17 -0
  35. package/esm/openapi/build-openapi-document.d.mts.map +1 -0
  36. package/esm/openapi/build-openapi-document.mjs +124 -0
  37. package/esm/openapi/build-openapi-document.mjs.map +1 -0
  38. package/esm/openapi/cast-schema.mjs +89 -0
  39. package/esm/openapi/cast-schema.mjs.map +1 -0
  40. package/esm/openapi/collect-openapi-document.mjs +40 -0
  41. package/esm/openapi/collect-openapi-document.mjs.map +1 -0
  42. package/esm/openapi/development-openapi-provider.d.mts +28 -0
  43. package/esm/openapi/development-openapi-provider.d.mts.map +1 -0
  44. package/esm/openapi/development-openapi-provider.mjs +35 -0
  45. package/esm/openapi/development-openapi-provider.mjs.map +1 -0
  46. package/esm/openapi/error-responses.mjs +67 -0
  47. package/esm/openapi/error-responses.mjs.map +1 -0
  48. package/esm/openapi/index.d.mts +6 -0
  49. package/esm/openapi/index.mjs +6 -0
  50. package/esm/openapi/openapi-child-output.mjs +57 -0
  51. package/esm/openapi/openapi-child-output.mjs.map +1 -0
  52. package/esm/openapi/openapi-protocol.mjs +30 -0
  53. package/esm/openapi/openapi-protocol.mjs.map +1 -0
  54. package/esm/openapi/paths.mjs +63 -0
  55. package/esm/openapi/paths.mjs.map +1 -0
  56. package/esm/openapi/postman/build-request-body.mjs +86 -0
  57. package/esm/openapi/postman/build-request-body.mjs.map +1 -0
  58. package/esm/openapi/postman/build-request-url.mjs +85 -0
  59. package/esm/openapi/postman/build-request-url.mjs.map +1 -0
  60. package/esm/openapi/postman/build-saved-responses.mjs +60 -0
  61. package/esm/openapi/postman/build-saved-responses.mjs.map +1 -0
  62. package/esm/openapi/postman/example-from-schema.d.mts +17 -0
  63. package/esm/openapi/postman/example-from-schema.d.mts.map +1 -0
  64. package/esm/openapi/postman/example-from-schema.mjs +92 -0
  65. package/esm/openapi/postman/example-from-schema.mjs.map +1 -0
  66. package/esm/openapi/postman/http-status-text.mjs +31 -0
  67. package/esm/openapi/postman/http-status-text.mjs.map +1 -0
  68. package/esm/openapi/postman/index.d.mts +3 -0
  69. package/esm/openapi/postman/openapi-to-postman-collection.d.mts +22 -0
  70. package/esm/openapi/postman/openapi-to-postman-collection.d.mts.map +1 -0
  71. package/esm/openapi/postman/openapi-to-postman-collection.mjs +109 -0
  72. package/esm/openapi/postman/openapi-to-postman-collection.mjs.map +1 -0
  73. package/esm/openapi/postman/postman-types.d.mts +119 -0
  74. package/esm/openapi/postman/postman-types.d.mts.map +1 -0
  75. package/esm/openapi/postman/resolve-operation-auth.mjs +51 -0
  76. package/esm/openapi/postman/resolve-operation-auth.mjs.map +1 -0
  77. package/esm/openapi/postman/schema-refs.d.mts +7 -0
  78. package/esm/openapi/postman/schema-refs.d.mts.map +1 -0
  79. package/esm/openapi/postman/schema-refs.mjs +41 -0
  80. package/esm/openapi/postman/schema-refs.mjs.map +1 -0
  81. package/esm/openapi/postman/uuid-v5.mjs +29 -0
  82. package/esm/openapi/postman/uuid-v5.mjs.map +1 -0
  83. package/esm/openapi/request-schemas.mjs +138 -0
  84. package/esm/openapi/request-schemas.mjs.map +1 -0
  85. package/esm/openapi/response-schemas.mjs +85 -0
  86. package/esm/openapi/response-schemas.mjs.map +1 -0
  87. package/esm/openapi/schema-registry.mjs +143 -0
  88. package/esm/openapi/schema-registry.mjs.map +1 -0
  89. package/esm/openapi/security.mjs +98 -0
  90. package/esm/openapi/security.mjs.map +1 -0
  91. package/esm/openapi/types.d.mts +119 -0
  92. package/esm/openapi/types.d.mts.map +1 -0
  93. package/esm/production/registration-child-runner.mjs +139 -0
  94. package/esm/production/registration-child-runner.mjs.map +1 -0
  95. package/esm/production/route-registration-child.mjs +30 -2
  96. package/esm/production/route-registration-child.mjs.map +1 -1
  97. package/esm/production/route-registration-snapshot.mjs +22 -119
  98. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  99. package/esm/resource/define-resource.d.mts +44 -14
  100. package/esm/resource/define-resource.d.mts.map +1 -1
  101. package/esm/resource/define-resource.mjs +0 -40
  102. package/esm/resource/define-resource.mjs.map +1 -1
  103. package/esm/resource/index.d.mts +1 -1
  104. package/esm/resource/resource.d.mts +4 -4
  105. package/esm/resource/resource.d.mts.map +1 -1
  106. package/esm/resource/resource.mjs +2 -4
  107. package/esm/resource/resource.mjs.map +1 -1
  108. package/esm/resource/types.d.mts +89 -4
  109. package/esm/resource/types.d.mts.map +1 -1
  110. package/esm/router/index.d.mts +1 -1
  111. package/esm/router/named-api-routes-with-responses.mjs +59 -0
  112. package/esm/router/named-api-routes-with-responses.mjs.map +1 -0
  113. package/esm/router/response-schema-types.mjs +100 -0
  114. package/esm/router/response-schema-types.mjs.map +1 -0
  115. package/esm/router/router.d.mts +2 -2
  116. package/esm/router/router.d.mts.map +1 -1
  117. package/esm/router/router.mjs +38 -11
  118. package/esm/router/router.mjs.map +1 -1
  119. package/esm/router/types.d.mts +16 -1
  120. package/esm/router/types.d.mts.map +1 -1
  121. package/llms-full.txt +293 -5
  122. package/llms.txt +3 -2
  123. package/package.json +13 -13
  124. package/skills/README.md +3 -2
  125. package/skills/create-controller/SKILL.md +39 -2
  126. package/skills/define-resource/SKILL.md +100 -1
  127. package/skills/generate-openapi/SKILL.md +148 -0
  128. package/skills/index.md +3 -3
  129. package/skills/validate-input/SKILL.md +2 -2
@@ -0,0 +1,109 @@
1
+ import { buildRequestBody } from "./build-request-body.mjs";
2
+ import { buildHeaderParameters, buildRequestUrl } from "./build-request-url.mjs";
3
+ import { buildSavedResponses } from "./build-saved-responses.mjs";
4
+ import { collectionBearerAuth, hasBearerScheme, resolveOperationAuth } from "./resolve-operation-auth.mjs";
5
+ import { uuidV5 } from "./uuid-v5.mjs";
6
+
7
+ //#region ../core/src/openapi/postman/openapi-to-postman-collection.ts
8
+ /** `info.schema` of every collection this converter produces. */
9
+ const POSTMAN_COLLECTION_SCHEMA = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json";
10
+ /** `{{baseUrl}}` when neither the options nor the document name a server. */
11
+ const DEFAULT_POSTMAN_BASE_URL = "http://localhost:3000";
12
+ const OPERATION_METHODS = [
13
+ "get",
14
+ "put",
15
+ "post",
16
+ "delete",
17
+ "options",
18
+ "head",
19
+ "patch",
20
+ "trace"
21
+ ];
22
+ function buildRequestItem(path, method, operation, document) {
23
+ const components = document.components?.schemas;
24
+ const parameters = operation.parameters ?? [];
25
+ const body = buildRequestBody(operation.requestBody, components);
26
+ const { auth, note } = resolveOperationAuth(operation, document.components);
27
+ const description = [operation.description, note].filter((line) => Boolean(line)).join("\n\n");
28
+ const header = [...body?.contentType ? [{
29
+ key: "Content-Type",
30
+ value: body.contentType,
31
+ type: "text"
32
+ }] : [], ...buildHeaderParameters(parameters, components)];
33
+ const originalRequest = {
34
+ method: method.toUpperCase(),
35
+ header,
36
+ ...body ? { body: body.body } : {},
37
+ url: buildRequestUrl(path, parameters, components)
38
+ };
39
+ return {
40
+ name: operation.summary ?? operation.operationId,
41
+ request: {
42
+ ...originalRequest,
43
+ ...auth ? { auth } : {},
44
+ ...description ? { description } : {}
45
+ },
46
+ response: buildSavedResponses(operation.responses, originalRequest, components)
47
+ };
48
+ }
49
+ function normalizeBaseUrl(baseUrl) {
50
+ return baseUrl.replace(/\/+$/, "");
51
+ }
52
+ /**
53
+ * Convert an OpenAPI document into a Postman Collection v2.1.0 object.
54
+ *
55
+ * Pure and deterministic (the collection id is a UUIDv5 of the title), derived only from the
56
+ * document so the collection can never disagree with the OpenAPI output. Each tag becomes a
57
+ * folder in order of first appearance and untagged operations stay at the root; each
58
+ * operation becomes a request with example bodies generated from its schemas and a saved
59
+ * response per declared status. `{{baseUrl}}` and `{{token}}` are collection variables; a
60
+ * bearer scheme becomes collection-level bearer auth and public operations opt out of it.
61
+ */
62
+ function openApiToPostmanCollection(document, options = {}) {
63
+ const name = options.title ?? document.info.title;
64
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? document.servers?.[0]?.url ?? "http://localhost:3000");
65
+ const root = [];
66
+ const folders = /* @__PURE__ */ new Map();
67
+ for (const [path, pathItem] of Object.entries(document.paths)) for (const [method, operation] of Object.entries(pathItem)) {
68
+ if (!OPERATION_METHODS.includes(method)) continue;
69
+ const item = buildRequestItem(path, method, operation, document);
70
+ const tag = operation.tags?.[0];
71
+ if (tag === void 0) {
72
+ root.push(item);
73
+ continue;
74
+ }
75
+ let folder = folders.get(tag);
76
+ if (!folder) {
77
+ folder = {
78
+ name: tag,
79
+ item: []
80
+ };
81
+ folders.set(tag, folder);
82
+ root.push(folder);
83
+ }
84
+ folder.item.push(item);
85
+ }
86
+ return {
87
+ info: {
88
+ _postman_id: uuidV5(name),
89
+ name,
90
+ ...document.info.description ? { description: document.info.description } : {},
91
+ schema: POSTMAN_COLLECTION_SCHEMA
92
+ },
93
+ item: root,
94
+ ...hasBearerScheme(document.components) ? { auth: collectionBearerAuth() } : {},
95
+ variable: [{
96
+ key: "baseUrl",
97
+ value: baseUrl,
98
+ type: "string"
99
+ }, {
100
+ key: "token",
101
+ value: "",
102
+ type: "string"
103
+ }]
104
+ };
105
+ }
106
+
107
+ //#endregion
108
+ export { DEFAULT_POSTMAN_BASE_URL, POSTMAN_COLLECTION_SCHEMA, openApiToPostmanCollection };
109
+ //# sourceMappingURL=openapi-to-postman-collection.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-to-postman-collection.mjs","names":[],"sources":["../../../../../../../../core/src/openapi/postman/openapi-to-postman-collection.ts"],"sourcesContent":["import type { OpenApiDocument, OpenApiOperation } from \"../types\";\nimport { buildRequestBody } from \"./build-request-body\";\nimport { buildHeaderParameters, buildRequestUrl } from \"./build-request-url\";\nimport { buildSavedResponses } from \"./build-saved-responses\";\nimport type {\n PostmanCollection,\n PostmanCollectionOptions,\n PostmanFolder,\n PostmanHeader,\n PostmanItem,\n PostmanRequestItem,\n} from \"./postman-types\";\nimport { collectionBearerAuth, hasBearerScheme, resolveOperationAuth } from \"./resolve-operation-auth\";\nimport { uuidV5 } from \"./uuid-v5\";\n\n/** `info.schema` of every collection this converter produces. */\nexport const POSTMAN_COLLECTION_SCHEMA = \"https://schema.getpostman.com/json/collection/v2.1.0/collection.json\";\n\n/** `{{baseUrl}}` when neither the options nor the document name a server. */\nexport const DEFAULT_POSTMAN_BASE_URL = \"http://localhost:3000\";\n\nconst OPERATION_METHODS = [\"get\", \"put\", \"post\", \"delete\", \"options\", \"head\", \"patch\", \"trace\"];\n\nfunction buildRequestItem(\n path: string,\n method: string,\n operation: OpenApiOperation,\n document: OpenApiDocument,\n): PostmanRequestItem {\n const components = document.components?.schemas;\n const parameters = operation.parameters ?? [];\n const body = buildRequestBody(operation.requestBody, components);\n const { auth, note } = resolveOperationAuth(operation, document.components);\n const description = [operation.description, note].filter((line): line is string => Boolean(line)).join(\"\\n\\n\");\n\n const header: PostmanHeader[] = [\n ...(body?.contentType ? [{ key: \"Content-Type\", value: body.contentType, type: \"text\" as const }] : []),\n ...buildHeaderParameters(parameters, components),\n ];\n\n const originalRequest = {\n method: method.toUpperCase(),\n header,\n ...(body ? { body: body.body } : {}),\n url: buildRequestUrl(path, parameters, components),\n };\n\n return {\n name: operation.summary ?? operation.operationId,\n request: {\n ...originalRequest,\n ...(auth ? { auth } : {}),\n ...(description ? { description } : {}),\n },\n response: buildSavedResponses(operation.responses, originalRequest, components),\n };\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\");\n}\n\n/**\n * Convert an OpenAPI document into a Postman Collection v2.1.0 object.\n *\n * Pure and deterministic (the collection id is a UUIDv5 of the title), derived only from the\n * document so the collection can never disagree with the OpenAPI output. Each tag becomes a\n * folder in order of first appearance and untagged operations stay at the root; each\n * operation becomes a request with example bodies generated from its schemas and a saved\n * response per declared status. `{{baseUrl}}` and `{{token}}` are collection variables; a\n * bearer scheme becomes collection-level bearer auth and public operations opt out of it.\n */\nexport function openApiToPostmanCollection(\n document: OpenApiDocument,\n options: PostmanCollectionOptions = {},\n): PostmanCollection {\n const name = options.title ?? document.info.title;\n const baseUrl = normalizeBaseUrl(options.baseUrl ?? document.servers?.[0]?.url ?? DEFAULT_POSTMAN_BASE_URL);\n const root: PostmanItem[] = [];\n const folders = new Map<string, PostmanFolder>();\n\n for (const [path, pathItem] of Object.entries(document.paths)) {\n for (const [method, operation] of Object.entries(pathItem)) {\n if (!OPERATION_METHODS.includes(method)) {\n continue;\n }\n\n const item = buildRequestItem(path, method, operation, document);\n const tag = operation.tags?.[0];\n\n if (tag === undefined) {\n root.push(item);\n\n continue;\n }\n\n let folder = folders.get(tag);\n\n if (!folder) {\n folder = { name: tag, item: [] };\n folders.set(tag, folder);\n root.push(folder);\n }\n\n folder.item.push(item);\n }\n }\n\n return {\n info: {\n _postman_id: uuidV5(name),\n name,\n ...(document.info.description ? { description: document.info.description } : {}),\n schema: POSTMAN_COLLECTION_SCHEMA,\n },\n item: root,\n ...(hasBearerScheme(document.components) ? { auth: collectionBearerAuth() } : {}),\n variable: [\n { key: \"baseUrl\", value: baseUrl, type: \"string\" },\n { key: \"token\", value: \"\", type: \"string\" },\n ],\n };\n}\n"],"mappings":";;;;;;;;AAgBA,MAAa,4BAA4B;;AAGzC,MAAa,2BAA2B;AAExC,MAAM,oBAAoB;CAAC;CAAO;CAAO;CAAQ;CAAU;CAAW;CAAQ;CAAS;AAAO;AAE9F,SAAS,iBACP,MACA,QACA,WACA,UACoB;CACpB,MAAM,aAAa,SAAS,YAAY;CACxC,MAAM,aAAa,UAAU,cAAc,CAAC;CAC5C,MAAM,OAAO,iBAAiB,UAAU,aAAa,UAAU;CAC/D,MAAM,EAAE,MAAM,SAAS,qBAAqB,WAAW,SAAS,UAAU;CAC1E,MAAM,cAAc,CAAC,UAAU,aAAa,IAAI,CAAC,CAAC,QAAQ,SAAyB,QAAQ,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM;CAE7G,MAAM,SAA0B,CAC9B,GAAI,MAAM,cAAc,CAAC;EAAE,KAAK;EAAgB,OAAO,KAAK;EAAa,MAAM;CAAgB,CAAC,IAAI,CAAC,GACrG,GAAG,sBAAsB,YAAY,UAAU,CACjD;CAEA,MAAM,kBAAkB;EACtB,QAAQ,OAAO,YAAY;EAC3B;EACA,GAAI,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;EAClC,KAAK,gBAAgB,MAAM,YAAY,UAAU;CACnD;CAEA,OAAO;EACL,MAAM,UAAU,WAAW,UAAU;EACrC,SAAS;GACP,GAAG;GACH,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;GACvB,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;EACvC;EACA,UAAU,oBAAoB,UAAU,WAAW,iBAAiB,UAAU;CAChF;AACF;AAEA,SAAS,iBAAiB,SAAyB;CACjD,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AACnC;;;;;;;;;;;AAYA,SAAgB,2BACd,UACA,UAAoC,CAAC,GAClB;CACnB,MAAM,OAAO,QAAQ,SAAS,SAAS,KAAK;CAC5C,MAAM,UAAU,iBAAiB,QAAQ,WAAW,SAAS,UAAU,EAAE,EAAE,8BAA+B;CAC1G,MAAM,OAAsB,CAAC;CAC7B,MAAM,0BAAU,IAAI,IAA2B;CAE/C,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,SAAS,KAAK,GAC1D,KAAK,MAAM,CAAC,QAAQ,cAAc,OAAO,QAAQ,QAAQ,GAAG;EAC1D,IAAI,CAAC,kBAAkB,SAAS,MAAM,GACpC;EAGF,MAAM,OAAO,iBAAiB,MAAM,QAAQ,WAAW,QAAQ;EAC/D,MAAM,MAAM,UAAU,OAAO;EAE7B,IAAI,QAAQ,QAAW;GACrB,KAAK,KAAK,IAAI;GAEd;EACF;EAEA,IAAI,SAAS,QAAQ,IAAI,GAAG;EAE5B,IAAI,CAAC,QAAQ;GACX,SAAS;IAAE,MAAM;IAAK,MAAM,CAAC;GAAE;GAC/B,QAAQ,IAAI,KAAK,MAAM;GACvB,KAAK,KAAK,MAAM;EAClB;EAEA,OAAO,KAAK,KAAK,IAAI;CACvB;CAGF,OAAO;EACL,MAAM;GACJ,aAAa,OAAO,IAAI;GACxB;GACA,GAAI,SAAS,KAAK,cAAc,EAAE,aAAa,SAAS,KAAK,YAAY,IAAI,CAAC;GAC9E,QAAQ;EACV;EACA,MAAM;EACN,GAAI,gBAAgB,SAAS,UAAU,IAAI,EAAE,MAAM,qBAAqB,EAAE,IAAI,CAAC;EAC/E,UAAU,CACR;GAAE,KAAK;GAAW,OAAO;GAAS,MAAM;EAAS,GACjD;GAAE,KAAK;GAAS,OAAO;GAAI,MAAM;EAAS,CAC5C;CACF;AACF"}
@@ -0,0 +1,119 @@
1
+ //#region ../core/src/openapi/postman/postman-types.d.ts
2
+ /**
3
+ * The slice of the Postman Collection Format v2.1.0 the converter emits.
4
+ * Schema: https://schema.getpostman.com/json/collection/v2.1.0/collection.json
5
+ */
6
+ type PostmanVariable = {
7
+ key: string;
8
+ value: string;
9
+ type?: "string";
10
+ description?: string;
11
+ };
12
+ type PostmanAuthEntry = {
13
+ key: string;
14
+ value: string;
15
+ type: "string";
16
+ };
17
+ type PostmanAuth = {
18
+ type: "noauth";
19
+ } | {
20
+ type: "bearer";
21
+ bearer: PostmanAuthEntry[];
22
+ };
23
+ type PostmanHeader = {
24
+ key: string;
25
+ value: string;
26
+ type?: "text";
27
+ description?: string;
28
+ disabled?: boolean;
29
+ };
30
+ type PostmanQueryParam = {
31
+ key: string;
32
+ value: string;
33
+ description?: string;
34
+ disabled?: boolean;
35
+ };
36
+ type PostmanUrl = {
37
+ raw: string;
38
+ host: string[];
39
+ path: string[];
40
+ variable?: PostmanVariable[];
41
+ query?: PostmanQueryParam[];
42
+ };
43
+ type PostmanFormParam = {
44
+ key: string;
45
+ value: string;
46
+ type: "text";
47
+ description?: string;
48
+ } | {
49
+ key: string;
50
+ type: "file";
51
+ src: string[];
52
+ description?: string;
53
+ };
54
+ type PostmanBody = {
55
+ mode: "raw";
56
+ raw: string;
57
+ options: {
58
+ raw: {
59
+ language: "json" | "text";
60
+ };
61
+ };
62
+ } | {
63
+ mode: "formdata";
64
+ formdata: PostmanFormParam[];
65
+ } | {
66
+ mode: "urlencoded";
67
+ urlencoded: {
68
+ key: string;
69
+ value: string;
70
+ type: "text";
71
+ }[];
72
+ };
73
+ type PostmanOriginalRequest = {
74
+ method: string;
75
+ header: PostmanHeader[];
76
+ body?: PostmanBody;
77
+ url: PostmanUrl;
78
+ };
79
+ type PostmanRequest = PostmanOriginalRequest & {
80
+ auth?: PostmanAuth;
81
+ description?: string;
82
+ };
83
+ type PostmanResponse = {
84
+ name: string;
85
+ originalRequest: PostmanOriginalRequest;
86
+ status: string;
87
+ code: number;
88
+ _postman_previewlanguage: "json" | "html" | "text";
89
+ header: PostmanHeader[];
90
+ body: string;
91
+ };
92
+ type PostmanRequestItem = {
93
+ name: string;
94
+ request: PostmanRequest;
95
+ response: PostmanResponse[];
96
+ };
97
+ type PostmanFolder = {
98
+ name: string;
99
+ item: PostmanItem[];
100
+ };
101
+ type PostmanItem = PostmanFolder | PostmanRequestItem;
102
+ type PostmanCollection = {
103
+ info: {
104
+ _postman_id: string;
105
+ name: string;
106
+ description?: string;
107
+ schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json";
108
+ };
109
+ item: PostmanItem[];
110
+ auth?: PostmanAuth;
111
+ variable: PostmanVariable[];
112
+ };
113
+ type PostmanCollectionOptions = {
114
+ /** Collection name. Defaults to `info.title` of the document. */title?: string; /** Value of the `{{baseUrl}}` variable. Defaults to `servers[0].url`, else `http://localhost:3000`. */
115
+ baseUrl?: string;
116
+ };
117
+ //#endregion
118
+ export { PostmanAuth, PostmanAuthEntry, PostmanBody, PostmanCollection, PostmanCollectionOptions, PostmanFolder, PostmanFormParam, PostmanHeader, PostmanItem, PostmanOriginalRequest, PostmanQueryParam, PostmanRequest, PostmanRequestItem, PostmanResponse, PostmanUrl, PostmanVariable };
119
+ //# sourceMappingURL=postman-types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postman-types.d.mts","names":[],"sources":["../../../../../../../../core/src/openapi/postman/postman-types.ts"],"mappings":";;AAKA;;;KAAY,eAAA;EACV,GAAA;EACA,KAAA;EACA,IAAA;EACA,WAAA;AAAA;AAAA,KAGU,gBAAA;EAAqB,GAAA;EAAa,KAAA;EAAe,IAAA;AAAA;AAAA,KAEjD,WAAA;EAAgB,IAAA;AAAA;EAAqB,IAAA;EAAgB,MAAA,EAAQ,gBAAgB;AAAA;AAAA,KAE7E,aAAA;EACV,GAAA;EACA,KAAA;EACA,IAAA;EACA,WAAA;EACA,QAAA;AAAA;AAAA,KAGU,iBAAA;EACV,GAAA;EACA,KAAA;EACA,WAAA;EACA,QAAA;AAAA;AAAA,KAGU,UAAA;EACV,GAAA;EACA,IAAA;EACA,IAAA;EACA,QAAA,GAAW,eAAA;EACX,KAAA,GAAQ,iBAAiB;AAAA;AAAA,KAGf,gBAAA;EACN,GAAA;EAAa,KAAA;EAAe,IAAA;EAAc,WAAA;AAAA;EAC1C,GAAA;EAAa,IAAA;EAAc,GAAA;EAAe,WAAA;AAAA;AAAA,KAEpC,WAAA;EACN,IAAA;EAAa,GAAA;EAAa,OAAA;IAAW,GAAA;MAAO,QAAA;IAAA;EAAA;AAAA;EAC5C,IAAA;EAAkB,QAAA,EAAU,gBAAgB;AAAA;EAC5C,IAAA;EAAoB,UAAA;IAAc,GAAA;IAAa,KAAA;IAAe,IAAA;EAAA;AAAA;AAAA,KAExD,sBAAA;EACV,MAAA;EACA,MAAA,EAAQ,aAAA;EACR,IAAA,GAAO,WAAA;EACP,GAAA,EAAK,UAAA;AAAA;AAAA,KAGK,cAAA,GAAiB,sBAAA;EAC3B,IAAA,GAAO,WAAW;EAClB,WAAA;AAAA;AAAA,KAGU,eAAA;EACV,IAAA;EACA,eAAA,EAAiB,sBAAA;EACjB,MAAA;EACA,IAAA;EACA,wBAAA;EACA,MAAA,EAAQ,aAAa;EACrB,IAAA;AAAA;AAAA,KAGU,kBAAA;EACV,IAAA;EACA,OAAA,EAAS,cAAA;EACT,QAAA,EAAU,eAAe;AAAA;AAAA,KAGf,aAAA;EACV,IAAA;EACA,IAAA,EAAM,WAAW;AAAA;AAAA,KAGP,WAAA,GAAc,aAAA,GAAgB,kBAAkB;AAAA,KAEhD,iBAAA;EACV,IAAA;IACE,WAAA;IACA,IAAA;IACA,WAAA;IACA,MAAA;EAAA;EAEF,IAAA,EAAM,WAAA;EACN,IAAA,GAAO,WAAA;EACP,QAAA,EAAU,eAAA;AAAA;AAAA,KAGA,wBAAA;EA3CK,iEA6Cf,KAAA,WA1CU;EA4CV,OAAO;AAAA"}
@@ -0,0 +1,51 @@
1
+ //#region ../core/src/openapi/postman/resolve-operation-auth.ts
2
+ /** The collection-level auth: a bearer token read from the `{{token}}` variable. */
3
+ function collectionBearerAuth() {
4
+ return {
5
+ type: "bearer",
6
+ bearer: [{
7
+ key: "token",
8
+ value: "{{token}}",
9
+ type: "string"
10
+ }]
11
+ };
12
+ }
13
+ function isBearer(scheme) {
14
+ return scheme?.type === "http" && scheme.scheme.toLowerCase() === "bearer";
15
+ }
16
+ /**
17
+ * Whether the document declares an HTTP bearer scheme, which is what the collection-level
18
+ * auth stands for.
19
+ */
20
+ function hasBearerScheme(components) {
21
+ return Object.values(components?.securitySchemes ?? {}).some(isBearer);
22
+ }
23
+ /**
24
+ * How one operation relates to the collection-level bearer auth.
25
+ *
26
+ * - No `security` (a public route): `noauth`, so the token is not sent.
27
+ * - A bearer requirement: inherits the collection auth.
28
+ * - Cookie-only: `noauth` plus a note, because Postman keeps cookies per domain instead of
29
+ * in collection auth.
30
+ */
31
+ function resolveOperationAuth(operation, components) {
32
+ const bearerDocument = hasBearerScheme(components);
33
+ const schemes = components?.securitySchemes ?? {};
34
+ const names = (operation.security ?? []).flatMap((requirement) => Object.keys(requirement));
35
+ if (names.some((name) => isBearer(schemes[name]))) return {};
36
+ const cookies = names.flatMap((name) => {
37
+ const scheme = schemes[name];
38
+ return scheme?.type === "apiKey" && scheme.in === "cookie" ? [scheme.name] : [];
39
+ });
40
+ const auth = bearerDocument ? { auth: { type: "noauth" } } : {};
41
+ if (cookies.length === 0) return auth;
42
+ const cookieList = cookies.map((cookie) => `"${cookie}"`).join(", ");
43
+ return {
44
+ ...auth,
45
+ note: `Authenticates with the ${cookieList} cookie. Postman keeps cookies per domain, so sign in against {{baseUrl}} first or add the cookie in the Cookies manager.`
46
+ };
47
+ }
48
+
49
+ //#endregion
50
+ export { collectionBearerAuth, hasBearerScheme, resolveOperationAuth };
51
+ //# sourceMappingURL=resolve-operation-auth.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-operation-auth.mjs","names":[],"sources":["../../../../../../../../core/src/openapi/postman/resolve-operation-auth.ts"],"sourcesContent":["import type { OpenApiComponents, OpenApiOperation, OpenApiSecurityScheme } from \"../types\";\nimport type { PostmanAuth } from \"./postman-types\";\n\nexport type OperationAuth = {\n /** Set only when the operation must not inherit the collection auth. */\n auth?: PostmanAuth;\n /** Text appended to the request description. */\n note?: string;\n};\n\n/** The collection-level auth: a bearer token read from the `{{token}}` variable. */\nexport function collectionBearerAuth(): PostmanAuth {\n return { type: \"bearer\", bearer: [{ key: \"token\", value: \"{{token}}\", type: \"string\" }] };\n}\n\nfunction isBearer(scheme: OpenApiSecurityScheme | undefined): boolean {\n return scheme?.type === \"http\" && scheme.scheme.toLowerCase() === \"bearer\";\n}\n\n/**\n * Whether the document declares an HTTP bearer scheme, which is what the collection-level\n * auth stands for.\n */\nexport function hasBearerScheme(components: OpenApiComponents | undefined): boolean {\n return Object.values(components?.securitySchemes ?? {}).some(isBearer);\n}\n\n/**\n * How one operation relates to the collection-level bearer auth.\n *\n * - No `security` (a public route): `noauth`, so the token is not sent.\n * - A bearer requirement: inherits the collection auth.\n * - Cookie-only: `noauth` plus a note, because Postman keeps cookies per domain instead of\n * in collection auth.\n */\nexport function resolveOperationAuth(\n operation: OpenApiOperation,\n components: OpenApiComponents | undefined,\n): OperationAuth {\n const bearerDocument = hasBearerScheme(components);\n const schemes = components?.securitySchemes ?? {};\n const names = (operation.security ?? []).flatMap((requirement) => Object.keys(requirement));\n\n if (names.some((name) => isBearer(schemes[name]))) {\n return {};\n }\n\n const cookies = names.flatMap((name) => {\n const scheme = schemes[name];\n\n return scheme?.type === \"apiKey\" && scheme.in === \"cookie\" ? [scheme.name] : [];\n });\n\n const auth = bearerDocument ? { auth: { type: \"noauth\" } as const } : {};\n\n if (cookies.length === 0) {\n return auth;\n }\n\n const cookieList = cookies.map((cookie) => `\"${cookie}\"`).join(\", \");\n\n return {\n ...auth,\n note: `Authenticates with the ${cookieList} cookie. Postman keeps cookies per domain, so sign in against {{baseUrl}} first or add the cookie in the Cookies manager.`,\n };\n}\n"],"mappings":";;AAWA,SAAgB,uBAAoC;CAClD,OAAO;EAAE,MAAM;EAAU,QAAQ,CAAC;GAAE,KAAK;GAAS,OAAO;GAAa,MAAM;EAAS,CAAC;CAAE;AAC1F;AAEA,SAAS,SAAS,QAAoD;CACpE,OAAO,QAAQ,SAAS,UAAU,OAAO,OAAO,YAAY,MAAM;AACpE;;;;;AAMA,SAAgB,gBAAgB,YAAoD;CAClF,OAAO,OAAO,OAAO,YAAY,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ;AACvE;;;;;;;;;AAUA,SAAgB,qBACd,WACA,YACe;CACf,MAAM,iBAAiB,gBAAgB,UAAU;CACjD,MAAM,UAAU,YAAY,mBAAmB,CAAC;CAChD,MAAM,SAAS,UAAU,YAAY,CAAC,EAAC,CAAE,SAAS,gBAAgB,OAAO,KAAK,WAAW,CAAC;CAE1F,IAAI,MAAM,MAAM,SAAS,SAAS,QAAQ,KAAK,CAAC,GAC9C,OAAO,CAAC;CAGV,MAAM,UAAU,MAAM,SAAS,SAAS;EACtC,MAAM,SAAS,QAAQ;EAEvB,OAAO,QAAQ,SAAS,YAAY,OAAO,OAAO,WAAW,CAAC,OAAO,IAAI,IAAI,CAAC;CAChF,CAAC;CAED,MAAM,OAAO,iBAAiB,EAAE,MAAM,EAAE,MAAM,SAAS,EAAW,IAAI,CAAC;CAEvE,IAAI,QAAQ,WAAW,GACrB,OAAO;CAGT,MAAM,aAAa,QAAQ,KAAK,WAAW,IAAI,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI;CAEnE,OAAO;EACL,GAAG;EACH,MAAM,0BAA0B,WAAW;CAC7C;AACF"}
@@ -0,0 +1,7 @@
1
+ import { OpenApiSchema } from "../types.mjs";
2
+
3
+ //#region ../core/src/openapi/postman/schema-refs.d.ts
4
+ type SchemaComponents = Record<string, OpenApiSchema> | undefined;
5
+ //#endregion
6
+ export { SchemaComponents };
7
+ //# sourceMappingURL=schema-refs.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-refs.d.mts","names":[],"sources":["../../../../../../../../core/src/openapi/postman/schema-refs.ts"],"mappings":";;;KAEY,gBAAA,GAAmB,MAAM,SAAS,aAAA"}
@@ -0,0 +1,41 @@
1
+ //#region ../core/src/openapi/postman/schema-refs.ts
2
+ const REF_PREFIX = "#/components/schemas/";
3
+ function isRecord(value) {
4
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5
+ }
6
+ /**
7
+ * Component name a `$ref` schema points at, or `undefined` for a schema that is not a local
8
+ * component reference.
9
+ */
10
+ function refName(schema) {
11
+ const ref = schema.$ref;
12
+ return typeof ref === "string" && ref.startsWith(REF_PREFIX) ? ref.slice(21) : void 0;
13
+ }
14
+ /**
15
+ * Follow `$ref`s (with a hop limit, so a ref loop cannot hang) to the schema they name.
16
+ * An unknown component resolves to `{}`.
17
+ */
18
+ function resolveSchema(schema, components) {
19
+ let current = schema;
20
+ for (let hop = 0; hop < 32; hop++) {
21
+ const name = refName(current);
22
+ if (name === void 0) return current;
23
+ current = components?.[name] ?? {};
24
+ }
25
+ return {};
26
+ }
27
+ /**
28
+ * The effective JSON Schema type: `type`, or the first non-null member of a `type` array
29
+ * (the 3.1 nullable form), or one inferred from `properties` / `items`.
30
+ */
31
+ function primaryType(schema) {
32
+ const { type } = schema;
33
+ if (typeof type === "string") return type;
34
+ if (Array.isArray(type)) return type.find((member) => typeof member === "string" && member !== "null") ?? (type.includes("null") ? "null" : void 0);
35
+ if (isRecord(schema.properties)) return "object";
36
+ if (isRecord(schema.items)) return "array";
37
+ }
38
+
39
+ //#endregion
40
+ export { isRecord, primaryType, refName, resolveSchema };
41
+ //# sourceMappingURL=schema-refs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-refs.mjs","names":[],"sources":["../../../../../../../../core/src/openapi/postman/schema-refs.ts"],"sourcesContent":["import type { OpenApiSchema } from \"../types\";\n\nexport type SchemaComponents = Record<string, OpenApiSchema> | undefined;\n\nconst REF_PREFIX = \"#/components/schemas/\";\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Component name a `$ref` schema points at, or `undefined` for a schema that is not a local\n * component reference.\n */\nexport function refName(schema: OpenApiSchema): string | undefined {\n const ref = schema.$ref;\n\n return typeof ref === \"string\" && ref.startsWith(REF_PREFIX) ? ref.slice(REF_PREFIX.length) : undefined;\n}\n\n/**\n * Follow `$ref`s (with a hop limit, so a ref loop cannot hang) to the schema they name.\n * An unknown component resolves to `{}`.\n */\nexport function resolveSchema(schema: OpenApiSchema, components: SchemaComponents): OpenApiSchema {\n let current = schema;\n\n for (let hop = 0; hop < 32; hop++) {\n const name = refName(current);\n\n if (name === undefined) {\n return current;\n }\n\n current = components?.[name] ?? {};\n }\n\n return {};\n}\n\n/**\n * The effective JSON Schema type: `type`, or the first non-null member of a `type` array\n * (the 3.1 nullable form), or one inferred from `properties` / `items`.\n */\nexport function primaryType(schema: OpenApiSchema): string | undefined {\n const { type } = schema;\n\n if (typeof type === \"string\") {\n return type;\n }\n\n if (Array.isArray(type)) {\n const concrete = type.find((member): member is string => typeof member === \"string\" && member !== \"null\");\n\n return concrete ?? (type.includes(\"null\") ? \"null\" : undefined);\n }\n\n if (isRecord(schema.properties)) {\n return \"object\";\n }\n\n if (isRecord(schema.items)) {\n return \"array\";\n }\n\n return undefined;\n}\n"],"mappings":";AAIA,MAAM,aAAa;AAEnB,SAAgB,SAAS,OAAkD;CACzE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;;;AAMA,SAAgB,QAAQ,QAA2C;CACjE,MAAM,MAAM,OAAO;CAEnB,OAAO,OAAO,QAAQ,YAAY,IAAI,WAAW,UAAU,IAAI,IAAI,MAAM,EAAiB,IAAI;AAChG;;;;;AAMA,SAAgB,cAAc,QAAuB,YAA6C;CAChG,IAAI,UAAU;CAEd,KAAK,IAAI,MAAM,GAAG,MAAM,IAAI,OAAO;EACjC,MAAM,OAAO,QAAQ,OAAO;EAE5B,IAAI,SAAS,QACX,OAAO;EAGT,UAAU,aAAa,SAAS,CAAC;CACnC;CAEA,OAAO,CAAC;AACV;;;;;AAMA,SAAgB,YAAY,QAA2C;CACrE,MAAM,EAAE,SAAS;CAEjB,IAAI,OAAO,SAAS,UAClB,OAAO;CAGT,IAAI,MAAM,QAAQ,IAAI,GAGpB,OAFiB,KAAK,MAAM,WAA6B,OAAO,WAAW,YAAY,WAAW,MAEpF,MAAM,KAAK,SAAS,MAAM,IAAI,SAAS;CAGvD,IAAI,SAAS,OAAO,UAAU,GAC5B,OAAO;CAGT,IAAI,SAAS,OAAO,KAAK,GACvB,OAAO;AAIX"}
@@ -0,0 +1,29 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ //#region ../core/src/openapi/postman/uuid-v5.ts
4
+ /**
5
+ * Namespace of every id the Postman converter derives. Fixed forever: changing it would
6
+ * change the `_postman_id` of every collection already imported.
7
+ */
8
+ const POSTMAN_ID_NAMESPACE = "8f1c2b64-3d7a-4c59-9a0e-5b6d1e47c2a3";
9
+ /**
10
+ * RFC 4122 version 5 (SHA-1, name based) UUID, so the same name always yields the same id.
11
+ */
12
+ function uuidV5(name, namespace = POSTMAN_ID_NAMESPACE) {
13
+ const digest = createHash("sha1").update(Buffer.from(namespace.replace(/-/g, ""), "hex")).update(name, "utf8").digest();
14
+ const bytes = Buffer.from(digest.subarray(0, 16));
15
+ bytes.writeUInt8(bytes.readUInt8(6) & 15 | 80, 6);
16
+ bytes.writeUInt8(bytes.readUInt8(8) & 63 | 128, 8);
17
+ const hex = bytes.toString("hex");
18
+ return [
19
+ hex.slice(0, 8),
20
+ hex.slice(8, 12),
21
+ hex.slice(12, 16),
22
+ hex.slice(16, 20),
23
+ hex.slice(20, 32)
24
+ ].join("-");
25
+ }
26
+
27
+ //#endregion
28
+ export { uuidV5 };
29
+ //# sourceMappingURL=uuid-v5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid-v5.mjs","names":[],"sources":["../../../../../../../../core/src/openapi/postman/uuid-v5.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\n/**\n * Namespace of every id the Postman converter derives. Fixed forever: changing it would\n * change the `_postman_id` of every collection already imported.\n */\nexport const POSTMAN_ID_NAMESPACE = \"8f1c2b64-3d7a-4c59-9a0e-5b6d1e47c2a3\";\n\n/**\n * RFC 4122 version 5 (SHA-1, name based) UUID, so the same name always yields the same id.\n */\nexport function uuidV5(name: string, namespace: string = POSTMAN_ID_NAMESPACE): string {\n const digest = createHash(\"sha1\")\n .update(Buffer.from(namespace.replace(/-/g, \"\"), \"hex\"))\n .update(name, \"utf8\")\n .digest();\n const bytes = Buffer.from(digest.subarray(0, 16));\n\n bytes.writeUInt8((bytes.readUInt8(6) & 0x0f) | 0x50, 6);\n bytes.writeUInt8((bytes.readUInt8(8) & 0x3f) | 0x80, 8);\n\n const hex = bytes.toString(\"hex\");\n\n return [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20, 32)].join(\"-\");\n}\n"],"mappings":";;;;;;;AAMA,MAAa,uBAAuB;;;;AAKpC,SAAgB,OAAO,MAAc,YAAoB,sBAA8B;CACrF,MAAM,SAAS,WAAW,MAAM,CAAC,CAC9B,OAAO,OAAO,KAAK,UAAU,QAAQ,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CACvD,OAAO,MAAM,MAAM,CAAC,CACpB,OAAO;CACV,MAAM,QAAQ,OAAO,KAAK,OAAO,SAAS,GAAG,EAAE,CAAC;CAEhD,MAAM,WAAY,MAAM,UAAU,CAAC,IAAI,KAAQ,IAAM,CAAC;CACtD,MAAM,WAAY,MAAM,UAAU,CAAC,IAAI,KAAQ,KAAM,CAAC;CAEtD,MAAM,MAAM,MAAM,SAAS,KAAK;CAEhC,OAAO;EAAC,IAAI,MAAM,GAAG,CAAC;EAAG,IAAI,MAAM,GAAG,EAAE;EAAG,IAAI,MAAM,IAAI,EAAE;EAAG,IAAI,MAAM,IAAI,EAAE;EAAG,IAAI,MAAM,IAAI,EAAE;CAAC,CAAC,CAAC,KAAK,GAAG;AAC9G"}
@@ -0,0 +1,138 @@
1
+ import { extractPathParamNames, isBodylessMethod } from "./paths.mjs";
2
+
3
+ //#region ../core/src/openapi/request-schemas.ts
4
+ const VALIDATING_LOCATIONS = [
5
+ "body",
6
+ "query",
7
+ "params",
8
+ "headers"
9
+ ];
10
+ /**
11
+ * Convert a Seal validator to a JSON Schema object (`draft-2020-12`, the OpenAPI 3.1 dialect)
12
+ * and split it into properties and required keys. Never throws: a validator that cannot be
13
+ * converted is reported through `warn` and yields `undefined`.
14
+ */
15
+ function readObjectSchema(validator, what, warn) {
16
+ const convert = validator?.toJsonSchema;
17
+ if (typeof convert !== "function") return;
18
+ let schema;
19
+ try {
20
+ schema = convert.call(validator, "draft-2020-12");
21
+ } catch (error) {
22
+ warn(`${what} could not be converted to JSON Schema and is not documented: ${error instanceof Error ? error.message : String(error)}`);
23
+ return;
24
+ }
25
+ const properties = schema?.properties;
26
+ if (typeof schema !== "object" || schema === null || !isRecord(properties)) {
27
+ warn(`${what} is not an object schema and is not documented.`);
28
+ return;
29
+ }
30
+ const required = schema.required;
31
+ return {
32
+ schema,
33
+ properties,
34
+ required: new Set(Array.isArray(required) ? required.filter((key) => typeof key === "string") : [])
35
+ };
36
+ }
37
+ function isRecord(value) {
38
+ return typeof value === "object" && value !== null && !Array.isArray(value);
39
+ }
40
+ function describeParameter(name, location, schema, required) {
41
+ const parameter = {
42
+ name,
43
+ in: location,
44
+ required,
45
+ schema
46
+ };
47
+ if (typeof schema.description === "string") parameter.description = schema.description;
48
+ if (location === "query" && schema.type === "object") {
49
+ parameter.style = "deepObject";
50
+ parameter.explode = true;
51
+ }
52
+ return parameter;
53
+ }
54
+ /**
55
+ * Where an unscoped or scoped `validating` puts the non-path properties.
56
+ *
57
+ * `validateAll` merges body and query (unset) or exactly the listed sources. The OpenAPI
58
+ * placement follows: unset -> by method, a single source -> that source, `body` + `query`
59
+ * -> body for verbs that carry one, query for the rest (and a warning, it is ambiguous).
60
+ */
61
+ function resolvePrimaryLocation(method, validating, warn) {
62
+ if (validating.length === 0) return isBodylessMethod(method) ? "query" : "body";
63
+ const hasBody = validating.includes("body");
64
+ const hasQuery = validating.includes("query");
65
+ if (hasBody && hasQuery) {
66
+ warn(`validating lists both "body" and "query"; the properties are documented as ${isBodylessMethod(method) ? "query parameters" : "the request body"}.`);
67
+ return isBodylessMethod(method) ? "query" : "body";
68
+ }
69
+ if (hasBody) return "body";
70
+ if (hasQuery) return "query";
71
+ if (validating.includes("headers")) return "header";
72
+ }
73
+ /**
74
+ * Map a handler's `validation` to OpenAPI parameters and a request body.
75
+ *
76
+ * - `validation.params` -> one required path parameter per property.
77
+ * - Path segments without a declared schema -> required string path parameters.
78
+ * - `validation.schema` -> request body (verbs that carry one) or query parameters, placed by
79
+ * `validating` exactly as `validateAll` reads the request; a property named like a path
80
+ * segment is a path parameter whenever `params` is validated (unset counts).
81
+ */
82
+ function buildRequestParts(input) {
83
+ const { method, path, validation, warn } = input;
84
+ const validating = (validation?.validating ?? []).filter((location) => VALIDATING_LOCATIONS.includes(location));
85
+ const pathNames = extractPathParamNames(path);
86
+ const paramsSchema = validation?.params ? readObjectSchema(validation.params, "validation.params", warn) : void 0;
87
+ const mainSchema = validation?.schema ? readObjectSchema(validation.schema, "validation.schema", warn) : void 0;
88
+ const pathSchemas = /* @__PURE__ */ new Map();
89
+ const primary = resolvePrimaryLocation(method, validating, warn);
90
+ const takesPathNames = validating.length === 0 || validating.includes("params");
91
+ const located = {
92
+ body: [],
93
+ query: [],
94
+ header: []
95
+ };
96
+ if (paramsSchema) {
97
+ for (const name of pathNames) if (paramsSchema.properties[name]) pathSchemas.set(name, paramsSchema.properties[name]);
98
+ }
99
+ if (mainSchema) for (const [key, propertySchema] of Object.entries(mainSchema.properties)) {
100
+ if (takesPathNames && pathNames.includes(key)) {
101
+ if (!pathSchemas.has(key)) pathSchemas.set(key, propertySchema);
102
+ continue;
103
+ }
104
+ if (!primary) {
105
+ warn(`validation.schema property "${key}" is not a path parameter and is not documented.`);
106
+ continue;
107
+ }
108
+ located[primary].push({
109
+ key,
110
+ schema: propertySchema
111
+ });
112
+ }
113
+ const requiredKeys = mainSchema?.required ?? /* @__PURE__ */ new Set();
114
+ const parameters = pathNames.map((name) => describeParameter(name, "path", pathSchemas.get(name) ?? { type: "string" }, true));
115
+ for (const { key, schema } of located.query) parameters.push(describeParameter(key, "query", schema, requiredKeys.has(key)));
116
+ for (const { key, schema } of located.header) parameters.push(describeParameter(key.toLowerCase(), "header", schema, requiredKeys.has(key)));
117
+ if (located.body.length === 0 || !mainSchema) return { parameters };
118
+ const properties = {};
119
+ for (const { key, schema } of located.body) properties[key] = schema;
120
+ const required = located.body.filter(({ key }) => mainSchema.required.has(key)).map(({ key }) => key);
121
+ const bodySchema = {
122
+ ...mainSchema.schema,
123
+ properties
124
+ };
125
+ if (required.length > 0) bodySchema.required = required;
126
+ else delete bodySchema.required;
127
+ return {
128
+ parameters,
129
+ requestBody: {
130
+ required: required.length > 0,
131
+ content: { "application/json": { schema: bodySchema } }
132
+ }
133
+ };
134
+ }
135
+
136
+ //#endregion
137
+ export { buildRequestParts };
138
+ //# sourceMappingURL=request-schemas.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-schemas.mjs","names":[],"sources":["../../../../../../../core/src/openapi/request-schemas.ts"],"sourcesContent":["import type { RequestHandlerValidation } from \"../router/types\";\nimport { extractPathParamNames, isBodylessMethod, type OpenApiMethod } from \"./paths\";\nimport type {\n OpenApiParameter,\n OpenApiRequestBody,\n OpenApiSchema,\n WarningSink,\n} from \"./types\";\n\ntype ValidatingLocation = \"body\" | \"query\" | \"params\" | \"headers\";\n\ntype ObjectSchemaParts = {\n schema: OpenApiSchema;\n properties: Record<string, OpenApiSchema>;\n required: Set<string>;\n};\n\n/** A `validation.schema` property placed in a request location, with its JSON Schema. */\ntype LocatedProperty = {\n key: string;\n schema: OpenApiSchema;\n};\n\nexport type RequestParts = {\n parameters: OpenApiParameter[];\n requestBody?: OpenApiRequestBody;\n};\n\nexport type BuildRequestPartsInput = {\n method: OpenApiMethod;\n /** Warlock path, with `:param` segments. */\n path: string;\n validation: RequestHandlerValidation | undefined;\n warn: WarningSink;\n};\n\nconst VALIDATING_LOCATIONS: readonly string[] = [\"body\", \"query\", \"params\", \"headers\"];\n\n/**\n * Convert a Seal validator to a JSON Schema object (`draft-2020-12`, the OpenAPI 3.1 dialect)\n * and split it into properties and required keys. Never throws: a validator that cannot be\n * converted is reported through `warn` and yields `undefined`.\n */\nfunction readObjectSchema(\n validator: unknown,\n what: string,\n warn: WarningSink,\n): ObjectSchemaParts | undefined {\n const convert = (validator as { toJsonSchema?: unknown } | undefined)?.toJsonSchema;\n\n if (typeof convert !== \"function\") {\n return undefined;\n }\n\n let schema: unknown;\n\n try {\n schema = convert.call(validator, \"draft-2020-12\");\n } catch (error) {\n warn(\n `${what} could not be converted to JSON Schema and is not documented: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n\n return undefined;\n }\n\n const properties = (schema as { properties?: unknown } | undefined)?.properties;\n\n if (typeof schema !== \"object\" || schema === null || !isRecord(properties)) {\n warn(`${what} is not an object schema and is not documented.`);\n\n return undefined;\n }\n\n const required = (schema as { required?: unknown }).required;\n\n return {\n schema: schema as OpenApiSchema,\n properties: properties as Record<string, OpenApiSchema>,\n required: new Set(Array.isArray(required) ? required.filter((key) => typeof key === \"string\") : []),\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction describeParameter(\n name: string,\n location: OpenApiParameter[\"in\"],\n schema: OpenApiSchema,\n required: boolean,\n): OpenApiParameter {\n const parameter: OpenApiParameter = { name, in: location, required, schema };\n\n if (typeof schema.description === \"string\") {\n parameter.description = schema.description;\n }\n\n if (location === \"query\" && schema.type === \"object\") {\n parameter.style = \"deepObject\";\n parameter.explode = true;\n }\n\n return parameter;\n}\n\n/**\n * Where an unscoped or scoped `validating` puts the non-path properties.\n *\n * `validateAll` merges body and query (unset) or exactly the listed sources. The OpenAPI\n * placement follows: unset -> by method, a single source -> that source, `body` + `query`\n * -> body for verbs that carry one, query for the rest (and a warning, it is ambiguous).\n */\nfunction resolvePrimaryLocation(\n method: OpenApiMethod,\n validating: ValidatingLocation[],\n warn: WarningSink,\n): \"body\" | \"query\" | \"header\" | undefined {\n if (validating.length === 0) {\n return isBodylessMethod(method) ? \"query\" : \"body\";\n }\n\n const hasBody = validating.includes(\"body\");\n const hasQuery = validating.includes(\"query\");\n\n if (hasBody && hasQuery) {\n warn(\n `validating lists both \"body\" and \"query\"; the properties are documented as ${\n isBodylessMethod(method) ? \"query parameters\" : \"the request body\"\n }.`,\n );\n\n return isBodylessMethod(method) ? \"query\" : \"body\";\n }\n\n if (hasBody) {\n return \"body\";\n }\n\n if (hasQuery) {\n return \"query\";\n }\n\n if (validating.includes(\"headers\")) {\n return \"header\";\n }\n\n return undefined;\n}\n\n/**\n * Map a handler's `validation` to OpenAPI parameters and a request body.\n *\n * - `validation.params` -> one required path parameter per property.\n * - Path segments without a declared schema -> required string path parameters.\n * - `validation.schema` -> request body (verbs that carry one) or query parameters, placed by\n * `validating` exactly as `validateAll` reads the request; a property named like a path\n * segment is a path parameter whenever `params` is validated (unset counts).\n */\nexport function buildRequestParts(input: BuildRequestPartsInput): RequestParts {\n const { method, path, validation, warn } = input;\n const validating = (validation?.validating ?? []).filter((location): location is ValidatingLocation =>\n VALIDATING_LOCATIONS.includes(location),\n );\n const pathNames = extractPathParamNames(path);\n\n const paramsSchema = validation?.params\n ? readObjectSchema(validation.params, \"validation.params\", warn)\n : undefined;\n const mainSchema = validation?.schema\n ? readObjectSchema(validation.schema, \"validation.schema\", warn)\n : undefined;\n\n const pathSchemas = new Map<string, OpenApiSchema>();\n const primary = resolvePrimaryLocation(method, validating, warn);\n const takesPathNames = validating.length === 0 || validating.includes(\"params\");\n\n const located: Record<\"body\" | \"query\" | \"header\", LocatedProperty[]> = {\n body: [],\n query: [],\n header: [],\n };\n\n if (paramsSchema) {\n for (const name of pathNames) {\n if (paramsSchema.properties[name]) {\n pathSchemas.set(name, paramsSchema.properties[name]);\n }\n }\n }\n\n if (mainSchema) {\n for (const [key, propertySchema] of Object.entries(mainSchema.properties)) {\n if (takesPathNames && pathNames.includes(key)) {\n if (!pathSchemas.has(key)) {\n pathSchemas.set(key, propertySchema);\n }\n\n continue;\n }\n\n if (!primary) {\n warn(`validation.schema property \"${key}\" is not a path parameter and is not documented.`);\n\n continue;\n }\n\n located[primary].push({ key, schema: propertySchema });\n }\n }\n\n const requiredKeys = mainSchema?.required ?? new Set<string>();\n\n const parameters: OpenApiParameter[] = pathNames.map((name) =>\n describeParameter(name, \"path\", pathSchemas.get(name) ?? { type: \"string\" }, true),\n );\n\n for (const { key, schema } of located.query) {\n parameters.push(describeParameter(key, \"query\", schema, requiredKeys.has(key)));\n }\n\n for (const { key, schema } of located.header) {\n parameters.push(describeParameter(key.toLowerCase(), \"header\", schema, requiredKeys.has(key)));\n }\n\n if (located.body.length === 0 || !mainSchema) {\n return { parameters };\n }\n\n const properties: Record<string, OpenApiSchema> = {};\n\n for (const { key, schema } of located.body) {\n properties[key] = schema;\n }\n\n const required = located.body.filter(({ key }) => mainSchema.required.has(key)).map(({ key }) => key);\n const bodySchema: OpenApiSchema = { ...mainSchema.schema, properties };\n\n if (required.length > 0) {\n bodySchema.required = required;\n } else {\n delete bodySchema.required;\n }\n\n return {\n parameters,\n requestBody: {\n required: required.length > 0,\n content: { \"application/json\": { schema: bodySchema } },\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ //#region ../core/src/openapi/response-schemas.ts
2
+ const STATUS_TEXT = {
3
+ "200": "Successful response",
4
+ "201": "Created",
5
+ "202": "Accepted",
6
+ "204": "No content",
7
+ "301": "Moved permanently",
8
+ "302": "Found",
9
+ "304": "Not modified",
10
+ "400": "Bad request",
11
+ "401": "Unauthorized",
12
+ "403": "Forbidden",
13
+ "404": "Not found",
14
+ "405": "Method not allowed",
15
+ "409": "Conflict",
16
+ "410": "Gone",
17
+ "422": "Validation failed",
18
+ "429": "Too many requests",
19
+ "500": "Internal server error",
20
+ "503": "Service unavailable"
21
+ };
22
+ function isRecord(value) {
23
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24
+ }
25
+ /**
26
+ * Human description of a status code for the required `description` of a response.
27
+ */
28
+ function describeStatus(status) {
29
+ return STATUS_TEXT[status] ?? `Status ${status}`;
30
+ }
31
+ /**
32
+ * JSON Schema of one response body value: a cast string, a resource class, `[Resource]`
33
+ * (array of it) or a nested plain object of those. Anything else (a resolver function, a
34
+ * builder) is `{}` plus a warning.
35
+ */
36
+ function bodyValueSchema(value, path, registry, warn) {
37
+ if (typeof value === "string") return registry.cast(value, path, warn);
38
+ if (typeof value === "function") {
39
+ if (registry.isResourceClass(value)) return registry.resourceRef(value);
40
+ warn(`${path}: a function that is not a resource class has no static shape and is documented as {}.`);
41
+ return {};
42
+ }
43
+ if (Array.isArray(value)) return {
44
+ type: "array",
45
+ items: bodyValueSchema(value[0], `${path}[]`, registry, warn)
46
+ };
47
+ if (isRecord(value)) return objectSchema(value, path, registry, warn);
48
+ warn(`${path}: unsupported response value is documented as {}.`);
49
+ return {};
50
+ }
51
+ function objectSchema(body, path, registry, warn) {
52
+ const properties = {};
53
+ for (const [key, member] of Object.entries(body)) properties[key] = bodyValueSchema(member, `${path}.${key}`, registry, warn);
54
+ return {
55
+ type: "object",
56
+ properties,
57
+ required: Object.keys(properties)
58
+ };
59
+ }
60
+ /**
61
+ * Map a handler's `responseSchema` to OpenAPI responses, one per declared status code.
62
+ *
63
+ * `routeLabel` prefixes warnings (for example `POST /login`). A status without a usable
64
+ * `body` becomes a description-only response.
65
+ */
66
+ function buildDeclaredResponses(responseSchema, registry, warn) {
67
+ const responses = {};
68
+ if (!isRecord(responseSchema)) return responses;
69
+ for (const [status, entry] of Object.entries(responseSchema)) {
70
+ const body = isRecord(entry) ? entry.body : void 0;
71
+ if (!isRecord(body)) {
72
+ responses[status] = { description: describeStatus(status) };
73
+ continue;
74
+ }
75
+ responses[status] = {
76
+ description: describeStatus(status),
77
+ content: { "application/json": { schema: objectSchema(body, `response ${status}`, registry, warn) } }
78
+ };
79
+ }
80
+ return responses;
81
+ }
82
+
83
+ //#endregion
84
+ export { buildDeclaredResponses, describeStatus };
85
+ //# sourceMappingURL=response-schemas.mjs.map