@aesop-fables/triginta 0.6.0 → 0.7.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/lib/index.d.ts CHANGED
@@ -4,15 +4,17 @@ export * from './Decorators';
4
4
  export * from './http/HttpLambda';
5
5
  export * from './http/HttpLambdaServices';
6
6
  export { default as RouteRegistry, IRouteRegistry } from './RouteRegistry';
7
- export * from './SqsLambda';
8
- export * from './SqsLambdaServices';
9
- export * from './ISqsMessageHandler';
7
+ export * from './sqs/SqsLambda';
8
+ export * from './sqs/SqsLambdaServices';
9
+ export * from './sqs/ISqsMessageHandler';
10
+ export * from './sqs/RecordMatchers';
11
+ export * from './sqs/ISqsMessage';
10
12
  export * from './http/IConfiguredRoute';
11
13
  export * from './TrigintaConfig';
12
14
  export * as Localization from './localization';
13
15
  export * as Validation from './validation';
14
16
  import * as httpUtils from './http/invokeHttpHandler';
15
- import * as sqsUtils from './invokeSqsHandler';
17
+ import * as sqsUtils from './sqs/invokeSqsHandler';
16
18
  /**
17
19
  * Provides helper functions for invoking lambdas in unit/integration tests.
18
20
  */
package/lib/index.js CHANGED
@@ -37,15 +37,17 @@ __exportStar(require("./http/HttpLambda"), exports);
37
37
  __exportStar(require("./http/HttpLambdaServices"), exports);
38
38
  var RouteRegistry_1 = require("./RouteRegistry");
39
39
  Object.defineProperty(exports, "RouteRegistry", { enumerable: true, get: function () { return __importDefault(RouteRegistry_1).default; } });
40
- __exportStar(require("./SqsLambda"), exports);
41
- __exportStar(require("./SqsLambdaServices"), exports);
42
- __exportStar(require("./ISqsMessageHandler"), exports);
40
+ __exportStar(require("./sqs/SqsLambda"), exports);
41
+ __exportStar(require("./sqs/SqsLambdaServices"), exports);
42
+ __exportStar(require("./sqs/ISqsMessageHandler"), exports);
43
+ __exportStar(require("./sqs/RecordMatchers"), exports);
44
+ __exportStar(require("./sqs/ISqsMessage"), exports);
43
45
  __exportStar(require("./http/IConfiguredRoute"), exports);
44
46
  __exportStar(require("./TrigintaConfig"), exports);
45
47
  exports.Localization = __importStar(require("./localization"));
46
48
  exports.Validation = __importStar(require("./validation"));
47
49
  const httpUtils = __importStar(require("./http/invokeHttpHandler"));
48
- const sqsUtils = __importStar(require("./invokeSqsHandler"));
50
+ const sqsUtils = __importStar(require("./sqs/invokeSqsHandler"));
49
51
  /**
50
52
  * Provides helper functions for invoking lambdas in unit/integration tests.
51
53
  */
