@automatons/typescript-server-nextjs 1.0.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.
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/index.d.ts +5 -0
- package/index.js +412 -0
- package/index.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) tanmen (yt.prog@gmail.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @automatons/typescript-server-nextjs
|
|
2
|
+
|
|
3
|
+
[](https://github.com/openapi-automatons/typescript-server-nextjs/actions/workflows/ci-cd.yml)
|
|
4
|
+
[](https://github.com/semantic-release/semantic-release)
|
|
5
|
+
[](https://www.npmjs.com/package/@automatons/typescript-server-nextjs)
|
|
6
|
+
|
|
7
|
+
## What is @automatons/typescript-server-nextjs
|
|
8
|
+
|
|
9
|
+
A contract-first Next.js (App Router) server generator for [openapi-automatons](https://github.com/openapi-automatons/openapi-automatons).
|
|
10
|
+
|
|
11
|
+
From an OpenAPI document it generates:
|
|
12
|
+
|
|
13
|
+
- `models/` — plain TypeScript types for every schema
|
|
14
|
+
- `services/` — one interface per tag: the contract you implement (your logic lives outside the generated code, so regenerating is always safe)
|
|
15
|
+
- `handlers/` — one factory per OpenAPI path building the [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) object (`{GET, POST, ...}`). Handlers await `context.params`, read and coerce query parameters (answering `400` when a required one is missing), parse the JSON body, delegate to your service, and answer with the right status (`200`, `201` for `POST`, `204` without a response schema)
|
|
16
|
+
|
|
17
|
+
This package is **ESM-only** and requires **Node.js >= 22**. Peer dependency: `next` **15 or 16**.
|
|
18
|
+
|
|
19
|
+
## Generated server
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// your code — implement the generated contract
|
|
23
|
+
import { PetsService } from "@/generated/services";
|
|
24
|
+
import { Pet } from "@/generated/models";
|
|
25
|
+
|
|
26
|
+
export const petsService: PetsService = {
|
|
27
|
+
async listPets(status, limit, tags, deep): Promise<Pet[]> {
|
|
28
|
+
// ...your logic
|
|
29
|
+
},
|
|
30
|
+
// ...
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// app/pets/[petId]/route.ts — mount the generated handlers
|
|
34
|
+
import { createPetsPetIdHandlers } from "@/generated/handlers";
|
|
35
|
+
import { petsService } from "@/services/pets";
|
|
36
|
+
|
|
37
|
+
export const { GET, DELETE } = createPetsPetIdHandlers(petsService);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Every handler file documents the `app/.../route.ts` location it belongs to.
|
|
41
|
+
|
|
42
|
+
Not covered (yet): header/cookie parameters and non-JSON request bodies.
|
|
43
|
+
|
|
44
|
+
## How can I use @automatons/typescript-server-nextjs?
|
|
45
|
+
|
|
46
|
+
Only use openapi-automatons.
|
|
47
|
+
|
|
48
|
+
This library is designed to be used by [openapi-automatons](https://github.com/openapi-automatons/openapi-automatons).
|
|
49
|
+
Please read the [readme](https://github.com/openapi-automatons/openapi-automatons#readme) of openapi-automatons for how to use it.
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
// src/generator/generate.ts
|
|
2
|
+
import { parser } from "@automatons/parser";
|
|
3
|
+
|
|
4
|
+
// src/generator/render.ts
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
7
|
+
import { IndentationText, Project, QuoteKind } from "ts-morph";
|
|
8
|
+
import { format } from "prettier";
|
|
9
|
+
var project = new Project({
|
|
10
|
+
useInMemoryFileSystem: true,
|
|
11
|
+
manipulationSettings: {
|
|
12
|
+
quoteKind: QuoteKind.Double,
|
|
13
|
+
indentationText: IndentationText.TwoSpaces,
|
|
14
|
+
useTrailingCommas: true
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
var counter = 0;
|
|
18
|
+
var render = (build) => {
|
|
19
|
+
const sf = project.createSourceFile(`__gen_${counter++}.ts`, "", { overwrite: true });
|
|
20
|
+
build(sf);
|
|
21
|
+
const text = sf.getFullText();
|
|
22
|
+
project.removeSourceFile(sf);
|
|
23
|
+
return text;
|
|
24
|
+
};
|
|
25
|
+
var write = async (outDir, relPath, text) => {
|
|
26
|
+
const outputPath = path.resolve(outDir, relPath);
|
|
27
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
28
|
+
const formatted = await format(text, { parser: "typescript" });
|
|
29
|
+
await writeFile(outputPath, formatted, { encoding: "utf-8" });
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/generator/schema.ts
|
|
33
|
+
var quoteKey = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
34
|
+
var nullable = (schema) => schema.nullable ? " | null" : "";
|
|
35
|
+
var schemaToType = (schema) => {
|
|
36
|
+
switch (schema.type) {
|
|
37
|
+
case "model":
|
|
38
|
+
return `${schema.name}${nullable(schema)}`;
|
|
39
|
+
case "object": {
|
|
40
|
+
if (schema.properties && schema.properties.length) {
|
|
41
|
+
const props = schema.properties.map(
|
|
42
|
+
(property) => `${quoteKey(property.name)}${property.required ? "" : "?"}: ${schemaToType(property.schema)};`
|
|
43
|
+
).join("\n");
|
|
44
|
+
return `{
|
|
45
|
+
${props}
|
|
46
|
+
}${nullable(schema)}`;
|
|
47
|
+
}
|
|
48
|
+
return `Record<string, unknown>${nullable(schema)}`;
|
|
49
|
+
}
|
|
50
|
+
case "allOf":
|
|
51
|
+
return `(${schema.schemas.map(schemaToType).join(" & ")})${nullable(schema)}`;
|
|
52
|
+
case "oneOf":
|
|
53
|
+
case "anyOf":
|
|
54
|
+
return `(${schema.schemas.map(schemaToType).join(" | ")})${nullable(schema)}`;
|
|
55
|
+
case "array":
|
|
56
|
+
return `${schema.items ? `Array<${schemaToType(schema.items)}>` : "any[]"}${nullable(schema)}`;
|
|
57
|
+
case "boolean":
|
|
58
|
+
return `boolean${nullable(schema)}`;
|
|
59
|
+
case "string": {
|
|
60
|
+
const base = schema.enum && schema.enum.length ? schema.enum.map((value) => `"${value}"`).join(" | ") : schema.format === "date" || schema.format === "date-time" ? "Date" : schema.format === "url" ? "URL" : "string";
|
|
61
|
+
return `${base}${nullable(schema)}`;
|
|
62
|
+
}
|
|
63
|
+
case "integer":
|
|
64
|
+
case "number": {
|
|
65
|
+
const base = schema.enum && schema.enum.length ? schema.enum.join(" | ") : "number";
|
|
66
|
+
return `${base}${nullable(schema)}`;
|
|
67
|
+
}
|
|
68
|
+
default:
|
|
69
|
+
throw new Error(`Unsupported schema type: ${schema.type}`);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/generator/model.ts
|
|
74
|
+
var emitModel = (model) => render((sf) => {
|
|
75
|
+
model.imports.forEach(
|
|
76
|
+
(imported) => sf.addImportDeclaration({
|
|
77
|
+
isTypeOnly: true,
|
|
78
|
+
namedImports: [imported.title],
|
|
79
|
+
moduleSpecifier: `./${imported.filename}`
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
sf.addTypeAlias({
|
|
83
|
+
isExported: true,
|
|
84
|
+
name: model.title,
|
|
85
|
+
type: schemaToType(model.schema)
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
var emitModelsIndex = (models) => render((sf) => models.forEach((model) => sf.addExportDeclaration({ moduleSpecifier: `./${model.filename}` })));
|
|
89
|
+
|
|
90
|
+
// src/generator/meta.ts
|
|
91
|
+
var baseName = (api) => api.title.replace(/Api$/, "");
|
|
92
|
+
var baseFilename = (api) => api.filename.replace(/Api$/, "");
|
|
93
|
+
var upperFirst = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
94
|
+
var lowerFirst = (value) => value.charAt(0).toLowerCase() + value.slice(1);
|
|
95
|
+
var serviceKey = (api) => lowerFirst(baseName(api));
|
|
96
|
+
var identifier = (name) => name.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
97
|
+
var isAffect = (path2) => "forms" in path2;
|
|
98
|
+
var pascalSegment = (segment) => segment.replace(/[{}]/g, "").split(/[^A-Za-z0-9]+/).filter(Boolean).map(upperFirst).join("");
|
|
99
|
+
var routeName = (routePath) => routePath.split("/").map(pascalSegment).join("") || "Root";
|
|
100
|
+
var appRoutePath = (routePath) => {
|
|
101
|
+
const segments = routePath.split("/").filter(Boolean).map((segment) => segment.replace(/\{([^}]+)}/g, "[$1]"));
|
|
102
|
+
return ["app", ...segments, "route.ts"].join("/");
|
|
103
|
+
};
|
|
104
|
+
var groupRoutes = (apis) => {
|
|
105
|
+
const routes = /* @__PURE__ */ new Map();
|
|
106
|
+
apis.forEach(
|
|
107
|
+
(api) => api.paths.forEach((path2) => {
|
|
108
|
+
const operations = routes.get(path2.path) ?? [];
|
|
109
|
+
operations.push({ api, path: path2 });
|
|
110
|
+
routes.set(path2.path, operations);
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
return [...routes.entries()].map(([path2, operations]) => ({ path: path2, operations }));
|
|
114
|
+
};
|
|
115
|
+
var routeApis = (route) => {
|
|
116
|
+
const apis = [];
|
|
117
|
+
route.operations.forEach(({ api }) => {
|
|
118
|
+
if (!apis.includes(api)) apis.push(api);
|
|
119
|
+
});
|
|
120
|
+
return apis;
|
|
121
|
+
};
|
|
122
|
+
var endpointParams = (path2) => {
|
|
123
|
+
const params = [];
|
|
124
|
+
(path2.parameters ?? []).forEach(
|
|
125
|
+
(parameter) => params.push({
|
|
126
|
+
kind: "path",
|
|
127
|
+
id: identifier(parameter.name),
|
|
128
|
+
name: parameter.name,
|
|
129
|
+
type: schemaToType(parameter.schema),
|
|
130
|
+
optional: false
|
|
131
|
+
})
|
|
132
|
+
);
|
|
133
|
+
const forms = isAffect(path2) ? path2.forms ?? [] : [];
|
|
134
|
+
if (forms.length) {
|
|
135
|
+
params.push({
|
|
136
|
+
kind: "body",
|
|
137
|
+
id: "body",
|
|
138
|
+
name: "body",
|
|
139
|
+
type: forms.map((form) => schemaToType(form.schema)).join(" | "),
|
|
140
|
+
optional: !forms.some((form) => form.required)
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
(path2.queries ?? []).forEach(
|
|
144
|
+
(query) => params.push({
|
|
145
|
+
kind: "query",
|
|
146
|
+
id: identifier(query.name),
|
|
147
|
+
name: query.name,
|
|
148
|
+
type: schemaToType(query.schema),
|
|
149
|
+
optional: !query.required
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
return params.sort((a, b) => Number(a.optional) - Number(b.optional));
|
|
153
|
+
};
|
|
154
|
+
var responseType = (path2) => path2.schema ? schemaToType(path2.schema) : "void";
|
|
155
|
+
var successStatus = (path2) => path2.schema ? path2.method.toLowerCase() === "post" ? 201 : 200 : 204;
|
|
156
|
+
var collectFromSchema = (schema, names) => {
|
|
157
|
+
if (!schema) return;
|
|
158
|
+
switch (schema.type) {
|
|
159
|
+
case "model":
|
|
160
|
+
names.add(schema.name);
|
|
161
|
+
break;
|
|
162
|
+
case "array":
|
|
163
|
+
collectFromSchema(schema.items, names);
|
|
164
|
+
break;
|
|
165
|
+
case "object":
|
|
166
|
+
(schema.properties ?? []).forEach((property) => collectFromSchema(property.schema, names));
|
|
167
|
+
break;
|
|
168
|
+
case "allOf":
|
|
169
|
+
case "oneOf":
|
|
170
|
+
case "anyOf":
|
|
171
|
+
schema.schemas.forEach((inner) => collectFromSchema(inner, names));
|
|
172
|
+
break;
|
|
173
|
+
default:
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var referencedModels = (api) => {
|
|
178
|
+
const names = /* @__PURE__ */ new Set();
|
|
179
|
+
api.paths.forEach((path2) => {
|
|
180
|
+
(path2.parameters ?? []).forEach((parameter) => collectFromSchema(parameter.schema, names));
|
|
181
|
+
(path2.queries ?? []).forEach((query) => collectFromSchema(query.schema, names));
|
|
182
|
+
if (isAffect(path2)) (path2.forms ?? []).forEach((form) => collectFromSchema(form.schema, names));
|
|
183
|
+
collectFromSchema(path2.schema, names);
|
|
184
|
+
});
|
|
185
|
+
return api.imports.filter((model) => names.has(model.title));
|
|
186
|
+
};
|
|
187
|
+
var routeModels = (route) => {
|
|
188
|
+
const names = /* @__PURE__ */ new Set();
|
|
189
|
+
route.operations.forEach(({ path: path2 }) => {
|
|
190
|
+
(path2.parameters ?? []).forEach((parameter) => collectFromSchema(parameter.schema, names));
|
|
191
|
+
(path2.queries ?? []).forEach((query) => collectFromSchema(query.schema, names));
|
|
192
|
+
if (isAffect(path2)) (path2.forms ?? []).forEach((form) => collectFromSchema(form.schema, names));
|
|
193
|
+
});
|
|
194
|
+
const seen = /* @__PURE__ */ new Set();
|
|
195
|
+
const models = [];
|
|
196
|
+
routeApis(route).forEach(
|
|
197
|
+
(api) => api.imports.forEach((model) => {
|
|
198
|
+
if (names.has(model.title) && !seen.has(model.title)) {
|
|
199
|
+
seen.add(model.title);
|
|
200
|
+
models.push(model);
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
return models;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/generator/service.ts
|
|
208
|
+
var emitService = (api) => render((sf) => {
|
|
209
|
+
const models = referencedModels(api);
|
|
210
|
+
if (models.length) {
|
|
211
|
+
sf.addImportDeclaration({
|
|
212
|
+
isTypeOnly: true,
|
|
213
|
+
namedImports: models.map((model) => model.title),
|
|
214
|
+
moduleSpecifier: "../models"
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const serviceInterface = sf.addInterface({ isExported: true, name: `${baseName(api)}Service` });
|
|
218
|
+
api.paths.forEach(
|
|
219
|
+
(path2) => serviceInterface.addMethod({
|
|
220
|
+
name: path2.name,
|
|
221
|
+
parameters: endpointParams(path2).map((param) => ({
|
|
222
|
+
name: param.id,
|
|
223
|
+
type: param.type,
|
|
224
|
+
hasQuestionToken: param.optional
|
|
225
|
+
})),
|
|
226
|
+
returnType: `Promise<${responseType(path2)}>`
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
var emitServicesIndex = (apis) => render((sf) => apis.forEach((api) => sf.addExportDeclaration({ moduleSpecifier: `./${baseFilename(api)}.service` })));
|
|
231
|
+
|
|
232
|
+
// src/generator/handler.ts
|
|
233
|
+
import { VariableDeclarationKind } from "ts-morph";
|
|
234
|
+
var convertExpr = (schema, raw) => {
|
|
235
|
+
switch (schema.type) {
|
|
236
|
+
case "string":
|
|
237
|
+
if (schema.enum && schema.enum.length) return `${raw} as ${schemaToType(schema)}`;
|
|
238
|
+
if (schema.format === "date" || schema.format === "date-time") return `new Date(${raw})`;
|
|
239
|
+
if (schema.format === "url") return `new URL(${raw})`;
|
|
240
|
+
return null;
|
|
241
|
+
case "integer":
|
|
242
|
+
case "number":
|
|
243
|
+
return schema.enum && schema.enum.length ? `Number(${raw}) as ${schemaToType(schema)}` : `Number(${raw})`;
|
|
244
|
+
case "boolean":
|
|
245
|
+
return `${raw} === "true"`;
|
|
246
|
+
default:
|
|
247
|
+
return `${raw} as unknown as ${schemaToType(schema)}`;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var convertArrayExpr = (items, raw) => {
|
|
251
|
+
if (!items) return raw;
|
|
252
|
+
switch (items.type) {
|
|
253
|
+
case "integer":
|
|
254
|
+
case "number":
|
|
255
|
+
return items.enum && items.enum.length ? `${raw}.map(Number) as Array<${schemaToType(items)}>` : `${raw}.map(Number)`;
|
|
256
|
+
case "boolean":
|
|
257
|
+
return `${raw}.map((value) => value === "true")`;
|
|
258
|
+
case "string":
|
|
259
|
+
if (items.enum && items.enum.length) return `${raw} as Array<${schemaToType(items)}>`;
|
|
260
|
+
if (items.format === "date" || items.format === "date-time") return `${raw}.map((value) => new Date(value))`;
|
|
261
|
+
if (items.format === "url") return `${raw}.map((value) => new URL(value))`;
|
|
262
|
+
return raw;
|
|
263
|
+
default:
|
|
264
|
+
return `${raw} as unknown as Array<${schemaToType(items)}>`;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
var needsContext = (path2) => (path2.parameters ?? []).length > 0;
|
|
268
|
+
var usesRequest = (path2) => (path2.queries ?? []).length > 0 || isAffect(path2) && (path2.forms ?? []).length > 0;
|
|
269
|
+
var pathParamStatements = (parameters) => {
|
|
270
|
+
const elements = parameters.map((parameter) => {
|
|
271
|
+
const id = identifier(parameter.name);
|
|
272
|
+
const target = convertExpr(parameter.schema, "raw") === null ? id : `${id}Raw`;
|
|
273
|
+
return parameter.name === target ? parameter.name : `${quoteKey(parameter.name)}: ${target}`;
|
|
274
|
+
});
|
|
275
|
+
const statements = [`const {${elements.join(", ")}} = await context.params;`];
|
|
276
|
+
parameters.forEach((parameter) => {
|
|
277
|
+
const id = identifier(parameter.name);
|
|
278
|
+
const converted = convertExpr(parameter.schema, `${id}Raw`);
|
|
279
|
+
if (converted !== null) statements.push(`const ${id} = ${converted};`);
|
|
280
|
+
});
|
|
281
|
+
return statements;
|
|
282
|
+
};
|
|
283
|
+
var queryStatements = (query) => {
|
|
284
|
+
const id = identifier(query.name);
|
|
285
|
+
if (query.schema.type === "array") {
|
|
286
|
+
const getAll = `request.nextUrl.searchParams.getAll(${JSON.stringify(query.name)})`;
|
|
287
|
+
return [`const ${id} = ${convertArrayExpr(query.schema.items, getAll)};`];
|
|
288
|
+
}
|
|
289
|
+
const get = `request.nextUrl.searchParams.get(${JSON.stringify(query.name)})`;
|
|
290
|
+
const missing = `return NextResponse.json({message: ${JSON.stringify(
|
|
291
|
+
`Missing required query parameter: ${query.name}`
|
|
292
|
+
)}}, {status: 400});`;
|
|
293
|
+
const converted = convertExpr(query.schema, `${id}Raw`);
|
|
294
|
+
if (converted === null) {
|
|
295
|
+
if (query.required) {
|
|
296
|
+
return [`const ${id} = ${get};`, `if (${id} === null) {`, missing, `}`];
|
|
297
|
+
}
|
|
298
|
+
return [`const ${id} = ${get} ?? undefined;`];
|
|
299
|
+
}
|
|
300
|
+
if (query.required) {
|
|
301
|
+
return [`const ${id}Raw = ${get};`, `if (${id}Raw === null) {`, missing, `}`, `const ${id} = ${converted};`];
|
|
302
|
+
}
|
|
303
|
+
return [`const ${id}Raw = ${get};`, `const ${id} = ${id}Raw === null ? undefined : ${converted};`];
|
|
304
|
+
};
|
|
305
|
+
var bodyStatement = (forms) => {
|
|
306
|
+
const type = forms.map((form) => schemaToType(form.schema)).join(" | ");
|
|
307
|
+
return forms.some((form) => form.required) ? `const body = (await request.json()) as ${type};` : `const body = (await request.json().catch(() => undefined)) as ${type} | undefined;`;
|
|
308
|
+
};
|
|
309
|
+
var operationText = (path2, serviceExpr) => {
|
|
310
|
+
const parameters = path2.parameters ?? [];
|
|
311
|
+
const queries = path2.queries ?? [];
|
|
312
|
+
const forms = isAffect(path2) ? path2.forms ?? [] : [];
|
|
313
|
+
const statements = [];
|
|
314
|
+
if (parameters.length) statements.push(...pathParamStatements(parameters));
|
|
315
|
+
queries.forEach((query) => statements.push(...queryStatements(query)));
|
|
316
|
+
if (forms.length) statements.push(bodyStatement(forms));
|
|
317
|
+
const args = endpointParams(path2).map((param) => param.id).join(", ");
|
|
318
|
+
if (path2.schema) {
|
|
319
|
+
statements.push(`const result = await ${serviceExpr}.${path2.name}(${args});`);
|
|
320
|
+
statements.push(`return NextResponse.json(result, {status: ${successStatus(path2)}});`);
|
|
321
|
+
} else {
|
|
322
|
+
statements.push(`await ${serviceExpr}.${path2.name}(${args});`);
|
|
323
|
+
statements.push(`return new NextResponse(null, {status: 204});`);
|
|
324
|
+
}
|
|
325
|
+
const signature = needsContext(path2) ? `async (${usesRequest(path2) ? "request" : "_request"}: NextRequest, context: {params: Promise<{${parameters.map((parameter) => `${quoteKey(parameter.name)}: string`).join("; ")}}>}): Promise<NextResponse>` : usesRequest(path2) ? `async (request: NextRequest): Promise<NextResponse>` : `async (): Promise<NextResponse>`;
|
|
326
|
+
return `${path2.method.toUpperCase()}: ${signature} => {
|
|
327
|
+
${statements.join("\n")}
|
|
328
|
+
}`;
|
|
329
|
+
};
|
|
330
|
+
var emitHandler = (route, name) => render((sf) => {
|
|
331
|
+
const apis = routeApis(route);
|
|
332
|
+
const multi = apis.length > 1;
|
|
333
|
+
const anyNextRequest = route.operations.some(({ path: path2 }) => needsContext(path2) || usesRequest(path2));
|
|
334
|
+
sf.addImportDeclaration({
|
|
335
|
+
moduleSpecifier: "next/server",
|
|
336
|
+
namedImports: [...anyNextRequest ? [{ name: "NextRequest", isTypeOnly: true }] : [], { name: "NextResponse" }]
|
|
337
|
+
});
|
|
338
|
+
sf.addImportDeclaration({
|
|
339
|
+
isTypeOnly: true,
|
|
340
|
+
namedImports: apis.map((api) => `${baseName(api)}Service`),
|
|
341
|
+
moduleSpecifier: "../services"
|
|
342
|
+
});
|
|
343
|
+
const models = routeModels(route);
|
|
344
|
+
if (models.length) {
|
|
345
|
+
sf.addImportDeclaration({
|
|
346
|
+
isTypeOnly: true,
|
|
347
|
+
namedImports: models.map((model) => model.title),
|
|
348
|
+
moduleSpecifier: "../models"
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
const factoryParam = multi ? `services: {${apis.map((api) => `${serviceKey(api)}: ${baseName(api)}Service`).join("; ")}}` : `service: ${apis.map((api) => `${baseName(api)}Service`).join("")}`;
|
|
352
|
+
const operations = route.operations.map(
|
|
353
|
+
({ api, path: path2 }) => operationText(path2, multi ? `services.${serviceKey(api)}` : "service")
|
|
354
|
+
);
|
|
355
|
+
sf.addVariableStatement({
|
|
356
|
+
isExported: true,
|
|
357
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
358
|
+
docs: [`Route handlers for \`${route.path}\` \u2014 re-export them from \`${appRoutePath(route.path)}\`.`],
|
|
359
|
+
declarations: [
|
|
360
|
+
{
|
|
361
|
+
name: `create${name}Handlers`,
|
|
362
|
+
initializer: `(${factoryParam}) => ({
|
|
363
|
+
${operations.join(",\n")},
|
|
364
|
+
})`
|
|
365
|
+
}
|
|
366
|
+
]
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
var emitHandlersIndex = (filenames) => render((sf) => filenames.forEach((filename) => sf.addExportDeclaration({ moduleSpecifier: `./${filename}` })));
|
|
370
|
+
|
|
371
|
+
// src/generator/generate.ts
|
|
372
|
+
var emitIndex = (hasModels, hasApis) => render((sf) => {
|
|
373
|
+
if (hasModels) sf.addExportDeclaration({ moduleSpecifier: "./models" });
|
|
374
|
+
if (hasApis) {
|
|
375
|
+
sf.addExportDeclaration({ moduleSpecifier: "./services" });
|
|
376
|
+
sf.addExportDeclaration({ moduleSpecifier: "./handlers" });
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
var generate = async (openapi, settings) => {
|
|
380
|
+
const { outDir } = settings;
|
|
381
|
+
const { models, apis } = await parser(openapi, settings);
|
|
382
|
+
const tasks = [];
|
|
383
|
+
if (models.length) {
|
|
384
|
+
tasks.push(write(outDir, "models/index.ts", emitModelsIndex(models)));
|
|
385
|
+
models.forEach((model) => tasks.push(write(outDir, `models/${model.filename}.ts`, emitModel(model))));
|
|
386
|
+
}
|
|
387
|
+
if (apis.length) {
|
|
388
|
+
tasks.push(write(outDir, "services/index.ts", emitServicesIndex(apis)));
|
|
389
|
+
apis.forEach((api) => tasks.push(write(outDir, `services/${baseFilename(api)}.service.ts`, emitService(api))));
|
|
390
|
+
const names = /* @__PURE__ */ new Set();
|
|
391
|
+
const handlers = groupRoutes(apis).map((route) => {
|
|
392
|
+
let name = routeName(route.path);
|
|
393
|
+
for (let n = 2; names.has(name); n++) name = `${routeName(route.path)}${n}`;
|
|
394
|
+
names.add(name);
|
|
395
|
+
return { route, name };
|
|
396
|
+
});
|
|
397
|
+
tasks.push(write(outDir, "handlers/index.ts", emitHandlersIndex(handlers.map(({ name }) => lowerFirst(name)))));
|
|
398
|
+
handlers.forEach(
|
|
399
|
+
({ route, name }) => tasks.push(write(outDir, `handlers/${lowerFirst(name)}.ts`, emitHandler(route, name)))
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
tasks.push(write(outDir, "index.ts", emitIndex(models.length > 0, apis.length > 0)));
|
|
403
|
+
return Promise.all(tasks);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// src/index.ts
|
|
407
|
+
var generatorTypescriptNextjsServer = (openapi, settings) => generate(openapi, settings);
|
|
408
|
+
var index_default = generatorTypescriptNextjsServer;
|
|
409
|
+
export {
|
|
410
|
+
index_default as default
|
|
411
|
+
};
|
|
412
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generator/generate.ts","../src/generator/render.ts","../src/generator/schema.ts","../src/generator/model.ts","../src/generator/meta.ts","../src/generator/service.ts","../src/generator/handler.ts","../src/index.ts"],"sourcesContent":["import {AutomatonSettings, Openapi} from \"@automatons/tools\";\nimport {parser} from \"@automatons/parser\";\nimport {render, write} from \"./render\";\nimport {emitModel, emitModelsIndex} from \"./model\";\nimport {emitService, emitServicesIndex} from \"./service\";\nimport {emitHandler, emitHandlersIndex} from \"./handler\";\nimport {baseFilename, groupRoutes, lowerFirst, routeName} from \"./meta\";\n\nconst emitIndex = (hasModels: boolean, hasApis: boolean): string =>\n render((sf) => {\n if (hasModels) sf.addExportDeclaration({moduleSpecifier: \"./models\"});\n if (hasApis) {\n sf.addExportDeclaration({moduleSpecifier: \"./services\"});\n sf.addExportDeclaration({moduleSpecifier: \"./handlers\"});\n }\n });\n\nexport const generate = async (openapi: Openapi, settings: AutomatonSettings): Promise<void[]> => {\n const {outDir} = settings;\n const {models, apis} = await parser(openapi, settings);\n const tasks: Promise<void>[] = [];\n\n if (models.length) {\n tasks.push(write(outDir, \"models/index.ts\", emitModelsIndex(models)));\n models.forEach((model) => tasks.push(write(outDir, `models/${model.filename}.ts`, emitModel(model))));\n }\n\n if (apis.length) {\n tasks.push(write(outDir, \"services/index.ts\", emitServicesIndex(apis)));\n apis.forEach((api) => tasks.push(write(outDir, `services/${baseFilename(api)}.service.ts`, emitService(api))));\n\n // Distinct paths can collapse to the same name (`/pets-x` vs `/pets/x`); suffix duplicates.\n const names = new Set<string>();\n const handlers = groupRoutes(apis).map((route) => {\n let name = routeName(route.path);\n for (let n = 2; names.has(name); n++) name = `${routeName(route.path)}${n}`;\n names.add(name);\n return {route, name};\n });\n tasks.push(write(outDir, \"handlers/index.ts\", emitHandlersIndex(handlers.map(({name}) => lowerFirst(name)))));\n handlers.forEach(({route, name}) =>\n tasks.push(write(outDir, `handlers/${lowerFirst(name)}.ts`, emitHandler(route, name))),\n );\n }\n\n tasks.push(write(outDir, \"index.ts\", emitIndex(models.length > 0, apis.length > 0)));\n\n return Promise.all(tasks);\n};\n","import path from \"node:path\";\nimport {mkdir, writeFile} from \"node:fs/promises\";\nimport {IndentationText, Project, QuoteKind, SourceFile} from \"ts-morph\";\nimport {format} from \"prettier\";\n\nconst project = new Project({\n useInMemoryFileSystem: true,\n manipulationSettings: {\n quoteKind: QuoteKind.Double,\n indentationText: IndentationText.TwoSpaces,\n useTrailingCommas: true,\n },\n});\n\nlet counter = 0;\n\n/**\n * Build a TypeScript source string by manipulating a throwaway ts-morph SourceFile.\n */\nexport const render = (build: (sf: SourceFile) => void): string => {\n const sf = project.createSourceFile(`__gen_${counter++}.ts`, \"\", {overwrite: true});\n build(sf);\n const text = sf.getFullText();\n project.removeSourceFile(sf);\n return text;\n};\n\n/**\n * Format with prettier and write the file under outDir.\n */\nexport const write = async (outDir: string, relPath: string, text: string): Promise<void> => {\n const outputPath = path.resolve(outDir, relPath);\n await mkdir(path.dirname(outputPath), {recursive: true});\n const formatted = await format(text, {parser: \"typescript\"});\n await writeFile(outputPath, formatted, {encoding: \"utf-8\"});\n};\n","import {Schema} from \"@automatons/parser\";\n\n/** Quote an object key / destructuring key unless it is a valid identifier. */\nexport const quoteKey = (name: string): string =>\n /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);\n\nconst nullable = (schema: {nullable?: boolean}): string => (schema.nullable ? \" | null\" : \"\");\n\n/**\n * Render a parser Schema as a TypeScript type string. Model references resolve to\n * the generated model type names.\n */\nexport const schemaToType = (schema: Schema): string => {\n switch (schema.type) {\n case \"model\":\n return `${schema.name}${nullable(schema)}`;\n case \"object\": {\n if (schema.properties && schema.properties.length) {\n const props = schema.properties\n .map(\n (property) =>\n `${quoteKey(property.name)}${property.required ? \"\" : \"?\"}: ${schemaToType(property.schema)};`,\n )\n .join(\"\\n\");\n return `{\\n${props}\\n}${nullable(schema)}`;\n }\n return `Record<string, unknown>${nullable(schema)}`;\n }\n case \"allOf\":\n return `(${schema.schemas.map(schemaToType).join(\" & \")})${nullable(schema)}`;\n case \"oneOf\":\n case \"anyOf\":\n return `(${schema.schemas.map(schemaToType).join(\" | \")})${nullable(schema)}`;\n case \"array\":\n return `${schema.items ? `Array<${schemaToType(schema.items)}>` : \"any[]\"}${nullable(schema)}`;\n case \"boolean\":\n return `boolean${nullable(schema)}`;\n case \"string\": {\n const base =\n schema.enum && schema.enum.length\n ? schema.enum.map((value) => `\"${value}\"`).join(\" | \")\n : schema.format === \"date\" || schema.format === \"date-time\"\n ? \"Date\"\n : schema.format === \"url\"\n ? \"URL\"\n : \"string\";\n return `${base}${nullable(schema)}`;\n }\n case \"integer\":\n case \"number\": {\n const base = schema.enum && schema.enum.length ? schema.enum.join(\" | \") : \"number\";\n return `${base}${nullable(schema)}`;\n }\n default:\n throw new Error(`Unsupported schema type: ${(schema as {type: string}).type}`);\n }\n};\n","import {Model} from \"@automatons/parser\";\nimport {render} from \"./render\";\nimport {schemaToType} from \"./schema\";\n\n/**\n * Emit a single model file: imports for referenced models + an exported type alias.\n */\nexport const emitModel = (model: Model): string =>\n render((sf) => {\n model.imports.forEach((imported) =>\n sf.addImportDeclaration({\n isTypeOnly: true,\n namedImports: [imported.title],\n moduleSpecifier: `./${imported.filename}`,\n }),\n );\n sf.addTypeAlias({\n isExported: true,\n name: model.title,\n type: schemaToType(model.schema),\n });\n });\n\n/**\n * Emit models/index.ts re-exporting every model.\n */\nexport const emitModelsIndex = (models: Model[]): string =>\n render((sf) => models.forEach((model) => sf.addExportDeclaration({moduleSpecifier: `./${model.filename}`})));\n","import {AffectPath, Api, Model, Path, Schema} from \"@automatons/parser\";\nimport {quoteKey, schemaToType} from \"./schema\";\n\n/** `PetsApi` -> `Pets` (the service base name). */\nexport const baseName = (api: Api): string => api.title.replace(/Api$/, \"\");\n/** `petsApi` -> `pets` (the service file name). */\nexport const baseFilename = (api: Api): string => api.filename.replace(/Api$/, \"\");\n\nexport const upperFirst = (value: string): string => value.charAt(0).toUpperCase() + value.slice(1);\nexport const lowerFirst = (value: string): string => value.charAt(0).toLowerCase() + value.slice(1);\n\n/** `Pets` -> `pets` (the key of a service in a multi-service handler factory). */\nexport const serviceKey = (api: Api): string => lowerFirst(baseName(api));\n\n/** Make an OpenAPI parameter name safe to use as a JS identifier. */\nexport const identifier = (name: string): string => name.replace(/[^A-Za-z0-9_$]/g, \"_\");\n\nexport const isAffect = (path: Path): path is AffectPath => \"forms\" in path;\n\nconst pascalSegment = (segment: string): string =>\n segment\n .replace(/[{}]/g, \"\")\n .split(/[^A-Za-z0-9]+/)\n .filter(Boolean)\n .map(upperFirst)\n .join(\"\");\n\n/** `/pets/{petId}` -> `PetsPetId` (the base of the handler factory name). */\nexport const routeName = (routePath: string): string => routePath.split(\"/\").map(pascalSegment).join(\"\") || \"Root\";\n\n/** `/pets/{petId}` -> `app/pets/[petId]/route.ts` (where the user mounts the handlers). */\nexport const appRoutePath = (routePath: string): string => {\n const segments = routePath\n .split(\"/\")\n .filter(Boolean)\n .map((segment) => segment.replace(/\\{([^}]+)}/g, \"[$1]\"));\n return [\"app\", ...segments, \"route.ts\"].join(\"/\");\n};\n\nexport type RouteOperation = {api: Api; path: Path};\n/** All operations sharing one literal OpenAPI path (one Next.js route file). */\nexport type Route = {path: string; operations: RouteOperation[]};\n\n/** Group every operation of every api by its literal OpenAPI path. */\nexport const groupRoutes = (apis: Api[]): Route[] => {\n const routes = new Map<string, RouteOperation[]>();\n apis.forEach((api) =>\n api.paths.forEach((path) => {\n const operations = routes.get(path.path) ?? [];\n operations.push({api, path});\n routes.set(path.path, operations);\n }),\n );\n return [...routes.entries()].map(([path, operations]) => ({path, operations}));\n};\n\n/** The distinct apis serving a route, in first-appearance order. */\nexport const routeApis = (route: Route): Api[] => {\n const apis: Api[] = [];\n route.operations.forEach(({api}) => {\n if (!apis.includes(api)) apis.push(api);\n });\n return apis;\n};\n\nexport type EndpointParam = {\n kind: \"path\" | \"query\" | \"body\";\n /** JS identifier used in the service signature and the handler body. */\n id: string;\n /** Original OpenAPI parameter name. */\n name: string;\n type: string;\n optional: boolean;\n};\n\n/** Build the parameter list of an operation, required-first (path, body, then queries). */\nexport const endpointParams = (path: Path): EndpointParam[] => {\n const params: EndpointParam[] = [];\n\n (path.parameters ?? []).forEach((parameter) =>\n params.push({\n kind: \"path\",\n id: identifier(parameter.name),\n name: parameter.name,\n type: schemaToType(parameter.schema),\n optional: false,\n }),\n );\n\n const forms = isAffect(path) ? (path.forms ?? []) : [];\n if (forms.length) {\n params.push({\n kind: \"body\",\n id: \"body\",\n name: \"body\",\n type: forms.map((form) => schemaToType(form.schema)).join(\" | \"),\n optional: !forms.some((form) => form.required),\n });\n }\n\n (path.queries ?? []).forEach((query) =>\n params.push({\n kind: \"query\",\n id: identifier(query.name),\n name: query.name,\n type: schemaToType(query.schema),\n optional: !query.required,\n }),\n );\n\n return params.sort((a, b) => Number(a.optional) - Number(b.optional));\n};\n\n/** The response type of an operation (`void` when there is no body schema). */\nexport const responseType = (path: Path): string => (path.schema ? schemaToType(path.schema) : \"void\");\n\n/** The HTTP status the handler responds with (the parser exposes only the success schema). */\nexport const successStatus = (path: Path): number =>\n path.schema ? (path.method.toLowerCase() === \"post\" ? 201 : 200) : 204;\n\nconst collectFromSchema = (schema: Schema | undefined, names: Set<string>): void => {\n if (!schema) return;\n switch (schema.type) {\n case \"model\":\n names.add(schema.name);\n break;\n case \"array\":\n collectFromSchema(schema.items, names);\n break;\n case \"object\":\n (schema.properties ?? []).forEach((property) => collectFromSchema(property.schema, names));\n break;\n case \"allOf\":\n case \"oneOf\":\n case \"anyOf\":\n schema.schemas.forEach((inner) => collectFromSchema(inner, names));\n break;\n default:\n break;\n }\n};\n\n/** The models referenced by an api's operation signatures (imported by its service file). */\nexport const referencedModels = (api: Api): Model[] => {\n const names = new Set<string>();\n api.paths.forEach((path) => {\n (path.parameters ?? []).forEach((parameter) => collectFromSchema(parameter.schema, names));\n (path.queries ?? []).forEach((query) => collectFromSchema(query.schema, names));\n if (isAffect(path)) (path.forms ?? []).forEach((form) => collectFromSchema(form.schema, names));\n collectFromSchema(path.schema, names);\n });\n return api.imports.filter((model) => names.has(model.title));\n};\n\n/**\n * The models whose names appear in a route's handler code: request bodies and parameter\n * coercions only — response types never show up in handler text.\n */\nexport const routeModels = (route: Route): Model[] => {\n const names = new Set<string>();\n route.operations.forEach(({path}) => {\n (path.parameters ?? []).forEach((parameter) => collectFromSchema(parameter.schema, names));\n (path.queries ?? []).forEach((query) => collectFromSchema(query.schema, names));\n if (isAffect(path)) (path.forms ?? []).forEach((form) => collectFromSchema(form.schema, names));\n });\n const seen = new Set<string>();\n const models: Model[] = [];\n routeApis(route).forEach((api) =>\n api.imports.forEach((model) => {\n if (names.has(model.title) && !seen.has(model.title)) {\n seen.add(model.title);\n models.push(model);\n }\n }),\n );\n return models;\n};\n\nexport {quoteKey};\n","import {Api} from \"@automatons/parser\";\nimport {render} from \"./render\";\nimport {baseFilename, baseName, endpointParams, referencedModels, responseType} from \"./meta\";\n\n/**\n * Emit services/<tag>.service.ts: the interface declaring the contract the user must implement.\n * Regenerating this file never touches the user's concrete implementation.\n */\nexport const emitService = (api: Api): string =>\n render((sf) => {\n const models = referencedModels(api);\n if (models.length) {\n sf.addImportDeclaration({\n isTypeOnly: true,\n namedImports: models.map((model) => model.title),\n moduleSpecifier: \"../models\",\n });\n }\n\n const serviceInterface = sf.addInterface({isExported: true, name: `${baseName(api)}Service`});\n api.paths.forEach((path) =>\n serviceInterface.addMethod({\n name: path.name,\n parameters: endpointParams(path).map((param) => ({\n name: param.id,\n type: param.type,\n hasQuestionToken: param.optional,\n })),\n returnType: `Promise<${responseType(path)}>`,\n }),\n );\n });\n\n/**\n * Emit services/index.ts re-exporting every service.\n */\nexport const emitServicesIndex = (apis: Api[]): string =>\n render((sf) => apis.forEach((api) => sf.addExportDeclaration({moduleSpecifier: `./${baseFilename(api)}.service`})));\n","import {Form, Path, PathParameter, QueryParameter, Schema} from \"@automatons/parser\";\nimport {VariableDeclarationKind} from \"ts-morph\";\nimport {render} from \"./render\";\nimport {\n appRoutePath,\n baseName,\n endpointParams,\n identifier,\n isAffect,\n quoteKey,\n Route,\n routeApis,\n routeModels,\n serviceKey,\n successStatus,\n} from \"./meta\";\nimport {schemaToType} from \"./schema\";\n\n/**\n * Expression converting a raw path/query string to the declared schema type.\n * `null` means the raw string is already the right type.\n */\nconst convertExpr = (schema: Schema, raw: string): string | null => {\n switch (schema.type) {\n case \"string\":\n if (schema.enum && schema.enum.length) return `${raw} as ${schemaToType(schema)}`;\n if (schema.format === \"date\" || schema.format === \"date-time\") return `new Date(${raw})`;\n if (schema.format === \"url\") return `new URL(${raw})`;\n return null;\n case \"integer\":\n case \"number\":\n return schema.enum && schema.enum.length ? `Number(${raw}) as ${schemaToType(schema)}` : `Number(${raw})`;\n case \"boolean\":\n return `${raw} === \"true\"`;\n default:\n return `${raw} as unknown as ${schemaToType(schema)}`;\n }\n};\n\n/** Expression converting the `string[]` of `searchParams.getAll` to the declared item type. */\nconst convertArrayExpr = (items: Schema | undefined, raw: string): string => {\n if (!items) return raw;\n switch (items.type) {\n case \"integer\":\n case \"number\":\n return items.enum && items.enum.length\n ? `${raw}.map(Number) as Array<${schemaToType(items)}>`\n : `${raw}.map(Number)`;\n case \"boolean\":\n return `${raw}.map((value) => value === \"true\")`;\n case \"string\":\n if (items.enum && items.enum.length) return `${raw} as Array<${schemaToType(items)}>`;\n if (items.format === \"date\" || items.format === \"date-time\") return `${raw}.map((value) => new Date(value))`;\n if (items.format === \"url\") return `${raw}.map((value) => new URL(value))`;\n return raw;\n default:\n return `${raw} as unknown as Array<${schemaToType(items)}>`;\n }\n};\n\nconst needsContext = (path: Path): boolean => (path.parameters ?? []).length > 0;\nconst usesRequest = (path: Path): boolean =>\n (path.queries ?? []).length > 0 || (isAffect(path) && (path.forms ?? []).length > 0);\n\n/** `const {petId} = await context.params;` plus coercions for non-string params. */\nconst pathParamStatements = (parameters: PathParameter[]): string[] => {\n const elements = parameters.map((parameter) => {\n const id = identifier(parameter.name);\n const target = convertExpr(parameter.schema, \"raw\") === null ? id : `${id}Raw`;\n return parameter.name === target ? parameter.name : `${quoteKey(parameter.name)}: ${target}`;\n });\n const statements = [`const {${elements.join(\", \")}} = await context.params;`];\n parameters.forEach((parameter) => {\n const id = identifier(parameter.name);\n const converted = convertExpr(parameter.schema, `${id}Raw`);\n if (converted !== null) statements.push(`const ${id} = ${converted};`);\n });\n return statements;\n};\n\n/** Read one query parameter off `request.nextUrl.searchParams`, 400 when a required one is missing. */\nconst queryStatements = (query: QueryParameter): string[] => {\n const id = identifier(query.name);\n if (query.schema.type === \"array\") {\n const getAll = `request.nextUrl.searchParams.getAll(${JSON.stringify(query.name)})`;\n return [`const ${id} = ${convertArrayExpr(query.schema.items, getAll)};`];\n }\n\n const get = `request.nextUrl.searchParams.get(${JSON.stringify(query.name)})`;\n const missing = `return NextResponse.json({message: ${JSON.stringify(\n `Missing required query parameter: ${query.name}`,\n )}}, {status: 400});`;\n const converted = convertExpr(query.schema, `${id}Raw`);\n\n if (converted === null) {\n if (query.required) {\n return [`const ${id} = ${get};`, `if (${id} === null) {`, missing, `}`];\n }\n return [`const ${id} = ${get} ?? undefined;`];\n }\n if (query.required) {\n return [`const ${id}Raw = ${get};`, `if (${id}Raw === null) {`, missing, `}`, `const ${id} = ${converted};`];\n }\n return [`const ${id}Raw = ${get};`, `const ${id} = ${id}Raw === null ? undefined : ${converted};`];\n};\n\n/** Parse the JSON request body, cast to the declared form type(s). */\nconst bodyStatement = (forms: Form[]): string => {\n const type = forms.map((form) => schemaToType(form.schema)).join(\" | \");\n return forms.some((form) => form.required)\n ? `const body = (await request.json()) as ${type};`\n : `const body = (await request.json().catch(() => undefined)) as ${type} | undefined;`;\n};\n\n/** One `METHOD: async (...) => {...}` property of the handlers object. */\nconst operationText = (path: Path, serviceExpr: string): string => {\n const parameters = path.parameters ?? [];\n const queries = path.queries ?? [];\n const forms = isAffect(path) ? (path.forms ?? []) : [];\n\n const statements: string[] = [];\n if (parameters.length) statements.push(...pathParamStatements(parameters));\n queries.forEach((query) => statements.push(...queryStatements(query)));\n if (forms.length) statements.push(bodyStatement(forms));\n\n const args = endpointParams(path)\n .map((param) => param.id)\n .join(\", \");\n if (path.schema) {\n statements.push(`const result = await ${serviceExpr}.${path.name}(${args});`);\n statements.push(`return NextResponse.json(result, {status: ${successStatus(path)}});`);\n } else {\n statements.push(`await ${serviceExpr}.${path.name}(${args});`);\n statements.push(`return new NextResponse(null, {status: 204});`);\n }\n\n const signature = needsContext(path)\n ? `async (${usesRequest(path) ? \"request\" : \"_request\"}: NextRequest, context: {params: Promise<{${parameters\n .map((parameter) => `${quoteKey(parameter.name)}: string`)\n .join(\"; \")}}>}): Promise<NextResponse>`\n : usesRequest(path)\n ? `async (request: NextRequest): Promise<NextResponse>`\n : `async (): Promise<NextResponse>`;\n\n return `${path.method.toUpperCase()}: ${signature} => {\\n${statements.join(\"\\n\")}\\n}`;\n};\n\n/**\n * Emit handlers/<route>.ts: a factory building the route-handler object for one OpenAPI path.\n * The user re-exports the returned handlers from the matching app/.../route.ts file.\n */\nexport const emitHandler = (route: Route, name: string): string =>\n render((sf) => {\n const apis = routeApis(route);\n const multi = apis.length > 1;\n\n const anyNextRequest = route.operations.some(({path}) => needsContext(path) || usesRequest(path));\n sf.addImportDeclaration({\n moduleSpecifier: \"next/server\",\n namedImports: [...(anyNextRequest ? [{name: \"NextRequest\", isTypeOnly: true}] : []), {name: \"NextResponse\"}],\n });\n\n sf.addImportDeclaration({\n isTypeOnly: true,\n namedImports: apis.map((api) => `${baseName(api)}Service`),\n moduleSpecifier: \"../services\",\n });\n\n const models = routeModels(route);\n if (models.length) {\n sf.addImportDeclaration({\n isTypeOnly: true,\n namedImports: models.map((model) => model.title),\n moduleSpecifier: \"../models\",\n });\n }\n\n const factoryParam = multi\n ? `services: {${apis.map((api) => `${serviceKey(api)}: ${baseName(api)}Service`).join(\"; \")}}`\n : `service: ${apis.map((api) => `${baseName(api)}Service`).join(\"\")}`;\n\n const operations = route.operations.map(({api, path}) =>\n operationText(path, multi ? `services.${serviceKey(api)}` : \"service\"),\n );\n\n sf.addVariableStatement({\n isExported: true,\n declarationKind: VariableDeclarationKind.Const,\n docs: [`Route handlers for \\`${route.path}\\` — re-export them from \\`${appRoutePath(route.path)}\\`.`],\n declarations: [\n {\n name: `create${name}Handlers`,\n initializer: `(${factoryParam}) => ({\\n${operations.join(\",\\n\")},\\n})`,\n },\n ],\n });\n });\n\n/**\n * Emit handlers/index.ts re-exporting every handler factory.\n */\nexport const emitHandlersIndex = (filenames: string[]): string =>\n render((sf) => filenames.forEach((filename) => sf.addExportDeclaration({moduleSpecifier: `./${filename}`})));\n","import {Automaton} from \"@automatons/tools\";\nimport {generate} from \"./generator\";\n\nconst generatorTypescriptNextjsServer: Automaton = (openapi, settings) => generate(openapi, settings);\n\nexport default generatorTypescriptNextjsServer;\n"],"mappings":"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","names":["path","path","path"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@automatons/typescript-server-nextjs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"repository": "https://github.com/openapi-automatons/typescript-server-nextjs.git",
|
|
5
|
+
"author": "tanmen <yt.prog@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"packageManager": "yarn@1.22.22",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"openapi",
|
|
11
|
+
"openapi-automatons",
|
|
12
|
+
"automatons",
|
|
13
|
+
"nextjs",
|
|
14
|
+
"next",
|
|
15
|
+
"app-router",
|
|
16
|
+
"route-handlers",
|
|
17
|
+
"server"
|
|
18
|
+
],
|
|
19
|
+
"main": "index.js",
|
|
20
|
+
"module": "index.js",
|
|
21
|
+
"types": "index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./index.d.ts",
|
|
25
|
+
"import": "./index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"prebuild": "depcheck",
|
|
33
|
+
"build": "tsup && cp package.json README.md LICENSE dist",
|
|
34
|
+
"lint": "eslint src",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"test:coverage": "vitest run --coverage",
|
|
38
|
+
"prepare": "husky"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@automatons/parser": "^1.2.0",
|
|
42
|
+
"@automatons/tools": "^2.2.0",
|
|
43
|
+
"prettier": "^3.8.3",
|
|
44
|
+
"ts-morph": "^28.0.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"next": "^15 || ^16",
|
|
48
|
+
"openapi-automatons": "^2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@commitlint/cli": "^21.0.2",
|
|
52
|
+
"@commitlint/config-conventional": "^21.0.2",
|
|
53
|
+
"@eslint/js": "^10.0.1",
|
|
54
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
55
|
+
"@semantic-release/git": "^10.0.1",
|
|
56
|
+
"@types/node": "^24.0.0",
|
|
57
|
+
"@vitest/coverage-v8": "^3.2.0",
|
|
58
|
+
"depcheck": "^1.4.7",
|
|
59
|
+
"eslint": "^10.4.1",
|
|
60
|
+
"eslint-config-prettier": "^10.1.8",
|
|
61
|
+
"husky": "^9.1.7",
|
|
62
|
+
"lint-staged": "^17.0.7",
|
|
63
|
+
"next": "^16.0.0",
|
|
64
|
+
"semantic-release": "^25.0.3",
|
|
65
|
+
"tsup": "^8.5.0",
|
|
66
|
+
"typescript": "^6.0.3",
|
|
67
|
+
"typescript-eslint": "^8.46.0",
|
|
68
|
+
"vitest": "^3.2.0"
|
|
69
|
+
},
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"access": "public"
|
|
72
|
+
}
|
|
73
|
+
}
|