@cognisivelabs/openapi-to-express 0.1.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.
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Parses an OpenAPI 3.x spec and extracts structured data for code generation.
3
+ */
4
+ import { toPascalCase } from "../utils/strings.js";
5
+ export function refToTypeName(ref) {
6
+ const raw = ref.split("/").pop();
7
+ return toPascalCase(raw);
8
+ }
9
+ function schemaTypeToTs(schema) {
10
+ if (!schema)
11
+ return "string";
12
+ if (schema.type === "integer" || schema.type === "number")
13
+ return "number";
14
+ if (schema.type === "boolean")
15
+ return "boolean";
16
+ if (schema.type === "array")
17
+ return `${schemaTypeToTs(schema.items)}[]`;
18
+ return "string";
19
+ }
20
+ function resolveParam(p, spec) {
21
+ if (p.$ref) {
22
+ // Use raw name (not PascalCased) — parameter keys aren't schema names
23
+ const rawName = p.$ref.split("/").pop();
24
+ return spec.components?.parameters?.[rawName] ?? p;
25
+ }
26
+ return p;
27
+ }
28
+ function extractParams(operation, pathItem, spec) {
29
+ const pathParams = pathItem.parameters ?? [];
30
+ const opParams = operation.parameters ?? [];
31
+ const merged = new Map();
32
+ for (const p of pathParams) {
33
+ const resolved = resolveParam(p, spec);
34
+ merged.set(`${resolved.in}:${resolved.name}`, resolved);
35
+ }
36
+ for (const p of opParams) {
37
+ const resolved = resolveParam(p, spec);
38
+ merged.set(`${resolved.in}:${resolved.name}`, resolved);
39
+ }
40
+ return Array.from(merged.values())
41
+ .filter((p) => p.in === "path" || p.in === "query" || p.in === "header")
42
+ .map((p) => ({
43
+ name: p.name,
44
+ in: p.in,
45
+ required: p.required ?? p.in === "path",
46
+ type: schemaTypeToTs(p.schema),
47
+ }));
48
+ }
49
+ function resolveSchema(schema, operationId, suffix, schemas) {
50
+ if (!schema)
51
+ return null;
52
+ if (schema.$ref)
53
+ return refToTypeName(schema.$ref);
54
+ // Inline schema — generate a name and register it
55
+ if (schema.type === "object" || schema.properties || schema.allOf || schema.oneOf || schema.anyOf) {
56
+ const name = `${toPascalCase(operationId)}${suffix}`;
57
+ schemas[name] = schema;
58
+ return name;
59
+ }
60
+ return null;
61
+ }
62
+ export function parseSpec(spec) {
63
+ const operations = [];
64
+ // Sanitize schema names to valid PascalCase identifiers
65
+ const rawSchemas = spec.components?.schemas ?? {};
66
+ const schemas = {};
67
+ for (const [name, schema] of Object.entries(rawSchemas)) {
68
+ schemas[toPascalCase(name)] = schema;
69
+ }
70
+ const HTTP_METHODS = new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
71
+ const SUCCESS_CODES = ["200", "201", "202", "204"];
72
+ for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
73
+ for (const [method, operation] of Object.entries(pathItem)) {
74
+ if (!HTTP_METHODS.has(method))
75
+ continue;
76
+ if (!operation.operationId)
77
+ continue;
78
+ const tag = operation.tags?.[0] ?? "Default";
79
+ let requestType = null;
80
+ let requestRequired = true;
81
+ if (operation.requestBody) {
82
+ const bodySchema = operation.requestBody.content?.["application/json"]?.schema;
83
+ requestType = resolveSchema(bodySchema, operation.operationId, "Request", schemas);
84
+ requestRequired = operation.requestBody.required ?? false;
85
+ }
86
+ let responseType = "void";
87
+ for (const code of SUCCESS_CODES) {
88
+ const response = operation.responses?.[code];
89
+ if (response) {
90
+ if (code === "204") {
91
+ // 204 No Content — void return, no body
92
+ responseType = "void";
93
+ break;
94
+ }
95
+ const respSchema = response.content?.["application/json"]?.schema;
96
+ const resolved = resolveSchema(respSchema, operation.operationId, "Response", schemas);
97
+ if (resolved) {
98
+ responseType = resolved;
99
+ break;
100
+ }
101
+ }
102
+ }
103
+ // Extract error response types (4xx, 5xx)
104
+ const errorTypes = [];
105
+ for (const [code, resp] of Object.entries(operation.responses ?? {})) {
106
+ if (code.startsWith("4") || code.startsWith("5")) {
107
+ const errSchema = resp.content?.["application/json"]?.schema;
108
+ const errType = resolveSchema(errSchema, operation.operationId, `Error${code}`, schemas);
109
+ if (errType) {
110
+ errorTypes.push({ status: code, type: errType });
111
+ }
112
+ }
113
+ }
114
+ const allParams = extractParams(operation, pathItem, spec);
115
+ const pathParams = allParams.filter((p) => p.in === "path");
116
+ const queryParams = allParams.filter((p) => p.in === "query");
117
+ const headerParams = allParams.filter((p) => p.in === "header");
118
+ operations.push({
119
+ path, method, operationId: operation.operationId,
120
+ requestType, requestRequired, responseType, errorTypes, tag,
121
+ pathParams, queryParams, headerParams,
122
+ summary: operation.summary,
123
+ description: operation.description,
124
+ deprecated: operation.deprecated ?? false,
125
+ });
126
+ }
127
+ }
128
+ const byTag = new Map();
129
+ for (const op of operations) {
130
+ const ops = byTag.get(op.tag) ?? [];
131
+ ops.push(op);
132
+ byTag.set(op.tag, ops);
133
+ }
134
+ return {
135
+ operations,
136
+ schemas,
137
+ byTag,
138
+ };
139
+ }
140
+ /**
141
+ * Collect all $ref type names from a schema node (non-recursive into schemas map).
142
+ */
143
+ export function collectRefsFromSchema(schema, refs) {
144
+ if (!schema)
145
+ return;
146
+ if (schema.$ref)
147
+ refs.add(refToTypeName(schema.$ref));
148
+ if (schema.items)
149
+ collectRefsFromSchema(schema.items, refs);
150
+ for (const key of ["allOf", "oneOf", "anyOf"]) {
151
+ if (schema[key]) {
152
+ for (const sub of schema[key])
153
+ collectRefsFromSchema(sub, refs);
154
+ }
155
+ }
156
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
157
+ collectRefsFromSchema(schema.additionalProperties, refs);
158
+ }
159
+ for (const propDef of Object.values(schema.properties ?? {})) {
160
+ collectRefsFromSchema(propDef, refs);
161
+ }
162
+ }
163
+ /**
164
+ * Walk a named schema recursively through the schemas map, collecting
165
+ * all transitively referenced schema names.
166
+ */
167
+ export function walkSchemaRefs(name, schemas, collected) {
168
+ if (collected.has(name))
169
+ return;
170
+ collected.add(name);
171
+ const schema = schemas[name];
172
+ if (!schema)
173
+ return;
174
+ const refs = new Set();
175
+ collectRefsFromSchema(schema, refs);
176
+ for (const ref of refs) {
177
+ walkSchemaRefs(ref, schemas, collected);
178
+ }
179
+ }
180
+ /**
181
+ * Classify schemas into common (multi-tag) vs tag-specific.
182
+ */
183
+ export function classifySchemas(byTag, schemas) {
184
+ const schemaToTags = new Map();
185
+ for (const [tag, operations] of byTag) {
186
+ const tagSchemas = new Set();
187
+ for (const op of operations) {
188
+ if (op.requestType)
189
+ walkSchemaRefs(op.requestType, schemas, tagSchemas);
190
+ if (op.responseType !== "void")
191
+ walkSchemaRefs(op.responseType, schemas, tagSchemas);
192
+ for (const err of op.errorTypes)
193
+ walkSchemaRefs(err.type, schemas, tagSchemas);
194
+ }
195
+ for (const schema of tagSchemas) {
196
+ const tags = schemaToTags.get(schema) ?? new Set();
197
+ tags.add(tag);
198
+ schemaToTags.set(schema, tags);
199
+ }
200
+ }
201
+ const common = [];
202
+ const perTag = new Map();
203
+ for (const [schema, tags] of schemaToTags) {
204
+ if (tags.size > 1) {
205
+ common.push(schema);
206
+ }
207
+ else {
208
+ const tag = [...tags][0];
209
+ const list = perTag.get(tag) ?? [];
210
+ list.push(schema);
211
+ perTag.set(tag, list);
212
+ }
213
+ }
214
+ return { common: common.sort(), perTag };
215
+ }
216
+ //# sourceMappingURL=spec-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-parser.js","sourceRoot":"","sources":["../../src/parser/spec-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Compute a relative import path from one directory to another.
3
+ *
4
+ * Example: computeRelativeImport("controllers", "types") → "../types"
5
+ * computeRelativeImport("src/controllers", "src/types") → "../types"
6
+ * computeRelativeImport("types", "types") → "."
7
+ */
8
+ export declare function computeRelativeImport(fromDir: string, toDir: string): string;
@@ -0,0 +1,17 @@
1
+ import { relative, posix } from "node:path";
2
+ /**
3
+ * Compute a relative import path from one directory to another.
4
+ *
5
+ * Example: computeRelativeImport("controllers", "types") → "../types"
6
+ * computeRelativeImport("src/controllers", "src/types") → "../types"
7
+ * computeRelativeImport("types", "types") → "."
8
+ */
9
+ export function computeRelativeImport(fromDir, toDir) {
10
+ const rel = relative(fromDir, toDir).split("\\").join(posix.sep);
11
+ if (rel === "")
12
+ return ".";
13
+ if (!rel.startsWith("."))
14
+ return `./${rel}`;
15
+ return rel;
16
+ }
17
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe,EAAE,KAAa;IAClE,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjE,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,GAAG,CAAC;IAC3B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,GAAG,EAAE,CAAC;IAC5C,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared string utilities.
3
+ */
4
+ export declare function capitalize(s: string): string;
5
+ /**
6
+ * Convert a string to PascalCase.
7
+ * "repos" → "Repos", "pull-requests" → "PullRequests", "code_scanning" → "CodeScanning"
8
+ */
9
+ export declare function toPascalCase(s: string): string;
10
+ /**
11
+ * Convert an operationId to a valid JS identifier.
12
+ * "repos/list-for-org" → "reposListForOrg"
13
+ * "get-user" → "getUser"
14
+ * "get.user.by-id_v2" → "getUserByIdV2"
15
+ */
16
+ export declare function toMethodName(operationId: string): string;
17
+ /**
18
+ * Escape `* /` sequences that would break JSDoc comments.
19
+ */
20
+ export declare function escapeJsDoc(text: string): string;
21
+ /**
22
+ * Sanitize a description for JSDoc — handle multi-line content.
23
+ * Returns lines with ` * ` prefix (indented for interface methods).
24
+ */
25
+ export declare function toJsDocLines(text: string): string[];
26
+ /**
27
+ * Quote a property name if it's not a valid JS identifier.
28
+ */
29
+ export declare function safePropertyName(name: string): string;
30
+ export declare function enumKeyFromValue(value: any): string;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shared string utilities.
3
+ */
4
+ export function capitalize(s) {
5
+ return s.charAt(0).toUpperCase() + s.slice(1);
6
+ }
7
+ /**
8
+ * Convert a string to PascalCase.
9
+ * "repos" → "Repos", "pull-requests" → "PullRequests", "code_scanning" → "CodeScanning"
10
+ */
11
+ export function toPascalCase(s) {
12
+ return s
13
+ .split(/[_\-\s\/\.]+/)
14
+ .filter(Boolean)
15
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
16
+ .join("");
17
+ }
18
+ /**
19
+ * Convert an operationId to a valid JS identifier.
20
+ * "repos/list-for-org" → "reposListForOrg"
21
+ * "get-user" → "getUser"
22
+ * "get.user.by-id_v2" → "getUserByIdV2"
23
+ */
24
+ export function toMethodName(operationId) {
25
+ const parts = operationId.split(/[\/\-_\.\s]+/).filter(Boolean);
26
+ if (parts.length === 0)
27
+ return operationId;
28
+ return parts[0] + parts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
29
+ }
30
+ /**
31
+ * Escape `* /` sequences that would break JSDoc comments.
32
+ */
33
+ export function escapeJsDoc(text) {
34
+ return text.replace(/\*\//g, "*\\/");
35
+ }
36
+ /**
37
+ * Sanitize a description for JSDoc — handle multi-line content.
38
+ * Returns lines with ` * ` prefix (indented for interface methods).
39
+ */
40
+ export function toJsDocLines(text) {
41
+ return escapeJsDoc(text).split("\n").map((line) => ` * ${line}`);
42
+ }
43
+ /**
44
+ * Check if a string is a valid JS identifier (can be used unquoted as a property name).
45
+ */
46
+ function isValidIdentifier(s) {
47
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(s);
48
+ }
49
+ /**
50
+ * Quote a property name if it's not a valid JS identifier.
51
+ */
52
+ export function safePropertyName(name) {
53
+ return isValidIdentifier(name) ? name : `"${name}"`;
54
+ }
55
+ export function enumKeyFromValue(value) {
56
+ const str = String(value);
57
+ const cleaned = str
58
+ .replace(/[^a-zA-Z0-9_]+/g, "_")
59
+ .replace(/^_+|_+$/g, "");
60
+ if (!cleaned || /^[0-9]/.test(cleaned)) {
61
+ return `Value_${cleaned || str.replace(/[^a-zA-Z0-9]/g, "")}`;
62
+ }
63
+ return cleaned
64
+ .split("_")
65
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
66
+ .join("");
67
+ }
68
+ //# sourceMappingURL=strings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings.js","sourceRoot":"","sources":["../../src/utils/strings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,OAAO,CAAC;SACL,KAAK,CAAC,cAAc,CAAC;SACrB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAC3D,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC;IAC3C,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC/F,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,CAAS;IAClC,OAAO,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAU;IACzC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,MAAM,OAAO,GAAG,GAAG;SAChB,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC;SAC/B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,OAAO,SAAS,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,EAAE,CAAC;IAChE,CAAC;IACD,OAAO,OAAO;SACX,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SACzE,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@cognisivelabs/openapi-to-express",
3
+ "version": "0.1.0",
4
+ "description": "Generate Express routes, controller interfaces, and TypeScript types from an OpenAPI spec. Design-first API development.",
5
+ "author": "Sachin Gupta",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/cognisivelabs/openapi-to-express"
10
+ },
11
+ "homepage": "https://github.com/cognisivelabs/openapi-to-express#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/cognisivelabs/openapi-to-express/issues"
14
+ },
15
+ "keywords": [
16
+ "openapi",
17
+ "express",
18
+ "codegen",
19
+ "typescript",
20
+ "design-first",
21
+ "contract-first",
22
+ "api",
23
+ "code-generator",
24
+ "routes",
25
+ "controller"
26
+ ],
27
+ "bin": {
28
+ "openapi-to-express": "dist/cli.js"
29
+ },
30
+ "main": "dist/index.js",
31
+ "types": "dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "import": "./dist/index.js",
35
+ "types": "./dist/index.d.ts"
36
+ }
37
+ },
38
+ "type": "module",
39
+ "sideEffects": false,
40
+ "engines": {
41
+ "node": ">=20.0.0"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "LICENSE",
46
+ "README.md"
47
+ ],
48
+ "scripts": {
49
+ "prebuild": "rm -rf dist",
50
+ "build": "tsc && chmod +x dist/cli.js",
51
+ "test": "node --import tsx --test tests/**/*.test.ts",
52
+ "dev": "tsx src/cli.ts",
53
+ "prepublishOnly": "npm run test && npm run build"
54
+ },
55
+ "dependencies": {
56
+ "yaml": "^2.9.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^22.0.0",
60
+ "tsx": "^4.19.4",
61
+ "typescript": "^5.8.3"
62
+ }
63
+ }