@rexeus/typeweaver-server 0.5.1 → 0.6.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 CHANGED
@@ -60,7 +60,7 @@ For a resource `User`, the plugin generates:
60
60
  generated/
61
61
  lib/server/ ← TypeweaverApp, middleware types, etc.
62
62
  user/
63
- UserRouter.ts ← Router class + UserApiHandler type
63
+ UserRouter.ts ← Router class + ServerUserApiHandler type
64
64
  GetUserRequest.ts ← Request types (IGetUserRequest)
65
65
  GetUserResponse.ts ← Response types + factory classes
66
66
  ...
@@ -79,9 +79,9 @@ bodies.
79
79
  ```ts
80
80
  // user-handlers.ts
81
81
  import { HttpStatusCode } from "@rexeus/typeweaver-core";
82
- import type { UserApiHandler } from "./generated";
82
+ import type { ServerUserApiHandler } from "./generated";
83
83
 
84
- export const userHandlers: UserApiHandler = {
84
+ export const userHandlers: ServerUserApiHandler = {
85
85
  async handleListUsersRequest() {
86
86
  return {
87
87
  statusCode: HttpStatusCode.OK,
@@ -99,7 +99,11 @@ export const userHandlers: UserApiHandler = {
99
99
  async handleGetUserRequest(request) {
100
100
  return {
101
101
  statusCode: HttpStatusCode.OK,
102
- body: { id: request.param.userId, name: "Jane", email: "jane@example.com" },
102
+ body: {
103
+ id: request.param.userId,
104
+ name: "Jane",
105
+ email: "jane@example.com",
106
+ },
103
107
  };
104
108
  },
105
109
 
@@ -183,12 +187,13 @@ to provide it.
183
187
  **Requiring upstream state** — declare dependencies:
184
188
 
185
189
  ```ts
186
- const permissions = defineMiddleware<{ permissions: string[] }, { userId: string }>(
187
- async (ctx, next) => {
188
- const userId = ctx.state.get("userId"); // string — no cast, no undefined
189
- return next({ permissions: await loadPermissions(userId) });
190
- }
191
- );
190
+ const permissions = defineMiddleware<
191
+ { permissions: string[] },
192
+ { userId: string }
193
+ >(async (ctx, next) => {
194
+ const userId = ctx.state.get("userId"); // string — no cast, no undefined
195
+ return next({ permissions: await loadPermissions(userId) });
196
+ });
192
197
  ```
193
198
 
194
199
  Registering `permissions` before `auth` produces a **compile-time error** because `userId` is not
@@ -201,7 +206,7 @@ const logger = defineMiddleware(async (ctx, next) => {
201
206
  const start = Date.now();
202
207
  const response = await next();
203
208
  console.log(
204
- `${ctx.request.method} ${ctx.request.path} -> ${response.statusCode} (${Date.now() - start}ms)`
209
+ `${ctx.request.method} ${ctx.request.path} -> ${response.statusCode} (${Date.now() - start}ms)`,
205
210
  );
206
211
  return response;
207
212
  });
@@ -269,7 +274,7 @@ CORS always execute.
269
274
  ```ts
270
275
  const app = new TypeweaverApp({
271
276
  maxBodySize: 5 * 1024 * 1024, // 5 MB
272
- onError: error => logger.error("Unhandled error", error),
277
+ onError: (error) => logger.error("Unhandled error", error),
273
278
  });
