allserver 2.2.1 → 2.4.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
@@ -361,8 +361,6 @@ app.use("/route-with-allsever", middleware);
361
361
 
362
362
  Doesn't require a dedicated client transport. Use the HTTP client below.
363
363
 
364
- NB: not yet tested in production.
365
-
366
364
  ```js
367
365
  const { Allserver, LambdaTransport } = require("allserver");
368
366
 
@@ -655,13 +653,11 @@ import { Lambda } from "@aws-sdk/client-lambda";
655
653
  const invocationResponse = await new Lambda().invoke({
656
654
  FunctionName: "my-lambda-name",
657
655
  Payload: JSON.stringify({
656
+ _: { procedureName: "updateUser" },
658
657
  id: "123412341234123412341234",
659
658
  firstName: "Fred",
660
659
  lastName: "Flinstone",
661
660
  }),
662
- ClientContext: JSON.stringify({
663
- procedureName: "updateUser",
664
- }),
665
661
  });
666
662
  const { success, code, message, user } = JSON.parse(invocationResponse.Payload);
667
663
  ```
@@ -669,7 +665,7 @@ const { success, code, message, user } = JSON.parse(invocationResponse.Payload);
669
665
  Alternatively, you can call the same procedure using the `aws` CLI:
670
666
 
671
667
  ```shell
672
- aws lambda invoke --function-name my-lambda-name --client-context '{"procedureName":"updateUser"}' --payload '{"id":"123412341234123412341234","firstName":"Fred","lastName":"Flinstone"}'
668
+ aws lambda invoke --function-name my-lambda-name --payload '{"_":{"procedureName":"updateUser"},"id":"123412341234123412341234","firstName":"Fred","lastName":"Flinstone"}}'
673
669
  ```
674
670
 
675
671
  ## `AllserverClient` options
@@ -913,7 +909,7 @@ const client = AllserverClient({
913
909
  ctx.http.headers.authorization = "Basic my-token";
914
910
  },
915
911
  async after(ctx) {
916
- if (ctx.error) console.error(ctx.error) else console.log(ctx.result);
912
+ if (ctx.error) console.error(ctx.error); else console.log(ctx.result);
917
913
  },
918
914
  });
