(
definition: Definition,
event: Payload,
input?: {
readonly seq: number
readonly aggregateID: string
readonly ownerID?: string
readonly strictOwner?: boolean
},
commit?: (seq: number) => Effect.Effect<void>,
)
| 203 | ) |
| 204 | |
| 205 | function commitDurableEvent( |
| 206 | definition: Definition, |
| 207 | event: Payload, |
| 208 | input?: { |
| 209 | readonly seq: number |
| 210 | readonly aggregateID: string |
| 211 | readonly ownerID?: string |
| 212 | readonly strictOwner?: boolean |
| 213 | }, |
| 214 | commit?: (seq: number) => Effect.Effect<void>, |
| 215 | ) { |
| 216 | return Effect.gen(function* () { |
| 217 | const durable = definition?.durable |
| 218 | if (durable) { |
| 219 | const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate] |
| 220 | if (typeof aggregateID !== "string") { |
| 221 | yield* Effect.die( |
| 222 | new InvalidDurableEventError({ |
| 223 | type: event.type, |
| 224 | message: `Expected string aggregate field ${durable.aggregate}`, |
| 225 | }), |
| 226 | ) |
| 227 | } else { |
| 228 | if (input && input.aggregateID !== aggregateID) { |
| 229 | yield* Effect.die( |
| 230 | new InvalidDurableEventError({ |
| 231 | type: event.type, |
| 232 | message: `Aggregate mismatch: expected ${input.aggregateID}, got ${aggregateID}`, |
| 233 | }), |
| 234 | ) |
| 235 | } |
| 236 | const list = projectors.get(event.type) ?? [] |
| 237 | return yield* Effect.uninterruptible( |
| 238 | Effect.gen(function* () { |
| 239 | const committed = yield* db |
| 240 | .transaction( |
| 241 | () => |
| 242 | Effect.gen(function* () { |
| 243 | const row = yield* db |
| 244 | .select({ seq: EventSequenceTable.seq, ownerID: EventSequenceTable.owner_id }) |
| 245 | .from(EventSequenceTable) |
| 246 | .where(eq(EventSequenceTable.aggregate_id, aggregateID)) |
| 247 | .get() |
| 248 | .pipe(Effect.orDie) |
| 249 | const latest = row?.seq ?? -1 |
| 250 | const encoded = Schema.encodeUnknownSync(definition.data)(event.data) as Record< |
| 251 | string, |
| 252 | unknown |
| 253 | > |
| 254 | if (input?.strictOwner && row?.ownerID && row.ownerID !== input.ownerID) { |
| 255 | yield* Effect.die( |
| 256 | new InvalidDurableEventError({ |
| 257 | type: event.type, |
| 258 | message: `Replay owner mismatch for aggregate ${aggregateID}: expected ${row.ownerID}, got ${input.ownerID ?? "none"}`, |
| 259 | }), |
| 260 | ) |
| 261 | } |
| 262 | if (input && input.seq <= latest) { |
no test coverage detected