@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 +1 -1
- package/dist/index.cjs +43 -78
- package/dist/index.mjs +38 -67
- package/dist/index.mjs.map +1 -1
- package/dist/lib/NodeAdapter.ts +13 -10
- package/dist/lib/TypeweaverApp.ts +29 -35
- package/dist/lib/middleware/basicAuth.ts +6 -4
- package/dist/lib/middleware/bearerAuth.ts +6 -4
- package/package.json +6 -6
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/
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
generate(context);
|
|
98
69
|
}
|
|
99
70
|
};
|
|
100
|
-
|
|
101
71
|
//#endregion
|
|
102
72
|
export { ServerPlugin as default };
|
|
73
|
+
|
|
103
74
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/
|
|
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"}
|
package/dist/lib/NodeAdapter.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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:
|
|
441
|
+
statusCode: validationDefaultError.statusCode,
|
|
448
442
|
body: {
|
|
449
|
-
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
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:
|
|
25
|
+
statusCode: unauthorizedDefaultError.statusCode,
|
|
24
26
|
header: { "www-authenticate": `Basic realm="${realm}"` },
|
|
25
|
-
body:
|
|
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:
|
|
24
|
+
statusCode: unauthorizedDefaultError.statusCode,
|
|
23
25
|
header: { "www-authenticate": `Bearer realm="${realm}"` },
|
|
24
|
-
body:
|
|
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.
|
|
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.
|
|
51
|
-
"@rexeus/typeweaver-core": "^0.
|
|
50
|
+
"@rexeus/typeweaver-gen": "^0.9.1",
|
|
51
|
+
"@rexeus/typeweaver-core": "^0.9.1"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"get-port": "^7.
|
|
54
|
+
"get-port": "^7.2.0",
|
|
55
55
|
"test-utils": "file:../test-utils",
|
|
56
56
|
"tsx": "^4.21.0",
|
|
57
|
-
"@rexeus/typeweaver-core": "^0.
|
|
58
|
-
"@rexeus/typeweaver-gen": "^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"
|