@@ -0,0 +1,16 @@
1
+ import { SQSMessageAttributes } from 'aws-lambda';
2
+ export interface ISqsMessage {
3
+ type: string;
4
+ getAttributes(): SQSMessageAttributes;
5
+ getBody(): string;
6
+ }
7
+ export declare const TrigintaMessageHeaders: {
8
+ MessageType: string;
9
+ };
10
+ export declare abstract class BaseSqsMessage implements ISqsMessage {
11
+ readonly type: string;
12
+ constructor(type: string);
13
+ getAttributes(): SQSMessageAttributes;
14
+ getData(): any;
15
+ getBody(): string;
16
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseSqsMessage = exports.TrigintaMessageHeaders = void 0;
4
+ exports.TrigintaMessageHeaders = {
5
+ MessageType: 'X-Message-Type',
6
+ };
7
+ class BaseSqsMessage {
8
+ constructor(type) {
9
+ this.type = type;
10
+ }
11
+ getAttributes() {
12
+ const attributes = {
13
+ [exports.TrigintaMessageHeaders.MessageType]: {
14
+ dataType: 'String',
15
+ stringValue: this.type,
16
+ },
17
+ };
18
+ return attributes;
19
+ }
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ getData() {
22
+ return undefined;
23
+ }
24
+ getBody() {
25
+ var _a;
26
+ const data = (_a = this.getData()) !== null && _a !== void 0 ? _a : {};
27
+ return JSON.stringify(data);
28
+ }
29
+ }
30
+ exports.BaseSqsMessage = BaseSqsMessage;
@@ -1,5 +1,6 @@
1
1
  import { SQSBatchResponse, SQSEvent, SQSRecord } from 'aws-lambda';
2
+ import { ISqsMessage } from './ISqsMessage';
2
3
  export declare type SqsOutput = SQSBatchResponse | void;
3
- export interface ISqsMessageHandler<Message, Output extends SqsOutput = void> {
4
+ export interface ISqsMessageHandler<Message extends ISqsMessage, Output extends SqsOutput = void> {
4
5
  handle(message: Message, record: SQSRecord, event: SQSEvent): Promise<Output>;
5
6
  }
@@ -0,0 +1,31 @@
1
+ import { SQSMessageAttributes, SQSRecord } from 'aws-lambda';
2
+ import { ISqsMessage } from './ISqsMessage';
3
+ export interface ISqsRecordMatcher {
4
+ matches(record: SQSRecord): boolean;
5
+ deserializeMessage<Message extends ISqsMessage>(record: SQSRecord): Promise<Message>;
6
+ }
7
+ export declare function retrieveMessageType(messageAttributes: SQSMessageAttributes): string;
8
+ export declare class DefaultSqsRecordMatcher implements ISqsRecordMatcher {
9
+ matches(): boolean;
10
+ deserializeMessage<Message extends ISqsMessage>(record: SQSRecord): Promise<Message>;
11
+ }
12
+ export interface ISqsMessageDeserializer {
13
+ deserializeMessage<Message extends ISqsMessage>(record: SQSRecord): Promise<Message>;
14
+ }
15
+ export declare class SqsMessageDeserializer implements ISqsMessageDeserializer {
16
+ private readonly matchers;
17
+ private readonly defaultMatcher;
18
+ constructor(matchers: ISqsRecordMatcher[], defaultMatcher: ISqsRecordMatcher);
19
+ deserializeMessage<Message extends ISqsMessage>(record: SQSRecord): Promise<Message>;
20
+ }
21
+ export declare type MessageConfigAttributes<Message> = {
22
+ [Property in keyof Message]: string;
23
+ };
24
+ export declare type MessageFactory<Message, Body> = (attributes: Partial<MessageConfigAttributes<Message>>, body: Body) => Promise<Message>;
25
+ export declare type MessageConfig<Message, Options> = {
26
+ type: string;
27
+ attributes: Partial<MessageConfigAttributes<Message>>;
28
+ constructUsing: MessageFactory<Message, Options>;
29
+ };
30
+ export declare type MessageExpression<Message extends ISqsMessage> = Omit<Message, 'getAttributes' | 'getBody' | 'getData'>;
31
+ export declare function createMatcher<Message extends ISqsMessage, Options>(configuration: MessageConfig<MessageExpression<Message>, Options>): ISqsRecordMatcher;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
16
+ return new (P || (P = Promise))(function (resolve, reject) {
17
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
20
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
21
+ });
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.createMatcher = exports.SqsMessageDeserializer = exports.DefaultSqsRecordMatcher = exports.retrieveMessageType = void 0;
25
+ const ISqsMessage_1 = require("./ISqsMessage");
26
+ const SqsLambdaServices_1 = require("./SqsLambdaServices");
27
+ const containr_1 = require("@aesop-fables/containr");
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ const jsonSafeParse = (text) => {
30
+ if (typeof text !== 'string')
31
+ return text;
32
+ const firstChar = text[0];
33
+ if (firstChar !== '{' && firstChar !== '[' && firstChar !== '"')
34
+ return text;
35
+ try {
36
+ return JSON.parse(text);
37
+ }
38
+ catch (e) { }
39
+ return text;
40
+ };
41
+ function retrieveMessageType(messageAttributes) {
42
+ var _a, _b;
43
+ return (_b = (_a = messageAttributes[ISqsMessage_1.TrigintaMessageHeaders.MessageType]) === null || _a === void 0 ? void 0 : _a.stringValue) !== null && _b !== void 0 ? _b : '';
44
+ }
45
+ exports.retrieveMessageType = retrieveMessageType;
46
+ class DefaultSqsRecordMatcher {
47
+ matches() {
48
+ return true;
49
+ }
50
+ deserializeMessage(record) {
51
+ return __awaiter(this, void 0, void 0, function* () {
52
+ const body = jsonSafeParse(record.body);
53
+ if (typeof body === 'string' && body === record.body) {
54
+ throw new Error(`Invalid JSON body for record ${record.messageId}: ${body}`);
55
+ }
56
+ const { messageAttributes = {} } = record;
57
+ return Object.assign(Object.assign({}, body), { type: retrieveMessageType(messageAttributes) });
58
+ });
59
+ }
60
+ }
61
+ exports.DefaultSqsRecordMatcher = DefaultSqsRecordMatcher;
62
+ let SqsMessageDeserializer = class SqsMessageDeserializer {
63
+ constructor(matchers, defaultMatcher) {
64
+ this.matchers = matchers;
65
+ this.defaultMatcher = defaultMatcher;
66
+ }
67
+ deserializeMessage(record) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ for (let i = 0; i < this.matchers.length; i++) {
70
+ const matcher = this.matchers[i];
71
+ if (matcher.matches(record)) {
72
+ return matcher.deserializeMessage(record);
73
+ }
74
+ }
75
+ return this.defaultMatcher.deserializeMessage(record);
76
+ });
77
+ }
78
+ };
79
+ SqsMessageDeserializer = __decorate([
80
+ __param(0, (0, containr_1.inject)(SqsLambdaServices_1.SqsLambdaServices.RecordMatchers)),
81
+ __param(1, (0, containr_1.inject)(SqsLambdaServices_1.SqsLambdaServices.DefaultRecordMatcher)),
82
+ __metadata("design:paramtypes", [Array, Object])
83
+ ], SqsMessageDeserializer);
84
+ exports.SqsMessageDeserializer = SqsMessageDeserializer;
85
+ // Test coverage in SqsLambda.test.ts
86
+ function createMatcher(
87
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
88
+ configuration) {
89
+ const { attributes: attributeMap, constructUsing, type } = configuration;
90
+ const defaultMatcher = new DefaultSqsRecordMatcher();
91
+ return {
92
+ matches(record) {
93
+ const { messageAttributes = {} } = record;
94
+ const messageType = retrieveMessageType(messageAttributes);
95
+ return messageType === type;
96
+ },
97
+ deserializeMessage(record) {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ const attributes = {};
100
+ Object.entries(attributeMap).forEach(([key, val]) => {
101
+ var _a, _b;
102
+ const { messageAttributes = {} } = record;
103
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
+ attributes[key] = (_b = (_a = messageAttributes[val]) === null || _a === void 0 ? void 0 : _a.stringValue) !== null && _b !== void 0 ? _b : '';
105
+ });
106
+ const body = yield defaultMatcher.deserializeMessage(record);
107
+ const result = (yield constructUsing(attributes, body));
108
+ return result;
109
+ });
110
+ },
111
+ };
112
+ }
113
+ exports.createMatcher = createMatcher;
@@ -0,0 +1,29 @@
1
+ import { IServiceContainer, IServiceModule, Newable } from '@aesop-fables/containr';
2
+ import { SQSHandler } from 'aws-lambda';
3
+ import { ISqsMessageHandler, SqsOutput } from './ISqsMessageHandler';
4
+ import { ISqsMessage } from './ISqsMessage';
5
+ import { ISqsRecordMatcher } from './RecordMatchers';
6
+ export interface BootstrappedSqsLambdaContext {
7
+ container: IServiceContainer;
8
+ createHandler<Message extends ISqsMessage, Output extends SqsOutput = void>(newable: Newable<ISqsMessageHandler<Message, Output>>): SQSHandler;
9
+ }
10
+ export interface ISqsLambdaFactory {
11
+ createHandler<Message extends ISqsMessage, Output extends SqsOutput = void>(newable: Newable<ISqsMessageHandler<Message, Output>>): SQSHandler;
12
+ }
13
+ export declare class SqsLambdaFactory implements ISqsLambdaFactory {
14
+ private readonly container;
15
+ constructor(container: IServiceContainer);
16
+ createHandler<Message extends ISqsMessage, Output extends SqsOutput = void>(newable: Newable<ISqsMessageHandler<Message, Output>>): SQSHandler;
17
+ }
18
+ export interface TrigintaSqsOptions {
19
+ matchers?: ISqsRecordMatcher[];
20
+ }
21
+ export declare const useTrigintaSqs: import("@aesop-fables/containr").ServiceModuleMiddlewareWithOptionsFactory<TrigintaSqsOptions>;
22
+ export interface SqsLambdaBootstrapExpression {
23
+ matchers?: ISqsRecordMatcher[];
24
+ modules?: IServiceModule[];
25
+ }
26
+ export declare class SqsLambda {
27
+ static initialize(expression: SqsLambdaBootstrapExpression): BootstrappedSqsLambdaContext;
28
+ static getContainer(): IServiceContainer;
29
+ }
@@ -18,20 +18,9 @@ exports.SqsLambda = exports.useTrigintaSqs = exports.SqsLambdaFactory = void 0;
18
18
  /* eslint-disable @typescript-eslint/no-unused-vars */
