@klarna/nest-lambda-microservice 0.1.0-beta.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/CHANGELOG.md +12 -0
- package/LICENSE +202 -0
- package/README.md +364 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +17 -0
- package/dist/client/lambda-microservice.client.d.ts +21 -0
- package/dist/client/lambda-microservice.client.js +102 -0
- package/dist/ctx-host/index.d.ts +1 -0
- package/dist/ctx-host/index.js +17 -0
- package/dist/ctx-host/lambda.context.d.ts +10 -0
- package/dist/ctx-host/lambda.context.js +18 -0
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.js +17 -0
- package/dist/decorators/use-partial-pattern-match.d.ts +21 -0
- package/dist/decorators/use-partial-pattern-match.js +53 -0
- package/dist/errors/IncomingResponseError.d.ts +6 -0
- package/dist/errors/IncomingResponseError.js +25 -0
- package/dist/errors/NoConsumerError.d.ts +3 -0
- package/dist/errors/NoConsumerError.js +9 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +18 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +24 -0
- package/dist/interfaces/index.d.ts +4 -0
- package/dist/interfaces/index.js +20 -0
- package/dist/interfaces/lambda-microservice.config.d.ts +9 -0
- package/dist/interfaces/lambda-microservice.config.js +2 -0
- package/dist/interfaces/lambda-microservice.packet.d.ts +35 -0
- package/dist/interfaces/lambda-microservice.packet.js +2 -0
- package/dist/interfaces/request.builder.d.ts +4 -0
- package/dist/interfaces/request.builder.js +2 -0
- package/dist/interfaces/response.builder.d.ts +3 -0
- package/dist/interfaces/response.builder.js +2 -0
- package/dist/lambda/event-mapper.d.ts +7 -0
- package/dist/lambda/event-mapper.js +54 -0
- package/dist/lambda/index.d.ts +2 -0
- package/dist/lambda/index.js +18 -0
- package/dist/lambda/integrations/apigateway.d.ts +5 -0
- package/dist/lambda/integrations/apigateway.js +26 -0
- package/dist/lambda/integrations/dynamodb.d.ts +3 -0
- package/dist/lambda/integrations/dynamodb.js +16 -0
- package/dist/lambda/integrations/eventbridge.d.ts +10 -0
- package/dist/lambda/integrations/eventbridge.js +13 -0
- package/dist/lambda/integrations/index.d.ts +7 -0
- package/dist/lambda/integrations/index.js +23 -0
- package/dist/lambda/integrations/kinesis.d.ts +3 -0
- package/dist/lambda/integrations/kinesis.js +16 -0
- package/dist/lambda/integrations/s3.d.ts +3 -0
- package/dist/lambda/integrations/s3.js +16 -0
- package/dist/lambda/integrations/sns.d.ts +4 -0
- package/dist/lambda/integrations/sns.js +26 -0
- package/dist/lambda/integrations/sqs.d.ts +5 -0
- package/dist/lambda/integrations/sqs.js +38 -0
- package/dist/record-builders/api-gateway-request.builder.d.ts +17 -0
- package/dist/record-builders/api-gateway-request.builder.js +43 -0
- package/dist/record-builders/api-gateway-response.builder.d.ts +6 -0
- package/dist/record-builders/api-gateway-response.builder.js +23 -0
- package/dist/record-builders/custom-request.builder.d.ts +10 -0
- package/dist/record-builders/custom-request.builder.js +22 -0
- package/dist/record-builders/custom-response.builder.d.ts +6 -0
- package/dist/record-builders/custom-response.builder.js +27 -0
- package/dist/record-builders/event-bridge-request.builder.d.ts +16 -0
- package/dist/record-builders/event-bridge-request.builder.js +29 -0
- package/dist/record-builders/event-bridge-response.builder.d.ts +6 -0
- package/dist/record-builders/event-bridge-response.builder.js +23 -0
- package/dist/record-builders/index.d.ts +12 -0
- package/dist/record-builders/index.js +28 -0
- package/dist/record-builders/s3-request.builder.d.ts +15 -0
- package/dist/record-builders/s3-request.builder.js +30 -0
- package/dist/record-builders/s3-response.builder.d.ts +6 -0
- package/dist/record-builders/s3-response.builder.js +19 -0
- package/dist/record-builders/sns-request.builder.d.ts +16 -0
- package/dist/record-builders/sns-request.builder.js +51 -0
- package/dist/record-builders/sns-response.builder.d.ts +6 -0
- package/dist/record-builders/sns-response.builder.js +19 -0
- package/dist/record-builders/sqs-request.builder.d.ts +16 -0
- package/dist/record-builders/sqs-request.builder.js +57 -0
- package/dist/record-builders/sqs-response.builder.d.ts +11 -0
- package/dist/record-builders/sqs-response.builder.js +24 -0
- package/dist/server/__mocks__/index.d.ts +1 -0
- package/dist/server/__mocks__/index.js +17 -0
- package/dist/server/__mocks__/lambda-microservice.broker.d.ts +11 -0
- package/dist/server/__mocks__/lambda-microservice.broker.js +37 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +18 -0
- package/dist/server/lambda-microservice.broker.d.ts +13 -0
- package/dist/server/lambda-microservice.broker.js +44 -0
- package/dist/server/lambda-microservice.server.d.ts +26 -0
- package/dist/server/lambda-microservice.server.js +144 -0
- package/dist/server/mocks.d.ts +1 -0
- package/dist/server/mocks.js +17 -0
- package/package.json +59 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SqsRequestBuilder = void 0;
|
|
4
|
+
const lambda_1 = require("../lambda");
|
|
5
|
+
const sns_request_builder_1 = require("./sns-request.builder");
|
|
6
|
+
class SqsRequestBuilder {
|
|
7
|
+
data;
|
|
8
|
+
context;
|
|
9
|
+
static buildPattern(data) {
|
|
10
|
+
try {
|
|
11
|
+
const snsMessage = (0, lambda_1.tryUnwrapSnsMessageFromSqsRecord)(data);
|
|
12
|
+
return sns_request_builder_1.SnsRequestBuilder.buildSnsMessagePattern(snsMessage);
|
|
13
|
+
}
|
|
14
|
+
catch (_error) {
|
|
15
|
+
return SqsRequestBuilder.buildSqsMessagePattern(data);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
static buildSqsMessagePattern(data) {
|
|
19
|
+
const pattern = {};
|
|
20
|
+
const messageAttrs = data.messageAttributes;
|
|
21
|
+
return Reflect.ownKeys(messageAttrs).reduce((pattern, key) => {
|
|
22
|
+
Reflect.set(pattern, key, SqsRequestBuilder.mapSqsMessageAttributes(Reflect.get(messageAttrs, key)));
|
|
23
|
+
return pattern;
|
|
24
|
+
}, pattern);
|
|
25
|
+
}
|
|
26
|
+
static mapSqsMessageAttributes(messageAttribute) {
|
|
27
|
+
switch (messageAttribute.dataType) {
|
|
28
|
+
case 'String':
|
|
29
|
+
try {
|
|
30
|
+
return messageAttribute.stringValue ? JSON.parse(messageAttribute.stringValue) : '';
|
|
31
|
+
}
|
|
32
|
+
catch (_error) {
|
|
33
|
+
return messageAttribute.stringValue ?? '';
|
|
34
|
+
}
|
|
35
|
+
case 'Number':
|
|
36
|
+
return messageAttribute.stringValue ? JSON.parse(messageAttribute.stringValue) : 0;
|
|
37
|
+
case 'Binary':
|
|
38
|
+
default:
|
|
39
|
+
throw new Error('Attribute type not implemented');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
pattern = {};
|
|
43
|
+
constructor(data, context) {
|
|
44
|
+
this.data = data;
|
|
45
|
+
this.context = context;
|
|
46
|
+
this.pattern = SqsRequestBuilder.buildPattern(this.data);
|
|
47
|
+
}
|
|
48
|
+
build() {
|
|
49
|
+
return {
|
|
50
|
+
id: this.data.messageId,
|
|
51
|
+
pattern: this.pattern,
|
|
52
|
+
data: this.data,
|
|
53
|
+
context: this.context,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.SqsRequestBuilder = SqsRequestBuilder;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SQSBatchItemFailure } from 'aws-lambda/trigger/sqs';
|
|
2
|
+
import { ResponseBuilder, ResponseTuple } from '../interfaces';
|
|
3
|
+
export declare class SqsResponseBuilder implements ResponseBuilder {
|
|
4
|
+
protected readonly responseTuples: ResponseTuple[];
|
|
5
|
+
static isSQSBatchItemFailure: (item: unknown) => item is SQSBatchItemFailure;
|
|
6
|
+
static responseCompareFn: (a: SQSBatchItemFailure, b: SQSBatchItemFailure) => number;
|
|
7
|
+
constructor(responseTuples: ResponseTuple[]);
|
|
8
|
+
build(): {
|
|
9
|
+
batchItemFailures: SQSBatchItemFailure[];
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SqsResponseBuilder = void 0;
|
|
4
|
+
const errors_1 = require("../errors");
|
|
5
|
+
class SqsResponseBuilder {
|
|
6
|
+
responseTuples;
|
|
7
|
+
static isSQSBatchItemFailure = (item) => typeof item === 'object' && item !== null && 'itemIdentifier' in item && Boolean(item?.itemIdentifier);
|
|
8
|
+
static responseCompareFn = (a, b) => (a?.itemIdentifier ?? '').localeCompare(b?.itemIdentifier ?? '');
|
|
9
|
+
constructor(responseTuples) {
|
|
10
|
+
this.responseTuples = responseTuples;
|
|
11
|
+
}
|
|
12
|
+
build() {
|
|
13
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting
|
|
14
|
+
return {
|
|
15
|
+
batchItemFailures: this.responseTuples
|
|
16
|
+
.map(([request, response]) => ({
|
|
17
|
+
itemIdentifier: response instanceof errors_1.IncomingResponseError ? request.id : null,
|
|
18
|
+
}))
|
|
19
|
+
.filter(SqsResponseBuilder.isSQSBatchItemFailure)
|
|
20
|
+
.sort(SqsResponseBuilder.responseCompareFn),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.SqsResponseBuilder = SqsResponseBuilder;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lambda-microservice.broker';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./lambda-microservice.broker"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MockedObject } from 'jest-mock';
|
|
2
|
+
import { LambdaMicroserviceBroker } from '../lambda-microservice.broker';
|
|
3
|
+
export declare const sendRequest: import("jest-mock").Mock<any>;
|
|
4
|
+
export declare const on: import("jest-mock").Mock<any>;
|
|
5
|
+
export declare const emit: import("jest-mock").Mock<any>;
|
|
6
|
+
export declare const once: import("jest-mock").Mock<any>;
|
|
7
|
+
export declare const off: import("jest-mock").Mock<any>;
|
|
8
|
+
export declare const listen: import("jest-mock").Mock<any>;
|
|
9
|
+
export declare const close: import("jest-mock").Mock<any>;
|
|
10
|
+
export declare const mockServerResponse: (requestId: string, response: unknown) => void;
|
|
11
|
+
export declare const LambdaMicroserviceBrokerFactory: () => MockedObject<LambdaMicroserviceBroker>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LambdaMicroserviceBrokerFactory = exports.mockServerResponse = exports.close = exports.listen = exports.off = exports.once = exports.emit = exports.on = exports.sendRequest = void 0;
|
|
4
|
+
const jest_mock_1 = require("jest-mock");
|
|
5
|
+
const rxjs_1 = require("rxjs");
|
|
6
|
+
const globals_1 = require("@jest/globals");
|
|
7
|
+
exports.sendRequest = globals_1.jest.fn();
|
|
8
|
+
exports.on = globals_1.jest.fn();
|
|
9
|
+
exports.emit = globals_1.jest.fn();
|
|
10
|
+
exports.once = globals_1.jest.fn();
|
|
11
|
+
exports.off = globals_1.jest.fn();
|
|
12
|
+
exports.listen = globals_1.jest.fn().mockImplementation((cb) => cb && cb());
|
|
13
|
+
exports.close = globals_1.jest.fn().mockImplementation((cb) => cb && cb());
|
|
14
|
+
const mockServerResponse = (requestId, response) => {
|
|
15
|
+
exports.sendRequest.mockImplementationOnce(() => {
|
|
16
|
+
const subject = new rxjs_1.ReplaySubject();
|
|
17
|
+
subject.next({
|
|
18
|
+
id: requestId,
|
|
19
|
+
response: response instanceof Error ? undefined : response,
|
|
20
|
+
err: response instanceof Error ? response : undefined,
|
|
21
|
+
isDisposed: true,
|
|
22
|
+
});
|
|
23
|
+
subject.complete();
|
|
24
|
+
return subject;
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
exports.mockServerResponse = mockServerResponse;
|
|
28
|
+
const LambdaMicroserviceBrokerFactory = () => (0, jest_mock_1.mocked)({
|
|
29
|
+
listen: exports.listen,
|
|
30
|
+
close: exports.close,
|
|
31
|
+
sendRequest: exports.sendRequest,
|
|
32
|
+
on: exports.on,
|
|
33
|
+
off: exports.off,
|
|
34
|
+
once: exports.once,
|
|
35
|
+
emit: exports.emit,
|
|
36
|
+
});
|
|
37
|
+
exports.LambdaMicroserviceBrokerFactory = LambdaMicroserviceBrokerFactory;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./lambda-microservice.server"), exports);
|
|
18
|
+
__exportStar(require("./lambda-microservice.broker"), exports);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import EventEmitter from 'events';
|
|
3
|
+
import { ReplaySubject } from 'rxjs';
|
|
4
|
+
import { OutgoingRequest, OutgoingResponse } from '../interfaces';
|
|
5
|
+
export declare const RequestEvent: unique symbol;
|
|
6
|
+
export declare const ResponseEvent: unique symbol;
|
|
7
|
+
export type SupportedEvent = typeof RequestEvent | typeof ResponseEvent;
|
|
8
|
+
export declare class LambdaMicroserviceBroker extends EventEmitter {
|
|
9
|
+
listen(callbackFn?: (...args: unknown[]) => void): void;
|
|
10
|
+
close(callbackFn?: (...args: unknown[]) => void): void;
|
|
11
|
+
sendRequest(outgoingRequest: OutgoingRequest): ReplaySubject<OutgoingResponse>;
|
|
12
|
+
}
|
|
13
|
+
export declare const LambdaMicroserviceBrokerFactory: () => LambdaMicroserviceBroker;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LambdaMicroserviceBrokerFactory = exports.LambdaMicroserviceBroker = exports.ResponseEvent = exports.RequestEvent = void 0;
|
|
7
|
+
const events_1 = __importDefault(require("events"));
|
|
8
|
+
const rxjs_1 = require("rxjs");
|
|
9
|
+
const errors_1 = require("../errors");
|
|
10
|
+
exports.RequestEvent = Symbol('Request');
|
|
11
|
+
exports.ResponseEvent = Symbol('Response');
|
|
12
|
+
class LambdaMicroserviceBroker extends events_1.default {
|
|
13
|
+
listen(callbackFn) {
|
|
14
|
+
callbackFn && callbackFn();
|
|
15
|
+
}
|
|
16
|
+
close(callbackFn) {
|
|
17
|
+
this.removeAllListeners(exports.ResponseEvent);
|
|
18
|
+
this.removeAllListeners(exports.RequestEvent);
|
|
19
|
+
callbackFn && callbackFn();
|
|
20
|
+
}
|
|
21
|
+
sendRequest(outgoingRequest) {
|
|
22
|
+
const responseStream$ = new rxjs_1.ReplaySubject();
|
|
23
|
+
const responseEventListener = (outgoingResponse) => {
|
|
24
|
+
if (outgoingResponse.id === outgoingRequest.id) {
|
|
25
|
+
responseStream$.next(outgoingResponse);
|
|
26
|
+
if (outgoingResponse.isDisposed) {
|
|
27
|
+
responseStream$.complete();
|
|
28
|
+
this.off(exports.ResponseEvent, responseEventListener);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
if (this.listeners(exports.RequestEvent).length) {
|
|
33
|
+
this.on(exports.ResponseEvent, responseEventListener);
|
|
34
|
+
this.emit(exports.RequestEvent, outgoingRequest);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
responseStream$.error(new errors_1.NoConsumerError());
|
|
38
|
+
}
|
|
39
|
+
return responseStream$;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.LambdaMicroserviceBroker = LambdaMicroserviceBroker;
|
|
43
|
+
const LambdaMicroserviceBrokerFactory = () => new LambdaMicroserviceBroker();
|
|
44
|
+
exports.LambdaMicroserviceBrokerFactory = LambdaMicroserviceBrokerFactory;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Logger } from '@nestjs/common';
|
|
3
|
+
import { CustomTransportStrategy, MessageHandler, MsPattern, Server } from '@nestjs/microservices';
|
|
4
|
+
import { IncomingRequest } from '../interfaces';
|
|
5
|
+
import { LambdaMicroserviceBroker } from './lambda-microservice.broker';
|
|
6
|
+
export interface LambdaMicroserviceServerOptions {
|
|
7
|
+
broker: LambdaMicroserviceBroker;
|
|
8
|
+
}
|
|
9
|
+
export declare const CatchAllPattern = "*";
|
|
10
|
+
export declare const PartialMatchExtraProp = "partialMatch";
|
|
11
|
+
export declare const AwsLambdaTransportId: unique symbol;
|
|
12
|
+
export declare class LambdaMicroserviceServer extends Server implements CustomTransportStrategy {
|
|
13
|
+
private readonly options;
|
|
14
|
+
readonly transportId: symbol;
|
|
15
|
+
protected readonly broker: LambdaMicroserviceBroker;
|
|
16
|
+
protected readonly handlerPatternsMap: Map<any, any>;
|
|
17
|
+
protected readonly logger: Logger;
|
|
18
|
+
constructor(options: LambdaMicroserviceServerOptions);
|
|
19
|
+
listen(callback: (error?: unknown) => void): Promise<void>;
|
|
20
|
+
close(): void;
|
|
21
|
+
addHandler(pattern: any, callback: MessageHandler, isEventHandler?: boolean, extras?: Record<string, any>): void;
|
|
22
|
+
protected handleMessage: (payload: IncomingRequest) => Promise<void | import("rxjs").Subscription>;
|
|
23
|
+
protected trySerializeIncomingRequest(incomingRequest: IncomingRequest): string;
|
|
24
|
+
getHandlerByPattern(messagePattern: MsPattern): MessageHandler | null;
|
|
25
|
+
performPatternMatch(handlerPatternPart: unknown, messagePatternPart: unknown, isPartialObjectMatchAllowed?: boolean): boolean;
|
|
26
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LambdaMicroserviceServer = exports.AwsLambdaTransportId = exports.PartialMatchExtraProp = exports.CatchAllPattern = void 0;
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const microservices_1 = require("@nestjs/microservices");
|
|
7
|
+
const deserializers_1 = require("@nestjs/microservices/deserializers");
|
|
8
|
+
const invalid_message_exception_1 = require("@nestjs/microservices/errors/invalid-message.exception");
|
|
9
|
+
const serializers_1 = require("@nestjs/microservices/serializers");
|
|
10
|
+
const ctx_host_1 = require("../ctx-host");
|
|
11
|
+
const lambda_microservice_broker_1 = require("./lambda-microservice.broker");
|
|
12
|
+
exports.CatchAllPattern = '*';
|
|
13
|
+
exports.PartialMatchExtraProp = 'partialMatch';
|
|
14
|
+
exports.AwsLambdaTransportId = Symbol('AWS_LAMBDA'); // Use it when registering the client
|
|
15
|
+
class LambdaMicroserviceServer extends microservices_1.Server {
|
|
16
|
+
options;
|
|
17
|
+
transportId = exports.AwsLambdaTransportId;
|
|
18
|
+
broker;
|
|
19
|
+
handlerPatternsMap = new Map();
|
|
20
|
+
logger = new common_1.Logger(LambdaMicroserviceServer.name);
|
|
21
|
+
constructor(options) {
|
|
22
|
+
super();
|
|
23
|
+
this.options = options;
|
|
24
|
+
this.broker = this.options.broker;
|
|
25
|
+
this.serializer = new serializers_1.IdentitySerializer();
|
|
26
|
+
this.deserializer = new deserializers_1.IdentityDeserializer();
|
|
27
|
+
}
|
|
28
|
+
async listen(callback) {
|
|
29
|
+
try {
|
|
30
|
+
this.broker.listen(() => {
|
|
31
|
+
this.broker.on(lambda_microservice_broker_1.RequestEvent, this.handleMessage);
|
|
32
|
+
callback();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
callback(error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
close() {
|
|
40
|
+
this.broker.off(lambda_microservice_broker_1.RequestEvent, this.handleMessage);
|
|
41
|
+
this.broker.close();
|
|
42
|
+
}
|
|
43
|
+
addHandler(pattern, callback, isEventHandler = false, extras = {}) {
|
|
44
|
+
const normalizedPattern = this.normalizePattern(pattern);
|
|
45
|
+
// Guard against multiple handlers using same pattern
|
|
46
|
+
if (this.handlerPatternsMap.has(normalizedPattern)) {
|
|
47
|
+
throw new invalid_message_exception_1.InvalidMessageException();
|
|
48
|
+
}
|
|
49
|
+
this.handlerPatternsMap.set(normalizedPattern, pattern);
|
|
50
|
+
return super.addHandler(pattern, callback, isEventHandler, extras);
|
|
51
|
+
}
|
|
52
|
+
handleMessage = async (payload) => {
|
|
53
|
+
const { id, context } = payload;
|
|
54
|
+
const packet = await this.deserializer.deserialize(payload);
|
|
55
|
+
const lambdaContext = new ctx_host_1.LambdaContext([context]);
|
|
56
|
+
const publish = (data) => {
|
|
57
|
+
this.broker.emit(lambda_microservice_broker_1.ResponseEvent, this.serializer.serialize({ ...data, id }));
|
|
58
|
+
};
|
|
59
|
+
const handler = this.getHandlerByPattern(packet.pattern);
|
|
60
|
+
if (handler?.isEventHandler) {
|
|
61
|
+
this.logger.error('Event handlers are not supported by the lambda microservice. Switch to using message handlers to fix this error.', this.trySerializeIncomingRequest(payload));
|
|
62
|
+
return publish({
|
|
63
|
+
err: new Error('An illegal event handler qualified for processing the current message'),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (!handler) {
|
|
67
|
+
this.logger.error('No handler qualified to process the message', this.trySerializeIncomingRequest(payload));
|
|
68
|
+
return publish({
|
|
69
|
+
err: new Error('No handler qualified to process the message'),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const response$ = this.transformToObservable(await handler(packet.data, lambdaContext));
|
|
73
|
+
return this.send(response$, publish);
|
|
74
|
+
};
|
|
75
|
+
trySerializeIncomingRequest(incomingRequest) {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.stringify(incomingRequest);
|
|
78
|
+
}
|
|
79
|
+
catch (_error) {
|
|
80
|
+
return incomingRequest;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
getHandlerByPattern(messagePattern) {
|
|
84
|
+
const route = this.getRouteFromPattern(messagePattern); // The getRouteFromPattern typings is wrongly accepting just strings
|
|
85
|
+
try {
|
|
86
|
+
// Exact match of the normalized pattern
|
|
87
|
+
if (this.messageHandlers.has(route)) {
|
|
88
|
+
return this.messageHandlers.get(route) ?? null;
|
|
89
|
+
}
|
|
90
|
+
// Denormalized match (e.g. if the original pattern is an object)
|
|
91
|
+
for (const [handlerPattern, handler] of this.messageHandlers.entries()) {
|
|
92
|
+
const originalHandlerPattern = this.handlerPatternsMap.get(handlerPattern) ?? route;
|
|
93
|
+
const isPartialMatch = Reflect.get(handler.extras ?? {}, exports.PartialMatchExtraProp) ?? false;
|
|
94
|
+
if (this.performPatternMatch(originalHandlerPattern, messagePattern, isPartialMatch)) {
|
|
95
|
+
return handler;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Catch all pattern
|
|
99
|
+
if (this.messageHandlers.has(exports.CatchAllPattern)) {
|
|
100
|
+
return this.messageHandlers.get(exports.CatchAllPattern) ?? null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (_error) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
performPatternMatch(handlerPatternPart, messagePatternPart, isPartialObjectMatchAllowed = false) {
|
|
109
|
+
const isObject = (value) => typeof value === 'object' && Boolean(value);
|
|
110
|
+
const isPrimitive = (value) => ['string', 'number', 'boolean'].includes(typeof value);
|
|
111
|
+
// TODO If the handler provided @MessagePattern is a serialized MsObjectPattern, and its elements order does not match the incoming message pattern, it won't get qualified
|
|
112
|
+
// Potential solution detect, denormalize and match
|
|
113
|
+
if (isPrimitive(messagePatternPart)) {
|
|
114
|
+
return isPrimitive(handlerPatternPart) ? handlerPatternPart === messagePatternPart : false;
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(messagePatternPart) && Array.isArray(handlerPatternPart)) {
|
|
117
|
+
const sortedMessagePattern = [...messagePatternPart].sort();
|
|
118
|
+
const sortedHandlerPattern = [...handlerPatternPart].sort();
|
|
119
|
+
if (isPartialObjectMatchAllowed) {
|
|
120
|
+
return sortedHandlerPattern.every((messageVal) => {
|
|
121
|
+
if (isPrimitive(messageVal)) {
|
|
122
|
+
return sortedMessagePattern.includes(messageVal);
|
|
123
|
+
}
|
|
124
|
+
return sortedHandlerPattern.some((handlerVal) => this.performPatternMatch(handlerVal, messageVal, isPartialObjectMatchAllowed));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return sortedMessagePattern.every((val, index) => {
|
|
128
|
+
return this.performPatternMatch(sortedHandlerPattern[index], val, isPartialObjectMatchAllowed);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (isObject(messagePatternPart) && isObject(handlerPatternPart)) {
|
|
132
|
+
const handlerPatternKeys = Object.keys(handlerPatternPart);
|
|
133
|
+
const messagePatternKeys = Object.keys(messagePatternPart);
|
|
134
|
+
const keys = isPartialObjectMatchAllowed ? handlerPatternKeys : messagePatternKeys;
|
|
135
|
+
const hasSameShape = isPartialObjectMatchAllowed || handlerPatternKeys.length === messagePatternKeys.length;
|
|
136
|
+
return (hasSameShape &&
|
|
137
|
+
keys.every((key) => {
|
|
138
|
+
return this.performPatternMatch(handlerPatternPart[key], messagePatternPart[key], isPartialObjectMatchAllowed);
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.LambdaMicroserviceServer = LambdaMicroserviceServer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './__mocks__';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./__mocks__"), exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@klarna/nest-lambda-microservice",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "NestJS based microservice for writing applications that run on AWS Lambda",
|
|
5
|
+
"private": false,
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git@github.com:klarna-incubator/nest-lambda-microservice.git"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/**"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "yarn build:clean && yarn build:ts",
|
|
16
|
+
"build:clean": "rm -rf dist",
|
|
17
|
+
"build:ts": "tsc -p tsconfig.build.json",
|
|
18
|
+
"build:watch": "tsc -p tsconfig.build.json --watch",
|
|
19
|
+
"lint": "eslint . --ext .ts --fix && prettier . --write --list-different --ignore-unknown && tsc -p tsconfig.json --noEmit",
|
|
20
|
+
"test": "yarn test:unit && yarn test:integration",
|
|
21
|
+
"test:unit": "jest --selectProjects unit --passWithNoTests --runInBand --detectOpenHandles --coverage",
|
|
22
|
+
"test:integration": "jest --selectProjects integration --passWithNoTests --runInBand --detectOpenHandles --coverage",
|
|
23
|
+
"prettier": "prettier 'src/**/*.ts'",
|
|
24
|
+
"prettier:fix": "prettier --write 'src/**/*.ts'"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@nestjs/common": "10.3.0",
|
|
28
|
+
"@nestjs/core": "10.3.0",
|
|
29
|
+
"@nestjs/microservices": "10.3.0",
|
|
30
|
+
"reflect-metadata": "0.1.14",
|
|
31
|
+
"rxjs": "7.8.1",
|
|
32
|
+
"uuid": "9.0.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@jest/globals": "29.7.0",
|
|
36
|
+
"@nestjs/testing": "10.3.0",
|
|
37
|
+
"@tsconfig/node18": "18.2.2",
|
|
38
|
+
"@types/aws-lambda": "8.10.130",
|
|
39
|
+
"@types/node": "18.19.3",
|
|
40
|
+
"@types/uuid": "9.0.7",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "6.15.0",
|
|
42
|
+
"@typescript-eslint/parser": "6.15.0",
|
|
43
|
+
"class-transformer": "0.5.1",
|
|
44
|
+
"class-validator": "0.14.0",
|
|
45
|
+
"eslint": "8.56.0",
|
|
46
|
+
"eslint-config-prettier": "9.1.0",
|
|
47
|
+
"eslint-plugin-import": "2.29.1",
|
|
48
|
+
"eslint-plugin-jest": "27.6.0",
|
|
49
|
+
"eslint-plugin-prettier": "5.1.1",
|
|
50
|
+
"jest": "29.7.0",
|
|
51
|
+
"prettier": "3.1.1",
|
|
52
|
+
"ts-jest": "29.1.1",
|
|
53
|
+
"typescript": "5.3.3"
|
|
54
|
+
},
|
|
55
|
+
"volta": {
|
|
56
|
+
"node": "18.19.0"
|
|
57
|
+
},
|
|
58
|
+
"license": "Apache-2.0"
|
|
59
|
+
}
|