rpc-express-toolkit 4.1.0 → 4.2.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 +72 -0
- package/examples/introspection/client.js +68 -0
- package/examples/introspection/server.js +89 -0
- package/examples/safe-preset-example.js +3 -3
- package/package.json +1 -1
- package/src/batch.js +6 -2
- package/src/index.js +1035 -842
- package/src/safe.d.ts +33 -0
- package/src/safe.js +78 -20
- package/src/validation.js +222 -221
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://www.npmjs.com/package/rpc-express-toolkit)
|
|
7
7
|
[](https://www.npmjs.com/package/rpc-express-toolkit)
|
|
8
|
+
[](https://www.npmjs.com/package/rpc-express-toolkit)
|
|
8
9
|
|
|
9
10
|
Enterprise-ready JSON-RPC 2.0 toolkit for Express.js with simplified APIs, middleware, schema validation, batch support, and optional safe type disambiguation.
|
|
10
11
|
|
|
@@ -61,6 +62,9 @@ Safe type disambiguation is disabled by default for maximum JSON-RPC 2.0 compati
|
|
|
61
62
|
|
|
62
63
|
### Safe Import (opt-in)
|
|
63
64
|
|
|
65
|
+
[](https://www.npmjs.com/package/rpc-express-toolkit-safe)
|
|
66
|
+
[](#safe-import-opt-in)
|
|
67
|
+
|
|
64
68
|
When you control both client and server and want safer type round-trips, import the safe preset. It enables safe serialization by default (and strict mode on the server):
|
|
65
69
|
|
|
66
70
|
```js
|
|
@@ -80,10 +84,78 @@ const client = createSafeClient('http://localhost:3000/api');
|
|
|
80
84
|
|
|
81
85
|
This keeps JSON-RPC 2.0 compliance as default for the main entrypoint, while offering a convenient safe-mode import for projects that prefer explicit type disambiguation.
|
|
82
86
|
|
|
87
|
+
Alternative: npm proxy package
|
|
88
|
+
|
|
89
|
+
If you prefer a dedicated package name, you can install the thin proxy `rpc-express-toolkit-safe` which simply re-exports the safe preset:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install rpc-express-toolkit-safe
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// Server
|
|
97
|
+
const express = require('express');
|
|
98
|
+
const { createSafeEndpoint } = require('rpc-express-toolkit-safe');
|
|
99
|
+
const app = express();
|
|
100
|
+
app.use(express.json());
|
|
101
|
+
const rpc = createSafeEndpoint(app, {}, { endpoint: '/api' });
|
|
102
|
+
|
|
103
|
+
// Client
|
|
104
|
+
const { createSafeClient } = require('rpc-express-toolkit-safe');
|
|
105
|
+
const client = createSafeClient('http://localhost:3000/api');
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Introspection Methods
|
|
109
|
+
|
|
110
|
+
Enable introspection to expose metadata about registered methods via reserved `__rpc.*` methods:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const rpc = new RpcEndpoint(app, context, {
|
|
114
|
+
enableIntrospection: true // Enable __rpc.* methods
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Register methods with public schemas
|
|
118
|
+
rpc.addMethod('add', async (req, ctx, params) => {
|
|
119
|
+
return params.a + params.b;
|
|
120
|
+
}, {
|
|
121
|
+
schema: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {
|
|
124
|
+
a: { type: 'number' },
|
|
125
|
+
b: { type: 'number' }
|
|
126
|
+
},
|
|
127
|
+
required: ['a', 'b']
|
|
128
|
+
},
|
|
129
|
+
exposeSchema: true, // Make schema publicly queryable
|
|
130
|
+
description: 'Add two numbers'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Available introspection methods:
|
|
134
|
+
// __rpc.listMethods() → ["add", "multiply", ...]
|
|
135
|
+
// __rpc.describe({method: "add"}) → {name, schema, description}
|
|
136
|
+
// __rpc.describeAll() → [{name, schema, description}, ...]
|
|
137
|
+
// __rpc.version() → {toolkit, version, expressVersion, nodeVersion}
|
|
138
|
+
// __rpc.capabilities() → {safeMode, batch, introspection, ...}
|
|
139
|
+
|
|
140
|
+
// Client usage
|
|
141
|
+
const methods = await client.call('__rpc.listMethods');
|
|
142
|
+
const addInfo = await client.call('__rpc.describe', { method: 'add' });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Note:** The introspection prefix is configurable via `introspectionPrefix` option (default: `__rpc`). User methods starting with this prefix are rejected to prevent conflicts.
|
|
146
|
+
|
|
83
147
|
## Full Details
|
|
84
148
|
|
|
85
149
|
For advanced configuration, middleware, structured logging, safe serialization, error handling, and more, see `README_ADVANCED.md`.
|
|
86
150
|
|
|
151
|
+
## 🔗 Related Projects
|
|
152
|
+
|
|
153
|
+
- [rpc-php-toolkit](https://github.com/n-car/rpc-php-toolkit) - PHP implementation
|
|
154
|
+
- [rpc-dotnet-toolkit](https://github.com/n-car/rpc-dotnet-toolkit) - .NET implementation
|
|
155
|
+
- [rpc-arduino-toolkit](https://github.com/n-car/rpc-arduino-toolkit) - Arduino/ESP32 implementation
|
|
156
|
+
- [rpc-java-toolkit](https://github.com/n-car/rpc-java-toolkit) - Java & Android implementation
|
|
157
|
+
- [node-red-contrib-rpc-toolkit](https://github.com/n-car/node-red-contrib-rpc-toolkit) - Node-RED visual programming
|
|
158
|
+
|
|
87
159
|
## Contributing
|
|
88
160
|
|
|
89
161
|
```bash
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Introspection Example - Client
|
|
3
|
+
* Demonstrates how to use __rpc.* introspection methods
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { RpcClient } = require('../..');
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
const client = new RpcClient('http://localhost:3000/api');
|
|
10
|
+
|
|
11
|
+
console.log('=== RPC Introspection Demo ===\n');
|
|
12
|
+
|
|
13
|
+
// 1. List all methods
|
|
14
|
+
console.log('1. __rpc.listMethods():');
|
|
15
|
+
const methods = await client.call('__rpc.listMethods');
|
|
16
|
+
console.log(' Available methods:', methods);
|
|
17
|
+
console.log('');
|
|
18
|
+
|
|
19
|
+
// 2. Get version info
|
|
20
|
+
console.log('2. __rpc.version():');
|
|
21
|
+
const version = await client.call('__rpc.version');
|
|
22
|
+
console.log(' Toolkit:', version.toolkit);
|
|
23
|
+
console.log(' Version:', version.version);
|
|
24
|
+
console.log(' Express:', version.expressVersion);
|
|
25
|
+
console.log(' Node:', version.nodeVersion);
|
|
26
|
+
console.log('');
|
|
27
|
+
|
|
28
|
+
// 3. Get server capabilities
|
|
29
|
+
console.log('3. __rpc.capabilities():');
|
|
30
|
+
const caps = await client.call('__rpc.capabilities');
|
|
31
|
+
console.log(' Safe Mode:', caps.safeMode);
|
|
32
|
+
console.log(' Batch Support:', caps.batch);
|
|
33
|
+
console.log(' Introspection:', caps.introspection);
|
|
34
|
+
console.log(' Method Count:', caps.methodCount);
|
|
35
|
+
console.log('');
|
|
36
|
+
|
|
37
|
+
// 4. Describe a specific method
|
|
38
|
+
console.log('4. __rpc.describe({ method: "add" }):');
|
|
39
|
+
const addInfo = await client.call('__rpc.describe', { method: 'add' });
|
|
40
|
+
console.log(' Name:', addInfo.name);
|
|
41
|
+
console.log(' Description:', addInfo.description);
|
|
42
|
+
console.log(' Schema:', JSON.stringify(addInfo.schema, null, 2));
|
|
43
|
+
console.log('');
|
|
44
|
+
|
|
45
|
+
// 5. Describe all public methods
|
|
46
|
+
console.log('5. __rpc.describeAll():');
|
|
47
|
+
const allMethods = await client.call('__rpc.describeAll');
|
|
48
|
+
allMethods.forEach((method) => {
|
|
49
|
+
console.log(` - ${method.name}: ${method.description}`);
|
|
50
|
+
});
|
|
51
|
+
console.log('');
|
|
52
|
+
|
|
53
|
+
// 6. Try to describe a private method (should fail)
|
|
54
|
+
console.log('6. Try to describe private method:');
|
|
55
|
+
try {
|
|
56
|
+
await client.call('__rpc.describe', { method: 'internalCalculation' });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.log(' Error:', error.message);
|
|
59
|
+
}
|
|
60
|
+
console.log('');
|
|
61
|
+
|
|
62
|
+
// 7. Test a public method
|
|
63
|
+
console.log('7. Call add method:');
|
|
64
|
+
const sum = await client.call('add', { a: 5, b: 3 });
|
|
65
|
+
console.log(' 5 + 3 =', sum);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Introspection Example - Server
|
|
3
|
+
* Demonstrates how to enable and use __rpc.* introspection methods
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const { RpcEndpoint } = require('../..');
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
app.use(express.json());
|
|
11
|
+
|
|
12
|
+
// Context for methods
|
|
13
|
+
const context = {
|
|
14
|
+
database: 'mock-db',
|
|
15
|
+
config: { maxValue: 1000 },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Create endpoint with introspection enabled
|
|
19
|
+
const rpc = new RpcEndpoint(app, context, {
|
|
20
|
+
endpoint: '/api',
|
|
21
|
+
enableIntrospection: true, // Enable __rpc.* methods
|
|
22
|
+
introspectionPrefix: '__rpc', // Optional: customize prefix (default: __rpc)
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Public method with exposed schema
|
|
26
|
+
rpc.addMethod('add', async (req, ctx, params) => params.a + params.b, {
|
|
27
|
+
schema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
a: { type: 'number' },
|
|
31
|
+
b: { type: 'number' },
|
|
32
|
+
},
|
|
33
|
+
required: ['a', 'b'],
|
|
34
|
+
},
|
|
35
|
+
exposeSchema: true,
|
|
36
|
+
description: 'Add two numbers and return the sum',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Public method with exposed schema
|
|
40
|
+
rpc.addMethod('multiply', async (req, ctx, params) => params.a * params.b, {
|
|
41
|
+
schema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
a: { type: 'number' },
|
|
45
|
+
b: { type: 'number' },
|
|
46
|
+
},
|
|
47
|
+
required: ['a', 'b'],
|
|
48
|
+
},
|
|
49
|
+
exposeSchema: true,
|
|
50
|
+
description: 'Multiply two numbers',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Private method (schema not exposed)
|
|
54
|
+
rpc.addMethod(
|
|
55
|
+
'internalCalculation',
|
|
56
|
+
async (req, ctx, params) => params.value * 2 + 42,
|
|
57
|
+
{
|
|
58
|
+
schema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
value: { type: 'number' },
|
|
62
|
+
},
|
|
63
|
+
required: ['value'],
|
|
64
|
+
},
|
|
65
|
+
exposeSchema: false, // Schema is private
|
|
66
|
+
description: 'Internal calculation (not exposed)',
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Public method without schema
|
|
71
|
+
rpc.addMethod('echo', async (req, ctx, params) => params, {
|
|
72
|
+
exposeSchema: true,
|
|
73
|
+
description: 'Echo back the parameters',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Start server
|
|
77
|
+
const PORT = process.env.PORT || 3000;
|
|
78
|
+
app.listen(PORT, () => {
|
|
79
|
+
console.log(
|
|
80
|
+
`RPC Server with introspection running on http://localhost:${PORT}/api`
|
|
81
|
+
);
|
|
82
|
+
console.log('\nAvailable introspection methods:');
|
|
83
|
+
console.log('- __rpc.listMethods()');
|
|
84
|
+
console.log('- __rpc.describe({method: "add"})');
|
|
85
|
+
console.log('- __rpc.describeAll()');
|
|
86
|
+
console.log('- __rpc.version()');
|
|
87
|
+
console.log('- __rpc.capabilities()');
|
|
88
|
+
console.log('\nTry: node client.js');
|
|
89
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
/* eslint-disable import/no-unresolved, prettier/prettier */
|
|
3
|
-
const {
|
|
3
|
+
const { RpcSafeEndpoint, RpcSafeClient } = require('rpc-express-toolkit/safe');
|
|
4
4
|
/* eslint-enable import/no-unresolved, prettier/prettier */
|
|
5
5
|
|
|
6
6
|
async function main() {
|
|
@@ -8,7 +8,7 @@ async function main() {
|
|
|
8
8
|
app.use(express.json());
|
|
9
9
|
|
|
10
10
|
// Server with safe defaults (safeEnabled=true, strictMode=true)
|
|
11
|
-
const rpc =
|
|
11
|
+
const rpc = new RpcSafeEndpoint(app, {}, { endpoint: '/api' });
|
|
12
12
|
|
|
13
13
|
rpc.addMethod('sum', (req, ctx, params) => {
|
|
14
14
|
// params are already deserialized according to the client's safe header
|
|
@@ -29,7 +29,7 @@ async function main() {
|
|
|
29
29
|
console.log('Safe server listening on', url);
|
|
30
30
|
|
|
31
31
|
// Safe client (sends header and expects safe response)
|
|
32
|
-
const client =
|
|
32
|
+
const client = new RpcSafeClient(url);
|
|
33
33
|
const res = await client.call('sum', { a: '10n', b: '32n' });
|
|
34
34
|
console.log('sum result (BigInt):', res.toString());
|
|
35
35
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rpc-express-toolkit",
|
|
3
|
-
"version": "4.1
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"description": "Enterprise-ready JSON-RPC 2.0 toolkit for Express.js with simplified APIs, structured logging, middleware system, schema validation, comprehensive BigInt/Date support. JSON middleware validation and explicit configuration.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
package/src/batch.js
CHANGED
|
@@ -140,8 +140,8 @@ class BatchHandler {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
const
|
|
144
|
-
if (!
|
|
143
|
+
const methodConfig = this.endpoint.methods[method];
|
|
144
|
+
if (!methodConfig) {
|
|
145
145
|
return {
|
|
146
146
|
jsonrpc: '2.0',
|
|
147
147
|
id: id || null,
|
|
@@ -153,6 +153,10 @@ class BatchHandler {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// Extract handler (support both function and config object)
|
|
157
|
+
const handler =
|
|
158
|
+
typeof methodConfig === 'function' ? methodConfig : methodConfig.handler;
|
|
159
|
+
|
|
156
160
|
// If no id is provided, this is a notification - don't return response
|
|
157
161
|
const isNotification = id === undefined || id === null;
|
|
158
162
|
|