allserver 2.4.1 → 2.6.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
@@ -6,8 +6,11 @@ Think of Remote Procedure Calls using exactly the same client and server code bu
6
6
 
7
7
  Should be used in (micro)services where JavaScript is able to run - your computer, Docker, k8s, virtual machines, serverless functions (Lambdas, Google Cloud Functions, Azure Functions, etc), RaspberryPI, SharedWorker, thread, you name it.
8
8
 
9
+ > Uber [moved](https://www.infoq.com/news/2023/10/uber-up-cloud-microservices/) most of its containerized microservices from µDeploy to a new multi-cloud platform named Up in preparation for migrating a considerable portion of its compute footprint to the cloud. **The company spent two years** working on making its many microservices portable to migrate between different computing infrastructures and container management platforms.
10
+
9
11
  Superpowers the `Allserver` gives you:
10
12
 
13
+ - Move your code around infrastructures, deployment types, runtimes, platforms, etc with ease.
11
14
  - Call gRPC server methods from browser/curl/Postman.
12
15
  - Run your HTTP server as gRPC with a single line change (almost).
13
16
  - Serve same logic via HTTP and gRPC (or more) simultaneously in the same node.js process.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allserver",
3
- "version": "2.4.1",
3
+ "version": "2.6.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": {
@@ -34,12 +34,12 @@
34
34
  "author": "Vasyl Boroviak",
35
35
  "license": "MIT",
36
36
  "peerDependencies": {
37
- "@grpc/grpc-js": "^1.1.7",
38
- "@grpc/proto-loader": "^0.7.5",
39
- "bullmq": "^3.10.1",
40
- "express": "^4.18.2",
41
- "micro": "^10.0.1",
42
- "node-fetch": "^2.6.9"
37
+ "@grpc/grpc-js": "1",
38
+ "@grpc/proto-loader": "0",
39
+ "bullmq": "3 - 5",
40
+ "express": "4",
41
+ "micro": "10",
42
+ "node-fetch": "2"
43
43
  },
44
44
  "peerDependenciesMeta": {
45
45
  "@grpc/grpc-js": {
@@ -62,17 +62,17 @@
62
62
  }
63
63
  },
64
64
  "devDependencies": {
65
- "@grpc/grpc-js": "^1.1.7",
66
- "@grpc/proto-loader": "^0.7.5",
67
- "bullmq": "^3.10.1",
65
+ "@grpc/grpc-js": "^1.13.4",
66
+ "@grpc/proto-loader": "^0.8.0",
67
+ "bullmq": "^5.56.9",
68
68
  "cls-hooked": "^4.2.2",
69
- "eslint": "^8.55.0",
70
- "express": "^4.18.2",
69
+ "eslint": "^8.57.1",
70
+ "express": "^4.21.2",
71
71
  "lambda-local": "^1.7.3",
72
72
  "micro": "^10.0.1",
73
- "mocha": "^10.2.0",
73
+ "mocha": "^11.7.1",
74
74
  "node-fetch": "^2.6.9",
75
- "nyc": "^15.1.0",
75
+ "nyc": "^17.1.0",
76
76
  "prettier": "^2.1.1"
77
77
  },
78
78
  "dependencies": {
@@ -141,6 +141,8 @@ module.exports = require("stampit")({
141
141
  [p]: {
142
142
  // The protocol implementation strategy.
143
143
  transport: null,
144
+ // The maximum time to wait until returning the ALLSERVER_CLIENT_TIMEOUT error. 0 means - no timeout.
145
+ timeout: 0,
144
146
  // Disable any exception throwing when calling any methods. Otherwise, throws network and server errors.
145
147
  neverThrow: true,
146
148
  // Automatically find (introspect) and call corresponding remote procedures. Use only the methods defined in client side.
@@ -173,6 +175,7 @@ module.exports = require("stampit")({
173
175
  {
174
176
  uri,
175
177
  transport,
178
+ timeout,
176
179
  neverThrow,
177
180
  dynamicMethods,
178
181
  autoIntrospect,
@@ -183,6 +186,7 @@ module.exports = require("stampit")({
183
186
  },
184
187
  { stamp }
185
188
  ) {
189
+ this[p].timeout = timeout != null ? timeout : this[p].timeout;
186
190
  this[p].neverThrow = neverThrow != null ? neverThrow : this[p].neverThrow;
187
191
  this[p].dynamicMethods = dynamicMethods != null ? dynamicMethods : this[p].dynamicMethods;
188
192
  this[p].autoIntrospect = autoIntrospect != null ? autoIntrospect : this[p].autoIntrospect;
@@ -203,8 +207,8 @@ module.exports = require("stampit")({
203
207
  this[p].transport = getTransport()({ uri });
204
208
  }
205
209
 
206
- if (before) this[p].before = [].concat(before).concat(this[p].before);
207
- if (after) this[p].after = [].concat(after).concat(this[p].after);
210
+ if (before) this[p].before = [].concat(this[p].before).concat(before).filter(isFunction);
211
+ if (after) this[p].after = [].concat(this[p].after).concat(after).filter(isFunction);
208
212
  },
209
213
 
210
214
  methods: {
@@ -218,10 +222,10 @@ module.exports = require("stampit")({
218
222
  try {
219
223
  // This is supposed to be executed only once (per uri) unless it throws.
220
224
  // There are only 3 situations when this throws:
221
- // * the "introspect" method not found on server,
222
- // * the network request is malformed,
225
+ // * the "introspect" method not found on server, (GRPC)
226
+ // * the network request is malformed, (HTTP-like)
223
227
  // * couldn't connect to the remote host.
224
- ctx.result = await transport.introspect(ctx);
228
+ ctx.result = await this._callTransport(ctx);
225
229
  } catch (err) {
226
230
  ctx.result = {
227
231
  success: false,
@@ -281,19 +285,41 @@ module.exports = require("stampit")({
281
285
  return await runMiddlewares(middlewares);
282
286
  },
283
287
 
288
+ _callTransport(ctx) {
289
+ const transportMethod = ctx.isIntrospection ? "introspect" : "call";
290
+ // In JavaScript if the `timeout` is null or undefined or some other object this condition will return `false`
291
+ if (this[p].timeout > 0) {
292
+ return Promise.race([
293
+ this[p].transport[transportMethod](ctx),
294
+ new Promise((resolve) =>
295
+ setTimeout(
296
+ () =>
297
+ resolve({
298
+ success: false,
299
+ code: "ALLSERVER_CLIENT_TIMEOUT",
300
+ message: `The remote procedure ${ctx.procedureName} timed out in ${this[p].timeout} ms`,
301
+ }),
302
+ this[p].timeout
303
+ )
304
+ ),
305
+ ]);
306
+ }
307
+
308
+ return this[p].transport[transportMethod](ctx);
309
+ },
310
+
284
311
  async call(procedureName, arg) {
285
312
  if (!arg) arg = {};
286
313
  if (!arg._) arg._ = {};
287
314
  arg._.procedureName = procedureName;
288
315
 
289
- const transport = this[p].transport;
290
316
  const defaultCtx = { procedureName, arg, client: this };
291
- const ctx = transport.createCallContext(defaultCtx);
317
+ const ctx = this[p].transport.createCallContext(defaultCtx);
292
318
 
293
319
  await this._callMiddlewares(ctx, "before", async () => {
294
320
  if (!ctx.result) {
295
321
  try {
296
- ctx.result = await transport.call(ctx);
322
+ ctx.result = await this._callTransport(ctx);
297
323
  } catch (err) {
298
324
  if (!this[p].neverThrow) throw err;
299
325
 
@@ -335,8 +361,9 @@ module.exports = require("stampit")({
335
361
  before,
336
362
  after,
337
363
  } = {}) {
338
- if (before) before = [].concat(before);
339
- if (after) after = [].concat(after);
364
+ if (before != null) before = (Array.isArray(before) ? before : [before]).filter(isFunction);
365
+ if (after != null) after = (Array.isArray(after) ? after : [after]).filter(isFunction);
366
+
340
367
  return this.deepProps({
341
368
  [p]: {
342
369
  transport,
@@ -1,17 +1,20 @@
1
1
  const assert = require("assert");
2
2
 
3
- const { isObject, isBoolean, isFunction } = require("../util");
3
+ const { isObject, isBoolean, isFunction, uniq } = require("../util");
4
4
 
5
5
  module.exports = require("stampit")({
6
6
  name: "Allserver",
7
7
 
8
+ deepProps: {
9
+ before: null,
10
+ after: null,
11
+ },
12
+
8
13
  props: {
9
14
  procedures: {},
10
15
  transport: null,
11
16
  logger: console,
12
17
  introspection: true,
13
- before: null,
14
- after: null,
15
18
 
16
19
  callsCount: 0,
17
20
  },
@@ -21,8 +24,8 @@ module.exports = require("stampit")({
21
24
  this.transport = transport || this.transport || require("./HttpTransport")();
22
25
  this.logger = logger || this.logger;
23
26
  this.introspection = introspection != null ? introspection : this.introspection;
24
- this.before = before || this.before;
25
- this.after = after || this.after;
27
+ if (before) this.before = uniq([].concat(this.before).concat(before).filter(isFunction));
28
+ if (after) this.after = uniq([].concat(this.after).concat(after).filter(isFunction));
26
29
 
27
30
  this._validateProcedures();
28
31
  },
@@ -178,7 +181,13 @@ module.exports = require("stampit")({
178
181
 
179
182
  statics: {
180
183
  defaults({ procedures, transport, logger, introspection, before, after } = {}) {
181
- return this.props({ procedures, transport, logger, introspection, before, after });
184
+ if (before != null) before = (Array.isArray(before) ? before : [before]).filter(isFunction);
185
+ if (after != null) after = (Array.isArray(after) ? after : [after]).filter(isFunction);
186
+
187
+ return this.compose({
188
+ props: { procedures, transport, logger, introspection },
189
+ deepProps: { before, after },
190
+ });
182
191
  },
183
192
  },
184
193
  });
@@ -81,8 +81,6 @@ module.exports = require("./Transport").compose({
81
81
  err ? reject(err) : resolve(result)
82
82
  );
83
83
  });
84
-
85
- return this.server.start();
86
84
  },
87
85
  stopServer() {
88
86
  return new Promise((r) => this.server.tryShutdown(r));
@@ -60,6 +60,7 @@ module.exports = require("./Transport").compose({
60
60
  return new Promise((r) => {
61
61
  if (this.server.closeIdleConnections) this.server.closeIdleConnections();
62
62
  this.server.close(r);
63
+ if (this.server.closeAllConnections) this.server.closeAllConnections();
63
64
  });
64
65
  },
65
66
 
package/src/util.js CHANGED
@@ -5,4 +5,5 @@ module.exports = {
5
5
  isFunction: (o) => is(o, "function"),
6
6
  isObject: (o) => is(o, "object"),
7
7
  isPlainObject: (o) => o && o.constructor === Object,
8
+ uniq: (a) => Array.from(new Set(a)),
8
9
  };