elysia-openapi-codegen 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -318,26 +318,14 @@ your-project/
318
318
  ```bash
319
319
  # Install dependencies
320
320
  bun install
321
- ```
322
-
323
- ### Local Development
324
321
 
325
- ```bash
326
- # Run directly with Bun during development
322
+ # Run the generator locally
327
323
  bun index.ts -i ./example/openapi.json -o ./output
328
324
  ```
329
325
 
330
- ### Building for Production
331
-
332
- ```bash
333
- # Compile TypeScript to JavaScript
334
- npm run build
335
-
336
- # Test the compiled version
337
- node dist/index.js --help
338
- ```
326
+ ### Building
339
327
 
340
- The build outputs JavaScript files to the `dist/` folder, which is what gets published to npm.
328
+ This project uses Bun as the runtime. No build step is necessary for development.
341
329
 
342
330
  ## How It Works
343
331
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/index.ts
3
+ // index.ts
4
4
  import fs from "fs";
5
5
  import path from "path";
6
6
  import https from "https";
@@ -11,7 +11,7 @@ async function fetchSpec(source) {
11
11
  const client = source.startsWith("https://") ? https : http;
12
12
  client.get(source, (res) => {
13
13
  let data = "";
14
- res.on("data", (chunk) => data += chunk.toString());
14
+ res.on("data", (chunk) => data += chunk);
15
15
  res.on("end", () => {
16
16
  try {
17
17
  resolve(JSON.parse(data));
@@ -77,7 +77,7 @@ function generateTypes(spec) {
77
77
  if (!["get", "post", "put", "patch", "delete"].includes(method))
78
78
  continue;
79
79
  const op = operation;
80
- const opId = op.operationId || `${method}${pathUrl.replace(/[^a-zA-Z0-9]/g, "")}`;
80
+ const opId = toSafeIdentifier(op.operationId || `${method}${pathUrl.replace(/[^a-zA-Z0-9]/g, "")}`);
81
81
  const response = op.responses?.["200"];
82
82
  if (response?.content?.["application/json"]?.schema) {
83
83
  const responseType = resolveType(response.content["application/json"].schema);
@@ -112,7 +112,7 @@ function generateHooks(spec) {
112
112
  if (!["get", "post", "put", "patch", "delete"].includes(method))
113
113
  continue;
114
114
  const op = operation;
115
- const opId = op.operationId || `${method}${pathUrl.replace(/[^a-zA-Z0-9]/g, "")}`;
115
+ const opId = toSafeIdentifier(op.operationId || `${method}${pathUrl.replace(/[^a-zA-Z0-9]/g, "")}`);
116
116
  const hasResponse = !!op.responses?.["200"]?.content?.["application/json"]?.schema;
117
117
  const responseType = hasResponse ? `${capitalize(opId)}Response` : "any";
118
118
  const params = op.parameters || [];
@@ -121,8 +121,18 @@ function generateHooks(spec) {
121
121
  const hasBody = !!op.requestBody?.content?.["application/json"]?.schema;
122
122
  const bodyType = hasBody ? `${capitalize(opId)}Body` : "void";
123
123
  if (method === "get") {
124
- const queryParams = params.map((p) => p.name);
125
- const queryString = queryParams.length > 0 ? `?${queryParams.map((p) => `\${params?.${p} !== undefined ? '${p}=' + params.${p} : ''}`).join("&")}` : "";
124
+ const pathParamsList = params.filter((p) => p.in === "path");
125
+ const queryParamsList = params.filter((p) => p.in === "query");
126
+ let urlPath = pathUrl;
127
+ for (const p of pathParamsList) {
128
+ urlPath = urlPath.replace(`{${p.name}}`, `\${params.${p.name}}`);
129
+ }
130
+ const qsLines = queryParamsList.map((p) => ` if (params?.${p.name} !== undefined) _qs.set('${p.name}', String(params.${p.name}));`).join(`
131
+ `);
132
+ const fetchStatement = queryParamsList.length > 0 ? ` const _qs = new URLSearchParams();
133
+ ${qsLines}
134
+ const _qstr = _qs.toString();
135
+ const res = await fetch(\`\${baseUrl}${urlPath}\${_qstr ? '?' + _qstr : ''}\`);` : ` const res = await fetch(\`\${baseUrl}${urlPath}\`);`;
126
136
  hooks.push(`
127
137
  export const use${capitalize(opId)} = (
128
138
  params${hasParams ? "" : "?"}: ${paramsType},
@@ -131,7 +141,7 @@ export const use${capitalize(opId)} = (
131
141
  return useQuery<${responseType}>({
132
142
  queryKey: ['${opId}', params],
133
143
  queryFn: async () => {
134
- const res = await fetch(\`\${baseUrl}${pathUrl}${queryString}\`);
144
+ ${fetchStatement}
135
145
  if (!res.ok) throw new Error('API Error');
136
146
  return res.json();
137
147
  },
@@ -139,26 +149,40 @@ export const use${capitalize(opId)} = (
139
149
  });
140
150
  };`);
141
151
  } else {
142
- let inputType = "void";
143
- let inputArg = "";
144
- if (hasBody) {
152
+ const pathParamsList = params.filter((p) => p.in === "path");
153
+ const hasPathParams = pathParamsList.length > 0;
154
+ let urlPath = pathUrl;
155
+ for (const p of pathParamsList) {
156
+ urlPath = urlPath.replace(`{${p.name}}`, `\${${p.name}}`);
157
+ }
158
+ let inputType;
159
+ let mutationArg;
160
+ if (hasBody && hasPathParams) {
161
+ inputType = `${capitalize(opId)}Params & ${capitalize(opId)}Body`;
162
+ const pathParamNames = pathParamsList.map((p) => p.name).join(", ");
163
+ mutationArg = `{ ${pathParamNames}, ...body }`;
164
+ } else if (hasBody) {
145
165
  inputType = bodyType;
146
- inputArg = "body";
147
- } else if (hasParams) {
166
+ mutationArg = "body";
167
+ } else if (hasPathParams) {
148
168
  inputType = paramsType;
149
- inputArg = "params";
169
+ const pathParamNames = pathParamsList.map((p) => p.name).join(", ");
170
+ mutationArg = `{ ${pathParamNames} }`;
171
+ } else {
172
+ inputType = "void";
173
+ mutationArg = "";
150
174
  }
151
- const hasInput = inputType !== "void";
175
+ const fetchBodyLine = hasBody ? `
176
+ body: JSON.stringify(body),` : "";
152
177
  hooks.push(`
153
178
  export const use${capitalize(opId)} = (
154
179
  options?: UseMutationOptions<${responseType}, Error, ${inputType}>
155
180
  ) => {
156
181
  return useMutation<${responseType}, Error, ${inputType}>({
157
- mutationFn: async (${hasInput ? inputArg : ""}) => {
158
- const res = await fetch(\`\${baseUrl}${pathUrl}\`, {
182
+ mutationFn: async (${mutationArg}) => {
183
+ const res = await fetch(\`\${baseUrl}${urlPath}\`, {
159
184
  method: '${method.toUpperCase()}',
160
- headers: { 'Content-Type': 'application/json' },
161
- body: JSON.stringify(${hasInput ? inputArg : "{}"}),
185
+ headers: { 'Content-Type': 'application/json' },${fetchBodyLine}
162
186
  });
163
187
  if (!res.ok) throw new Error('API Error');
164
188
  return res.json();
@@ -175,6 +199,9 @@ export const use${capitalize(opId)} = (
175
199
  function capitalize(str) {
176
200
  return str.charAt(0).toUpperCase() + str.slice(1);
177
201
  }
202
+ function toSafeIdentifier(id) {
203
+ return id.replace(/[^a-zA-Z0-9_-]/g, "").replace(/[-_]+(.?)/g, (_, c) => c ? c.toUpperCase() : "");
204
+ }
178
205
  async function parseArgs() {
179
206
  const args = process.argv.slice(2);
180
207
  const config = {
package/package.json CHANGED
@@ -1,27 +1,31 @@
1
1
  {
2
2
  "name": "elysia-openapi-codegen",
3
- "module": "index.ts",
4
- "main": "dist/index.js",
5
- "types": "dist/index.d.ts",
6
- "type": "module",
7
- "version": "0.1.4",
8
- "description": "Generate React Query hooks and fully typed TypeScript interfaces from Elysia OpenAPI specs.",
9
- "scripts": {
10
- "build": "bun build --target=node ./src/index.ts --outfile=dist/index.js && bun run build:declaration",
11
- "build:declaration": "tsc --emitDeclarationOnly --project tsconfig.types.json",
12
- "postbuild": "rimraf tsconfig.types.tsbuildinfo"
3
+ "version": "0.1.6",
4
+ "author": "Khantamir mkhantamir77@gmail.com",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/mkhantamir/elysia-openapi-codegen.git"
13
8
  },
9
+ "main": "dist/index.js",
10
+ "module": "index.ts",
14
11
  "devDependencies": {
15
- "@types/bun": "latest",
16
- "typescript": "^5"
12
+ "@types/bun": "latest"
17
13
  },
18
14
  "peerDependencies": {
19
- "typescript": "^5"
15
+ "typescript": "^6.0.2"
20
16
  },
21
- "repository": {
22
- "type": "git",
23
- "url": "https://github.com/mkhantamir/elysia-openapi-codegen.git"
17
+ "bin": {
18
+ "elysia-codegen": "./dist/index.js"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/mkhantamir/elysia-openapi-codegen/issues"
24
22
  },
23
+ "description": "Generate React Query hooks and fully typed TypeScript interfaces from Elysia OpenAPI specs.",
24
+ "files": [
25
+ "dist",
26
+ "README.md"
27
+ ],
28
+ "homepage": "https://github.com/mkhantamir/elysia-openapi-codegen#readme",
25
29
  "keywords": [
26
30
  "elysia",
27
31
  "openapi",
@@ -30,11 +34,9 @@
30
34
  "react-query",
31
35
  "tanstack-query"
32
36
  ],
33
- "author": "Khantamir",
34
37
  "license": "MIT",
35
- "files": [
36
- "dist/*.js",
37
- "dist/*.d.ts"
38
- ],
39
- "homepage": "https://github.com/mkhantamir/elysia-openapi-codegen#readme"
40
- }
38
+ "scripts": {
39
+ "build": "bun build index.ts --outfile dist/index.js --target node"
40
+ },
41
+ "type": "module"
42
+ }
package/dist/index.d.ts DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Elysia OpenAPI Code Generator
4
- * Generates typed React Query hooks from OpenAPI specifications.
5
- */
6
- export {};
package/dist/types.d.ts DELETED
@@ -1,70 +0,0 @@
1
- export interface OpenAPISpec {
2
- openapi: string;
3
- info: {
4
- title: string;
5
- version: string;
6
- [key: string]: unknown;
7
- };
8
- servers?: Array<{
9
- url: string;
10
- description?: string;
11
- }>;
12
- paths: Record<string, OpenAPIPathItem>;
13
- components?: {
14
- schemas?: Record<string, OpenAPISchema>;
15
- [key: string]: unknown;
16
- };
17
- [key: string]: unknown;
18
- }
19
- export interface OpenAPIPathItem {
20
- get?: OpenAPIOperation;
21
- post?: OpenAPIOperation;
22
- put?: OpenAPIOperation;
23
- delete?: OpenAPIOperation;
24
- patch?: OpenAPIOperation;
25
- [key: string]: unknown;
26
- }
27
- export interface OpenAPIOperation {
28
- operationId?: string;
29
- summary?: string;
30
- description?: string;
31
- parameters?: OpenAPIParameter[];
32
- responses?: Record<string, OpenAPIResponse>;
33
- requestBody?: {
34
- content: {
35
- [contentType: string]: {
36
- schema: OpenAPISchema;
37
- };
38
- };
39
- };
40
- [key: string]: unknown;
41
- }
42
- export interface OpenAPIParameter {
43
- name: string;
44
- in: 'query' | 'header' | 'path' | 'cookie';
45
- description?: string;
46
- required?: boolean;
47
- schema?: OpenAPISchema;
48
- [key: string]: unknown;
49
- }
50
- export interface OpenAPIResponse {
51
- description: string;
52
- content?: {
53
- [contentType: string]: {
54
- schema: OpenAPISchema;
55
- };
56
- };
57
- [key: string]: unknown;
58
- }
59
- export interface OpenAPISchema {
60
- type?: string;
61
- items?: OpenAPISchema;
62
- properties?: Record<string, OpenAPISchema>;
63
- required?: string[];
64
- $ref?: string;
65
- nullable?: boolean;
66
- anyOf?: OpenAPISchema[];
67
- oneOf?: OpenAPISchema[];
68
- allOf?: OpenAPISchema[];
69
- [key: string]: unknown;
70
- }