effect-dynamodb 1.26.0 → 1.27.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.
@@ -15,7 +15,9 @@
15
15
  * - Stream indexes (`makeStream({ indexes })`): sub-streams ordered by a key
16
16
  * derived from each event, on an LSI or GSI, each projecting all of the
17
17
  * event, all but its metadata, or only its keys — `readIndex`,
18
- * `query.index`, and `indexDefinitions` for the `CreateTable` fragments
18
+ * `query.index`, and `indexDefinitions` for the `CreateTable` fragments.
19
+ * A GSI may derive its partition too, so several streams share one
20
+ * partition of it, snapshots included (#149)
19
21
  * - `fold` / `foldFrom` helpers for state reconstruction
20
22
  * - Packed storage (`makeStream({ storedAs: { data } })`, `snapshot.storedAs`):
21
23
  * event data and snapshot state as one JSON string, optionally compressed —
@@ -444,20 +446,27 @@ export interface LatestState<TState, TEvent, M = Record<string, unknown> | undef
444
446
  readonly events: ReadonlyArray<StreamEvent<TEvent, M>>;
445
447
  readonly version: number;
446
448
  }
449
+ /**
450
+ * The stream id as a stream's index functions receive it: the value of each
451
+ * `streamId.composite` field, by name.
452
+ */
453
+ export type StreamIndexStreamId = {
454
+ readonly [field: string]: string;
455
+ };
447
456
  /**
448
457
  * Derives an event's key in a stream index from the domain event (before it is
449
- * encoded) and the version it is appended at.
458
+ * encoded), the version it is appended at — its version in the stream — and
459
+ * the stream id it is appended to.
450
460
  *
451
461
  * - A string is the event's sort key in the index. It is stored **raw**: no
452
462
  * casing and no prefixing is applied, so compose it to sort the way you
453
463
  * query (zero-pad numbers, for example).
454
464
  * - `undefined` leaves the event out of the index (a sparse index).
455
- * - An empty string (or any other value) fails the append with
456
- * `ValidationError` before anything is written — DynamoDB rejects empty key
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.
465
+ * - Any other value, or a `key` that throws, fails the append with
466
+ * `ValidationError` before anything is written. An empty string or a key
467
+ * over DynamoDB's 1024-byte sort-key limit is sent and rejected by
468
+ * DynamoDB; the append then fails with `ValidationError` naming the index
469
+ * and the event (#144), and nothing is written.
461
470
  *
462
471
  * The key is derived only when an event is appended. Events are never
463
472
  * rewritten, so changing `key` later leaves events already stored under their
@@ -467,7 +476,60 @@ export interface LatestState<TState, TEvent, M = Record<string, unknown> | undef
467
476
  * existing events is the application's decision (see `indexes` on
468
477
  * {@link makeStream}).
469
478
  */
470
- export type StreamIndexKey<TEvent> = (event: TEvent, version: number) => string | undefined;
479
+ export type StreamIndexKey<TEvent, TStreamId = StreamIndexStreamId> = (event: TEvent, version: number, streamId: TStreamId) => string | undefined;
480
+ /**
481
+ * Derives an event's partition in a GSI stream index (`partition` on a
482
+ * {@link GlobalStreamIndexConfig}, #149) from the domain event, the version it
483
+ * is appended at and the stream id — the arguments of the index's `key`, in
484
+ * the same order. Called for every appended event, with `key`.
485
+ *
486
+ * - A string is the event's partition key in the index, stored **raw** (no
487
+ * casing, no prefix) in the index's `pk` attribute. Several streams may
488
+ * derive the same value, so one query of the index reads a group of
489
+ * streams — `ORDER#<orderId>` for an order's line, payment and shipment
490
+ * streams, say.
491
+ * - `undefined` leaves the event out of the index, as an `undefined` `key`
492
+ * does. An event is indexed only when both return a string.
493
+ * - Within one append, every event whose partition is a string must have the
494
+ * same one, and so must the inline snapshot's entry (see
495
+ * {@link StreamIndexSnapshotEntry}); otherwise the append fails with
496
+ * `ValidationError` and nothing is written. Another value, or a function
497
+ * that throws, fails it the same way. An empty string, or one over
498
+ * DynamoDB's 2048-byte partition-key limit, is DynamoDB's to reject: the
499
+ * rejection is reported as a `ValidationError` naming the index and the
500
+ * event.
501
+ */
502
+ export type StreamIndexPartition<TEvent, TStreamId = StreamIndexStreamId> = (event: TEvent, version: number, streamId: TStreamId) => string | undefined;
503
+ /**
504
+ * Puts a stream's snapshot in a GSI stream index with a derived partition
505
+ * (`snapshot` on a {@link GlobalStreamIndexConfig}, #149): from the state
506
+ * being written (before it is encoded), the snapshot's `asOfVersion` and the
507
+ * stream id — in the order of the events' `key` and `partition` — the
508
+ * snapshot item's partition and sort key in the index —
509
+ * both stored **raw** — or `undefined` to leave it out.
510
+ *
511
+ * Every snapshot write derives it — `writeSnapshot`, the inline snapshot of
512
+ * `append` and `commandHandler` (`mode: "inline"` or `"after-append"`) — and
513
+ * the attributes are part of the snapshot item itself, so an inline snapshot
514
+ * enters the index in the append's own transaction. The snapshot `Put`
515
+ * replaces the whole item, so returning `undefined` takes a snapshot already
516
+ * in the index out of it.
517
+ *
518
+ * The inline snapshot of an append must have the partition of the append's
519
+ * events in the index (when they have one); a standalone `writeSnapshot` has
520
+ * no events to compare with. Another value than an object of two strings, or
521
+ * a function that throws, fails the write with `ValidationError` before
522
+ * anything is written.
523
+ *
524
+ * A snapshot is the state **as of** `asOfVersion`: it is current after every
525
+ * append only with `snapshot: { mode: "inline" }` and no `every`. A reader of
526
+ * the index that needs the head folds the stream's indexed events after
527
+ * `asOfVersion` onto it.
528
+ */
529
+ export type StreamIndexSnapshotEntry<TState, TStreamId = StreamIndexStreamId> = (state: TState, asOfVersion: number, streamId: TStreamId) => {
530
+ readonly partition: string;
531
+ readonly key: string;
532
+ } | undefined;
471
533
  /**
472
534
  * A stream index on a **local** secondary index (the default `type`). An LSI
473
535
  * shares the table's partition key (`pk`), so its items are exactly the
@@ -484,13 +546,17 @@ export type StreamIndexKey<TEvent> = (event: TEvent, version: number) => string
484
546
  * the index copies from each event is its `projection` — see
485
547
  * {@link StreamIndexProjection}.
486
548
  */
