allserver 1.2.0 → 2.0.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
- npm i allserver micro
147
+ ```shell
148
+ npm i allserver micro@10
149
+ ```
150
+
151
+ Alternatively, you can embed Allserver into your existing Express.js application:
152
+
153
+ ```shell
154
+ npm i allserver express
148
155
  ```
149
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": "2.0.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": {
@@ -31,15 +31,44 @@
31
31
  "homepage": "https://github.com/flash-oss/allserver",
32
32
  "author": "Vasyl Boroviak",
33
33
  "license": "MIT",
34
+ "peerDependencies": {
35
+ "@grpc/grpc-js": "^1.1.7",
36
+ "@grpc/proto-loader": "^0.7.5",
37
+ "bullmq": "^3.10.1",
38
+ "express": "^4.18.2",
39
+ "micro": "^10.0.1",
40
+ "node-fetch": "^2.6.9"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "@grpc/grpc-js": {
44
+ "optional": true
45
+ },
46
+ "@grpc/proto-loader": {
47
+ "optional": true
48
+ },
49
+ "bullmq": {
50
+ "optional": true
51
+ },
52
+ "express": {
53
+ "optional": true
54
+ },
55
+ "micro": {
56
+ "optional": true
57
+ },
58
+ "node-fetch": {
59
+ "optional": true
60
+ }
61
+ },
34
62
  "devDependencies": {
35
63
  "@grpc/grpc-js": "^1.1.7",
36
- "@grpc/proto-loader": "^0.5.5",
37
- "bullmq": "^3.5.7",
64
+ "@grpc/proto-loader": "^0.7.5",
65
+ "bullmq": "^3.10.1",
38
66
  "eslint": "^7.9.0",
67
+ "express": "^4.18.2",
39
68
  "lambda-local": "^1.7.3",
40
- "micro": "^9.3.4",
69
+ "micro": "^10.0.1",
41
70
  "mocha": "^10.2.0",
42
- "node-fetch": "^2.6.1",
71
+ "node-fetch": "^2.6.9",
43
72
  "nyc": "^15.1.0",
44
73
  "prettier": "^2.1.1"
45
74
  },
@@ -5,8 +5,10 @@ module.exports = require("./ClientTransport").compose({
5
5
 
6
6
  props: {
7
7
  // eslint-disable-next-line no-undef
8
- fetch: (typeof self !== "undefined" && self.fetch) || require("node-fetch"),
9
- headers: {},
8
+ fetch: (typeof globalThis !== "undefined" && globalThis.fetch) || require("node-fetch"),
9
+ headers: {
10
+ "Content-Type": "application/json; charset=utf-8",
11
+ },
10
12
  },
11
13
 
12
14
  init({ headers, fetch }) {
@@ -29,7 +31,8 @@ module.exports = require("./ClientTransport").compose({
29
31
  response = await this.fetch(this.uri + procedureName, http);
30
32
  http.response = response;
31
33
  } catch (err) {
32
- if (err.code === "ECONNREFUSED") err.noNetToServer = true;
34
+ if (err.code === "ECONNREFUSED" || (err.cause && err.cause.code === "ECONNREFUSED"))
35
+ err.noNetToServer = true;
33
36
  throw err;
34
37
  }
35
38
 
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
+ });
@@ -1,3 +1,4 @@
1
+ const http = require("http");
1
2
  const { parse: parseUrl, URLSearchParams } = require("url");
2
3
 
3
4
  module.exports = require("./Transport").compose({
@@ -39,22 +40,27 @@ module.exports = require("./Transport").compose({
39
40
  },
40
41
 
41
42
  startServer(defaultCtx) {
42
- this.server = this.micro(async (req, res) => {
43
- const ctx = { ...defaultCtx, http: { req, res, url: parseUrl(req.url) } };
43
+ this.server = new http.Server(
44
+ this.micro.serve(async (req, res) => {
45
+ const ctx = { ...defaultCtx, http: { req, res, url: parseUrl(req.url) } };
44
46
 
45
- ctx.http.query = {};
46
- if (ctx.http.url.query) {
47
- for (const [key, value] of new URLSearchParams(ctx.http.url.query).entries()) {
48
- ctx.http.query[key] = value;
47
+ ctx.http.query = {};
48
+ if (ctx.http.url.query) {
49
+ for (const [key, value] of new URLSearchParams(ctx.http.url.query).entries()) {
50
+ ctx.http.query[key] = value;
51
+ }
49
52
  }
50
- }
51
53
 
52
- await this._handleRequest(ctx);
53
- });
54
+ await this._handleRequest(ctx);
55
+ })
56
+ );
54
57
  return new Promise((r) => this.server.listen(this.port, r));
55
58
  },
56
59
  stopServer() {
57
- return new Promise((r) => this.server.close(r));
60
+ return new Promise((r) => {
61
+ if (this.server.closeIdleConnections) this.server.closeIdleConnections();
62
+ this.server.close(r);
63
+ });
58
64
  },
59
65
 
60
66
  getProcedureName(ctx) {