@rexeus/typeweaver-server 0.8.0 → 0.9.1

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
@@ -46,7 +46,7 @@ npm install @rexeus/typeweaver-core
46
46
  ## 💡 How to use
47
47
 
48
48
  ```bash
49
- npx typeweaver generate --input ./api/definition --output ./api/generated --plugins server
49
+ npx typeweaver generate --input ./api/spec/index.ts --output ./api/generated --plugins server
50
50
  ```
51
51
 
52
52
  More on the CLI in
package/dist/index.cjs CHANGED
@@ -6,16 +6,12 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
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
- }
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
19
15
  }
20
16
  return to;
21
17
  };
@@ -23,7 +19,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
19
  value: mod,
24
20
  enumerable: true
25
21
  }) : target, mod));
26
-
27
22
  //#endregion
28
23
  let node_path = require("node:path");
29
24
  node_path = __toESM(node_path);
@@ -32,77 +27,48 @@ let _rexeus_typeweaver_gen = require("@rexeus/typeweaver-gen");
32
27
  let _rexeus_typeweaver_core = require("@rexeus/typeweaver-core");
33
28
  let case$1 = require("case");
34
29
  case$1 = __toESM(case$1);
35
-
36
- //#region src/RouterGenerator.ts
30
+ //#region src/routerGenerator.ts
37
31
  /**
38
32
  * Generates TypeweaverRouter subclasses from API definitions.
39
33
  *
40
34
  * For each resource (e.g., `Todo`, `Account`), produces a `<ResourceName>Router.ts`
41
35
  * file that extends `TypeweaverRouter` and registers all operations as routes.
42
36
  */
43
- var RouterGenerator = class {
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 operationId = resource.definition.operationId;
70
- const className = case$1.default.pascal(operationId);
71
- return {
72
- operationId,
73
- className,
74
- handlerName: `handle${className}Request`,
75
- method: resource.definition.method,
76
- path: resource.definition.path
77
- };
78
- }
79
- static compareRoutes(a, b) {
80
- const aSegments = a.path.split("/").filter((s) => s);
81
- const bSegments = b.path.split("/").filter((s) => s);
82
- if (aSegments.length !== bSegments.length) return aSegments.length - bSegments.length;
83
- for (let i = 0; i < aSegments.length; i++) {
84
- const aSegment = aSegments[i];
85
- const bSegment = bSegments[i];
86
- const aIsParam = aSegment.startsWith(":");
87
- if (aIsParam !== bSegment.startsWith(":")) return aIsParam ? 1 : -1;
88
- if (aSegment !== bSegment) return aSegment.localeCompare(bSegment);
89
- }
90
- return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);
91
- }
92
- static METHOD_PRIORITY = {
93
- GET: 1,
94
- POST: 2,
95
- PUT: 3,
96
- PATCH: 4,
97
- DELETE: 5,
98
- OPTIONS: 6,
99
- HEAD: 7
37
+ /**
38
+ * Generates router files for all resources in the given context.
39
+ *
40
+ * @param context - The generator context containing resources, templates, and output configuration
41
+ */
42
+ function generate(context) {
43
+ const moduleDir = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
44
+ const templateFile = node_path.default.join(moduleDir, "templates", "Router.ejs");
45
+ for (const resource of context.normalizedSpec.resources) writeRouter(resource, templateFile, context);
46
+ }
47
+ function writeRouter(resource, templateFile, context) {
48
+ const pascalCaseEntityName = case$1.default.pascal(resource.name);
49
+ const outputDir = context.getResourceOutputDir(resource.name);
50
+ const outputPath = node_path.default.join(outputDir, `${pascalCaseEntityName}Router.ts`);
51
+ const operations = resource.operations.filter((operation) => operation.method !== _rexeus_typeweaver_core.HttpMethod.HEAD).map((operation) => createOperationData(operation)).sort((a, b) => (0, _rexeus_typeweaver_gen.compareRoutes)(a, b));
52
+ const content = context.renderTemplate(templateFile, {
53
+ coreDir: (0, _rexeus_typeweaver_gen.relative)(outputDir, context.outputDir),
54
+ entityName: resource.name,
55
+ pascalCaseEntityName,
56
+ operations
57
+ });
58
+ const relativePath = node_path.default.relative(context.outputDir, outputPath);
59
+ context.writeFile(relativePath, content);
60
+ }
61
+ function createOperationData(operation) {
62
+ const operationId = operation.operationId;
63
+ const className = case$1.default.pascal(operationId);
64
+ return {
65
+ operationId,
66
+ className,
67
+ handlerName: `handle${className}Request`,
68
+ method: operation.method,
69
+ path: operation.path
100
70
  };
101
- static getMethodPriority(method) {
102
- return this.METHOD_PRIORITY[method] ?? 999;
103
- }
104
- };
105
-
71
+ }
106
72
  //#endregion
