allserver 2.6.0 → 2.6.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 CHANGED
@@ -162,7 +162,7 @@ npm i allserver express
162
162
  Optionally, you can use Allserver's built-in client:
163
163
 
164
164
  ```shell
165
- npm i allserver node-fetch
165
+ npm i allserver
166
166
  ```
167
167
 
168
168
  Or do HTTP requests using any module you like.
@@ -390,10 +390,8 @@ exports.handler = Allserver({
390
390
 
391
391
  #### Using built-in client
392
392
 
393
- You'd need to install `node-fetch` optional dependency.
394
-
395
393
  ```shell
396
- npm i allserver node-fetch
394
+ npm i allserver
397
395
  ```
398
396
 
399
397
  Note, that this code is **same** as the gRPC client code example below!
@@ -618,13 +616,7 @@ assert(success === true);
618
616
 
619
617
  ### Bare AWS Lambda invocation
620
618
 
621
- First you need to install any of the AWS SDK versions.
622
-
623
- ```shell
624
- npm i allserver aws-sdk
625
- ```
626
-
627
- or
619
+ First you need to install the AWS SDK v3.
628
620
 
629
621
  ```shell
630
622
  npm i allserver @aws-sdk/client-lambda
@@ -681,6 +673,9 @@ aws lambda invoke --function-name my-lambda-name --payload '{"_":{"procedureName
681
673
  - `transport`<br>
682
674
  The transport implementation object. The `uri` is ignored if this option provided. If not given then it will be automatically created based on the `uri` schema. E.g. if it starts with `http://` or `https://` then `HttpClientTransport` will be used. If starts with `grpc://` then `GrpcClientTransport` will be used. If starts with `bullmq://` then `BullmqClientTransport` is used.
683
675
 
676
+ - `timeout=60_000`<br>
677
+ Set it to `0` if you don't need a timeout. If the procedure call takes longer than this value then the `AllserverClient` will return `success=false` and `code=ALLSERVER_CLIENT_TIMEOUT`.
678
+
684
679
  - `neverThrow=true`<br>
685
680
  Set it to `false` if you want to get exceptions when there are a network, or a server errors during a procedure call. Otherwise, the standard `{success,code,message}` object is returned from method calls. The Allserver error `code`s are always start with `"ALLSERVER_"`. E.g. `"ALLSERVER_CLIENT_MALFORMED_INTROSPECTION"`.
686
681
 
@@ -709,6 +704,7 @@ You can change the above mentioned options default values like this:
709
704
  ```js
710
705
  AllseverClient = AllserverClient.defaults({
711
706
  transport,
707
+ timeout,
712
708
  neverThrow,
713
709
  dynamicMethods,
714
710
  autoIntrospect,
@@ -912,7 +908,8 @@ const client = AllserverClient({
912
908
  ctx.http.headers.authorization = "Basic my-token";
913
909
  },
914
910
  async after(ctx) {
915
- if (ctx.error) console.error(ctx.error); else console.log(ctx.result);
911
+ if (ctx.error) console.error(ctx.error);
912
+ else console.log(ctx.result);
916
913
  },
917
914
  });
918
915
  ```
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "allserver",
3
- "version": "2.6.0",
4
- "description": "Multi-protocol simple RPC server and [optional] client. Boilerplate-less. Opinionated. Minimalistic. DX-first.",
3
+ "version": "2.6.1",
4
+ "description": "Multi-protocol RPC server and [optional] client. DX-first. Minimalistic. Boilerplate-less. Opinionated.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
7
- "lint": "eslint ./",
8
- "test": "mocha",
9
- "cov": "nyc --reporter=html npm run test"
7
+ "lint": "oxlint ./",
8
+ "test": "node --test --test-force-exit --trace-deprecation",
9
+ "cov": "nyc --reporter=html node --run test"
10
10
  },
11
11
  "keywords": [
12
12
  "simple",
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "repository": {
27
27
  "type": "git",
28
- "url": "http://github.com/flash-oss/allserver"
28
+ "url": "https://github.com/flash-oss/allserver"
29
29
  },
30
30
  "bugs": {
31
31
  "url": "https://github.com/flash-oss/allserver/issues"
@@ -34,14 +34,17 @@
34
34
  "author": "Vasyl Boroviak",
35
35
  "license": "MIT",
36
36
  "peerDependencies": {
37
+ "@aws-sdk/client-lambda": "3",
37
38
  "@grpc/grpc-js": "1",
38
39
  "@grpc/proto-loader": "0",
39
40
  "bullmq": "3 - 5",
40
- "express": "4",
41
- "micro": "10",
42
- "node-fetch": "2"
41
+ "express": "4 - 6",
42
+ "micro": "10"
43
43
  },
44
44
  "peerDependenciesMeta": {
45
+ "@aws-sdk/client-lambda": {
46
+ "optional": true
47
+ },
45
48
  "@grpc/grpc-js": {
46
49
  "optional": true
47
50
  },
@@ -56,41 +59,22 @@
56
59
  },
57
60
  "micro": {
58
61
  "optional": true
59
- },
60
- "node-fetch": {
61
- "optional": true
62
62
  }
63
63
  },
64
64
  "devDependencies": {
65
+ "@aws-sdk/client-lambda": "^3.921.0",
65
66
  "@grpc/grpc-js": "^1.13.4",
66
67
  "@grpc/proto-loader": "^0.8.0",
67
68
  "bullmq": "^5.56.9",
68
69
  "cls-hooked": "^4.2.2",
69
- "eslint": "^8.57.1",
70
70
  "express": "^4.21.2",
71
- "lambda-local": "^1.7.3",
71
+ "lambda-local": "^2.2.0",
72
72
  "micro": "^10.0.1",
73
- "mocha": "^11.7.1",
74
- "node-fetch": "^2.6.9",
75
73
  "nyc": "^17.1.0",
74
+ "oxlint": "^1.25.0",
76
75
  "prettier": "^2.1.1"
77
76
  },
78
77
  "dependencies": {
79
- "stampit": "^4.3.1"
80
- },
81
- "eslintConfig": {
82
- "parserOptions": {
83
- "ecmaVersion": 2022
84
- },
85
- "env": {
86
- "es6": true,
87
- "node": true,
88
- "mocha": true
89
- },
90
- "extends": "eslint:recommended"
91
- },
92
- "mocha": {
93
- "recursive": true,
94
- "exit": true
78
+ "stampit": "^5.0.1"
95
79
  }
96
80
  }
@@ -142,7 +142,7 @@ module.exports = require("stampit")({
142
142
  // The protocol implementation strategy.
143
143
  transport: null,
144
144
  // The maximum time to wait until returning the ALLSERVER_CLIENT_TIMEOUT error. 0 means - no timeout.
145
- timeout: 0,
145
+ timeout: 60_000,
146
146
  // Disable any exception throwing when calling any methods. Otherwise, throws network and server errors.
147
147
  neverThrow: true,
148
148
  // Automatically find (introspect) and call corresponding remote procedures. Use only the methods defined in client side.
@@ -204,7 +204,7 @@ module.exports = require("stampit")({
204
204
  const getTransport = stamp.compose.deepConfiguration.transports[schema.toLowerCase()];
205
205
  if (!getTransport) throw new Error(`Schema not supported: ${uri}`);
206
206
 
207
- this[p].transport = getTransport()({ uri });
207
+ this[p].transport = getTransport()({ uri, timeout: this[p].timeout });
208
208
  }
209
209
 
210
210
  if (before) this[p].before = [].concat(this[p].before).concat(before).filter(isFunction);
@@ -289,6 +289,9 @@ module.exports = require("stampit")({
289
289
  const transportMethod = ctx.isIntrospection ? "introspect" : "call";
290
290
  // In JavaScript if the `timeout` is null or undefined or some other object this condition will return `false`
291
291
  if (this[p].timeout > 0) {
292
+ let timeout = this[p].timeout;
293
+ // Let's give a chance to the Transport to return its native timeout response before returning Client's timeout response.
294
+ if (timeout >= 100) timeout = Math.round(timeout / 100 + timeout);
292
295
  return Promise.race([
293
296
  this[p].transport[transportMethod](ctx),
294
297
  new Promise((resolve) =>
@@ -299,7 +302,7 @@ module.exports = require("stampit")({
299
302
  code: "ALLSERVER_CLIENT_TIMEOUT",
300
303
  message: `The remote procedure ${ctx.procedureName} timed out in ${this[p].timeout} ms`,
301
304
  }),
302
- this[p].timeout
305
+ timeout
303
306
  )
304
307
  ),
305
308
  ]);
@@ -351,31 +354,26 @@ module.exports = require("stampit")({
351
354
  },
352
355
 
353
356
  statics: {
354
- defaults({
355
- transport,
356
- neverThrow,
357
- dynamicMethods,
358
- autoIntrospect,
359
- callIntrospectedProceduresOnly,
360
- nameMapper,
361
- before,
362
- after,
363
- } = {}) {
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
-
367
- return this.deepProps({
368
- [p]: {
369
- transport,
370
- neverThrow,
371
- dynamicMethods,
372
- callIntrospectedProceduresOnly,
373
- autoIntrospect,
374
- nameMapper,
375
- before,
376
- after,
377
- },
378
- });
357
+ /**
358
+ * @param [props.transport] {Object}
359
+ * @param [props.timeout] {number}
360
+ * @param [props.neverThrow] {boolean}
361
+ * @param [props.dynamicMethods] {Object}
362
+ * @param [props.autoIntrospect] {boolean}
363
+ * @param [props.callIntrospectedProceduresOnly] {boolean}
364
+ * @param [props.nameMapper] {Function}
365
+ * @param [props.before] {Function|Function[]}
366
+ * @param [props.after] {Function|Function[]}
367
+ * @return Copy of self
368
+ */
369
+ defaults(props = {}) {
370
+ props = Object.fromEntries(Object.entries(props).filter(([key, value]) => value !== undefined));
371
+ if (props.before)
372
+ props.before = (Array.isArray(props.before) ? props.before : [props.before]).filter(isFunction);
373
+ if (props.after)
374
+ props.after = (Array.isArray(props.after) ? props.after : [props.after]).filter(isFunction);
375
+
376
+ return this.deepProps({ [p]: props });
379
377
  },
380
378
 
381
379
  addTransport({ schema, Transport }) {
@@ -4,7 +4,6 @@ module.exports = require("./ClientTransport").compose({
4
4
  props: {
5
5
  Queue: require("bullmq").Queue,
6
6
  QueueEvents: require("bullmq").QueueEvents,
7
- _timeout: 60000,
8
7
  _queue: null,
9
8
  _queueEvents: null,
10
9
  _jobsOptions: null,
@@ -22,7 +21,6 @@ module.exports = require("./ClientTransport").compose({
22
21
  retryStrategy: null, // only one attempt to connect
23
22
  };
24
23
  }
25
- this._timeout = timeout || this._timeout;
26
24
 
27
25
  this._queue = new this.Queue(queueName, { connection: connectionOptions });
28
26
  this._queue.on("error", () => {}); // The only reason we subscribe is to avoid bullmq to print errors to console
@@ -49,7 +47,7 @@ module.exports = require("./ClientTransport").compose({
49
47
  try {
50
48
  await this._queue.waitUntilReady();
51
49
  const job = await bullmq.queue.add(procedureName, bullmq.data, bullmq.jobsOptions);
52
- return await job.waitUntilFinished(bullmq.queueEvents, this._timeout);
50
+ return await job.waitUntilFinished(bullmq.queueEvents, this.timeout); // this.timeout is a property of the parent ClientTransport
53
51
  } catch (err) {
54
52
  if (err.code === "ECONNREFUSED") err.noNetToServer = true;
55
53
  throw err;
@@ -5,13 +5,16 @@ module.exports = require("stampit")({
5
5
 
6
6
  props: {
7
7
  uri: null,
8
+ timeout: 60_000,
8
9
  },
9
10
 
10
- init({ uri }) {
11
+ init({ uri, timeout }) {
11
12
  if (!isFunction(this.introspect)) throw new Error("ClientTransport must implement introspect()");
12
13
  if (!isFunction(this.call)) throw new Error("ClientTransport must implement call()");
13
14
 
14
15
  this.uri = uri || this.uri;
15
16
  if (!isString(this.uri)) throw new Error("`uri` connection string is required");
17
+
18
+ this.timeout = timeout != null ? timeout : this.timeout;
16
19
  },
17
20
  });
@@ -4,7 +4,7 @@ module.exports = require("./ClientTransport").compose({
4
4
  name: "GrpcClientTransport",
5
5
 
6
6
  props: {
7
- _fs: require("fs"),
7
+ _fs: require("node:fs"),
8
8
  _grpc: require("@grpc/grpc-js"),
9
9
  _protoLoader: require("@grpc/proto-loader"),
10
10
  _grpcClientForIntrospection: null,
@@ -4,8 +4,7 @@ module.exports = require("./ClientTransport").compose({
4
4
  name: "HttpClientTransport",
5
5
 
6
6
  props: {
7
- // eslint-disable-next-line no-undef
8
- fetch: (typeof globalThis !== "undefined" && globalThis.fetch) || require("node-fetch"),
7
+ fetch: globalThis.fetch,
9
8
  headers: {
10
9
  "Content-Type": "application/json; charset=utf-8",
11
10
  },
@@ -51,7 +50,7 @@ module.exports = require("./ClientTransport").compose({
51
50
  const json = JSON.parse(text);
52
51
  error = new Error((json && json.message) || text);
53
52
  if (json && json.code) error.code = json.code;
54
- } catch (err) {
53
+ } catch {
55
54
  // ignoring. Not a JSON
56
55
  }
57
56
  }
@@ -7,13 +7,7 @@ module.exports = require("./ClientTransport").compose({
7
7
 
8
8
  init() {
9
9
  if (!this.awsSdkLambdaClient) {
10
- let Lambda;
11
- try {
12
- Lambda = require("aws-sdk").Lambda; // AWS SDK v2 adoption
13
- } catch {
14
- Lambda = require("@aws-sdk/client-lambda").Lambda;
15
- }
16
-
10
+ const { Lambda } = require("@aws-sdk/client-lambda");
17
11
  this.awsSdkLambdaClient = new Lambda();
18
12
  }
19
13
  },
@@ -29,30 +23,48 @@ module.exports = require("./ClientTransport").compose({
29
23
  },
30
24
 
31
25
  async call(ctx) {
32
- let promise = this.awsSdkLambdaClient.invoke({
33
- FunctionName: this.uri.substring("lambda://".length),
34
- // TODO: change to this during the next major release:
35
- // Payload: JSON.stringify(ctx.arg),
36
- Payload: JSON.stringify({
37
- callContext: { ...ctx.lambda.callContext, procedureName: ctx.procedureName },
38
- callArg: ctx.lambda.callArg,
39
- ...ctx.arg,
40
- }),
41
- });
42
- if (typeof promise.promise === "function") promise = promise.promise(); // AWS SDK v2 adoption
43
- const invocationResponse = await promise;
44
- return JSON.parse(Buffer.from(invocationResponse.Payload));
26
+ let invocationResponse;
27
+ try {
28
+ invocationResponse = await this.awsSdkLambdaClient.invoke(
29
+ {
30
+ FunctionName: this.uri.substring("lambda://".length),
31
+ Payload: JSON.stringify(ctx.arg),
32
+ },
33
+ { requestTimeout: this.timeout } // this.timeout is a property of the parent ClientTransport
34
+ );
35
+ ctx.lambda.response = invocationResponse;
36
+ } catch (e) {
37
+ if (e.name.includes("ProviderError") || e.name.includes("NotFound")) e.noNetToServer = true;
38
+ throw e;
39
+ }
40
+
41
+ let json;
42
+ try {
43
+ json = JSON.parse(Buffer.from(invocationResponse.Payload));
44
+ } catch (e) {
45
+ e.code = "ALLSERVER_RPC_RESPONSE_IS_NOT_JSON";
46
+ throw e;
47
+ }
48
+
49
+ const error = new Error("Bad response payload");
50
+ if (json.constructor !== Object) {
51
+ error.code = "ALLSERVER_RPC_RESPONSE_IS_NOT_OBJECT";
52
+ throw error;
53
+ }
54
+
55
+ // Yes, the AWS Lambda sometimes returns a empty object when the Lambda runtime shuts down abruptly for no apparent reason.
56
+ if (Object.keys(json).length === 0) {
57
+ error.code = "ALLSERVER_RPC_RESPONSE_IS_EMPTY_OBJECT";
58
+ throw error;
59
+ }
60
+
61
+ return json;
45
62
  },
46
63
 
47
64
  createCallContext(defaultCtx) {
48
65
  return {
49
66
  ...defaultCtx,
50
- // TODO: change to this during the next major release:
51
- // lambda: {}
52
- lambda: {
53
- callContext: {},
54
- callArg: defaultCtx.arg,
55
- },
67
+ lambda: {},
56
68
  };
57
69
  },
58
70
  },
@@ -1,4 +1,4 @@
1
- const assert = require("assert");
1
+ const assert = require("node:assert/strict");
2
2
 
3
3
  const { isObject, isBoolean, isFunction, uniq } = require("../util");
4
4
 
@@ -180,14 +180,22 @@ module.exports = require("stampit")({
180
180
  },
181
181
 
182
182
  statics: {
183
- defaults({ 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);
183
+ /**
184
+ * @param [propsArg.procedures] {Object}
185
+ * @param [propsArg.transport] {Object}
186
+ * @param [propsArg.logger] {Object}
187
+ * @param [propsArg.introspection] {boolean}
188
+ * @param [propsArg.before] {Object}
189
+ * @param [propsArg.after] {Object}
190
+ * @return Copy of self
191
+ */
192
+ defaults(propsArg = {}) {
193
+ propsArg = Object.fromEntries(Object.entries(propsArg).filter(([key, value]) => value !== undefined));
194
+ let { before, after, ...props } = propsArg;
195
+ if (before) before = (Array.isArray(before) ? before : [before]).filter(isFunction);
196
+ if (after) after = (Array.isArray(after) ? after : [after]).filter(isFunction);
186
197
 
187
- return this.compose({
188
- props: { procedures, transport, logger, introspection },
189
- deepProps: { before, after },
190
- });
198
+ return this.compose({ props, deepProps: { before, after } });
191
199
  },
192
200
  },
193
201
  });
@@ -1,4 +1,4 @@
1
- const { parse: parseUrl } = require("url");
1
+ const { parse: parseUrl } = require("node:url");
2
2
 
3
3
  module.exports = require("./Transport").compose({
4
4
  name: "ExpressTransport",
@@ -4,7 +4,7 @@ module.exports = require("./Transport").compose({
4
4
  name: "GrpcTransport",
5
5
 
6
6
  props: {
7
- _fs: require("fs"),
7
+ _fs: require("node:fs"),
8
8
  _grpc: require("@grpc/grpc-js"),
9
9
  _protoLoader: require("@grpc/proto-loader"),
10
10
  port: process.env.PORT,
@@ -1,5 +1,5 @@
1
- const http = require("http");
2
- const { parse: parseUrl, URLSearchParams } = require("url");
1
+ const http = require("node:http");
2
+ const { parse: parseUrl, URLSearchParams } = require("node:url");
3
3
 
4
4
  module.exports = require("./Transport").compose({
5
5
  name: "HttpTransport",
@@ -22,7 +22,7 @@ module.exports = require("./Transport").compose({
22
22
  if (bodyBuffer.length !== 0) arg = await this.micro.json(ctx.http.req);
23
23
  ctx.arg = arg;
24
24
  return true;
25
- } catch (err) {
25
+ } catch {
26
26
  return false;
27
27
  }
28
28
  },
@@ -17,13 +17,11 @@ module.exports = require("./Transport").compose({
17
17
  }
18
18
  }
19
19
  return true;
20
- } catch (err) {
20
+ } catch {
21
21
  return false;
22
22
  }
23
23
  } else {
24
- // TODO: change to this during the next major release:
25
- // ctx.arg = ctx.lambda.invoke || {};
26
- ctx.arg = ctx.lambda.invoke.callArg || ctx.lambda.invoke || {};
24
+ ctx.arg = ctx.lambda.invoke || {};
27
25
  return true;
28
26
  }
29
27
  },
@@ -56,11 +54,7 @@ module.exports = require("./Transport").compose({
56
54
  headers: event?.headers,
57
55
  };
58
56
  } else {
59
- // TODO: change to this during the next major release:
60
- // lambda.invoke = event || {};
61
- lambda.invoke = event?.callArg
62
- ? { callContext: event?.callContext, callArg: event?.callArg }
63
- : event || {};
57
+ lambda.invoke = event || {};
64
58
  }
65
59
 
66
60
  this._handleRequest({ ...defaultCtx, lambda });
@@ -70,9 +64,7 @@ module.exports = require("./Transport").compose({
70
64
 
71
65
  getProcedureName(ctx) {
72
66
  const { isHttp, http, invoke } = ctx.lambda;
73
- // TODO: change to this during the next major release:
74
- // return (isHttp ? http.path.substr(1) : invoke._?.procedureName) || ""; // if no procedureName then it's an introspection
75
- return isHttp ? http.path.substr(1) : invoke.callContext?.procedureName || invoke._?.procedureName || ""; // if no procedureName then it's an introspection
67
+ return (isHttp ? http.path.substr(1) : invoke._?.procedureName) || ""; // if no procedureName then it's an introspection
76
68
  },
77
69
 
78
70
  isIntrospection(ctx) {