274
279
  ```
275
280
 
@@ -277,13 +282,13 @@ const app = new TypeweaverApp({
277
282
 
278
283
  Each router accepts `TypeweaverRouterOptions`:
279
284
 
280
- | Option | Type | Default | Description |
281
- | -------------------------- | ---------------------- | ---------- | ---------------------------------- |
282
- | `requestHandlers` | `<Resource>ApiHandler` | _required_ | Handler methods for each operation |
283
- | `validateRequests` | `boolean` | `true` | Enable/disable request validation |
284
- | `handleValidationErrors` | `boolean \| function` | `true` | Handle validation errors |
285
- | `handleHttpResponseErrors` | `boolean \| function` | `true` | Handle thrown `HttpResponse` |
286
- | `handleUnknownErrors` | `boolean \| function` | `true` | Handle unexpected errors |
285
+ | Option | Type | Default | Description |
286
+ | -------------------------- | ---------------------------- | ---------- | ---------------------------------- |
287
+ | `requestHandlers` | `Server<Resource>ApiHandler` | _required_ | Handler methods for each operation |
288
+ | `validateRequests` | `boolean` | `true` | Enable/disable request validation |
289
+ | `handleValidationErrors` | `boolean \| function` | `true` | Handle validation errors |
290
+ | `handleHttpResponseErrors` | `boolean \| function` | `true` | Handle thrown `HttpResponse` |
291
+ | `handleUnknownErrors` | `boolean \| function` | `true` | Handle unexpected errors |
287
292
 
288
293
  When set to `true`, error handlers use sensible defaults (400/500 responses). When set to `false`,
289
294
  errors fall through to the next handler in the chain. When set to a function, it receives the error
@@ -353,7 +358,10 @@ new UserRouter({
353
358
  new UserRouter({
354
359
  requestHandlers: userHandlers,
355
360
  handleHttpResponseErrors: (error, ctx) => {
356
- logger.warn("HTTP error", { status: error.statusCode, path: ctx.request.path });
361
+ logger.warn("HTTP error", {
362
+ status: error.statusCode,
363
+ path: ctx.request.path,
364
+ });
357
365
  return error;
358
366
  },
359
367
  });
package/dist/index.cjs CHANGED
@@ -1,110 +1,129 @@
1
- 'use strict';
2
-
3
- var path = require('node:path');
4
- var node_url = require('node:url');
5
- var typeweaverGen = require('@rexeus/typeweaver-gen');
6
- var typeweaverCore = require('@rexeus/typeweaver-core');
7
- var Case = require('case');
8
-
9
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
10
26
 
11
- var path__default = /*#__PURE__*/_interopDefault(path);
12
- var Case__default = /*#__PURE__*/_interopDefault(Case);
27
+ //#endregion
28
+ let node_path = require("node:path");
29
+ node_path = __toESM(node_path);
30
+ let node_url = require("node:url");
31
+ let _rexeus_typeweaver_gen = require("@rexeus/typeweaver-gen");
32
+ let _rexeus_typeweaver_core = require("@rexeus/typeweaver-core");
33
+ let case$1 = require("case");
34
+ case$1 = __toESM(case$1);
13
35
 
14
- // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
15
- var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
16
- var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
36
+ //#region src/RouterGenerator.ts
37
+ /**
38
+ * Generates TypeweaverRouter subclasses from API definitions.
39
+ *
40
+ * For each resource (e.g., `Todo`, `Account`), produces a `<ResourceName>Router.ts`
41
+ * file that extends `TypeweaverRouter` and registers all operations as routes.
42
+ */
17
43
  var RouterGenerator = class {
18
- /**
19
- * Generates router files for all resources in the given context.
20
- *
21
- * @param context - The generator context containing resources, templates, and output configuration
22
- */
23
- static generate(context) {
24
- const moduleDir2 = path__default.default.dirname(node_url.fileURLToPath(importMetaUrl));
25
- const templateFile = path__default.default.join(moduleDir2, "templates", "Router.ejs");
26
- for (const [entityName, entityResource] of Object.entries(
27
- context.resources.entityResources
28
- )) {
29
- this.writeRouter(
30
- entityName,
31
- templateFile,
32
- entityResource.operations,
33
- context
34
- );
35
- }
36
- }
37
- static writeRouter(entityName, templateFile, operationResources, context) {
38
- const pascalCaseEntityName = Case__default.default.pascal(entityName);
39
- const outputDir = path__default.default.join(context.outputDir, entityName);
40
- const outputPath = path__default.default.join(outputDir, `${pascalCaseEntityName}Router.ts`);
41
- const operations = operationResources.filter((resource) => resource.definition.method !== typeweaverCore.HttpMethod.HEAD).map((resource) => this.createOperationData(resource)).sort((a, b) => this.compareRoutes(a, b));
42
- const content = context.renderTemplate(templateFile, {
43
- coreDir: typeweaverGen.Path.relative(outputDir, context.outputDir),
44
- entityName,
45
- pascalCaseEntityName,
46
- operations
47
- });
48
- const relativePath = path__default.default.relative(context.outputDir, outputPath);
49
- context.writeFile(relativePath, content);
50
- }
51
- static createOperationData(resource) {
52
- const className = Case__default.default.pascal(resource.definition.operationId);
53
- return {
54
- className,
55
- handlerName: `handle${className}Request`,
56
- method: resource.definition.method,
57
- path: resource.definition.path
58
- };
59
- }
60
- static compareRoutes(a, b) {
61
- const aSegments = a.path.split("/").filter((s) => s);
62
- const bSegments = b.path.split("/").filter((s) => s);
63
- if (aSegments.length !== bSegments.length) {
64
- return aSegments.length - bSegments.length;
65
- }
66
- for (let i = 0; i < aSegments.length; i++) {
67
- const aSegment = aSegments[i];
68
- const bSegment = bSegments[i];
69
- const aIsParam = aSegment.startsWith(":");
70
- const bIsParam = bSegment.startsWith(":");
71
- if (aIsParam !== bIsParam) {
72
- return aIsParam ? 1 : -1;
73
- }
74
- if (aSegment !== bSegment) {
75
- return aSegment.localeCompare(bSegment);
76
- }
77
- }
78
- return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);
79
- }
80
- static METHOD_PRIORITY = {
81
- GET: 1,
82
- POST: 2,
83
- PUT: 3,
84
- PATCH: 4,
85
- DELETE: 5,
86
- OPTIONS: 6,
87
- HEAD: 7
88
- };
89
- static getMethodPriority(method) {
90
- return this.METHOD_PRIORITY[method] ?? 999;
91
- }
44
+ /**
45
+ * Generates router files for all resources in the given context.
46
+ *
47
+ * @param context - The generator context containing resources, templates, and output configuration
48
+ */
49
+ static generate(context) {
50
+ const moduleDir = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
51
+ const templateFile = node_path.default.join(moduleDir, "templates", "Router.ejs");
52
+ for (const [entityName, entityResource] of Object.entries(context.resources.entityResources)) this.writeRouter(entityName, templateFile, entityResource.operations, context);
53
+ }
54
+ static writeRouter(entityName, templateFile, operationResources, context) {
55
+ const pascalCaseEntityName = case$1.default.pascal(entityName);
56
+ const outputDir = node_path.default.join(context.outputDir, entityName);
57
+ const outputPath = node_path.default.join(outputDir, `${pascalCaseEntityName}Router.ts`);
58
+ const operations = operationResources.filter((resource) => resource.definition.method !== _rexeus_typeweaver_core.HttpMethod.HEAD).map((resource) => this.createOperationData(resource)).sort((a, b) => this.compareRoutes(a, b));
59
+ const content = context.renderTemplate(templateFile, {
60
+ coreDir: _rexeus_typeweaver_gen.Path.relative(outputDir, context.outputDir),
61
+ entityName,
62
+ pascalCaseEntityName,
63
+ operations
64
+ });
65
+ const relativePath = node_path.default.relative(context.outputDir, outputPath);
66
+ context.writeFile(relativePath, content);
67
+ }
68
+ static createOperationData(resource) {
69
+ const className = case$1.default.pascal(resource.definition.operationId);
70
+ return {
71
+ className,
72
+ handlerName: `handle${className}Request`,
73
+ method: resource.definition.method,
74
+ path: resource.definition.path
75
+ };
76
+ }
77
+ static compareRoutes(a, b) {
78
+ const aSegments = a.path.split("/").filter((s) => s);
79
+ const bSegments = b.path.split("/").filter((s) => s);
80
+ if (aSegments.length !== bSegments.length) return aSegments.length - bSegments.length;
81
+ for (let i = 0; i < aSegments.length; i++) {
82
+ const aSegment = aSegments[i];
83
+ const bSegment = bSegments[i];
84
+ const aIsParam = aSegment.startsWith(":");
85
+ if (aIsParam !== bSegment.startsWith(":")) return aIsParam ? 1 : -1;
86
+ if (aSegment !== bSegment) return aSegment.localeCompare(bSegment);
87
+ }
88
+ return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);
89
+ }
90
+ static METHOD_PRIORITY = {
91
+ GET: 1,
92
+ POST: 2,
93
+ PUT: 3,
94
+ PATCH: 4,
95
+ DELETE: 5,
96
+ OPTIONS: 6,
97
+ HEAD: 7
98
+ };
99
+ static getMethodPriority(method) {
100
+ return this.METHOD_PRIORITY[method] ?? 999;
101
+ }
92
102
  };
93
103
 
94
- // src/index.ts
95
- var moduleDir = path__default.default.dirname(node_url.fileURLToPath(importMetaUrl));
96
- var ServerPlugin = class extends typeweaverGen.BasePlugin {
97
- name = "server";
98
- /**
99
- * Generates the server runtime and typed routers for all resources.
100
- *
101
- * @param context - The generator context
102
- */
103
- generate(context) {
104
- const libSourceDir = path__default.default.join(moduleDir, "lib");
105
- this.copyLibFiles(context, libSourceDir, this.name);
106
- RouterGenerator.generate(context);
107
- }
104
+ //#endregion
105
+ //#region src/index.ts
106
+ const moduleDir = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
107
+ /**
108
+ * Typeweaver plugin that generates a lightweight, dependency-free server
109
+ * with built-in routing and middleware support.
110
+ *
111
+ * Copies the runtime library files (`TypeweaverApp`, `TypeweaverRouter`, `Router`,
112
+ * `Middleware`, etc.) and generates typed router classes for each resource.
113
+ */
114
+ var ServerPlugin = class extends _rexeus_typeweaver_gen.BasePlugin {
115
+ name = "server";
116
+ /**
117
+ * Generates the server runtime and typed routers for all resources.
118
+ *
119
+ * @param context - The generator context
120
+ */
121
+ generate(context) {
122
+ const libSourceDir = node_path.default.join(moduleDir, "lib");
123
+ this.copyLibFiles(context, libSourceDir, this.name);
124
+ RouterGenerator.generate(context);
125
+ }
108
126
  };
109
127
 
110
- module.exports = ServerPlugin;
128
+ //#endregion
129
+ module.exports = ServerPlugin;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { BasePlugin, GeneratorContext } from '@rexeus/typeweaver-gen';
1
+ import { BasePlugin, GeneratorContext } from "@rexeus/typeweaver-gen";
2
2
 
3
+ //#region src/index.d.ts
3
4
  /**
4
5
  * Typeweaver plugin that generates a lightweight, dependency-free server
5
6
  * with built-in routing and middleware support.
@@ -8,13 +9,13 @@ import { BasePlugin, GeneratorContext } from '@rexeus/typeweaver-gen';
8
9
  * `Middleware`, etc.) and generates typed router classes for each resource.
9
10
  */
10
11
  declare class ServerPlugin extends BasePlugin {
11
- name: string;
12
- /**
13
- * Generates the server runtime and typed routers for all resources.
14
- *
15
- * @param context - The generator context
16
- */
17
- generate(context: GeneratorContext): void;
12
+ name: string;
13
+ /**
14
+ * Generates the server runtime and typed routers for all resources.
15
+ *
16
+ * @param context - The generator context
17
+ */
18
+ generate(context: GeneratorContext): void;
18
19
  }
19
-
20
- export { ServerPlugin as default };
20
+ export = ServerPlugin;
21
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;AAG+D;;;;;;cAY1C,YAAA,SAAqB,UAAA;EACjC,IAAA;EAOkB;;;;;EAAT,QAAA,CAAS,OAAA,EAAS,gBAAA;AAAA;AAAA"}
@@ -1,5 +1,6 @@
1
- import { BasePlugin, GeneratorContext } from '@rexeus/typeweaver-gen';
1
+ import { BasePlugin, GeneratorContext } from "@rexeus/typeweaver-gen";
2
2
 
3
+ //#region src/index.d.ts
3
4
  /**
4
5
  * Typeweaver plugin that generates a lightweight, dependency-free server
5
6
  * with built-in routing and middleware support.
@@ -8,13 +9,14 @@ import { BasePlugin, GeneratorContext } from '@rexeus/typeweaver-gen';
8
9
  * `Middleware`, etc.) and generates typed router classes for each resource.
9
10
  */
10
11
  declare class ServerPlugin extends BasePlugin {
11
- name: string;
12
- /**
13
- * Generates the server runtime and typed routers for all resources.
14
- *
15
- * @param context - The generator context
16
- */
17
- generate(context: GeneratorContext): void;
12
+ name: string;
13
+ /**
14
+ * Generates the server runtime and typed routers for all resources.
15
+ *
16
+ * @param context - The generator context
17
+ */
18
+ generate(context: GeneratorContext): void;
18
19
  }
19
-
20
+ //#endregion
20
21
  export { ServerPlugin as default };
22
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;AAG+D;;;;;;cAY1C,YAAA,SAAqB,UAAA;EACjC,IAAA;EAOkB;;;;;EAAT,QAAA,CAAS,OAAA,EAAS,gBAAA;AAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,101 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { BasePlugin, Path } from "@rexeus/typeweaver-gen";
4
+ import { HttpMethod } from "@rexeus/typeweaver-core";
5
+ import Case from "case";
6
+
7
+ //#region src/RouterGenerator.ts
8
+ /**
9
+ * Generates TypeweaverRouter subclasses from API definitions.
10
+ *
11
+ * For each resource (e.g., `Todo`, `Account`), produces a `<ResourceName>Router.ts`
12
+ * file that extends `TypeweaverRouter` and registers all operations as routes.
13
+ */
14
+ var RouterGenerator = class {
15
+ /**
16
+ * Generates router files for all resources in the given context.
17
+ *
18
+ * @param context - The generator context containing resources, templates, and output configuration
19
+ */
20
+ static generate(context) {
21
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
22
+ const templateFile = path.join(moduleDir, "templates", "Router.ejs");
23
+ for (const [entityName, entityResource] of Object.entries(context.resources.entityResources)) this.writeRouter(entityName, templateFile, entityResource.operations, context);
24
+ }
25
+ static writeRouter(entityName, templateFile, operationResources, context) {
26
+ const pascalCaseEntityName = Case.pascal(entityName);
27
+ const outputDir = path.join(context.outputDir, entityName);
28
+ const outputPath = path.join(outputDir, `${pascalCaseEntityName}Router.ts`);
29
+ const operations = operationResources.filter((resource) => resource.definition.method !== HttpMethod.HEAD).map((resource) => this.createOperationData(resource)).sort((a, b) => this.compareRoutes(a, b));
30
+ const content = context.renderTemplate(templateFile, {
31
+ coreDir: Path.relative(outputDir, context.outputDir),
32
+ entityName,
33
+ pascalCaseEntityName,
34
+ operations
35
+ });
36
+ const relativePath = path.relative(context.outputDir, outputPath);
37
+ context.writeFile(relativePath, content);
38
+ }
39
+ static createOperationData(resource) {
40
+ const className = Case.pascal(resource.definition.operationId);
41
+ return {
42
+ className,
43
+ handlerName: `handle${className}Request`,
44
+ method: resource.definition.method,
45
+ path: resource.definition.path
46
+ };
47
+ }
48
+ static compareRoutes(a, b) {
49
+ const aSegments = a.path.split("/").filter((s) => s);
50
+ const bSegments = b.path.split("/").filter((s) => s);
51
+ if (aSegments.length !== bSegments.length) return aSegments.length - bSegments.length;
52
+ for (let i = 0; i < aSegments.length; i++) {
53
+ const aSegment = aSegments[i];
54
+ const bSegment = bSegments[i];
55
+ const aIsParam = aSegment.startsWith(":");
56
+ if (aIsParam !== bSegment.startsWith(":")) return aIsParam ? 1 : -1;
57
+ if (aSegment !== bSegment) return aSegment.localeCompare(bSegment);
58
+ }
59
+ return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);
60
+ }
61
+ static METHOD_PRIORITY = {
62
+ GET: 1,
63
+ POST: 2,
64
+ PUT: 3,
65
+ PATCH: 4,
66
+ DELETE: 5,
67
+ OPTIONS: 6,
68
+ HEAD: 7
69
+ };
70
+ static getMethodPriority(method) {
71
+ return this.METHOD_PRIORITY[method] ?? 999;
72
+ }
73
+ };
74
+
75
+ //#endregion
76
+ //#region src/index.ts
77
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
78
+ /**
79
+ * Typeweaver plugin that generates a lightweight, dependency-free server
80
+ * with built-in routing and middleware support.
81
+ *
82
+ * Copies the runtime library files (`TypeweaverApp`, `TypeweaverRouter`, `Router`,
83
+ * `Middleware`, etc.) and generates typed router classes for each resource.
84
+ */
85
+ var ServerPlugin = class extends BasePlugin {
86
+ name = "server";
87
+ /**
88
+ * Generates the server runtime and typed routers for all resources.
89
+ *
90
+ * @param context - The generator context
91
+ */
92
+ generate(context) {
93
+ const libSourceDir = path.join(moduleDir, "lib");
94
+ this.copyLibFiles(context, libSourceDir, this.name);
95
+ RouterGenerator.generate(context);
96
+ }
97
+ };
98
+
99
+ //#endregion
100
+ export { ServerPlugin as default };
101
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/RouterGenerator.ts","../src/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { HttpMethod } from \"@rexeus/typeweaver-core\";\nimport { Path } from \"@rexeus/typeweaver-gen\";\nimport type {\n GeneratorContext,\n OperationResource,\n} from \"@rexeus/typeweaver-gen\";\nimport Case from \"case\";\n\ntype OperationData = {\n readonly className: string;\n readonly handlerName: string;\n readonly method: string;\n readonly path: string;\n};\n\n/**\n * Generates TypeweaverRouter subclasses from API definitions.\n *\n * For each resource (e.g., `Todo`, `Account`), produces a `<ResourceName>Router.ts`\n * file that extends `TypeweaverRouter` and registers all operations as routes.\n */\nexport class RouterGenerator {\n /**\n * Generates router files for all resources in the given context.\n *\n * @param context - The generator context containing resources, templates, and output configuration\n */\n public static generate(context: GeneratorContext): void {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n const templateFile = path.join(moduleDir, \"templates\", \"Router.ejs\");\n\n for (const [entityName, entityResource] of Object.entries(\n context.resources.entityResources\n )) {\n this.writeRouter(\n entityName,\n templateFile,\n entityResource.operations,\n context\n );\n }\n }\n\n private static writeRouter(\n entityName: string,\n templateFile: string,\n operationResources: OperationResource[],\n context: GeneratorContext\n ): void {\n const pascalCaseEntityName = Case.pascal(entityName);\n const outputDir = path.join(context.outputDir, entityName);\n const outputPath = path.join(outputDir, `${pascalCaseEntityName}Router.ts`);\n\n const operations = operationResources\n .filter(resource => resource.definition.method !== HttpMethod.HEAD)\n .map(resource => this.createOperationData(resource))\n .sort((a, b) => this.compareRoutes(a, b));\n\n const content = context.renderTemplate(templateFile, {\n coreDir: Path.relative(outputDir, context.outputDir),\n entityName,\n pascalCaseEntityName,\n operations,\n });\n\n const relativePath = path.relative(context.outputDir, outputPath);\n context.writeFile(relativePath, content);\n }\n\n private static createOperationData(\n resource: OperationResource\n ): OperationData {\n const className = Case.pascal(resource.definition.operationId);\n\n return {\n className,\n handlerName: `handle${className}Request`,\n method: resource.definition.method,\n path: resource.definition.path,\n };\n }\n\n private static compareRoutes(a: OperationData, b: OperationData): number {\n const aSegments = a.path.split(\"/\").filter(s => s);\n const bSegments = b.path.split(\"/\").filter(s => s);\n\n // 1. Compare by depth first (shallow to deep)\n if (aSegments.length !== bSegments.length) {\n return aSegments.length - bSegments.length;\n }\n\n // 2. Compare segment by segment\n for (let i = 0; i < aSegments.length; i++) {\n const aSegment = aSegments[i]!;\n const bSegment = bSegments[i]!;\n\n const aIsParam = aSegment.startsWith(\":\");\n const bIsParam = bSegment.startsWith(\":\");\n\n // Static segments before parameters\n if (aIsParam !== bIsParam) {\n return aIsParam ? 1 : -1;\n }\n\n // Within same type, alphabetical order\n if (aSegment !== bSegment) {\n return aSegment.localeCompare(bSegment);\n }\n }\n\n // 3. Same path = sort by HTTP method priority\n return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);\n }\n\n private static readonly METHOD_PRIORITY: Record<string, number> = {\n GET: 1,\n POST: 2,\n PUT: 3,\n PATCH: 4,\n DELETE: 5,\n OPTIONS: 6,\n HEAD: 7,\n };\n\n private static getMethodPriority(method: string): number {\n return this.METHOD_PRIORITY[method] ?? 999;\n }\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { BasePlugin } from \"@rexeus/typeweaver-gen\";\nimport type { GeneratorContext } from \"@rexeus/typeweaver-gen\";\nimport { RouterGenerator } from \"./RouterGenerator\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\n/**\n * Typeweaver plugin that generates a lightweight, dependency-free server\n * with built-in routing and middleware support.\n *\n * Copies the runtime library files (`TypeweaverApp`, `TypeweaverRouter`, `Router`,\n * `Middleware`, etc.) and generates typed router classes for each resource.\n */\nexport default class ServerPlugin extends BasePlugin {\n public name = \"server\";\n\n /**\n * Generates the server runtime and typed routers for all resources.\n *\n * @param context - The generator context\n */\n public override generate(context: GeneratorContext): void {\n const libSourceDir = path.join(moduleDir, \"lib\");\n this.copyLibFiles(context, libSourceDir, this.name);\n\n RouterGenerator.generate(context);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAuBA,IAAa,kBAAb,MAA6B;;;;;;CAM3B,OAAc,SAAS,SAAiC;EACtD,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;EAC9D,MAAM,eAAe,KAAK,KAAK,WAAW,aAAa,aAAa;AAEpE,OAAK,MAAM,CAAC,YAAY,mBAAmB,OAAO,QAChD,QAAQ,UAAU,gBACnB,CACC,MAAK,YACH,YACA,cACA,eAAe,YACf,QACD;;CAIL,OAAe,YACb,YACA,cACA,oBACA,SACM;EACN,MAAM,uBAAuB,KAAK,OAAO,WAAW;EACpD,MAAM,YAAY,KAAK,KAAK,QAAQ,WAAW,WAAW;EAC1D,MAAM,aAAa,KAAK,KAAK,WAAW,GAAG,qBAAqB,WAAW;EAE3E,MAAM,aAAa,mBAChB,QAAO,aAAY,SAAS,WAAW,WAAW,WAAW,KAAK,CAClE,KAAI,aAAY,KAAK,oBAAoB,SAAS,CAAC,CACnD,MAAM,GAAG,MAAM,KAAK,cAAc,GAAG,EAAE,CAAC;EAE3C,MAAM,UAAU,QAAQ,eAAe,cAAc;GACnD,SAAS,KAAK,SAAS,WAAW,QAAQ,UAAU;GACpD;GACA;GACA;GACD,CAAC;EAEF,MAAM,eAAe,KAAK,SAAS,QAAQ,WAAW,WAAW;AACjE,UAAQ,UAAU,cAAc,QAAQ;;CAG1C,OAAe,oBACb,UACe;EACf,MAAM,YAAY,KAAK,OAAO,SAAS,WAAW,YAAY;AAE9D,SAAO;GACL;GACA,aAAa,SAAS,UAAU;GAChC,QAAQ,SAAS,WAAW;GAC5B,MAAM,SAAS,WAAW;GAC3B;;CAGH,OAAe,cAAc,GAAkB,GAA0B;EACvE,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;EAClD,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;AAGlD,MAAI,UAAU,WAAW,UAAU,OACjC,QAAO,UAAU,SAAS,UAAU;AAItC,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;GACzC,MAAM,WAAW,UAAU;GAC3B,MAAM,WAAW,UAAU;GAE3B,MAAM,WAAW,SAAS,WAAW,IAAI;AAIzC,OAAI,aAHa,SAAS,WAAW,IAAI,CAIvC,QAAO,WAAW,IAAI;AAIxB,OAAI,aAAa,SACf,QAAO,SAAS,cAAc,SAAS;;AAK3C,SAAO,KAAK,kBAAkB,EAAE,OAAO,GAAG,KAAK,kBAAkB,EAAE,OAAO;;CAG5E,OAAwB,kBAA0C;EAChE,KAAK;EACL,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;EACR,SAAS;EACT,MAAM;EACP;CAED,OAAe,kBAAkB,QAAwB;AACvD,SAAO,KAAK,gBAAgB,WAAW;;;;;;ACzH3C,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;;;;;;;;AAS9D,IAAqB,eAArB,cAA0C,WAAW;CACnD,AAAO,OAAO;;;;;;CAOd,AAAgB,SAAS,SAAiC;EACxD,MAAM,eAAe,KAAK,KAAK,WAAW,MAAM;AAChD,OAAK,aAAa,SAAS,cAAc,KAAK,KAAK;AAEnD,kBAAgB,SAAS,QAAQ"}
@@ -5,8 +5,6 @@
5
5
  * @generated by @rexeus/typeweaver
6
6
  */
7
7
 
8
- /* eslint-disable @typescript-eslint/no-empty-object-type */
9
-
10
8
  import type { IHttpResponse } from "@rexeus/typeweaver-core";
11
9
  import type { Middleware } from "./Middleware";
12
10
  import type { ServerContext } from "./ServerContext";
@@ -55,7 +55,6 @@ export type TypeweaverAppOptions = {
55
55
  readonly onError?: (error: unknown) => void;
56
56
  };
57
57
 
58
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
59
58
  export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
60
59
  private static readonly INTERNAL_SERVER_ERROR_BODY = {
61
60
  code: "INTERNAL_SERVER_ERROR",
package/dist/lib/index.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * @generated by @rexeus/typeweaver
6
6
  */
7
7
 
8
- /* eslint-disable import/max-dependencies */
8
+ /* oxlint-disable import/max-dependencies */
9
9
 
10
10
  export { TypeweaverApp, type TypeweaverAppOptions } from "./TypeweaverApp";
11
11
  export {
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver-server",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Generates a lightweight, dependency-free server with built-in routing and middleware from your API definitions. Powered by Typeweaver.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "main": "dist/index.cjs",
8
- "module": "dist/index.js",
9
- "types": "dist/index.d.ts",
8
+ "module": "dist/index.mjs",
9
+ "types": "dist/index.d.mts",
10
10
  "exports": {
11
11
  ".": {
12
12
  "import": {
13
- "types": "./dist/index.d.ts",
14
- "default": "./dist/index.js"
13
+ "types": "./dist/index.d.mts",
14
+ "default": "./dist/index.mjs"
15
15
  },
16
16
  "require": {
17
17
  "types": "./dist/index.d.cts",
@@ -26,14 +26,6 @@
26
26
  "LICENSE",
27
27
  "NOTICE"
28
28
  ],
29
- "scripts": {
30
- "typecheck": "tsc --noEmit",
31
- "format": "prettier --write .",
32
- "build": "tsup && mkdir -p ./dist/templates ./dist/lib && cp -r ./src/templates/* ./dist/templates/ && cp -r ./src/lib/* ./dist/lib/ && cp ../../LICENSE ../../NOTICE ./dist/",
33
- "test": "vitest --run",
34
- "prepack": "npm run build",
35
- "preversion": "npm run build"
36
- },
37
29
  "keywords": [
38
30
  "typeweaver",
39
31
  "plugin",
@@ -55,17 +47,24 @@
55
47
  },
56
48
  "homepage": "https://github.com/rexeus/typeweaver#readme",
57
49
  "peerDependencies": {
58
- "@rexeus/typeweaver-core": "workspace:^",
59
- "@rexeus/typeweaver-gen": "workspace:^"
50
+ "@rexeus/typeweaver-core": "^0.6.0",
51
+ "@rexeus/typeweaver-gen": "^0.6.0"
60
52
  },
61
53
  "devDependencies": {
62
- "@rexeus/typeweaver-core": "workspace:^",
63
- "@rexeus/typeweaver-gen": "workspace:^",
64
54
  "get-port": "^7.1.0",
65
55
  "test-utils": "file:../test-utils",
66
- "tsx": "^4.21.0"
56
+ "tsx": "^4.21.0",
57
+ "@rexeus/typeweaver-core": "^0.6.0",
58
+ "@rexeus/typeweaver-gen": "^0.6.0"
67
59
  },
68
60
  "dependencies": {
69
61
  "case": "^1.6.3"
62
+ },
63
+ "scripts": {
64
+ "typecheck": "tsc --noEmit",
65
+ "format": "oxfmt",
66
+ "build": "tsdown && mkdir -p ./dist/templates ./dist/lib && cp -r ./src/templates/* ./dist/templates/ && cp -r ./src/lib/* ./dist/lib/ && cp ../../LICENSE ../../NOTICE ./dist/",
67
+ "test": "vitest --run",
68
+ "preversion": "npm run build"
70
69
  }
71
- }
70
+ }
package/dist/index.js DELETED
@@ -1,101 +0,0 @@
1
- import path from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
- import { BasePlugin, Path } from '@rexeus/typeweaver-gen';
4
- import { HttpMethod } from '@rexeus/typeweaver-core';
5
- import Case from 'case';
6
-
7
- // src/index.ts
8
- var RouterGenerator = class {
9
- /**
10
- * Generates router files for all resources in the given context.
11
- *
12
- * @param context - The generator context containing resources, templates, and output configuration
13
- */
14
- static generate(context) {
15
- const moduleDir2 = path.dirname(fileURLToPath(import.meta.url));
16
- const templateFile = path.join(moduleDir2, "templates", "Router.ejs");
17
- for (const [entityName, entityResource] of Object.entries(
18
- context.resources.entityResources
19
- )) {
20
- this.writeRouter(
21
- entityName,
22
- templateFile,
23
- entityResource.operations,
24
- context
25
- );
26
- }
27
- }
28
- static writeRouter(entityName, templateFile, operationResources, context) {
29
- const pascalCaseEntityName = Case.pascal(entityName);
30
- const outputDir = path.join(context.outputDir, entityName);
31
- const outputPath = path.join(outputDir, `${pascalCaseEntityName}Router.ts`);
32
- const operations = operationResources.filter((resource) => resource.definition.method !== HttpMethod.HEAD).map((resource) => this.createOperationData(resource)).sort((a, b) => this.compareRoutes(a, b));
33
- const content = context.renderTemplate(templateFile, {
34
- coreDir: Path.relative(outputDir, context.outputDir),
35
- entityName,
36
- pascalCaseEntityName,
37
- operations
38
- });
39
- const relativePath = path.relative(context.outputDir, outputPath);
40
- context.writeFile(relativePath, content);
41
- }
42
- static createOperationData(resource) {
43
- const className = Case.pascal(resource.definition.operationId);
44
- return {
45
- className,
46
- handlerName: `handle${className}Request`,
47
- method: resource.definition.method,
48
- path: resource.definition.path
49
- };
50
- }
51
- static compareRoutes(a, b) {
52
- const aSegments = a.path.split("/").filter((s) => s);
53
- const bSegments = b.path.split("/").filter((s) => s);
54
- if (aSegments.length !== bSegments.length) {
55
- return aSegments.length - bSegments.length;
56
- }
57
- for (let i = 0; i < aSegments.length; i++) {
58
- const aSegment = aSegments[i];
59
- const bSegment = bSegments[i];
60
- const aIsParam = aSegment.startsWith(":");
61
- const bIsParam = bSegment.startsWith(":");
62
- if (aIsParam !== bIsParam) {
63
- return aIsParam ? 1 : -1;
64
- }
65
- if (aSegment !== bSegment) {
66
- return aSegment.localeCompare(bSegment);
67
- }
68
- }
69
- return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);
70
- }
71
- static METHOD_PRIORITY = {
72
- GET: 1,
73
- POST: 2,
74
- PUT: 3,
75
- PATCH: 4,
76
- DELETE: 5,
77
- OPTIONS: 6,
78
- HEAD: 7
79
- };
80
- static getMethodPriority(method) {
81
- return this.METHOD_PRIORITY[method] ?? 999;
82
- }
83
- };
84
-
85
- // src/index.ts
86
- var moduleDir = path.dirname(fileURLToPath(import.meta.url));
87
- var ServerPlugin = class extends BasePlugin {
88
- name = "server";
89
- /**
90
- * Generates the server runtime and typed routers for all resources.
91
- *
92
- * @param context - The generator context
93
- */
94
- generate(context) {
95
- const libSourceDir = path.join(moduleDir, "lib");
96
- this.copyLibFiles(context, libSourceDir, this.name);
97
- RouterGenerator.generate(context);
98
- }
99
- };
100
-
101
- export { ServerPlugin as default };
@@ -1 +0,0 @@
1
- {"inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js":{"bytes":569,"imports":[],"format":"esm"},"src/RouterGenerator.ts":{"bytes":3869,"imports":[{"path":"node:path","kind":"import-statement","external":true},{"path":"node:url","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-core","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-gen","kind":"import-statement","external":true},{"path":"case","kind":"import-statement","external":true},{"path":"/Users/denniswentzien/Development/rexeus/typeweaver-5/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":1036,"imports":[{"path":"node:path","kind":"import-statement","external":true},{"path":"node:url","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-gen","kind":"import-statement","external":true},{"path":"src/RouterGenerator.ts","kind":"import-statement","original":"./RouterGenerator"},{"path":"/Users/denniswentzien/Development/rexeus/typeweaver-5/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/index.cjs":{"imports":[{"path":"node:path","kind":"import-statement","external":true},{"path":"node:url","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-gen","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"node:url","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-core","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-gen","kind":"import-statement","external":true},{"path":"case","kind":"import-statement","external":true}],"exports":["default"],"entryPoint":"src/index.ts","inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js":{"bytesInOutput":314},"src/index.ts":{"bytesInOutput":583},"src/RouterGenerator.ts":{"bytesInOutput":2862}},"bytes":3988}}}
@@ -1 +0,0 @@
1
- {"inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js":{"bytes":322,"imports":[{"path":"node:path","kind":"import-statement","external":true},{"path":"node:url","kind":"import-statement","external":true}],"format":"esm"},"src/RouterGenerator.ts":{"bytes":3869,"imports":[{"path":"node:path","kind":"import-statement","external":true},{"path":"node:url","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-core","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-gen","kind":"import-statement","external":true},{"path":"case","kind":"import-statement","external":true},{"path":"/Users/denniswentzien/Development/rexeus/typeweaver-5/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":1036,"imports":[{"path":"node:path","kind":"import-statement","external":true},{"path":"node:url","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-gen","kind":"import-statement","external":true},{"path":"src/RouterGenerator.ts","kind":"import-statement","original":"./RouterGenerator"},{"path":"/Users/denniswentzien/Development/rexeus/typeweaver-5/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/index.js":{"imports":[{"path":"node:path","kind":"import-statement","external":true},{"path":"node:url","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-gen","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"node:url","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-core","kind":"import-statement","external":true},{"path":"@rexeus/typeweaver-gen","kind":"import-statement","external":true},{"path":"case","kind":"import-statement","external":true}],"exports":["default"],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":585},"src/RouterGenerator.ts":{"bytesInOutput":2864}},"bytes":3547}}}