Skip to content
This repository was archived by the owner on Dec 10, 2024. It is now read-only.

Commit

Permalink
feat: add event horizon primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
pevisscher committed Nov 29, 2022
1 parent d37793f commit 7106d17
Show file tree
Hide file tree
Showing 28 changed files with 303 additions and 30 deletions.
26 changes: 0 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"dependencies": {
"@skyleague/axioms": "^1.9.0",
"@skyleague/event-horizon": "^1.26.2",
"@skyleague/space-junk": "^1.2.1",
"@skyleague/therefore": "^1.20.2",
"@types/express": "^4.17.14",
"express": "^4.18.2",
Expand All @@ -69,4 +68,4 @@
"registry": "https://registry.npmjs.org",
"access": "public"
}
}
}
6 changes: 4 additions & 2 deletions src/commands/start/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { context } from '../../lib'

import { entriesOf, random, valuesOf } from '@skyleague/axioms'
import type { EventHandler } from '@skyleague/event-horizon/src/handlers/types'
import { arbitraryContext } from '@skyleague/space-junk'
import type { APIGatewayProxyHandler, APIGatewayProxyResult } from 'aws-lambda'
import express from 'express'
import Router from 'express-promise-router'
Expand All @@ -28,6 +29,7 @@ export async function handler(argv: ReturnType<typeof builder>['argv']): Promise

if (debug) {
process.env.IS_DEBUG = 'true'
process.env.POWERTOOLS_DEV = 'true'
}

const router = Router()
Expand All @@ -48,7 +50,7 @@ export async function handler(argv: ReturnType<typeof builder>['argv']): Promise
pathParameters: req.params,
body: req.body,
} as any,
random(await arbitraryContext()).raw,
random(await context()).raw,
{} as any
)) as APIGatewayProxyResult

Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { install } from 'source-map-support'
import type { CommandModule } from 'yargs'
import yargs from 'yargs'

export * from './lib'

export async function run(): Promise<void> {
install()

Expand Down
9 changes: 9 additions & 0 deletions src/lib/event-horizon/context/context.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { context } from './context'

import { Context } from '../../aws'

import { forAll } from '@skyleague/axioms'

test('context === context', async () => {
forAll(await context(), (c) => Context.assert(c.raw))
})
47 changes: 47 additions & 0 deletions src/lib/event-horizon/context/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Context } from '../../aws/lambda'
import { mock } from '../../test'

import type { Arbitrary, Dependent } from '@skyleague/axioms'
import { constant, isFunction, object, random, string } from '@skyleague/axioms'
import type { Config, EventHandlerDefinition, LambdaContext, Logger, Metrics, Services, Tracer } from '@skyleague/event-horizon'
import { arbitrary } from '@skyleague/therefore'
import type { Context as AwsContext } from 'aws-lambda'

export interface ContextOptions {
exhaustive?: boolean
}

export async function context<C = never, S = never>(
{ config, services, isSensitive }: EventHandlerDefinition & { config?: Config<C>; services?: Services<C, S> } = {},
options: ContextOptions = {}
): Promise<Dependent<LambdaContext<C, S> & { mockClear: () => void }>> {
const { exhaustive = false } = options
const configObj = isFunction(config) ? await config() : config
const ctxArb = arbitrary(Context) as Dependent<AwsContext>
return object({
logger: constant(mock<Logger>()),
tracer: constant(mock<Tracer>()),
metrics: constant(mock<Metrics>()),
requestId: string({ minLength: 2 }),
traceId: string({ minLength: 2 }),
isSensitive: constant(isSensitive ?? false),
raw: exhaustive ? ctxArb : constant(random(ctxArb)),
config: constant(configObj) as Arbitrary<C>,
services: constant(isFunction(services) ? await services(configObj as C) : services) as Arbitrary<S>,
})
.map((o) => {
return {
...o,
mockClear: () => {
// reset state on each evaluation
o.logger.mockClear()
o.tracer.mockClear()
o.metrics.mockClear()
},
}
})
.map((o) => {
o.mockClear()
return o
})
}
1 change: 1 addition & 0 deletions src/lib/event-horizon/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { context } from './context'
18 changes: 18 additions & 0 deletions src/lib/event-horizon/eventbridge/eventbridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { EventBridgeEvent as AWSEventBridgeEvent } from '../../aws'

import type { Dependent } from '@skyleague/axioms'
import { tuple, unknown } from '@skyleague/axioms'
import type { EventBridgeEvent, EventBridgeHandler } from '@skyleague/event-horizon'
import { arbitrary } from '@skyleague/therefore'