487
- export interface LocalStreamIndexConfig<TEvent> {
549
+ export interface LocalStreamIndexConfig<TEvent, TStreamId = StreamIndexStreamId> {
488
550
  readonly type?: "lsi" | undefined;
489
551
  readonly index: string;
490
552
  readonly sk: string;
491
553
  /** An LSI uses the table partition key; supplying `pk` is refused (`[EDD-9063]`). */
492
554
  readonly pk?: undefined;
493
- readonly key: StreamIndexKey<TEvent>;
555
+ readonly key: StreamIndexKey<TEvent, TStreamId>;
556
+ /** An LSI's partition is the stream's; supplying `partition` is refused (`[EDD-9063]`). */
557
+ readonly partition?: undefined;
558
+ /** Only a GSI with a derived `partition` indexes snapshots (`[EDD-9063]`). */
559
+ readonly snapshot?: undefined;
494
560
  /**
495
561
  * What the index copies from each event — see {@link StreamIndexProjection}.
496
562
  * Default `"all"`. Must match the deployed index.
@@ -498,30 +564,56 @@ export interface LocalStreamIndexConfig<TEvent> {
498
564
  readonly projection?: StreamIndexProjection | undefined;
499
565
  }
500
566
  /**
501
- * A stream index on a **global** secondary index. `append` writes the stream's
502
- * partition key value into the `pk` attribute of each indexed event, so the
503
- * index is scoped to the same stream: the eventually consistent equivalent of
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.
567
+ * A stream index on a **global** secondary index. By default `append` writes
568
+ * the stream's partition key value into the `pk` attribute of each indexed
569
+ * event, so the index is scoped to the same stream: the eventually consistent
570
+ * equivalent of an LSI, without the LSI's 10 GB item-collection cap. DynamoDB
571
+ * rejects a strongly consistent read of it, diagnosed as a `ValidationError`
572
+ * naming the index.
573
+ *
574
+ * With `partition` (#149), the partition is derived instead — see
575
+ * {@link StreamIndexPartition} — and stored raw, so several streams can write
576
+ * the same partition of one GSI they all declare, and one query of it reads
577
+ * the whole group (an order's line, payment and shipment streams under
578
+ * `ORDER#<orderId>`, say). {@link indexDefinitions} emits a physical index
579
+ * the streams declare alike once. With `snapshot` as well, each stream's
580
+ * snapshot item is in the index too — see {@link StreamIndexSnapshotEntry}.
581
+ * {@link EventStream.readIndex} then takes the partition to read, and
582
+ * returns the given stream's events in it.
507
583
  *
508
584
  * Unlike an LSI, the GSI itself can be added to an existing table
509
- * (`UpdateTable`), but that does not index the stream's existing events: the
510
- * index attributes are written only by `append`, so only events appended after
511
- * the index is declared are in it — see {@link StreamIndexKey}.
585
+ * (`UpdateTable`), but that does not index the stream's existing items: the
586
+ * index attributes are written only by `append` (and snapshot writes), so only
587
+ * events appended after the index is declared are in it — see
588
+ * {@link StreamIndexKey}.
512
589
  *
513
590
  * - `index` — the physical GSI name.
514
- * - `pk` — the attribute carrying the stream's partition key (the GSI's HASH key).
591
+ * - `pk` — the attribute carrying the partition (the GSI's HASH key): the
592
+ * stream's partition key, or the derived `partition`.
515
593
  * - `sk` — the attribute carrying the derived key (the GSI's RANGE key).
516
594
  * - `key` — see {@link StreamIndexKey}.
595
+ * - `partition` — optional, see {@link StreamIndexPartition}.
596
+ * - `snapshot` — optional, requires `partition` and a stream `snapshot`
597
+ * config; see {@link StreamIndexSnapshotEntry}.
517
598
  * - `projection` — see {@link StreamIndexProjection}.
599
+ *
600
+ * Write `type: "gsi"` in the entry itself. TypeScript infers the parameter
601
+ * types of `partition` and `snapshot` from the GSI member of the entry union,
602
+ * which it selects by the literal `type`: an entry whose `type` comes from an
603
+ * object spread (`{ ...shared, partition: (event) => … }`) gets implicit-`any`
604
+ * parameters there, although `key` still infers. Annotate them, or give the
605
+ * shared fragment a {@link GlobalStreamIndexConfig} type.
518
606
  */
519
- export interface GlobalStreamIndexConfig<TEvent> {
607
+ export interface GlobalStreamIndexConfig<TEvent, TStreamId = StreamIndexStreamId, TState = unknown> {
520
608
  readonly type: "gsi";
521
609
  readonly index: string;
522
610
  readonly pk: string;
523
611
  readonly sk: string;
524
- readonly key: StreamIndexKey<TEvent>;
612
+ readonly key: StreamIndexKey<TEvent, TStreamId>;
613
+ /** The derived partition — see {@link StreamIndexPartition}. */
614
+ readonly partition?: StreamIndexPartition<TEvent, TStreamId> | undefined;
615
+ /** The snapshot's entry — see {@link StreamIndexSnapshotEntry}. Requires `partition`. */
616
+ readonly snapshot?: StreamIndexSnapshotEntry<TState, TStreamId> | undefined;
525
617
  /**
526
618
  * What the index copies from each event — see {@link StreamIndexProjection}.
527
619
  * Default `"all"`. Must match the deployed index.
@@ -533,7 +625,7 @@ export interface GlobalStreamIndexConfig<TEvent> {
533
625
  * ordered by a key derived from each event. See {@link LocalStreamIndexConfig}
534
626
  * and {@link GlobalStreamIndexConfig}.
535
627
  */
536
- export type StreamIndexConfig<TEvent> = LocalStreamIndexConfig<TEvent> | GlobalStreamIndexConfig<TEvent>;
628
+ export type StreamIndexConfig<TEvent, TStreamId = StreamIndexStreamId, TState = unknown> = LocalStreamIndexConfig<TEvent, TStreamId> | GlobalStreamIndexConfig<TEvent, TStreamId, TState>;
537
629
  /**
538
630
  * What a stream index copies from each event (`projection` on an entry of
539
631
  * `makeStream({ indexes })`). {@link indexDefinitions} emits the matching
@@ -542,7 +634,7 @@ export type StreamIndexConfig<TEvent> = LocalStreamIndexConfig<TEvent> | GlobalS
542
634
  * | Value | Emitted `Projection` | Read |
543
635
  * |---|---|---|
544
636
  * | `"all"` (default) | `ALL` | one query |
545
- * | `"withoutMetadata"` | `INCLUDE` of every event attribute except `metadata` | one query; `metadata` is `undefined` (and typed so) |
637
+ * | `"withoutMetadata"` | `INCLUDE` of every event attribute except `metadata` (plus the snapshot's `asOfVersion` and `state` when a stream indexes its snapshot in it) | one query; `metadata` is `undefined` (and typed so) |
546
638
  * | `"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
639
  *
548
640
  * `__edd_e__` is always projected because every read filters on it, and
@@ -575,6 +667,18 @@ export interface StreamIndexSettings {
575
667
  readonly pk: string;
576
668
  readonly sk: string;
577
669
  readonly projection: StreamIndexProjection;
670
+ /**
671
+ * Where the index's partition comes from: `"stream"` — the stream's own
672
+ * partition key (every LSI, and a GSI without `partition`) — or
673
+ * `"derived"`, a GSI's `partition` function (#149). A stream always sets
674
+ * it; absent (a settings object built by hand) reads as `"stream"`.
675
+ */
676
+ readonly partition?: "stream" | "derived" | undefined;
677
+ /**
678
+ * Whether the stream's snapshot is in the index (a GSI's `snapshot`, #149).
679
+ * A stream always sets it; absent reads as `false`.
680
+ */
681
+ readonly snapshot?: boolean | undefined;
578
682
  }
579
683
  /**
580
684
  * The `metadata` type of an event read through a stream index of projection
@@ -595,6 +699,16 @@ type ProjectionOf<C> = C extends {
595
699
  export type IndexProjectionsOf<TIndexes> = {
596
700
  readonly [K in keyof TIndexes & string]: ProjectionOf<TIndexes[K]>;
597
701
  };
702
+ /**
703
+ * The names of the indexes of a `makeStream({ indexes })` config that derive
704
+ * their partition (`partition`, #149) — those {@link EventStream.readIndex}
705
+ * and `query.index` read with an explicit `partition`.
706
+ */
707
+ export type PartitionedIndexesOf<TIndexes> = {
708
+ readonly [K in keyof TIndexes & string]: TIndexes[K] extends {
709
+ readonly partition: (...args: ReadonlyArray<any>) => unknown;
710
+ } ? K : never;
711
+ }[keyof TIndexes & string];
598
712
  /** @internal The members a stream index entry may declare. */
599
713
  type StreamIndexConfigKey = keyof LocalStreamIndexConfig<unknown> | keyof GlobalStreamIndexConfig<unknown>;
600
714
  /**
@@ -604,8 +718,8 @@ type StreamIndexConfigKey = keyof LocalStreamIndexConfig<unknown> | keyof Global
604
718
  * `TIndexes` from the entries turns off TypeScript's own excess-property check,
605
719
  * which would otherwise catch it.
606
720
  */
607
- export type StreamIndexesConfig<TIndexes, TEvent> = {
608
- readonly [K in keyof TIndexes]: StreamIndexConfig<TEvent> & {
721
+ export type StreamIndexesConfig<TIndexes, TEvent, TStreamId = StreamIndexStreamId, TState = unknown> = {
722
+ readonly [K in keyof TIndexes]: StreamIndexConfig<TEvent, TStreamId, TState> & {
609
723
  readonly [P in Exclude<keyof TIndexes[K], StreamIndexConfigKey>]: never;
610
724
  };
611
725
  };
@@ -623,6 +737,9 @@ export type StreamIndexesConfig<TIndexes, TEvent> = {
623
737
  * - `consistentRead` — strongly consistent read. DynamoDB rejects it on a
624
738
  * GSI-backed index, which it reads only eventually consistently: the read
625
739
  * fails with a `ValidationError` naming the index.
740
+ *
741
+ * An index that derives its partition (#149) also takes `partition` — see
742
+ * {@link ReadIndexArgs}.
626
743
  */
627
744
  export type ReadIndexOptions = {
628
745
  readonly reverse?: boolean | undefined;
@@ -635,9 +752,47 @@ export type ReadIndexOptions = {
635
752
  readonly between?: readonly [string, string] | undefined;
636
753
  readonly beginsWith?: undefined;
637
754
  });
755
+ /**
756
+ * The options argument of {@link EventStream.readIndex} for index `N`. An
757
+ * index that derives its partition (`partition`, #149) is read one partition
758
+ * at a time, so `partition` — the value to read, as the index's `partition`
759
+ * function returns it — is required, and the read returns the events of the
760
+ * given stream in it. Any other index is scoped to the stream already: a
761
+ * `partition` passed for it fails the read with `ValidationError` (it stays
762
+ * in the type so that a stream whose indexes derive their partition remains
763
+ * assignable to one whose indexes do not, as every stream is to
764
+ * `EventStream<E, F, M, S>`).
765
+ */
766
+ export type ReadIndexArgs<N extends string, TPartitionedIndexName extends string> = [
767
+ TPartitionedIndexName
768
+ ] extends [never] ? [
769
+ options?: (ReadIndexOptions & {
770
+ readonly partition?: string | undefined;
771
+ }) | undefined
772
+ ] : [N] extends [TPartitionedIndexName] ? [options: ReadIndexOptions & {
773
+ readonly partition: string;
774
+ }] : [options?: (ReadIndexOptions & {
775
+ readonly partition?: string | undefined;
776
+ }) | undefined];
777
+ /**
778
+ * The options argument of `query.index` for index `N` — `partition`, required
779
+ * for an index that derives its partition, as for {@link ReadIndexArgs}; on
780
+ * any other index, one passed throws `[EDD-9080]`.
781
+ */
782
+ export type IndexQueryArgs<N extends string, TPartitionedIndexName extends string> = [
783
+ TPartitionedIndexName
784
+ ] extends [never] ? [options?: {
785
+ readonly partition?: string | undefined;
786
+ } | undefined] : [N] extends [TPartitionedIndexName] ? [options: {
787
+ readonly partition: string;
788
+ }] : [options?: {
789
+ readonly partition?: string | undefined;
790
+ } | undefined];
638
791
  type StreamIdInput<T extends ReadonlyArray<string>> = {
639
792
  readonly [K in T[number]]: string;
640
793
  };
794
+ /** @internal The state type of a `makeStream({ snapshot })` config; `never` without one. */
795
+ type SnapshotStateOf<TSnapshot> = TSnapshot extends SnapshotConfig<infer TStateSchema> ? Schema.Schema.Type<TStateSchema> : never;
641
796
  declare const EventStreamTypeId: unique symbol;
642
797
  export type EventStreamTypeId = typeof EventStreamTypeId;
643
798
  /**
@@ -651,12 +806,14 @@ export type EventStreamTypeId = typeof EventStreamTypeId;
651
806
  * `query.index` only accept a declared name. `TIndexProjections` is each
652
807
  * index's {@link StreamIndexProjection} by name, so an event read through a
653
808
  * `"withoutMetadata"` index has `metadata: undefined`.
809
+ * `TPartitionedIndexName` names the indexes that derive their partition
810
+ * (#149), which are read with an explicit `partition`.
654
811
  */
655
812
  export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState = never, TIndexName extends string = never, TIndexProjections extends {
656
813
  readonly [K in TIndexName]: StreamIndexProjection;
657
814
  } = {
658
815
  readonly [K in TIndexName]: "all";
659
- }> extends Pipeable.Pipeable {
816
+ }, TPartitionedIndexName extends string = never> extends Pipeable.Pipeable {
660
817
  readonly [EventStreamTypeId]: EventStreamTypeId;
661
818
  readonly streamName: string;
662
819
  readonly eventSchema: Schema.Top;
@@ -769,8 +926,14 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
769
926
  * `metadata: undefined`, as the type says); for `"keys"`, one query with
770
927
  * `Select: ALL_ATTRIBUTES` on an LSI, or the query followed by `BatchGetItem`
771
928
  * (unprocessed keys retried, index order kept) on a GSI.
929
+ *
930
+ * An index that derives its partition (`partition`, #149) is read one
931
+ * partition at a time: pass the partition as `options.partition` (required
932
+ * by the types — see {@link ReadIndexArgs}). The read keeps the events of
933
+ * `streamId` in it — not the other streams sharing the partition, nor any
934
+ * snapshot — by filtering on the table's `pk`, which every index projects.
772
935
  */
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>;
936
+ readIndex<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>, ...options: ReadIndexArgs<N, TPartitionedIndexName>): Effect.Effect<ReadonlyArray<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>, DynamoClientError | ValidationError, DynamoClient | TableConfig>;
774
937
  /**
775
938
  * The bytes {@link EventStream.append} would write for these arguments, by
776
939
  * DynamoDB's own size accounting — for planning chunks against the 400 KB
@@ -845,8 +1008,12 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
845
1008
  * counts index entries and fetches nothing. On a `"keys"` or
846
1009
  * `"withoutMetadata"` index, a `Query.filter` on an attribute the index
847
1010
  * does not project is refused by DynamoDB — use `Query.filterBy`.
1011
+ *
1012
+ * An index that derives its partition (#149) takes the partition to read
1013
+ * as `options.partition` (see {@link IndexQueryArgs}), and the query keeps
1014
+ * `streamId`'s events in it, as {@link EventStream.readIndex} does.
848
1015
  */
849
- index<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>): Query.Query<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>;
1016
+ index<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>, ...options: IndexQueryArgs<N, TPartitionedIndexName>): Query.Query<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>;
850
1017
  };
851
1018
  }
852
1019
  /**
@@ -906,11 +1073,20 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
906
1073
  * `indexes` declares sub-streams ordered by a key derived from each event
907
1074
  * (#140), on a local (default) or global secondary index. `append` writes each
908
1075
  * event's derived key (and, for a GSI, the stream's partition key) on the
909
- * event item; snapshots and idempotency sentinels never carry them, so they
910
- * are never in an index. Read with {@link EventStream.readIndex} or
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}.
1076
+ * event item. Idempotency sentinels never carry them; snapshots carry an
1077
+ * entry only in a GSI that derives its partition and declares `snapshot`.
1078
+ * Read with {@link EventStream.readIndex} or `query.index`, and create the
1079
+ * indexes with {@link indexDefinitions}. Each index's `projection` (default
1080
+ * `"all"`) sets what it copies from each event and how it is read — see
1081
+ * {@link StreamIndexProjection}.
1082
+ *
1083
+ * A GSI's `partition` (#149) derives the partition from the stream id and the
1084
+ * event instead, stored raw, so several streams declaring the same GSI write
1085
+ * one partition of it — an order's line, payment and shipment streams under
1086
+ * `ORDER#<orderId>`, read by one query — and its `snapshot` puts each stream's
1087
+ * snapshot item in that partition too, in the same `Put` (so an inline
1088
+ * snapshot enters the index in the append's own transaction). See
1089
+ * {@link GlobalStreamIndexConfig}.
914
1090
  *
915
1091
  * Index attributes are written only at append time and events are never
916
1092
  * rewritten. Declaring an index on a stream that already holds events, or
@@ -922,6 +1098,36 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
922
1098
  *
923
1099
  * @example
924
1100
  * ```typescript
1101
+ * // Lines and payments of an order share `gsi1` under `ORDER#<orderId>`.
1102
+ * const OrderLines = EventStore.makeStream({
1103
+ * table: EventsTable,
1104
+ * streamName: "OrderLine",
1105
+ * events: [LineAdded, LineRemoved],
1106
+ * streamId: { composite: ["orderId", "lineId"] },
1107
+ * snapshot: { schema: LineState, mode: "inline" },
1108
+ * indexes: {
1109
+ * byOrder: {
1110
+ * type: "gsi",
1111
+ * index: "gsi1",
1112
+ * pk: "gsi1pk",
1113
+ * sk: "gsi1sk",
1114
+ * partition: (_event, _version, streamId) => `ORDER#${streamId.orderId}`,
1115
+ * key: (_event, version, streamId) => `LINE#${streamId.lineId}#EVT#${pad(version)}`,
1116
+ * snapshot: (_state, _asOfVersion, streamId) => ({
1117
+ * partition: `ORDER#${streamId.orderId}`,
1118
+ * key: `LINE#${streamId.lineId}#STATE`,
1119
+ * }),
1120
+ * },
1121
+ * },
1122
+ * })
1123
+ *
1124
+ * const lineEvents = yield* OrderLines.readIndex("byOrder", { orderId, lineId }, {
1125
+ * partition: `ORDER#${orderId}`,
1126
+ * })
1127
+ * ```
1128
+ *
1129
+ * @example
1130
+ * ```typescript
925
1131
  * const Entries = EventStore.makeStream({
926
1132
  * table: EventsTable,
927
1133
  * streamName: "Ledger",
@@ -946,10 +1152,12 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
946
1152
  * @throws `[EDD-9062]` when `snapshot.mode` is neither `"after-append"` nor
947
1153
  * `"inline"`.
948
1154
  * @throws `[EDD-9063]` for a malformed index: a `type` other than `"lsi"` /
949
- * `"gsi"`, a `gsi` without `pk`, an `lsi` with `pk`, an empty `index` / `sk`
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).
1155
+ * `"gsi"`, a `gsi` without `pk`, an `lsi` with `pk`, `partition` or
1156
+ * `snapshot`, an empty `index` / `sk` / `pk`, a `key` / `partition` /
1157
+ * `snapshot` that is not a function, a `snapshot` without `partition` or on
1158
+ * a stream without a `snapshot` config, a `projection` other than `"all"` /
1159
+ * `"withoutMetadata"` / `"keys"`, or a member an index entry does not
1160
+ * declare (such as a misspelled `projection`, also a type error).
953
1161
  * @throws `[EDD-9064]` when an index attribute is one the stream writes itself
954
1162
  * (`pk`, `sk`, `__edd_e__`, `streamId`, `version`, `eventType`, `data`,
955
1163
  * `metadata`, `timestamp`, `asOfVersion`, `state`, `commandId`, or the
@@ -973,7 +1181,7 @@ export interface EventStream<TEvent, TStreamIdFields extends ReadonlyArray<strin
973
1181
  */
974
1182
  export declare const makeStream: <const TEvents extends ReadonlyArray<Schema.Top>, TTable extends Table, const TStreamName extends string, const TStreamId extends {
975
1183
  readonly composite: ReadonlyArray<string>;
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: {
1184
+ }, 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]>, StreamIdInput<TStreamId["composite"]>, SnapshotStateOf<TSnapshot>>; } = {}>(config: {
977
1185
  readonly table: TTable;
978
1186
  readonly streamName: TStreamName;
979
1187
  readonly events: TEvents;
@@ -981,10 +1189,10 @@ export declare const makeStream: <const TEvents extends ReadonlyArray<Schema.Top
981
1189
  readonly metadata?: TMetadata;
982
1190
  readonly snapshot?: TSnapshot;
983
1191
  readonly casing?: DynamoSchema.Casing | undefined;
984
- readonly indexes?: TIndexes & StreamIndexesConfig<TIndexes, Schema.Schema.Type<TEvents[number]>>;
1192
+ readonly indexes?: TIndexes & StreamIndexesConfig<TIndexes, Schema.Schema.Type<TEvents[number]>, StreamIdInput<TStreamId["composite"]>, SnapshotStateOf<TSnapshot>>;
985
1193
  /** How event `data` is stored — see {@link StreamStorageConfig}. */
986
1194
  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>>;
1195
+ }) => EventStream<Schema.Schema.Type<TEvents[number]>, TStreamId["composite"], TMetadata extends Schema.Top ? Schema.Schema.Type<TMetadata> : undefined, SnapshotStateOf<TSnapshot>, keyof TIndexes & string, IndexProjectionsOf<TIndexes>, PartitionedIndexesOf<TIndexes>>;
988
1196
  /**
989
1197
  * An EventStream whose operations have `DynamoClient` and `TableConfig` already
990
1198
  * resolved, so all methods return `Effect<A, E, never>`.
@@ -1009,7 +1217,7 @@ export interface BoundEventStream<TEvent, TStreamIdFields extends ReadonlyArray<
1009
1217
  readonly [K in TIndexName]: StreamIndexProjection;
1010
1218
  } = {
1011
1219
  readonly [K in TIndexName]: "all";
1012
- }> extends Pipeable.Pipeable {
1220
+ }, TPartitionedIndexName extends string = never> extends Pipeable.Pipeable {
1013
1221
  readonly [EventStreamTypeId]: EventStreamTypeId;
1014
1222
  readonly streamName: string;
1015
1223
  readonly eventSchema: Schema.Top;
@@ -1036,7 +1244,7 @@ export interface BoundEventStream<TEvent, TStreamIdFields extends ReadonlyArray<
1036
1244
  /** See {@link EventStream.readLatest}. */
1037
1245
  readLatest(streamId: StreamIdInput<TStreamIdFields>, options?: ReadLatestOptions | undefined): Effect.Effect<LatestState<TState, TEvent, StreamMetadata<TMetadata>>, DynamoClientError | ValidationError, never>;
1038
1246
  /** See {@link EventStream.readIndex}. */
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>;
1247
+ readIndex<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>, ...options: ReadIndexArgs<N, TPartitionedIndexName>): Effect.Effect<ReadonlyArray<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>, DynamoClientError | ValidationError, never>;
1040
1248
  /** See {@link EventStream.measureAppend}. */
1041
1249
  measureAppend(streamId: StreamIdInput<TStreamIdFields>, events: ReadonlyArray<TEvent>, expectedVersion: number, options?: MeasureAppendOptions<TMetadata, TState> | undefined): Effect.Effect<AppendMeasurement, ValidationError, never>;
1042
1250
  /** See {@link EventStream.measureSnapshot}. */
@@ -1056,7 +1264,7 @@ export interface BoundEventStream<TEvent, TStreamIdFields extends ReadonlyArray<
1056
1264
  readonly query: {
1057
1265
  events(streamId: StreamIdInput<TStreamIdFields>): Query.Query<StreamEvent<TEvent, StreamMetadata<TMetadata>>>;
1058
1266
  /** See `EventStream.query.index`. */
1059
- index<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>): Query.Query<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>;
1267
+ index<N extends TIndexName>(name: N, streamId: StreamIdInput<TStreamIdFields>, ...options: IndexQueryArgs<N, TPartitionedIndexName>): Query.Query<StreamEvent<TEvent, IndexEventMetadata<TMetadata, TIndexProjections[N]>>>;
1060
1268
  };