19
19
  const containr_1 = require("@aesop-fables/containr");
20
20
  const core_1 = __importDefault(require("@middy/core"));
21
- const Decorators_1 = require("./Decorators");
21
+ const Decorators_1 = require("../Decorators");
22
22
  const SqsLambdaServices_1 = require("./SqsLambdaServices");
23
- const jsonSafeParse = (text) => {
24
- if (typeof text !== 'string')
25
- return text;
26
- const firstChar = text[0];
27
- if (firstChar !== '{' && firstChar !== '[' && firstChar !== '"')
28
- return text;
29
- try {
30
- return JSON.parse(text);
31
- }
32
- catch (e) { }
33
- return text;
34
- };
23
+ const RecordMatchers_1 = require("./RecordMatchers");
35
24
  class SqsLambdaFactory {
36
25
  constructor(container) {
37
26
  this.container = container;
@@ -40,10 +29,11 @@ class SqsLambdaFactory {
40
29
  const handler = (event) => __awaiter(this, void 0, void 0, function* () {
41
30
  const childContainer = this.container.createChildContainer('sqsLambda');
42
31
  try {
43
- const handler = this.container.resolve(newable);
32
+ const handler = childContainer.resolve(newable);
33
+ const deserializer = childContainer.get(SqsLambdaServices_1.SqsLambdaServices.MessageDeserializer);
44
34
  for (let i = 0; i < event.Records.length; i++) {
45
35
  const record = event.Records[i];
46
- const message = jsonSafeParse(record.body);
36
+ const message = yield deserializer.deserializeMessage(record);
47
37
  yield handler.handle(message, record, event);
48
38
  }
49
39
  }
@@ -70,15 +60,36 @@ class SqsLambdaFactory {
70
60
  }
71
61
  }
72
62
  exports.SqsLambdaFactory = SqsLambdaFactory;
73
- exports.useTrigintaSqs = (0, containr_1.createServiceModule)('triginta/sqs', (services) => {
63
+ // Here until we fix containr's array stuff
64
+ // I think we might want to just use a different inject call for arrays
65
+ class NoOpMatcher {
66
+ matches() {
67
+ return false;
68
+ }
69
+ deserializeMessage() {
70
+ throw new Error('Method not implemented.');
71
+ }
72
+ }
73
+ exports.useTrigintaSqs = (0, containr_1.createServiceModuleWithOptions)('triginta/sqs', (services, options) => {
74
74
  services.register(SqsLambdaServices_1.SqsLambdaServices.SqsLambdaFactory, (container) => new SqsLambdaFactory(container));
75
+ const { matchers = [] } = options;
76
+ if (matchers.length === 0) {
77
+ matchers.push(new NoOpMatcher());
78
+ }
79
+ matchers.forEach((matcher) => {
80
+ services.addDependency(SqsLambdaServices_1.SqsLambdaServices.RecordMatchers, matcher);
81
+ });
82
+ services.use(SqsLambdaServices_1.SqsLambdaServices.DefaultRecordMatcher, RecordMatchers_1.DefaultSqsRecordMatcher);
83
+ services.use(SqsLambdaServices_1.SqsLambdaServices.MessageDeserializer, RecordMatchers_1.SqsMessageDeserializer);
75
84
  });
76
85
  let _currentContainer;
77
86
  class SqsLambda {
78
- static initialize(modules = []) {
79
- const container = (0, containr_1.createContainer)([exports.useTrigintaSqs, ...modules]);
87
+ static initialize(expression) {
88
+ const { matchers, modules = [] } = expression;
89
+ const container = (0, containr_1.createContainer)([(0, exports.useTrigintaSqs)({ matchers }), ...modules]);
80
90
  _currentContainer = container;
81
91
  return {
92
+ container,
82
93
  createHandler(newable) {
83
94
  const factory = container.get(SqsLambdaServices_1.SqsLambdaServices.SqsLambdaFactory);
84
95
  return factory.createHandler(newable);
@@ -0,0 +1,6 @@
1
+ export declare const SqsLambdaServices: {
2
+ SqsLambdaFactory: string;
3
+ DefaultRecordMatcher: string;
4
+ MessageDeserializer: string;
5
+ RecordMatchers: string;
6
+ };
@@ -3,4 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SqsLambdaServices = void 0;
4
4
  exports.SqsLambdaServices = {
5
5
  SqsLambdaFactory: 'SqsLambdaFactory',
6
+ DefaultRecordMatcher: 'DefaultRecordMatcher',
7
+ MessageDeserializer: 'MessageDeserializer',
8
+ RecordMatchers: 'RecordMatchers',
6
9
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aesop-fables/triginta",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "A lightweight framework that wraps the basic infrastructure usages of AWS Lambda (SQS, Kinesis, etc.).",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -33,9 +33,10 @@
33
33
  "@types/jest": "^29.5.0",
34
34
  "@types/node": "^18.11.11",
35
35
  "@typescript-eslint/eslint-plugin": "^5.45.1",
36
- "@typescript-eslint/parser": "5.59.1",
36
+ "@typescript-eslint/parser": "5.59.2",
37
+ "aws-sdk": "^2.1363.0",
37
38
  "esbuild": "^0.17.8",
38
- "eslint": "8.38.0",
39
+ "eslint": "8.39.0",
39
40
  "eslint-config-prettier": "^8.5.0",
40
41
  "eslint-plugin-jest": "27.2.1",
41
42
  "eslint-plugin-prettier": "^4.2.1",
@@ -43,7 +44,7 @@
43
44
  "jest-mock-extended": "^3.0.1",
44
45
  "prettier": "^2.8.1",
45
46
  "reflect-metadata": "^0.1.13",
46
- "ts-jest": "29.0.5",
47
+ "ts-jest": "29.1.0",
47
48
  "typescript": "4.9.5"
48
49
  },
49
50
  "files": [
@@ -54,6 +55,7 @@
54
55
  "@middy/core": "4.x",
55
56
  "@middy/http-error-handler": "4.x",
56
57
  "@middy/http-json-body-parser": "4.x",
58
+ "aws-sdk": "2.x",
57
59
  "reflect-metadata": "0.1.x"
58
60
  },
59
61
  "repository": {
@@ -1,19 +0,0 @@
1
- import { IServiceContainer, IServiceModule, Newable } from '@aesop-fables/containr';
2
- import { SQSHandler } from 'aws-lambda';
3
- import { ISqsMessageHandler, SqsOutput } from './ISqsMessageHandler';
4
- export interface BootstrappedSqsLambdaContext {
5
- createHandler<Message, Output extends SqsOutput = void>(newable: Newable<ISqsMessageHandler<Message, Output>>): SQSHandler;
6
- }
7
- export interface ISqsLambdaFactory {
8
- createHandler<Message, Output extends SqsOutput = void>(newable: Newable<ISqsMessageHandler<Message, Output>>): SQSHandler;
9
- }
10
- export declare class SqsLambdaFactory implements ISqsLambdaFactory {
11
- private readonly container;
12
- constructor(container: IServiceContainer);
13
- createHandler<Message, Output extends SqsOutput = void>(newable: Newable<ISqsMessageHandler<Message, Output>>): SQSHandler;
14
- }
15
- export declare const useTrigintaSqs: IServiceModule;
16
- export declare class SqsLambda {
17
- static initialize(modules?: IServiceModule[]): BootstrappedSqsLambdaContext;
18
- static getContainer(): IServiceContainer;
19
- }
@@ -1,3 +0,0 @@
1
- export declare const SqsLambdaServices: {
2
- SqsLambdaFactory: string;
3
- };