export function eventBridgeEvent<C = never, S = never, P = unknown, R = unknown>(
definition: EventBridgeHandler<C, S, P, R>
): Dependent<EventBridgeEvent<P>> {
const { eventBridge } = definition
const record = arbitrary(AWSEventBridgeEvent)
const payload = eventBridge.schema.payload !== undefined ? arbitrary(eventBridge.schema.payload) : unknown()
return tuple(record, payload).map(([r, p]) => ({
raw: r,
payload: p,
})) as unknown as Dependent<EventBridgeEvent<P>>
}
1 change: 1 addition & 0 deletions src/lib/event-horizon/eventbridge/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { eventBridgeEvent } from './eventbridge'
18 changes: 18 additions & 0 deletions src/lib/event-horizon/firehose/firehose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FirehoseTransformationEventRecord } from '../../aws/firehose'

import type { Dependent } from '@skyleague/axioms'
import { tuple, unknown } from '@skyleague/axioms'
import type { FirehoseTransformationEvent, FirehoseTransformationHandler } from '@skyleague/event-horizon'
import { arbitrary } from '@skyleague/therefore'

export function firehoseTransformationEvent<C = never, S = never, P = unknown, R = unknown>(
definition: FirehoseTransformationHandler<C, S, P, R>
): Dependent<FirehoseTransformationEvent<P>> {
const { firehose } = definition
const record = arbitrary(FirehoseTransformationEventRecord)
const payload = firehose.schema.payload !== undefined ? arbitrary(firehose.schema.payload) : unknown()
return tuple(record, payload).map(([r, p]) => ({
raw: r,
payload: p,
})) as unknown as Dependent<FirehoseTransformationEvent<P>>
}
1 change: 1 addition & 0 deletions src/lib/event-horizon/firehose/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { firehoseTransformationEvent } from './firehose'
26 changes: 26 additions & 0 deletions src/lib/event-horizon/http/http.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { httpEvent } from './http'

import { APIGatewayProxyEvent } from '../../aws/apigateway'

import { forAll, isString } from '@skyleague/axioms'

test('httpEvent === httpEvent', () => {
forAll(httpEvent({ http: { method: 'get', path: '/', handler: jest.fn(), schema: { responses: {} } } }), (e) =>
APIGatewayProxyEvent.assert(e.raw)
)
})

test('httpEvent body === body', () => {
forAll(
httpEvent({
http: {
method: 'get',
path: '/',
handler: jest.fn(),
schema: { body: { schema: { type: 'string' } } as any, responses: {} },
},
}),
(e) => isString(e.body)
)
})
53 changes: 53 additions & 0 deletions src/lib/event-horizon/http/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { APIGatewayProxyEvent } from '../../aws/apigateway'

import type { Dependent } from '@skyleague/axioms'
import { constant, object } from '@skyleague/axioms'
import type { GatewayVersion, HttpHandler, HttpRequest } from '@skyleague/event-horizon'
import { arbitrary } from '@skyleague/therefore'

export function httpEvent<
C = unknown,
S = unknown,
HttpB = unknown,
HttpP = unknown,
HttpQ = unknown,
HttpH = unknown,
HttpR = unknown,
GV extends GatewayVersion = 'v1'
>(definition: HttpHandler<C, S, HttpB, HttpP, HttpQ, HttpH, HttpR, GV>): Dependent<HttpRequest<HttpB, HttpP, HttpQ, HttpH, GV>> {
const { http } = definition
const { bodyType = 'json' } = http

const body = http.schema.body !== undefined ? arbitrary(http.schema.body) : constant(undefined)
const headers = http.schema.headers !== undefined ? arbitrary(http.schema.headers) : constant(undefined)
const query = http.schema.query !== undefined ? arbitrary(http.schema.query) : constant(undefined)
const path = http.schema.pathParams !== undefined ? arbitrary(http.schema.pathParams) : constant(undefined)
const raw = arbitrary(APIGatewayProxyEvent)

return raw.chain((r) => {
return object({
body,
headers,
query,
path,
raw: constant(r),
}).map((event) => {
if (bodyType !== 'binary') {
const eventBody = event.body ?? event.raw.body
const b = (bodyType === 'json' ? JSON.stringify(eventBody) : eventBody) ?? ''
event.raw.body = (
event.raw.isBase64Encoded ? Buffer.from(b.toString()).toString('base64') : b
) as typeof event.raw.body
}
event.raw.headers ??= (event.headers as typeof event.raw.headers) ?? {}
event.raw.queryStringParameters ??= (event.query as typeof event.raw.queryStringParameters) ?? {}
const rawEvent = event.raw
return {
...event,
get raw() {
return rawEvent
},
}
})
}) as unknown as Dependent<HttpRequest<HttpB, HttpP, HttpQ, HttpH, GV>>
}
1 change: 1 addition & 0 deletions src/lib/event-horizon/http/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { httpEvent } from './http'
9 changes: 9 additions & 0 deletions src/lib/event-horizon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export * from './context'
export * from './eventbridge'
export * from './firehose'
export * from './http'
export * from './kinesis'
export * from './s3'
export * from './s3-batch'
export * from './secret-rotation'
export * from './sqs'
1 change: 1 addition & 0 deletions src/lib/event-horizon/kinesis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { kinesisEvent } from './kinesis'
16 changes: 16 additions & 0 deletions src/lib/event-horizon/kinesis/kinesis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { KinesisStreamRecord } from '../../aws/kinesis'