1061
1269
  /** Escape hatch: provide DynamoClient | TableConfig to an arbitrary effect. */
1062
1270
  readonly provide: <A, E>(effect: Effect.Effect<A, E, DynamoClient | TableConfig>) => Effect.Effect<A, E, never>;
@@ -1075,7 +1283,7 @@ export interface BoundEventStream<TEvent, TStreamIdFields extends ReadonlyArray<
1075
1283
  * yield* stream.append({ matchId: "m-1" }, [event], 0) // R = never
1076
1284
  * ```
1077
1285
  */
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>;
1286
+ 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"; }, TPartitionedIndexName extends string = never>(stream: EventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName, TIndexProjections, TPartitionedIndexName>) => Effect.Effect<BoundEventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName, TIndexProjections, TPartitionedIndexName>, never, DynamoClient | TableConfig>;
1079
1287
  /**
1080
1288
  * `CreateTable` fragments for the stream indexes of one or more streams — see
1081
1289
  * {@link indexDefinitions}. The lists are absent when empty, because
@@ -1101,7 +1309,7 @@ export interface StreamIndexDefinitions {
1101
1309
  * | `projection` | `Projection` |
1102
1310
  * |---|---|
1103
1311
  * | `"all"` (default) | `{ ProjectionType: "ALL" }` |
1104
- * | `"withoutMetadata"` | `{ ProjectionType: "INCLUDE", NonKeyAttributes: ["__edd_e__", "data", "eventType", "streamId", "timestamp", "version"] }` |
1312
+ * | `"withoutMetadata"` | `{ ProjectionType: "INCLUDE", NonKeyAttributes: ["__edd_e__", "data", "eventType", "streamId", "timestamp", "version"] }` — plus `"asOfVersion"` and `"state"` when a stream declaring the index puts its snapshot in it (#149) |
1105
1313
  * | `"keys"` | `{ ProjectionType: "INCLUDE", NonKeyAttributes: ["__edd_e__"] }` — not `KEYS_ONLY`: every read filters on `__edd_e__` |
1106
1314
  *
1107
1315
  * The deployed index must project what is declared — copy `Projection` from
@@ -1112,7 +1320,12 @@ export interface StreamIndexDefinitions {
1112
1320
  * table over the limit).
1113
1321
  *
1114
1322
  * An index shared by several streams with the same definition is emitted
1115
- * once. Lists are sorted by index name, attributes by attribute name.
1323
+ * once — the way several streams write one GSI whose partition they derive
1324
+ * (`partition`, #149): an order's line, payment and shipment streams under
1325
+ * `ORDER#<orderId>`, say. Whether each declaration derives its partition or
1326
+ * indexes its snapshot is not part of the definition: the index's key schema
1327
+ * and projection kind are. Lists are sorted by index name, attributes by
1328
+ * attribute name.
1116
1329
  *
