@auriclabs/events 0.2.0 → 0.4.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/.turbo/turbo-build.log +36 -22
- package/CHANGELOG.md +14 -0
- package/dist/index.cjs +4 -82
- package/dist/index.d.cts +1 -142
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -142
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -81
- package/dist/index.mjs.map +1 -1
- package/dist/index2.d.cts +148 -0
- package/dist/index2.d.cts.map +1 -0
- package/dist/index2.d.mts +148 -0
- package/dist/index2.d.mts.map +1 -0
- package/dist/stream-handler.cjs +91 -0
- package/dist/stream-handler.entry.cjs +8 -0
- package/dist/stream-handler.entry.d.cts +7 -0
- package/dist/stream-handler.entry.d.cts.map +1 -0
- package/dist/stream-handler.entry.d.mts +7 -0
- package/dist/stream-handler.entry.d.mts.map +1 -0
- package/dist/stream-handler.entry.mjs +10 -0
- package/dist/stream-handler.entry.mjs.map +1 -0
- package/dist/stream-handler.mjs +88 -0
- package/dist/stream-handler.mjs.map +1 -0
- package/package.json +7 -2
- package/src/create-event-listener.test.ts +5 -4
- package/src/dispatch-event.test.ts +11 -0
- package/src/dispatch-events.test.ts +3 -3
- package/src/event-service.test.ts +74 -32
- package/src/event-service.ts +4 -0
- package/src/init.test.ts +2 -0
- package/src/stream-handler.entry.ts +6 -0
- package/src/stream-handler.test.ts +95 -35
- package/src/stream-handler.ts +6 -2
- package/src/types.ts +3 -0
- package/tsconfig.test.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
import { t as createStreamHandler } from "./stream-handler.mjs";
|
|
1
2
|
import { normalizePaginationResponse } from "@auriclabs/pagination";
|
|
2
3
|
import { ConditionalCheckFailedException, DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
3
4
|
import { DynamoDBDocumentClient, GetCommand, QueryCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb";
|
|
4
5
|
import { retry } from "@auriclabs/api-core";
|
|
5
6
|
import { logger } from "@auriclabs/logger";
|
|
6
7
|
import { ulid } from "ulid";
|
|
7
|
-
import { EventBridgeClient, PutEventsCommand } from "@aws-sdk/client-eventbridge";
|
|
8
|
-
import { SQSClient, SendMessageBatchCommand } from "@aws-sdk/client-sqs";
|
|
9
|
-
import { unmarshall } from "@aws-sdk/util-dynamodb";
|
|
10
|
-
import { kebabCase } from "lodash-es";
|
|
11
8
|
//#region src/event-service.ts
|
|
12
9
|
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient(), { marshallOptions: { removeUndefinedValues: true } });
|
|
13
10
|
const pad = (n, w = 9) => String(n).padStart(w, "0");
|
|
@@ -16,7 +13,7 @@ function createEventService(tableName) {
|
|
|
16
13
|
const TABLE = tableName;
|
|
17
14
|
return {
|
|
18
15
|
async appendEvent(args) {
|
|
19
|
-
const { aggregateType, aggregateId, expectedVersion, idempotencyKey, eventId, eventType, occurredAt, source, payload, schemaVersion, correlationId, causationId, actorId } = args;
|
|
16
|
+
const { tenantId, aggregateType, aggregateId, expectedVersion, idempotencyKey, eventId, eventType, occurredAt, source, payload, schemaVersion, correlationId, causationId, actorId } = args;
|
|
20
17
|
const pk = pkFor(aggregateType, aggregateId);
|
|
21
18
|
const nextVersion = expectedVersion + 1;
|
|
22
19
|
const sk = `EVT#${pad(nextVersion)}`;
|
|
@@ -51,6 +48,7 @@ function createEventService(tableName) {
|
|
|
51
48
|
aggregateId,
|
|
52
49
|
aggregateType,
|
|
53
50
|
version: nextVersion,
|
|
51
|
+
tenantId,
|
|
54
52
|
eventId,
|
|
55
53
|
eventType,
|
|
56
54
|
schemaVersion: schemaVersion ?? 1,
|
|
@@ -223,82 +221,6 @@ const createEventListener = (eventHandlers, { debug = false } = {}) => async (sq
|
|
|
223
221
|
return response;
|
|
224
222
|
};
|
|
225
223
|
//#endregion
|
|
226
|
-
//#region src/stream-handler.ts
|
|
227
|
-
const BATCH_SIZE = 10;
|
|
228
|
-
/**
|
|
229
|
-
* Creates a Lambda handler for DynamoDB stream events.
|
|
230
|
-
* Processes INSERT events from the event store table and forwards them to SQS queues and EventBridge.
|
|
231
|
-
*/
|
|
232
|
-
function createStreamHandler(config) {
|
|
233
|
-
const sqsClient = new SQSClient();
|
|
234
|
-
const eventBridge = new EventBridgeClient({});
|
|
235
|
-
function chunkArray(array, chunkSize) {
|
|
236
|
-
const chunks = [];
|
|
237
|
-
for (let i = 0; i < array.length; i += chunkSize) chunks.push(array.slice(i, i + chunkSize));
|
|
238
|
-
return chunks;
|
|
239
|
-
}
|
|
240
|
-
async function sendToQueuesBatch(eventRecords) {
|
|
241
|
-
await Promise.all(config.queueUrls.map((queue) => sendToQueueBatch(eventRecords, queue)));
|
|
242
|
-
}
|
|
243
|
-
async function sendToQueueBatch(eventRecords, queue) {
|
|
244
|
-
const batches = chunkArray(eventRecords, BATCH_SIZE);
|
|
245
|
-
for (const batch of batches) try {
|
|
246
|
-
const entries = batch.map((eventRecord, index) => ({
|
|
247
|
-
Id: `${eventRecord.eventId}-${index}`,
|
|
248
|
-
MessageBody: JSON.stringify(eventRecord),
|
|
249
|
-
MessageGroupId: eventRecord.aggregateId,
|
|
250
|
-
MessageDeduplicationId: eventRecord.eventId
|
|
251
|
-
}));
|
|
252
|
-
await sqsClient.send(new SendMessageBatchCommand({
|
|
253
|
-
QueueUrl: queue,
|
|
254
|
-
Entries: entries
|
|
255
|
-
}));
|
|
256
|
-
} catch (error) {
|
|
257
|
-
logger.error({
|
|
258
|
-
error,
|
|
259
|
-
batch,
|
|
260
|
-
queue
|
|
261
|
-
}, "Error sending batch to queue");
|
|
262
|
-
throw error;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
async function sendToBusBatch(eventRecords) {
|
|
266
|
-
const batches = chunkArray(eventRecords, BATCH_SIZE);
|
|
267
|
-
for (const batch of batches) try {
|
|
268
|
-
const entries = batch.map((eventRecord) => {
|
|
269
|
-
return {
|
|
270
|
-
Source: eventRecord.source ?? kebabCase(eventRecord.aggregateType.split(".")[0]),
|
|
271
|
-
DetailType: eventRecord.eventType,
|
|
272
|
-
Detail: JSON.stringify(eventRecord),
|
|
273
|
-
EventBusName: config.busName
|
|
274
|
-
};
|
|
275
|
-
});
|
|
276
|
-
await eventBridge.send(new PutEventsCommand({ Entries: entries }));
|
|
277
|
-
} catch (error) {
|
|
278
|
-
logger.error({
|
|
279
|
-
error,
|
|
280
|
-
batch
|
|
281
|
-
}, "Error sending batch to bus");
|
|
282
|
-
throw error;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return async (event) => {
|
|
286
|
-
const eventRecords = event.Records.filter((record) => record.eventName === "INSERT").map((record) => {
|
|
287
|
-
try {
|
|
288
|
-
const data = record.dynamodb?.NewImage;
|
|
289
|
-
return unmarshall(data);
|
|
290
|
-
} catch (error) {
|
|
291
|
-
logger.error({
|
|
292
|
-
error,
|
|
293
|
-
record
|
|
294
|
-
}, "Error unmarshalling event record");
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
}).filter((eventRecord) => eventRecord?.itemType === "event");
|
|
298
|
-
if (eventRecords.length > 0) await Promise.all([sendToBusBatch(eventRecords), sendToQueuesBatch(eventRecords)]);
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
//#endregion
|
|
302
224
|
export { appendEventContext, createDispatch, createEventListener, createEventService, createStreamHandler, dispatchEvent, dispatchEvents, getEventContext, getEventService, initEvents, resetEventContext, setEventContext };
|
|
303
225
|
|
|
304
226
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../src/create-event-listener.ts","../src/stream-handler.ts"],"sourcesContent":["import { normalizePaginationResponse, PaginationResponse } from '@auriclabs/pagination';\nimport { DynamoDBClient, ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb';\nimport {\n DynamoDBDocumentClient,\n TransactWriteCommand,\n GetCommand,\n QueryCommand,\n} from '@aws-sdk/lib-dynamodb';\n\nimport type {\n EventRecord,\n AggregateHead,\n AggregatePK,\n EventId,\n AggregateId,\n AggregateType,\n EventSK,\n Source,\n} from './types';\n\nconst ddb = DynamoDBDocumentClient.from(new DynamoDBClient(), {\n marshallOptions: {\n removeUndefinedValues: true,\n },\n});\n\nconst pad = (n: number, w = 9): EventId => String(n).padStart(w, '0') as EventId;\nconst pkFor = (aggregateType: string, aggregateId: string): AggregatePK =>\n `AGG#${aggregateType}#${aggregateId}`;\n\nexport interface AppendArgs<P = unknown> {\n aggregateType: string;\n aggregateId: string;\n source: string;\n /** Version you observed before appending (0 for brand new) */\n expectedVersion: number;\n /** Required for idempotent retries (e.g., the command id) */\n idempotencyKey: string;\n\n // Event properties (flattened)\n eventId: string; // ULID/UUID – must be stable across retries\n eventType: string;\n occurredAt?: string; // default: now ISO\n payload?: Readonly<P>;\n schemaVersion?: number; // optional but recommended\n\n // Optional metadata\n correlationId?: string;\n causationId?: string;\n actorId?: string;\n}\n\nexport interface AppendEventResult {\n pk: string;\n sk: string;\n version: number;\n}\n\nexport interface EventService {\n appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult>;\n getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined>;\n getEvent(\n aggregateType: string,\n aggregateId: string,\n version: number,\n ): Promise<EventRecord | undefined>;\n listEvents(params: {\n aggregateType: string;\n aggregateId: string;\n fromVersionExclusive?: number;\n toVersionInclusive?: number;\n limit?: number;\n }): Promise<PaginationResponse<EventRecord>>;\n}\n\nexport function createEventService(tableName: string): EventService {\n const TABLE = tableName;\n\n return {\n async appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult> {\n const {\n aggregateType,\n aggregateId,\n expectedVersion,\n idempotencyKey,\n eventId,\n eventType,\n occurredAt,\n source,\n payload,\n schemaVersion,\n correlationId,\n causationId,\n actorId,\n } = args;\n\n const pk = pkFor(aggregateType, aggregateId);\n const nextVersion = expectedVersion + 1;\n const sk = `EVT#${pad(nextVersion)}` as EventSK;\n const nowIso = new Date().toISOString();\n const eventOccurredAt = occurredAt ?? nowIso;\n\n try {\n await ddb.send(\n new TransactWriteCommand({\n TransactItems: [\n {\n Update: {\n TableName: TABLE,\n Key: { pk, sk: 'HEAD' },\n UpdateExpression:\n 'SET currentVersion = :next, lastEventId = :eid, lastIdemKey = :idem, updatedAt = :now, aggregateId = if_not_exists(aggregateId, :aid), aggregateType = if_not_exists(aggregateType, :atype)',\n ConditionExpression:\n '(attribute_not_exists(currentVersion) AND :expected = :zero) ' +\n 'OR currentVersion = :expected ' +\n 'OR lastIdemKey = :idem',\n ExpressionAttributeValues: {\n ':zero': 0,\n ':expected': expectedVersion,\n ':next': nextVersion,\n ':eid': eventId,\n ':idem': idempotencyKey,\n ':now': nowIso,\n ':aid': aggregateId,\n ':atype': aggregateType,\n },\n },\n },\n {\n Put: {\n TableName: TABLE,\n Item: {\n pk,\n sk,\n itemType: 'event',\n source: source as Source,\n aggregateId: aggregateId as AggregateId,\n aggregateType: aggregateType as AggregateType,\n version: nextVersion,\n\n eventId: eventId as EventId,\n eventType: eventType,\n schemaVersion: schemaVersion ?? 1,\n occurredAt: eventOccurredAt,\n\n correlationId,\n causationId,\n actorId,\n\n payload: payload as Readonly<unknown>,\n } satisfies EventRecord,\n ConditionExpression: 'attribute_not_exists(pk) OR eventId = :eid',\n ExpressionAttributeValues: { ':eid': eventId },\n },\n },\n ],\n }),\n );\n } catch (err) {\n if (err instanceof ConditionalCheckFailedException) {\n throw new Error(\n `OCC failed for aggregate ${aggregateType}/${aggregateId}: expectedVersion=${expectedVersion}`,\n );\n }\n throw err;\n }\n\n return { pk, sk, version: nextVersion };\n },\n\n async getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined> {\n const pk = pkFor(aggregateType, aggregateId);\n const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk, sk: 'HEAD' } }));\n return res.Item as AggregateHead | undefined;\n },\n\n async getEvent(\n aggregateType: string,\n aggregateId: string,\n version: number,\n ): Promise<EventRecord | undefined> {\n const pk = pkFor(aggregateType, aggregateId);\n const sk = `EVT#${pad(version)}`;\n const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk, sk } }));\n return res.Item as EventRecord | undefined;\n },\n\n async listEvents(params: {\n aggregateType: string;\n aggregateId: string;\n fromVersionExclusive?: number;\n toVersionInclusive?: number;\n limit?: number;\n }): Promise<PaginationResponse<EventRecord>> {\n const pk = pkFor(params.aggregateType, params.aggregateId);\n const fromSk =\n params.fromVersionExclusive != null\n ? `EVT#${pad(params.fromVersionExclusive + 1)}`\n : 'EVT#000000000';\n const toSk =\n params.toVersionInclusive != null\n ? `EVT#${pad(params.toVersionInclusive)}`\n : 'EVT#999999999';\n\n const res = await ddb.send(\n new QueryCommand({\n TableName: TABLE,\n KeyConditionExpression: 'pk = :pk AND sk BETWEEN :from AND :to',\n ExpressionAttributeValues: {\n ':pk': pk,\n ':from': fromSk,\n ':to': toSk,\n },\n ScanIndexForward: true,\n Limit: params.limit,\n }),\n );\n\n return normalizePaginationResponse({\n data: (res.Items ?? []) as EventRecord[],\n cursor: res.LastEvaluatedKey && (res.LastEvaluatedKey as { pk: string; sk: string }).sk,\n });\n },\n };\n}\n","import { createEventService, EventService } from './event-service';\n\nlet _eventService: EventService | undefined;\n\nexport function initEvents(config: { tableName: string }): void {\n _eventService = createEventService(config.tableName);\n}\n\nexport function getEventService(): EventService {\n if (!_eventService) {\n throw new Error('Call initEvents() before using events');\n }\n return _eventService;\n}\n","import { AppendArgs } from './event-service';\n\nexport type EventContext = Partial<AppendArgs>;\n\nlet context: EventContext = {};\n\nexport const setEventContext = (newContext: EventContext) => {\n context = { ...newContext };\n};\n\nexport const getEventContext = () => context;\n\nexport const resetEventContext = () => {\n context = {};\n};\n\nexport const appendEventContext = (event: EventContext) => {\n context = { ...context, ...event };\n};\n","import { retry } from '@auriclabs/api-core';\nimport { logger } from '@auriclabs/logger';\nimport { ulid } from 'ulid';\n\nimport { getEventContext } from './context';\nimport { AppendArgs, AppendEventResult } from './event-service';\nimport { getEventService } from './init';\n\nexport type DispatchEventArgs = Omit<\n AppendArgs,\n 'eventId' | 'expectedVersion' | 'schemaVersion' | 'occurredAt' | 'idempotencyKey'\n> &\n Partial<Pick<AppendArgs, 'idempotencyKey' | 'eventId'>>;\n\nexport const dispatchEvent = async (event: DispatchEventArgs): Promise<AppendEventResult> => {\n const eventService = getEventService();\n const eventId = event.eventId ?? `evt-${ulid()}`;\n const occurredAt = new Date().toISOString();\n const idempotencyKey = event.idempotencyKey ?? eventId;\n\n return retry(async () => {\n const head = await eventService.getHead(event.aggregateType, event.aggregateId);\n logger.debug({ event }, 'Dispatching event');\n return eventService.appendEvent({\n ...getEventContext(),\n ...event,\n eventId,\n expectedVersion: head?.currentVersion ?? 0,\n schemaVersion: 1,\n occurredAt,\n idempotencyKey,\n });\n });\n};\n","import { dispatchEvent, DispatchEventArgs } from './dispatch-event';\n\nexport interface DispatchEventsArgs {\n inOrder?: boolean;\n}\n\nexport const dispatchEvents = async (\n events: DispatchEventArgs[],\n { inOrder = false }: DispatchEventsArgs = {},\n) => {\n if (inOrder) {\n for (const event of events) {\n await dispatchEvent(event);\n }\n } else {\n await Promise.all(events.map((event) => dispatchEvent(event)));\n }\n};\n","import { ulid } from 'ulid';\n\nimport { EventContext, getEventContext } from './context';\nimport { dispatchEvent, DispatchEventArgs } from './dispatch-event';\nimport { AppendEventResult } from './event-service';\n\nexport type MakePartial<T, O> = Omit<T, keyof O> & Partial<O>;\n\nexport type DispatchRecord<\n Options extends Partial<DispatchEventArgs> = Partial<DispatchEventArgs>,\n> = Record<\n string,\n (\n ...args: any[]\n ) =>\n | ValueOrFactoryRecord<MakePartial<DispatchEventArgs, Options>>\n | DispatchEventArgsFactory<Options>\n>;\n\nexport type ValueOrFactory<T> = T | ((context: EventContext) => T);\nexport type ValueOrFactoryRecord<T> = {\n [K in keyof T]: ValueOrFactory<T[K]>;\n};\n\nexport type DispatchEventArgsFactory<Options extends Partial<DispatchEventArgs>> = (\n context: EventContext,\n) => MakePartial<DispatchEventArgs, Options>;\n\nexport type DispatchRecordResponse<\n R extends DispatchRecord<O>,\n O extends Partial<DispatchEventArgs>,\n> = {\n [K in keyof R]: (...args: Parameters<R[K]>) => Promise<AppendEventResult>;\n};\n\nexport function createDispatch<DR extends DispatchRecord<O>, O extends Partial<DispatchEventArgs>>(\n record: DR,\n optionsOrFactory?: ValueOrFactoryRecord<O> | ((context: EventContext) => O),\n): DispatchRecordResponse<DR, O> {\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [\n key,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (...args: any[]) => {\n const eventId = `evt-${ulid()}`;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const result = value(...args);\n const context: EventContext = { eventId, ...getEventContext() };\n const executeValueFn = (value: any) =>\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call\n typeof value === 'function' ? value(context) : value;\n const parseResponse = (result: any) =>\n Object.fromEntries(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n Object.entries(result).map(([key, value]) => [key, executeValueFn(value)]),\n );\n Object.assign(\n context,\n typeof result === 'function' ? result(context) : parseResponse(result),\n );\n Object.assign(\n context,\n typeof optionsOrFactory === 'function'\n ? optionsOrFactory(context)\n : parseResponse(optionsOrFactory),\n );\n return dispatchEvent(context as DispatchEventArgs);\n },\n ]),\n ) as DispatchRecordResponse<DR, O>;\n}\n","import { logger } from '@auriclabs/logger';\nimport { SQSBatchResponse, SQSEvent } from 'aws-lambda';\n\nimport { setEventContext } from './context';\nimport { EventHandlers, EventRecord } from './types';\n\nexport interface CreateEventListenerOptions {\n debug?: boolean;\n}\n\nexport const createEventListener =\n (eventHandlers: EventHandlers, { debug = false }: CreateEventListenerOptions = {}) =>\n async (sqsEvent: SQSEvent) => {\n const response: SQSBatchResponse = {\n batchItemFailures: [],\n };\n let hasFailed = false;\n for (const record of sqsEvent.Records) {\n // skip the job if it has failed\n if (hasFailed) {\n response.batchItemFailures.push({\n itemIdentifier: record.messageId,\n });\n continue;\n }\n\n let event: EventRecord | undefined;\n try {\n event = JSON.parse(record.body) as EventRecord;\n if (debug) {\n logger.debug({ event }, 'Processing event');\n }\n let handler = eventHandlers[event.eventType];\n while (typeof handler === 'string') {\n handler = eventHandlers[handler];\n }\n if (typeof handler === 'function') {\n setEventContext({\n causationId: event.eventId,\n correlationId: event.correlationId,\n actorId: event.actorId,\n });\n await handler(event);\n }\n } catch (error) {\n hasFailed = true;\n logger.error({ error, event, body: record.body }, 'Error processing event');\n response.batchItemFailures.push({\n itemIdentifier: record.messageId,\n });\n }\n }\n return response;\n };\n","import { logger } from '@auriclabs/logger';\nimport { AttributeValue } from '@aws-sdk/client-dynamodb';\nimport { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';\nimport { SendMessageBatchCommand, SQSClient } from '@aws-sdk/client-sqs';\nimport { unmarshall } from '@aws-sdk/util-dynamodb';\nimport { DynamoDBStreamEvent } from 'aws-lambda';\nimport { kebabCase } from 'lodash-es';\n\nimport { AggregateHead, EventRecord } from './types';\n\nconst BATCH_SIZE = 10;\n\nexport interface CreateStreamHandlerConfig {\n busName: string;\n queueUrls: string[];\n}\n\n/**\n * Creates a Lambda handler for DynamoDB stream events.\n * Processes INSERT events from the event store table and forwards them to SQS queues and EventBridge.\n */\nexport function createStreamHandler(config: CreateStreamHandlerConfig) {\n const sqsClient = new SQSClient();\n const eventBridge = new EventBridgeClient({});\n\n function chunkArray<T>(array: T[], chunkSize: number): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < array.length; i += chunkSize) {\n chunks.push(array.slice(i, i + chunkSize));\n }\n return chunks;\n }\n\n async function sendToQueuesBatch(eventRecords: EventRecord[]) {\n await Promise.all(config.queueUrls.map((queue) => sendToQueueBatch(eventRecords, queue)));\n }\n\n async function sendToQueueBatch(eventRecords: EventRecord[], queue: string) {\n const batches = chunkArray(eventRecords, BATCH_SIZE);\n\n for (const batch of batches) {\n try {\n const entries = batch.map((eventRecord, index) => ({\n Id: `${eventRecord.eventId}-${index}`,\n MessageBody: JSON.stringify(eventRecord),\n MessageGroupId: eventRecord.aggregateId,\n MessageDeduplicationId: eventRecord.eventId,\n }));\n\n await sqsClient.send(\n new SendMessageBatchCommand({\n QueueUrl: queue,\n Entries: entries,\n }),\n );\n } catch (error) {\n logger.error({ error, batch, queue }, 'Error sending batch to queue');\n throw error;\n }\n }\n }\n\n async function sendToBusBatch(eventRecords: EventRecord[]) {\n const batches = chunkArray(eventRecords, BATCH_SIZE);\n\n for (const batch of batches) {\n try {\n const entries = batch.map((eventRecord) => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const source = eventRecord.source ?? kebabCase(eventRecord.aggregateType.split('.')[0]);\n return {\n Source: source,\n DetailType: eventRecord.eventType,\n Detail: JSON.stringify(eventRecord),\n EventBusName: config.busName,\n };\n });\n\n await eventBridge.send(\n new PutEventsCommand({\n Entries: entries,\n }),\n );\n } catch (error) {\n logger.error({ error, batch }, 'Error sending batch to bus');\n throw error;\n }\n }\n }\n\n return async (event: DynamoDBStreamEvent): Promise<void> => {\n const eventRecords = event.Records.filter((record) => record.eventName === 'INSERT')\n .map((record) => {\n try {\n const data = record.dynamodb?.NewImage;\n return unmarshall(data as Record<string, AttributeValue>) as EventRecord | AggregateHead;\n } catch (error) {\n logger.error({ error, record }, 'Error unmarshalling event record');\n return undefined;\n }\n })\n .filter((eventRecord): eventRecord is EventRecord => eventRecord?.itemType === 'event');\n\n if (eventRecords.length > 0) {\n await Promise.all([sendToBusBatch(eventRecords), sendToQueuesBatch(eventRecords)]);\n }\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../src/create-event-listener.ts"],"sourcesContent":["import { normalizePaginationResponse, PaginationResponse } from '@auriclabs/pagination';\nimport { DynamoDBClient, ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb';\nimport {\n DynamoDBDocumentClient,\n TransactWriteCommand,\n GetCommand,\n QueryCommand,\n} from '@aws-sdk/lib-dynamodb';\n\nimport type {\n EventRecord,\n AggregateHead,\n AggregatePK,\n EventId,\n AggregateId,\n AggregateType,\n EventSK,\n Source,\n} from './types';\n\nconst ddb = DynamoDBDocumentClient.from(new DynamoDBClient(), {\n marshallOptions: {\n removeUndefinedValues: true,\n },\n});\n\nconst pad = (n: number, w = 9): EventId => String(n).padStart(w, '0') as EventId;\nconst pkFor = (aggregateType: string, aggregateId: string): AggregatePK =>\n `AGG#${aggregateType}#${aggregateId}`;\n\nexport interface AppendArgs<P = unknown> {\n tenantId: string;\n aggregateType: string;\n aggregateId: string;\n source: string;\n /** Version you observed before appending (0 for brand new) */\n expectedVersion: number;\n /** Required for idempotent retries (e.g., the command id) */\n idempotencyKey: string;\n\n // Event properties (flattened)\n eventId: string; // ULID/UUID – must be stable across retries\n eventType: string;\n occurredAt?: string; // default: now ISO\n payload?: Readonly<P>;\n schemaVersion?: number; // optional but recommended\n\n // Optional metadata\n correlationId?: string;\n causationId?: string;\n actorId?: string;\n}\n\nexport interface AppendEventResult {\n pk: string;\n sk: string;\n version: number;\n}\n\nexport interface EventService {\n appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult>;\n getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined>;\n getEvent(\n aggregateType: string,\n aggregateId: string,\n version: number,\n ): Promise<EventRecord | undefined>;\n listEvents(params: {\n aggregateType: string;\n aggregateId: string;\n fromVersionExclusive?: number;\n toVersionInclusive?: number;\n limit?: number;\n }): Promise<PaginationResponse<EventRecord>>;\n}\n\nexport function createEventService(tableName: string): EventService {\n const TABLE = tableName;\n\n return {\n async appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult> {\n const {\n tenantId,\n aggregateType,\n aggregateId,\n expectedVersion,\n idempotencyKey,\n eventId,\n eventType,\n occurredAt,\n source,\n payload,\n schemaVersion,\n correlationId,\n causationId,\n actorId,\n } = args;\n\n const pk = pkFor(aggregateType, aggregateId);\n const nextVersion = expectedVersion + 1;\n const sk = `EVT#${pad(nextVersion)}` as EventSK;\n const nowIso = new Date().toISOString();\n const eventOccurredAt = occurredAt ?? nowIso;\n\n try {\n await ddb.send(\n new TransactWriteCommand({\n TransactItems: [\n {\n Update: {\n TableName: TABLE,\n Key: { pk, sk: 'HEAD' },\n UpdateExpression:\n 'SET currentVersion = :next, lastEventId = :eid, lastIdemKey = :idem, updatedAt = :now, aggregateId = if_not_exists(aggregateId, :aid), aggregateType = if_not_exists(aggregateType, :atype)',\n ConditionExpression:\n '(attribute_not_exists(currentVersion) AND :expected = :zero) ' +\n 'OR currentVersion = :expected ' +\n 'OR lastIdemKey = :idem',\n ExpressionAttributeValues: {\n ':zero': 0,\n ':expected': expectedVersion,\n ':next': nextVersion,\n ':eid': eventId,\n ':idem': idempotencyKey,\n ':now': nowIso,\n ':aid': aggregateId,\n ':atype': aggregateType,\n },\n },\n },\n {\n Put: {\n TableName: TABLE,\n Item: {\n pk,\n sk,\n itemType: 'event',\n source: source as Source,\n aggregateId: aggregateId as AggregateId,\n aggregateType: aggregateType as AggregateType,\n version: nextVersion,\n\n tenantId,\n\n eventId: eventId as EventId,\n eventType: eventType,\n schemaVersion: schemaVersion ?? 1,\n occurredAt: eventOccurredAt,\n\n correlationId,\n causationId,\n actorId,\n\n payload: payload as Readonly<unknown>,\n } satisfies EventRecord,\n ConditionExpression: 'attribute_not_exists(pk) OR eventId = :eid',\n ExpressionAttributeValues: { ':eid': eventId },\n },\n },\n ],\n }),\n );\n } catch (err) {\n if (err instanceof ConditionalCheckFailedException) {\n throw new Error(\n `OCC failed for aggregate ${aggregateType}/${aggregateId}: expectedVersion=${expectedVersion}`,\n );\n }\n throw err;\n }\n\n return { pk, sk, version: nextVersion };\n },\n\n async getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined> {\n const pk = pkFor(aggregateType, aggregateId);\n const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk, sk: 'HEAD' } }));\n return res.Item as AggregateHead | undefined;\n },\n\n async getEvent(\n aggregateType: string,\n aggregateId: string,\n version: number,\n ): Promise<EventRecord | undefined> {\n const pk = pkFor(aggregateType, aggregateId);\n const sk = `EVT#${pad(version)}`;\n const res = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk, sk } }));\n return res.Item as EventRecord | undefined;\n },\n\n async listEvents(params: {\n aggregateType: string;\n aggregateId: string;\n fromVersionExclusive?: number;\n toVersionInclusive?: number;\n limit?: number;\n }): Promise<PaginationResponse<EventRecord>> {\n const pk = pkFor(params.aggregateType, params.aggregateId);\n const fromSk =\n params.fromVersionExclusive != null\n ? `EVT#${pad(params.fromVersionExclusive + 1)}`\n : 'EVT#000000000';\n const toSk =\n params.toVersionInclusive != null\n ? `EVT#${pad(params.toVersionInclusive)}`\n : 'EVT#999999999';\n\n const res = await ddb.send(\n new QueryCommand({\n TableName: TABLE,\n KeyConditionExpression: 'pk = :pk AND sk BETWEEN :from AND :to',\n ExpressionAttributeValues: {\n ':pk': pk,\n ':from': fromSk,\n ':to': toSk,\n },\n ScanIndexForward: true,\n Limit: params.limit,\n }),\n );\n\n return normalizePaginationResponse({\n data: (res.Items ?? []) as EventRecord[],\n cursor: res.LastEvaluatedKey && (res.LastEvaluatedKey as { pk: string; sk: string }).sk,\n });\n },\n };\n}\n","import { createEventService, EventService } from './event-service';\n\nlet _eventService: EventService | undefined;\n\nexport function initEvents(config: { tableName: string }): void {\n _eventService = createEventService(config.tableName);\n}\n\nexport function getEventService(): EventService {\n if (!_eventService) {\n throw new Error('Call initEvents() before using events');\n }\n return _eventService;\n}\n","import { AppendArgs } from './event-service';\n\nexport type EventContext = Partial<AppendArgs>;\n\nlet context: EventContext = {};\n\nexport const setEventContext = (newContext: EventContext) => {\n context = { ...newContext };\n};\n\nexport const getEventContext = () => context;\n\nexport const resetEventContext = () => {\n context = {};\n};\n\nexport const appendEventContext = (event: EventContext) => {\n context = { ...context, ...event };\n};\n","import { retry } from '@auriclabs/api-core';\nimport { logger } from '@auriclabs/logger';\nimport { ulid } from 'ulid';\n\nimport { getEventContext } from './context';\nimport { AppendArgs, AppendEventResult } from './event-service';\nimport { getEventService } from './init';\n\nexport type DispatchEventArgs = Omit<\n AppendArgs,\n 'eventId' | 'expectedVersion' | 'schemaVersion' | 'occurredAt' | 'idempotencyKey'\n> &\n Partial<Pick<AppendArgs, 'idempotencyKey' | 'eventId'>>;\n\nexport const dispatchEvent = async (event: DispatchEventArgs): Promise<AppendEventResult> => {\n const eventService = getEventService();\n const eventId = event.eventId ?? `evt-${ulid()}`;\n const occurredAt = new Date().toISOString();\n const idempotencyKey = event.idempotencyKey ?? eventId;\n\n return retry(async () => {\n const head = await eventService.getHead(event.aggregateType, event.aggregateId);\n logger.debug({ event }, 'Dispatching event');\n return eventService.appendEvent({\n ...getEventContext(),\n ...event,\n eventId,\n expectedVersion: head?.currentVersion ?? 0,\n schemaVersion: 1,\n occurredAt,\n idempotencyKey,\n });\n });\n};\n","import { dispatchEvent, DispatchEventArgs } from './dispatch-event';\n\nexport interface DispatchEventsArgs {\n inOrder?: boolean;\n}\n\nexport const dispatchEvents = async (\n events: DispatchEventArgs[],\n { inOrder = false }: DispatchEventsArgs = {},\n) => {\n if (inOrder) {\n for (const event of events) {\n await dispatchEvent(event);\n }\n } else {\n await Promise.all(events.map((event) => dispatchEvent(event)));\n }\n};\n","import { ulid } from 'ulid';\n\nimport { EventContext, getEventContext } from './context';\nimport { dispatchEvent, DispatchEventArgs } from './dispatch-event';\nimport { AppendEventResult } from './event-service';\n\nexport type MakePartial<T, O> = Omit<T, keyof O> & Partial<O>;\n\nexport type DispatchRecord<\n Options extends Partial<DispatchEventArgs> = Partial<DispatchEventArgs>,\n> = Record<\n string,\n (\n ...args: any[]\n ) =>\n | ValueOrFactoryRecord<MakePartial<DispatchEventArgs, Options>>\n | DispatchEventArgsFactory<Options>\n>;\n\nexport type ValueOrFactory<T> = T | ((context: EventContext) => T);\nexport type ValueOrFactoryRecord<T> = {\n [K in keyof T]: ValueOrFactory<T[K]>;\n};\n\nexport type DispatchEventArgsFactory<Options extends Partial<DispatchEventArgs>> = (\n context: EventContext,\n) => MakePartial<DispatchEventArgs, Options>;\n\nexport type DispatchRecordResponse<\n R extends DispatchRecord<O>,\n O extends Partial<DispatchEventArgs>,\n> = {\n [K in keyof R]: (...args: Parameters<R[K]>) => Promise<AppendEventResult>;\n};\n\nexport function createDispatch<DR extends DispatchRecord<O>, O extends Partial<DispatchEventArgs>>(\n record: DR,\n optionsOrFactory?: ValueOrFactoryRecord<O> | ((context: EventContext) => O),\n): DispatchRecordResponse<DR, O> {\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [\n key,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (...args: any[]) => {\n const eventId = `evt-${ulid()}`;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const result = value(...args);\n const context: EventContext = { eventId, ...getEventContext() };\n const executeValueFn = (value: any) =>\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call\n typeof value === 'function' ? value(context) : value;\n const parseResponse = (result: any) =>\n Object.fromEntries(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n Object.entries(result).map(([key, value]) => [key, executeValueFn(value)]),\n );\n Object.assign(\n context,\n typeof result === 'function' ? result(context) : parseResponse(result),\n );\n Object.assign(\n context,\n typeof optionsOrFactory === 'function'\n ? optionsOrFactory(context)\n : parseResponse(optionsOrFactory),\n );\n return dispatchEvent(context as DispatchEventArgs);\n },\n ]),\n ) as DispatchRecordResponse<DR, O>;\n}\n","import { logger } from '@auriclabs/logger';\nimport { SQSBatchResponse, SQSEvent } from 'aws-lambda';\n\nimport { setEventContext } from './context';\nimport { EventHandlers, EventRecord } from './types';\n\nexport interface CreateEventListenerOptions {\n debug?: boolean;\n}\n\nexport const createEventListener =\n (eventHandlers: EventHandlers, { debug = false }: CreateEventListenerOptions = {}) =>\n async (sqsEvent: SQSEvent) => {\n const response: SQSBatchResponse = {\n batchItemFailures: [],\n };\n let hasFailed = false;\n for (const record of sqsEvent.Records) {\n // skip the job if it has failed\n if (hasFailed) {\n response.batchItemFailures.push({\n itemIdentifier: record.messageId,\n });\n continue;\n }\n\n let event: EventRecord | undefined;\n try {\n event = JSON.parse(record.body) as EventRecord;\n if (debug) {\n logger.debug({ event }, 'Processing event');\n }\n let handler = eventHandlers[event.eventType];\n while (typeof handler === 'string') {\n handler = eventHandlers[handler];\n }\n if (typeof handler === 'function') {\n setEventContext({\n causationId: event.eventId,\n correlationId: event.correlationId,\n actorId: event.actorId,\n });\n await handler(event);\n }\n } catch (error) {\n hasFailed = true;\n logger.error({ error, event, body: record.body }, 'Error processing event');\n response.batchItemFailures.push({\n itemIdentifier: record.messageId,\n });\n }\n }\n return response;\n };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { n as SQSEvent, r as DynamoDBStreamEvent, t as SQSBatchResponse } from "./index.cjs";
|
|
2
|
+
import { PaginationResponse } from "@auriclabs/pagination";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
type Brand<T, B extends string> = T & {
|
|
6
|
+
readonly __brand: B;
|
|
7
|
+
};
|
|
8
|
+
type Source = Brand<string, 'Source'>;
|
|
9
|
+
type AggregateId = Brand<string, 'AggregateId'>;
|
|
10
|
+
type AggregateType = Brand<string, 'AggregateType'>;
|
|
11
|
+
type EventId = Brand<string, 'EventId'>;
|
|
12
|
+
type AggregatePK = `AGG#${string}#${string}`;
|
|
13
|
+
type EventSK = `EVT#${string}` | 'HEAD';
|
|
14
|
+
type ItemType = 'event' | 'head';
|
|
15
|
+
interface EventRecord<P = unknown> {
|
|
16
|
+
/** DynamoDB keys */
|
|
17
|
+
pk: AggregatePK;
|
|
18
|
+
sk: EventSK;
|
|
19
|
+
itemType: Extract<ItemType, 'event'>;
|
|
20
|
+
/** Aggregate routing */
|
|
21
|
+
source: Source;
|
|
22
|
+
aggregateId: AggregateId;
|
|
23
|
+
aggregateType: AggregateType;
|
|
24
|
+
version: number;
|
|
25
|
+
/** Tenant isolation */
|
|
26
|
+
tenantId: string;
|
|
27
|
+
/** Event identity & semantics */
|
|
28
|
+
eventId: EventId;
|
|
29
|
+
eventType: string;
|
|
30
|
+
schemaVersion?: number;
|
|
31
|
+
occurredAt: string;
|
|
32
|
+
/** Tracing (optional) */
|
|
33
|
+
correlationId?: string;
|
|
34
|
+
causationId?: string;
|
|
35
|
+
actorId?: string;
|
|
36
|
+
/** Domain payload */
|
|
37
|
+
payload: Readonly<P>;
|
|
38
|
+
}
|
|
39
|
+
interface AggregateHead {
|
|
40
|
+
/** DynamoDB keys */
|
|
41
|
+
pk: AggregatePK;
|
|
42
|
+
sk: 'HEAD';
|
|
43
|
+
itemType: Extract<ItemType, 'head'>;
|
|
44
|
+
/** Aggregate identity */
|
|
45
|
+
aggregateId: AggregateId;
|
|
46
|
+
aggregateType: AggregateType;
|
|
47
|
+
/** Version tracking */
|
|
48
|
+
currentVersion: number;
|
|
49
|
+
/** Idempotency/debug */
|
|
50
|
+
lastEventId?: EventId;
|
|
51
|
+
lastIdemKey?: string;
|
|
52
|
+
updatedAt: string;
|
|
53
|
+
}
|
|
54
|
+
type EventHandlers = Record<string, ((event: EventRecord<any>) => Promise<void> | void) | string>;
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/event-service.d.ts
|
|
57
|
+
interface AppendArgs<P = unknown> {
|
|
58
|
+
tenantId: string;
|
|
59
|
+
aggregateType: string;
|
|
60
|
+
aggregateId: string;
|
|
61
|
+
source: string;
|
|
62
|
+
/** Version you observed before appending (0 for brand new) */
|
|
63
|
+
expectedVersion: number;
|
|
64
|
+
/** Required for idempotent retries (e.g., the command id) */
|
|
65
|
+
idempotencyKey: string;
|
|
66
|
+
eventId: string;
|
|
67
|
+
eventType: string;
|
|
68
|
+
occurredAt?: string;
|
|
69
|
+
payload?: Readonly<P>;
|
|
70
|
+
schemaVersion?: number;
|
|
71
|
+
correlationId?: string;
|
|
72
|
+
causationId?: string;
|
|
73
|
+
actorId?: string;
|
|
74
|
+
}
|
|
75
|
+
interface AppendEventResult {
|
|
76
|
+
pk: string;
|
|
77
|
+
sk: string;
|
|
78
|
+
version: number;
|
|
79
|
+
}
|
|
80
|
+
interface EventService {
|
|
81
|
+
appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult>;
|
|
82
|
+
getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined>;
|
|
83
|
+
getEvent(aggregateType: string, aggregateId: string, version: number): Promise<EventRecord | undefined>;
|
|
84
|
+
listEvents(params: {
|
|
85
|
+
aggregateType: string;
|
|
86
|
+
aggregateId: string;
|
|
87
|
+
fromVersionExclusive?: number;
|
|
88
|
+
toVersionInclusive?: number;
|
|
89
|
+
limit?: number;
|
|
90
|
+
}): Promise<PaginationResponse<EventRecord>>;
|
|
91
|
+
}
|
|
92
|
+
declare function createEventService(tableName: string): EventService;
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/init.d.ts
|
|
95
|
+
declare function initEvents(config: {
|
|
96
|
+
tableName: string;
|
|
97
|
+
}): void;
|
|
98
|
+
declare function getEventService(): EventService;
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/context.d.ts
|
|
101
|
+
type EventContext = Partial<AppendArgs>;
|
|
102
|
+
declare const setEventContext: (newContext: EventContext) => void;
|
|
103
|
+
declare const getEventContext: () => Partial<AppendArgs<unknown>>;
|
|
104
|
+
declare const resetEventContext: () => void;
|
|
105
|
+
declare const appendEventContext: (event: EventContext) => void;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/dispatch-event.d.ts
|
|
108
|
+
type DispatchEventArgs = Omit<AppendArgs, 'eventId' | 'expectedVersion' | 'schemaVersion' | 'occurredAt' | 'idempotencyKey'> & Partial<Pick<AppendArgs, 'idempotencyKey' | 'eventId'>>;
|
|
109
|
+
declare const dispatchEvent: (event: DispatchEventArgs) => Promise<AppendEventResult>;
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/dispatch-events.d.ts
|
|
112
|
+
interface DispatchEventsArgs {
|
|
113
|
+
inOrder?: boolean;
|
|
114
|
+
}
|
|
115
|
+
declare const dispatchEvents: (events: DispatchEventArgs[], {
|
|
116
|
+
inOrder
|
|
117
|
+
}?: DispatchEventsArgs) => Promise<void>;
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/create-dispatch.d.ts
|
|
120
|
+
type MakePartial<T, O> = Omit<T, keyof O> & Partial<O>;
|
|
121
|
+
type DispatchRecord<Options extends Partial<DispatchEventArgs> = Partial<DispatchEventArgs>> = Record<string, (...args: any[]) => ValueOrFactoryRecord<MakePartial<DispatchEventArgs, Options>> | DispatchEventArgsFactory<Options>>;
|
|
122
|
+
type ValueOrFactory<T> = T | ((context: EventContext) => T);
|
|
123
|
+
type ValueOrFactoryRecord<T> = { [K in keyof T]: ValueOrFactory<T[K]> };
|
|
124
|
+
type DispatchEventArgsFactory<Options extends Partial<DispatchEventArgs>> = (context: EventContext) => MakePartial<DispatchEventArgs, Options>;
|
|
125
|
+
type DispatchRecordResponse<R extends DispatchRecord<O>, O extends Partial<DispatchEventArgs>> = { [K in keyof R]: (...args: Parameters<R[K]>) => Promise<AppendEventResult> };
|
|
126
|
+
declare function createDispatch<DR extends DispatchRecord<O>, O extends Partial<DispatchEventArgs>>(record: DR, optionsOrFactory?: ValueOrFactoryRecord<O> | ((context: EventContext) => O)): DispatchRecordResponse<DR, O>;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/create-event-listener.d.ts
|
|
129
|
+
interface CreateEventListenerOptions {
|
|
130
|
+
debug?: boolean;
|
|
131
|
+
}
|
|
132
|
+
declare const createEventListener: (eventHandlers: EventHandlers, {
|
|
133
|
+
debug
|
|
134
|
+
}?: CreateEventListenerOptions) => (sqsEvent: SQSEvent) => Promise<SQSBatchResponse>;
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/stream-handler.d.ts
|
|
137
|
+
interface CreateStreamHandlerConfig {
|
|
138
|
+
busName?: string;
|
|
139
|
+
queueUrls: string[];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Creates a Lambda handler for DynamoDB stream events.
|
|
143
|
+
* Processes INSERT events from the event store table and forwards them to SQS queues and EventBridge.
|
|
144
|
+
*/
|
|
145
|
+
declare function createStreamHandler(config: CreateStreamHandlerConfig): (event: DynamoDBStreamEvent) => Promise<void>;
|
|
146
|
+
//#endregion
|
|
147
|
+
export { AggregateHead, AggregateId, AggregatePK, AggregateType, AppendArgs, AppendEventResult, Brand, CreateEventListenerOptions, CreateStreamHandlerConfig, DispatchEventArgs, DispatchEventArgsFactory, DispatchEventsArgs, DispatchRecord, DispatchRecordResponse, EventContext, EventHandlers, EventId, EventRecord, EventSK, EventService, ItemType, MakePartial, Source, ValueOrFactory, ValueOrFactoryRecord, appendEventContext, createDispatch, createEventListener, createEventService, createStreamHandler, dispatchEvent, dispatchEvents, getEventContext, getEventService, initEvents, resetEventContext, setEventContext };
|
|
148
|
+
//# sourceMappingURL=index2.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index2.d.cts","names":[],"sources":["../src/types.ts","../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../src/create-event-listener.ts","../src/stream-handler.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { n as SQSEvent, r as DynamoDBStreamEvent, t as SQSBatchResponse } from "./index.mjs";
|
|
2
|
+
import { PaginationResponse } from "@auriclabs/pagination";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
type Brand<T, B extends string> = T & {
|
|
6
|
+
readonly __brand: B;
|
|
7
|
+
};
|
|
8
|
+
type Source = Brand<string, 'Source'>;
|
|
9
|
+
type AggregateId = Brand<string, 'AggregateId'>;
|
|
10
|
+
type AggregateType = Brand<string, 'AggregateType'>;
|
|
11
|
+
type EventId = Brand<string, 'EventId'>;
|
|
12
|
+
type AggregatePK = `AGG#${string}#${string}`;
|
|
13
|
+
type EventSK = `EVT#${string}` | 'HEAD';
|
|
14
|
+
type ItemType = 'event' | 'head';
|
|
15
|
+
interface EventRecord<P = unknown> {
|
|
16
|
+
/** DynamoDB keys */
|
|
17
|
+
pk: AggregatePK;
|
|
18
|
+
sk: EventSK;
|
|
19
|
+
itemType: Extract<ItemType, 'event'>;
|
|
20
|
+
/** Aggregate routing */
|
|
21
|
+
source: Source;
|
|
22
|
+
aggregateId: AggregateId;
|
|
23
|
+
aggregateType: AggregateType;
|
|
24
|
+
version: number;
|
|
25
|
+
/** Tenant isolation */
|
|
26
|
+
tenantId: string;
|
|
27
|
+
/** Event identity & semantics */
|
|
28
|
+
eventId: EventId;
|
|
29
|
+
eventType: string;
|
|
30
|
+
schemaVersion?: number;
|
|
31
|
+
occurredAt: string;
|
|
32
|
+
/** Tracing (optional) */
|
|
33
|
+
correlationId?: string;
|
|
34
|
+
causationId?: string;
|
|
35
|
+
actorId?: string;
|
|
36
|
+
/** Domain payload */
|
|
37
|
+
payload: Readonly<P>;
|
|
38
|
+
}
|
|
39
|
+
interface AggregateHead {
|
|
40
|
+
/** DynamoDB keys */
|
|
41
|
+
pk: AggregatePK;
|
|
42
|
+
sk: 'HEAD';
|
|
43
|
+
itemType: Extract<ItemType, 'head'>;
|
|
44
|
+
/** Aggregate identity */
|
|
45
|
+
aggregateId: AggregateId;
|
|
46
|
+
aggregateType: AggregateType;
|
|
47
|
+
/** Version tracking */
|
|
48
|
+
currentVersion: number;
|
|
49
|
+
/** Idempotency/debug */
|
|
50
|
+
lastEventId?: EventId;
|
|
51
|
+
lastIdemKey?: string;
|
|
52
|
+
updatedAt: string;
|
|
53
|
+
}
|
|
54
|
+
type EventHandlers = Record<string, ((event: EventRecord<any>) => Promise<void> | void) | string>;
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/event-service.d.ts
|
|
57
|
+
interface AppendArgs<P = unknown> {
|
|
58
|
+
tenantId: string;
|
|
59
|
+
aggregateType: string;
|
|
60
|
+
aggregateId: string;
|
|
61
|
+
source: string;
|
|
62
|
+
/** Version you observed before appending (0 for brand new) */
|
|
63
|
+
expectedVersion: number;
|
|
64
|
+
/** Required for idempotent retries (e.g., the command id) */
|
|
65
|
+
idempotencyKey: string;
|
|
66
|
+
eventId: string;
|
|
67
|
+
eventType: string;
|
|
68
|
+
occurredAt?: string;
|
|
69
|
+
payload?: Readonly<P>;
|
|
70
|
+
schemaVersion?: number;
|
|
71
|
+
correlationId?: string;
|
|
72
|
+
causationId?: string;
|
|
73
|
+
actorId?: string;
|
|
74
|
+
}
|
|
75
|
+
interface AppendEventResult {
|
|
76
|
+
pk: string;
|
|
77
|
+
sk: string;
|
|
78
|
+
version: number;
|
|
79
|
+
}
|
|
80
|
+
interface EventService {
|
|
81
|
+
appendEvent<P = unknown>(args: AppendArgs<P>): Promise<AppendEventResult>;
|
|
82
|
+
getHead(aggregateType: string, aggregateId: string): Promise<AggregateHead | undefined>;
|
|
83
|
+
getEvent(aggregateType: string, aggregateId: string, version: number): Promise<EventRecord | undefined>;
|
|
84
|
+
listEvents(params: {
|
|
85
|
+
aggregateType: string;
|
|
86
|
+
aggregateId: string;
|
|
87
|
+
fromVersionExclusive?: number;
|
|
88
|
+
toVersionInclusive?: number;
|
|
89
|
+
limit?: number;
|
|
90
|
+
}): Promise<PaginationResponse<EventRecord>>;
|
|
91
|
+
}
|
|
92
|
+
declare function createEventService(tableName: string): EventService;
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/init.d.ts
|
|
95
|
+
declare function initEvents(config: {
|
|
96
|
+
tableName: string;
|
|
97
|
+
}): void;
|
|
98
|
+
declare function getEventService(): EventService;
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/context.d.ts
|
|
101
|
+
type EventContext = Partial<AppendArgs>;
|
|
102
|
+
declare const setEventContext: (newContext: EventContext) => void;
|
|
103
|
+
declare const getEventContext: () => Partial<AppendArgs<unknown>>;
|
|
104
|
+
declare const resetEventContext: () => void;
|
|
105
|
+
declare const appendEventContext: (event: EventContext) => void;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/dispatch-event.d.ts
|
|
108
|
+
type DispatchEventArgs = Omit<AppendArgs, 'eventId' | 'expectedVersion' | 'schemaVersion' | 'occurredAt' | 'idempotencyKey'> & Partial<Pick<AppendArgs, 'idempotencyKey' | 'eventId'>>;
|
|
109
|
+
declare const dispatchEvent: (event: DispatchEventArgs) => Promise<AppendEventResult>;
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/dispatch-events.d.ts
|
|
112
|
+
interface DispatchEventsArgs {
|
|
113
|
+
inOrder?: boolean;
|
|
114
|
+
}
|
|
115
|
+
declare const dispatchEvents: (events: DispatchEventArgs[], {
|
|
116
|
+
inOrder
|
|
117
|
+
}?: DispatchEventsArgs) => Promise<void>;
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/create-dispatch.d.ts
|
|
120
|
+
type MakePartial<T, O> = Omit<T, keyof O> & Partial<O>;
|
|
121
|
+
type DispatchRecord<Options extends Partial<DispatchEventArgs> = Partial<DispatchEventArgs>> = Record<string, (...args: any[]) => ValueOrFactoryRecord<MakePartial<DispatchEventArgs, Options>> | DispatchEventArgsFactory<Options>>;
|
|
122
|
+
type ValueOrFactory<T> = T | ((context: EventContext) => T);
|
|
123
|
+
type ValueOrFactoryRecord<T> = { [K in keyof T]: ValueOrFactory<T[K]> };
|
|
124
|
+
type DispatchEventArgsFactory<Options extends Partial<DispatchEventArgs>> = (context: EventContext) => MakePartial<DispatchEventArgs, Options>;
|
|
125
|
+
type DispatchRecordResponse<R extends DispatchRecord<O>, O extends Partial<DispatchEventArgs>> = { [K in keyof R]: (...args: Parameters<R[K]>) => Promise<AppendEventResult> };
|
|
126
|
+
declare function createDispatch<DR extends DispatchRecord<O>, O extends Partial<DispatchEventArgs>>(record: DR, optionsOrFactory?: ValueOrFactoryRecord<O> | ((context: EventContext) => O)): DispatchRecordResponse<DR, O>;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/create-event-listener.d.ts
|
|
129
|
+
interface CreateEventListenerOptions {
|
|
130
|
+
debug?: boolean;
|
|
131
|
+
}
|
|
132
|
+
declare const createEventListener: (eventHandlers: EventHandlers, {
|
|
133
|
+
debug
|
|
134
|
+
}?: CreateEventListenerOptions) => (sqsEvent: SQSEvent) => Promise<SQSBatchResponse>;
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/stream-handler.d.ts
|
|
137
|
+
interface CreateStreamHandlerConfig {
|
|
138
|
+
busName?: string;
|
|
139
|
+
queueUrls: string[];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Creates a Lambda handler for DynamoDB stream events.
|
|
143
|
+
* Processes INSERT events from the event store table and forwards them to SQS queues and EventBridge.
|
|
144
|
+
*/
|
|
145
|
+
declare function createStreamHandler(config: CreateStreamHandlerConfig): (event: DynamoDBStreamEvent) => Promise<void>;
|
|
146
|
+
//#endregion
|
|
147
|
+
export { AggregateHead, AggregateId, AggregatePK, AggregateType, AppendArgs, AppendEventResult, Brand, CreateEventListenerOptions, CreateStreamHandlerConfig, DispatchEventArgs, DispatchEventArgsFactory, DispatchEventsArgs, DispatchRecord, DispatchRecordResponse, EventContext, EventHandlers, EventId, EventRecord, EventSK, EventService, ItemType, MakePartial, Source, ValueOrFactory, ValueOrFactoryRecord, appendEventContext, createDispatch, createEventListener, createEventService, createStreamHandler, dispatchEvent, dispatchEvents, getEventContext, getEventService, initEvents, resetEventContext, setEventContext };
|
|
148
|
+
//# sourceMappingURL=index2.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index2.d.mts","names":[],"sources":["../src/types.ts","../src/event-service.ts","../src/init.ts","../src/context.ts","../src/dispatch-event.ts","../src/dispatch-events.ts","../src/create-dispatch.ts","../src/create-event-listener.ts","../src/stream-handler.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
let _auriclabs_logger = require("@auriclabs/logger");
|
|
2
|
+
let _aws_sdk_client_eventbridge = require("@aws-sdk/client-eventbridge");
|
|
3
|
+
let _aws_sdk_client_sqs = require("@aws-sdk/client-sqs");
|
|
4
|
+
let _aws_sdk_util_dynamodb = require("@aws-sdk/util-dynamodb");
|
|
5
|
+
let lodash_es = require("lodash-es");
|
|
6
|
+
//#region src/stream-handler.ts
|
|
7
|
+
const BATCH_SIZE = 10;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a Lambda handler for DynamoDB stream events.
|
|
10
|
+
* Processes INSERT events from the event store table and forwards them to SQS queues and EventBridge.
|
|
11
|
+
*/
|
|
12
|
+
function createStreamHandler(config) {
|
|
13
|
+
const sqsClient = new _aws_sdk_client_sqs.SQSClient();
|
|
14
|
+
const eventBridge = new _aws_sdk_client_eventbridge.EventBridgeClient({});
|
|
15
|
+
function chunkArray(array, chunkSize) {
|
|
16
|
+
const chunks = [];
|
|
17
|
+
for (let i = 0; i < array.length; i += chunkSize) chunks.push(array.slice(i, i + chunkSize));
|
|
18
|
+
return chunks;
|
|
19
|
+
}
|
|
20
|
+
async function sendToQueuesBatch(eventRecords) {
|
|
21
|
+
await Promise.all(config.queueUrls.map((queue) => sendToQueueBatch(eventRecords, queue)));
|
|
22
|
+
}
|
|
23
|
+
async function sendToQueueBatch(eventRecords, queue) {
|
|
24
|
+
const batches = chunkArray(eventRecords, BATCH_SIZE);
|
|
25
|
+
for (const batch of batches) try {
|
|
26
|
+
const entries = batch.map((eventRecord, index) => ({
|
|
27
|
+
Id: `${eventRecord.eventId}-${index}`,
|
|
28
|
+
MessageBody: JSON.stringify(eventRecord),
|
|
29
|
+
MessageGroupId: eventRecord.aggregateId,
|
|
30
|
+
MessageDeduplicationId: eventRecord.eventId
|
|
31
|
+
}));
|
|
32
|
+
await sqsClient.send(new _aws_sdk_client_sqs.SendMessageBatchCommand({
|
|
33
|
+
QueueUrl: queue,
|
|
34
|
+
Entries: entries
|
|
35
|
+
}));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
_auriclabs_logger.logger.error({
|
|
38
|
+
error,
|
|
39
|
+
batch,
|
|
40
|
+
queue
|
|
41
|
+
}, "Error sending batch to queue");
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function sendToBusBatch(eventRecords) {
|
|
46
|
+
const batches = chunkArray(eventRecords, BATCH_SIZE);
|
|
47
|
+
for (const batch of batches) try {
|
|
48
|
+
const entries = batch.map((eventRecord) => {
|
|
49
|
+
return {
|
|
50
|
+
Source: eventRecord.source ?? (0, lodash_es.kebabCase)(eventRecord.aggregateType.split(".")[0]),
|
|
51
|
+
DetailType: eventRecord.eventType,
|
|
52
|
+
Detail: JSON.stringify(eventRecord),
|
|
53
|
+
EventBusName: config.busName
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
await eventBridge.send(new _aws_sdk_client_eventbridge.PutEventsCommand({ Entries: entries }));
|
|
57
|
+
} catch (error) {
|
|
58
|
+
_auriclabs_logger.logger.error({
|
|
59
|
+
error,
|
|
60
|
+
batch
|
|
61
|
+
}, "Error sending batch to bus");
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return async (event) => {
|
|
66
|
+
const eventRecords = event.Records.filter((record) => record.eventName === "INSERT").map((record) => {
|
|
67
|
+
try {
|
|
68
|
+
const data = record.dynamodb?.NewImage;
|
|
69
|
+
return (0, _aws_sdk_util_dynamodb.unmarshall)(data);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
_auriclabs_logger.logger.error({
|
|
72
|
+
error,
|
|
73
|
+
record
|
|
74
|
+
}, "Error unmarshalling event record");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}).filter((eventRecord) => eventRecord?.itemType === "event");
|
|
78
|
+
if (eventRecords.length > 0) {
|
|
79
|
+
const tasks = [sendToQueuesBatch(eventRecords)];
|
|
80
|
+
if (config.busName) tasks.push(sendToBusBatch(eventRecords));
|
|
81
|
+
await Promise.all(tasks);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
Object.defineProperty(exports, "createStreamHandler", {
|
|
87
|
+
enumerable: true,
|
|
88
|
+
get: function() {
|
|
89
|
+
return createStreamHandler;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/stream-handler.entry.ts
|
|
3
|
+
const handler = require("./stream-handler.cjs").createStreamHandler({
|
|
4
|
+
busName: process.env.EVENT_BUS_NAME,
|
|
5
|
+
queueUrls: JSON.parse(process.env.QUEUE_URL_LIST ?? "[]")
|
|
6
|
+
});
|
|
7
|
+
//#endregion
|
|
8
|
+
exports.handler = handler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-handler.entry.d.cts","names":[],"sources":["../src/stream-handler.entry.ts"],"mappings":";;;cAEa,OAAA,GAAO,KAAA,EAGlB,mBAAA,KAHkB,OAAA"}
|