@cosmicdrift/kumiko-types 0.163.2 → 0.164.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.163.2",
3
+ "version": "0.164.0",
4
4
  "description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types, ohne Runtime-Code. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -15,6 +15,25 @@ export class VersionConflictError extends Error {
15
15
  }
16
16
  }
17
17
 
18
+ // Thrown when append() collides on the partial unique index over
19
+ // metadata.idempotencyKey (tenant-scoped). Distinct from VersionConflictError:
20
+ // a version conflict means two writers raced the same predecessor; this
21
+ // means the same idempotency key was used twice, which is a caller-side
22
+ // retry that must have already appended once. Callers that set
23
+ // idempotencyKey should treat this as "already applied" rather than retry.
24
+ export class IdempotentAppendConflictError extends Error {
25
+ public readonly tenantId: string;
26
+ public readonly idempotencyKey: string;
27
+ constructor(tenantId: string, idempotencyKey: string) {
28
+ super(
29
+ `Idempotency conflict on tenant ${tenantId}: an event with idempotencyKey "${idempotencyKey}" was already appended.`,
30
+ );
31
+ this.name = "IdempotentAppendConflictError";
32
+ this.tenantId = tenantId;
33
+ this.idempotencyKey = idempotencyKey;
34
+ }
35
+ }
36
+
18
37
  // Thrown when ctx.appendEvent targets an archived stream. Archived aggregates
19
38
  // are read-only — restoreStream() makes them writable again. The archive
20
39
  // state is not carried on the events themselves; it lives on the sparse
@@ -12,6 +12,14 @@ export type EventMetadata = {
12
12
  // set to event.id when an MSP-apply runs ctx.appendEvent. Together with
13
13
  // correlationId forms a causation DAG across aggregate streams.
14
14
  readonly causationId?: string;
15
+ // Opt-in second line of defense against duplicate appends when the
16
+ // Redis-backed HTTP idempotency guard (pipeline/idempotency.ts) misses a
17
+ // retry window (Redis unreachable, TTL race). Callers that want a hard
18
+ // per-event uniqueness guarantee set a stable key derived from the
19
+ // triggering request; the event-store enforces it via a tenant-scoped
20
+ // partial unique index and throws IdempotentAppendConflictError on a
21
+ // repeat. Unset by default — no uniqueness constraint applies.
22
+ readonly idempotencyKey?: string;
15
23
  // Marten-conform free key/value space for app-specific metadata that
16
24
  // doesn't deserve its own EventMetadata field. Examples: A/B-test bucket,
17
25
  // feature-flag snapshot, geo-region, client SDK version. Persisted into