@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 CHANGED
@@ -1,7 +1,149 @@
1
- # rpc-server-express
1
+ # @fy-tools/rpc-server-expressjs
2
2
 
3
- This library was generated with [Nx](https://nx.dev).
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
- ## Building
5
+ ## Installation
6
6
 
7
- Run `nx build rpc-server-express` to build the library.
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` |
@@ -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;IAS3D,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,CAAC;CAGxD"}
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
- this.C[i] = new controller_1.Controller(this._app, _schema._controllers[_schema._controllers_map[i]]);
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,KAkBD,GAAG,iHAlBD;gBAEU,IAAI,EAAE,GAAG,EAAS,OAAO,EAAE,MAAM;IA6BpD,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;CAGjD"}
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"}
@@ -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
- routes[i] = new route_1.Route(router, _schema._routes[_schema._routes_map[i]]);
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, router);
27
+ this._app.use(basePath, currentRouter);
25
28
  this.R = new Proxy(routes, {
26
29
  get(target, p) {
27
30
  return routes[p];
@@ -1,11 +1,12 @@
1
- import type { AnyRoute, Response } from '@fy-tools/rpc-server';
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>>): this;
10
+ handler(fn: (ctx: RouteToContext<Schema>) => Promise<Response<Schema>>): void;
10
11
  }
11
12
  //# sourceMappingURL=route.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/lib/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC/D,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;IAEf,IAAI,EAAE,GAAG;IAAS,OAAO,EAAE,MAAM;gBAAjC,IAAI,EAAE,GAAG,EAAS,OAAO,EAAE,MAAM;IAEpD,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;CA0EvE"}
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
- build(fn) {
12
- return new Route(fn(this._app), this._schema);
13
- }
14
- handler(fn) {
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
- res
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
- res
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
- res
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
- return this;
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.10387",
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.10387",
25
+ "@fy-tools/rpc-server": "0.0.119-alpha.10389",
26
26
  "express": "^4.21.2",
27
27
  "tslib": "^2.3.0"
28
28
  },