107
73
  //#region src/index.ts
108
74
  const moduleDir = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
@@ -123,9 +89,8 @@ var ServerPlugin = class extends _rexeus_typeweaver_gen.BasePlugin {
123
89
  generate(context) {
124
90
  const libSourceDir = node_path.default.join(moduleDir, "lib");
125
91
  this.copyLibFiles(context, libSourceDir, this.name);
126
- RouterGenerator.generate(context);
92
+ generate(context);
127
93
  }
128
94
  };
129
-
130
95
  //#endregion
131
- module.exports = ServerPlugin;
96
+ module.exports = ServerPlugin;
package/dist/index.mjs CHANGED
@@ -1,79 +1,50 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- import { BasePlugin, Path } from "@rexeus/typeweaver-gen";
3
+ import { BasePlugin, compareRoutes, relative } from "@rexeus/typeweaver-gen";
4
4
  import { HttpMethod } from "@rexeus/typeweaver-core";
5
5
  import Case from "case";
6
-
7
- //#region src/RouterGenerator.ts
6
+ //#region src/routerGenerator.ts
8
7
  /**
9
8
  * Generates TypeweaverRouter subclasses from API definitions.
10
9
  *
11
10
  * For each resource (e.g., `Todo`, `Account`), produces a `<ResourceName>Router.ts`
12
11
  * file that extends `TypeweaverRouter` and registers all operations as routes.
13
12
  */
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 operationId = resource.definition.operationId;
41
- const className = Case.pascal(operationId);
42
- return {
43
- operationId,
44
- className,
45
- handlerName: `handle${className}Request`,
46
- method: resource.definition.method,
47
- path: resource.definition.path
48
- };
49
- }
50
- static compareRoutes(a, b) {
51
- const aSegments = a.path.split("/").filter((s) => s);
52
- const bSegments = b.path.split("/").filter((s) => s);
53
- if (aSegments.length !== bSegments.length) return aSegments.length - bSegments.length;
54
- for (let i = 0; i < aSegments.length; i++) {
55
- const aSegment = aSegments[i];
56
- const bSegment = bSegments[i];
57
- const aIsParam = aSegment.startsWith(":");
58
- if (aIsParam !== bSegment.startsWith(":")) return aIsParam ? 1 : -1;
59
- if (aSegment !== bSegment) return aSegment.localeCompare(bSegment);
60
- }
61
- return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);
62
- }
63
- static METHOD_PRIORITY = {
64
- GET: 1,
65
- POST: 2,
66
- PUT: 3,
67
- PATCH: 4,
68
- DELETE: 5,
69
- OPTIONS: 6,
70
- HEAD: 7
13
+ /**
14
+ * Generates router files for all resources in the given context.
15
+ *
16
+ * @param context - The generator context containing resources, templates, and output configuration
17
+ */
18
+ function generate(context) {
19
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
20
+ const templateFile = path.join(moduleDir, "templates", "Router.ejs");
21
+ for (const resource of context.normalizedSpec.resources) writeRouter(resource, templateFile, context);
22
+ }
23
+ function writeRouter(resource, templateFile, context) {
24
+ const pascalCaseEntityName = Case.pascal(resource.name);
25
+ const outputDir = context.getResourceOutputDir(resource.name);
26
+ const outputPath = path.join(outputDir, `${pascalCaseEntityName}Router.ts`);
27
+ const operations = resource.operations.filter((operation) => operation.method !== HttpMethod.HEAD).map((operation) => createOperationData(operation)).sort((a, b) => compareRoutes(a, b));
28
+ const content = context.renderTemplate(templateFile, {
29
+ coreDir: relative(outputDir, context.outputDir),
30
+ entityName: resource.name,
31
+ pascalCaseEntityName,
32
+ operations
33
+ });
34
+ const relativePath = path.relative(context.outputDir, outputPath);
35
+ context.writeFile(relativePath, content);
36
+ }
37
+ function createOperationData(operation) {
38
+ const operationId = operation.operationId;
39
+ const className = Case.pascal(operationId);
40
+ return {
41
+ operationId,
42
+ className,
43
+ handlerName: `handle${className}Request`,
44
+ method: operation.method,
45
+ path: operation.path
71
46
  };
72
- static getMethodPriority(method) {
73
- return this.METHOD_PRIORITY[method] ?? 999;
74
- }
75
- };
76
-
47
+ }
77
48
  //#endregion
