@photon-grove/evt 0.1.1 → 0.2.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 +51 -4
- package/dist/aggregate.d.ts +26 -0
- package/dist/aggregate.d.ts.map +1 -1
- package/dist/aggregate.js.map +1 -1
- package/dist/dynamodb/head.d.ts +6 -0
- package/dist/dynamodb/head.d.ts.map +1 -1
- package/dist/dynamodb/head.js +11 -0
- package/dist/dynamodb/head.js.map +1 -1
- package/dist/dynamodb/index.d.ts +2 -1
- package/dist/dynamodb/index.d.ts.map +1 -1
- package/dist/dynamodb/index.js +2 -1
- package/dist/dynamodb/index.js.map +1 -1
- package/dist/dynamodb/repository.d.ts +9 -2
- package/dist/dynamodb/repository.d.ts.map +1 -1
- package/dist/dynamodb/repository.js +123 -27
- package/dist/dynamodb/repository.js.map +1 -1
- package/dist/dynamodb/requests.d.ts +37 -3
- package/dist/dynamodb/requests.d.ts.map +1 -1
- package/dist/dynamodb/requests.js +67 -9
- package/dist/dynamodb/requests.js.map +1 -1
- package/dist/dynamodb/views.d.ts +18 -0
- package/dist/dynamodb/views.d.ts.map +1 -0
- package/dist/dynamodb/views.js +56 -0
- package/dist/dynamodb/views.js.map +1 -0
- package/dist/errors.d.ts +51 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +60 -4
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/memory/repository.d.ts +3 -2
- package/dist/memory/repository.d.ts.map +1 -1
- package/dist/memory/repository.js +78 -29
- package/dist/memory/repository.js.map +1 -1
- package/dist/postgres/client.d.ts +32 -0
- package/dist/postgres/client.d.ts.map +1 -0
- package/dist/postgres/client.js +4 -0
- package/dist/postgres/client.js.map +1 -0
- package/dist/postgres/index.d.ts +4 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +6 -0
- package/dist/postgres/index.js.map +1 -0
- package/dist/postgres/repository.d.ts +62 -0
- package/dist/postgres/repository.d.ts.map +1 -0
- package/dist/postgres/repository.js +598 -0
- package/dist/postgres/repository.js.map +1 -0
- package/dist/postgres/schema.d.ts +81 -0
- package/dist/postgres/schema.d.ts.map +1 -0
- package/dist/postgres/schema.js +249 -0
- package/dist/postgres/schema.js.map +1 -0
- package/dist/repository.d.ts +101 -6
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js +108 -6
- package/dist/repository.js.map +1 -1
- package/dist/staging.d.ts +28 -0
- package/dist/staging.d.ts.map +1 -0
- package/dist/staging.js +89 -0
- package/dist/staging.js.map +1 -0
- package/dist/store.d.ts +15 -3
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +38 -54
- package/dist/store.js.map +1 -1
- package/dist/transaction.d.ts +113 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +207 -0
- package/dist/transaction.js.map +1 -0
- package/package.json +5 -1
- package/src/aggregate.ts +28 -0
- package/src/dynamodb/head.ts +15 -0
- package/src/dynamodb/index.ts +2 -0
- package/src/dynamodb/repository.ts +177 -33
- package/src/dynamodb/requests.ts +114 -10
- package/src/dynamodb/views.ts +67 -0
- package/src/errors.ts +86 -4
- package/src/index.ts +19 -6
- package/src/memory/repository.ts +93 -32
- package/src/postgres/client.ts +37 -0
- package/src/postgres/index.ts +23 -0
- package/src/postgres/repository.ts +858 -0
- package/src/postgres/schema.ts +302 -0
- package/src/repository.ts +220 -8
- package/src/staging.ts +138 -0
- package/src/store.ts +66 -65
- package/src/transaction.ts +322 -0
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@ held to the fixtures Go and Rust generate, byte for byte.
|
|
|
11
11
|
npm install @photon-grove/evt
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
So far it has the core,
|
|
15
|
-
|
|
16
|
-
releases, and the
|
|
14
|
+
So far it has the core, multi-stream transactions with read fences, the in-memory, DynamoDB and
|
|
15
|
+
PostgreSQL backends, and the testing helpers. Commit-wide projectors and command outcomes come in
|
|
16
|
+
later releases, and the
|
|
17
17
|
[feature matrix](https://github.com/photon-grove/evt/blob/main/docs/reference/feature-matrix.md)
|
|
18
18
|
tracks each one. The
|
|
19
19
|
[getting started guide](https://github.com/photon-grove/evt/blob/main/docs/guide/getting-started.md#the-same-model-in-typescript)
|
|
@@ -26,7 +26,8 @@ builds a bank account aggregate with it, end to end.
|
|
|
26
26
|
| `@photon-grove/evt` | Aggregates, codecs, upcasters, the `Store`, retry, and the wire codecs |
|
|
27
27
|
| `@photon-grove/evt/memory` | `MemRepository`, the in-memory backend for tests and local development |
|
|
28
28
|
| `@photon-grove/evt/testing` | `scenario` and `Fixture`, the given/when/then helpers over `MemRepository` |
|
|
29
|
-
| `@photon-grove/evt/dynamodb` | `DynamoRepository`, and the
|
|
29
|
+
| `@photon-grove/evt/dynamodb` | `DynamoRepository`, `viewProjector`, and the item and request codecs |
|
|
30
|
+
| `@photon-grove/evt/postgres` | `PgRepository`, and the schema: table names, relations and `schemaDdl` |
|
|
30
31
|
|
|
31
32
|
## DynamoDB
|
|
32
33
|
|
|
@@ -59,6 +60,35 @@ hasn't loaded the stream gets a conflict, and its retry passes. TTL deletes item
|
|
|
59
60
|
use retention only for short-lived streams that nothing replays, and declare the retained types in
|
|
60
61
|
the feature manifest from Go or Rust, so that their writers refuse read fences on them.
|
|
61
62
|
|
|
63
|
+
`viewProjector('views')`, listed in an aggregate's `projectors`, puts the entity's view row with
|
|
64
|
+
every commit, in the same transaction: `pk = {type}#{id}`, `sk = VIEW`, and the state as its
|
|
65
|
+
codec encodes it, as Go's `dynamo.NewViewProjector` does.
|
|
66
|
+
|
|
67
|
+
## PostgreSQL
|
|
68
|
+
|
|
69
|
+
`PgRepository` takes a pool shaped like `pg`'s `Pool`, and writes the tables the
|
|
70
|
+
[PostgreSQL guide](https://github.com/photon-grove/evt/blob/main/docs/guide/postgres.md) describes,
|
|
71
|
+
as Go's and Rust's repositories do:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import {Store} from '@photon-grove/evt'
|
|
75
|
+
import {PgRepository} from '@photon-grove/evt/postgres'
|
|
76
|
+
import {Pool} from 'pg'
|
|
77
|
+
|
|
78
|
+
const repository = new PgRepository(new Pool({connectionString: process.env.DATABASE_URL}))
|
|
79
|
+
await repository.ensureSchema() // idempotent, safe on every start
|
|
80
|
+
const store = new Store(repository)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The package imports no driver: Neon's serverless `Pool` works too, and so does a single connection
|
|
84
|
+
without `connect()`, such as PGlite, which the repository runs one operation at a time on. Every
|
|
85
|
+
value it sends is a string and every column it reads is text, so no driver's type parsing touches a
|
|
86
|
+
stored value. `{eventsTable, snapshotsTable}` let several event logs share a database. A writer
|
|
87
|
+
initializes a fresh store itself on its first write, as Go's and Rust's do, and `initialize()` is
|
|
88
|
+
there for a deploy step that treats every backend alike. Every transaction runs at `READ
|
|
89
|
+
COMMITTED`, whatever the server's default. An abort is checked between statements and rolls the
|
|
90
|
+
transaction back.
|
|
91
|
+
|
|
62
92
|
## Runtimes
|
|
63
93
|
|
|
64
94
|
The package is ESM only and has no runtime dependencies. CI runs it on Node 26, Node 22.12, Bun and
|
|
@@ -103,9 +133,26 @@ of `AbortSignal`, which the DOM library, `@types/node`, and Bun's and Deno's typ
|
|
|
103
133
|
refuses an entity type the manifest declares history-dependent. `MemRepository` declares the
|
|
104
134
|
required `stream-heads` feature on a fresh store's first write, and `initialize()` declares it
|
|
105
135
|
up front.
|
|
136
|
+
- **Projectors.** An aggregate's `projectors` run on the state after each commit's events, and
|
|
137
|
+
the operations they return are written in the commit's own transaction, by the backend they
|
|
138
|
+
name. `MemRepository` budgets them and writes none, as Go's in-memory repository does, so code
|
|
139
|
+
with projectors runs on it unchanged. A commit with operations but no events is refused with a
|
|
140
|
+
`ProjectorOnlyCommitError`.
|
|
106
141
|
- **Rebuilds.** While a projection rebuild holds the store, every commit fails with a
|
|
107
142
|
`RebuildInProgressError`. It's retryable, so `executeWithRetry` runs the command again after its
|
|
108
143
|
backoff, though a rebuild usually outlasts the default policy's attempts.
|
|
144
|
+
- **Transactions.** `store.transaction()` commits several streams at once. `read(aggregate, id)`
|
|
145
|
+
loads a stream of any aggregate type and returns a typed handle, `append(handle, events)` stages
|
|
146
|
+
the events decided for it, and `commit({metadata})` stamps one commit ID on every event and
|
|
147
|
+
writes them all atomically, with each stream's snapshot and projector operations, or writes
|
|
148
|
+
nothing. A stream read but never appended to is fenced at the head it was read at, so the commit
|
|
149
|
+
fails with a `FenceConflictError` once another writer moves it. `fence({entityId, entityType,
|
|
150
|
+
head})` adds a fence of your own on a stream the transaction doesn't read. A fence the engine
|
|
151
|
+
filled is retryable, and one the caller stamped isn't. A transaction commits once, and `execute`
|
|
152
|
+
stays the shortcut for a single stream. Underneath, every repository is a
|
|
153
|
+
`TransactionalRepository`, whose `commitTransaction({streams, fences})` takes the streams and
|
|
154
|
+
fences directly. A fence on an entity type with retention is refused with a
|
|
155
|
+
`RetentionConflictError`.
|
|
109
156
|
- **Sequences** are `bigint`s, because Go and Rust store them as 64-bit integers.
|
|
110
157
|
|
|
111
158
|
Errors are `EvtError`s with a `code`. `isConflict` and `isRetryable` tell them apart.
|
package/dist/aggregate.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Codec } from './codec.js';
|
|
2
2
|
import type { SerializedEvent } from './event.js';
|
|
3
3
|
import { type JsonValue } from './json.js';
|
|
4
|
+
import type { ProjectorGroup } from './repository.js';
|
|
4
5
|
/**
|
|
5
6
|
* Migrates a stored payload of its event type before it is decoded, taking an event at the
|
|
6
7
|
* upcaster's from version to its to version. An event type's upcasters run in declaration order,
|
|
@@ -68,6 +69,31 @@ export interface AggregateDefinition<S, C, D extends EventDefinitions> {
|
|
|
68
69
|
* snapshots.
|
|
69
70
|
*/
|
|
70
71
|
readonly snapshotInterval?: number | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* The projectors whose operations every commit of this type writes in the same transaction as
|
|
74
|
+
* its events, in this order, such as the DynamoDB backend's `viewProjector`.
|
|
75
|
+
*/
|
|
76
|
+
readonly projectors?: readonly Projector<S>[] | undefined;
|
|
77
|
+
}
|
|
78
|
+
/** What a projector sees of a commit: its entity, the state after its events, and the events. */
|
|
79
|
+
export interface Projection<S> {
|
|
80
|
+
readonly entityType: string;
|
|
81
|
+
readonly entityId: string;
|
|
82
|
+
/** The state after the commit's events. */
|
|
83
|
+
readonly state: S;
|
|
84
|
+
/** The state as the aggregate's state codec encodes it, as a snapshot stores it. */
|
|
85
|
+
readonly encodedState: Uint8Array;
|
|
86
|
+
/** Copies of the commit's events, as they are stored. */
|
|
87
|
+
readonly events: readonly Readonly<SerializedEvent>[];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Builds the operations a commit writes beside its events, in the same transaction, such as a
|
|
91
|
+
* view of the entity's state. Its group names the backend that writes it. `project` returns null
|
|
92
|
+
* when the commit needs none. It runs only for a commit with events, again on every retry, and it
|
|
93
|
+
* must not fail or change the state.
|
|
94
|
+
*/
|
|
95
|
+
export interface Projector<S> {
|
|
96
|
+
project(projection: Projection<S>): ProjectorGroup | null;
|
|
71
97
|
}
|
|
72
98
|
/** A checked aggregate type. */
|
|
73
99
|
export type Aggregate<S, C, D extends EventDefinitions> = Readonly<AggregateDefinition<S, C, D>>;
|
package/dist/aggregate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,YAAY,CAAA;AAErC,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAC,KAAK,SAAS,EAA2B,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"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"}
|
package/dist/aggregate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,gFAAgF;AAGhF,OAAO,EAAC,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAC,MAAM,aAAa,CAAA;AAE5D,OAAO,EAAiB,SAAS,EAAE,aAAa,EAAC,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"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"}
|
package/dist/dynamodb/head.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export interface HeadAction {
|
|
|
25
25
|
* retention.
|
|
26
26
|
*/
|
|
27
27
|
export declare function headUpdate(entityId: string, first: bigint, last: bigint, ttl?: number): HeadAction;
|
|
28
|
+
/** The condition of a fence on a stream with no head: it still has none. */
|
|
29
|
+
export declare const FENCE_ABSENT_CONDITION = "attribute_not_exists(head)";
|
|
30
|
+
/** The condition of a fence on a stream with a head: it still equals the one observed. */
|
|
31
|
+
export declare const FENCE_CONDITION = "head = :head";
|
|
32
|
+
/** The `ConditionCheck` a transaction fences a stream it read at `head` with. */
|
|
33
|
+
export declare function headFence(entityId: string, head: bigint): HeadAction;
|
|
28
34
|
/**
|
|
29
35
|
* An action's size in the commit budget, by the rule for any update, delete or condition check:
|
|
30
36
|
* its key attributes plus its expression strings, name placeholders (the placeholder and the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../../src/dynamodb/head.ts"],"names":[],"mappings":"AAQA,4EAA4E;AAC5E,eAAO,MAAM,WAAW,qBAAqB,CAAA;AAE7C,iGAAiG;AACjG,eAAO,MAAM,oBAAoB,gCAAgC,CAAA;AAEjE,0FAA0F;AAC1F,eAAO,MAAM,qBAAqB,+BAA+B,CAAA;AAEjE,gGAAgG;AAChG,eAAO,MAAM,qBAAqB,iBAAiB,CAAA;AAEnD,oFAAoF;AACpF,eAAO,MAAM,8BAA8B,gEACoB,CAAA;AAE/D,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,kEAAkE;IAClE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAA;IACzB,2DAA2D;IAC3D,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAA;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,GACX,UAAU,CA0BZ;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAazD"}
|
|
1
|
+
{"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../../src/dynamodb/head.ts"],"names":[],"mappings":"AAQA,4EAA4E;AAC5E,eAAO,MAAM,WAAW,qBAAqB,CAAA;AAE7C,iGAAiG;AACjG,eAAO,MAAM,oBAAoB,gCAAgC,CAAA;AAEjE,0FAA0F;AAC1F,eAAO,MAAM,qBAAqB,+BAA+B,CAAA;AAEjE,gGAAgG;AAChG,eAAO,MAAM,qBAAqB,iBAAiB,CAAA;AAEnD,oFAAoF;AACpF,eAAO,MAAM,8BAA8B,gEACoB,CAAA;AAE/D,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,kEAAkE;IAClE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAA;IACzB,2DAA2D;IAC3D,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAA;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,GACX,UAAU,CA0BZ;AAED,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,+BAA+B,CAAA;AAElE,0FAA0F;AAC1F,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAMpE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAazD"}
|
package/dist/dynamodb/head.js
CHANGED
|
@@ -43,6 +43,17 @@ export function headUpdate(entityId, first, last, ttl) {
|
|
|
43
43
|
values,
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
/** The condition of a fence on a stream with no head: it still has none. */
|
|
47
|
+
export const FENCE_ABSENT_CONDITION = 'attribute_not_exists(head)';
|
|
48
|
+
/** The condition of a fence on a stream with a head: it still equals the one observed. */
|
|
49
|
+
export const FENCE_CONDITION = 'head = :head';
|
|
50
|
+
/** The `ConditionCheck` a transaction fences a stream it read at `head` with. */
|
|
51
|
+
export function headFence(entityId, head) {
|
|
52
|
+
const check = { entityId, updateExpression: null, names: [] };
|
|
53
|
+
return head === 0n
|
|
54
|
+
? { ...check, conditionExpression: FENCE_ABSENT_CONDITION, values: [] }
|
|
55
|
+
: { ...check, conditionExpression: FENCE_CONDITION, values: [[':head', head]] };
|
|
56
|
+
}
|
|
46
57
|
/**
|
|
47
58
|
* An action's size in the commit budget, by the rule for any update, delete or condition check:
|
|
48
59
|
* its key attributes plus its expression strings, name placeholders (the placeholder and the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"head.js","sourceRoot":"","sources":["../../src/dynamodb/head.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,4FAA4F;AAC5F,wFAAwF;AAExF,OAAO,EAAC,mBAAmB,EAAC,MAAM,cAAc,CAAA;AAChD,OAAO,EAAC,UAAU,EAAC,MAAM,YAAY,CAAA;AACrC,OAAO,EAAC,aAAa,EAAE,QAAQ,EAAC,MAAM,WAAW,CAAA;AAEjD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAA;AAE7C,iGAAiG;AACjG,MAAM,CAAC,MAAM,oBAAoB,GAAG,6BAA6B,CAAA;AAEjE,0FAA0F;AAC1F,MAAM,CAAC,MAAM,qBAAqB,GAAG,4BAA4B,CAAA;AAEjE,gGAAgG;AAChG,MAAM,CAAC,MAAM,qBAAqB,GAAG,cAAc,CAAA;AAEnD,oFAAoF;AACpF,MAAM,CAAC,MAAM,8BAA8B,GACzC,6DAA6D,CAAA;AAc/D;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,QAAgB,EAChB,KAAa,EACb,IAAY,EACZ,GAAY;IAEZ,MAAM,MAAM,GAAG,KAAK,IAAI,EAAE,CAAA;IAC1B,MAAM,MAAM,GAAuB,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAA;IACpD,MAAM,KAAK,GAAuB,EAAE,CAAA;IAEpC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,KAAK,GAAG,EAAE,CAAC,CAAC,CAAA;IACpC,CAAC;IACD,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;QAC3B,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9D,CAAC;IAED,IAAI,mBAAmB,GAAG,qBAAqB,CAAA;IAC/C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,mBAAmB;YACjB,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,8BAA8B,CAAA;IAC9E,CAAC;IAED,OAAO;QACL,QAAQ;QACR,gBAAgB,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,oBAAoB;QACxE,mBAAmB;QACnB,KAAK;QACL,MAAM;KACP,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAkB;IAC/C,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAC,EAAE,EAAE,EAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,EAAC,EAAE,EAAE,EAAE,EAAC,CAAC,EAAE,aAAa,CAAC,QAAQ,EAAE,EAAC,EAAC,CAAC,CAAA;IACnF,IAAI,IAAI,GACN,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAA;IAE1F,KAAK,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAC/C,IAAI,IAAI,UAAU,CAAC,WAAW,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IACpD,CAAC;IACD,KAAK,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClD,IAAI,IAAI,UAAU,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAA;IAC5D,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
1
|
+
{"version":3,"file":"head.js","sourceRoot":"","sources":["../../src/dynamodb/head.ts"],"names":[],"mappings":"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"}
|
package/dist/dynamodb/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export { type AttributeValue, type Item, DEFAULT_VIEW_SORT_KEY, HEAD_SORT_KEY, S
|
|
|
2
2
|
export { type ConditionCheckAction, type DeleteAction, type DynamoClient, type Expressions, type GetItemInput, type Page, type PutAction, type PutItemInput, type QueryInput, type RequestOptions, type ScanInput, type TransactItem, type UpdateAction, } from './client.js';
|
|
3
3
|
export { FEATURES_PK, REBUILD_LOCK_PK, manifestFromItem, markerItem } from './marker.js';
|
|
4
4
|
export { DynamoRepository, type DynamoRepositoryOptions } from './repository.js';
|
|
5
|
-
export {
|
|
5
|
+
export { DYNAMODB_BACKEND, transactItemSize, viewProjector } from './views.js';
|
|
6
|
+
export { type CommitTtl, type SnapshotTtl, commitTransaction, snapshotTransaction, transactionItems, } from './requests.js';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dynamodb/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,MAAM,EACN,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,WAAW,EACX,QAAQ,GACT,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAC,WAAW,EAAE,eAAe,EAAE,gBAAgB,EAAE,UAAU,EAAC,MAAM,aAAa,CAAA;AACtF,OAAO,EAAC,gBAAgB,EAAE,KAAK,uBAAuB,EAAC,MAAM,iBAAiB,CAAA;AAC9E,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,iBAAiB,EACjB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dynamodb/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,MAAM,EACN,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,WAAW,EACX,QAAQ,GACT,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAC,WAAW,EAAE,eAAe,EAAE,gBAAgB,EAAE,UAAU,EAAC,MAAM,aAAa,CAAA;AACtF,OAAO,EAAC,gBAAgB,EAAE,KAAK,uBAAuB,EAAC,MAAM,iBAAiB,CAAA;AAC9E,OAAO,EAAC,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAC,MAAM,YAAY,CAAA;AAC5E,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,eAAe,CAAA"}
|
package/dist/dynamodb/index.js
CHANGED
|
@@ -4,5 +4,6 @@ export { DEFAULT_VIEW_SORT_KEY, HEAD_SORT_KEY, SNAPSHOT_SORT_KEY, eventFromItem,
|
|
|
4
4
|
export {} from './client.js';
|
|
5
5
|
export { FEATURES_PK, REBUILD_LOCK_PK, manifestFromItem, markerItem } from './marker.js';
|
|
6
6
|
export { DynamoRepository } from './repository.js';
|
|
7
|
-
export {
|
|
7
|
+
export { DYNAMODB_BACKEND, transactItemSize, viewProjector } from './views.js';
|
|
8
|
+
export { commitTransaction, snapshotTransaction, transactionItems, } from './requests.js';
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dynamodb/index.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,+FAA+F;AAE/F,OAAO,EAGL,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,MAAM,EACN,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,WAAW,EACX,QAAQ,GACT,MAAM,WAAW,CAAA;AAClB,OAAO,EAcN,MAAM,aAAa,CAAA;AACpB,OAAO,EAAC,WAAW,EAAE,eAAe,EAAE,gBAAgB,EAAE,UAAU,EAAC,MAAM,aAAa,CAAA;AACtF,OAAO,EAAC,gBAAgB,EAA+B,MAAM,iBAAiB,CAAA;AAC9E,OAAO,EAGL,iBAAiB,EACjB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dynamodb/index.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,+FAA+F;AAE/F,OAAO,EAGL,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,MAAM,EACN,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,WAAW,EACX,QAAQ,GACT,MAAM,WAAW,CAAA;AAClB,OAAO,EAcN,MAAM,aAAa,CAAA;AACpB,OAAO,EAAC,WAAW,EAAE,eAAe,EAAE,gBAAgB,EAAE,UAAU,EAAC,MAAM,aAAa,CAAA;AACtF,OAAO,EAAC,gBAAgB,EAA+B,MAAM,iBAAiB,CAAA;AAC9E,OAAO,EAAC,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAC,MAAM,YAAY,CAAA;AAC5E,OAAO,EAGL,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,eAAe,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SerializedEvent, SerializedSnapshot } from '../event.js';
|
|
2
|
-
import { type CallOptions, type Commit, type
|
|
2
|
+
import { type CallOptions, type Commit, type StoreInitializer, type TransactionCommit, type TransactionalRepository } from '../repository.js';
|
|
3
3
|
import type { DynamoClient } from './client.js';
|
|
4
4
|
/** How a {@link DynamoRepository} reads and initializes its store. */
|
|
5
5
|
export interface DynamoRepositoryOptions {
|
|
@@ -39,7 +39,7 @@ export interface DynamoRepositoryOptions {
|
|
|
39
39
|
* declares a feature, the next write fails, validates the manifest again, and either refuses an
|
|
40
40
|
* unsupported feature or fails once with a retryable {@link FeatureManifestChangedError}.
|
|
41
41
|
*/
|
|
42
|
-
export declare class DynamoRepository implements
|
|
42
|
+
export declare class DynamoRepository implements TransactionalRepository, StoreInitializer {
|
|
43
43
|
#private;
|
|
44
44
|
constructor(client: DynamoClient, eventsTable: string, options?: DynamoRepositoryOptions);
|
|
45
45
|
/** The events table this repository writes. */
|
|
@@ -51,6 +51,13 @@ export declare class DynamoRepository implements Repository, StoreInitializer {
|
|
|
51
51
|
*/
|
|
52
52
|
initialize(options?: CallOptions): Promise<boolean>;
|
|
53
53
|
commit(commit: Commit, options?: CallOptions): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Commits every stream and checks every fence in one `TransactWriteItems` call: the control
|
|
56
|
+
* checks, the projector operations, each written stream's actions, then a check on each fenced
|
|
57
|
+
* stream's head. A fence on a type this repository retains is refused before any request, and
|
|
58
|
+
* one on a type the store's manifest retains after the marker's validation.
|
|
59
|
+
*/
|
|
60
|
+
commitTransaction(transaction: TransactionCommit, options?: CallOptions): Promise<void>;
|
|
54
61
|
events(entityId: string, options?: CallOptions): Promise<SerializedEvent[]>;
|
|
55
62
|
eventsAfter(entityId: string, after: bigint, options?: CallOptions): Promise<SerializedEvent[]>;
|
|
56
63
|
latest(entityId: string, n: number, options?: CallOptions): Promise<SerializedEvent[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/dynamodb/repository.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/dynamodb/repository.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAC,eAAe,EAAE,kBAAkB,EAAC,MAAM,aAAa,CAAA;AASpE,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,MAAM,EAEX,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAS7B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAC,YAAY,EAAqC,MAAM,aAAa,CAAA;AAwBjF,sEAAsE;AACtE,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1E;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB;AAoBD;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,YAAW,uBAAuB,EAAE,gBAAgB;;IAgBhF,YAAY,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,uBAA4B,EAO3F;IAED,+CAA+C;IAC/C,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;;OAIG;IACG,UAAU,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAyBxD;IAEK,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAOjE;IAED;;;;;OAKG;IACG,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAM5F;IA4CK,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAEhF;IAEK,WAAW,CACf,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAa5B;IAEK,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAmB3F;IAEK,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAI1F;IAEK,WAAW,CAAC,QAAQ,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAsEvF;IAEK,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA+C5F;CAunBF"}
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
// Pages, retries and the probe's reads each depend on the call before them, so they await in loops
|
|
9
9
|
// on purpose.
|
|
10
10
|
/* oxlint-disable no-await-in-loop */
|
|
11
|
-
import { CompactionUncoveredError, ConflictError, EvtError, FeatureManifestChangedError, HistoryDependentProjectionError, RebuildInProgressError, SequenceGapError, StoreNotInitializedError, StreamHeadDriftError, StreamHeadsNotMigratedError, TransientError, } from '../errors.js';
|
|
11
|
+
import { CompactionUncoveredError, ConflictError, EvtError, FeatureManifestChangedError, FenceConflictError, HistoryDependentProjectionError, RebuildInProgressError, SequenceGapError, StoreNotInitializedError, StreamHeadDriftError, StreamHeadsNotMigratedError, TransientError, } from '../errors.js';
|
|
12
12
|
import { STREAM_HEADS_FEATURE, STREAM_HEADS_VERSION, checkSupported, declaresStreamHeads, } from '../features.js';
|
|
13
13
|
import { int64Argument } from '../fields.js';
|
|
14
|
-
import { checkBudget,
|
|
14
|
+
import { checkBudget, checkFenceRetention, checkNotReserved, isReserved, normalizeTransaction, rankFailures, transactionSizes, validateCommit, } from '../repository.js';
|
|
15
15
|
import { HEAD_SORT_KEY, eventFromItem, expiry, headFromItem, snapshotFromItem, sortKey, ttlFromItem, } from './item.js';
|
|
16
16
|
import { FEATURES_PK, manifestFromItem, markerItem } from './marker.js';
|
|
17
|
-
import {
|
|
17
|
+
import { DYNAMODB_BACKEND, transactItemSize } from './views.js';
|
|
18
|
+
import { controlKey, headKey, projectorItems, buildTransaction, snapshotTransaction, } from './requests.js';
|
|
18
19
|
// A declaration is a read-modify-write; racing declarations retry this many times.
|
|
19
20
|
const MAX_DECLARE_ATTEMPTS = 8;
|
|
20
21
|
// Each page of a scan that only has to find one item.
|
|
@@ -94,22 +95,56 @@ export class DynamoRepository {
|
|
|
94
95
|
options?.signal?.throwIfAborted();
|
|
95
96
|
validateCommit(commit);
|
|
96
97
|
checkNotReserved(commit.entityId);
|
|
97
|
-
if (commit.events.length
|
|
98
|
-
|
|
98
|
+
if (commit.events.length > 0) {
|
|
99
|
+
await this.#write(normalizeTransaction({ streams: [commit] }), options);
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Commits every stream and checks every fence in one `TransactWriteItems` call: the control
|
|
104
|
+
* checks, the projector operations, each written stream's actions, then a check on each fenced
|
|
105
|
+
* stream's head. A fence on a type this repository retains is refused before any request, and
|
|
106
|
+
* one on a type the store's manifest retains after the marker's validation.
|
|
107
|
+
*/
|
|
108
|
+
async commitTransaction(transaction, options) {
|
|
109
|
+
options?.signal?.throwIfAborted();
|
|
110
|
+
const normalized = normalizeTransaction(transaction);
|
|
111
|
+
if (normalized.streams.length > 0 || normalized.fences.length > 0) {
|
|
112
|
+
await this.#write(normalized, options);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async #write(transaction, options) {
|
|
116
|
+
// One `ttl` per entity type for the whole transaction, so a stream's head's is never below one
|
|
117
|
+
// of its own items.
|
|
118
|
+
const ttls = new Map();
|
|
119
|
+
const ttlFor = (entityType) => {
|
|
120
|
+
if (!ttls.has(entityType)) {
|
|
121
|
+
ttls.set(entityType, this.#ttlFor(entityType));
|
|
122
|
+
}
|
|
123
|
+
return ttls.get(entityType);
|
|
124
|
+
};
|
|
103
125
|
// Every backend budgets a commit the same way, so an oversized one fails before any request.
|
|
104
|
-
|
|
126
|
+
// The projector operations are sized from the actions this repository sends, which refuses a
|
|
127
|
+
// group for another backend here too.
|
|
128
|
+
const sizes = projectorItems(transaction.projections).map(transactItemSize);
|
|
129
|
+
const projections = [{ backend: DYNAMODB_BACKEND, operations: sizes, sizes }];
|
|
130
|
+
checkBudget(transactionSizes({ ...transaction, projections }, ttlFor));
|
|
131
|
+
// A fence on a type this repository retains needs no request to refuse.
|
|
132
|
+
checkFenceRetention(transaction, (entityType) => this.#retention.has(entityType));
|
|
105
133
|
const request = requestOptions(options);
|
|
106
134
|
const version = await this.#markerVersion(request);
|
|
107
|
-
|
|
135
|
+
if (transaction.fences.length > 0) {
|
|
136
|
+
// The store's manifest may retain a type this repository doesn't. The marker check holds it
|
|
137
|
+
// at the version read here, or fails the transaction.
|
|
138
|
+
const manifest = await this.#readManifest(request);
|
|
139
|
+
const retention = manifest?.retention ?? [];
|
|
140
|
+
checkFenceRetention(transaction, (entityType) => retention.includes(entityType));
|
|
141
|
+
}
|
|
142
|
+
const { items, layout } = buildTransaction(this.#table, transaction, version, (commit) => this.#commitTtl(commit, ttlFor(commit.events[0].entityType)));
|
|
108
143
|
try {
|
|
109
144
|
await this.#client.transactWriteItems({ TransactItems: items }, request);
|
|
110
145
|
}
|
|
111
146
|
catch (error) {
|
|
112
|
-
throw await this.#classifyCommit(
|
|
147
|
+
throw await this.#classifyCommit(layout, error, request);
|
|
113
148
|
}
|
|
114
149
|
}
|
|
115
150
|
async events(entityId, options) {
|
|
@@ -176,6 +211,9 @@ export class DynamoRepository {
|
|
|
176
211
|
if (codes?.some((code) => THROTTLED.has(code))) {
|
|
177
212
|
throw new TransientError('the snapshot write was throttled', { cause: error });
|
|
178
213
|
}
|
|
214
|
+
if (codes?.includes('TransactionConflict')) {
|
|
215
|
+
throw new TransientError(`transaction conflict on ${snapshot.entityId}: another writer was committing at the same time`, { cause: error });
|
|
216
|
+
}
|
|
179
217
|
throw error;
|
|
180
218
|
}
|
|
181
219
|
// The head guard failed. A refreshing guard can fail on its ttl clause alone, when another
|
|
@@ -357,15 +395,22 @@ export class DynamoRepository {
|
|
|
357
395
|
}
|
|
358
396
|
return new FeatureManifestChangedError({ cause });
|
|
359
397
|
}
|
|
360
|
-
|
|
398
|
+
// Maps a cancelled transaction to the error it reports. The control checks outrank everything.
|
|
399
|
+
// Then every failed stream and fence is classified, and the ranking reports the first drift, else
|
|
400
|
+
// the first gap, else the first other failure, in transaction order.
|
|
401
|
+
async #classifyCommit(layout, error, request) {
|
|
361
402
|
const codes = cancellationCodes(error);
|
|
362
403
|
if (codes === null) {
|
|
363
404
|
return error;
|
|
364
405
|
}
|
|
365
406
|
const failed = codes.indexOf(CONDITION_FAILED);
|
|
366
407
|
if (failed === -1) {
|
|
367
|
-
|
|
368
|
-
|
|
408
|
+
// Another transaction was writing one of the items, perhaps one this commit never changes
|
|
409
|
+
// (a control check): nothing was written and nothing is known to be stale, so it is
|
|
410
|
+
// transient, like throttling, and never a conflict, as in Go and Rust.
|
|
411
|
+
const colliding = codes.indexOf('TransactionConflict');
|
|
412
|
+
if (colliding !== -1) {
|
|
413
|
+
return new TransientError(`transaction conflict on ${entityAt(layout, colliding)}: another writer was committing at the same time`, { cause: error });
|
|
369
414
|
}
|
|
370
415
|
if (codes.some((code) => THROTTLED.has(code))) {
|
|
371
416
|
return new TransientError('the commit was throttled', { cause: error });
|
|
@@ -379,14 +424,42 @@ export class DynamoRepository {
|
|
|
379
424
|
if (codes[1] === CONDITION_FAILED) {
|
|
380
425
|
return new RebuildInProgressError();
|
|
381
426
|
}
|
|
382
|
-
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
427
|
+
const failures = [];
|
|
428
|
+
for (const stream of layout.streams) {
|
|
429
|
+
const failure = await this.#streamFailure(stream, codes, request);
|
|
430
|
+
if (failure !== null) {
|
|
431
|
+
failures.push(failure);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
for (const { index, fence } of layout.fences) {
|
|
435
|
+
if (codes[index] !== CONDITION_FAILED) {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const verdict = await this.#probe(fence.entityId, false, request);
|
|
439
|
+
failures.push(verdict.drift ?? new FenceConflictError(fence.entityId, fence.head, fence.provenance));
|
|
440
|
+
}
|
|
441
|
+
const ranked = rankFailures(failures);
|
|
442
|
+
if (ranked !== undefined) {
|
|
443
|
+
return ranked;
|
|
444
|
+
}
|
|
445
|
+
// Only a projector operation's condition failed: its own error, as Go reports it.
|
|
446
|
+
if (failed < layout.projectorEnd) {
|
|
447
|
+
return error;
|
|
448
|
+
}
|
|
449
|
+
return new ConflictError(entityAt(layout, failed), 'conditional check failed: the sequence or snapshot changed since it was read');
|
|
450
|
+
}
|
|
451
|
+
// What a written stream contributes to a failed transaction, or null when none of its actions
|
|
452
|
+
// failed. A failed head update is classified by the probe. A head update that passed while an
|
|
453
|
+
// event put failed is an event above the head, unless retention explains it. A failed snapshot
|
|
454
|
+
// put alone is an ordinary conflict.
|
|
455
|
+
async #streamFailure(stream, codes, request) {
|
|
456
|
+
const { commit } = stream;
|
|
386
457
|
const { entityId } = commit;
|
|
387
458
|
const first = commit.events[0].sequence;
|
|
388
459
|
const retained = this.#retention.has(commit.events[0].entityType);
|
|
389
|
-
|
|
460
|
+
const eventFailed = codes.slice(stream.head + 1, stream.eventsEnd).includes(CONDITION_FAILED);
|
|
461
|
+
const snapshotFailed = stream.snapshot !== null && codes[stream.snapshot] === CONDITION_FAILED;
|
|
462
|
+
if (codes[stream.head] === CONDITION_FAILED) {
|
|
390
463
|
const verdict = await this.#probe(entityId, retained, request);
|
|
391
464
|
if (verdict.drift) {
|
|
392
465
|
return verdict.drift;
|
|
@@ -394,17 +467,21 @@ export class DynamoRepository {
|
|
|
394
467
|
if (verdict.healthy && first > 1n && verdict.stored < first - 1n) {
|
|
395
468
|
return new SequenceGapError(entityId, verdict.stored, first);
|
|
396
469
|
}
|
|
470
|
+
return conflict(entityId);
|
|
397
471
|
}
|
|
398
|
-
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
472
|
+
if (eventFailed && codes[stream.head] === 'None') {
|
|
473
|
+
// With retention, TTL can delete the head before an expired event, so a create passes its
|
|
474
|
+
// head condition and fails on the leftover item: a conflict until TTL deletes the rest. The
|
|
475
|
+
// probe already read the remaining items when it found the head item absent below the head
|
|
476
|
+
// its log derives; read them only if it didn't.
|
|
402
477
|
const verdict = await this.#probe(entityId, retained, request);
|
|
403
|
-
|
|
478
|
+
const expired = retained && (verdict.expired ?? (await this.#remainingItemsExpired(entityId, request)));
|
|
479
|
+
if (!expired) {
|
|
404
480
|
return verdict.drift ?? drift(entityId, verdict.head, verdict.derived);
|
|
405
481
|
}
|
|
482
|
+
return conflict(entityId);
|
|
406
483
|
}
|
|
407
|
-
return
|
|
484
|
+
return eventFailed || snapshotFailed ? conflict(entityId) : null;
|
|
408
485
|
}
|
|
409
486
|
// Classifies a stream whose head update failed, as the failure-path probe does: a stored head
|
|
410
487
|
// below the head its log derives is drift, an equal one is healthy, and one above it is either a
|
|
@@ -416,8 +493,10 @@ export class DynamoRepository {
|
|
|
416
493
|
const head = (await this.#readHead(entityId, request))?.head ?? null;
|
|
417
494
|
const stored = head ?? 0n;
|
|
418
495
|
if (stored < derived) {
|
|
419
|
-
if (retained && head === null
|
|
420
|
-
|
|
496
|
+
if (retained && head === null) {
|
|
497
|
+
const expired = await this.#remainingItemsExpired(entityId, request);
|
|
498
|
+
const drifted = expired ? null : drift(entityId, head, derived);
|
|
499
|
+
return { healthy: false, drift: drifted, stored, head, derived, expired };
|
|
421
500
|
}
|
|
422
501
|
return { healthy: false, drift: drift(entityId, head, derived), stored, head, derived };
|
|
423
502
|
}
|
|
@@ -678,6 +757,23 @@ export class DynamoRepository {
|
|
|
678
757
|
const CONDITION_FAILED = 'ConditionalCheckFailed';
|
|
679
758
|
// The cancellation reasons of a transaction DynamoDB throttled, which Go and Rust retry too.
|
|
680
759
|
const THROTTLED = new Set(['ThrottlingError', 'ProvisionedThroughputExceeded']);
|
|
760
|
+
function conflict(entityId) {
|
|
761
|
+
return new ConflictError(entityId, 'conditional check failed: the sequence or snapshot changed since it was read');
|
|
762
|
+
}
|
|
763
|
+
// The stream an action of a transaction belongs to, for a failure no stream check explains: the
|
|
764
|
+
// written or fenced stream at that index, or else the transaction's first.
|
|
765
|
+
function entityAt(layout, index) {
|
|
766
|
+
for (const stream of layout.streams) {
|
|
767
|
+
if (index >= stream.head && (index < stream.eventsEnd || index === stream.snapshot)) {
|
|
768
|
+
return stream.commit.entityId;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const fenced = layout.fences.find((each) => each.index === index);
|
|
772
|
+
if (fenced !== undefined) {
|
|
773
|
+
return fenced.fence.entityId;
|
|
774
|
+
}
|
|
775
|
+
return layout.streams[0]?.commit.entityId ?? layout.fences[0]?.fence.entityId ?? '';
|
|
776
|
+
}
|
|
681
777
|
function drift(entityId, head, derived) {
|
|
682
778
|
return new StreamHeadDriftError(entityId, head, derived);
|
|
683
779
|
}
|