effect-dynamodb 1.24.0 → 1.26.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/dist/Aggregate.d.ts +12 -2
- package/dist/Aggregate.d.ts.map +1 -1
- package/dist/Aggregate.js +284 -55
- package/dist/Aggregate.js.map +1 -1
- package/dist/Batch.d.ts +14 -5
- package/dist/Batch.d.ts.map +1 -1
- package/dist/Batch.js +184 -119
- package/dist/Batch.js.map +1 -1
- package/dist/Collection.d.ts +9 -1
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +5 -4
- package/dist/Collection.js.map +1 -1
- package/dist/DynamoClient.d.ts +80 -28
- package/dist/DynamoClient.d.ts.map +1 -1
- package/dist/DynamoClient.js +125 -25
- package/dist/DynamoClient.js.map +1 -1
- package/dist/Entity.d.ts +99 -40
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js +693 -302
- package/dist/Entity.js.map +1 -1
- package/dist/EventStore.d.ts +598 -75
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +852 -221
- package/dist/EventStore.js.map +1 -1
- package/dist/Query.d.ts +100 -14
- package/dist/Query.d.ts.map +1 -1
- package/dist/Query.js +275 -71
- package/dist/Query.js.map +1 -1
- package/dist/StorageEncoding.d.ts +160 -0
- package/dist/StorageEncoding.d.ts.map +1 -0
- package/dist/StorageEncoding.js +258 -0
- package/dist/StorageEncoding.js.map +1 -0
- package/dist/Table.d.ts +56 -6
- package/dist/Table.d.ts.map +1 -1
- package/dist/Table.js +142 -60
- package/dist/Table.js.map +1 -1
- package/dist/Transaction.d.ts +8 -0
- package/dist/Transaction.d.ts.map +1 -1
- package/dist/Transaction.js +72 -22
- package/dist/Transaction.js.map +1 -1
- package/dist/VectorSearchEmulation.d.ts +3 -2
- package/dist/VectorSearchEmulation.d.ts.map +1 -1
- package/dist/VectorSearchEmulation.js +14 -2
- package/dist/VectorSearchEmulation.js.map +1 -1
- package/dist/compression/zlib.d.ts +75 -0
- package/dist/compression/zlib.d.ts.map +1 -0
- package/dist/compression/zlib.js +109 -0
- package/dist/compression/zlib.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/BoundCrud.d.ts +39 -35
- package/dist/internal/BoundCrud.d.ts.map +1 -1
- package/dist/internal/BoundCrud.js.map +1 -1
- package/dist/internal/BoundQuery.d.ts +6 -5
- package/dist/internal/BoundQuery.d.ts.map +1 -1
- package/dist/internal/BoundQuery.js.map +1 -1
- package/dist/internal/BoundVectorQuery.d.ts +17 -1
- package/dist/internal/BoundVectorQuery.d.ts.map +1 -1
- package/dist/internal/BoundVectorQuery.js +31 -6
- package/dist/internal/BoundVectorQuery.js.map +1 -1
- package/dist/internal/EntityCombinators.d.ts +8 -5
- package/dist/internal/EntityCombinators.d.ts.map +1 -1
- package/dist/internal/EntityCombinators.js +24 -6
- package/dist/internal/EntityCombinators.js.map +1 -1
- package/dist/internal/EntityOps.d.ts +1 -1
- package/dist/internal/Expr.d.ts +9 -3
- package/dist/internal/Expr.d.ts.map +1 -1
- package/dist/internal/Expr.js +7 -5
- package/dist/internal/Expr.js.map +1 -1
- package/dist/internal/ExpressionLimits.d.ts +45 -0
- package/dist/internal/ExpressionLimits.d.ts.map +1 -0
- package/dist/internal/ExpressionLimits.js +89 -0
- package/dist/internal/ExpressionLimits.js.map +1 -0
- package/dist/internal/Hydrate.d.ts +62 -0
- package/dist/internal/Hydrate.d.ts.map +1 -0
- package/dist/internal/Hydrate.js +191 -0
- package/dist/internal/Hydrate.js.map +1 -0
- package/dist/internal/ItemSize.d.ts +60 -24
- package/dist/internal/ItemSize.d.ts.map +1 -1
- package/dist/internal/ItemSize.js +122 -39
- package/dist/internal/ItemSize.js.map +1 -1
- package/dist/internal/Packed.d.ts +289 -0
- package/dist/internal/Packed.d.ts.map +1 -0
- package/dist/internal/Packed.js +654 -0
- package/dist/internal/Packed.js.map +1 -0
- package/dist/internal/PathBuilder.d.ts +45 -2
- package/dist/internal/PathBuilder.d.ts.map +1 -1
- package/dist/internal/PathBuilder.js +4 -0
- package/dist/internal/PathBuilder.js.map +1 -1
- package/dist/internal/TransactWriteOps.d.ts +22 -46
- package/dist/internal/TransactWriteOps.d.ts.map +1 -1
- package/dist/internal/TransactWriteOps.js +26 -95
- package/dist/internal/TransactWriteOps.js.map +1 -1
- package/dist/internal/TransactableOps.d.ts +1 -1
- package/dist/internal/TransactableOps.d.ts.map +1 -1
- package/dist/internal/TransactableOps.js +2 -0
- package/dist/internal/TransactableOps.js.map +1 -1
- package/dist/internal/WriteDiagnosis.d.ts +353 -0
- package/dist/internal/WriteDiagnosis.d.ts.map +1 -0
- package/dist/internal/WriteDiagnosis.js +1028 -0
- package/dist/internal/WriteDiagnosis.js.map +1 -0
- package/dist/internal/sparse_check.js +43 -79
- package/dist/internal/sparse_check.js.map +1 -1
- package/package.json +6 -2
package/dist/EventStore.d.ts
CHANGED
|
@@ -13,18 +13,26 @@
|
|
|
13
13
|
* `VersionConflict` retry policy, caller-supplied `expectedVersion`,
|
|
14
14
|
* decision-derived `additionalItems`, inline snapshots)
|
|
15
15
|
* - Stream indexes (`makeStream({ indexes })`): sub-streams ordered by a key
|
|
16
|
-
* derived from each event, on an LSI or GSI
|
|
17
|
-
*
|
|
16
|
+
* derived from each event, on an LSI or GSI, each projecting all of the
|
|
17
|
+
* event, all but its metadata, or only its keys — `readIndex`,
|
|
18
|
+
* `query.index`, and `indexDefinitions` for the `CreateTable` fragments
|
|
18
19
|
* - `fold` / `foldFrom` helpers for state reconstruction
|
|
20
|
+
* - Packed storage (`makeStream({ storedAs: { data } })`, `snapshot.storedAs`):
|
|
21
|
+
* event data and snapshot state as one JSON string, optionally compressed —
|
|
22
|
+
* read by the stored form, so a stream may mix native, JSON and compressed
|
|
23
|
+
* items; `measureAppend` / `measureSnapshot` for caller-side size planning,
|
|
24
|
+
* and `decodeStreamImage` for DynamoDB Streams records
|
|
19
25
|
*
|
|
20
26
|
* Built on the existing library primitives (DynamoSchema, KeyComposer, Query,
|
|
21
27
|
* DynamoClient, Marshaller).
|
|
22
28
|
*/
|
|
23
29
|
import type { AttributeDefinition, GlobalSecondaryIndex, LocalSecondaryIndex } from "@aws-sdk/client-dynamodb";
|
|
30
|
+
import { type PackedConfig } from "@effect-dynamodb/schema/DynamoModel.js";
|
|
24
31
|
import * as DynamoSchema from "@effect-dynamodb/schema/DynamoSchema.js";
|
|
25
32
|
import { AdditionalItemConditionFailed, AppendTooLarge, type ConcurrentModification, DuplicateCommand, type OptimisticLockError, TransactionCancelled, type UniqueConstraintViolation, ValidationError, VersionConflict } from "@effect-dynamodb/schema/Errors.js";
|
|
26
|
-
import { type Duration, Effect, Option, Pipeable, Schedule, Schema } from "effect";
|
|
33
|
+
import { DateTime, type Duration, Effect, Option, Pipeable, Schedule, Schema } from "effect";
|
|
27
34
|
import { DynamoClient, type DynamoClientError } from "./DynamoClient.js";
|
|
35
|
+
import { type StreamImage } from "./internal/Packed.js";
|
|
28
36
|
import { type TransactWriteOp } from "./internal/TransactWriteOps.js";
|
|
29
37
|
import * as Query from "./Query.js";
|
|
30
38
|
import { type Table, type TableConfig } from "./Table.js";
|
|
@@ -58,8 +66,35 @@ export interface StreamEvent<A, M = Record<string, unknown> | undefined> {
|
|
|
58
66
|
readonly eventType: string;
|
|
59
67
|
readonly data: A;
|
|
60
68
|
readonly metadata: M;
|
|
69
|
+
/**
|
|
70
|
+
* ISO 8601 (UTC): when the event was appended, by the append's `Clock` — or
|
|
71
|
+
* the `eventTimestamps` entry it was appended with
|
|
72
|
+
* ({@link AppendOptionsBase.eventTimestamps}).
|
|
73
|
+
*/
|
|
61
74
|
readonly timestamp: string;
|
|
62
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* A {@link StreamEvent} together with the event's stored `data` in its wire
|
|
78
|
+
* form — what `decodeStreamImage(image, { raw: true })` returns.
|
|
79
|
+
*/
|
|
80
|
+
export interface RawStreamEvent<A, M = Record<string, unknown> | undefined> extends StreamEvent<A, M> {
|
|
81
|
+
/**
|
|
82
|
+
* The event's `data` as stored, before the event schema decoded it: the
|
|
83
|
+
* parsed JSON of a packed `data` (`{ _tag, ...fields }`), or the unmarshalled
|
|
84
|
+
* map of a native one — fields the event schema does not model included. The
|
|
85
|
+
* same object the decode read, so nothing is parsed twice.
|
|
86
|
+
*/
|
|
87
|
+
readonly rawData: Readonly<Record<string, unknown>>;
|
|
88
|
+
}
|
|
89
|
+
/** Options of `decodeStreamImage`. */
|
|
90
|
+
export interface DecodeStreamImageOptions {
|
|
91
|
+
/**
|
|
92
|
+
* Also return the event's stored `data` in wire form (`rawData`, see
|
|
93
|
+
* {@link RawStreamEvent}) — for a consumer that needs fields the event
|
|
94
|
+
* schema does not model, without parsing the image a second time.
|
|
95
|
+
*/
|
|
96
|
+
readonly raw?: boolean | undefined;
|
|
97
|
+
}
|
|
63
98
|
/**
|
|
64
99
|
* Metadata type carried on {@link StreamEvent} for a stream: the decoded
|
|
65
100
|
* metadata schema type when the stream declares one, an untyped record
|
|
@@ -122,7 +157,69 @@ export interface SnapshotConfig<TSchema extends Schema.Top = Schema.Top> {
|
|
|
122
157
|
readonly schema: TSchema;
|
|
123
158
|
readonly mode?: SnapshotMode | undefined;
|
|
124
159
|
readonly every?: number | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* How the snapshot's `state` is stored: a native map (omitted, the default)
|
|
162
|
+
* or packed — `DynamoModel.Packed({ format: "json", compression? })`, one
|
|
163
|
+
* JSON string, compressed to binary at or over the threshold when that is
|
|
164
|
+
* smaller. Packing shrinks a large state the most: an inline snapshot rides
|
|
165
|
+
* in every append's transaction, and DynamoDB's item and transaction size
|
|
166
|
+
* limits count its **stored** bytes.
|
|
167
|
+
*
|
|
168
|
+
* The state schema's encoded side must be object- or array-shaped
|
|
169
|
+
* (`[EDD-9073]`). Reads go by the stored form, so changing this setting
|
|
170
|
+
* needs no rewrite: a snapshot written before still decodes.
|
|
171
|
+
*/
|
|
172
|
+
readonly storedAs?: PackedConfig | undefined;
|
|
125
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* How a stream stores event `data` (`makeStream({ storedAs })`). `data` is a
|
|
176
|
+
* native map by default; `DynamoModel.Packed({ format: "json", compression? })`
|
|
177
|
+
* stores it as one JSON string (`S`), compressed to binary (`B`) at or over the
|
|
178
|
+
* threshold when that is smaller. Metadata always stays a map.
|
|
179
|
+
*
|
|
180
|
+
* Reads go by each item's stored form, never by this setting, so a stream may
|
|
181
|
+
* hold a mix — events appended before packing was turned on still decode, and
|
|
182
|
+
* so do packed events after it is turned off.
|
|
183
|
+
*/
|
|
184
|
+
export interface StreamStorageConfig {
|
|
185
|
+
readonly data?: PackedConfig | undefined;
|
|
186
|
+
}
|
|
187
|
+
/** The resolved storage settings exposed on a stream (`stream.storedAs`). */
|
|
188
|
+
export interface StreamStorageSettings {
|
|
189
|
+
/** Event `data` (`makeStream({ storedAs: { data } })`); `undefined` = native map. */
|
|
190
|
+
readonly data: PackedConfig | undefined;
|
|
191
|
+
/** Snapshot `state` (`snapshot.storedAs`); `undefined` = native map. */
|
|
192
|
+
readonly state: PackedConfig | undefined;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The bytes an {@link EventStream.append} would write, by DynamoDB's own size
|
|
196
|
+
* accounting — {@link EventStream.measureAppend}. Every number counts stored
|
|
197
|
+
* bytes: packed (and compressed) `data` and `state` as they would be sent.
|
|
198
|
+
*/
|
|
199
|
+
export interface AppendMeasurement {
|
|
200
|
+
/**
|
|
201
|
+
* The item size of each event `Put`, in order — what DynamoDB's 400 KB
|
|
202
|
+
* (409,600-byte) item limit measures.
|
|
203
|
+
*/
|
|
204
|
+
readonly events: ReadonlyArray<number>;
|
|
205
|
+
/** The item size of the idempotency sentinel `Put`, when `idempotency` is given. */
|
|
206
|
+
readonly sentinel: number | undefined;
|
|
207
|
+
/** The item size of the inline snapshot `Put`, when `snapshot` is given. */
|
|
208
|
+
readonly snapshot: number | undefined;
|
|
209
|
+
/**
|
|
210
|
+
* What the stream's own actions (the contiguity check, the event puts, the
|
|
211
|
+
* sentinel and the inline snapshot) add to the `TransactWriteItems` request
|
|
212
|
+
* — its 4 MB (4,194,304-byte) limit counts items, keys and expressions.
|
|
213
|
+
* Caller `additionalItems` are not included: add their size yourself.
|
|
214
|
+
*/
|
|
215
|
+
readonly transactionBytes: number;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The options {@link EventStream.measureAppend} accepts: those of
|
|
219
|
+
* {@link EventStream.append} except `additionalItems`.
|
|
220
|
+
*/
|
|
221
|
+
export type MeasureAppendOptions<TMetadata, TState = never> = Omit<AppendOptionsBase<TState>, "additionalItems"> & AppendMetadataOptions<TMetadata>;
|
|
222
|
+
export type { LambdaAttributeValue, StreamImage } from "./internal/Packed.js";
|
|
126
223
|
/**
|
|
127
224
|
* How {@link commandHandler} writes snapshots.
|
|
128
225
|
*
|
|
@@ -130,7 +227,7 @@ export interface SnapshotConfig<TSchema extends Schema.Top = Schema.Top> {
|
|
|
130
227
|
* succeeds, at the `every` cadence. A failed snapshot write never fails the
|
|
131
228
|
* command; the snapshot simply lags until the next threshold crossing.
|
|
132
229
|
* - `"inline"` — the snapshot `Put` rides in the append's own transaction (see
|
|
133
|
-
* {@link
|
|
230
|
+
* {@link AppendOptionsBase.snapshot}), on every append, or at the `every` cadence
|
|
134
231
|
* when one is set. The snapshot is then current after every command that
|
|
135
232
|
* writes one, so {@link EventStream.readLatest} loads state in one request.
|
|
136
233
|
* The snapshot counts towards the transaction's item and size limits.
|
|
@@ -153,7 +250,7 @@ export interface SnapshotSettings {
|
|
|
153
250
|
* redelivery whose `decide` returns no events against the loaded state never
|
|
154
251
|
* appends, so it succeeds as a no-op instead of failing with
|
|
155
252
|
* `DuplicateCommand`. Whether a no-op redelivery needs to be told apart is the
|
|
156
|
-
* application's call (see {@link
|
|
253
|
+
* application's call (see {@link CommandOptionsBase.commandId}).
|
|
157
254
|
*
|
|
158
255
|
* The sentinel is co-located in the stream's own partition, so `commandId`
|
|
159
256
|
* uniqueness is scoped to the stream (which is what "have I already applied this
|
|
@@ -178,14 +275,46 @@ export interface AppendIdempotency {
|
|
|
178
275
|
readonly ttl?: Duration.Duration | string;
|
|
179
276
|
}
|
|
180
277
|
/**
|
|
181
|
-
*
|
|
278
|
+
* The metadata of an {@link EventStream.append}: one value shared by every
|
|
279
|
+
* event (`metadata`), or one entry per event (`eventMetadata`) — never both.
|
|
280
|
+
*
|
|
281
|
+
* Passing both is a compile error, and a `ValidationError` at runtime for an
|
|
282
|
+
* untyped caller. `undefined` counts as absent for either, so
|
|
283
|
+
* `{ metadata: undefined, eventMetadata: [...] }` is not "both".
|
|
284
|
+
*/
|
|
285
|
+
export type AppendMetadataOptions<TMetadata> = {
|
|
286
|
+
/**
|
|
287
|
+
* Metadata written onto **every** event of the append — a copy per item,
|
|
288
|
+
* encoded once through the stream's metadata schema when one is
|
|
289
|
+
* configured. For metadata that belongs to each event (an event id, the
|
|
290
|
+
* original occurrence time of an imported event) use `eventMetadata`:
|
|
291
|
+
* packing a list into `metadata` stores every entry on every item, so
|
|
292
|
+
* stored metadata grows with the square of the append size.
|
|
293
|
+
*/
|
|
294
|
+
readonly metadata?: TMetadata;
|
|
295
|
+
readonly eventMetadata?: undefined;
|
|
296
|
+
} | {
|
|
297
|
+
readonly metadata?: undefined;
|
|
298
|
+
/**
|
|
299
|
+
* Per-event metadata: entry `i` is written onto event `i` (and read back
|
|
300
|
+
* as that event's `StreamEvent.metadata`). Each defined entry is encoded
|
|
301
|
+
* through the stream's metadata schema, as `metadata` is; an `undefined`
|
|
302
|
+
* entry writes its event without a `metadata` attribute.
|
|
303
|
+
*
|
|
304
|
+
* Must have exactly one entry per event — a length mismatch fails with
|
|
305
|
+
* `ValidationError` before anything is read or written, also for an
|
|
306
|
+
* append of no events (where only `[]` is accepted).
|
|
307
|
+
*/
|
|
308
|
+
readonly eventMetadata?: ReadonlyArray<TMetadata | undefined>;
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* The options of {@link EventStream.append} other than its metadata (see
|
|
312
|
+
* {@link AppendMetadataOptions}).
|
|
182
313
|
*
|
|
183
314
|
* `TState` is the stream's snapshot state type (`never` for a stream without a
|
|
184
315
|
* `snapshot` config, so `snapshot` cannot be supplied there).
|
|
185
316
|
*/
|
|
186
|
-
export interface
|
|
187
|
-
/** Per-append metadata, validated against the stream's metadata schema when configured. */
|
|
188
|
-
readonly metadata?: TMetadata;
|
|
317
|
+
export interface AppendOptionsBase<TState = never> {
|
|
189
318
|
/**
|
|
190
319
|
* Caller-owned transact items committed atomically with the events — the same
|
|
191
320
|
* op union `Transaction.transactWrite` accepts (`EntityPut`, `EntityDelete`,
|
|
@@ -224,7 +353,34 @@ export interface AppendOptions<TMetadata, TState = never> {
|
|
|
224
353
|
* is `never`) it dies with `[EDD-9026]`, as `writeSnapshot` does.
|
|
225
354
|
*/
|
|
226
355
|
readonly snapshot?: TState;
|
|
356
|
+
/**
|
|
357
|
+
* Per-event timestamps: entry `i` is written as event `i`'s `timestamp` (and
|
|
358
|
+
* read back as that event's `StreamEvent.timestamp`), for history imported
|
|
359
|
+
* with its original occurrence times. An `undefined` entry keeps the
|
|
360
|
+
* append's `Clock` time. Independent of `metadata` / `eventMetadata`.
|
|
361
|
+
*
|
|
362
|
+
* Written with `DateTime.formatIso` — the format the append's own time is
|
|
363
|
+
* written in — so a zoned `DateTime` is stored as its UTC instant.
|
|
364
|
+
*
|
|
365
|
+
* - Must have exactly one entry per event, each a valid `DateTime` (finite
|
|
366
|
+
* epoch) or `undefined` — anything else fails with `ValidationError` before anything is read or
|
|
367
|
+
* written, also for an append of no events (where only `[]` is accepted).
|
|
368
|
+
* - Only the event items change: the idempotency sentinel, its TTL and the
|
|
369
|
+
* inline snapshot record when the append was written.
|
|
370
|
+
* - No ordering is enforced. `version` orders a stream; timestamps may
|
|
371
|
+
* repeat, run backwards or lie in the future, as imported history can.
|
|
372
|
+
*/
|
|
373
|
+
readonly eventTimestamps?: ReadonlyArray<DateTime.DateTime | undefined>;
|
|
227
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Options accepted by {@link EventStream.append}: {@link AppendOptionsBase}
|
|
377
|
+
* plus the exclusive `metadata` / `eventMetadata` pair
|
|
378
|
+
* ({@link AppendMetadataOptions}).
|
|
379
|
+
*
|
|
380
|
+
* A type alias rather than an interface, since it is an intersection with a
|
|
381
|
+
* union: an interface cannot extend it.
|
|
382
|
+
*/
|
|
383
|
+
export type AppendOptions<TMetadata, TState = never> = AppendOptionsBase<TState> & AppendMetadataOptions<TMetadata>;
|
|
228
384
|
/** Error channel of {@link EventStream.append}. */
|
|
229
385
|
export type AppendError = VersionConflict | DuplicateCommand | AdditionalItemConditionFailed | AppendTooLarge | DynamoClientError | ValidationError | TransactionCancelled | UniqueConstraintViolation | OptimisticLockError | ConcurrentModification;
|
|
230
386
|
/**
|
|
@@ -298,8 +454,10 @@ export interface LatestState<TState, TEvent, M = Record<string, unknown> | undef
|
|
|
298
454
|
* - `undefined` leaves the event out of the index (a sparse index).
|
|
299
455
|
* - An empty string (or any other value) fails the append with
|
|
300
456
|
* `ValidationError` before anything is written — DynamoDB rejects empty key
|
|
301
|
-
* values
|
|
302
|
-
*
|
|
457
|
+
* values — as does a `key` that throws. A key over DynamoDB's 1024-byte
|
|
458
|
+
* sort-key limit is sent and rejected by DynamoDB; the append then fails
|
|
459
|
+
* with `ValidationError` naming the index and the event (#144), and nothing
|
|
460
|
+
* is written.
|
|
303
461
|
*
|
|
304
462
|
* The key is derived only when an event is appended. Events are never
|
|
305
463
|
* rewritten, so changing `key` later leaves events already stored under their
|
|
@@ -322,9 +480,9 @@ export type StreamIndexKey<TEvent> = (event: TEvent, version: number) => string
|
|
|
322
480
|
* An LSI must be created with the table. A table with any LSI caps the item
|
|
323
481
|
* collection of **every** partition key value in it at 10 GB — not only the
|
|
324
482
|
* indexed stream's partitions, but every stream's (indexed or not) and every
|
|
325
|
-
* entity partition sharing the table; a write that would exceed it fails.
|
|
326
|
-
*
|
|
327
|
-
*
|
|
483
|
+
* entity partition sharing the table; a write that would exceed it fails. What
|
|
484
|
+
* the index copies from each event is its `projection` — see
|
|
485
|
+
* {@link StreamIndexProjection}.
|
|
328
486
|
*/
|
|
329
487
|
export interface LocalStreamIndexConfig<TEvent> {
|
|
330
488
|
readonly type?: "lsi" | undefined;
|
|
@@ -333,13 +491,19 @@ export interface LocalStreamIndexConfig<TEvent> {
|
|
|
333
491
|
/** An LSI uses the table partition key; supplying `pk` is refused (`[EDD-9063]`). */
|
|
334
492
|
readonly pk?: undefined;
|
|
335
493
|
readonly key: StreamIndexKey<TEvent>;
|
|
494
|
+
/**
|
|
495
|
+
* What the index copies from each event — see {@link StreamIndexProjection}.
|
|
496
|
+
* Default `"all"`. Must match the deployed index.
|
|
497
|
+
*/
|
|
498
|
+
readonly projection?: StreamIndexProjection | undefined;
|
|
336
499
|
}
|
|
337
500
|
/**
|
|
338
501
|
* A stream index on a **global** secondary index. `append` writes the stream's
|
|
339
502
|
* partition key value into the `pk` attribute of each indexed event, so the
|
|
340
503
|
* index is scoped to the same stream: the eventually consistent equivalent of
|
|
341
|
-
* an LSI, without the LSI's 10 GB item-collection cap.
|
|
342
|
-
* read of it
|
|
504
|
+
* an LSI, without the LSI's 10 GB item-collection cap. DynamoDB rejects a
|
|
505
|
+
* strongly consistent read of it, diagnosed as a `ValidationError` naming the
|
|
506
|
+
* index.
|
|
343
507
|
*
|
|
344
508
|
* Unlike an LSI, the GSI itself can be added to an existing table
|
|
345
509
|
* (`UpdateTable`), but that does not index the stream's existing events: the
|
|
@@ -350,6 +514,7 @@ export interface LocalStreamIndexConfig<TEvent> {
|
|
|
350
514
|
* - `pk` — the attribute carrying the stream's partition key (the GSI's HASH key).
|
|
351
515
|
* - `sk` — the attribute carrying the derived key (the GSI's RANGE key).
|
|
352
516
|
* - `key` — see {@link StreamIndexKey}.
|
|
517
|
+
* - `projection` — see {@link StreamIndexProjection}.
|
|
353
518
|
*/
|
|
354
519
|
export interface GlobalStreamIndexConfig<TEvent> {
|
|
355
520
|
readonly type: "gsi";
|
|
@@ -357,6 +522,11 @@ export interface GlobalStreamIndexConfig<TEvent> {
|
|
|
357
522
|
readonly pk: string;
|
|
358
523
|
readonly sk: string;
|
|
359
524
|
readonly key: StreamIndexKey<TEvent>;
|
|
525
|
+
/**
|
|
526
|
+
* What the index copies from each event — see {@link StreamIndexProjection}.
|
|
527
|
+
* Default `"all"`. Must match the deployed index.
|
|
528
|
+
*/
|
|
529
|
+
readonly projection?: StreamIndexProjection | undefined;
|
|
360
530
|
}
|
|
361
531
|
/**
|
|
362
532
|
* One entry of `makeStream({ indexes })`: a sub-stream of the stream's events,
|
|
@@ -364,30 +534,95 @@ export interface GlobalStreamIndexConfig<TEvent> {
|
|
|
364
534
|
* and {@link GlobalStreamIndexConfig}.
|
|
365
535
|
*/
|
|
366
536
|
export type StreamIndexConfig<TEvent> = LocalStreamIndexConfig<TEvent> | GlobalStreamIndexConfig<TEvent>;
|
|
537
|
+
/**
|
|
538
|
+
* What a stream index copies from each event (`projection` on an entry of
|
|
539
|
+
* `makeStream({ indexes })`). {@link indexDefinitions} emits the matching
|
|
540
|
+
* `Projection`, and the reads choose their path from it.
|
|
541
|
+
*
|
|
542
|
+
* | Value | Emitted `Projection` | Read |
|
|
543
|
+
* |---|---|---|
|
|
544
|
+
* | `"all"` (default) | `ALL` | one query |
|
|
545
|
+
* | `"withoutMetadata"` | `INCLUDE` of every event attribute except `metadata` | one query; `metadata` is `undefined` (and typed so) |
|
|
546
|
+
* | `"keys"` | `INCLUDE ["__edd_e__"]` — **not** `KEYS_ONLY` | LSI: one query with `Select: ALL_ATTRIBUTES` (DynamoDB fetches each event from the table). GSI: the query, then `BatchGetItem` for the events of each page, in index order |
|
|
547
|
+
*
|
|
548
|
+
* `__edd_e__` is always projected because every read filters on it, and
|
|
549
|
+
* DynamoDB refuses a filter on an attribute an index does not project.
|
|
550
|
+
*
|
|
551
|
+
* `metadata` is often an event's largest attribute; `"withoutMetadata"` keeps
|
|
552
|
+
* it out of the index, and `"keys"` keeps the index entries minimal at the cost
|
|
553
|
+
* of a table read per event read (rounded up to 4 KB each).
|
|
554
|
+
*
|
|
555
|
+
* **The declared projection must match the deployed index.** The library does
|
|
556
|
+
* not migrate: an LSI's projection is fixed when the table is created, and a
|
|
557
|
+
* GSI's cannot be updated (it must be deleted and created again). Tables
|
|
558
|
+
* deployed with CDK or Terraform copy `Projection` from
|
|
559
|
+
* {@link indexDefinitions}' output. An index deployed narrower than declared
|
|
560
|
+
* fails reads: DynamoDB refuses the `__edd_e__` filter ("does not project one
|
|
561
|
+
* or more filter attributes"), or rows lack event attributes, which the read
|
|
562
|
+
* reports as a `ValidationError` (`operation: "EventStore.index.decode"`)
|
|
563
|
+
* naming the index.
|
|
564
|
+
*/
|
|
565
|
+
export type StreamIndexProjection = "all" | "withoutMetadata" | "keys";
|
|
367
566
|
/**
|
|
368
567
|
* The runtime settings of one stream index, as exposed on
|
|
369
568
|
* {@link EventStream.indexes}. `pk` is the index's partition key attribute:
|
|
370
569
|
* the table's `pk` for an LSI, the configured attribute for a GSI.
|
|
570
|
+
* `projection` is resolved: `"all"` when the index declares none.
|
|
371
571
|
*/
|
|
372
572
|
export interface StreamIndexSettings {
|
|
373
573
|
readonly type: "lsi" | "gsi";
|
|
374
574
|
readonly index: string;
|
|
375
575
|
readonly pk: string;
|
|
376
576
|
readonly sk: string;
|
|
577
|
+
readonly projection: StreamIndexProjection;
|
|
377
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* The `metadata` type of an event read through a stream index of projection
|
|
581
|
+
* `P`: `undefined` for a `"withoutMetadata"` index, which does not hold it,
|
|
582
|
+
* the stream's {@link StreamMetadata} otherwise.
|
|
583
|
+
*/
|
|
584
|
+
export type IndexEventMetadata<TMetadata, P extends StreamIndexProjection> = [P] extends [
|
|
585
|
+
"withoutMetadata"
|
|
586
|
+
] ? undefined : StreamMetadata<TMetadata>;
|
|
587
|
+
/** @internal The `projection` an index config declares (`"all"` when none). */
|
|
588
|
+
type ProjectionOf<C> = C extends {
|
|
589
|
+
readonly projection: infer P extends StreamIndexProjection;
|
|
590
|
+
} ? P : "all";
|
|
591
|
+
/**
|
|
592
|
+
* The projection of each index of a `makeStream({ indexes })` config, by name.
|
|
593
|
+
* `"all"` for an index that declares none.
|
|
594
|
+
*/
|
|
595
|
+
export type IndexProjectionsOf<TIndexes> = {
|
|
596
|
+
readonly [K in keyof TIndexes & string]: ProjectionOf<TIndexes[K]>;
|
|
597
|
+
};
|
|
598
|
+
/** @internal The members a stream index entry may declare. */
|
|
599
|
+
type StreamIndexConfigKey = keyof LocalStreamIndexConfig<unknown> | keyof GlobalStreamIndexConfig<unknown>;
|
|
600
|
+
/**
|
|
601
|
+
* The `indexes` parameter of {@link makeStream}: each entry is a
|
|
602
|
+
* {@link StreamIndexConfig}, and a member it does not declare (a misspelled
|
|
603
|
+
* `projection`, say) is typed `never`, so it is a compile error. Inferring
|
|
604
|
+
* `TIndexes` from the entries turns off TypeScript's own excess-property check,
|
|
605
|
+
* which would otherwise catch it.
|
|
606
|
+
*/
|
|
607
|
+
export type StreamIndexesConfig<TIndexes, TEvent> = {
|
|
608
|
+
readonly [K in keyof TIndexes]: StreamIndexConfig<TEvent> & {
|
|
609
|
+
readonly [P in Exclude<keyof TIndexes[K], StreamIndexConfigKey>]: never;
|
|
610
|
+
};
|
|
611
|
+
};
|
|
378
612
|
/**
|
|
379
613
|
* Options accepted by {@link EventStream.readIndex}.
|
|
380
614
|
*
|
|
381
615
|
* - `beginsWith` / `between` — a sort-key condition on the derived key (at most
|
|
382
|
-
* one of them). Omitted, the whole sub-stream is read.
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
616
|
+
* one of them, string bounds). Omitted, the whole sub-stream is read.
|
|
617
|
+
* DynamoDB rejects an empty bound, and a `between` whose lower bound sorts
|
|
618
|
+
* (by UTF-8 bytes) after its upper bound; the read then fails with a
|
|
619
|
+
* `ValidationError` naming the index and the bound, DynamoDB's error as its
|
|
620
|
+
* cause.
|
|
386
621
|
* - `reverse` — descending key order.
|
|
387
622
|
* - `limit` — return at most this many events (a positive integer).
|
|
388
|
-
* - `consistentRead` — strongly consistent read.
|
|
389
|
-
*
|
|
390
|
-
*
|
|
623
|
+
* - `consistentRead` — strongly consistent read. DynamoDB rejects it on a
|
|
624
|
+
* GSI-backed index, which it reads only eventually consistently: the read
|
|
625
|
+
* fails with a `ValidationError` naming the index.
|
|
391
626
|
*/
|
|
392
627
|
export type ReadIndexOptions = {
|
|
393
628
|
readonly reverse?: boolean | undefined;
|
|
@@ -413,9 +648,15 @@ export type EventStreamTypeId = typeof EventStreamTypeId;
|
|
|
413
648
|
*
|
|
414
649
|
* `TIndexName` is the union of the stream's index names (`makeStream({ indexes })`),
|
|
415
650
|
* `never` for a stream without indexes, so {@link EventStream.readIndex} and
|
|
416
|
-
* `query.index` only accept a declared name.
|
|
651
|
+
* `query.index` only accept a declared name. `TIndexProjections` is each
|
|
652
|
+
* index's {@link StreamIndexProjection} by name, so an event read through a
|
|
653
|
+
* `"withoutMetadata"` index has `metadata: undefined`.
|
|
417
654
|
*/
|
|
418
|
-
export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState = never, TIndexName extends string = never
|
|
655
|
+
export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState = never, TIndexName extends string = never, TIndexProjections extends {
|
|
656
|
+
readonly [K in TIndexName]: StreamIndexProjection;
|
|
657
|
+
} = {
|
|
658
|
+
readonly [K in TIndexName]: "all";
|
|
659
|
+
}> extends Pipeable.Pipeable {
|
|
419
660
|
readonly [EventStreamTypeId]: EventStreamTypeId;
|
|
420
661
|
readonly streamName: string;
|
|
421
662
|
readonly eventSchema: Schema.Top;
|
|
@@ -434,6 +675,11 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
|
|
|
434
675
|
* snapshots.
|
|
435
676
|
*/
|
|
436
677
|
readonly snapshotConfig: SnapshotSettings | undefined;
|
|
678
|
+
/**
|
|
679
|
+
* How the stream stores event `data` and snapshot `state` — see
|
|
680
|
+
* {@link StreamStorageConfig} and {@link SnapshotConfig.storedAs}.
|
|
681
|
+
*/
|
|
682
|
+
readonly storedAs: StreamStorageSettings;
|
|
437
683
|
/**
|
|
438
684
|
* Write (or overwrite) this stream's snapshot.
|
|
439
685
|
*
|
|
@@ -515,22 +761,92 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
|
|
|
515
761
|
* Read the stream's events in the order of one of its indexes (#140) — the
|
|
516
762
|
* events whose index `key` returned a string, ordered by that key. A
|
|
517
763
|
* convenience over `query.index`; pass `beginsWith` or `between` to narrow
|
|
518
|
-
* the key range, `reverse`, `limit` and `consistentRead` (
|
|
519
|
-
* — see {@link ReadIndexOptions}.
|
|
764
|
+
* the key range, `reverse`, `limit` and `consistentRead` (which DynamoDB
|
|
765
|
+
* rejects on a GSI; the error names the index) — see {@link ReadIndexOptions}.
|
|
766
|
+
*
|
|
767
|
+
* How the events are read follows the index's {@link StreamIndexProjection}:
|
|
768
|
+
* one query for `"all"` and `"withoutMetadata"` (whose events have
|
|
769
|
+
* `metadata: undefined`, as the type says); for `"keys"`, one query with
|
|
770
|
+
* `Select: ALL_ATTRIBUTES` on an LSI, or the query followed by `BatchGetItem`
|
|
771
|
+
* (unprocessed keys retried, index order kept) on a GSI.
|
|
772
|
+
*/
|
|
773
|
+
readIndex<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>, options?: ReadIndexOptions | undefined): Effect.Effect<ReadonlyArray<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>, DynamoClientError | ValidationError, DynamoClient | TableConfig>;
|
|
774
|
+
/**
|
|
775
|
+
* The bytes {@link EventStream.append} would write for these arguments, by
|
|
776
|
+
* DynamoDB's own size accounting — for planning chunks against the 400 KB
|
|
777
|
+
* item and 4 MB transaction limits without re-implementing the stream's
|
|
778
|
+
* item layout or packing. The items are built by the same code `append`
|
|
779
|
+
* uses (envelope, keys, packed and compressed `data`, metadata, stream-index
|
|
780
|
+
* attributes, the sentinel, the inline snapshot), so the numbers are exact
|
|
781
|
+
* for the request `append` would send now with this runtime's compressors.
|
|
782
|
+
*
|
|
783
|
+
* Nothing is read or written, and nothing is refused for its size: this is
|
|
784
|
+
* caller-side planning, not a pre-check. `additionalItems` are not accepted
|
|
785
|
+
* — add their size yourself. Fails as `append` would on invalid options
|
|
786
|
+
* (`ValidationError`), and — with a `CompressorNotRegistered` cause — when a
|
|
787
|
+
* value crosses its compression threshold without a registered compressor.
|
|
788
|
+
*/
|
|
789
|
+
measureAppend(streamId: StreamIdInput<TStreamIdFields>, events: ReadonlyArray<TEvent>, expectedVersion: number, options?: MeasureAppendOptions<TMetadata, TState> | undefined): Effect.Effect<AppendMeasurement, ValidationError, TableConfig>;
|
|
790
|
+
/**
|
|
791
|
+
* The item size, in bytes, of the snapshot {@link EventStream.writeSnapshot}
|
|
792
|
+
* (or an inline snapshot at `asOfVersion`) would write — packed `state` as
|
|
793
|
+
* stored. Caller-side planning, like {@link EventStream.measureAppend}. Dies
|
|
794
|
+
* with `[EDD-9026]` on a stream without a `snapshot` config.
|
|
520
795
|
*/
|
|
521
|
-
|
|
796
|
+
measureSnapshot(streamId: StreamIdInput<TStreamIdFields>, state: TState, asOfVersion: number): Effect.Effect<number, ValidationError, TableConfig>;
|
|
797
|
+
/**
|
|
798
|
+
* Decode a DynamoDB Streams record image (`NewImage` / `OldImage`) of one
|
|
799
|
+
* of this stream's events into exactly the {@link StreamEvent} the read
|
|
800
|
+
* paths return — `data` decoded through the event schemas (by its stored
|
|
801
|
+
* form: native map, JSON string or compressed binary), `metadata` through
|
|
802
|
+
* the metadata schema when the stream declares one.
|
|
803
|
+
*
|
|
804
|
+
* Accepts DynamoDB JSON as Lambda and EventBridge Pipes deliver it (`B` /
|
|
805
|
+
* `BS` members as base64 text, or as bytes), or an already-unmarshalled
|
|
806
|
+
* item (a packed `data` as bytes or base64 text). `Option.none()` for an
|
|
807
|
+
* image that is not one of this stream's events — its snapshot, an
|
|
808
|
+
* idempotency sentinel, another stream's or an entity's item — so one
|
|
809
|
+
* handler can fan a table's records across decoders. The type is read off
|
|
810
|
+
* the raw image before anything is unmarshalled, so nothing in another
|
|
811
|
+
* item's image (a number the SDK cannot represent, say) fails the decode.
|
|
812
|
+
* An event image that does not decode fails with `ValidationError`.
|
|
813
|
+
*
|
|
814
|
+
* With `{ raw: true }`, each event also carries `rawData`: its `data` as
|
|
815
|
+
* stored, in wire form, unmodelled fields included (see
|
|
816
|
+
* {@link RawStreamEvent}).
|
|
817
|
+
*
|
|
818
|
+
* The compressor registry is not in the type (`R = never`): this unbound
|
|
819
|
+
* method reads the `Compression` service from the running fiber. When the
|
|
820
|
+
* stream's `storedAs.data` or `snapshot.storedAs` names a codec that
|
|
821
|
+
* registry does not hold — including no `Compression` at all, for example
|
|
822
|
+
* one lost behind `Layer.provide` — it dies with the `[EDD-9078]`
|
|
823
|
+
* configuration defect on first use, whatever the image, rather than
|
|
824
|
+
* failing with a typed error. Provide `Compression` where the handler runs,
|
|
825
|
+
* or decode through {@link EventStore.bind}, which captures it.
|
|
826
|
+
*/
|
|
827
|
+
decodeStreamImage(image: StreamImage, options: DecodeStreamImageOptions & {
|
|
828
|
+
readonly raw: true;
|
|
829
|
+
}): Effect.Effect<Option.Option<RawStreamEvent<TEvent, StreamMetadata<TMetadata>>>, ValidationError, never>;
|
|
830
|
+
decodeStreamImage(image: StreamImage, options?: DecodeStreamImageOptions): Effect.Effect<Option.Option<StreamEvent<TEvent, StreamMetadata<TMetadata>>>, ValidationError, never>;
|
|
522
831
|
readonly query: {
|
|
523
832
|
events(streamId: StreamIdInput<TStreamIdFields>): Query.Query<StreamEvent<TEvent, StreamMetadata<TMetadata>>>;
|
|
524
833
|
/**
|
|
525
834
|
* A query of one of the stream's indexes (#140), scoped to `streamId`:
|
|
526
835
|
* decoded events in index-key order. It composes with the `Query`
|
|
527
836
|
* combinators — `Query.where` (a condition on the derived key),
|
|
528
|
-
* `Query.reverse`, `Query.limit`, `Query.consistentRead` (
|
|
529
|
-
*
|
|
837
|
+
* `Query.reverse`, `Query.limit`, `Query.consistentRead` (DynamoDB rejects
|
|
838
|
+
* it on a GSI-backed index: a `ValidationError` naming the index) — and terminals
|
|
530
839
|
* (`Query.collect`, `Query.execute`, `Query.paginate`). The
|
|
531
840
|
* `__edd_e__ = <stream>.event` filter still applies.
|
|
841
|
+
*
|
|
842
|
+
* It reads as {@link EventStream.readIndex} does for the index's
|
|
843
|
+
* projection: a `"keys"` GSI fetches each page's events with
|
|
844
|
+
* `BatchGetItem` (`Query.select` moves onto that fetch); `Query.count`
|
|
845
|
+
* counts index entries and fetches nothing. On a `"keys"` or
|
|
846
|
+
* `"withoutMetadata"` index, a `Query.filter` on an attribute the index
|
|
847
|
+
* does not project is refused by DynamoDB — use `Query.filterBy`.
|
|
532
848
|
*/
|
|
533
|
-
index(name:
|
|
849
|
+
index<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>): Query.Query<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>;
|
|
534
850
|
};
|
|
535
851
|
}
|
|
536
852
|
/**
|
|
@@ -592,7 +908,9 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
|
|
|
592
908
|
* event's derived key (and, for a GSI, the stream's partition key) on the
|
|
593
909
|
* event item; snapshots and idempotency sentinels never carry them, so they
|
|
594
910
|
* are never in an index. Read with {@link EventStream.readIndex} or
|
|
595
|
-
* `query.index`, and create the indexes with {@link indexDefinitions}.
|
|
911
|
+
* `query.index`, and create the indexes with {@link indexDefinitions}. Each
|
|
912
|
+
* index's `projection` (default `"all"`) sets what it copies from each event
|
|
913
|
+
* and how it is read — see {@link StreamIndexProjection}.
|
|
596
914
|
*
|
|
597
915
|
* Index attributes are written only at append time and events are never
|
|
598
916
|
* rewritten. Declaring an index on a stream that already holds events, or
|
|
@@ -629,7 +947,9 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
|
|
|
629
947
|
* `"inline"`.
|
|
630
948
|
* @throws `[EDD-9063]` for a malformed index: a `type` other than `"lsi"` /
|
|
631
949
|
* `"gsi"`, a `gsi` without `pk`, an `lsi` with `pk`, an empty `index` / `sk`
|
|
632
|
-
* / `pk`,
|
|
950
|
+
* / `pk`, a `key` that is not a function, a `projection` other than
|
|
951
|
+
* `"all"` / `"withoutMetadata"` / `"keys"`, or a member an index entry does
|
|
952
|
+
* not declare (such as a misspelled `projection`, also a type error).
|
|
633
953
|
* @throws `[EDD-9064]` when an index attribute is one the stream writes itself
|
|
634
954
|
* (`pk`, `sk`, `__edd_e__`, `streamId`, `version`, `eventType`, `data`,
|
|
635
955
|
* `metadata`, `timestamp`, `asOfVersion`, `state`, `commandId`, or the
|
|
@@ -638,10 +958,22 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
|
|
|
638
958
|
* with `ValidationError`.
|
|
639
959
|
* @throws `[EDD-9065]` when two indexes share a physical index name or an
|
|
640
960
|
* attribute (or a GSI's `pk` and `sk` are the same attribute).
|
|
961
|
+
* @throws `[EDD-9079]` when `storedAs` declares a key other than `data`
|
|
962
|
+
* (metadata always stays a map), or a `storedAs.data` / `snapshot.storedAs`
|
|
963
|
+
* value is not a `DynamoModel.Packed(...)` config.
|
|
964
|
+
* @throws `[EDD-9073]` when `snapshot.storedAs` is set and the state schema's
|
|
965
|
+
* encoded side is not object- or array-shaped.
|
|
966
|
+
*
|
|
967
|
+
* `storedAs: { data: DynamoModel.Packed(...) }` stores event `data` packed, and
|
|
968
|
+
* `snapshot.storedAs` the snapshot `state` — see {@link StreamStorageConfig}.
|
|
969
|
+
* Register the codecs a compressed config names through the `Compression`
|
|
970
|
+
* service (`Compression.layer([...])` merged into the application layer): a
|
|
971
|
+
* bound stream refuses to bind without them, and an unbound one to run its
|
|
972
|
+
* first operation (`[EDD-9078]`).
|
|
641
973
|
*/
|
|
642
974
|
export declare const makeStream: <const TEvents extends ReadonlyArray<Schema.Top>, TTable extends Table, const TStreamName extends string, const TStreamId extends {
|
|
643
975
|
readonly composite: ReadonlyArray<string>;
|
|
644
|
-
}, TMetadata extends Schema.Top | undefined = undefined, TSnapshot extends SnapshotConfig | undefined = undefined, const
|
|
976
|
+
}, TMetadata extends Schema.Top | undefined = undefined, TSnapshot extends SnapshotConfig | undefined = undefined, const TIndexes extends { readonly [K in keyof TIndexes]: StreamIndexConfig<Schema.Schema.Type<TEvents[number]>>; } = {}>(config: {
|
|
645
977
|
readonly table: TTable;
|
|
646
978
|
readonly streamName: TStreamName;
|
|
647
979
|
readonly events: TEvents;
|
|
@@ -649,8 +981,10 @@ export declare const makeStream: <const TEvents extends ReadonlyArray<Schema.Top
|
|
|
649
981
|
readonly metadata?: TMetadata;
|
|
650
982
|
readonly snapshot?: TSnapshot;
|
|
651
983
|
readonly casing?: DynamoSchema.Casing | undefined;
|
|
652
|
-
readonly indexes?:
|
|
653
|
-
|
|
984
|
+
readonly indexes?: TIndexes & StreamIndexesConfig<TIndexes, Schema.Schema.Type<TEvents[number]>>;
|
|
985
|
+
/** How event `data` is stored — see {@link StreamStorageConfig}. */
|
|
986
|
+
readonly storedAs?: StreamStorageConfig | undefined;
|
|
987
|
+
}) => EventStream<Schema.Schema.Type<TEvents[number]>, TStreamId["composite"], TMetadata extends Schema.Top ? Schema.Schema.Type<TMetadata> : undefined, TSnapshot extends SnapshotConfig<infer TStateSchema> ? Schema.Schema.Type<TStateSchema> : never, keyof TIndexes & string, IndexProjectionsOf<TIndexes>>;
|
|
654
988
|
/**
|
|
655
989
|
* An EventStream whose operations have `DynamoClient` and `TableConfig` already
|
|
656
990
|
* resolved, so all methods return `Effect<A, E, never>`.
|
|
@@ -671,12 +1005,18 @@ export declare const makeStream: <const TEvents extends ReadonlyArray<Schema.Top
|
|
|
671
1005
|
* }) {}
|
|
672
1006
|
* ```
|
|
673
1007
|
*/
|
|
674
|
-
export interface BoundEventStream<TEvent, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState = never, TIndexName extends string = never
|
|
1008
|
+
export interface BoundEventStream<TEvent, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState = never, TIndexName extends string = never, TIndexProjections extends {
|
|
1009
|
+
readonly [K in TIndexName]: StreamIndexProjection;
|
|
1010
|
+
} = {
|
|
1011
|
+
readonly [K in TIndexName]: "all";
|
|
1012
|
+
}> extends Pipeable.Pipeable {
|
|
675
1013
|
readonly [EventStreamTypeId]: EventStreamTypeId;
|
|
676
1014
|
readonly streamName: string;
|
|
677
1015
|
readonly eventSchema: Schema.Top;
|
|
678
1016
|
/** See {@link EventStream.snapshotConfig}. */
|
|
679
1017
|
readonly snapshotConfig: SnapshotSettings | undefined;
|
|
1018
|
+
/** See {@link EventStream.storedAs}. */
|
|
1019
|
+
readonly storedAs: StreamStorageSettings;
|
|
680
1020
|
/** See {@link EventStream.indexes}. */
|
|
681
1021
|
readonly indexes: {
|
|
682
1022
|
readonly [K in TIndexName]: StreamIndexSettings;
|
|
@@ -696,15 +1036,27 @@ export interface BoundEventStream<TEvent, TStreamIdFields extends ReadonlyArray<
|
|
|
696
1036
|
/** See {@link EventStream.readLatest}. */
|
|
697
1037
|
readLatest(streamId: StreamIdInput<TStreamIdFields>, options?: ReadLatestOptions | undefined): Effect.Effect<LatestState<TState, TEvent, StreamMetadata<TMetadata>>, DynamoClientError | ValidationError, never>;
|
|
698
1038
|
/** See {@link EventStream.readIndex}. */
|
|
699
|
-
readIndex(name:
|
|
1039
|
+
readIndex<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>, options?: ReadIndexOptions | undefined): Effect.Effect<ReadonlyArray<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>, DynamoClientError | ValidationError, never>;
|
|
1040
|
+
/** See {@link EventStream.measureAppend}. */
|
|
1041
|
+
measureAppend(streamId: StreamIdInput<TStreamIdFields>, events: ReadonlyArray<TEvent>, expectedVersion: number, options?: MeasureAppendOptions<TMetadata, TState> | undefined): Effect.Effect<AppendMeasurement, ValidationError, never>;
|
|
1042
|
+
/** See {@link EventStream.measureSnapshot}. */
|
|
1043
|
+
measureSnapshot(streamId: StreamIdInput<TStreamIdFields>, state: TState, asOfVersion: number): Effect.Effect<number, ValidationError, never>;
|
|
1044
|
+
/** See {@link EventStream.decodeStreamImage}. */
|
|
1045
|
+
decodeStreamImage(image: StreamImage, options: DecodeStreamImageOptions & {
|
|
1046
|
+
readonly raw: true;
|
|
1047
|
+
}): Effect.Effect<Option.Option<RawStreamEvent<TEvent, StreamMetadata<TMetadata>>>, ValidationError, never>;
|
|
1048
|
+
decodeStreamImage(image: StreamImage, options?: DecodeStreamImageOptions): Effect.Effect<Option.Option<StreamEvent<TEvent, StreamMetadata<TMetadata>>>, ValidationError, never>;
|
|
700
1049
|
/**
|
|
701
1050
|
* The stream's query builders — unbound: run them with {@link provide}, or
|
|
702
|
-
* where `DynamoClient` and `TableConfig` are provided.
|
|
1051
|
+
* where `DynamoClient` and `TableConfig` are provided. They do not use the
|
|
1052
|
+
* context this bound stream captured unless run through {@link provide}:
|
|
1053
|
+
* run directly, raw-row unpacking (`Query.select`) and decoding read the
|
|
1054
|
+
* `Compression` registry of the caller's context, not the bound one.
|
|
703
1055
|
*/
|
|
704
1056
|
readonly query: {
|
|
705
1057
|
events(streamId: StreamIdInput<TStreamIdFields>): Query.Query<StreamEvent<TEvent, StreamMetadata<TMetadata>>>;
|
|
706
1058
|
/** See `EventStream.query.index`. */
|
|
707
|
-
index(name:
|
|
1059
|
+
index<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>): Query.Query<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>;
|
|
708
1060
|
};
|
|
709
1061
|
/** Escape hatch: provide DynamoClient | TableConfig to an arbitrary effect. */
|
|
710
1062
|
readonly provide: <A, E>(effect: Effect.Effect<A, E, DynamoClient | TableConfig>) => Effect.Effect<A, E, never>;
|
|
@@ -723,7 +1075,7 @@ export interface BoundEventStream<TEvent, TStreamIdFields extends ReadonlyArray<
|
|
|
723
1075
|
* yield* stream.append({ matchId: "m-1" }, [event], 0) // R = never
|
|
724
1076
|
* ```
|
|
725
1077
|
*/
|
|
726
|
-
export declare const bind: <TEvent, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState, TIndexName extends string = never>(stream: EventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName>) => Effect.Effect<BoundEventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName>, never, DynamoClient | TableConfig>;
|
|
1078
|
+
export declare const bind: <TEvent, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState, TIndexName extends string = never, TIndexProjections extends { readonly [K in TIndexName]: StreamIndexProjection; } = { readonly [K in TIndexName]: "all"; }>(stream: EventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName, TIndexProjections>) => Effect.Effect<BoundEventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName, TIndexProjections>, never, DynamoClient | TableConfig>;
|
|
727
1079
|
/**
|
|
728
1080
|
* `CreateTable` fragments for the stream indexes of one or more streams — see
|
|
729
1081
|
* {@link indexDefinitions}. The lists are absent when empty, because
|
|
@@ -738,11 +1090,26 @@ export interface StreamIndexDefinitions {
|
|
|
738
1090
|
/**
|
|
739
1091
|
* Derive the `CreateTable` fragments for the stream indexes (#140) of the given
|
|
740
1092
|
* streams (`EventStream` or `BoundEventStream`) — `AttributeDefinitions`,
|
|
741
|
-
* `LocalSecondaryIndexes` and `GlobalSecondaryIndexes
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
1093
|
+
* `LocalSecondaryIndexes` and `GlobalSecondaryIndexes` — to merge into the
|
|
1094
|
+
* caller's `CreateTable` input. Event tables are not derived by
|
|
1095
|
+
* `Table.definition`, and the table's own `pk` / `sk` are not included: add
|
|
1096
|
+
* them (and, on a provisioned table, each GSI's `ProvisionedThroughput`)
|
|
1097
|
+
* yourself.
|
|
1098
|
+
*
|
|
1099
|
+
* Each index's `Projection` follows its {@link StreamIndexProjection}:
|
|
1100
|
+
*
|
|
1101
|
+
* | `projection` | `Projection` |
|
|
1102
|
+
* |---|---|
|
|
1103
|
+
* | `"all"` (default) | `{ ProjectionType: "ALL" }` |
|
|
1104
|
+
* | `"withoutMetadata"` | `{ ProjectionType: "INCLUDE", NonKeyAttributes: ["__edd_e__", "data", "eventType", "streamId", "timestamp", "version"] }` |
|
|
1105
|
+
* | `"keys"` | `{ ProjectionType: "INCLUDE", NonKeyAttributes: ["__edd_e__"] }` — not `KEYS_ONLY`: every read filters on `__edd_e__` |
|
|
1106
|
+
*
|
|
1107
|
+
* The deployed index must project what is declared — copy `Projection` from
|
|
1108
|
+
* this output into CDK or Terraform definitions. Neither an LSI's nor a GSI's
|
|
1109
|
+
* projection can be changed in place, and the library migrates nothing. A
|
|
1110
|
+
* table projects at most 100 `NonKeyAttributes` across all its indexes;
|
|
1111
|
+
* `"withoutMetadata"` spends 6 of them and `"keys"` 1 (DynamoDB refuses a
|
|
1112
|
+
* table over the limit).
|
|
746
1113
|
*
|
|
747
1114
|
* An index shared by several streams with the same definition is emitted
|
|
748
1115
|
* once. Lists are sorted by index name, attributes by attribute name.
|
|
@@ -750,7 +1117,6 @@ export interface StreamIndexDefinitions {
|
|
|
750
1117
|
* - An LSI can only be created with the table, and caps the item collection
|
|
751
1118
|
* of every partition key value in the table at 10 GB — entity partitions
|
|
752
1119
|
* and streams without indexes included.
|
|
753
|
-
* - The projection must stay `ALL`: events are decoded from the index item.
|
|
754
1120
|
*
|
|
755
1121
|
* @example
|
|
756
1122
|
* ```typescript
|
|
@@ -773,12 +1139,15 @@ export interface StreamIndexDefinitions {
|
|
|
773
1139
|
* ```
|
|
774
1140
|
*
|
|
775
1141
|
* @throws `[EDD-9067]` when two streams define the same physical index
|
|
776
|
-
* differently (another type,
|
|
1142
|
+
* differently (another type, other key attributes, or another projection).
|
|
777
1143
|
*/
|
|
778
1144
|
export declare const indexDefinitions: (...streams: ReadonlyArray<{
|
|
779
1145
|
readonly streamName: string;
|
|
780
1146
|
readonly indexes: {
|
|
781
|
-
readonly [name: string]: StreamIndexSettings
|
|
1147
|
+
readonly [name: string]: Omit<StreamIndexSettings, "projection"> & {
|
|
1148
|
+
/** Read as `"all"` when absent (a hand-built settings object). */
|
|
1149
|
+
readonly projection?: StreamIndexProjection | undefined;
|
|
1150
|
+
};
|
|
782
1151
|
};
|
|
783
1152
|
}>) => StreamIndexDefinitions;
|
|
784
1153
|
/**
|
|
@@ -803,7 +1172,7 @@ export interface CommandHandlerOptions {
|
|
|
803
1172
|
* initial attempt). Omit for the default: no retry.
|
|
804
1173
|
*
|
|
805
1174
|
* `DuplicateCommand` is deliberately NOT retried — it is terminal. Nor is
|
|
806
|
-
* any call that supplies {@link
|
|
1175
|
+
* any call that supplies {@link CommandOptionsBase.expectedVersion}: the caller asked
|
|
807
1176
|
* for a conditional write, so its `VersionConflict` is always surfaced.
|
|
808
1177
|
*/
|
|
809
1178
|
readonly retry?: number | Schedule.Schedule<unknown, VersionConflict> | undefined;
|
|
@@ -850,7 +1219,7 @@ export interface CommandHandlerOptions {
|
|
|
850
1219
|
*
|
|
851
1220
|
* - **No snapshot item** (a stream not yet snapshotted): the verified load
|
|
852
1221
|
* runs at once, and `decide` runs once.
|
|
853
|
-
* - **With {@link
|
|
1222
|
+
* - **With {@link CommandOptionsBase.expectedVersion}**: a snapshot whose
|
|
854
1223
|
* `asOfVersion` differs from it is re-read verified before the If-Match
|
|
855
1224
|
* check, so only a genuine mismatch is reported (its `actualVersion` is
|
|
856
1225
|
* the verified head). A snapshot at the expected version holds exactly the
|
|
@@ -890,7 +1259,9 @@ export interface CommandHandlerOptions {
|
|
|
890
1259
|
}
|
|
891
1260
|
/**
|
|
892
1261
|
* The outcome of one `decide` call, handed to a function-form
|
|
893
|
-
* {@link
|
|
1262
|
+
* {@link CommandOptionsBase.additionalItems}, to `eventMetadata`
|
|
1263
|
+
* ({@link CommandMetadataOptions}) and to
|
|
1264
|
+
* {@link CommandOptionsBase.eventTimestamps}.
|
|
894
1265
|
*
|
|
895
1266
|
* - `events` — what `decide` returned (never empty: the function is not called
|
|
896
1267
|
* for a no-op decision).
|
|
@@ -911,7 +1282,7 @@ export interface Decision<State, Event> {
|
|
|
911
1282
|
readonly version: number;
|
|
912
1283
|
}
|
|
913
1284
|
/**
|
|
914
|
-
* The forms {@link
|
|
1285
|
+
* The forms {@link CommandOptionsBase.additionalItems} accepts: a static array of
|
|
915
1286
|
* transact ops, or a function of the {@link Decision} returning the array
|
|
916
1287
|
* directly (pure projection) or as an `Effect` (a projection that needs a read).
|
|
917
1288
|
* The effect's `E2` joins the handler's error channel and its `R2` the handler's
|
|
@@ -919,15 +1290,97 @@ export interface Decision<State, Event> {
|
|
|
919
1290
|
*/
|
|
920
1291
|
export type AdditionalItemsInput<State, Event, E2 = never, R2 = never> = ReadonlyArray<TransactWriteOp> | ((decision: Decision<State, Event>) => ReadonlyArray<TransactWriteOp> | Effect.Effect<ReadonlyArray<TransactWriteOp>, E2, R2>);
|
|
921
1292
|
/**
|
|
922
|
-
*
|
|
1293
|
+
* The form `eventMetadata` ({@link CommandMetadataOptions}) accepts: a
|
|
1294
|
+
* function of the {@link Decision} returning one metadata entry per event
|
|
1295
|
+
* (`undefined` for an event without metadata), directly or as an `Effect`. The
|
|
1296
|
+
* effect's `E3` joins the handler's error channel and its `R3` the handler's
|
|
1297
|
+
* requirements.
|
|
923
1298
|
*
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
927
|
-
*
|
|
1299
|
+
* Function-only: the number of events is known only once `decide` has run.
|
|
1300
|
+
* The `Effect` form lets ids be minted through the `Crypto` service
|
|
1301
|
+
* (`crypto.randomULID`, or `randomUUIDv7`), which keeps them deterministic
|
|
1302
|
+
* under `TestClock` and a test `Crypto` layer with fixed bytes.
|
|
928
1303
|
*/
|
|
929
|
-
export
|
|
1304
|
+
export type EventMetadataInput<State, Event, TMetadata, E3 = never, R3 = never> = (decision: Decision<State, Event>) => ReadonlyArray<TMetadata | undefined> | Effect.Effect<ReadonlyArray<TMetadata | undefined>, E3, R3>;
|
|
1305
|
+
/**
|
|
1306
|
+
* The form {@link CommandOptionsBase.eventTimestamps} accepts: a function of
|
|
1307
|
+
* the {@link Decision} returning one timestamp per event (`undefined` keeps
|
|
1308
|
+
* the append's `Clock` time), directly or as an `Effect` — exactly as
|
|
1309
|
+
* {@link EventMetadataInput}. The effect's `E4` joins the handler's error
|
|
1310
|
+
* channel and its `R4` the handler's requirements.
|
|
1311
|
+
*
|
|
1312
|
+
* Function-only: the number of events is known only once `decide` has run.
|
|
1313
|
+
* The `Effect` form lets times come from a service (a source log, a
|
|
1314
|
+
* per-tenant clock) or from `Clock`, which keeps them deterministic under
|
|
1315
|
+
* `TestClock`.
|
|
1316
|
+
*
|
|
1317
|
+
* `E4` / `R4` are separate from `eventMetadata`'s `E3` / `R3`: with one shared
|
|
1318
|
+
* pair, an `eventMetadata` effect and an `eventTimestamps` effect with
|
|
1319
|
+
* different error types would not compile — TypeScript fixes the parameter
|
|
1320
|
+
* from one candidate and rejects the other instead of inferring a union.
|
|
1321
|
+
*/
|
|
1322
|
+
export type EventTimestampsInput<State, Event, E4 = never, R4 = never> = (decision: Decision<State, Event>) => ReadonlyArray<DateTime.DateTime | undefined> | Effect.Effect<ReadonlyArray<DateTime.DateTime | undefined>, E4, R4>;
|
|
1323
|
+
/**
|
|
1324
|
+
* The per-call metadata of a {@link commandHandler} handler: one value shared
|
|
1325
|
+
* by every event (`metadata`), or a function deriving one entry per event from
|
|
1326
|
+
* the decision (`eventMetadata`) — never both (see
|
|
1327
|
+
* {@link AppendMetadataOptions}).
|
|
1328
|
+
*/
|
|
1329
|
+
export type CommandMetadataOptions<TMetadata, State, Event, E3 = never, R3 = never> = {
|
|
1330
|
+
/**
|
|
1331
|
+
* Metadata written onto every event the decision appends (see
|
|
1332
|
+
* {@link AppendMetadataOptions}). Fixed before `decide` runs; to give
|
|
1333
|
+
* each event its own entry, use `eventMetadata`.
|
|
1334
|
+
*/
|
|
930
1335
|
readonly metadata?: TMetadata;
|
|
1336
|
+
readonly eventMetadata?: undefined;
|
|
1337
|
+
} | {
|
|
1338
|
+
readonly metadata?: undefined;
|
|
1339
|
+
/**
|
|
1340
|
+
* Per-event metadata derived from the {@link Decision}: the function
|
|
1341
|
+
* returns one entry per event, in order, and entry `i` is written onto
|
|
1342
|
+
* event `i` (see {@link AppendMetadataOptions}).
|
|
1343
|
+
*
|
|
1344
|
+
* - Runs after `decide` and after the new events are folded, receiving
|
|
1345
|
+
* the same `Decision` a function-form `additionalItems` does — and
|
|
1346
|
+
* **before** `additionalItems`, so an effectful pair runs in a
|
|
1347
|
+
* predictable order.
|
|
1348
|
+
* - Not called when `decide` returns no events.
|
|
1349
|
+
* - Re-run on every attempt: each `VersionConflict` retry, and the
|
|
1350
|
+
* re-decision a stale unverified snapshot triggers
|
|
1351
|
+
* ({@link CommandHandlerOptions.verifySnapshot}`: false`). Ids minted
|
|
1352
|
+
* per attempt are safe — a failed attempt writes nothing.
|
|
1353
|
+
* - A result whose length differs from the decision's events fails with
|
|
1354
|
+
* `ValidationError` (`operation: "EventStore.commandHandler.eventMetadata"`),
|
|
1355
|
+
* never retried. A failure of the returned `Effect` surfaces as is,
|
|
1356
|
+
* and is not retried either.
|
|
1357
|
+
*
|
|
1358
|
+
* Without `idempotency`, a redelivery after a lost response decides and
|
|
1359
|
+
* appends again, minting new ids. With it, the redelivery fails with
|
|
1360
|
+
* `DuplicateCommand`, so the first commit's ids are the only ones written:
|
|
1361
|
+
*
|
|
1362
|
+
* ```ts
|
|
1363
|
+
* // `crypto` is the `Crypto.Crypto` service; a ULID takes its time from the
|
|
1364
|
+
* // `Clock`, so `TestClock` plus a fixed-bytes `Crypto` make it deterministic
|
|
1365
|
+
* const handle = EventStore.commandHandler(decider, stream, {
|
|
1366
|
+
* idempotency: { ttl: Duration.days(1) },
|
|
1367
|
+
* })
|
|
1368
|
+
* yield* handle(streamId, command, {
|
|
1369
|
+
* commandId: deliveryId,
|
|
1370
|
+
* eventMetadata: ({ events }) =>
|
|
1371
|
+
* Effect.forEach(events, () =>
|
|
1372
|
+
* Effect.map(crypto.randomULID, (eventId) => ({ eventId })),
|
|
1373
|
+
* ),
|
|
1374
|
+
* })
|
|
1375
|
+
* ```
|
|
1376
|
+
*/
|
|
1377
|
+
readonly eventMetadata?: EventMetadataInput<State, Event, TMetadata, E3, R3>;
|
|
1378
|
+
};
|
|
1379
|
+
/**
|
|
1380
|
+
* The options of a {@link commandHandler} handler other than its metadata
|
|
1381
|
+
* (see {@link CommandMetadataOptions}).
|
|
1382
|
+
*/
|
|
1383
|
+
export interface CommandOptionsBase<State = unknown, Event = unknown, E2 = never, R2 = never, E4 = never, R4 = never> {
|
|
931
1384
|
/**
|
|
932
1385
|
* Identifier for this command delivery. Required when the handler was created
|
|
933
1386
|
* with `idempotency`; a replayed id fails with `DuplicateCommand` — also when
|
|
@@ -964,7 +1417,7 @@ export interface CommandOptions<TMetadata, State = unknown, Event = unknown, E2
|
|
|
964
1417
|
readonly expectedVersion?: number | undefined;
|
|
965
1418
|
/**
|
|
966
1419
|
* Caller-owned transact items committed atomically with the produced events
|
|
967
|
-
* (see {@link
|
|
1420
|
+
* (see {@link AppendOptionsBase.additionalItems}).
|
|
968
1421
|
*
|
|
969
1422
|
* Either a static array, or a function of the {@link Decision} — an inline
|
|
970
1423
|
* projection — returning the array or an `Effect` of it. The function runs
|
|
@@ -974,31 +1427,80 @@ export interface CommandOptions<TMetadata, State = unknown, Event = unknown, E2
|
|
|
974
1427
|
* `AdditionalItemConditionFailed` refer to the array it returned.
|
|
975
1428
|
*/
|
|
976
1429
|
readonly additionalItems?: AdditionalItemsInput<State, Event, E2, R2>;
|
|
1430
|
+
/**
|
|
1431
|
+
* Per-event timestamps derived from the {@link Decision}: the function
|
|
1432
|
+
* returns one entry per event, in order, directly or as an `Effect` (see
|
|
1433
|
+
* {@link EventTimestampsInput}), and entry `i` is written as event `i`'s
|
|
1434
|
+
* `timestamp` — `undefined` keeps the append's `Clock` time (see
|
|
1435
|
+
* {@link AppendOptionsBase.eventTimestamps}). Independent of `metadata` /
|
|
1436
|
+
* `eventMetadata`. The idempotency sentinel, its TTL and an inline snapshot
|
|
1437
|
+
* keep the append's `Clock` time; timestamps carry no ordering check.
|
|
1438
|
+
*
|
|
1439
|
+
* - Runs after `eventMetadata` and before `additionalItems`, each to
|
|
1440
|
+
* completion before the next, all receiving the same `Decision`; not
|
|
1441
|
+
* called when `decide` returns no events; re-run on every attempt — each
|
|
1442
|
+
* `VersionConflict` retry and the re-decision a stale unverified snapshot
|
|
1443
|
+
* triggers — as `eventMetadata` is.
|
|
1444
|
+
* - A failure of the returned `Effect` surfaces as is, is not retried, and
|
|
1445
|
+
* `additionalItems` is not run.
|
|
1446
|
+
* - A result — plain or produced by the `Effect` — of the wrong length, or
|
|
1447
|
+
* with an entry that is neither a valid `DateTime` (finite epoch) nor
|
|
1448
|
+
* `undefined`, fails with `ValidationError`
|
|
1449
|
+
* (`operation: "EventStore.commandHandler.eventTimestamps"`), never
|
|
1450
|
+
* retried. A value that is not a function is refused before anything is
|
|
1451
|
+
* read.
|
|
1452
|
+
*
|
|
1453
|
+
* ```ts
|
|
1454
|
+
* // plain: the times are in the events
|
|
1455
|
+
* eventTimestamps: ({ events }) => events.map((event) => event.occurredAt)
|
|
1456
|
+
*
|
|
1457
|
+
* // Effect: the times come from a service
|
|
1458
|
+
* eventTimestamps: ({ events }) =>
|
|
1459
|
+
* Effect.forEach(events, (event) => SourceLog.originalTime(event.sourceId))
|
|
1460
|
+
* ```
|
|
1461
|
+
*/
|
|
1462
|
+
readonly eventTimestamps?: EventTimestampsInput<State, Event, E4, R4>;
|
|
977
1463
|
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Per-call options accepted by a handler produced by {@link commandHandler}:
|
|
1466
|
+
* {@link CommandOptionsBase} plus the exclusive `metadata` / `eventMetadata`
|
|
1467
|
+
* pair ({@link CommandMetadataOptions}).
|
|
1468
|
+
*
|
|
1469
|
+
* `State` / `Event` type the {@link Decision} a function-form `additionalItems`,
|
|
1470
|
+
* `eventMetadata` or `eventTimestamps` receives; `E2` / `R2` are the error and
|
|
1471
|
+
* requirements of the `Effect` `additionalItems` may return, `E3` / `R3` those
|
|
1472
|
+
* of the `Effect` `eventMetadata` may return, `E4` / `R4` those of the
|
|
1473
|
+
* `Effect` `eventTimestamps` may return — separate, so effects with different
|
|
1474
|
+
* error types all join the handler's error channel. The handler infers all of
|
|
1475
|
+
* them, so they only need naming when this type is written out by hand.
|
|
1476
|
+
*/
|
|
1477
|
+
export type CommandOptions<TMetadata, State = unknown, Event = unknown, E2 = never, R2 = never, E3 = never, R3 = never, E4 = never, R4 = never> = CommandOptionsBase<State, Event, E2, R2, E4, R4> & CommandMetadataOptions<TMetadata, State, Event, E3, R3>;
|
|
978
1478
|
/** Per-call options when the handler was created with `idempotency` — `commandId` is required. */
|
|
979
|
-
export
|
|
1479
|
+
export type IdempotentCommandOptions<TMetadata, State = unknown, Event = unknown, E2 = never, R2 = never, E3 = never, R3 = never, E4 = never, R4 = never> = CommandOptions<TMetadata, State, Event, E2, R2, E3, R3, E4, R4> & {
|
|
980
1480
|
readonly commandId: string;
|
|
981
|
-
}
|
|
1481
|
+
};
|
|
982
1482
|
/**
|
|
983
1483
|
* Options arity: configuring `idempotency` makes the handler's options parameter
|
|
984
1484
|
* required (and `commandId` within it non-optional), so a missing `commandId`
|
|
985
1485
|
* is a compile error rather than a silent downgrade to at-least-once.
|
|
986
1486
|
*/
|
|
987
|
-
type CommandOptionsArgs<TMetadata, TConfig extends CommandHandlerOptions | undefined, State, TEvent, E2, R2> = TConfig extends {
|
|
1487
|
+
type CommandOptionsArgs<TMetadata, TConfig extends CommandHandlerOptions | undefined, State, TEvent, E2, R2, E3, R3, E4, R4> = TConfig extends {
|
|
988
1488
|
readonly idempotency: object;
|
|
989
|
-
} ? [options: IdempotentCommandOptions<TMetadata, State, TEvent, E2, R2>] : [options?: CommandOptions<TMetadata, State, TEvent, E2, R2> | undefined];
|
|
1489
|
+
} ? [options: IdempotentCommandOptions<TMetadata, State, TEvent, E2, R2, E3, R3, E4, R4>] : [options?: CommandOptions<TMetadata, State, TEvent, E2, R2, E3, R3, E4, R4> | undefined];
|
|
990
1490
|
type CommandHandlerErrors<E> = E | VersionConflict | DuplicateCommand | AdditionalItemConditionFailed | AppendTooLarge | DynamoClientError | ValidationError | TransactionCancelled | UniqueConstraintViolation | OptimisticLockError | ConcurrentModification;
|
|
991
1491
|
/**
|
|
992
1492
|
* A handler over an {@link EventStream}. Generic per call over the error (`E2`)
|
|
993
1493
|
* and requirements (`R2`) of a function-form `additionalItems` that returns an
|
|
994
|
-
* `Effect
|
|
1494
|
+
* `Effect`, those (`E3` / `R3`) of an `eventMetadata` that does, and those
|
|
1495
|
+
* (`E4` / `R4`) of an `eventTimestamps` that does; all are `never` otherwise.
|
|
995
1496
|
*/
|
|
996
|
-
type CommandHandler<State, Command, TEvent, E, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TConfig extends CommandHandlerOptions | undefined = undefined> = <E2 = never, R2 = never>(streamId: StreamIdInput<TStreamIdFields>, command: Command, ...options: CommandOptionsArgs<TMetadata, TConfig, State, TEvent, E2, R2>) => Effect.Effect<CommandHandlerResult<State, TEvent>, CommandHandlerErrors<E> | E2, DynamoClient | TableConfig | R2>;
|
|
1497
|
+
type CommandHandler<State, Command, TEvent, E, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TConfig extends CommandHandlerOptions | undefined = undefined> = <E2 = never, R2 = never, E3 = never, R3 = never, E4 = never, R4 = never>(streamId: StreamIdInput<TStreamIdFields>, command: Command, ...options: CommandOptionsArgs<TMetadata, TConfig, State, TEvent, E2, R2, E3, R3, E4, R4>) => Effect.Effect<CommandHandlerResult<State, TEvent>, CommandHandlerErrors<E> | E2 | E3 | E4, DynamoClient | TableConfig | R2 | R3 | R4>;
|
|
997
1498
|
/**
|
|
998
1499
|
* A handler over a {@link BoundEventStream}: the stream's services are already
|
|
999
|
-
* provided, so
|
|
1500
|
+
* provided, so the `R2` / `R3` / `R4` of a function-form `additionalItems` /
|
|
1501
|
+
* `eventMetadata` / `eventTimestamps` are its only requirements.
|
|
1000
1502
|
*/
|
|
1001
|
-
type BoundCommandHandler<State, Command, TEvent, E, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TConfig extends CommandHandlerOptions | undefined = undefined> = <E2 = never, R2 = never>(streamId: StreamIdInput<TStreamIdFields>, command: Command, ...options: CommandOptionsArgs<TMetadata, TConfig, State, TEvent, E2, R2>) => Effect.Effect<CommandHandlerResult<State, TEvent>, CommandHandlerErrors<E> | E2, R2>;
|
|
1503
|
+
type BoundCommandHandler<State, Command, TEvent, E, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TConfig extends CommandHandlerOptions | undefined = undefined> = <E2 = never, R2 = never, E3 = never, R3 = never, E4 = never, R4 = never>(streamId: StreamIdInput<TStreamIdFields>, command: Command, ...options: CommandOptionsArgs<TMetadata, TConfig, State, TEvent, E2, R2, E3, R3, E4, R4>) => Effect.Effect<CommandHandlerResult<State, TEvent>, CommandHandlerErrors<E> | E2 | E3 | E4, R2 | R3 | R4>;
|
|
1002
1504
|
/**
|
|
1003
1505
|
* The handler the data-last {@link commandHandler} returns for stream `S`: a
|
|
1004
1506
|
* {@link BoundCommandHandler} for a `BoundEventStream` (checked first — a bound
|
|
@@ -1042,6 +1544,24 @@ type CommandHandlerFor<S, State, Command, TEvent, E, TConfig extends CommandHand
|
|
|
1042
1544
|
* yield* handle({ matchId: "m-1" }, command, {
|
|
1043
1545
|
* additionalItems: ({ state }) => [Scoreboard.put({ matchId: "m-1", runs: state.runs })],
|
|
1044
1546
|
* })
|
|
1547
|
+
*
|
|
1548
|
+
* // Per-event metadata derived from the decision: entry i is written onto event i
|
|
1549
|
+
* // (`crypto` is the `Crypto.Crypto` service; `randomUUIDv7` works the same way)
|
|
1550
|
+
* yield* handle({ matchId: "m-1" }, command, {
|
|
1551
|
+
* eventMetadata: ({ events }) =>
|
|
1552
|
+
* Effect.forEach(events, () => Effect.map(crypto.randomULID, (eventId) => ({ eventId }))),
|
|
1553
|
+
* })
|
|
1554
|
+
*
|
|
1555
|
+
* // Per-event timestamps derived from the decision (undefined → the Clock time)
|
|
1556
|
+
* yield* handle({ matchId: "m-1" }, command, {
|
|
1557
|
+
* eventTimestamps: ({ events }) => events.map((event) => event.occurredAt),
|
|
1558
|
+
* })
|
|
1559
|
+
*
|
|
1560
|
+
* // ...or from a service, as an Effect (its errors / requirements join the handler's)
|
|
1561
|
+
* yield* handle({ matchId: "m-1" }, command, {
|
|
1562
|
+
* eventTimestamps: ({ events }) =>
|
|
1563
|
+
* Effect.forEach(events, (event) => SourceLog.originalTime(event.sourceId)),
|
|
1564
|
+
* })
|
|
1045
1565
|
* ```
|
|
1046
1566
|
*
|
|
1047
1567
|
* Each invocation runs
|
|
@@ -1057,7 +1577,7 @@ type CommandHandlerFor<S, State, Command, TEvent, E, TConfig extends CommandHand
|
|
|
1057
1577
|
* item alone (one `GetItem`); an answer that is not a successful append is
|
|
1058
1578
|
* confirmed against the head first, and a snapshot that proves stale falls
|
|
1059
1579
|
* back to the verified load — in which case `decide` may run twice.
|
|
1060
|
-
* - **expectedVersion.** See {@link
|
|
1580
|
+
* - **expectedVersion.** See {@link CommandOptionsBase.expectedVersion}.
|
|
1061
1581
|
* - **Fold before append.** The new events are folded into state before the
|
|
1062
1582
|
* append, so the state returned, snapshotted and handed to a function-form
|
|
1063
1583
|
* `additionalItems` (see {@link Decision}) is always the `evolve` fold.
|
|
@@ -1067,8 +1587,8 @@ type CommandHandlerFor<S, State, Command, TEvent, E, TConfig extends CommandHand
|
|
|
1067
1587
|
* fresh snapshot is written (best-effort) after a successful append once the
|
|
1068
1588
|
* cadence threshold is crossed. See {@link SnapshotMode}.
|
|
1069
1589
|
* - **One atomic append.** One command, one decision, one append: a decision
|
|
1070
|
-
* too large for one transaction
|
|
1071
|
-
* is written. A large command is split by the application into stepped
|
|
1590
|
+
* too large for one transaction is rejected whole by DynamoDB and fails with
|
|
1591
|
+
* `AppendTooLarge` — nothing is written. A large command is split by the application into stepped
|
|
1072
1592
|
* commands — each an ordinary call, chained by `expectedVersion` — so a
|
|
1073
1593
|
* failure partway leaves the stream at a real intermediate state.
|
|
1074
1594
|
*
|
|
@@ -1087,9 +1607,13 @@ type CommandHandlerFor<S, State, Command, TEvent, E, TConfig extends CommandHand
|
|
|
1087
1607
|
* `EventStreamTypeId` brand of the second argument.
|
|
1088
1608
|
*/
|
|
1089
1609
|
export declare const commandHandler: {
|
|
1090
|
-
<State, Command, TEvent, E, const TConfig extends CommandHandlerOptions | undefined = undefined>(decider: Decider<State, Command, TEvent, E>, options?: TConfig): <S extends BoundEventStream<TEvent, any, any, State, any> | EventStream<TEvent, ReadonlyArray<string>, any, State, any>>(stream: S) => CommandHandlerFor<S, State, Command, TEvent, E, TConfig>;
|
|
1091
|
-
<State, Command, TEvent, E, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState extends State, TIndexName extends string,
|
|
1092
|
-
|
|
1610
|
+
<State, Command, TEvent, E, const TConfig extends CommandHandlerOptions | undefined = undefined>(decider: Decider<State, Command, TEvent, E>, options?: TConfig): <S extends BoundEventStream<TEvent, any, any, State, any, any> | EventStream<TEvent, ReadonlyArray<string>, any, State, any, any>>(stream: S) => CommandHandlerFor<S, State, Command, TEvent, E, TConfig>;
|
|
1611
|
+
<State, Command, TEvent, E, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState extends State, TIndexName extends string, TIndexProjections extends {
|
|
1612
|
+
readonly [K in TIndexName]: StreamIndexProjection;
|
|
1613
|
+
}, const TConfig extends CommandHandlerOptions | undefined = undefined>(decider: Decider<State, Command, TEvent, E>, stream: BoundEventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName, TIndexProjections>, options?: TConfig): BoundCommandHandler<State, Command, TEvent, E, TStreamIdFields, TMetadata, TConfig>;
|
|
1614
|
+
<State, Command, TEvent, E, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState extends State, TIndexName extends string, TIndexProjections extends {
|
|
1615
|
+
readonly [K in TIndexName]: StreamIndexProjection;
|
|
1616
|
+
}, const TConfig extends CommandHandlerOptions | undefined = undefined>(decider: Decider<State, Command, TEvent, E>, stream: EventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName, TIndexProjections>, options?: TConfig): CommandHandler<State, Command, TEvent, E, TStreamIdFields, TMetadata, TConfig>;
|
|
1093
1617
|
};
|
|
1094
1618
|
/**
|
|
1095
1619
|
* Reconstruct state from events by folding through a decider's `evolve` function.
|
|
@@ -1109,5 +1633,4 @@ export declare const foldFrom: {
|
|
|
1109
1633
|
<A>(startState: unknown, events: ReadonlyArray<StreamEvent<A>>): <S, C, E>(decider: Decider<S, C, A, E>) => S;
|
|
1110
1634
|
<S, C, A, E>(decider: Decider<S, C, A, E>, startState: S, events: ReadonlyArray<StreamEvent<A>>): S;
|
|
1111
1635
|
};
|
|
1112
|
-
export {};
|
|
1113
1636
|
//# sourceMappingURL=EventStore.d.ts.map
|