78
49
  //#region src/index.ts
79
50
  const moduleDir = path.dirname(fileURLToPath(import.meta.url));
@@ -94,10 +65,10 @@ var ServerPlugin = class extends BasePlugin {
94
65
  generate(context) {
95
66
  const libSourceDir = path.join(moduleDir, "lib");
96
67
  this.copyLibFiles(context, libSourceDir, this.name);
97
- RouterGenerator.generate(context);
68
+ generate(context);
98
69
  }
99
70
  };
100
-
101
71
  //#endregion
102
72
  export { ServerPlugin as default };
73
+
103
74
  //# sourceMappingURL=index.mjs.map
@@ -1 +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 operationId: string;\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 operationId = resource.definition.operationId;\n const className = Case.pascal(operationId);\n\n return {\n operationId,\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":";;;;;;;;;;;;;AAwBA,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,cAAc,SAAS,WAAW;EACxC,MAAM,YAAY,KAAK,OAAO,YAAY;AAE1C,SAAO;GACL;GACA;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;;;;;;AC5H3C,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"}
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 { compareRoutes, relative } from \"@rexeus/typeweaver-gen\";\nimport type {\n GeneratorContext,\n NormalizedOperation,\n NormalizedResource,\n} from \"@rexeus/typeweaver-gen\";\nimport Case from \"case\";\n\ntype OperationData = {\n readonly operationId: string;\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 */\n\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 */\nexport function 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 resource of context.normalizedSpec.resources) {\n writeRouter(resource, templateFile, context);\n }\n}\n\nfunction writeRouter(\n resource: NormalizedResource,\n templateFile: string,\n context: GeneratorContext\n): void {\n const pascalCaseEntityName = Case.pascal(resource.name);\n const outputDir = context.getResourceOutputDir(resource.name);\n const outputPath = path.join(outputDir, `${pascalCaseEntityName}Router.ts`);\n\n const operations = resource.operations\n .filter(operation => operation.method !== HttpMethod.HEAD)\n .map(operation => createOperationData(operation))\n .sort((a, b) => compareRoutes(a, b));\n\n const content = context.renderTemplate(templateFile, {\n coreDir: relative(outputDir, context.outputDir),\n entityName: resource.name,\n pascalCaseEntityName,\n operations,\n });\n\n const relativePath = path.relative(context.outputDir, outputPath);\n context.writeFile(relativePath, content);\n}\n\nfunction createOperationData(operation: NormalizedOperation): OperationData {\n const operationId = operation.operationId;\n const className = Case.pascal(operationId);\n\n return {\n operationId,\n className,\n handlerName: `handle${className}Request`,\n method: operation.method,\n path: operation.path,\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 { generate as generateRouters } 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 generateRouters(context);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA+BA,SAAgB,SAAS,SAAiC;CACxD,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;CAC9D,MAAM,eAAe,KAAK,KAAK,WAAW,aAAa,aAAa;AAEpE,MAAK,MAAM,YAAY,QAAQ,eAAe,UAC5C,aAAY,UAAU,cAAc,QAAQ;;AAIhD,SAAS,YACP,UACA,cACA,SACM;CACN,MAAM,uBAAuB,KAAK,OAAO,SAAS,KAAK;CACvD,MAAM,YAAY,QAAQ,qBAAqB,SAAS,KAAK;CAC7D,MAAM,aAAa,KAAK,KAAK,WAAW,GAAG,qBAAqB,WAAW;CAE3E,MAAM,aAAa,SAAS,WACzB,QAAO,cAAa,UAAU,WAAW,WAAW,KAAK,CACzD,KAAI,cAAa,oBAAoB,UAAU,CAAC,CAChD,MAAM,GAAG,MAAM,cAAc,GAAG,EAAE,CAAC;CAEtC,MAAM,UAAU,QAAQ,eAAe,cAAc;EACnD,SAAS,SAAS,WAAW,QAAQ,UAAU;EAC/C,YAAY,SAAS;EACrB;EACA;EACD,CAAC;CAEF,MAAM,eAAe,KAAK,SAAS,QAAQ,WAAW,WAAW;AACjE,SAAQ,UAAU,cAAc,QAAQ;;AAG1C,SAAS,oBAAoB,WAA+C;CAC1E,MAAM,cAAc,UAAU;CAC9B,MAAM,YAAY,KAAK,OAAO,YAAY;AAE1C,QAAO;EACL;EACA;EACA,aAAa,SAAS,UAAU;EAChC,QAAQ,UAAU;EAClB,MAAM,UAAU;EACjB;;;;ACrEH,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;;;;;;;;AAS9D,IAAqB,eAArB,cAA0C,WAAW;CACnD,OAAc;;;;;;CAOd,SAAyB,SAAiC;EACxD,MAAM,eAAe,KAAK,KAAK,WAAW,MAAM;AAChD,OAAK,aAAa,SAAS,cAAc,KAAK,KAAK;AAEnD,WAAgB,QAAQ"}
@@ -5,6 +5,11 @@
5
5
  * @generated by @rexeus/typeweaver
