allserver 2.0.1 → 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
 
@@ -588,6 +592,86 @@ const data = await job.waitUntilFinished(queueEvents, 30_000);
588
592
  const { success, code, message, user } = data;
589
593
  ```
590
594
 
595
+ ### In memory
596
+
597
+ Sometimes you need to unit test your procedures via the `AllserverClient`. For that we have `MemoryTransport`.
598
+
599
+ ```js
600
+ const { Allserver, MemoryTransport } = require("allserver");
601
+
602
+ const memoryServer = Allserver({
603
+ procedures,
604
+ transport: MemoryTransport(),
605
+ });
606
+
607
+ const client = memoryServer.start();
608
+
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({
658
+ id: "123412341234123412341234",
659
+ firstName: "Fred",
660
+ lastName: "Flinstone",
661
+ }),
662
+ ClientContext: JSON.stringify({
663
+ procedureName: "updateUser",
664
+ }),
665
+ });
666
+ const { success, code, message, user } = JSON.parse(invocationResponse.Payload);
667
+ ```
668
+
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"}'
673
+ ```
674
+
591
675
  ## `AllserverClient` options
592
676
 
593
677
  **All the arguments are optional.** But either `uri` or `transport` must be provided. We are trying to keep the highest possible DX here.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allserver",
3
- "version": "2.0.1",
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
+ });
@@ -0,0 +1,29 @@
1
+ module.exports = require("./ClientTransport").compose({
2
+ name: "MemoryClientTransport",
3
+
4
+ props: {
5
+ _allserverContext: null,
6
+ uri: "memory",
7
+ },
8
+
9
+ init({ allserverContext }) {
10
+ this._allserverContext = allserverContext || this._allserverContext;
11
+ },
12
+
13
+ methods: {
14
+ async introspect(ctx) {
15
+ ctx.procedureName = "";
16
+ const result = await this.call(ctx);
17
+ if (!result) throw Error(); // The ClientTransport expects us to throw if call fails
18
+ return result;
19
+ },
20
+
21
+ async call(ctx) {
22
+ return this._allserverContext.allserver.handleCall(ctx);
23
+ },
24
+
25
+ createCallContext(defaultCtx) {
26
+ return { ...defaultCtx, ...this._allserverContext, memory: {} };
27
+ },
28
+ },
29
+ });
package/src/index.js CHANGED
@@ -23,6 +23,9 @@ module.exports = {
23
23
  get BullmqTransport() {
24
24
  return require("./server/BullmqTransport");
25
25
  },
26
+ get MemoryTransport() {
27
+ return require("./server/MemoryTransport");
28
+ },
26
29
 
27
30
  // client
28
31
 
@@ -41,4 +44,10 @@ module.exports = {
41
44
  get BullmqClientTransport() {
42
45
  return require("./client/BullmqClientTransport");
43
46
  },
47
+ get MemoryClientTransport() {
48
+ return require("./client/MemoryClientTransport");
49
+ },
50
+ get LambdaClientTransport() {
51
+ return require("./client/LambdaClientTransport");
52
+ },
44
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
  });
@@ -0,0 +1,28 @@
1
+ module.exports = require("./Transport").compose({
2
+ name: "MemoryTransport",
3
+
4
+ props: {
5
+ AllserverClient: require("../client/AllserverClient"),
6
+ MemoryClientTransport: require("../client/MemoryClientTransport"),
7
+ },
8
+
9
+ methods: {
10
+ startServer(defaultCtx) {
11
+ return this.AllserverClient({
12
+ transport: this.MemoryClientTransport({ allserverContext: { ...defaultCtx, memory: {} } }),
13
+ });
14
+ },
15
+
16
+ reply(ctx) {
17
+ return ctx.result;
18
+ },
19
+
20
+ getProcedureName(ctx) {
21
+ return ctx.procedureName;
22
+ },
23
+
24
+ isIntrospection(ctx) {
25
+ return this.getProcedureName(ctx) === "";
26
+ },
27
+ },
28
+ });