919
915
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allserver",
3
- "version": "2.2.1",
3
+ "version": "2.4.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": {
@@ -65,6 +65,7 @@
65
65
  "@grpc/grpc-js": "^1.1.7",
66
66
  "@grpc/proto-loader": "^0.7.5",
67
67
  "bullmq": "^3.10.1",
68
+ "cls-hooked": "^4.2.2",
68
69
  "eslint": "^8.55.0",
69
70
  "express": "^4.18.2",
70
71
  "lambda-local": "^1.7.3",
@@ -213,40 +213,55 @@ module.exports = require("stampit")({
213
213
  const defaultCtx = { client: this, isIntrospection: true };
214
214
  const ctx = transport.createCallContext(defaultCtx);
215
215
 
216
- await this._callMiddlewares(ctx, "before");
217
-
218
- if (!ctx.result) {
219
- try {
220
- // This is supposed to be executed only once (per uri) unless it throws.
221
- // There are only 3 situations when this throws:
222
- // * the "introspect" method not found on server,
223
- // * the network request is malformed,
224
- // * couldn't connect to the remote host.
225
- ctx.result = await transport.introspect(ctx);
226
- } catch (err) {
227
- ctx.result = {
228
- success: false,
229
- code: "ALLSERVER_CLIENT_INTROSPECTION_FAILED",
230
- message: `Couldn't introspect ${transport.uri} due to: ${err.message}`,
231
- noNetToServer: Boolean(err.noNetToServer),
232
- error: err,
233
- };
216
+ await this._callMiddlewares(ctx, "before", async () => {
217
+ if (!ctx.result) {
218
+ try {
219
+ // This is supposed to be executed only once (per uri) unless it throws.
220
+ // There are only 3 situations when this throws:
221
+ // * the "introspect" method not found on server,
222
+ // * the network request is malformed,
223
+ // * couldn't connect to the remote host.
224
+ ctx.result = await transport.introspect(ctx);
225
+ } catch (err) {
226
+ ctx.result = {
227
+ success: false,
228
+ code: "ALLSERVER_CLIENT_INTROSPECTION_FAILED",
229
+ message: `Couldn't introspect ${transport.uri} due to: ${err.message}`,
230
+ noNetToServer: Boolean(err.noNetToServer),
231
+ error: err,
232
+ };
233
+ }
234
234
  }
235
- }
236
235
 
237
- await this._callMiddlewares(ctx, "after");
236
+ await this._callMiddlewares(ctx, "after");
237
+ });
238
238
 
239
239
  return ctx.result;
240
240
  },
241
241
 
242
- async _callMiddlewares(ctx, middlewareType) {
243
- const middlewares = [].concat(this[p][middlewareType]).filter(isFunction);
244
- for (const middleware of middlewares) {
245
- try {
246
- const result = await middleware.call(this, ctx);
242
+ async _callMiddlewares(ctx, middlewareType, next) {
243
+ const runMiddlewares = async (middlewares) => {
244
+ if (!middlewares?.length) {
245
+ // no middlewares to run
246
+ if (next) return await next();
247
+ return;
248
+ }
249
+ const middleware = middlewares[0];
250
+ async function handleMiddlewareResult(result) {
247
251
  if (result !== undefined) {
248
252
  ctx.result = result;
249
- break;
253
+ // Do not call any more middlewares
254
+ } else {
255
+ await runMiddlewares(middlewares.slice(1));
256
+ }
257
+ }
258
+ try {
259
+ if (middleware.length > 1) {
260
+ // This middleware accepts more than one argument
261
+ await middleware.call(this, ctx, handleMiddlewareResult);
262
+ } else {
263
+ const result = await middleware.call(this, ctx);
264
+ await handleMiddlewareResult(result);
250
265
  }
251
266
  } catch (err) {
252
267
  if (!this[p].neverThrow) throw err;
@@ -257,34 +272,42 @@ module.exports = require("stampit")({
257
272
  message = `The '${middlewareType}' middleware error while calling '${ctx.procedureName}' procedure: ${err.message}`;
258
273
  }
259
274
  ctx.result = { success: false, code, message, error: err };
260
- return;
275
+ // Do not call any more middlewares
276
+ if (next) return await next();
261
277
  }
262
- }
278
+ };
279
+
280
+ const middlewares = [].concat(this[p][middlewareType]).filter(isFunction);
281
+ return await runMiddlewares(middlewares);
263
282
  },
264
283
 
265
284
  async call(procedureName, arg) {
285
+ if (!arg) arg = {};
286
+ if (!arg._) arg._ = {};
287
+ arg._.procedureName = procedureName;
288
+
266
289
  const transport = this[p].transport;
267
290
  const defaultCtx = { procedureName, arg, client: this };
268
291
  const ctx = transport.createCallContext(defaultCtx);
269
292
 
270
- await this._callMiddlewares(ctx, "before");
271
-
272
- if (!ctx.result) {
273
- try {
274
- ctx.result = await transport.call(ctx);
275
- } catch (err) {
276
- if (!this[p].neverThrow) throw err;
277
-
278
- let { code, message } = err;
279
- if (!err.code || err.noNetToServer) {
280
- code = "ALLSERVER_CLIENT_PROCEDURE_UNREACHABLE";
281
- message = `Couldn't reach remote procedure ${ctx.procedureName} due to: ${err.message}`;
293
+ await this._callMiddlewares(ctx, "before", async () => {
294
+ if (!ctx.result) {
295
+ try {
296
+ ctx.result = await transport.call(ctx);
297
+ } catch (err) {
298
+ if (!this[p].neverThrow) throw err;
299
+
300
+ let { code, message } = err;
301
+ if (!err.code || err.noNetToServer) {
302
+ code = "ALLSERVER_CLIENT_PROCEDURE_UNREACHABLE";
303
+ message = `Couldn't reach remote procedure ${ctx.procedureName} due to: ${err.message}`;
304
+ }
305
+ ctx.result = { success: false, code, message, error: err };
282
306
  }
283
- ctx.result = { success: false, code, message, error: err };
284
307
  }
285
- }
286
308
 
287
- await this._callMiddlewares(ctx, "after");
309
+ await this._callMiddlewares(ctx, "after");
310
+ });
288
311
 
289
312
  return ctx.result;
290
313
  },
@@ -28,12 +28,15 @@ module.exports = require("./ClientTransport").compose({
28
28
  return result;
29
29
  },
30
30
 
31
- async call({ procedureName, lambda }) {
31
+ async call(ctx) {
32
32
  let promise = this.awsSdkLambdaClient.invoke({
33
33
  FunctionName: this.uri.substring("lambda://".length),
34
+ // TODO: change to this during the next major release:
35
+ // Payload: JSON.stringify(ctx.arg),
34
36
  Payload: JSON.stringify({
35
- callContext: { ...lambda.callContext, procedureName },
36
- callArg: lambda.callArg,
37
+ callContext: { ...ctx.lambda.callContext, procedureName: ctx.procedureName },
38
+ callArg: ctx.lambda.callArg,
39
+ ...ctx.arg,
37
40
  }),
38
41
  });
39
42
  if (typeof promise.promise === "function") promise = promise.promise(); // AWS SDK v2 adoption
@@ -44,6 +47,8 @@ module.exports = require("./ClientTransport").compose({
44
47
  createCallContext(defaultCtx) {
45
48
  return {
46
49
  ...defaultCtx,
50
+ // TODO: change to this during the next major release:
51
+ // lambda: {}
47
52
  lambda: {
48
53
  callContext: {},
49
54
  callArg: defaultCtx.arg,
@@ -50,6 +50,13 @@ module.exports = require("stampit")({
50
50
  await this.transport.prepareIntrospectionReply(ctx);
51
51
  },
52
52
 
53
+ /**
54
+ * This method does not throw.
55
+ * The `ctx.procedure` is the function to call.
56
+ * @param ctx
57
+ * @return {Promise<void>}
58
+ * @private
59
+ */
53
60
  async _callProcedure(ctx) {
54
61
  if (!isFunction(ctx.procedure)) {
55
62
  ctx.result = {
@@ -91,16 +98,29 @@ module.exports = require("stampit")({
91
98
  }
92
99
  },
93
100
 
94
- async _callMiddlewares(ctx, middlewareType) {
95
- if (!this[middlewareType]) return;
96
-
97
- const middlewares = [].concat(this[middlewareType]).filter(isFunction);
98
- for (const middleware of middlewares) {
99
- try {
100
- const result = await middleware.call(this, ctx);
101
+ async _callMiddlewares(ctx, middlewareType, next) {
102
+ const runMiddlewares = async (middlewares) => {
103
+ if (!middlewares?.length) {
104
+ // no middlewares to run
105
+ if (next) return await next();
106
+ return;
107
+ }
108
+ const middleware = middlewares[0];
109
+ async function handleMiddlewareResult(result) {
101
110
  if (result !== undefined) {
102
111
  ctx.result = result;
103
- break;
112
+ // Do not call any more middlewares
113
+ } else {
114
+ await runMiddlewares(middlewares.slice(1));
115
+ }
116
+ }
117
+ try {
118
+ if (middleware.length > 1) {
119
+ // This middleware accepts more than one argument
120
+ await middleware.call(this, ctx, handleMiddlewareResult);
121
+ } else {
122
+ const result = await middleware.call(this, ctx);
123
+ await handleMiddlewareResult(result);
104
124
  }
105
125
  } catch (err) {
106
126
  const code = err.code || "ALLSERVER_MIDDLEWARE_ERROR";
@@ -111,9 +131,14 @@ module.exports = require("stampit")({
111
131
  code,
112
132
  message: `'${err.message}' error in '${middlewareType}' middleware`,
113
133
  };
114
- return;
134
+ // Do not call any more middlewares
135
+ if (next) return await next();
115
136
  }
116
- }
137
+ };
138
+
139
+ const middlewares = [].concat(this[middlewareType]).filter(isFunction);
140
+
141
+ return await runMiddlewares(middlewares);
117
142
  },
118
143
 
119
144
  async handleCall(ctx) {
@@ -123,18 +148,22 @@ module.exports = require("stampit")({
123
148
  ctx.isIntrospection = this.transport.isIntrospection(ctx);
124
149
  if (!ctx.isIntrospection && ctx.procedureName) ctx.procedure = this.procedures[ctx.procedureName];
125
150
 
126
- await this._callMiddlewares(ctx, "before");
151
+ if (!ctx.arg) ctx.arg = {};
152
+ if (!ctx.arg._) ctx.arg._ = {};
153
+ if (!ctx.arg._.procedureName) ctx.arg._.procedureName = ctx.procedureName;
127
154
 
128
- if (!ctx.result) {
129
- if (ctx.isIntrospection) {
130
- await this._introspect(ctx);
131
- } else {
132
- await this._callProcedure(ctx);
155
+ await this._callMiddlewares(ctx, "before", async () => {
156
+ if (!ctx.result) {
157
+ if (ctx.isIntrospection) {
158
+ await this._introspect(ctx);
159
+ } else {
160
+ await this._callProcedure(ctx);
161
+ }
133
162
  }
134
- }
135
163
 
136
- // Warning! This call might overwrite an existing result.
137
- await this._callMiddlewares(ctx, "after");
164
+ // Warning! This call might overwrite an existing result.
165
+ await this._callMiddlewares(ctx, "after");
166
+ });
138
167
 
139
168
  return this.transport.reply(ctx);
140
169
  },
@@ -21,7 +21,9 @@ module.exports = require("./Transport").compose({
21
21
  return false;
22
22
  }
23
23
  } else {
24
- ctx.arg = ctx.lambda.invoke.callArg || {};
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 || {};
25
27
  return true;
26
28
  }
27
29
  },
@@ -54,7 +56,11 @@ module.exports = require("./Transport").compose({
54
56
  headers: event?.headers,
55
57
  };
56
58
  } else {
57
- lambda.invoke = { callContext: event?.callContext, callArg: event?.callArg };
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 || {};
58
64
  }
59
65
 
60
66
  this._handleRequest({ ...defaultCtx, lambda });
@@ -64,7 +70,9 @@ module.exports = require("./Transport").compose({
64
70
 
65
71
  getProcedureName(ctx) {
66
72
  const { isHttp, http, invoke } = ctx.lambda;
67
- return isHttp ? http.path.substr(1) : invoke.callContext?.procedureName;
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
68
76
  },
69
77
 
70
78
  isIntrospection(ctx) {