@rexeus/typeweaver 0.8.0 → 0.9.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/README.md +91 -227
- package/dist/cli-B-KPX-Jc.mjs +407 -0
- package/dist/cli.cjs +311 -4337
- package/dist/cli.mjs +289 -4311
- package/dist/cli.mjs.map +1 -1
- package/dist/entry.mjs +3 -5
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -2
- package/dist/{tsx-loader-DUG5if6z.mjs → tsx-loader-DtL8gLLb.mjs} +1 -2
- package/package.json +22 -21
- package/dist/cli-AH4H-B8Q.mjs +0 -4430
package/README.md
CHANGED
|
@@ -68,20 +68,20 @@ More plugins are planned. If you want to build your own, check out the plugin sy
|
|
|
68
68
|
|
|
69
69
|
## ⌨️ CLI
|
|
70
70
|
|
|
71
|
-
Generate TypeScript code from
|
|
71
|
+
Generate TypeScript code from a spec entrypoint file:
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
# Node.js (npm)
|
|
75
|
-
npx typeweaver generate --input ./api/
|
|
75
|
+
npx typeweaver generate --input ./api/spec/index.ts --output ./api/generated --plugins clients
|
|
76
76
|
|
|
77
77
|
# Node.js (pnpm)
|
|
78
|
-
pnpx typeweaver generate --input ./api/
|
|
78
|
+
pnpx typeweaver generate --input ./api/spec/index.ts --output ./api/generated --plugins clients
|
|
79
79
|
|
|
80
80
|
# Deno
|
|
81
|
-
deno run -A npm:@rexeus/typeweaver generate --input ./api/
|
|
81
|
+
deno run -A npm:@rexeus/typeweaver generate --input ./api/spec/index.ts --output ./api/generated --plugins clients
|
|
82
82
|
|
|
83
83
|
# Bun
|
|
84
|
-
bunx typeweaver generate --input ./api/
|
|
84
|
+
bunx typeweaver generate --input ./api/spec/index.ts --output ./api/generated --plugins clients
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
> **Note**: Deno may require the `--sloppy-imports` flag or equivalent configuration in `deno.json`
|
|
@@ -89,10 +89,8 @@ bunx typeweaver generate --input ./api/definition --output ./api/generated --plu
|
|
|
89
89
|
|
|
90
90
|
### ⚙️ Options
|
|
91
91
|
|
|
92
|
-
- `--input, -i <path>`:
|
|
92
|
+
- `--input, -i <path>`: Spec entrypoint file (required)
|
|
93
93
|
- `--output, -o <path>`: Output directory for generated code (required)
|
|
94
|
-
- `-s, --shared <path>`: Shared directory for reusable schemas (optional, defaults to
|
|
95
|
-
`<input-path>/shared`)
|
|
96
94
|
- `--config, -c <path>`: Configuration file path (optional)
|
|
97
95
|
- `--plugins, -p <plugins>`: Comma-separated list of plugins to use (e.g., "clients,hono" or "all"
|
|
98
96
|
for all plugins)
|
|
@@ -105,7 +103,7 @@ Create a config file (e.g. `typeweaver.config.js`) for more complex configuratio
|
|
|
105
103
|
|
|
106
104
|
```javascript
|
|
107
105
|
export default {
|
|
108
|
-
input: "./api/
|
|
106
|
+
input: "./api/spec/index.ts",
|
|
109
107
|
output: "./api/generated",
|
|
110
108
|
plugins: ["clients", "hono", "aws-cdk"],
|
|
111
109
|
format: true,
|
|
@@ -126,222 +124,103 @@ npx typeweaver generate --config ./typeweaver.config.js
|
|
|
126
124
|
|
|
127
125
|
### 📁 Project Structure
|
|
128
126
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- A response definition file must include one default export of a `HttpResponseDefinition` instance
|
|
145
|
-
(e.g. `export default new HttpResponseDefinition({...})`)
|
|
146
|
-
- Responses shared across operations are possible, but need to be placed in the `shared` directory.
|
|
147
|
-
- The shared directory can be specified using the `--shared` option, but must be located within
|
|
148
|
-
the input directory
|
|
149
|
-
- Default shared directory is `<input-path>/shared`
|
|
150
|
-
- The shared directory is suitable not only as a place for responses but also for shared schemas
|
|
151
|
-
|
|
152
|
-
As you can see, the structure of the input directory is essential. However, you are completely free
|
|
153
|
-
to choose the structure and nesting within resource directories.
|
|
154
|
-
|
|
155
|
-
**Important**: All definition files and their dependencies (like separate schemas etc.) must be
|
|
156
|
-
self-contained within the input directory. Generated code creates an immutable snapshot of your
|
|
157
|
-
definitions, so any external imports (relative imports outside the input directory) will not work.
|
|
158
|
-
NPM package imports continue to work normally.
|
|
159
|
-
|
|
160
|
-
```
|
|
161
|
-
api/definition/
|
|
162
|
-
├── user/ # Resource directory
|
|
163
|
-
│ ├── errors/ # Resource-specific error definitions
|
|
164
|
-
│ │ │ # -> Because they are inside a resource directory,
|
|
165
|
-
│ │ │ # they can only be used within this resource
|
|
166
|
-
│ │ └── UserNotFoundErrorDefinition.ts
|
|
167
|
-
│ │ └── UserStatusTransitionInvalidErrorDefinition.ts
|
|
168
|
-
│ ├── CreateUserDefinition.ts # Operation definitions
|
|
169
|
-
│ ├── GetUserDefinition.ts
|
|
170
|
-
│ ├── ListUserDefinition.ts
|
|
171
|
-
│ ├── UpdateUserDefinition.ts
|
|
172
|
-
│ └── userSchema.ts # Schema for the resource, can be reused across operations
|
|
173
|
-
├── post/
|
|
174
|
-
│ ├── errors/
|
|
175
|
-
│ ├── CreatePostDefinition.ts
|
|
176
|
-
│ ├── GetPostDefinition.ts
|
|
177
|
-
│ ├── ...
|
|
178
|
-
├── ...
|
|
179
|
-
└── shared/ # Shared responses and schemas
|
|
180
|
-
│ # -> While it doesn't matter where schemas are defined
|
|
181
|
-
│ # inside the input directory, responses can only be
|
|
182
|
-
│ # shared across resources if they are located in the
|
|
183
|
-
│ # shared directory
|
|
184
|
-
├── ConflictErrorDefinition.ts
|
|
185
|
-
├── ForbiddenErrorDefinition.ts
|
|
186
|
-
├── InternalServerErrorDefinition.ts
|
|
187
|
-
├── NotFoundErrorDefinition.ts # Like BaseApiErrors, can be extended to be resource-specific
|
|
188
|
-
├── TooManyRequestsErrorDefinition.ts
|
|
189
|
-
├── UnauthorizedErrorDefinition.ts
|
|
190
|
-
├── ValidationErrorDefinition.ts
|
|
191
|
-
└── sharedResponses.ts # Collection of responses relevant for every operation
|
|
127
|
+
Typeweaver reads a single spec entrypoint. Organize files however you want, then assemble the
|
|
128
|
+
resource map in `defineSpec(...)`. Here is an example layout:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
api/spec/
|
|
132
|
+
├── index.ts # Spec entrypoint — exports defineSpec(...)
|
|
133
|
+
├── user/
|
|
134
|
+
│ ├── index.ts # Barrel exports for the user resource
|
|
135
|
+
│ ├── userSchema.ts # Zod schemas for the user entity
|
|
136
|
+
│ ├── GetUserDefinition.ts # defineOperation(...) for GET /users/:userId
|
|
137
|
+
│ └── errors/
|
|
138
|
+
│ └── UserNotFoundErrorDefinition.ts
|
|
139
|
+
└── shared/
|
|
140
|
+
├── sharedResponses.ts # Array of common error responses
|
|
141
|
+
└── ValidationErrorDefinition.ts
|
|
192
142
|
```
|
|
193
143
|
|
|
194
|
-
|
|
144
|
+
This is just one way to organize your spec. The directory layout is up to you — typeweaver only
|
|
145
|
+
cares about the `defineSpec(...)` entrypoint, not about folder names or file conventions.
|
|
195
146
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
147
|
+
- Resource names come from `defineSpec({ resources: ... })`, not from directory names.
|
|
148
|
+
- Shared responses and schemas can live anywhere that your spec entrypoint imports from.
|
|
149
|
+
- The CLI bundles the entrypoint, so local spec imports should stay within your project.
|
|
199
150
|
|
|
200
|
-
|
|
201
|
-
export const userStatusSchema = z.enum(["ACTIVE", "INACTIVE", "SUSPENDED"]);
|
|
202
|
-
|
|
203
|
-
// General user schema, can be reused across operations
|
|
204
|
-
export const userSchema = z.object({
|
|
205
|
-
id: z.uuid(),
|
|
206
|
-
name: z.string(),
|
|
207
|
-
email: z.email(),
|
|
208
|
-
status: userStatusSchema,
|
|
209
|
-
createdAt: z.iso.date(),
|
|
210
|
-
updatedAt: z.iso.date(),
|
|
211
|
-
});
|
|
212
|
-
```
|
|
151
|
+
### 💻 Sample Spec
|
|
213
152
|
|
|
214
153
|
```typescript
|
|
215
|
-
// api/
|
|
216
|
-
import {
|
|
154
|
+
// api/spec/user/GetUserDefinition.ts
|
|
155
|
+
import {
|
|
156
|
+
defineOperation,
|
|
157
|
+
defineResponse,
|
|
158
|
+
HttpMethod,
|
|
159
|
+
HttpStatusCode,
|
|
160
|
+
} from "@rexeus/typeweaver-core";
|
|
217
161
|
import { z } from "zod";
|
|
218
162
|
import { sharedResponses } from "../shared/sharedResponses";
|
|
219
163
|
import { userSchema } from "./userSchema";
|
|
220
164
|
import UserNotFoundErrorDefinition from "./errors/UserNotFoundErrorDefinition";
|
|
221
165
|
|
|
222
|
-
export default
|
|
166
|
+
export default defineOperation({
|
|
223
167
|
operationId: "GetUser",
|
|
224
168
|
method: HttpMethod.GET,
|
|
225
169
|
path: "/users/:userId",
|
|
170
|
+
summary: "Get a user by id",
|
|
226
171
|
request: {
|
|
227
172
|
param: z.object({
|
|
228
173
|
userId: z.uuid(),
|
|
229
174
|
}),
|
|
230
175
|
},
|
|
231
176
|
responses: [
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// - generally also multiple success responses could be defined
|
|
235
|
-
// - in this case the "general" user schema is imported and used
|
|
236
|
-
{
|
|
177
|
+
defineResponse({
|
|
178
|
+
name: "GetUserSuccess",
|
|
237
179
|
statusCode: HttpStatusCode.OK,
|
|
238
180
|
description: "User successfully retrieved",
|
|
239
181
|
header: z.object({
|
|
240
182
|
"Content-Type": z.literal("application/json"),
|
|
241
183
|
}),
|
|
242
184
|
body: userSchema,
|
|
243
|
-
},
|
|
244
|
-
UserNotFoundErrorDefinition,
|
|
245
|
-
...sharedResponses,
|
|
185
|
+
}),
|
|
186
|
+
UserNotFoundErrorDefinition,
|
|
187
|
+
...sharedResponses,
|
|
246
188
|
],
|
|
247
189
|
});
|
|
248
190
|
```
|
|
249
191
|
|
|
250
192
|
```typescript
|
|
251
|
-
// api/
|
|
252
|
-
import {
|
|
253
|
-
import
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
export default new HttpOperationDefinition({
|
|
260
|
-
operationId: "UpdateUser",
|
|
261
|
-
method: HttpMethod.PATCH,
|
|
262
|
-
path: "/users/:userId",
|
|
263
|
-
request: {
|
|
264
|
-
param: z.object({
|
|
265
|
-
userId: z.uuid(),
|
|
266
|
-
}),
|
|
267
|
-
// general user schema is processed via zod's pick and partial methods
|
|
268
|
-
// to match the update operation's requirements
|
|
269
|
-
body: userSchema
|
|
270
|
-
.pick({
|
|
271
|
-
name: true,
|
|
272
|
-
email: true,
|
|
273
|
-
status: true,
|
|
274
|
-
})
|
|
275
|
-
.partial(),
|
|
276
|
-
},
|
|
277
|
-
responses: [
|
|
278
|
-
{
|
|
279
|
-
statusCode: HttpStatusCode.OK,
|
|
280
|
-
description: "User successfully updated",
|
|
281
|
-
header: z.object({
|
|
282
|
-
"Content-Type": z.literal("application/json"),
|
|
283
|
-
}),
|
|
284
|
-
body: userSchema,
|
|
193
|
+
// api/spec/index.ts
|
|
194
|
+
import { defineSpec } from "@rexeus/typeweaver-core";
|
|
195
|
+
import GetUserDefinition from "./user/GetUserDefinition";
|
|
196
|
+
|
|
197
|
+
export default defineSpec({
|
|
198
|
+
resources: {
|
|
199
|
+
user: {
|
|
200
|
+
operations: [GetUserDefinition],
|
|
285
201
|
},
|
|
286
|
-
|
|
287
|
-
UserStatusTransitionInvalidErrorDefinition, // Resource specific response
|
|
288
|
-
...sharedResponses, // Commonly used responses across all operations, e.g. 401, 403, 500...
|
|
289
|
-
],
|
|
202
|
+
},
|
|
290
203
|
});
|
|
291
204
|
```
|
|
292
205
|
|
|
293
206
|
```typescript
|
|
294
|
-
// api/
|
|
207
|
+
// api/spec/user/userSchema.ts
|
|
295
208
|
import { z } from "zod";
|
|
296
|
-
import { NotFoundErrorDefinition } from "../../shared";
|
|
297
|
-
|
|
298
|
-
// - uses the shared NotFoundErrorDefinition as "base" and extends it
|
|
299
|
-
// - adds a specific message and code for the user resource
|
|
300
|
-
export default NotFoundErrorDefinition.extend({
|
|
301
|
-
name: "UserNotFoundError",
|
|
302
|
-
description: "User not found",
|
|
303
|
-
body: z.object({
|
|
304
|
-
message: z.literal("User not found"),
|
|
305
|
-
code: z.literal("USER_NOT_FOUND_ERROR"),
|
|
306
|
-
actualValues: z.object({
|
|
307
|
-
userId: z.uuid(),
|
|
308
|
-
}),
|
|
309
|
-
}),
|
|
310
|
-
});
|
|
311
|
-
```
|
|
312
209
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// or in this case does not extend a BaseApiError and defines everything itself
|
|
323
|
-
export default new HttpResponseDefinition({
|
|
324
|
-
name: "UserStatusTransitionInvalidError",
|
|
325
|
-
description: "User status transition is conflicting with current status",
|
|
326
|
-
body: z.object({
|
|
327
|
-
message: z.literal("User status transition is conflicting with current status"),
|
|
328
|
-
code: z.literal("USER_STATUS_TRANSITION_INVALID_ERROR"),
|
|
329
|
-
context: z.object({
|
|
330
|
-
userId: z.uuid(),
|
|
331
|
-
currentStatus: userStatusSchema,
|
|
332
|
-
}),
|
|
333
|
-
actualValues: z.object({
|
|
334
|
-
requestedStatus: userStatusSchema,
|
|
335
|
-
}),
|
|
336
|
-
expectedValues: z.object({
|
|
337
|
-
allowedStatuses: z.array(userStatusSchema),
|
|
338
|
-
}),
|
|
339
|
-
}),
|
|
210
|
+
export const userStatusSchema = z.enum(["ACTIVE", "INACTIVE", "SUSPENDED"]);
|
|
211
|
+
|
|
212
|
+
export const userSchema = z.object({
|
|
213
|
+
id: z.uuid(),
|
|
214
|
+
name: z.string(),
|
|
215
|
+
email: z.email(),
|
|
216
|
+
status: userStatusSchema,
|
|
217
|
+
createdAt: z.iso.date(),
|
|
218
|
+
updatedAt: z.iso.date(),
|
|
340
219
|
});
|
|
341
220
|
```
|
|
342
221
|
|
|
343
222
|
```typescript
|
|
344
|
-
// api/
|
|
223
|
+
// api/spec/shared/sharedResponses.ts
|
|
345
224
|
import ForbiddenErrorDefinition from "./ForbiddenErrorDefinition";
|
|
346
225
|
import InternalServerErrorDefinition from "./InternalServerErrorDefinition";
|
|
347
226
|
import TooManyRequestsErrorDefinition from "./TooManyRequestsErrorDefinition";
|
|
@@ -349,8 +228,6 @@ import UnauthorizedErrorDefinition from "./UnauthorizedErrorDefinition";
|
|
|
349
228
|
import UnsupportedMediaTypeErrorDefinition from "./UnsupportedMediaTypeErrorDefinition";
|
|
350
229
|
import ValidationErrorDefinition from "./ValidationErrorDefinition";
|
|
351
230
|
|
|
352
|
-
// various error responses which are relevant for every operation
|
|
353
|
-
// can be spread in the responses array of an HttpOperationDefinition
|
|
354
231
|
export const sharedResponses = [
|
|
355
232
|
ForbiddenErrorDefinition,
|
|
356
233
|
InternalServerErrorDefinition,
|
|
@@ -367,31 +244,29 @@ export const sharedResponses = [
|
|
|
367
244
|
# Generate with plugins:
|
|
368
245
|
# - Hono: to easily provide a web server
|
|
369
246
|
# - Clients: to get fitting API clients
|
|
370
|
-
npx typeweaver generate --input ./api/
|
|
247
|
+
npx typeweaver generate --input ./api/spec/index.ts --output ./api/generated --plugins clients,hono
|
|
371
248
|
```
|
|
372
249
|
|
|
250
|
+
> The CLI accepts a default export, a named `spec` export, or the module namespace itself as the
|
|
251
|
+
> `SpecDefinition` entrypoint.
|
|
252
|
+
|
|
373
253
|
### 🌐 Create Hono web server
|
|
374
254
|
|
|
375
255
|
```typescript
|
|
376
256
|
// api/user-handlers.ts
|
|
377
|
-
import {
|
|
378
|
-
import {
|
|
379
|
-
|
|
380
|
-
type IGetUserRequest,
|
|
381
|
-
GetUserResponse,
|
|
382
|
-
GetUserSuccessResponse,
|
|
383
|
-
type ICreateUserRequest,
|
|
384
|
-
CreateUserResponse,
|
|
385
|
-
type IUpdateUserRequest,
|
|
386
|
-
UpdateUserResponse,
|
|
387
|
-
type IListUserRequest,
|
|
388
|
-
ListUserResponse,
|
|
389
|
-
} from "./generated";
|
|
257
|
+
import type { Context } from "hono";
|
|
258
|
+
import type { HonoUserApiHandler, IGetUserRequest, GetUserResponse } from "./generated";
|
|
259
|
+
import { createGetUserSuccessResponse } from "./generated";
|
|
390
260
|
|
|
261
|
+
// Implement HonoUserApiHandler — the generated interface enforces
|
|
262
|
+
// that every operation in the "user" resource has a handler.
|
|
391
263
|
export class UserHandlers implements HonoUserApiHandler {
|
|
392
264
|
public constructor() {}
|
|
393
265
|
|
|
394
|
-
public async handleGetUserRequest(
|
|
266
|
+
public async handleGetUserRequest(
|
|
267
|
+
request: IGetUserRequest,
|
|
268
|
+
context: Context
|
|
269
|
+
): Promise<GetUserResponse> {
|
|
395
270
|
// Simulate fetching user data
|
|
396
271
|
const fetchedUser = {
|
|
397
272
|
id: request.param.userId,
|
|
@@ -402,8 +277,7 @@ export class UserHandlers implements HonoUserApiHandler {
|
|
|
402
277
|
updatedAt: new Date("2023-01-01").toISOString(),
|
|
403
278
|
};
|
|
404
279
|
|
|
405
|
-
return
|
|
406
|
-
statusCode: HttpStatusCode.OK,
|
|
280
|
+
return createGetUserSuccessResponse({
|
|
407
281
|
header: {
|
|
408
282
|
"Content-Type": "application/json",
|
|
409
283
|
},
|
|
@@ -411,17 +285,9 @@ export class UserHandlers implements HonoUserApiHandler {
|
|
|
411
285
|
});
|
|
412
286
|
}
|
|
413
287
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
public handleUpdateUserRequest(request: IUpdateUserRequest): Promise<UpdateUserResponse> {
|
|
419
|
-
throw new Error("Not implemented");
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
public handleListUserRequest(request: IListUserRequest): Promise<ListUserResponse> {
|
|
423
|
-
throw new Error("Not implemented");
|
|
424
|
-
}
|
|
288
|
+
// Implement further handlers for each operation in the resource.
|
|
289
|
+
// TypeScript enforces the contract — every handler declared in
|
|
290
|
+
// HonoUserApiHandler must be implemented before the code compiles.
|
|
425
291
|
}
|
|
426
292
|
```
|
|
427
293
|
|
|
@@ -472,23 +338,21 @@ tsx api/server.ts
|
|
|
472
338
|
|
|
473
339
|
```typescript
|
|
474
340
|
// api/client-test.ts
|
|
475
|
-
import { UserClient, GetUserRequestCommand
|
|
341
|
+
import { UserClient, GetUserRequestCommand } from "./generated";
|
|
476
342
|
|
|
477
343
|
const client = new UserClient({ baseUrl: "http://localhost:3000" });
|
|
478
344
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
console.log("Successfully fetched user:",
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
console.error("Other error occurred:", error);
|
|
491
|
-
}
|
|
345
|
+
const getUserRequestCommand = new GetUserRequestCommand({
|
|
346
|
+
param: { userId: "123" },
|
|
347
|
+
});
|
|
348
|
+
const response = await client.send(getUserRequestCommand);
|
|
349
|
+
|
|
350
|
+
if (response.type === "GetUserSuccess") {
|
|
351
|
+
console.log("Successfully fetched user:", response.body);
|
|
352
|
+
} else if (response.type === "UserNotFoundError") {
|
|
353
|
+
console.error("User not found:", response.body);
|
|
354
|
+
} else {
|
|
355
|
+
console.error("Other error occurred:", response.type);
|
|
492
356
|
}
|
|
493
357
|
```
|
|
494
358
|
|