6
6
  */
7
7
 
8
+ import {
9
+ createDefaultErrorBody,
10
+ internalServerErrorDefaultError,
11
+ payloadTooLargeDefaultError,
12
+ } from "@rexeus/typeweaver-core";
8
13
  import { PayloadTooLargeError } from "./Errors";
9
14
  import type { TypeweaverApp } from "./TypeweaverApp";
10
15
  import type { IncomingMessage, ServerResponse } from "node:http";
@@ -73,26 +78,24 @@ async function handleRequest(
73
78
  } catch (error) {
74
79
  if (error instanceof PayloadTooLargeError) {
75
80
  if (!res.headersSent) {
76
- res.writeHead(413, { "content-type": "application/json" });
81
+ res.writeHead(payloadTooLargeDefaultError.statusCode, {
82
+ "content-type": "application/json",
83
+ });
77
84
  }
78
85
  res.end(
79
- JSON.stringify({
80
- code: "PAYLOAD_TOO_LARGE",
81
- message: "Request body exceeds the size limit",
82
- })
86
+ JSON.stringify(createDefaultErrorBody(payloadTooLargeDefaultError))
83
87
  );
84
88
  return;
85
89
  }
86
90
 
87
91
  console.error(error);
88
92
  if (!res.headersSent) {
89
- res.writeHead(500, { "content-type": "application/json" });
93
+ res.writeHead(internalServerErrorDefaultError.statusCode, {
94
+ "content-type": "application/json",
95
+ });
90
96
  }
91
97
  res.end(
92
- JSON.stringify({
93
- code: "INTERNAL_SERVER_ERROR",
94
- message: "An unexpected error occurred",
95
- })
98
+ JSON.stringify(createDefaultErrorBody(internalServerErrorDefaultError))
96
99
  );
97
100
  }
98
101
  }
@@ -6,8 +6,16 @@
6
6
  */
7
7
 
8
8
  import {
9
+ badRequestDefaultError,
10
+ createDefaultErrorBody,
11
+ createDefaultErrorResponse,
12
+ internalServerErrorDefaultError,
9
13
  isTypedHttpResponse,
14
+ methodNotAllowedDefaultError,
15
+ notFoundDefaultError,
16
+ payloadTooLargeDefaultError,
10
17
  RequestValidationError,
18
+ validationDefaultError,
11
19
  } from "@rexeus/typeweaver-core";
12
20
  import type { IHttpResponse } from "@rexeus/typeweaver-core";
13
21
  import { BodyParseError, PayloadTooLargeError } from "./Errors";
@@ -61,10 +69,9 @@ export type TypeweaverAppOptions = {
61
69
  };
