@fy-tools/rpc-server-expressjs 0.0.119-alpha.10387 → 0.0.119-alpha.10389
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 +146 -4
- package/dist/lib/app.d.ts.map +1 -1
- package/dist/lib/app.js +3 -1
- package/dist/lib/controller.d.ts.map +1 -1
- package/dist/lib/controller.js +5 -2
- package/dist/lib/route.d.ts +3 -2
- package/dist/lib/route.d.ts.map +1 -1
- package/dist/lib/route.js +24 -26
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,7 +1,149 @@
|
|
|
1
|
-
# rpc-server-
|
|
1
|
+
# @fy-tools/rpc-server-expressjs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Express adapter for `@fy-tools/rpc-server`. Pass your app schema and an Express instance — routes are registered automatically, request validation is handled for you, and handler context is fully typed.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @fy-tools/rpc-server-expressjs express
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### 1. Define a schema
|
|
14
|
+
|
|
15
|
+
Use `@fy-tools/rpc-server` to define your API surface. See its [README](../rpc-server/README.md) for full details.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// schema.ts
|
|
19
|
+
import { App, Controller, HttpMethod, Route } from '@fy-tools/rpc-server';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
|
|
22
|
+
export const Schema = new App()
|
|
23
|
+
.controller(
|
|
24
|
+
new Controller('auth')
|
|
25
|
+
.route(
|
|
26
|
+
new Route('login', HttpMethod.POST)
|
|
27
|
+
.body(z.object({ email: z.string(), password: z.string() }))
|
|
28
|
+
.response(z.object({ token: z.string() }))
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
.controller(
|
|
32
|
+
new Controller('users')
|
|
33
|
+
.route(
|
|
34
|
+
new Route('/', HttpMethod.GET)
|
|
35
|
+
.response(z.object({
|
|
36
|
+
items: z.array(z.object({ id: z.string(), email: z.string() })),
|
|
37
|
+
}))
|
|
38
|
+
)
|
|
39
|
+
.route(
|
|
40
|
+
new Route(':id', HttpMethod.GET)
|
|
41
|
+
.params(z.object({ id: z.string() }))
|
|
42
|
+
.response(z.object({ id: z.string(), email: z.string() }))
|
|
43
|
+
)
|
|
44
|
+
.route(
|
|
45
|
+
new Route(':id', HttpMethod.DELETE)
|
|
46
|
+
.params(z.object({ id: z.string() }))
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
export type Schema = typeof Schema;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Create the server
|
|
54
|
+
|
|
55
|
+
Add any Express middleware before passing the app to `App` — JSON parsing is required for body validation to work.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { App } from '@fy-tools/rpc-server-expressjs';
|
|
59
|
+
import express from 'express';
|
|
60
|
+
import { Schema } from './schema';
|
|
61
|
+
|
|
62
|
+
const expressApp = express();
|
|
63
|
+
expressApp.use(express.json());
|
|
64
|
+
|
|
65
|
+
const server = new App(expressApp, Schema);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Implement handlers
|
|
69
|
+
|
|
70
|
+
Access controllers via `.C` and routes via `.R`. For path encoding rules, see the [rpc-server README](../rpc-server/README.md#path-encoding).
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
server.C.auth.R.post_login.handler(async (ctx) => {
|
|
74
|
+
const token = await authenticate(ctx.body.email, ctx.body.password);
|
|
75
|
+
return { token };
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
server.C.users.R.get_default.handler(async (ctx) => {
|
|
79
|
+
return { items: await db.users.findAll() };
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
server.C.users.R.get_$id.handler(async (ctx) => {
|
|
83
|
+
return db.users.findById(ctx.params.id);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
server.C.users.R.delete_$id.handler(async (ctx) => {
|
|
87
|
+
await db.users.delete(ctx.params.id);
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 4. Start the server
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
server._app.listen(3000);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Handler context
|
|
100
|
+
|
|
101
|
+
The context passed to each handler contains:
|
|
102
|
+
|
|
103
|
+
| Property | Type | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `body` | Inferred from schema | Validated and parsed request body |
|
|
106
|
+
| `params` | Inferred from schema | Validated URL path parameters |
|
|
107
|
+
| `query` | Inferred from schema | Validated query string parameters |
|
|
108
|
+
| `req` | `express.Request` | Raw Express request object |
|
|
109
|
+
| `res` | `express.Response` | Raw Express response object |
|
|
110
|
+
|
|
111
|
+
Validation runs automatically before the handler is called. If body, params, or query fail their schema, the route responds with `400` and a validation error — the handler is not invoked.
|
|
112
|
+
|
|
113
|
+
The return value of the handler is automatically sent as JSON. If you need to send a custom response (e.g. set headers, stream a file), call `res` directly and return anything — the adapter skips auto-send if headers have already been sent.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## API reference
|
|
118
|
+
|
|
119
|
+
### `App`
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
new App(app: Application, schema: AnyApp)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Registers all controllers from the schema onto the Express instance.
|
|
126
|
+
|
|
127
|
+
| Member | Description |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `.C` | Typed proxy of all controllers, keyed by encoded path |
|
|
130
|
+
| `.build(fn)` | Transforms the Express app and returns a new `App` |
|
|
131
|
+
| `._app` | The underlying Express instance — call `.listen()` on this |
|
|
132
|
+
|
|
133
|
+
### `Controller`
|
|
134
|
+
|
|
135
|
+
Instantiated automatically by `App`. Creates an Express `Router` and mounts it at the controller's base path.
|
|
136
|
+
|
|
137
|
+
| Member | Description |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `.R` | Typed proxy of all routes in the controller, keyed by encoded method + path |
|
|
140
|
+
| `.build(fn)` | Transforms the Express app and returns a new `Controller` |
|
|
141
|
+
|
|
142
|
+
### `Route`
|
|
143
|
+
|
|
144
|
+
Instantiated automatically by `Controller`. Wraps a single route schema.
|
|
145
|
+
|
|
146
|
+
| Member | Description |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `.handler(fn)` | Sets the request handler — called from `.R` on a controller |
|
|
149
|
+
| `.build(fn)` | Transforms the Express router and returns a new `Route` |
|
package/dist/lib/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/lib/app.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,qBAAa,GAAG,CAAC,UAAU,SAAS,WAAW,GAAG,WAAW,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM;IAYxE,IAAI,EAAE,UAAU;IAAS,OAAO,EAAE,MAAM;IAX3D;;;SAGK;IACE,CAAC,EAAS,GACd,GAAG,IAAI,kBAAkB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,CACrE,UAAU,EACV,oBAAoB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAC1D,GACF,CAAC;gBAEiB,IAAI,EAAE,UAAU,EAAS,OAAO,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/lib/app.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,qBAAa,GAAG,CAAC,UAAU,SAAS,WAAW,GAAG,WAAW,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM;IAYxE,IAAI,EAAE,UAAU;IAAS,OAAO,EAAE,MAAM;IAX3D;;;SAGK;IACE,CAAC,EAAS,GACd,GAAG,IAAI,kBAAkB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,CACrE,UAAU,EACV,oBAAoB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAC1D,GACF,CAAC;gBAEiB,IAAI,EAAE,UAAU,EAAS,OAAO,EAAE,MAAM;IAY3D,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,CAAC;CAGxD"}
|
package/dist/lib/app.js
CHANGED
|
@@ -14,7 +14,9 @@ class App {
|
|
|
14
14
|
this._app = _app;
|
|
15
15
|
this._schema = _schema;
|
|
16
16
|
for (const i in _schema._controllers_map) {
|
|
17
|
-
|
|
17
|
+
const controller = new controller_1.Controller(this._app, _schema._controllers[_schema._controllers_map[i]]);
|
|
18
|
+
this._app = controller._app;
|
|
19
|
+
this.C[i] = controller;
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
build(fn) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/lib/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,aAAa,EACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,qBAAa,UAAU,CACrB,GAAG,SAAS,WAAW,GAAG,WAAW,EACrC,MAAM,SAAS,aAAa,GAAG,aAAa;IAQzB,IAAI,EAAE,GAAG;IAAS,OAAO,EAAE,MAAM;IANpD;;;SAGK;IACE,CAAC,
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/lib/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,aAAa,EACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,qBAAa,UAAU,CACrB,GAAG,SAAS,WAAW,GAAG,WAAW,EACrC,MAAM,SAAS,aAAa,GAAG,aAAa;IAQzB,IAAI,EAAE,GAAG;IAAS,OAAO,EAAE,MAAM;IANpD;;;SAGK;IACE,CAAC,KAuBD,GAAG,iHAvBD;gBAEU,IAAI,EAAE,GAAG,EAAS,OAAO,EAAE,MAAM;IAkCpD,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;CAGjD"}
|
package/dist/lib/controller.js
CHANGED
|
@@ -17,11 +17,14 @@ class Controller {
|
|
|
17
17
|
this._schema = _schema;
|
|
18
18
|
const router = express_1.default.Router();
|
|
19
19
|
const routes = {};
|
|
20
|
+
let currentRouter = router;
|
|
20
21
|
for (const i in _schema._routes_map) {
|
|
21
|
-
|
|
22
|
+
const route = new route_1.Route(currentRouter, _schema._routes[_schema._routes_map[i]]);
|
|
23
|
+
currentRouter = route._app;
|
|
24
|
+
routes[i] = route;
|
|
22
25
|
}
|
|
23
26
|
const basePath = this._schema._basePath ? `/${this._schema._basePath}` : '/';
|
|
24
|
-
this._app.use(basePath,
|
|
27
|
+
this._app.use(basePath, currentRouter);
|
|
25
28
|
this.R = new Proxy(routes, {
|
|
26
29
|
get(target, p) {
|
|
27
30
|
return routes[p];
|
package/dist/lib/route.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { AnyRoute, Response } from '@fy-tools/rpc-server';
|
|
2
2
|
import type { Router } from 'express';
|
|
3
3
|
import type { RouteToContext } from './types';
|
|
4
4
|
export declare class Route<App extends Router = Router, Schema extends AnyRoute = AnyRoute> {
|
|
5
5
|
_app: App;
|
|
6
6
|
_schema: Schema;
|
|
7
|
+
private readonly _handlerRef;
|
|
7
8
|
constructor(_app: App, _schema: Schema);
|
|
8
9
|
build<T extends Router>(fn: (app: App) => T): Route<T, Schema>;
|
|
9
|
-
handler(fn: (ctx: RouteToContext<Schema>) => Promise<Response<Schema>>):
|
|
10
|
+
handler(fn: (ctx: RouteToContext<Schema>) => Promise<Response<Schema>>): void;
|
|
10
11
|
}
|
|
11
12
|
//# sourceMappingURL=route.d.ts.map
|
package/dist/lib/route.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/lib/route.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/lib/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAmB,MAAM,sBAAsB,CAAC;AAC3E,OAAO,KAAK,EAIV,MAAM,EACP,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,qBAAa,KAAK,CAChB,GAAG,SAAS,MAAM,GAAG,MAAM,EAC3B,MAAM,SAAS,QAAQ,GAAG,QAAQ;IAMf,IAAI,EAAE,GAAG;IAAS,OAAO,EAAE,MAAM;IAJpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAE1B;gBAEiB,IAAI,EAAE,GAAG,EAAS,OAAO,EAAE,MAAM;IAgEpD,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;IAI3C,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;CAGvE"}
|
package/dist/lib/route.js
CHANGED
|
@@ -1,55 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Route = void 0;
|
|
4
|
+
const rpc_server_1 = require("@fy-tools/rpc-server");
|
|
4
5
|
class Route {
|
|
5
6
|
_app;
|
|
6
7
|
_schema;
|
|
8
|
+
_handlerRef;
|
|
7
9
|
constructor(_app, _schema) {
|
|
8
10
|
this._app = _app;
|
|
9
11
|
this._schema = _schema;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
this._handlerRef = {
|
|
13
|
+
fn: () => {
|
|
14
|
+
throw new Error('Route handler not implemented');
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
// Capture ref object — closure reads handlerRef.fn, not this._handlerFn,
|
|
18
|
+
// so reassignment in handler() is always visible even if this-binding diverges.
|
|
19
|
+
const handlerRef = this._handlerRef;
|
|
15
20
|
const path = this._schema._path ? `/${this._schema._path}` : '/';
|
|
16
21
|
const method = this._schema._method;
|
|
17
|
-
this._app[method](path, async (req, res, next) => {
|
|
22
|
+
this._app = this._app[method](path, async (req, res, next) => {
|
|
18
23
|
try {
|
|
19
24
|
let body = req.body;
|
|
20
25
|
let params = req.params;
|
|
21
26
|
let query = req.query;
|
|
22
27
|
if (this._schema._body) {
|
|
23
28
|
const result = await this._schema._body['~standard'].validate(req.body);
|
|
24
|
-
if (result.issues)
|
|
25
|
-
|
|
26
|
-
.status(400)
|
|
27
|
-
.json({ error: 'Validation error', issues: result.issues });
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
29
|
+
if (result.issues)
|
|
30
|
+
return next(new rpc_server_1.ValidationError(result.issues));
|
|
30
31
|
body = result.value;
|
|
31
32
|
}
|
|
32
33
|
if (this._schema._params) {
|
|
33
34
|
const result = await this._schema._params['~standard'].validate(req.params);
|
|
34
|
-
if (result.issues)
|
|
35
|
-
|
|
36
|
-
.status(400)
|
|
37
|
-
.json({ error: 'Validation error', issues: result.issues });
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
35
|
+
if (result.issues)
|
|
36
|
+
return next(new rpc_server_1.ValidationError(result.issues));
|
|
40
37
|
params = result.value;
|
|
41
38
|
}
|
|
42
39
|
if (this._schema._query) {
|
|
43
40
|
const result = await this._schema._query['~standard'].validate(req.query);
|
|
44
|
-
if (result.issues)
|
|
45
|
-
|
|
46
|
-
.status(400)
|
|
47
|
-
.json({ error: 'Validation error', issues: result.issues });
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
41
|
+
if (result.issues)
|
|
42
|
+
return next(new rpc_server_1.ValidationError(result.issues));
|
|
50
43
|
query = result.value;
|
|
51
44
|
}
|
|
52
|
-
const response = await fn({
|
|
45
|
+
const response = await handlerRef.fn({
|
|
53
46
|
body,
|
|
54
47
|
params,
|
|
55
48
|
query,
|
|
@@ -63,7 +56,12 @@ class Route {
|
|
|
63
56
|
next(err);
|
|
64
57
|
}
|
|
65
58
|
});
|
|
66
|
-
|
|
59
|
+
}
|
|
60
|
+
build(fn) {
|
|
61
|
+
return new Route(fn(this._app), this._schema);
|
|
62
|
+
}
|
|
63
|
+
handler(fn) {
|
|
64
|
+
this._handlerRef.fn = fn;
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
exports.Route = Route;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fy-tools/rpc-server-expressjs",
|
|
3
|
-
"version": "0.0.119-alpha.
|
|
3
|
+
"version": "0.0.119-alpha.10389",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/festusyuma/fy-tools.git",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"!**/*.tsbuildinfo"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@fy-tools/rpc-server": "0.0.119-alpha.
|
|
25
|
+
"@fy-tools/rpc-server": "0.0.119-alpha.10389",
|
|
26
26
|
"express": "^4.21.2",
|
|
27
27
|
"tslib": "^2.3.0"
|
|
28
28
|
},
|