allserver 2.1.0 → 2.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
@@ -176,7 +176,10 @@ npm i allserver
176
176
 
177
177
  #### Client
178
178
 
179
- Same as the HTTP protocol client above.
179
+ Two ways:
180
+
181
+ - Same as the HTTP protocol client above.
182
+ - Direct invocation via AWS SDK or AWS CLI.
180
183
 
181
184
  ### gRPC protocol
182
185
 
@@ -369,15 +372,16 @@ exports.handler = Allserver({
369
372
  }).start();
370
373
  ```
371
374
 
372
- Or, if you want each individual procedure to be the Lambda handler, pass `mapProceduresToExports: true`.
375
+ ### Server in Bare AWS Lambda
376
+
377
+ Invoke directly via AWS SDK or AWS CLI. But better use the LambdaClientTransport (aka `"lambda://"` scheme) below.
373
378
 
374
379
  ```js
375
380
  const { Allserver, LambdaTransport } = require("allserver");
376
381
 
377
- // Note! No `handler` here.
378
- exports = Allserver({
382
+ exports.handler = Allserver({
379
383
  procedures,
380
- transport: LambdaTransport({ mapProceduresToExports: true }),
384
+ transport: LambdaTransport(),
381
385
  }).start();
382
386
  ```
383
387
 
@@ -596,19 +600,76 @@ Sometimes you need to unit test your procedures via the `AllserverClient`. For t
596
600
  const { Allserver, MemoryTransport } = require("allserver");
597
601
 
598
602
  const memoryServer = Allserver({
599
- procedures,
600
- transport: MemoryTransport(),
603
+ procedures,
604
+ transport: MemoryTransport(),
601
605
  });
602
606
 
603
607
  const client = memoryServer.start();
604
608
 
605
609
  const { success, code, message, user } = await client.updateUser({
610
+ id: "123412341234123412341234",
611
+ firstName: "Fred",
612
+ lastName: "Flinstone",
613
+ });
614
+
615
+ assert(success === true);
616
+ ```
617
+
618
+ ### Bare AWS Lambda invocation
619
+
620
+ First you need to install any of the AWS SDK versions.
621
+
622
+ ```shell
623
+ npm i allserver aws-sdk
624
+ ```
625
+
626
+ or
627
+
628
+ ```shell
629
+ npm i allserver @aws-sdk/client-lambda
630
+ ```
631
+
632
+ The invoke the lambda this way:
633
+
634
+ ```js
635
+ const { AllserverClient } = require("allserver");
636
+ // or
637
+ const AllserverClient = require("allserver/Client");
638
+
639
+ const client = AllserverClient({ uri: "lambda://my-lambda-name" });
640
+
641
+ const { success, code, message, user } = await client.updateUser({
642
+ id: "123412341234123412341234",
643
+ firstName: "Fred",
644
+ lastName: "Flinstone",
645
+ });
646
+ ```
647
+
648
+ #### Using AWS SDK
649
+
650
+ As usual, the client side does not require the Allserver packages at all.
651
+
652
+ ```js
653
+ import { Lambda } from "@aws-sdk/client-lambda";
654
+
655
+ const invocationResponse = await new Lambda().invoke({
656
+ FunctionName: "my-lambda-name",
657
+ Payload: JSON.stringify({
606
658
  id: "123412341234123412341234",
607
659
  firstName: "Fred",
608
660
  lastName: "Flinstone",
661
+ }),
662
+ ClientContext: JSON.stringify({
663
+ procedureName: "updateUser",
664
+ }),
609
665
  });
666
+ const { success, code, message, user } = JSON.parse(invocationResponse.Payload);
667
+ ```
610
668
 
611
- assert(success === true);
669
+ Alternatively, you can call the same procedure using the `aws` CLI:
670
+
671
+ ```shell
672
+ aws lambda invoke --function-name my-lambda-name --client-context '{"procedureName":"updateUser"}' --payload '{"id":"123412341234123412341234","firstName":"Fred","lastName":"Flinstone"}'
612
673
  ```
613
674
 
614
675
  ## `AllserverClient` options
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allserver",
3
- "version": "2.1.0",
3
+ "version": "2.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": {
@@ -9,6 +9,8 @@
9
9
  "cov": "nyc --reporter=html npm run test"
10
10
  },
11
11
  "keywords": [
12
+ "simple",
13
+ "rpc",
12
14
  "http",
13
15
  "grpc",
14
16
  "websocket",
@@ -63,7 +65,7 @@
63
65
  "@grpc/grpc-js": "^1.1.7",
64
66
  "@grpc/proto-loader": "^0.7.5",
65
67
  "bullmq": "^3.10.1",
66
- "eslint": "^7.9.0",
68
+ "eslint": "^8.55.0",
67
69
  "express": "^4.18.2",
68
70
  "lambda-local": "^1.7.3",
69
71
  "micro": "^10.0.1",
@@ -77,7 +79,7 @@
77
79
  },
78
80
  "eslintConfig": {
79
81
  "parserOptions": {
80
- "ecmaVersion": 2019
82
+ "ecmaVersion": 2022
81
83
  },
82
84
  "env": {
83
85
  "es6": true,
@@ -165,6 +165,7 @@ module.exports = require("stampit")({
165
165
  https() { return require("./HttpClientTransport"); },
166
166
  grpc() { return require("./GrpcClientTransport"); },
167
167
  bullmq() { return require("./BullmqClientTransport"); },
168
+ lambda() { return require("./LambdaClientTransport"); },
168
169
  },
169
170
  },
170
171
 
@@ -0,0 +1,58 @@
1
+ module.exports = require("./ClientTransport").compose({
2
+ name: "LambdaClientTransport",
3
+
4
+ props: {
5
+ awsSdkLambdaClient: null,
6
+ },
7
+
8
+ init() {
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
+
17
+ this.awsSdkLambdaClient = new Lambda();
18
+ }
19
+ },
20
+
21
+ methods: {
22
+ async introspect(ctx) {
23
+ ctx.procedureName = "";
24
+ const result = await this.call(ctx);
25
+ // The server-side Transport will not have the call result if introspection is not available on the server side,
26
+ // but the server itself is up and running processing calls.
27
+ if (!result.procedures) throw Error("The lambda introspection call returned nothing"); // The ClientTransport expects us to throw if call fails
28
+ return result;
29
+ },
30
+
31
+ async call({ procedureName, lambda }) {
32
+ let promise = this.awsSdkLambdaClient.invoke({
33
+ FunctionName: this.uri.substring("lambda://".length),
34
+ // InvocationType: "RequestResponse",
35
+ Payload: lambda.payload && JSON.stringify(lambda.payload),
36
+ ClientContext: Buffer.from(
37
+ JSON.stringify({
38
+ ...lambda.clientContext,
39
+ procedureName,
40
+ })
41
+ ).toString("base64"),
42
+ });
43
+ if (typeof promise.promise === "function") promise = promise.promise(); // AWS SDK v2 adoption
44
+ const invocationResponse = await promise;
45
+ return JSON.parse(invocationResponse.Payload);
46
+ },
47
+
48
+ createCallContext(defaultCtx) {
49
+ return {
50
+ ...defaultCtx,
51
+ lambda: {
52
+ clientContext: {},
53
+ payload: defaultCtx.arg,
54
+ },
55
+ };
56
+ },
57
+ },
58
+ });
package/src/index.js CHANGED
@@ -47,4 +47,7 @@ module.exports = {
47
47
  get MemoryClientTransport() {
48
48
  return require("./client/MemoryClientTransport");
49
49
  },
50
+ get LambdaClientTransport() {
51
+ return require("./client/LambdaClientTransport");
52
+ },
50
53
  };
@@ -1,30 +1,26 @@
1
1
  module.exports = require("./Transport").compose({
2
2
  name: "LambdaTransport",
3
3
 
4
- props: {
5
- _mapProceduresToExports: false,
6
- },
7
-
8
- init({ mapProceduresToExports }) {
9
- this._mapProceduresToExports = mapProceduresToExports || this._mapProceduresToExports;
10
- },
11
-
12
4
  methods: {
13
- async deserializeRequest(ctx) {
14
- const body = ctx.lambda.event.body;
15
- let arg = ctx.lambda.query;
16
- try {
17
- // If there is no body we will use request query (aka search params)
18
- if (body) arg = JSON.parse(body);
19
- ctx.arg = arg;
5
+ async deserializeEvent(ctx) {
6
+ if (ctx.lambda.isHttp) {
7
+ const body = ctx.lambda.event.body;
8
+ let query = ctx.lambda.query;
9
+ try {
10
+ // If there is no body we will use request query (aka search params)
11
+ ctx.arg = body ? JSON.parse(body) : query;
12
+ return true;
13
+ } catch (err) {
14
+ return false;
15
+ }
16
+ } else {
17
+ ctx.arg = ctx.lambda.event || {};
20
18
  return true;
21
- } catch (err) {
22
- return false;
23
19
  }
24
20
  },
25
21
 
26
22
  async _handleRequest(ctx) {
27
- if (await this.deserializeRequest(ctx)) {
23
+ if (await this.deserializeEvent(ctx)) {
28
24
  await ctx.allserver.handleCall(ctx);
29
25
  } else {
30
26
  // HTTP protocol request was malformed (not expected structure).
@@ -36,37 +32,21 @@ module.exports = require("./Transport").compose({
36
32
  },
37
33
 
38
34
  startServer(defaultCtx) {
39
- if (this._mapProceduresToExports) {
40
- const exports = {};
41
- for (const procedureName of Object.keys(defaultCtx.allserver.procedures)) {
42
- exports[procedureName] = async (event) =>
43
- new Promise((resolve) => {
44
- const path = "/" + procedureName;
45
- const ctx = {
46
- ...defaultCtx,
47
- lambda: { event, resolve, path, query: { ...(event.queryStringParameters || {}) } },
48
- };
49
-
50
- this._handleRequest(ctx);
51
- });
52
- }
53
- return exports;
54
- }
55
-
56
- return async (event) => {
35
+ return async (event, context) => {
57
36
  return new Promise((resolve) => {
58
- const ctx = {
59
- ...defaultCtx,
60
- lambda: { event, resolve, path: event.path, query: { ...(event.queryStringParameters || {}) } },
61
- };
37
+ const path = (event && (event.path || event.requestContext?.http?.path)) || undefined;
38
+ const isHttp = Boolean(path);
39
+ const procedureName = isHttp ? path.substr(1) : context.clientContext?.procedureName;
40
+ const query = { ...(event?.queryStringParameters || {}) };
41
+ const lambda = { resolve, isHttp, event, context, path, query, procedureName };
62
42
 
63
- this._handleRequest(ctx);
43
+ this._handleRequest({ ...defaultCtx, lambda });
64
44
  });
65
45
  };
66
46
  },
67
47
 
68
- getProcedureName(ctx) {
69
- return ctx.lambda.path.substr(1);
48
+ getProcedureName({ lambda: { procedureName } }) {
49
+ return procedureName;
70
50
  },
71
51
 
72
52
  isIntrospection(ctx) {
@@ -85,12 +65,16 @@ module.exports = require("./Transport").compose({
85
65
  },
86
66
 
87
67
  reply(ctx) {
88
- if (!ctx.lambda.statusCode) ctx.lambda.statusCode = 200;
89
- ctx.lambda.resolve({
90
- statusCode: ctx.lambda.statusCode,
91
- headers: { "content-type": "application/json" },
92
- body: JSON.stringify(ctx.result),
93
- });
68
+ if (ctx.lambda.isHttp) {
69
+ if (!ctx.lambda.statusCode) ctx.lambda.statusCode = 200;
70
+ ctx.lambda.resolve({
71
+ statusCode: ctx.lambda.statusCode,
72
+ headers: { "content-type": "application/json" },
73
+ body: JSON.stringify(ctx.result),
74
+ });
75
+ } else {
76
+ ctx.lambda.resolve(ctx.result);
77
+ }
94
78
  },
95
79
  },
96
80
  });