@othree.io/chisel-sdk 4.0.0 → 5.0.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/README.md +207 -1
- package/lib/cjs/aws/index.d.ts.map +1 -0
- package/lib/cjs/aws/index.js.map +1 -0
- package/lib/cjs/aws/read.d.ts +44 -0
- package/lib/cjs/aws/read.d.ts.map +1 -0
- package/lib/{aws → cjs/aws}/read.js +65 -48
- package/lib/cjs/aws/read.js.map +1 -0
- package/lib/cjs/aws/write.d.ts +23 -0
- package/lib/cjs/aws/write.d.ts.map +1 -0
- package/lib/{aws → cjs/aws}/write.js +56 -34
- package/lib/cjs/aws/write.js.map +1 -0
- package/lib/cjs/iac/index.d.ts +28 -0
- package/lib/cjs/iac/index.d.ts.map +1 -0
- package/lib/{iac → cjs/iac}/index.js +23 -8
- package/lib/cjs/iac/index.js.map +1 -0
- package/lib/cjs/iac/read.d.ts +50 -0
- package/lib/cjs/iac/read.d.ts.map +1 -0
- package/lib/{iac → cjs/iac}/read.js +58 -32
- package/lib/cjs/iac/read.js.map +1 -0
- package/lib/cjs/iac/shared.d.ts +14 -0
- package/lib/cjs/iac/shared.d.ts.map +1 -0
- package/lib/{iac → cjs/iac}/shared.js +12 -8
- package/lib/cjs/iac/shared.js.map +1 -0
- package/lib/cjs/iac/types.d.ts +28 -0
- package/lib/cjs/iac/types.d.ts.map +1 -0
- package/lib/cjs/iac/types.js +26 -0
- package/lib/cjs/iac/types.js.map +1 -0
- package/lib/cjs/iac/write.d.ts +32 -0
- package/lib/cjs/iac/write.d.ts.map +1 -0
- package/lib/{iac → cjs/iac}/write.js +40 -17
- package/lib/cjs/iac/write.js.map +1 -0
- package/lib/cjs/index.d.ts.map +1 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/esm/aws/index.js +3 -0
- package/lib/esm/aws/index.js.map +1 -0
- package/lib/esm/aws/read.js +119 -0
- package/lib/esm/aws/read.js.map +1 -0
- package/lib/esm/aws/write.js +99 -0
- package/lib/esm/aws/write.js.map +1 -0
- package/lib/esm/iac/index.js +66 -0
- package/lib/esm/iac/index.js.map +1 -0
- package/lib/esm/iac/read.js +168 -0
- package/lib/esm/iac/read.js.map +1 -0
- package/lib/esm/iac/shared.js +32 -0
- package/lib/esm/iac/shared.js.map +1 -0
- package/lib/esm/iac/types.js +23 -0
- package/lib/esm/iac/types.js.map +1 -0
- package/lib/esm/iac/write.js +92 -0
- package/lib/esm/iac/write.js.map +1 -0
- package/lib/esm/index.js +3 -0
- package/lib/esm/index.js.map +1 -0
- package/package.json +50 -21
- package/.gitlab-ci.yml +0 -29
- package/lib/aws/index.d.ts.map +0 -1
- package/lib/aws/index.js.map +0 -1
- package/lib/aws/read.d.ts +0 -35
- package/lib/aws/read.d.ts.map +0 -1
- package/lib/aws/read.js.map +0 -1
- package/lib/aws/write.d.ts +0 -24
- package/lib/aws/write.d.ts.map +0 -1
- package/lib/aws/write.js.map +0 -1
- package/lib/iac/index.d.ts +0 -24
- package/lib/iac/index.d.ts.map +0 -1
- package/lib/iac/index.js.map +0 -1
- package/lib/iac/read.d.ts +0 -48
- package/lib/iac/read.d.ts.map +0 -1
- package/lib/iac/read.js.map +0 -1
- package/lib/iac/shared.d.ts +0 -12
- package/lib/iac/shared.d.ts.map +0 -1
- package/lib/iac/shared.js.map +0 -1
- package/lib/iac/types.d.ts +0 -13
- package/lib/iac/types.d.ts.map +0 -1
- package/lib/iac/types.js +0 -14
- package/lib/iac/types.js.map +0 -1
- package/lib/iac/write.d.ts +0 -29
- package/lib/iac/write.d.ts.map +0 -1
- package/lib/iac/write.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/release.config.js +0 -4
- /package/lib/{aws → cjs/aws}/index.d.ts +0 -0
- /package/lib/{aws → cjs/aws}/index.js +0 -0
- /package/lib/{index.d.ts → cjs/index.d.ts} +0 -0
- /package/lib/{index.js → cjs/index.js} +0 -0
package/README.md
CHANGED
|
@@ -1 +1,207 @@
|
|
|
1
|
-
# chisel-sdk
|
|
1
|
+
# @othree.io/chisel-sdk
|
|
2
|
+
|
|
3
|
+
SDK for building event-sourced microservices with [Chisel](https://www.npmjs.com/package/@othree.io/chisel) on AWS. Provides pre-wired write-side actors, read-side projections, and CDK infrastructure stacks out of the box.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @othree.io/chisel-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Exports
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { aws, iac } from '@othree.io/chisel-sdk'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| Namespace | Purpose |
|
|
18
|
+
|-----------|---------|
|
|
19
|
+
| `aws` | Runtime Lambda handlers for write-side (actor) and read-side (projections) |
|
|
20
|
+
| `iac` | CDK stack factories for the full CQRS infrastructure |
|
|
21
|
+
|
|
22
|
+
## Write Side (`aws.createActor`)
|
|
23
|
+
|
|
24
|
+
Creates a fully wired Chisel actor with DynamoDB event persistence, sharded SNS publishing, and X-Ray tracing.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { aws } from '@othree.io/chisel-sdk'
|
|
28
|
+
|
|
29
|
+
const actor = aws.createActor<MyAggregate, MyEvent>({
|
|
30
|
+
boundedContext: 'Orders',
|
|
31
|
+
now: () => Date.now(),
|
|
32
|
+
getInitialState: async (contextId) => Optional(emptyOrder),
|
|
33
|
+
handleCommand: myCommandHandler,
|
|
34
|
+
reduceEvent: myReducer,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Lambda exports
|
|
38
|
+
export const handleCommand = actor.handleCommand
|
|
39
|
+
export const getState = actor.getState
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Environment variables** (read automatically):
|
|
43
|
+
|
|
44
|
+
| Variable | Description |
|
|
45
|
+
|----------|-------------|
|
|
46
|
+
| `EVENTS_TABLE` | DynamoDB events table name |
|
|
47
|
+
| `EVENTS_TOPICS` | Comma-separated SNS topic ARNs |
|
|
48
|
+
| `LOG_LEVEL` | Logging level (default: `info`) |
|
|
49
|
+
|
|
50
|
+
**Returns** a `LambdaActor<AggregateRoot>` with:
|
|
51
|
+
- `handleCommand` - processes a `LambdaCommand` and returns an `InvocationResult<CommandResult<AggregateRoot>>`
|
|
52
|
+
- `getState` - loads and returns the current aggregate state as `InvocationResult<AggregateRoot>` by `contextId`
|
|
53
|
+
|
|
54
|
+
## Read Side (`aws.getProjectionServices`)
|
|
55
|
+
|
|
56
|
+
Creates a fully wired projection processor with DynamoDB upsert/delete, SQS batch processing, and DLQ routing.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { aws } from '@othree.io/chisel-sdk'
|
|
60
|
+
|
|
61
|
+
const projection = aws.getProjectionServices<MyAggregate, MyEvent>({
|
|
62
|
+
boundedContext: 'Orders',
|
|
63
|
+
now: () => Date.now(),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Lambda exports
|
|
67
|
+
export const updateProjection = projection.sqs.updateProjection
|
|
68
|
+
export const getById = projection.services.getById
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Environment variables** (read automatically):
|
|
72
|
+
|
|
73
|
+
| Variable | Description |
|
|
74
|
+
|----------|-------------|
|
|
75
|
+
| `PROJECTION_TABLE` | DynamoDB projection table name |
|
|
76
|
+
| `PROJECTION_DLQ` | Application DLQ URL |
|
|
77
|
+
| `PROJECTION_PK` | Projection table partition key name |
|
|
78
|
+
| `DELETE_ON_EVENTS` | Comma-separated event types that trigger deletion |
|
|
79
|
+
| `LOG_LEVEL` | Logging level (default: `info`) |
|
|
80
|
+
|
|
81
|
+
**Returns:**
|
|
82
|
+
- `sqs.updateProjection` - SQS batch handler that upserts or deletes projections based on event type
|
|
83
|
+
- `services.getById` - queries the projection table by ID
|
|
84
|
+
|
|
85
|
+
### Standalone Functions
|
|
86
|
+
|
|
87
|
+
These are also exported for direct use or custom wiring:
|
|
88
|
+
|
|
89
|
+
| Function | Description |
|
|
90
|
+
|----------|-------------|
|
|
91
|
+
| `aws.getProcessInput` | Parses an SQS record containing an SNS-wrapped Chisel event |
|
|
92
|
+
| `aws.processEvent` | Routes to delete or upsert based on `deleteOnEvents` configuration |
|
|
93
|
+
| `aws.getProjectionById` | Queries the projection table and throws `NotFoundError` if empty |
|
|
94
|
+
| `aws.getProjectionServiceHandler` | Wraps a projection query function as a Lambda service handler |
|
|
95
|
+
| `aws.getConfiguration` | Reads projection configuration from environment variables |
|
|
96
|
+
|
|
97
|
+
## Infrastructure (`iac.createChiselStacks`)
|
|
98
|
+
|
|
99
|
+
Creates all CDK stacks for a Chisel microservice in a single call.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { App } from 'aws-cdk-lib'
|
|
103
|
+
import { iac } from '@othree.io/chisel-sdk'
|
|
104
|
+
|
|
105
|
+
const app = new App()
|
|
106
|
+
|
|
107
|
+
const stacks = iac.createChiselStacks(app, {
|
|
108
|
+
boundedContext: 'Orders',
|
|
109
|
+
version: 'v1',
|
|
110
|
+
projectMetadata: {
|
|
111
|
+
project: 'my-project',
|
|
112
|
+
environment: 'prod',
|
|
113
|
+
owner: 'team-a',
|
|
114
|
+
},
|
|
115
|
+
context: {
|
|
116
|
+
project: { env: 'prod', naming: { prefix: 'myapp' } },
|
|
117
|
+
lambda: { functions: [] },
|
|
118
|
+
chisel: { numberOfShards: 3 },
|
|
119
|
+
},
|
|
120
|
+
writeSide: {
|
|
121
|
+
codePath: 'dist/write',
|
|
122
|
+
},
|
|
123
|
+
readSide: {
|
|
124
|
+
codePath: 'dist/read',
|
|
125
|
+
deleteOnEvents: ['OrderDeleted'],
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The `context` is validated against `ChiselServiceContextConstraints` at runtime using Zod.
|
|
131
|
+
|
|
132
|
+
### Generated Stacks
|
|
133
|
+
|
|
134
|
+
| Stack | Description | Output |
|
|
135
|
+
|-------|-------------|--------|
|
|
136
|
+
| **EventBus** | FIFO SNS topics (one per shard) | `stacks.topics.stack`, `stacks.topics.topics` |
|
|
137
|
+
| **WritePersistence** | DynamoDB events table | `stacks.writeSide.persistence.eventsTable` |
|
|
138
|
+
| **WriteServices** | Command handler and get-state Lambda functions | `stacks.writeSide.services.handleCommandArn`, `stacks.writeSide.services.getStateArn` |
|
|
139
|
+
| **TopicSubscribers** | SQS queues subscribed to SNS topics with DLQ and redrive | `stacks.readSide.subscribers.eventQueues`, `stacks.readSide.subscribers.dlq` |
|
|
140
|
+
| **ProjectionPersistence** | DynamoDB projection table with optional GSIs | `stacks.readSide.persistence.projectionTable` |
|
|
141
|
+
| **ReadServices** | Update-projection and get-by-id Lambda functions | `stacks.readSide.servicesStack.stack` |
|
|
142
|
+
|
|
143
|
+
The read side is optional. Omit `readSide` from the input to create a write-only service.
|
|
144
|
+
|
|
145
|
+
### Context Configuration
|
|
146
|
+
|
|
147
|
+
The `ChiselServiceContext` controls stack behavior:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
type ChiselServiceContext = Readonly<{
|
|
151
|
+
project: {
|
|
152
|
+
env: string // Environment name (used as resource suffix)
|
|
153
|
+
naming?: { prefix?: string } // Optional resource name prefix
|
|
154
|
+
}
|
|
155
|
+
lambda: {
|
|
156
|
+
functions: Array<{
|
|
157
|
+
name: string
|
|
158
|
+
reservedConcurrentExecutions?: number
|
|
159
|
+
}>
|
|
160
|
+
}
|
|
161
|
+
chisel: {
|
|
162
|
+
numberOfShards: number // Number of FIFO SNS topics
|
|
163
|
+
}
|
|
164
|
+
}>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Read Side Options
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
type ReadSideInput = Readonly<{
|
|
171
|
+
codePath: string // Lambda code path
|
|
172
|
+
tablePk?: string // Projection table PK name (default: 'id')
|
|
173
|
+
globalSecondaryIndexes?: Array<GSIInput> // Optional GSIs
|
|
174
|
+
deleteOnEvents: Array<string> // Event types that trigger row deletion
|
|
175
|
+
}>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Peer Dependencies
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"@othree.io/auditor": "^5.0.0",
|
|
183
|
+
"@othree.io/awsome": "^5.0.0",
|
|
184
|
+
"@othree.io/cdk": "^6.0.0",
|
|
185
|
+
"@othree.io/chisel": "^7.0.0",
|
|
186
|
+
"@othree.io/chisel-aws": "^7.0.0",
|
|
187
|
+
"@othree.io/excuses": "^2.0.0",
|
|
188
|
+
"@othree.io/journal": "^3.0.0",
|
|
189
|
+
"@othree.io/stethoscope": "^5.0.0",
|
|
190
|
+
"aws-cdk-lib": "^2.239.0",
|
|
191
|
+
"aws-lambda": "^1.0.7",
|
|
192
|
+
"aws-xray-sdk-core": "^3.12.0",
|
|
193
|
+
"uuid": "^13.0.0"
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
npm install
|
|
201
|
+
npm run build # Dual CJS/ESM output
|
|
202
|
+
npm test # Vitest with coverage
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
ISC
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/aws/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/aws/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yCAAsB;AACtB,0CAAuB","sourcesContent":["export * from './read'\nexport * from './write'"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { dynamo } from '@othree.io/awsome';
|
|
2
|
+
import { Optional } from '@othree.io/optional';
|
|
3
|
+
import { ChiselEvent, TriggeredEvent } from '@othree.io/chisel';
|
|
4
|
+
import { SQSRecord } from 'aws-lambda';
|
|
5
|
+
export type ProjectionConfiguration = Readonly<{
|
|
6
|
+
projectionTable: string;
|
|
7
|
+
projectionDLQ: string;
|
|
8
|
+
projectionPk: string;
|
|
9
|
+
deleteOnEvents: Array<string>;
|
|
10
|
+
}>;
|
|
11
|
+
type CreateProjectionInput = Readonly<{
|
|
12
|
+
boundedContext: string;
|
|
13
|
+
now: () => number;
|
|
14
|
+
getConfiguration?: () => ProjectionConfiguration;
|
|
15
|
+
}>;
|
|
16
|
+
type EventState<AggregateRoot, EventType extends ChiselEvent> = Readonly<{
|
|
17
|
+
state: AggregateRoot;
|
|
18
|
+
event: TriggeredEvent<EventType>;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const getProcessInput: <AggregateRoot, EventType extends ChiselEvent>(input: SQSRecord) => Promise<Optional<EventState<AggregateRoot, EventType>>>;
|
|
21
|
+
export declare const processEvent: <AggregateRoot, EventType extends ChiselEvent>(configuration: ProjectionConfiguration) => (deleteByFn: (keys: dynamo.Keys) => Promise<Optional<boolean>>) => (upsertFn: (entity: AggregateRoot) => Promise<Optional<AggregateRoot>>) => (eventState: EventState<AggregateRoot, EventType>) => Promise<Optional<AggregateRoot>>;
|
|
22
|
+
export declare const getProjectionById: (boundedContext: string, configuration: ProjectionConfiguration) => <AggregateRoot>(getByFn: (keys: dynamo.Keys) => Promise<Optional<Array<AggregateRoot>>>) => (input: GetByIdQuery) => Promise<Optional<AggregateRoot>>;
|
|
23
|
+
export type ServiceQuery<T> = Readonly<{
|
|
24
|
+
query: T;
|
|
25
|
+
}>;
|
|
26
|
+
export type GetByIdQuery = Readonly<{
|
|
27
|
+
id: string;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const getProjectionServiceHandler: <QueryType, ProjectionType>(getProjection: (query: QueryType) => Promise<Optional<ProjectionType>>) => (event: ServiceQuery<QueryType>) => Promise<ProjectionType>;
|
|
30
|
+
export declare const getConfiguration: () => ProjectionConfiguration;
|
|
31
|
+
export declare const getProjectionServices: <AggregateRoot, EventType extends ChiselEvent>(input: CreateProjectionInput) => {
|
|
32
|
+
sqs: {
|
|
33
|
+
updateProjection: (event: import("aws-lambda").SQSEvent) => Promise<import("aws-lambda").SQSBatchResponse>;
|
|
34
|
+
};
|
|
35
|
+
services: {
|
|
36
|
+
getById: (event: Readonly<{
|
|
37
|
+
query: Readonly<{
|
|
38
|
+
id: string;
|
|
39
|
+
}>;
|
|
40
|
+
}>) => Promise<Optional<AggregateRoot>>;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/aws/read.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAA;AACxC,OAAO,EAAC,QAAQ,EAAW,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAC,WAAW,EAAE,cAAc,EAAC,MAAM,mBAAmB,CAAA;AAG7D,OAAO,EAAC,SAAS,EAAC,MAAM,YAAY,CAAA;AAGpC,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC3C,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CAChC,CAAC,CAAA;AAEF,KAAK,qBAAqB,GAAG,QAAQ,CAAC;IAClC,cAAc,EAAE,MAAM,CAAA;IACtB,GAAG,EAAE,MAAM,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,uBAAuB,CAAA;CACnD,CAAC,CAAA;AASF,KAAK,UAAU,CAAC,aAAa,EAAE,SAAS,SAAS,WAAW,IAAI,QAAQ,CAAC;IACrE,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,EAAE,cAAc,CAAC,SAAS,CAAC,CAAA;CACnC,CAAC,CAAA;AAEF,eAAO,MAAM,eAAe,GAAU,aAAa,EAAE,SAAS,SAAS,WAAW,EAAE,OAAO,SAAS,KAAG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAW5J,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,aAAa,EAAE,SAAS,SAAS,WAAW,EAAE,eAAe,uBAAuB,MAC5G,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MACzD,UAAU,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,MAC/D,YAAY,UAAU,CAAC,aAAa,EAAE,SAAS,CAAC,qCAQtD,CAAA;AAET,eAAO,MAAM,iBAAiB,GAAI,gBAAgB,MAAM,EAAE,eAAe,uBAAuB,MAC3F,aAAa,EAAE,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,MAC5E,OAAO,YAAY,KAAG,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAU3D,CAAA;AAET,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,QAAQ,CAAC;IACnC,KAAK,EAAE,CAAC,CAAA;CACX,CAAC,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;IAChC,EAAE,EAAE,MAAM,CAAA;CACb,CAAC,CAAA;AAEF,eAAO,MAAM,2BAA2B,GAAI,SAAS,EAAE,cAAc,EAAE,eAAe,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,MAClI,OAAO,YAAY,CAAC,SAAS,CAAC,KAAG,OAAO,CAAC,cAAc,CAE7D,CAAA;AAGL,eAAO,MAAM,gBAAgB,QAAO,uBASnC,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,aAAa,EAAE,SAAS,SAAS,WAAW,EAAE,OAAO,qBAAqB;;;;;;;oBApBxG,MAAM;;;;CA6Fb,CAAA"}
|
|
@@ -1,32 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.getProjectionServices = exports.getConfiguration = exports.getProjectionById = exports.processEvent = exports.getProcessInput = void 0;
|
|
3
|
+
exports.getProjectionServices = exports.getConfiguration = exports.getProjectionServiceHandler = exports.getProjectionById = exports.processEvent = exports.getProcessInput = void 0;
|
|
13
4
|
const stethoscope_1 = require("@othree.io/stethoscope");
|
|
14
5
|
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
6
|
+
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
15
7
|
const journal_1 = require("@othree.io/journal");
|
|
16
|
-
const
|
|
17
|
-
const projection_utils_1 = require("@othree.io/projection-utils");
|
|
8
|
+
const awsome_1 = require("@othree.io/awsome");
|
|
18
9
|
const optional_1 = require("@othree.io/optional");
|
|
19
10
|
const zod_1 = require("zod");
|
|
20
11
|
const auditor_1 = require("@othree.io/auditor");
|
|
21
12
|
const excuses_1 = require("@othree.io/excuses");
|
|
22
13
|
const ProjectionConstraints = zod_1.z.object({
|
|
23
|
-
projectionTable: auditor_1.
|
|
24
|
-
projectionDLQ: auditor_1.
|
|
25
|
-
projectionPk: auditor_1.
|
|
26
|
-
deleteOnEvents: zod_1.z.array(auditor_1.
|
|
14
|
+
projectionTable: auditor_1.NonEmptyString,
|
|
15
|
+
projectionDLQ: auditor_1.NonEmptyString,
|
|
16
|
+
projectionPk: auditor_1.NonEmptyString,
|
|
17
|
+
deleteOnEvents: zod_1.z.array(auditor_1.NonEmptyString)
|
|
27
18
|
});
|
|
28
|
-
const getProcessInput = (input) =>
|
|
29
|
-
return (0, optional_1.TryAsync)(() =>
|
|
19
|
+
const getProcessInput = async (input) => {
|
|
20
|
+
return (0, optional_1.TryAsync)(async () => {
|
|
30
21
|
const { body } = input;
|
|
31
22
|
const sqsEvent = JSON.parse(body);
|
|
32
23
|
const eventMessage = JSON.parse(sqsEvent.Message);
|
|
@@ -34,20 +25,20 @@ const getProcessInput = (input) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
34
25
|
state: eventMessage.state,
|
|
35
26
|
event: eventMessage.event
|
|
36
27
|
};
|
|
37
|
-
})
|
|
38
|
-
}
|
|
28
|
+
});
|
|
29
|
+
};
|
|
39
30
|
exports.getProcessInput = getProcessInput;
|
|
40
|
-
const processEvent = (configuration) => (
|
|
31
|
+
const processEvent = (configuration) => (deleteByFn) => (upsertFn) => async (eventState) => {
|
|
41
32
|
if (configuration.deleteOnEvents.includes(eventState.event.type)) {
|
|
42
|
-
return
|
|
33
|
+
return deleteByFn({
|
|
43
34
|
[configuration.projectionPk]: eventState.event.contextId
|
|
44
35
|
}).then(maybeSuccess => maybeSuccess.map(_ => eventState.state));
|
|
45
36
|
}
|
|
46
|
-
return
|
|
47
|
-
}
|
|
37
|
+
return upsertFn(eventState.state);
|
|
38
|
+
};
|
|
48
39
|
exports.processEvent = processEvent;
|
|
49
|
-
const getProjectionById = (boundedContext, configuration) => (
|
|
50
|
-
return (
|
|
40
|
+
const getProjectionById = (boundedContext, configuration) => (getByFn) => async (input) => {
|
|
41
|
+
return (await getByFn({
|
|
51
42
|
[configuration.projectionPk]: input.id
|
|
52
43
|
})).map(offers => {
|
|
53
44
|
if (offers.length > 0) {
|
|
@@ -55,10 +46,15 @@ const getProjectionById = (boundedContext, configuration) => (getBy) => (input)
|
|
|
55
46
|
}
|
|
56
47
|
throw new excuses_1.NotFoundError(input.id, boundedContext);
|
|
57
48
|
});
|
|
58
|
-
}
|
|
49
|
+
};
|
|
59
50
|
exports.getProjectionById = getProjectionById;
|
|
51
|
+
const getProjectionServiceHandler = (getProjection) => async (event) => {
|
|
52
|
+
return getProjection(event.query).then(_ => _.get());
|
|
53
|
+
};
|
|
54
|
+
exports.getProjectionServiceHandler = getProjectionServiceHandler;
|
|
55
|
+
/* v8 ignore start: wiring — covered by integration tests */
|
|
60
56
|
const getConfiguration = () => {
|
|
61
|
-
const envVars = auditor_1.
|
|
57
|
+
const envVars = (0, auditor_1.zodValidate)({ constraints: ProjectionConstraints })({
|
|
62
58
|
projectionTable: (0, optional_1.Optional)(process.env.PROJECTION_TABLE).map(String).orElse(''),
|
|
63
59
|
projectionDLQ: (0, optional_1.Optional)(process.env.PROJECTION_DLQ).map(String).orElse(''),
|
|
64
60
|
projectionPk: (0, optional_1.Optional)(process.env.PROJECTION_PK).map(String).orElse(''),
|
|
@@ -69,35 +65,55 @@ const getConfiguration = () => {
|
|
|
69
65
|
exports.getConfiguration = getConfiguration;
|
|
70
66
|
const getProjectionServices = (input) => {
|
|
71
67
|
const logLevel = (0, optional_1.Optional)(process.env.LOG_LEVEL)
|
|
72
|
-
.
|
|
73
|
-
console.warn(`LOG_LEVEL env var not defined. Defaulting to 'info'`);
|
|
74
|
-
return 'info';
|
|
75
|
-
})
|
|
76
|
-
.get();
|
|
68
|
+
.orElse('info');
|
|
77
69
|
const configuration = (0, optional_1.Optional)(input.getConfiguration)
|
|
78
70
|
.map(_ => _())
|
|
79
71
|
.orElse((0, exports.getConfiguration)());
|
|
80
|
-
const logger = journal_1.
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
const logger = (0, journal_1.createDefaultWinstonLogger)(console)(input.boundedContext, logLevel);
|
|
73
|
+
const logDebug = (0, journal_1.logIOAsync)({
|
|
74
|
+
now: input.now,
|
|
75
|
+
log: logger.debug.bind(logger),
|
|
76
|
+
logError: logger.error.bind(logger),
|
|
77
|
+
});
|
|
78
|
+
const logInfo = (0, journal_1.logIOAsync)({
|
|
79
|
+
now: input.now,
|
|
80
|
+
log: logger.info.bind(logger),
|
|
81
|
+
logError: logger.error.bind(logger),
|
|
82
|
+
});
|
|
84
83
|
const dynamoDbClient = new client_dynamodb_1.DynamoDBClient({});
|
|
85
|
-
const
|
|
84
|
+
const sqsClient = new client_sqs_1.SQSClient({});
|
|
85
|
+
const dynamoConfig = {
|
|
86
86
|
TableName: configuration.projectionTable
|
|
87
|
+
};
|
|
88
|
+
const upsertProjection = logDebug(awsome_1.dynamo.upsert({
|
|
89
|
+
dynamoDb: dynamoDbClient,
|
|
90
|
+
configuration: dynamoConfig,
|
|
87
91
|
}))(`upsert${input.boundedContext}`);
|
|
88
|
-
const deleteProjectionBy = logDebug(
|
|
89
|
-
|
|
92
|
+
const deleteProjectionBy = logDebug(awsome_1.dynamo.deleteBy({
|
|
93
|
+
dynamoDb: dynamoDbClient,
|
|
94
|
+
configuration: dynamoConfig,
|
|
90
95
|
}))(`delete${input.boundedContext}`);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
const queryProjection = awsome_1.dynamo.query({
|
|
97
|
+
dynamoDb: dynamoDbClient,
|
|
98
|
+
configuration: dynamoConfig,
|
|
99
|
+
});
|
|
100
|
+
const getProjectionBy = logDebug(awsome_1.dynamo.getBy({
|
|
101
|
+
query: queryProjection,
|
|
102
|
+
}))(`get${input.boundedContext}`);
|
|
94
103
|
const getById = logDebug((0, exports.getProjectionById)(input.boundedContext, configuration)(getProjectionBy))(`get${input.boundedContext}ById`);
|
|
95
104
|
const processEventFn = logDebug((0, exports.processEvent)(configuration)(deleteProjectionBy)(upsertProjection))(`process${input.boundedContext}Event`);
|
|
96
|
-
const processSqsFn = (0, stethoscope_1.processSqs)(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
const processSqsFn = (0, stethoscope_1.processSqs)({
|
|
106
|
+
log: logInfo,
|
|
107
|
+
sqsClient,
|
|
108
|
+
configuration: {
|
|
109
|
+
dlqUrl: configuration.projectionDLQ,
|
|
110
|
+
exponentialTimeouts: [5, 10, 30],
|
|
111
|
+
},
|
|
112
|
+
didItFail: (_) => false,
|
|
113
|
+
runProcess: processEventFn,
|
|
114
|
+
getProcessInput: (exports.getProcessInput),
|
|
115
|
+
});
|
|
116
|
+
const getByIdHandlerFn = logInfo((0, exports.getProjectionServiceHandler)(getById))(`get${input.boundedContext}ByIdHandler`);
|
|
101
117
|
return {
|
|
102
118
|
sqs: {
|
|
103
119
|
updateProjection: processSqsFn
|
|
@@ -108,4 +124,5 @@ const getProjectionServices = (input) => {
|
|
|
108
124
|
};
|
|
109
125
|
};
|
|
110
126
|
exports.getProjectionServices = getProjectionServices;
|
|
127
|
+
/* v8 ignore stop */
|
|
111
128
|
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../../src/aws/read.ts"],"names":[],"mappings":"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","sourcesContent":["import {processSqs} from '@othree.io/stethoscope'\nimport {DynamoDBClient} from '@aws-sdk/client-dynamodb'\nimport {SQSClient} from '@aws-sdk/client-sqs'\nimport {logIOAsync, createDefaultWinstonLogger} from '@othree.io/journal'\nimport {dynamo} from '@othree.io/awsome'\nimport {Optional, TryAsync} from '@othree.io/optional'\nimport {ChiselEvent, TriggeredEvent} from '@othree.io/chisel'\nimport {z} from 'zod'\nimport {zodValidate, NonEmptyString} from '@othree.io/auditor'\nimport {SQSRecord} from 'aws-lambda'\nimport {NotFoundError} from '@othree.io/excuses'\n\nexport type ProjectionConfiguration = Readonly<{\n projectionTable: string\n projectionDLQ: string\n projectionPk: string\n deleteOnEvents: Array<string>\n}>\n\ntype CreateProjectionInput = Readonly<{\n boundedContext: string\n now: () => number\n getConfiguration?: () => ProjectionConfiguration\n}>\n\nconst ProjectionConstraints: z.ZodType<ProjectionConfiguration> = z.object({\n projectionTable: NonEmptyString,\n projectionDLQ: NonEmptyString,\n projectionPk: NonEmptyString,\n deleteOnEvents: z.array(NonEmptyString)\n})\n\ntype EventState<AggregateRoot, EventType extends ChiselEvent> = Readonly<{\n state: AggregateRoot,\n event: TriggeredEvent<EventType>\n}>\n\nexport const getProcessInput = async <AggregateRoot, EventType extends ChiselEvent>(input: SQSRecord): Promise<Optional<EventState<AggregateRoot, EventType>>> => {\n return TryAsync(async () => {\n const {body} = input\n const sqsEvent = JSON.parse(body)\n const eventMessage = JSON.parse(sqsEvent.Message)\n\n return {\n state: eventMessage.state as AggregateRoot,\n event: eventMessage.event as TriggeredEvent<EventType>\n }\n })\n}\n\nexport const processEvent = <AggregateRoot, EventType extends ChiselEvent>(configuration: ProjectionConfiguration) =>\n (deleteByFn: (keys: dynamo.Keys) => Promise<Optional<boolean>>) =>\n (upsertFn: (entity: AggregateRoot) => Promise<Optional<AggregateRoot>>) =>\n async (eventState: EventState<AggregateRoot, EventType>) => {\n if (configuration.deleteOnEvents.includes(eventState.event.type)) {\n return deleteByFn({\n [configuration.projectionPk]: eventState.event.contextId\n }).then(maybeSuccess => maybeSuccess.map(_ => eventState.state))\n }\n\n return upsertFn(eventState.state)\n }\n\nexport const getProjectionById = (boundedContext: string, configuration: ProjectionConfiguration) =>\n <AggregateRoot>(getByFn: (keys: dynamo.Keys) => Promise<Optional<Array<AggregateRoot>>>) =>\n async (input: GetByIdQuery): Promise<Optional<AggregateRoot>> => {\n return (await getByFn({\n [configuration.projectionPk]: input.id\n })).map(offers => {\n if (offers.length > 0) {\n return offers[0]\n }\n\n throw new NotFoundError(input.id, boundedContext)\n })\n }\n\nexport type ServiceQuery<T> = Readonly<{\n query: T\n}>\n\nexport type GetByIdQuery = Readonly<{\n id: string\n}>\n\nexport const getProjectionServiceHandler = <QueryType, ProjectionType>(getProjection: (query: QueryType) => Promise<Optional<ProjectionType>>) =>\n async (event: ServiceQuery<QueryType>): Promise<ProjectionType> => {\n return getProjection(event.query).then(_ => _.get() as ProjectionType)\n }\n\n/* v8 ignore start: wiring — covered by integration tests */\nexport const getConfiguration = (): ProjectionConfiguration => {\n const envVars = zodValidate({ constraints: ProjectionConstraints })({\n projectionTable: Optional(process.env.PROJECTION_TABLE).map(String).orElse(''),\n projectionDLQ: Optional(process.env.PROJECTION_DLQ).map(String).orElse(''),\n projectionPk: Optional(process.env.PROJECTION_PK).map(String).orElse(''),\n deleteOnEvents: Optional(process.env.DELETE_ON_EVENTS).map(_ => _.split(',')).orElse([])\n }).get()\n\n return envVars\n}\n\nexport const getProjectionServices = <AggregateRoot, EventType extends ChiselEvent>(input: CreateProjectionInput) => {\n const logLevel = Optional(process.env.LOG_LEVEL)\n .orElse('info')\n\n const configuration = Optional(input.getConfiguration)\n .map(_ => _())\n .orElse(getConfiguration())\n\n const logger = createDefaultWinstonLogger(console)(input.boundedContext, logLevel)\n\n const logDebug = logIOAsync({\n now: input.now,\n log: logger.debug.bind(logger),\n logError: logger.error.bind(logger),\n })\n const logInfo = logIOAsync({\n now: input.now,\n log: logger.info.bind(logger),\n logError: logger.error.bind(logger),\n })\n\n const dynamoDbClient = new DynamoDBClient({})\n const sqsClient = new SQSClient({})\n\n const dynamoConfig: dynamo.DynamoConfiguration = {\n TableName: configuration.projectionTable\n }\n\n const upsertProjection: (entity: AggregateRoot) => Promise<Optional<AggregateRoot>> = logDebug(dynamo.upsert<AggregateRoot>({\n dynamoDb: dynamoDbClient,\n configuration: dynamoConfig,\n }))(`upsert${input.boundedContext}`)\n\n const deleteProjectionBy: (keys: dynamo.Keys) => Promise<Optional<boolean>> = logDebug(dynamo.deleteBy<AggregateRoot>({\n dynamoDb: dynamoDbClient,\n configuration: dynamoConfig,\n }))(`delete${input.boundedContext}`)\n\n const queryProjection = dynamo.query<AggregateRoot>({\n dynamoDb: dynamoDbClient,\n configuration: dynamoConfig,\n })\n\n const getProjectionBy: (keys: dynamo.Keys) => Promise<Optional<Array<AggregateRoot>>> = logDebug(dynamo.getBy<AggregateRoot>({\n query: queryProjection,\n }))(`get${input.boundedContext}`)\n\n const getById: (input: GetByIdQuery) => Promise<Optional<AggregateRoot>> = logDebug(getProjectionById(input.boundedContext, configuration)(getProjectionBy))(`get${input.boundedContext}ById`)\n\n const processEventFn: (eventState: EventState<AggregateRoot, EventType>) => Promise<Optional<AggregateRoot>> = logDebug(processEvent<AggregateRoot, EventType>(configuration)(deleteProjectionBy)(upsertProjection))(`process${input.boundedContext}Event`)\n\n const processSqsFn = processSqs<EventState<AggregateRoot, EventType>, AggregateRoot>({\n log: logInfo,\n sqsClient,\n configuration: {\n dlqUrl: configuration.projectionDLQ,\n exponentialTimeouts: [5, 10, 30],\n },\n didItFail: (_: Error) => false,\n runProcess: processEventFn,\n getProcessInput: getProcessInput<AggregateRoot, EventType>,\n })\n\n const getByIdHandlerFn = logInfo(getProjectionServiceHandler<GetByIdQuery, AggregateRoot>(getById))(`get${input.boundedContext}ByIdHandler`)\n\n return {\n sqs: {\n updateProjection: processSqsFn\n },\n services: {\n getById: getByIdHandlerFn\n }\n }\n}\n/* v8 ignore stop */\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { handlers } from '@othree.io/chisel-aws';
|
|
2
|
+
import { actor, ChiselEvent, CommandResult, eventSource, EventSourceConfiguration } from '@othree.io/chisel';
|
|
3
|
+
import { lambda } from '@othree.io/awsome';
|
|
4
|
+
import { Optional } from '@othree.io/optional';
|
|
5
|
+
import { ChiselDynamoConfiguration, ChiselShardedSnsConfiguration } from '@othree.io/chisel-aws';
|
|
6
|
+
type GetStateQuery = Readonly<{
|
|
7
|
+
contextId: string;
|
|
8
|
+
}>;
|
|
9
|
+
export type LambdaActor<AggregateRoot> = Readonly<{
|
|
10
|
+
handleCommand: (event: handlers.LambdaCommand) => Promise<lambda.InvocationResult<CommandResult<AggregateRoot>>>;
|
|
11
|
+
getState: (event: lambda.Query<GetStateQuery>) => Promise<lambda.InvocationResult<AggregateRoot>>;
|
|
12
|
+
}>;
|
|
13
|
+
type CreateLambdaActorInput<AggregateRoot, Event extends ChiselEvent> = Readonly<{
|
|
14
|
+
boundedContext: string;
|
|
15
|
+
now: () => number;
|
|
16
|
+
getInitialState: (contextId: Optional<string>) => Promise<Optional<AggregateRoot>>;
|
|
17
|
+
handleCommand: actor.HandleCommand<AggregateRoot>;
|
|
18
|
+
reduceEvent: eventSource.Reduce<AggregateRoot, Event>;
|
|
19
|
+
getConfiguration?: () => ChiselDynamoConfiguration & ChiselShardedSnsConfiguration<AggregateRoot> & EventSourceConfiguration<AggregateRoot>;
|
|
20
|
+
}>;
|
|
21
|
+
export declare const createActor: <AggregateRoot, Event extends ChiselEvent>(input: CreateLambdaActorInput<AggregateRoot, Event>) => LambdaActor<AggregateRoot>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/aws/write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAC,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,wBAAwB,EAAC,MAAM,mBAAmB,CAAA;AAC1G,OAAO,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAA;AAExC,OAAO,EAAC,QAAQ,EAAC,MAAM,qBAAqB,CAAA;AAE5C,OAAO,EAGH,yBAAyB,EACzB,6BAA6B,EAChC,MAAM,uBAAuB,CAAA;AAQ9B,KAAK,aAAa,GAAG,QAAQ,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAA;CACpB,CAAC,CAAA;AAEF,MAAM,MAAM,WAAW,CAAC,aAAa,IAAI,QAAQ,CAAC;IAC9C,aAAa,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAA;IAChH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAA;CACpG,CAAC,CAAA;AAkCF,KAAK,sBAAsB,CAAC,aAAa,EAAE,KAAK,SAAS,WAAW,IAAI,QAAQ,CAAC;IAC7E,cAAc,EAAE,MAAM,CAAA;IACtB,GAAG,EAAE,MAAM,MAAM,CAAA;IACjB,eAAe,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAA;IAClF,aAAa,EAAE,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC,CAAA;IACjD,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;IACrD,gBAAgB,CAAC,EAAE,MAAM,yBAAyB,GAAG,6BAA6B,CAAC,aAAa,CAAC,GAAG,wBAAwB,CAAC,aAAa,CAAC,CAAA;CAC9I,CAAC,CAAA;AAQF,eAAO,MAAM,WAAW,GAAI,aAAa,EAAE,KAAK,SAAS,WAAW,EAAE,OAAO,sBAAsB,CAAC,aAAa,EAAE,KAAK,CAAC,KAAG,WAAW,CAAC,aAAa,CAqEpJ,CAAA"}
|
|
@@ -32,34 +32,27 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
36
|
exports.createActor = void 0;
|
|
37
|
+
const chisel_aws_1 = require("@othree.io/chisel-aws");
|
|
46
38
|
const chisel_1 = require("@othree.io/chisel");
|
|
39
|
+
const awsome_1 = require("@othree.io/awsome");
|
|
47
40
|
const journal_1 = require("@othree.io/journal");
|
|
48
41
|
const optional_1 = require("@othree.io/optional");
|
|
49
|
-
const
|
|
42
|
+
const cerillo_1 = require("@othree.io/cerillo");
|
|
43
|
+
const chisel_aws_2 = require("@othree.io/chisel-aws");
|
|
50
44
|
const auditor_1 = require("@othree.io/auditor");
|
|
51
45
|
const zod_1 = require("zod");
|
|
52
46
|
const uuid_1 = require("uuid");
|
|
53
47
|
const AWSXRay = __importStar(require("aws-xray-sdk-core"));
|
|
54
48
|
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
55
49
|
const client_sns_1 = require("@aws-sdk/client-sns");
|
|
56
|
-
const actor_1 = require("@othree.io/chisel-aws/lib/actor");
|
|
57
50
|
const ChiselActorConstraints = zod_1.z.object({
|
|
58
|
-
eventsTable: auditor_1.
|
|
59
|
-
eventsTopics: zod_1.z.array(auditor_1.
|
|
51
|
+
eventsTable: auditor_1.NonEmptyString,
|
|
52
|
+
eventsTopics: zod_1.z.array(auditor_1.NonEmptyString),
|
|
60
53
|
});
|
|
61
54
|
const getConfiguration = (input) => {
|
|
62
|
-
const envVars = auditor_1.
|
|
55
|
+
const envVars = (0, auditor_1.zodValidate)({ constraints: ChiselActorConstraints })({
|
|
63
56
|
eventsTable: (0, optional_1.Optional)(process.env.EVENTS_TABLE).map(String).orElse(''),
|
|
64
57
|
eventsTopics: (0, optional_1.Optional)(process.env.EVENTS_TOPICS).map(_ => String(_).split(',')).orElse([]),
|
|
65
58
|
}).get();
|
|
@@ -70,7 +63,7 @@ const getConfiguration = (input) => {
|
|
|
70
63
|
TableName: envVars.eventsTable,
|
|
71
64
|
TopicsArn: envVars.eventsTopics,
|
|
72
65
|
SnapshotEventType: '$SNAPSHOT$',
|
|
73
|
-
GetMessageGroupId: (event,
|
|
66
|
+
GetMessageGroupId: (event, _state) => event.contextId,
|
|
74
67
|
IncludeState: true,
|
|
75
68
|
SnapshotSerializer: {
|
|
76
69
|
serialize: (state) => JSON.stringify(state),
|
|
@@ -78,36 +71,65 @@ const getConfiguration = (input) => {
|
|
|
78
71
|
}
|
|
79
72
|
};
|
|
80
73
|
};
|
|
81
|
-
const getStateHandler = (loadState) => (event) =>
|
|
74
|
+
const getStateHandler = (loadState) => async (event) => {
|
|
82
75
|
return loadState((0, optional_1.Optional)(event.query.contextId))
|
|
83
76
|
.then(_ => _.map(state => state.state).get());
|
|
84
|
-
}
|
|
77
|
+
};
|
|
85
78
|
const createActor = (input) => {
|
|
86
79
|
const logLevel = (0, optional_1.Optional)(process.env.LOG_LEVEL)
|
|
87
|
-
.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const logDebug = (0, journal_1.logIOAsync)(now)(logger.debug)(logger.error);
|
|
80
|
+
.orElse('info');
|
|
81
|
+
const logger = (0, journal_1.createDefaultWinstonLogger)(console)(input.boundedContext, logLevel);
|
|
82
|
+
const logDebug = (0, journal_1.logIOAsync)({
|
|
83
|
+
now: input.now,
|
|
84
|
+
log: logger.debug.bind(logger),
|
|
85
|
+
logError: logger.error.bind(logger),
|
|
86
|
+
});
|
|
95
87
|
const configuration = (0, optional_1.Optional)(input.getConfiguration)
|
|
96
88
|
.map(_ => _())
|
|
97
89
|
.orElse(getConfiguration(input));
|
|
98
90
|
const dynamoDbClient = AWSXRay.captureAWSv3Client(new client_dynamodb_1.DynamoDBClient({}));
|
|
99
91
|
const snsClient = AWSXRay.captureAWSv3Client(new client_sns_1.SNSClient({}));
|
|
100
|
-
const getEvents = logDebug(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
92
|
+
const getEvents = logDebug(chisel_aws_2.awsEventSource.getEvents({
|
|
93
|
+
dynamoDb: dynamoDbClient,
|
|
94
|
+
configuration,
|
|
95
|
+
}))('getEvents');
|
|
96
|
+
const persistEvent = logDebug(chisel_aws_2.awsEventSource.persist({
|
|
97
|
+
dynamoDb: dynamoDbClient,
|
|
98
|
+
configuration,
|
|
99
|
+
}))('persistEvent');
|
|
100
|
+
const publishEvent = logDebug(chisel_aws_2.awsActor.publishShardedEvent({
|
|
101
|
+
hashCode: chisel_aws_2.awsActor.hashCode,
|
|
102
|
+
snsClient,
|
|
103
|
+
configuration,
|
|
104
|
+
}))('publishEvent');
|
|
105
|
+
const loadState = logDebug(chisel_1.eventSource.loadState({
|
|
106
|
+
configuration,
|
|
107
|
+
getEvents: getEvents,
|
|
108
|
+
getInitialState: input.getInitialState,
|
|
109
|
+
reduce: input.reduceEvent,
|
|
110
|
+
match: cerillo_1.match,
|
|
111
|
+
}))('loadState');
|
|
112
|
+
const calculateNewState = logDebug(chisel_1.eventSource.calculateNewState({
|
|
113
|
+
configuration,
|
|
114
|
+
newId: uuid_1.v7,
|
|
115
|
+
now: input.now,
|
|
116
|
+
persistEvent: persistEvent,
|
|
117
|
+
reduce: input.reduceEvent,
|
|
118
|
+
}))('calculateNewState');
|
|
105
119
|
const getStateHandlerFn = logDebug(getStateHandler(loadState))('getStateHandler');
|
|
106
|
-
const actorHandler = logDebug(chisel_1.actor.handle(
|
|
107
|
-
|
|
120
|
+
const actorHandler = logDebug(chisel_1.actor.handle({
|
|
121
|
+
loadState,
|
|
122
|
+
handleCommand: input.handleCommand,
|
|
123
|
+
calculateNewState,
|
|
124
|
+
publishEvent,
|
|
125
|
+
match: cerillo_1.match,
|
|
126
|
+
}))('chiselHandleCommand');
|
|
127
|
+
const handleCommand = logDebug(chisel_aws_1.handlers.handleLambdaCommand({
|
|
128
|
+
handle: actorHandler,
|
|
129
|
+
}))('handleLambdaCommand');
|
|
108
130
|
return Object.freeze({
|
|
109
|
-
handleCommand: handleCommand,
|
|
110
|
-
getState: getStateHandlerFn,
|
|
131
|
+
handleCommand: awsome_1.lambda.unwrap(handleCommand),
|
|
132
|
+
getState: awsome_1.lambda.unwrap(getStateHandlerFn),
|
|
111
133
|
});
|
|
112
134
|
};
|
|
113
135
|
exports.createActor = createActor;
|