allserver 1.2.0 → 1.3.0

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
@@ -12,6 +12,7 @@ Superpowers the `Allserver` gives you:
12
12
  - Run your HTTP server as gRPC with a single line change (almost).
13
13
  - Serve same logic via HTTP and gRPC (or more) simultaneously in the same node.js process.
14
14
  - Deploy and run your HTTP server on AWS Lambda with no code changes.
15
+ - Embed same exact code into your existing Express.js, no need to implement any handlers/middleware.
15
16
  - Use Redis-backed job queue [BullMQ](https://docs.bullmq.io) to call remote procedures reliably.
16
17
  - And moar!
17
18
 
@@ -143,15 +144,21 @@ Please note, that Allserver depends only on a single tiny npm module [`stampit`]
143
144
 
144
145
  The default `HttpTransport` is using the [`micro`](http://npmjs.com/package/micro) npm module as an optional dependency.
145
146
 
146
- ```shell script
147
+ ```shell
147
148
  npm i allserver micro
148
149
  ```
149
150
 
151
+ Alternatively, you can embed Allserver into your existing Express.js application:
152
+
153
+ ```shell
154
+ npm i allserver express
155
+ ```
156
+
150
157
  #### Client
151
158
 
152
159
  Optionally, you can use Allserver's built-in client:
153
160
 
154
- ```shell script
161
+ ```shell
155
162
  npm i allserver node-fetch
156
163
  ```
157
164
 
@@ -163,7 +170,7 @@ Or do HTTP requests using any module you like.
163
170
 
164
171
  No dependencies other than `allserver` itself.
165
172
 
166
- ```shell script
173
+ ```shell
167
174
  npm i allserver
168
175
  ```
169
176
 
@@ -177,7 +184,7 @@ Same as the HTTP protocol client above.
177
184
 
178
185
  The default `GrpcTransport` is using the standard the [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) npm module as an optional dependency.
179
186
 
180
- ```shell script
187
+ ```shell
181
188
  npm i allserver @grpc/grpc-js@1 @grpc/proto-loader@0.5
182
189
  ```
183
190
 
@@ -187,7 +194,7 @@ Note, with gRPC server and client you'd need to have your own `.proto` file. See
187
194
 
188
195
  Optionally, you can use Allserver's built-in client:
189
196
 
190
- ```shell script
197
+ ```shell
191
198
  npm i allserver @grpc/grpc-js@1 @grpc/proto-loader@0.5
192
199
  ```
193
200
 
@@ -199,7 +206,7 @@ Or do gRPC requests using any module you like.
199
206
 
200
207
  The default `BullmqTransport` is using the [`bullmq`](https://www.npmjs.com/package/bullmq) module as a dependency, connects to Redis using `Worker` class.
201
208
 
202
- ```shell script
209
+ ```shell
203
210
  npm i allserver bullmq
204
211
  ```
205
212
 
@@ -207,7 +214,7 @@ npm i allserver bullmq
207
214
 
208
215
  Optionally, you can use Allserver's built-in client:
209
216
 
210
- ```shell script
217
+ ```shell
211
218
  npm i allserver bullmq
212
219
  ```
213
220
 
@@ -332,6 +339,23 @@ const procedures = {
332
339
 
333
340
  More info can be found in the [`micro`](http://npmjs.com/package/micro) NPM module docs.
334
341
 
342
+ ### HTTP server in Express.js
343
+
344
+ Doesn't require a dedicated client transport. Use the HTTP client below.
345
+
346
+ NB: not yet tested in production.
347
+
348
+ ```js
349
+ const { Allserver, ExpressTransport } = require("allserver");
350
+
351
+ const middleware = Allserver({
352
+ procedures,
353
+ transport: ExpressTransport(),
354
+ }).start();
355
+
356
+ app.use("/route-with-allsever", middleware);
357
+ ```
358
+
335
359
  ### HTTP server in AWS Lambda
336
360
 
337
361
  Doesn't require a dedicated client transport. Use the HTTP client below.
@@ -365,7 +389,7 @@ exports = Allserver({
365
389
 
366
390
  You'd need to install `node-fetch` optional dependency.
367
391
 
368
- ```shell script
392
+ ```shell
369
393
  npm i allserver node-fetch
370
394
  ```
371
395
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allserver",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Multi-protocol simple RPC server and [optional] client. Boilerplate-less. Opinionated. Minimalistic. DX-first.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -36,6 +36,7 @@
36
36
  "@grpc/proto-loader": "^0.5.5",
37
37
  "bullmq": "^3.5.7",
38
38
  "eslint": "^7.9.0",
39
+ "express": "^4.18.2",
39
40
  "lambda-local": "^1.7.3",
40
41
  "micro": "^9.3.4",
41
42
  "mocha": "^10.2.0",
@@ -6,7 +6,9 @@ module.exports = require("./ClientTransport").compose({
6
6
  props: {
7
7
  // eslint-disable-next-line no-undef
8
8
  fetch: (typeof self !== "undefined" && self.fetch) || require("node-fetch"),
9
- headers: {},
9
+ headers: {
10
+ "Content-Type": "application/json; charset=utf-8",
11
+ },
10
12
  },
11
13
 
12
14
  init({ headers, fetch }) {
package/src/index.js CHANGED
@@ -11,6 +11,9 @@ module.exports = {
11
11
  get HttpTransport() {
12
12
  return require("./server/HttpTransport");
13
13
  },
14
+ get ExpressTransport() {
15
+ return require("./server/ExpressTransport");
16
+ },
14
17
  get LambdaTransport() {
15
18
  return require("./server/LambdaTransport");
16
19
  },
@@ -0,0 +1,74 @@
1
+ const { parse: parseUrl } = require("url");
2
+
3
+ module.exports = require("./Transport").compose({
4
+ name: "ExpressTransport",
5
+
6
+ props: {
7
+ _express: require("express"),
8
+ },
9
+
10
+ methods: {
11
+ async deserializeRequest(ctx) {
12
+ if (ctx.http.req.deserializationFailed) return false;
13
+ // If there is no body we will use request query (aka search params)
14
+ let arg = ctx.http.query;
15
+ if (ctx.http.req.body && Object.keys(ctx.http.req.body).length > 0) arg = ctx.http.req.body;
16
+ ctx.arg = arg;
17
+ return true;
18
+ },
19
+
20
+ async _handleRequest(ctx) {
21
+ if (await this.deserializeRequest(ctx)) {
22
+ await ctx.allserver.handleCall(ctx);
23
+ } else {
24
+ // HTTP protocol request was malformed (not expected structure).
25
+ // We are not going to process it.
26
+ ctx.result = { success: false, code: "ALLSERVER_BAD_REQUEST", message: "Can't parse JSON" };
27
+ ctx.http.statusCode = 400;
28
+ this.reply(ctx);
29
+ }
30
+ },
31
+
32
+ startServer(defaultCtx) {
33
+ return [
34
+ this._express.json(),
35
+ (err, req, res, next) => {
36
+ if (err.statusCode) req.deserializationFailed = err; // overriding the error, processing the request as normal
37
+ next();
38
+ },
39
+ async (req, res) => {
40
+ const ctx = {
41
+ ...defaultCtx,
42
+ http: { req, res, query: req.query || {} },
43
+ };
44
+
45
+ await this._handleRequest(ctx);
46
+ },
47
+ ];
48
+ },
49
+
50
+ getProcedureName(ctx) {
51
+ return parseUrl(ctx.http.req.url).pathname.substr(1);
52
+ },
53
+
54
+ isIntrospection(ctx) {
55
+ return this.getProcedureName(ctx) === "";
56
+ },
57
+
58
+ prepareNotFoundReply(ctx) {
59
+ ctx.http.statusCode = 404;
60
+ },
61
+ prepareProcedureErrorReply(ctx) {
62
+ // Generic exception.
63
+ ctx.http.statusCode = 500;
64
+
65
+ // nodejs assert() exception. In HTTP world this likely means 400 "Bad Request".
66
+ if (ctx.error.code === "ERR_ASSERTION") ctx.http.statusCode = 400;
67
+ },
68
+
69
+ reply(ctx) {
70
+ if (!ctx.http.statusCode) ctx.http.statusCode = 200;
71
+ ctx.http.res.status(ctx.http.statusCode).json(ctx.result);
72
+ },
73
+ },
74
+ });