62
70
 
63
71
  export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
64
- private static readonly INTERNAL_SERVER_ERROR_BODY = {
65
- code: "INTERNAL_SERVER_ERROR",
66
- message: "An unexpected error occurred",
67
- } as const;
72
+ private static readonly INTERNAL_SERVER_ERROR_BODY = createDefaultErrorBody(
73
+ internalServerErrorDefaultError
74
+ );
68
75
 
69
76
  private readonly router = new Router();
70
77
  private readonly middlewares: Middleware[] = [];
@@ -168,19 +175,14 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
168
175
  } catch (error) {
169
176
  if (error instanceof PayloadTooLargeError) {
170
177
  this.safeOnError(error);
171
- return this.adapter.toResponse({
172
- statusCode: 413,
173
- body: {
174
- code: "PAYLOAD_TOO_LARGE",
175
- message: "Request body exceeds the size limit",
176
- },
177
- });
178
+ return this.adapter.toResponse(
179
+ createDefaultErrorResponse(payloadTooLargeDefaultError)
180
+ );
178
181
  }
179
182
  if (error instanceof BodyParseError) {
180
- return this.adapter.toResponse({
181
- statusCode: 400,
182
- body: { code: "BAD_REQUEST", message: "Malformed request body" },
183
- });
183
+ return this.adapter.toResponse(
184
+ createDefaultErrorResponse(badRequestDefaultError)
185
+ );
184
186
  }
185
187
  this.safeOnError(error);
186
188
  return TypeweaverApp.createErrorResponse();
@@ -243,20 +245,12 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
243
245
 
244
246
  const pathMatch = this.router.matchPath(pathname);
245
247
  if (pathMatch) {
246
- return {
247
- statusCode: 405,
248
+ return createDefaultErrorResponse(methodNotAllowedDefaultError, {
248
249
  header: { Allow: pathMatch.allowedMethods.join(", ") },
249
- body: {
250
- code: "METHOD_NOT_ALLOWED",
251
- message: "Method not supported for this resource",
252
- },
253
- };
250
+ });
254
251
  }
255
252
 
256
- return {
257
- statusCode: 404,
258
- body: { code: "NOT_FOUND", message: "No matching resource found" },
259
- };
253
+ return createDefaultErrorResponse(notFoundDefaultError);
260
254
  }
261
255
 
262
256
  private withPathParams(
@@ -444,20 +438,17 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
444
438
  if (param) issues.param = param;
445
439
 
446
440
  return {
447
- statusCode: 400,
441
+ statusCode: validationDefaultError.statusCode,
448
442
  body: {
449
- code: "VALIDATION_ERROR",
450
- message: err.message,
443
+ ...createDefaultErrorBody(validationDefaultError),
451
444
  issues,
452
445
  },
453
446
  };
454
447
  };
455
448
 
456
449
  private static defaultResponseValidationHandler: ResponseValidationErrorHandler =
457
- (): IHttpResponse => ({
458
- statusCode: 500,
459
- body: TypeweaverApp.INTERNAL_SERVER_ERROR_BODY,
460
- });
450
+ (): IHttpResponse =>
451
+ createDefaultErrorResponse(internalServerErrorDefaultError);
461
452
 
462
453
  private static defaultHttpResponseHandler: HttpResponseErrorHandler = (
463
454
  err
@@ -467,14 +458,17 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
467
458
  error
468
459
  ): IHttpResponse => {
469
460
  this.safeOnError(error);
470
- return { statusCode: 500, body: TypeweaverApp.INTERNAL_SERVER_ERROR_BODY };
461
+ return {
462
+ statusCode: internalServerErrorDefaultError.statusCode,
463
+ body: TypeweaverApp.INTERNAL_SERVER_ERROR_BODY,
464
+ };
471
465
  };
472
466
 