1117
1330
  * - An LSI can only be created with the table, and caps the item collection
1118
1331
  * of every partition key value in the table at 10 GB — entity partitions
@@ -1140,13 +1353,19 @@ export interface StreamIndexDefinitions {
1140
1353
  *
1141
1354
  * @throws `[EDD-9067]` when two streams define the same physical index
1142
1355
  * differently (another type, other key attributes, or another projection).
1356
+ * Whether a declaration derives its partition or indexes its snapshot is not
1357
+ * part of the index's definition: streams that differ only there share it.
1143
1358
  */
1144
1359
  export declare const indexDefinitions: (...streams: ReadonlyArray<{
1145
1360
  readonly streamName: string;
1146
1361
  readonly indexes: {
1147
- readonly [name: string]: Omit<StreamIndexSettings, "projection"> & {
1362
+ readonly [name: string]: Omit<StreamIndexSettings, "projection" | "partition" | "snapshot"> & {
1148
1363
  /** Read as `"all"` when absent (a hand-built settings object). */
1149
1364
  readonly projection?: StreamIndexProjection | undefined;
1365
+ /** Read as `"stream"` when absent (a hand-built settings object). */
1366
+ readonly partition?: "stream" | "derived" | undefined;
1367
+ /** Read as `false` when absent (a hand-built settings object). */
1368
+ readonly snapshot?: boolean | undefined;
1150
1369
  };
1151
1370
  };
1152
1371
  }>) => StreamIndexDefinitions;
@@ -1507,7 +1726,7 @@ type BoundCommandHandler<State, Command, TEvent, E, TStreamIdFields extends Read
1507
1726
  * stream is also structurally an `EventStream`), a {@link CommandHandler}
1508
1727
  * otherwise.
1509
1728
  */
1510
- type CommandHandlerFor<S, State, Command, TEvent, E, TConfig extends CommandHandlerOptions | undefined> = S extends BoundEventStream<any, infer TStreamIdFields, infer TMetadata, any, any> ? BoundCommandHandler<State, Command, TEvent, E, TStreamIdFields, TMetadata, TConfig> : S extends EventStream<any, infer TStreamIdFields, infer TMetadata, any, any> ? CommandHandler<State, Command, TEvent, E, TStreamIdFields, TMetadata, TConfig> : never;
1729
+ type CommandHandlerFor<S, State, Command, TEvent, E, TConfig extends CommandHandlerOptions | undefined> = S extends BoundEventStream<any, infer TStreamIdFields, infer TMetadata, any, any, any, any> ? BoundCommandHandler<State, Command, TEvent, E, TStreamIdFields, TMetadata, TConfig> : S extends EventStream<any, infer TStreamIdFields, infer TMetadata, any, any, any, any> ? CommandHandler<State, Command, TEvent, E, TStreamIdFields, TMetadata, TConfig> : never;
1511
1730
  /**
1512
1731
  * Create a command handler that reads, decides, and appends atomically.
1513
1732
  *
@@ -1607,13 +1826,13 @@ type CommandHandlerFor<S, State, Command, TEvent, E, TConfig extends CommandHand
1607
1826
  * `EventStreamTypeId` brand of the second argument.
1608
1827
  */
1609
1828
  export declare const commandHandler: {
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>;
1829
+ <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, any> | EventStream<TEvent, ReadonlyArray<string>, any, State, any, any, any>>(stream: S) => CommandHandlerFor<S, State, Command, TEvent, E, TConfig>;
1611
1830
  <State, Command, TEvent, E, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState extends State, TIndexName extends string, TIndexProjections extends {
1612
1831
  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>;
1832
+ }, TPartitionedIndexName extends string, const TConfig extends CommandHandlerOptions | undefined = undefined>(decider: Decider<State, Command, TEvent, E>, stream: BoundEventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName, TIndexProjections, TPartitionedIndexName>, options?: TConfig): BoundCommandHandler<State, Command, TEvent, E, TStreamIdFields, TMetadata, TConfig>;
1614
1833
  <State, Command, TEvent, E, TStreamIdFields extends ReadonlyArray<string>, TMetadata, TState extends State, TIndexName extends string, TIndexProjections extends {
1615
1834
  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>;
1835
+ }, TPartitionedIndexName extends string, const TConfig extends CommandHandlerOptions | undefined = undefined>(decider: Decider<State, Command, TEvent, E>, stream: EventStream<TEvent, TStreamIdFields, TMetadata, TState, TIndexName, TIndexProjections, TPartitionedIndexName>, options?: TConfig): CommandHandler<State, Command, TEvent, E, TStreamIdFields, TMetadata, TConfig>;
1617
1836
  };
1618
1837
  /**
1619
1838
  * Reconstruct state from events by folding through a decider's `evolve` function.