@rexeus/typeweaver 0.0.2 → 0.0.4

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
@@ -1,66 +1,201 @@
1
- # @rexeus/typeweaver
1
+ # 🧵✨ @rexeus/typeweaver
2
2
 
3
- CLI tool for generating type-safe API code from TypeWeaver definitions.
3
+ [![npm version](https://img.shields.io/npm/v/@rexeus/typeweaver.svg)](https://www.npmjs.com/package/@rexeus/typeweaver)
4
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
4
6
 
5
- ## Installation
7
+ Typeweaver is a type-safe HTTP API framework built for API-first development with a focus on
8
+ developer experience. Use typeweaver to specify your HTTP APIs in TypeScript and Zod, and generate
9
+ clients, validators, routers, and more ✨
10
+
11
+ ---
12
+
13
+ ## 📥 Installation
6
14
 
7
15
  ```bash
16
+ # Install the CLI as a dev dependency
8
17
  npm install -D @rexeus/typeweaver
18
+
19
+ # Install the runtime as a dependency
20
+ npm install @rexeus/typeweaver-core
9
21
  ```
10
22
 
11
- ## Usage
23
+ Now you are ready to start building! Check out [Quickstart](#-get-started)
24
+
25
+ ## 🎯 Why typeweaver?
26
+
27
+ - 📝 **Define once, generate everything**: API contracts in Zod become clients, servers, validators,
28
+ and docs.
29
+ - 📂 **Resource-based architecture**: APIs organized by resources (like user, todo, project, tag,
30
+ blog-post, etc.), each with its operations and generated components (e.g. clients). Scale
31
+ naturally as your API grows.
32
+ - 🔒 **Real type safety**: From API definition to client usage, every request and response is fully
33
+ typed. No more `any` types sneaking in.
34
+ - ✅ **Automatic validation**: Invalid requests never reach your code.
35
+ - 🔌 **Bring your own framework**: Ready-made adapters for popular frameworks, extensible plugin
36
+ system for everything else.
37
+ - 😊 **Finally, DX that doesn't suck**: One schema, no duplication, pure TypeScript.
38
+
39
+ ---
40
+
41
+ ## 🔌 Available Plugins
42
+
43
+ | Package | Description | Version |
44
+ | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
45
+ | [@rexeus/typeweaver-types](https://github.com/rexeus/typeweaver/tree/main/packages/types/README.md) | Plugin for request/response types and validation - the foundation for all other plugins and always included | ![npm](https://img.shields.io/npm/v/@rexeus/typeweaver-types) |
46
+ | [@rexeus/typeweaver-clients](https://github.com/rexeus/typeweaver/tree/main/packages/clients/README.md) | Plugin for HTTP clients using Axios | ![npm](https://img.shields.io/npm/v/@rexeus/typeweaver-clients) |
47
+ | [@rexeus/typeweaver-hono](https://github.com/rexeus/typeweaver/tree/main/packages/hono/README.md) | Plugin for Hono routers | ![npm](https://img.shields.io/npm/v/@rexeus/typeweaver-hono) |
48
+ | [@rexeus/typeweaver-aws-cdk](https://github.com/rexeus/typeweaver/tree/main/packages/aws-cdk/README.md) | Plugin for AWS CDK constructs for API Gateway V2 | ![npm](https://img.shields.io/npm/v/@rexeus/typeweaver-aws-cdk) |
49
+
50
+ More plugins are planned. If you want to build your own, check out the plugin system
51
+
52
+ [Plugin system](https://github.com/rexeus/typeweaver/tree/main/packages/gen/README.md#-how-to-use).
53
+
54
+ ---
55
+
56
+ ## ⌨️ CLI
12
57
 
13
58
  Generate TypeScript code from your API definitions:
14
59
 
15
60
  ```bash
16
- npx typeweaver generate --input ./api/definitions --output ./api/generated --plugins clients,aws-cdk
61
+ # Generate with clients plugin:
62
+ npx typeweaver generate --input ./api/definition --output ./api/generated --plugins clients
63
+
64
+ # Generate with multiple plugins:
65
+ npx typeweaver generate --input ./api/definition --output ./api/generated --plugins clients,hono,aws-cdk
66
+
67
+ # Using all available plugins:
68
+ npx typeweaver generate --input ./api/definition --output ./api/generated --plugins all
17
69
  ```
18
70
 
19
- ### Options
71
+ ### ⚙️ Options
20
72
 
21
73
  - `--input, -i <path>`: Input directory containing API definitions (required)
22
74
  - `--output, -o <path>`: Output directory for generated code (required)
75
+ - `-s, --shared <path>`: Shared directory for reusable schemas (optional, defaults to
76
+ `<input-path>/shared`)
23
77
  - `--config, -c <path>`: Configuration file path (optional)
24
- - `--plugins, -p <plugins>`: Comma-separated list of plugins to use (e.g., "clients,aws-cdk")
78
+ - `--plugins, -p <plugins>`: Comma-separated list of plugins to use (e.g., "clients,hono" or "all"
79
+ for all plugins)
25
80
  - `--prettier / --no-prettier`: Enable/disable code formatting with Prettier (default: true)
26
81
  - `--clean / --no-clean`: Enable/disable output directory cleaning (default: true)
27
82
 
28
- ### What Gets Generated
83
+ ### 📝 Configuration File
29
84
 
30
- From your API definitions, TypeWeaver generates:
85
+ Create a config file (e.g. `typeweaver.config.js`) for more complex configurations:
31
86
 
32
- 1. **Request Types & Commands** - Type-safe request interfaces and command classes
33
- 2. **Response Types** - Typed response interfaces for all status codes
34
- 3. **Validators** - Runtime validators for requests and responses using Zod
35
- 4. **API Clients** - Typed HTTP clients with automatic response validation
36
- 5. **Router Implementations** - HTTP API Gateway routers (with aws-cdk plugin)
37
- 6. **Shared Error Types** - Reusable error response types
87
+ ```javascript
88
+ export default {
89
+ input: "./api/definition",
90
+ output: "./api/generated",
91
+ plugins: ["clients", "hono", "aws-cdk"],
92
+ prettier: true,
93
+ clean: true,
94
+ };
95
+ ```
38
96
 
39
- ### Project Structure
97
+ Then run:
40
98
 
41
- Your API definitions should follow this structure:
99
+ ```bash
100
+ npx typeweaver generate --config ./typeweaver.config.js
101
+ ```
102
+
103
+ ## 🌱 Get Started
104
+
105
+ ### 📁 Project Structure
106
+
107
+ Your API definition must follow this structure:
108
+
109
+ - Each resource needs its own directory under the specified input directory (e.g. input dir:
110
+ `api/definition` contains `user/`, `post/` subdirectories)
111
+ - The directory name defines the resource name (e.g. `user`, `post`)
112
+ - The structure inside a resource directory can be nested to provide better organization (e.g.
113
+ `user/errors/...`, `user/mutations/...`)
114
+ - Inside a resource directory, each operation or response definition gets its own file (e.g.
115
+ `CreateUserDefinition.ts`, `UserNotFoundErrorDefinition.ts`)
116
+ - An operation definition file must include one default export of a `HttpOperationDefinition`
117
+ instance (e.g. `export default new HttpOperationDefinition({...})`)
118
+ - It is recommended to specify separate schemas for requests and responses, but this is not strictly
119
+ required.
120
+ - If separating schemas, Zod utilities can be used to apply general schemas case-specifically
121
+ (useful Zod utilities: pick, omit, merge...)
122
+ - A response definition file must include one default export of a `HttpResponseDefinition` instance
123
+ (e.g. `export default new HttpResponseDefinition({...})`)
124
+ - Responses shared across operations are possible, but need to be placed in the `shared` directory.
125
+ - The shared directory can be specified using the `--shared` option, but must be located within
126
+ the input directory
127
+ - Default shared directory is `<input-path>/shared`
128
+ - The shared directory is suitable not only as a place for responses but also for shared schemas
129
+
130
+ As you can see, the structure of the input directory is essential. However, you are completely free
131
+ to choose the structure and nesting within resource directories.
132
+
133
+ **Important**: All definition files and their dependencies (like separate schemas etc.) must be
134
+ self-contained within the input directory. Generated code creates an immutable snapshot of your
135
+ definitions, so any external imports (relative imports outside the input directory) will not work.
136
+ NPM package imports continue to work normally.
42
137
 
43
138
  ```
44
- api/definitions/
45
- ├── users/ # Entity directory
46
- │ ├── GetUserDefinition.ts # Operation definition
47
- ├── CreateUserDefinition.ts
48
- └── userSchema.ts # Schemas for the entity
49
- ├── posts/
139
+ api/definition/
140
+ ├── user/ # Resource directory
141
+ │ ├── errors/ # Resource-specific error definitions
142
+ │ │ # -> Because they are inside a resource directory,
143
+ │ │ # they can only be used within this resource
144
+ │ │ └── UserNotFoundErrorDefinition.ts
145
+ │ │ └── UserStatusTransitionInvalidErrorDefinition.ts
146
+ │ ├── CreateUserDefinition.ts # Operation definitions
147
+ │ ├── GetUserDefinition.ts
148
+ │ ├── ListUserDefinition.ts
149
+ │ ├── UpdateUserDefinition.ts
150
+ │ └── userSchema.ts # Schema for the resource, can be reused across operations
151
+ ├── post/
152
+ │ ├── errors/
153
+ │ ├── CreatePostDefinition.ts
50
154
  │ ├── GetPostDefinition.ts
51
- └── CreatePostDefinition.ts
52
- └── shared/ # Shared responses
53
- ├── NotFoundErrorDefinition.ts
54
- ├── ValidationErrorDefinition.ts
55
- └── sharedResponses.ts
155
+ ├── ...
156
+ ├── ...
157
+ └── shared/ # Shared responses and schemas
158
+ │ # -> While it doesn't matter where schemas are defined
159
+ │ # inside the input directory, responses can only be
160
+ │ # shared across resources if they are located in the
161
+ │ # shared directory
162
+ ├── ConflictErrorDefinition.ts
163
+ ├── ForbiddenErrorDefinition.ts
164
+ ├── InternalServerErrorDefinition.ts
165
+ ├── NotFoundErrorDefinition.ts # Like BaseApiErrors, can be extended to be resource-specific
166
+ ├── TooManyRequestsErrorDefinition.ts
167
+ ├── UnauthorizedErrorDefinition.ts
168
+ ├── ValidationErrorDefinition.ts
169
+ └── sharedResponses.ts # Collection of responses relevant for every operation
56
170
  ```
57
171
 
58
- ### Example Definition
172
+ ### 💻 Sample Definitions
59
173
 
60
174
  ```typescript
61
- // api/definitions/users/GetUserDefinition.ts
175
+ // api/definition/user/userSchema.ts
176
+ import { z } from "zod/v4";
177
+
178
+ // General schema for user status
179
+ export const userStatusSchema = z.enum(["ACTIVE", "INACTIVE", "SUSPENDED"]);
180
+
181
+ // General user schema, can be reused across operations
182
+ export const userSchema = z.object({
183
+ id: z.uuid(),
184
+ name: z.string(),
185
+ email: z.email(),
186
+ status: userStatusSchema,
187
+ createdAt: z.iso.date(),
188
+ updatedAt: z.iso.date(),
189
+ });
190
+ ```
191
+
192
+ ```typescript
193
+ // api/definition/user/GetUserDefinition.ts
62
194
  import { HttpOperationDefinition, HttpMethod, HttpStatusCode } from "@rexeus/typeweaver-core";
63
195
  import { z } from "zod/v4";
196
+ import { sharedResponses } from "../shared/sharedResponses";
197
+ import { userSchema } from "./userSchema";
198
+ import UserNotFoundErrorDefinition from "./errors/UserNotFoundErrorDefinition";
64
199
 
65
200
  export default new HttpOperationDefinition({
66
201
  operationId: "GetUser",
@@ -72,74 +207,272 @@ export default new HttpOperationDefinition({
72
207
  }),
73
208
  },
74
209
  responses: [
210
+ // - the only success response in this operation is defined inline
211
+ // - the response could also be defined in a separate file and be imported here
212
+ // - generally also multiple success responses could be defined
213
+ // - in this case the "general" user schema is imported and used
75
214
  {
76
215
  statusCode: HttpStatusCode.OK,
77
- body: z.object({
78
- id: z.uuid(),
79
- name: z.string(),
80
- email: z.email(),
216
+ description: "User successfully retrieved",
217
+ header: z.object({
218
+ "Content-Type": z.literal("application/json"),
81
219
  }),
220
+ body: userSchema,
82
221
  },
222
+ UserNotFoundErrorDefinition, // Resource specific response
223
+ ...sharedResponses, // Commonly used responses across all operations, e.g. 401, 403, 500...
83
224
  ],
84
225
  });
85
226
  ```
86
227
 
87
- ## Plugin System
88
-
89
- TypeWeaver supports a plugin-based architecture for extensible code generation. Available plugins:
90
-
91
- - **types** (default): TypeScript types and Zod validators
92
- - **clients**: HTTP API client generation
93
- - **aws-cdk**: AWS CDK constructs and HTTP API Gateway routers
94
-
95
- ### Using Plugins
96
-
97
- Via command line:
228
+ ```typescript
229
+ // api/definition/user/UpdateUserDefinition.ts
230
+ import { HttpOperationDefinition, HttpMethod, HttpStatusCode } from "@rexeus/typeweaver-core";
231
+ import { z } from "zod/v4";
232
+ import { sharedResponses } from "../shared/sharedResponses";
233
+ import { userSchema } from "./userSchema";
234
+ import UserNotFoundErrorDefinition from "./errors/UserNotFoundErrorDefinition";
235
+ import UserStatusTransitionInvalidErrorDefinition from "./errors/UserStatusTransitionInvalidErrorDefinition";
98
236
 
99
- ```bash
100
- npx typeweaver generate --input ./api/definitions --output ./api/generated --plugins clients,aws-cdk
237
+ export default new HttpOperationDefinition({
238
+ operationId: "UpdateUser",
239
+ method: HttpMethod.PATCH,
240
+ path: "/users/:userId",
241
+ request: {
242
+ param: z.object({
243
+ userId: z.uuid(),
244
+ }),
245
+ // general user schema is processed via zod's pick and partial methods
246
+ // to match the update operation's requirements
247
+ body: userSchema
248
+ .pick({
249
+ name: true,
250
+ email: true,
251
+ status: true,
252
+ })
253
+ .partial(),
254
+ },
255
+ responses: [
256
+ {
257
+ statusCode: HttpStatusCode.OK,
258
+ description: "User successfully updated",
259
+ header: z.object({
260
+ "Content-Type": z.literal("application/json"),
261
+ }),
262
+ body: userSchema,
263
+ },
264
+ UserNotFoundErrorDefinition, // Resource specific response
265
+ UserStatusTransitionInvalidErrorDefinition, // Resource specific response
266
+ ...sharedResponses, // Commonly used responses across all operations, e.g. 401, 403, 500...
267
+ ],
268
+ });
101
269
  ```
102
270
 
103
- ### Configuration File
271
+ ```typescript
272
+ // api/definition/user/errors/UserNotFoundErrorDefinition.ts
273
+ import { z } from "zod/v4";
274
+ import { NotFoundErrorDefinition } from "../../shared";
275
+
276
+ // - uses the shared NotFoundErrorDefinition as "base" and extends it
277
+ // - adds a specific message and code for the user resource
278
+ export default NotFoundErrorDefinition.extend({
279
+ name: "UserNotFoundError",
280
+ description: "User not found",
281
+ body: z.object({
282
+ message: z.literal("User not found"),
283
+ code: z.literal("USER_NOT_FOUND_ERROR"),
284
+ actualValues: z.object({
285
+ userId: z.uuid(),
286
+ }),
287
+ }),
288
+ });
289
+ ```
104
290
 
105
- Create a `typeweaver.config.js` file for more complex configurations:
291
+ ```typescript
292
+ // api/definition/user/errors/UserStatusTransitionInvalidErrorDefinition.ts
293
+ import { HttpResponseDefinition, HttpStatusCode } from "@rexeus/typeweaver-core";
294
+ import { z } from "zod/v4";
295
+ import { userStatusSchema } from "../userSchema";
296
+
297
+ // could also extend the shared ConflictErrorDefinition:
298
+ // export default ConflictErrorDefinition.extend({...});
299
+
300
+ // or in this case does not extend a BaseApiError and defines everything itself
301
+ export default new HttpResponseDefinition({
302
+ name: "UserStatusTransitionInvalidError",
303
+ description: "User status transition is conflicting with current status",
304
+ body: z.object({
305
+ message: z.literal("User status transition is conflicting with current status"),
306
+ code: z.literal("USER_STATUS_TRANSITION_INVALID_ERROR"),
307
+ context: z.object({
308
+ userId: z.uuid(),
309
+ currentStatus: userStatusSchema,
310
+ }),
311
+ actualValues: z.object({
312
+ requestedStatus: userStatusSchema,
313
+ }),
314
+ expectedValues: z.object({
315
+ allowedStatuses: z.array(userStatusSchema),
316
+ }),
317
+ }),
318
+ });
319
+ ```
106
320
 
107
- ```javascript
108
- export default {
109
- input: "./api/definitions",
110
- output: "./api/generated",
111
- plugins: ["clients", "aws-cdk"],
112
- prettier: true,
113
- clean: true,
114
- };
321
+ ```typescript
322
+ // api/definition/shared/sharedResponses.ts
323
+ import ForbiddenErrorDefinition from "./ForbiddenErrorDefinition";
324
+ import InternalServerErrorDefinition from "./InternalServerErrorDefinition";
325
+ import TooManyRequestsErrorDefinition from "./TooManyRequestsErrorDefinition";
326
+ import UnauthorizedErrorDefinition from "./UnauthorizedErrorDefinition";
327
+ import UnsupportedMediaTypeErrorDefinition from "./UnsupportedMediaTypeErrorDefinition";
328
+ import ValidationErrorDefinition from "./ValidationErrorDefinition";
329
+
330
+ // various error responses which are relevant for every operation
331
+ // can be spread in the responses array of an HttpOperationDefinition
332
+ export const sharedResponses = [
333
+ ForbiddenErrorDefinition,
334
+ InternalServerErrorDefinition,
335
+ TooManyRequestsErrorDefinition,
336
+ UnauthorizedErrorDefinition,
337
+ UnsupportedMediaTypeErrorDefinition,
338
+ ValidationErrorDefinition,
339
+ ];
115
340
  ```
116
341
 
117
- Then run:
342
+ ### 🔧 Generate using plugins
118
343
 
119
344
  ```bash
120
- npx typeweaver generate --config ./typeweaver.config.js
345
+ # Generate with plugins:
346
+ # - Hono: to easily provide a web server
347
+ # - Clients: to get fitting API clients
348
+ npx typeweaver generate --input ./api/definition --output ./api/generated --plugins clients,hono
121
349
  ```
122
350
 
123
- ## Generated Output
351
+ ### 🌐 Create Hono web server
124
352
 
125
- With the **clients** plugin, you'll get type-safe API clients:
353
+ ```typescript
354
+ // api/user-handlers.ts
355
+ import { HttpResponse, HttpStatusCode } from "@rexeus/typeweaver-core";
356
+ import {
357
+ type UserApiHandler,
358
+ type IGetUserRequest,
359
+ GetUserResponse,
360
+ GetUserSuccessResponse,
361
+ type ICreateUserRequest,
362
+ CreateUserResponse,
363
+ type IUpdateUserRequest,
364
+ UpdateUserResponse,
365
+ type IListUserRequest,
366
+ ListUserResponse,
367
+ } from "./generated";
368
+
369
+ export class UserHandlers implements UserApiHandler {
370
+ public constructor() {}
371
+
372
+ public async handleGetUserRequest(request: IGetUserRequest): Promise<GetUserResponse> {
373
+ // Simulate fetching user data
374
+ const fetchedUser = {
375
+ id: request.param.userId,
376
+ name: "John Doe",
377
+ email: "john.doe@example.com",
378
+ status: "ACTIVE",
379
+ createdAt: new Date("2023-01-01").toISOString(),
380
+ updatedAt: new Date("2023-01-01").toISOString(),
381
+ };
382
+
383
+ return new GetUserSuccessResponse({
384
+ statusCode: HttpStatusCode.OK,
385
+ header: {
386
+ "Content-Type": "application/json",
387
+ },
388
+ body: fetchedUser,
389
+ });
390
+ }
391
+
392
+ public handleCreateUserRequest(request: ICreateUserRequest): Promise<CreateUserResponse> {
393
+ throw new Error("Not implemented");
394
+ }
395
+
396
+ public handleUpdateUserRequest(request: IUpdateUserRequest): Promise<UpdateUserResponse> {
397
+ throw new Error("Not implemented");
398
+ }
399
+
400
+ public handleListUserRequest(request: IListUserRequest): Promise<ListUserResponse> {
401
+ throw new Error("Not implemented");
402
+ }
403
+ }
404
+ ```
126
405
 
127
406
  ```typescript
128
- import { UsersClient } from "./api/generated";
407
+ // api/server.ts
408
+ import { serve } from "@hono/node-server";
409
+ import { Hono } from "hono";
410
+ // an index file exporting all generated components is automatically provided
411
+ import { UserHandlers } from "./user-handlers";
412
+ import { PostHandlers } from "./post-handlers"; // Implement similarly to UserHandlers
413
+ import { UserHono, PostHono } from "./generated";
414
+
415
+ const app = new Hono();
416
+
417
+ const userHandlers = new UserHandlers();
418
+ const postHandlers = new PostHandlers();
419
+
420
+ // you have further config options, e.g. custom error response handling
421
+ // (useful for mapping validation errors to your specific response format)
422
+ const userRouter = new UserHono({
423
+ requestHandlers: userHandlers,
424
+ });
425
+ const postRouter = new PostHono({
426
+ requestHandlers: postHandlers,
427
+ });
428
+
429
+ app.route("/", userRouter);
430
+ app.route("/", postRouter);
129
431
 
130
- const client = new UsersClient({ baseURL: "https://api.example.com" });
131
- const result = await client.send(new GetUserRequestCommand({ param: { userId: "123" } }));
432
+ // Start server on port 3000
433
+ serve(
434
+ {
435
+ fetch: app.fetch,
436
+ port: 3000,
437
+ },
438
+ () => {
439
+ console.log("Server is running on http://localhost:3000");
440
+ }
441
+ );
132
442
  ```
133
443
 
134
- With the **aws-cdk** plugin, you'll get HTTP API Gateway routers:
444
+ ```bash
445
+ # Start your server locally
446
+ tsx api/server.ts
447
+ ```
448
+
449
+ ### 🔗 Communicate by using Clients
135
450
 
136
451
  ```typescript
137
- import { UsersHttpApiRouter } from "./api/generated";
452
+ // api/client-test.ts
453
+ import { UserClient, GetUserRequestCommand, UserNotFoundErrorResponse } from "./generated";
454
+
455
+ const client = new UserClient({ baseUrl: "http://localhost:3000" });
456
+
457
+ try {
458
+ const getUserRequestCommand = new GetUserRequestCommand({ param: { userId: "123" } });
459
+ const result = await client.send(getUserRequestCommand);
460
+
461
+ console.log("Successfully fetched user:", result.body);
462
+ } catch (error) {
463
+ if (error instanceof UserNotFoundErrorResponse) {
464
+ console.error("User not found:", error.body);
465
+ } else {
466
+ console.error("Other error occurred:", error);
467
+ }
468
+ }
469
+ ```
138
470
 
139
- const router = new UsersHttpApiRouter();
140
- // Use in your AWS CDK stack
471
+ ```bash
472
+ # Call your created Hono server
473
+ tsx api/client-test.ts
141
474
  ```
142
475
 
143
- ## License
476
+ ## 📄 License
144
477
 
145
- ISC © Dennis Wentzien 2025
478
+ Apache 2.0 © Dennis Wentzien 2025