import type { Dependent } from '@skyleague/axioms'
import { tuple, unknown } from '@skyleague/axioms'
import type { KinesisEvent, KinesisHandler } from '@skyleague/event-horizon'
import { arbitrary } from '@skyleague/therefore'

export function kinesisEvent<C = never, S = never, P = unknown>(definition: KinesisHandler<C, S, P>): Dependent<KinesisEvent<P>> {
const { kinesis } = definition
const record = arbitrary(KinesisStreamRecord)
const payload = kinesis.schema.payload !== undefined ? arbitrary(kinesis.schema.payload) : unknown()
return tuple(record, payload).map(([r, p]) => ({
raw: r,
payload: p,
})) as unknown as Dependent<KinesisEvent<P>>
}
1 change: 1 addition & 0 deletions src/lib/event-horizon/s3-batch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { s3BatchTask } from './s3'
21 changes: 21 additions & 0 deletions src/lib/event-horizon/s3-batch/s3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { S3BatchEvent, S3BatchEventTask } from '../../aws'

import type { Dependent } from '@skyleague/axioms'
import { omit, tuple } from '@skyleague/axioms'
import type { S3BatchTask } from '@skyleague/event-horizon/src/events/s3-batch/types'
import { arbitrary } from '@skyleague/therefore'

export function s3BatchTask(): Dependent<S3BatchTask> {
const task = arbitrary(S3BatchEventTask)
const event = arbitrary(S3BatchEvent)
return tuple(task, event).map(([t, e]) => ({
taskId: t.taskId,
s3Key: t.s3Key,
s3VersionId: t.s3VersionId,
s3BucketArn: t.s3BucketArn,
raw: {
task: task,
job: omit(e, ['tasks']),
},
})) as unknown as Dependent<S3BatchTask>
}
1 change: 1 addition & 0 deletions src/lib/event-horizon/s3/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { s3Event } from './s3'
12 changes: 12 additions & 0 deletions src/lib/event-horizon/s3/s3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { S3EventRecord } from '../../aws/s3/s3.type'

import type { Dependent } from '@skyleague/axioms'
import type { S3Event } from '@skyleague/event-horizon'
import { arbitrary } from '@skyleague/therefore'

export function s3Event(): Dependent<S3Event> {
const record = arbitrary(S3EventRecord)
return record.map((r) => ({
raw: r,
})) as unknown as Dependent<S3Event>
}
1 change: 1 addition & 0 deletions src/lib/event-horizon/secret-rotation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { secretRotationEvent } from './secret-rotation'
9 changes: 9 additions & 0 deletions src/lib/event-horizon/secret-rotation/secret-rotation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { secretRotationEvent } from './secret-rotation'

import { SecretRotationEvent } from '../../aws/secret-rotation/secret-rotation.type'

import { forAll } from '@skyleague/axioms'

test('SecretRotationEvent === SecretRotationRequest.raw', async () => {
forAll(await secretRotationEvent(), (r) => SecretRotationEvent.assert(r.raw))
})
16 changes: 16 additions & 0 deletions src/lib/event-horizon/secret-rotation/secret-rotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SecretRotationEvent } from '../../aws/secret-rotation/secret-rotation.type'

import type { Dependent } from '@skyleague/axioms'
import type { SecretRotationRequest, SecretRotationServices } from '@skyleague/event-horizon'
import { arbitrary } from '@skyleague/therefore'

export function secretRotationEvent<Config = never, Services extends SecretRotationServices = SecretRotationServices>(
_options: { config?: Config; services?: Services } = {}
): Dependent<SecretRotationRequest> {
return arbitrary(SecretRotationEvent).map((e) => ({
raw: e,
step: e.Step,
secretId: e.SecretId,
clientRequestToken: e.ClientRequestToken,
}))
}
1 change: 1 addition & 0 deletions src/lib/event-horizon/sns/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { snsEvent } from './sns'
Loading

0 comments on commit 7106d17

Please sign in to comment.