473
467
  private static createErrorResponse(): Response {
474
468
  return new Response(
475
469
  JSON.stringify(TypeweaverApp.INTERNAL_SERVER_ERROR_BODY),
476
470
  {
477
- status: 500,
471
+ status: internalServerErrorDefaultError.statusCode,
478
472
  headers: { "content-type": "application/json" },
479
473
  }
480
474
  );
@@ -1,3 +1,7 @@
1
+ import {
2
+ createDefaultErrorBody,
3
+ unauthorizedDefaultError,
4
+ } from "@rexeus/typeweaver-core";
1
5
  import type { IHttpResponse } from "@rexeus/typeweaver-core";
2
6
  import { defineMiddleware } from "../TypedMiddleware";
3
7
  import type { ServerContext } from "../ServerContext";
@@ -9,7 +13,6 @@ export type BasicAuthOptions = {
9
13
  ctx: ServerContext
10
14
  ) => boolean | Promise<boolean>;
11
15
  readonly realm?: string;
12
- readonly unauthorizedMessage?: string;
13
16
  readonly onUnauthorized?: (ctx: ServerContext) => IHttpResponse;
14
17
  };
15
18
 
@@ -17,12 +20,11 @@ const BASIC_PREFIX = "Basic ";
17
20
 
18
21
  export function basicAuth(options: BasicAuthOptions) {
19
22
  const realm = options.realm ?? "Secure Area";
20
- const message = options.unauthorizedMessage ?? "Unauthorized";
21
23
 
22
24
  const defaultResponse: IHttpResponse = {
23
- statusCode: 401,
25
+ statusCode: unauthorizedDefaultError.statusCode,
24
26
  header: { "www-authenticate": `Basic realm="${realm}"` },
25
- body: { code: "UNAUTHORIZED", message },
27
+ body: createDefaultErrorBody(unauthorizedDefaultError),
26
28
  };
27
29
 
28
30
  const deny = (ctx: ServerContext): IHttpResponse =>
@@ -1,3 +1,7 @@
1
+ import {
2
+ createDefaultErrorBody,
3
+ unauthorizedDefaultError,
4
+ } from "@rexeus/typeweaver-core";
1
5
  import type { IHttpResponse } from "@rexeus/typeweaver-core";
2
6
  import { defineMiddleware } from "../TypedMiddleware";
3
7
  import type { ServerContext } from "../ServerContext";
@@ -8,7 +12,6 @@ export type BearerAuthOptions = {
8
12
  ctx: ServerContext
9
13
  ) => boolean | Promise<boolean>;
10
14
  readonly realm?: string;
11
- readonly unauthorizedMessage?: string;
12
15
  readonly onUnauthorized?: (ctx: ServerContext) => IHttpResponse;
13
16
  };
14
17
 
@@ -16,12 +19,11 @@ const BEARER_PREFIX = "Bearer ";
16
19
 
17
20
  export function bearerAuth(options: BearerAuthOptions) {
18
21
  const realm = options.realm ?? "Secure Area";
19
- const message = options.unauthorizedMessage ?? "Unauthorized";
20
22
 
21
23
  const defaultResponse: IHttpResponse = {
22
- statusCode: 401,
24
+ statusCode: unauthorizedDefaultError.statusCode,
23
25
  header: { "www-authenticate": `Bearer realm="${realm}"` },
24
- body: { code: "UNAUTHORIZED", message },
26
+ body: createDefaultErrorBody(unauthorizedDefaultError),
25
27
  };
26
28
 
27
29
  const deny = (ctx: ServerContext): IHttpResponse =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver-server",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
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,
@@ -47,15 +47,15 @@
47
47
  },
48
48
  "homepage": "https://github.com/rexeus/typeweaver#readme",
49
49
  "peerDependencies": {
50
- "@rexeus/typeweaver-gen": "^0.8.0",
51
- "@rexeus/typeweaver-core": "^0.8.0"
50
+ "@rexeus/typeweaver-gen": "^0.9.1",
51
+ "@rexeus/typeweaver-core": "^0.9.1"
52
52
  },
53
53
  "devDependencies": {
54
- "get-port": "^7.1.0",
54
+ "get-port": "^7.2.0",
55
55
  "test-utils": "file:../test-utils",
56
56
  "tsx": "^4.21.0",
57
- "@rexeus/typeweaver-core": "^0.8.0",
58
- "@rexeus/typeweaver-gen": "^0.8.0"
57
+ "@rexeus/typeweaver-core": "^0.9.1",
58
+ "@rexeus/typeweaver-gen": "^0.9.1"
59
59
  },
60
60
  "dependencies": {
61
61
  "case": "^1.6.3"