├── .npmrc ├── .gitignore ├── src ├── index.ts ├── Bus.test.ts ├── Event.ts ├── Bus.ts └── Event.test.ts ├── .prettierrc ├── jest.config.js ├── .npmignore ├── tsconfig.json ├── .github └── workflows │ ├── pull-request.yml │ └── publish.yml ├── .eslintrc ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | dist 3 | node_modules 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { Bus } from './Bus'; 2 | export { Event, PublishedEvent } from './Event'; 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "bracketSpacing": true 5 | } 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | roots: ["/src/"] 5 | }; 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode 3 | .eslintrc 4 | .prettierrc 5 | node_modules 6 | src 7 | .gitignore 8 | jest.config.js 9 | tsconfig.json 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "esModuleInterop": true, 5 | "lib": [ 6 | "es2015", "DOM" 7 | ], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noFallthroughCasesInSwitch": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "outDir": "dist", 14 | "preserveConstEnums": true, 15 | "sourceMap": true, 16 | "strict": true, 17 | "target": "es5" 18 | }, 19 | "include": ["src"], 20 | "exclude": ["./**/*.test.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: CI for Pull requests 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v1 16 | - name: Install Node.js and npm 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 14.x 20 | registry-url: https://registry.npmjs.org 21 | - name: Install dependencies 22 | run: npm install 23 | - name: Test 24 | run: npm test 25 | - name: Lint 26 | run: npm run lint 27 | - name: Type 28 | run: npm run type 29 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | on: 3 | push: 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v1 13 | - name: Install Node.js and npm 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: 14.x 17 | registry-url: https://registry.npmjs.org 18 | - name: Install dependencies 19 | run: npm install 20 | - name: Build 21 | run: npm run build 22 | - name: Publish 23 | uses: JS-DevTools/npm-publish@v1 24 | with: 25 | token: ${{ secrets.NPM_TOKEN }} 26 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:prettier/recommended" 6 | ], 7 | "env": { 8 | "es6": true, 9 | "node": true 10 | }, 11 | "rules": { 12 | "prettier/prettier": "error", 13 | "import/extensions": 0, 14 | "import/no-unresolved": 0, 15 | "import/prefer-default-export": 0, 16 | "@typescript-eslint/explicit-function-return-type": 0, 17 | "@typescript-eslint/explicit-member-accessibility": 0, 18 | "@typescript-eslint/camelcase": 0, 19 | "@typescript-eslint/interface-name-prefix": 0, 20 | "complexity": ["error", 10], 21 | "max-lines": ["error", 200], 22 | "max-depth": ["error", 3], 23 | "max-params": ["error", 4], 24 | "eqeqeq": ["error", "always"], 25 | "arrow-body-style": ["error", "as-needed"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typebridge", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/fredericbarthelet/typebridge" 6 | }, 7 | "version": "1.2.0", 8 | "description": "Typescript toolbox for AWS EventBridge", 9 | "main": "dist/index.js", 10 | "author": "Frédéric Barthelet", 11 | "license": "MIT", 12 | "sideEffects": false, 13 | "scripts": { 14 | "build": "rm -rf dist && tsc -d", 15 | "lint": "eslint '**/*.ts'", 16 | "lint:fix": "eslint '**/*.ts' --fix", 17 | "watch": "tsc -w", 18 | "type": "tsc --noEmit", 19 | "test": "jest --runInBand" 20 | }, 21 | "dependencies": { 22 | "ajv": "^6.12.6", 23 | "json-schema-to-ts": "^2.5.5" 24 | }, 25 | "devDependencies": { 26 | "@aws-sdk/client-eventbridge": "^3.50.0", 27 | "@middy/core": "^1.4.0", 28 | "@middy/validator": "^1.5.0", 29 | "@types/aws-lambda": "^8.10.64", 30 | "@types/http-errors": "^1.8.2", 31 | "@types/jest": "^27.4.0", 32 | "@types/node": "^14.14.7", 33 | "@typescript-eslint/eslint-plugin": "^5.11.0", 34 | "@typescript-eslint/parser": "^5.11.0", 35 | "aws-lambda-mock-context": "^3.2.1", 36 | "aws-sdk-client-mock": "^0.5.6", 37 | "eslint": "^8.8.0", 38 | "eslint-config-prettier": "^8.3.0", 39 | "eslint-plugin-jest": "^26.1.0", 40 | "eslint-plugin-prettier": "^4.0.0", 41 | "http-errors": "^1.8.0", 42 | "jest": "^27.5.1", 43 | "prettier": "^2.2.0", 44 | "ts-jest": "^27.1.3", 45 | "typescript": "^4.0.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Bus.test.ts: -------------------------------------------------------------------------------- 1 | import { chunkEntries, computeEventSize } from './Bus'; 2 | 3 | const smallEntry = { 4 | Detail: 'small'.repeat(Math.round(25000 / 5)), // ~ 25Kb 5 | }; 6 | const bigEntry = { 7 | Detail: 'big'.repeat(Math.round(100000 / 3)), // ~ 100Kb 8 | }; 9 | 10 | const cases = [ 11 | { 12 | name: 'no entry', 13 | entries: [], 14 | output: [], 15 | }, 16 | { 17 | name: 'one small enough entry', 18 | entries: [smallEntry], 19 | output: [[smallEntry]], 20 | }, 21 | { 22 | name: '3 big entries that exceeds size limit', 23 | entries: new Array(3).fill(bigEntry), 24 | output: [[bigEntry, bigEntry], [bigEntry]], 25 | }, 26 | { 27 | name: '10 small enough entries', 28 | entries: new Array(10).fill(smallEntry), 29 | output: [new Array(10).fill(smallEntry)], 30 | }, 31 | { 32 | name: '5 big entries that exceeds size limit twice', 33 | entries: new Array(5).fill(bigEntry), 34 | output: [[bigEntry, bigEntry], [bigEntry, bigEntry], [bigEntry]], 35 | }, 36 | { 37 | name: '11 small entries that exceeds length limit', 38 | entries: new Array(11).fill(smallEntry), 39 | output: [new Array(10).fill(smallEntry), [smallEntry]], 40 | }, 41 | { 42 | name: 'small and big entries together', 43 | entries: [ 44 | smallEntry, 45 | smallEntry, 46 | bigEntry, 47 | bigEntry, 48 | ...new Array(11).fill(smallEntry), 49 | bigEntry, 50 | bigEntry, 51 | bigEntry, 52 | ], 53 | output: [ 54 | [smallEntry, smallEntry, bigEntry, bigEntry], // ~ 250Kb (+ 25Kb > 256Kb) 55 | new Array(10).fill(smallEntry), // 10 limit 56 | [smallEntry, bigEntry, bigEntry], // ~ 225Kb (+ 100Kb > 256Kb limit) 57 | [bigEntry], 58 | ], 59 | }, 60 | ]; 61 | 62 | describe('Bus', () => { 63 | describe('#computeEventSize', () => { 64 | it('should compute event size', () => { 65 | expect( 66 | computeEventSize({ 67 | DetailType: 'myType', 68 | Detail: JSON.stringify({ property: 'vaalue' }), 69 | }), 70 | ).toBe(27); 71 | }); 72 | }); 73 | 74 | describe('#chunkEntries', () => { 75 | it.each(cases)( 76 | 'should return the chunks array for $name', 77 | ({ entries, output }) => { 78 | expect(chunkEntries(entries)).toEqual(output); 79 | }, 80 | ); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /src/Event.ts: -------------------------------------------------------------------------------- 1 | import Ajv from 'ajv'; 2 | import type { 3 | PutEventsRequestEntry, 4 | PutEventsResponse, 5 | } from '@aws-sdk/client-eventbridge'; 6 | import { FromSchema, JSONSchema } from 'json-schema-to-ts'; 7 | import type { EventBridgeEvent } from 'aws-lambda'; 8 | 9 | import { Bus } from './Bus'; 10 | 11 | const ajv = new Ajv(); 12 | 13 | export class Event { 14 | private _name: N; 15 | private _source: string; 16 | private _bus: Bus; 17 | private _schema: S; 18 | private _validate: Ajv.ValidateFunction; 19 | private _pattern: { 'detail-type': [N]; source: string[] }; 20 | 21 | constructor({ 22 | name, 23 | source, 24 | bus, 25 | schema, 26 | }: { 27 | name: N; 28 | source: string; 29 | bus: Bus; 30 | schema: S; 31 | }) { 32 | this._name = name; 33 | this._source = source; 34 | this._bus = bus; 35 | this._schema = schema; 36 | this._validate = ajv.compile(schema); 37 | this._pattern = { source: [source], 'detail-type': [name] }; 38 | } 39 | 40 | get name(): N { 41 | return this._name; 42 | } 43 | 44 | get source(): string { 45 | return this._source; 46 | } 47 | 48 | get bus(): Bus { 49 | return this._bus; 50 | } 51 | 52 | get schema(): S { 53 | return this._schema; 54 | } 55 | 56 | get pattern(): { 'detail-type': [N]; source: string[] } { 57 | return this._pattern; 58 | } 59 | 60 | get publishedEventSchema(): { 61 | type: 'object'; 62 | properties: { 63 | source: { const: string }; 64 | 'detail-type': { const: N }; 65 | detail: S; 66 | }; 67 | required: ['source', 'detail-type', 'detail']; 68 | } { 69 | return { 70 | type: 'object', 71 | properties: { 72 | source: { const: this._source }, 73 | 'detail-type': { const: this._name }, 74 | detail: this._schema, 75 | }, 76 | required: ['source', 'detail-type', 'detail'], 77 | }; 78 | } 79 | 80 | create(event: FromSchema): PutEventsRequestEntry { 81 | if (!this._validate(event)) { 82 | throw new Error( 83 | 'Event does not satisfy schema' + JSON.stringify(this._validate.errors), 84 | ); 85 | } 86 | 87 | return { 88 | Source: this._source, 89 | DetailType: this._name, 90 | Detail: JSON.stringify(event), 91 | }; 92 | } 93 | 94 | async publish(event: FromSchema): Promise { 95 | return this._bus.put([this.create(event)]); 96 | } 97 | } 98 | 99 | type GenericEvent = Event; 100 | 101 | export type PublishedEvent = EventBridgeEvent< 102 | Event['name'], 103 | FromSchema 104 | >; 105 | -------------------------------------------------------------------------------- /src/Bus.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | EventBridgeClient, 3 | PutEventsResponse, 4 | PutEventsRequestEntry, 5 | PutEventsResultEntry, 6 | } from '@aws-sdk/client-eventbridge'; 7 | import { PutEventsCommand } from '@aws-sdk/client-eventbridge'; 8 | import { JSONSchema } from 'json-schema-to-ts'; 9 | 10 | import { Event } from './Event'; 11 | 12 | type ChunkedEntriesAccumulator = { 13 | chunkedEntries: PutEventsRequestEntry[][]; 14 | lastChunkSize: number; 15 | lastChunkLength: number; 16 | }; 17 | 18 | export class Bus { 19 | private _name: string; 20 | private _eventBridge: EventBridgeClient; 21 | constructor({ 22 | name, 23 | EventBridge, 24 | }: { 25 | name: string; 26 | EventBridge: EventBridgeClient; 27 | }) { 28 | this._name = name; 29 | this._eventBridge = EventBridge; 30 | } 31 | 32 | async put(events: PutEventsRequestEntry[]): Promise { 33 | const entries = events.map((entry) => 34 | Object.assign({}, { ...entry }, { EventBusName: this._name }), 35 | ); 36 | 37 | const chunkedEntries = chunkEntries(entries); 38 | const results = await Promise.all( 39 | chunkedEntries.map((chunk) => { 40 | const putEventsCommand = new PutEventsCommand({ Entries: chunk }); 41 | return this._eventBridge.send(putEventsCommand); 42 | }), 43 | ); 44 | 45 | return results.reduce<{ 46 | Entries: PutEventsResultEntry[]; 47 | FailedEntryCount: number; 48 | }>( 49 | (returnValue, result) => { 50 | if (result.FailedEntryCount) { 51 | returnValue.FailedEntryCount += result.FailedEntryCount; 52 | } 53 | if (result.Entries) { 54 | returnValue.Entries.push(...result.Entries); 55 | } 56 | 57 | return returnValue; 58 | }, 59 | { Entries: [], FailedEntryCount: 0 }, 60 | ); 61 | } 62 | 63 | computePattern(events: Event[]): { 64 | source?: string[]; 65 | 'detail-type'?: string[]; 66 | } { 67 | const pattern = {}; 68 | const areAllEventSourcesEqual = events.every( 69 | (event) => event.source === events[0].source, 70 | ); 71 | const areAllEventDetailTypesEqual = events.every( 72 | (event) => event.name === events[0].name, 73 | ); 74 | 75 | if (areAllEventSourcesEqual || areAllEventDetailTypesEqual) { 76 | Object.assign(pattern, { source: events.map((event) => event.source) }); 77 | } 78 | Object.assign(pattern, { 79 | 'detail-type': events.map((event) => event.name), 80 | }); 81 | 82 | return pattern; 83 | } 84 | } 85 | 86 | export function computeEventSize(event: PutEventsRequestEntry): number { 87 | let size = 0; 88 | 89 | if (event.Time) size += 14; 90 | if (event.Detail) size += Buffer.byteLength(event.Detail, 'utf8'); 91 | if (event.DetailType) size += Buffer.byteLength(event.DetailType, 'utf8'); 92 | if (event.Source) size += Buffer.byteLength(event.Source, 'utf8'); 93 | if (event.Resources) { 94 | event.Resources.forEach((resource) => Buffer.byteLength(resource, 'utf8')); 95 | } 96 | 97 | return size; 98 | } 99 | 100 | export function chunkEntries( 101 | events: PutEventsRequestEntry[], 102 | ): ChunkedEntriesAccumulator['chunkedEntries'] { 103 | return events.reduce( 104 | ( 105 | chunkedEntriesAccumulator: ChunkedEntriesAccumulator, 106 | entry: PutEventsRequestEntry, 107 | ) => { 108 | const { chunkedEntries, lastChunkSize, lastChunkLength } = 109 | chunkedEntriesAccumulator; 110 | const eventSize = computeEventSize(entry); 111 | 112 | if (lastChunkSize + eventSize > 256000 || lastChunkLength === 10) 113 | return { 114 | chunkedEntries: [...chunkedEntries, [entry]], 115 | lastChunkSize: eventSize, 116 | lastChunkLength: 1, 117 | }; 118 | 119 | const lastChunk = chunkedEntries.pop() ?? []; 120 | 121 | return { 122 | chunkedEntries: [...chunkedEntries, [...lastChunk, entry]], 123 | lastChunkSize: lastChunkSize + eventSize, 124 | lastChunkLength: lastChunkLength + 1, 125 | }; 126 | }, 127 | { 128 | chunkedEntries: [], 129 | lastChunkSize: 0, 130 | lastChunkLength: 0, 131 | }, 132 | ).chunkedEntries; 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeBridge 2 | 3 | Typescript toolbox for AWS EventBridge 4 | 5 | ## Advantages 6 | 7 | - Programmatical definition of your application events 8 | - Typed publish and consume APIs 9 | - Automatically batches `putEvents` call when publishing more than 10 events at a time 10 | - Check for event payload size before publishing 11 | 12 | ## Quick install 13 | 14 | ### Add typebridge dependency 15 | 16 | `npm i typebridge --save` 17 | 18 | > Typebridge `v1` and above is meant to be used with [AWS SDK v3](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-eventbridge/index.html). If you want to use Typebridge with AWS SDK v2, you should install `v0` versions of this package `npm i typebridge@^0` 19 | 20 | ### Define your bus and events 21 | 22 | ```ts 23 | import { EventBridgeClient } from '@aws-sdk/client-eventbridge'; 24 | import { Bus, Event } from 'typebridge'; 25 | 26 | export const MyBus = new Bus({ 27 | name: 'applicationBus', 28 | EventBridge: new EventBridgeClient({}), 29 | }); 30 | 31 | export const MyEventPayloadSchema = { 32 | type: 'object', 33 | properties: { 34 | stringAttribute: { type: 'string' }, 35 | numberAttribute: { type: 'integer' }, 36 | }, 37 | required: ['stringAttribute'], 38 | additionalProperties: false 39 | } as const; 40 | 41 | export const MyEvent = new Event({ 42 | name: 'MyEvent', 43 | bus: MyBus, 44 | schema: MyEventPayloadSchema, 45 | source: 'mySource' 46 | }); 47 | ``` 48 | 49 | ### Use the Event class to publish 50 | 51 | ```ts 52 | import { MyEvent } from './events.ts'; 53 | 54 | export const handler = async (event) => { 55 | await MyEvent.publish({ 56 | stringAttribute: 'string', 57 | numberAttribute: 12, 58 | }) 59 | 60 | return 'Event published !' 61 | }; 62 | ``` 63 | 64 | Typechecking is automatically enabled: 65 | 66 | ```ts 67 | await MyEvent.publish({ 68 | stringAttribute: 'string', 69 | numberAttribute: 12, 70 | // the following line will trigger a Typescript error 71 | anotherAttribute: 'wrong' 72 | }) 73 | ``` 74 | ### Use the Event class to create an event 75 | 76 | ```ts 77 | import { MyBus, MyEvent } from './events.ts'; 78 | 79 | export const handler = async (event) => { 80 | const events = event.details.map(detail => MyEvent.create({ 81 | stringAttribute: detail.stringAttribute, 82 | numberAttribute: detail.numberAttribute, 83 | }) 84 | await MyBus.put(events); 85 | 86 | return 'Event published !' 87 | }; 88 | ``` 89 | 90 | ### Use the Event class to generate trigger rules 91 | 92 | Using the serverless framework with `serverless.ts` service file: 93 | 94 | 95 | ```ts 96 | import type { Serverless } from 'serverless/aws'; 97 | 98 | const serverlessConfiguration: Serverless = { 99 | service: 'typebridge-test', 100 | provider: { 101 | name: 'aws', 102 | runtime: 'nodejs12.x', 103 | }, 104 | functions: { 105 | hello: { 106 | handler: 'MyEventHandler.handler', 107 | events: [ 108 | { 109 | eventBridge: { 110 | eventBus: 'applicationBus', 111 | pattern: NewUserConnectedEvent.pattern, 112 | }, 113 | }, 114 | ], 115 | } 116 | } 117 | } 118 | 119 | module.exports = serverlessConfiguration; 120 | ``` 121 | 122 | ### Use the Event class to type input event 123 | 124 | ```ts 125 | import { PublishedEvent } from 'typebridge'; 126 | import { MyEvent } from './events.ts'; 127 | 128 | export const handler = (event: PublishedEvent) => { 129 | // Typed as string 130 | return event.detail.stringAttribute; 131 | } 132 | ``` 133 | 134 | ### Use the Event class to validate the input event 135 | 136 | Using [middy](https://github.com/middyjs/middy) middleware stack in your lambda's handler, you can throw an error before your handler's code being executed if the input event `source` or `detail-type` were not expected, or if the `detail` property does not satisfy the JSON-schema used in `MyEvent` constructor. 137 | 138 | ```ts 139 | import middy from '@middy/core'; 140 | import jsonValidator from '@middy/validator'; 141 | import { MyEvent } from './events.ts'; 142 | 143 | const handler = (event) => { 144 | return 'Validation succeeded'; 145 | }; 146 | 147 | // If event.detail does not match the JSON-schema supplied to MyEvent constructor, the middleware will throw an error 148 | export const main = middy(handler).use( 149 | jsonValidator({ inputSchema: MyEvent.publishedEventSchema }), 150 | ); 151 | ``` 152 | -------------------------------------------------------------------------------- /src/Event.test.ts: -------------------------------------------------------------------------------- 1 | import { PutEventsCommand } from '@aws-sdk/client-eventbridge'; 2 | import { promisify } from 'util'; 3 | import { EventBridgeClient } from '@aws-sdk/client-eventbridge'; 4 | import { mockClient } from 'aws-sdk-client-mock'; 5 | import type { Handler } from 'aws-lambda'; 6 | import context from 'aws-lambda-mock-context'; 7 | import createError from 'http-errors'; 8 | import middy from '@middy/core'; 9 | import jsonValidator from '@middy/validator'; 10 | 11 | import { Bus } from './Bus'; 12 | import { Event } from './Event'; 13 | 14 | function invoke(handler: Handler, event = {}) { 15 | return promisify(handler)(event, context({ timeout: 1 })); 16 | } 17 | 18 | // Required by aws-lambda-mock-context getRemainingTimeInMillis functionality 19 | jest.useFakeTimers(); 20 | 21 | describe('Event', () => { 22 | describe('#construct', () => { 23 | const eventBridgeMock = mockClient(EventBridgeClient); 24 | 25 | eventBridgeMock 26 | .on(PutEventsCommand) 27 | .resolves({ Entries: [{ EventId: '123456' }] }); 28 | 29 | const myBus = new Bus({ 30 | name: 'test', 31 | // @ts-expect-error Mocking library mocked client is not type compatible with actual client 32 | EventBridge: eventBridgeMock, 33 | }); 34 | 35 | const schema = { 36 | type: 'object', 37 | properties: { 38 | attribute: { type: 'string' }, 39 | numberAttribute: { type: 'number' }, 40 | }, 41 | additionalProperties: false, 42 | required: ['attribute'], 43 | } as const; 44 | 45 | const myEvent = new Event({ 46 | name: 'myEvent', 47 | source: 'source', 48 | bus: myBus, 49 | schema, 50 | }); 51 | 52 | afterAll(() => { 53 | eventBridgeMock.reset(); 54 | }); 55 | 56 | it('should allow publishing an event', async () => { 57 | expect( 58 | await myEvent.publish({ 59 | attribute: 'hello', 60 | numberAttribute: 12, 61 | }), 62 | ).toHaveProperty('Entries', [{ EventId: '123456' }]); 63 | }); 64 | 65 | it('should fail with the use of validationMiddleware on wrong event', () => { 66 | const handler: Handler = (_event, _ctx, callback) => callback(null, '5'); 67 | const middyfiedHandler = middy(handler).use( 68 | jsonValidator({ inputSchema: myEvent.publishedEventSchema }), 69 | ); 70 | 71 | expect( 72 | invoke(middyfiedHandler, { 73 | source: myEvent.source, 74 | 'detail-type': myEvent.name, 75 | detail: { 76 | otherAttribute: 'hello', 77 | }, 78 | }), 79 | ).rejects.toEqual( 80 | new createError.BadRequest('Event object failed validation'), 81 | ); 82 | 83 | expect( 84 | invoke(middyfiedHandler, { 85 | source: 'unexpected source', 86 | 'detail-type': myEvent.name, 87 | detail: { 88 | attribute: 'goodbye', 89 | numberAttribute: 23, 90 | }, 91 | }), 92 | ).rejects.toEqual( 93 | new createError.BadRequest('Event object failed validation'), 94 | ); 95 | 96 | expect( 97 | invoke(middyfiedHandler, { 98 | source: myEvent.source, 99 | 'detail-type': 'unexpected detail type', 100 | detail: { 101 | attribute: 'goodbye', 102 | numberAttribute: 23, 103 | }, 104 | }), 105 | ).rejects.toEqual( 106 | new createError.BadRequest('Event object failed validation'), 107 | ); 108 | }); 109 | 110 | it('should succeed with the use of validationMiddleware on correct event', () => { 111 | const handler: Handler = (_event, _ctx, callback) => 112 | callback(null, 'returnValue'); 113 | const middyfiedHandler = middy(handler).use( 114 | jsonValidator({ inputSchema: myEvent.publishedEventSchema }), 115 | ); 116 | 117 | expect( 118 | invoke(middyfiedHandler, { 119 | source: myEvent.source, 120 | 'detail-type': myEvent.name, 121 | detail: { 122 | attribute: 'goodbye', 123 | numberAttribute: 23, 124 | }, 125 | }), 126 | ).resolves.toEqual('returnValue'); 127 | }); 128 | 129 | it('should compute correct pattern', async () => { 130 | expect(myEvent.pattern).toStrictEqual({ 131 | 'detail-type': ['myEvent'], 132 | source: ['source'], 133 | }); 134 | }); 135 | 136 | it('should create event pattern', () => { 137 | expect( 138 | myEvent.create({ 139 | attribute: 'hello', 140 | numberAttribute: 12, 141 | }), 142 | ).toEqual({ 143 | Source: 'source', 144 | DetailType: 'myEvent', 145 | Detail: JSON.stringify({ 146 | attribute: 'hello', 147 | numberAttribute: 12, 148 | }), 149 | }); 150 | }); 151 | }); 152 | }); 153 | --------------------------------------------------------------------------------