@campfire-interactive/platform-events 0.1.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/consumer.d.ts +19 -0
- package/dist/consumer.js +36 -0
- package/dist/dedup.d.ts +59 -0
- package/dist/dedup.js +63 -0
- package/dist/envelope.d.ts +231 -0
- package/dist/envelope.js +77 -0
- package/dist/events/master-data.d.ts +460 -0
- package/dist/events/master-data.js +337 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +31 -0
- package/dist/outbox.d.ts +44 -0
- package/dist/outbox.js +38 -0
- package/dist/publisher.d.ts +59 -0
- package/dist/publisher.js +130 -0
- package/dist/saga.d.ts +30 -0
- package/dist/saga.js +39 -0
- package/package.json +30 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Envelope } from './envelope';
|
|
2
|
+
import { MasterDataEventType, MasterDataPayload } from './events/master-data';
|
|
3
|
+
/**
|
|
4
|
+
* Consumer-side helpers (ADR §10: consumers need the types as much as
|
|
5
|
+
* producers). The eventId dedup helper and replay-drop default land in
|
|
6
|
+
* plan Phase 1b.
|
|
7
|
+
*/
|
|
8
|
+
/** Parse + validate the envelope of any message off the bus (payload untyped). */
|
|
9
|
+
export declare function parseEnvelope(detail: unknown): Envelope;
|
|
10
|
+
export interface MasterDataEvent<T extends MasterDataEventType = MasterDataEventType> {
|
|
11
|
+
type: T;
|
|
12
|
+
envelope: Envelope<MasterDataPayload<T>>;
|
|
13
|
+
}
|
|
14
|
+
export declare function isMasterDataEventType(type: string): type is MasterDataEventType;
|
|
15
|
+
/**
|
|
16
|
+
* Parse a message known to come from the master-data catalog; validates
|
|
17
|
+
* both envelope and payload and narrows the payload type on `type`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseMasterDataEvent(detail: unknown): MasterDataEvent;
|
package/dist/consumer.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseEnvelope = parseEnvelope;
|
|
4
|
+
exports.isMasterDataEventType = isMasterDataEventType;
|
|
5
|
+
exports.parseMasterDataEvent = parseMasterDataEvent;
|
|
6
|
+
const envelope_1 = require("./envelope");
|
|
7
|
+
const master_data_1 = require("./events/master-data");
|
|
8
|
+
/**
|
|
9
|
+
* Consumer-side helpers (ADR §10: consumers need the types as much as
|
|
10
|
+
* producers). The eventId dedup helper and replay-drop default land in
|
|
11
|
+
* plan Phase 1b.
|
|
12
|
+
*/
|
|
13
|
+
/** Parse + validate the envelope of any message off the bus (payload untyped). */
|
|
14
|
+
function parseEnvelope(detail) {
|
|
15
|
+
const raw = typeof detail === 'string' ? JSON.parse(detail) : detail;
|
|
16
|
+
return envelope_1.envelopeSchema.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
function isMasterDataEventType(type) {
|
|
19
|
+
return type in master_data_1.masterDataEvents;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse a message known to come from the master-data catalog; validates
|
|
23
|
+
* both envelope and payload and narrows the payload type on `type`.
|
|
24
|
+
*/
|
|
25
|
+
function parseMasterDataEvent(detail) {
|
|
26
|
+
const envelope = parseEnvelope(detail);
|
|
27
|
+
if (!isMasterDataEventType(envelope.type)) {
|
|
28
|
+
throw new Error(`platform-events: '${envelope.type}' is not a catalogued master-data event`);
|
|
29
|
+
}
|
|
30
|
+
const def = master_data_1.masterDataEvents[envelope.type];
|
|
31
|
+
const payload = def.payload.parse(envelope.payload);
|
|
32
|
+
return {
|
|
33
|
+
type: envelope.type,
|
|
34
|
+
envelope: { ...envelope, payload },
|
|
35
|
+
};
|
|
36
|
+
}
|
package/dist/dedup.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import { Envelope } from './envelope';
|
|
3
|
+
/**
|
|
4
|
+
* Consumer-side eventId dedup (ADR open problem 1, resolution per plan
|
|
5
|
+
* decision 6.1): a shared store claims `consumerApp#eventId` BEFORE side
|
|
6
|
+
* effects run, so at-least-once delivery doesn't double-execute them.
|
|
7
|
+
*
|
|
8
|
+
* Apps with natural state-machine idempotency (e.g. OMSF's
|
|
9
|
+
* processRoutedQuote status guard) may skip this — the obligation is
|
|
10
|
+
* idempotency, not this table.
|
|
11
|
+
*/
|
|
12
|
+
export interface DedupStore {
|
|
13
|
+
/**
|
|
14
|
+
* Atomically claim an eventId for a consumer. Returns true if this is
|
|
15
|
+
* the first claim (proceed), false if already claimed (skip).
|
|
16
|
+
*/
|
|
17
|
+
claim(consumerApp: string, eventId: string): Promise<boolean>;
|
|
18
|
+
}
|
|
19
|
+
export interface DynamoDbDedupStoreOptions {
|
|
20
|
+
/** e.g. 'platform-events-processed' — provisioned per environment in Phase 2 infra */
|
|
21
|
+
tableName: string;
|
|
22
|
+
client?: DynamoDBClient;
|
|
23
|
+
/** Retention for claims; default 14 days. */
|
|
24
|
+
ttlSeconds?: number;
|
|
25
|
+
/** Test seam; defaults to Date.now. */
|
|
26
|
+
now?: () => number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* DynamoDB-backed store: conditional put on `pk = <consumerApp>#<eventId>`
|
|
30
|
+
* with a TTL attribute. Table schema: pk (S, partition key), ttl (N,
|
|
31
|
+
* enabled as the TTL attribute).
|
|
32
|
+
*/
|
|
33
|
+
export declare class DynamoDbDedupStore implements DedupStore {
|
|
34
|
+
private readonly client;
|
|
35
|
+
private readonly tableName;
|
|
36
|
+
private readonly ttlSeconds;
|
|
37
|
+
private readonly now;
|
|
38
|
+
constructor(opts: DynamoDbDedupStoreOptions);
|
|
39
|
+
claim(consumerApp: string, eventId: string): Promise<boolean>;
|
|
40
|
+
}
|
|
41
|
+
/** True when the message was re-emitted by the replay runbook (Phase 6). */
|
|
42
|
+
export declare function isReplay(envelope: Envelope): boolean;
|
|
43
|
+
export type HandleResult = 'handled' | 'duplicate' | 'replay-dropped';
|
|
44
|
+
export interface WithIdempotencyOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Whether replayed messages run the handler. Default false (ADR open
|
|
47
|
+
* problem 2: non-idempotent side effects must not double-execute on
|
|
48
|
+
* archive replay) — opt in per handler for replay-safe event types.
|
|
49
|
+
*/
|
|
50
|
+
allowReplay?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Claim-then-handle wrapper. Skips duplicates and (by default) replays.
|
|
54
|
+
* If the handler throws AFTER the claim, the message redelivers via SQS
|
|
55
|
+
* but the claim stands — so handlers must put their own side effects
|
|
56
|
+
* behind the claim only when they are all-or-nothing; otherwise rely on
|
|
57
|
+
* state-machine idempotency for the partial-failure window.
|
|
58
|
+
*/
|
|
59
|
+
export declare function withIdempotency(store: DedupStore, consumerApp: string, envelope: Envelope, handler: () => Promise<void>, opts?: WithIdempotencyOptions): Promise<HandleResult>;
|
package/dist/dedup.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DynamoDbDedupStore = void 0;
|
|
4
|
+
exports.isReplay = isReplay;
|
|
5
|
+
exports.withIdempotency = withIdempotency;
|
|
6
|
+
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
7
|
+
const DEFAULT_TTL_SECONDS = 14 * 24 * 60 * 60;
|
|
8
|
+
/**
|
|
9
|
+
* DynamoDB-backed store: conditional put on `pk = <consumerApp>#<eventId>`
|
|
10
|
+
* with a TTL attribute. Table schema: pk (S, partition key), ttl (N,
|
|
11
|
+
* enabled as the TTL attribute).
|
|
12
|
+
*/
|
|
13
|
+
class DynamoDbDedupStore {
|
|
14
|
+
client;
|
|
15
|
+
tableName;
|
|
16
|
+
ttlSeconds;
|
|
17
|
+
now;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
this.client = opts.client ?? new client_dynamodb_1.DynamoDBClient({});
|
|
20
|
+
this.tableName = opts.tableName;
|
|
21
|
+
this.ttlSeconds = opts.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
22
|
+
this.now = opts.now ?? Date.now;
|
|
23
|
+
}
|
|
24
|
+
async claim(consumerApp, eventId) {
|
|
25
|
+
try {
|
|
26
|
+
await this.client.send(new client_dynamodb_1.PutItemCommand({
|
|
27
|
+
TableName: this.tableName,
|
|
28
|
+
Item: {
|
|
29
|
+
pk: { S: `${consumerApp}#${eventId}` },
|
|
30
|
+
ttl: { N: String(Math.floor(this.now() / 1000) + this.ttlSeconds) },
|
|
31
|
+
},
|
|
32
|
+
ConditionExpression: 'attribute_not_exists(pk)',
|
|
33
|
+
}));
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err instanceof client_dynamodb_1.ConditionalCheckFailedException)
|
|
38
|
+
return false;
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.DynamoDbDedupStore = DynamoDbDedupStore;
|
|
44
|
+
/** True when the message was re-emitted by the replay runbook (Phase 6). */
|
|
45
|
+
function isReplay(envelope) {
|
|
46
|
+
return envelope.replay === true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Claim-then-handle wrapper. Skips duplicates and (by default) replays.
|
|
50
|
+
* If the handler throws AFTER the claim, the message redelivers via SQS
|
|
51
|
+
* but the claim stands — so handlers must put their own side effects
|
|
52
|
+
* behind the claim only when they are all-or-nothing; otherwise rely on
|
|
53
|
+
* state-machine idempotency for the partial-failure window.
|
|
54
|
+
*/
|
|
55
|
+
async function withIdempotency(store, consumerApp, envelope, handler, opts = {}) {
|
|
56
|
+
if (isReplay(envelope) && !opts.allowReplay)
|
|
57
|
+
return 'replay-dropped';
|
|
58
|
+
const firstClaim = await store.claim(consumerApp, envelope.eventId);
|
|
59
|
+
if (!firstClaim)
|
|
60
|
+
return 'duplicate';
|
|
61
|
+
await handler();
|
|
62
|
+
return 'handled';
|
|
63
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Envelope v2 — ADR 2026-06-05 §6 ("A mandatory event/command envelope").
|
|
4
|
+
*
|
|
5
|
+
* schemaVersion 1 is the shape master-data publishes on the legacy
|
|
6
|
+
* `master-data-events` bus ({ schemaVersion: 1, eventId, tenantId,
|
|
7
|
+
* entityType, action, actor, timestamp, payload }). It is NOT modeled
|
|
8
|
+
* here: v1 keeps flowing untouched on the legacy bus during the
|
|
9
|
+
* dual-publish window, and consumers migrate envelope shape at the same
|
|
10
|
+
* moment they migrate buses (plan decision 2).
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Known producers. Matches master-data's deployed `EventCallerService`
|
|
14
|
+
* union — hyphenated `master-data`, settled at ratification (plan
|
|
15
|
+
* decision 3; the ADR uses this spelling).
|
|
16
|
+
*/
|
|
17
|
+
export declare const PRODUCERS: readonly ["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"];
|
|
18
|
+
export declare const producerSchema: z.ZodEnum<["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"]>;
|
|
19
|
+
export type Producer = z.infer<typeof producerSchema>;
|
|
20
|
+
export declare const ENVELOPE_SCHEMA_VERSION: 2;
|
|
21
|
+
/** Envelope with an unvalidated payload — use `envelopeFor(schema)` when the payload shape is known. */
|
|
22
|
+
export declare const envelopeSchema: z.ZodObject<{
|
|
23
|
+
schemaVersion: z.ZodLiteral<2>;
|
|
24
|
+
/** uuid — the idempotency/dedupe key */
|
|
25
|
+
eventId: z.ZodString;
|
|
26
|
+
/** ISO-8601 instant the state change committed at the producer */
|
|
27
|
+
occurredAt: z.ZodString;
|
|
28
|
+
producer: z.ZodEnum<["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"]>;
|
|
29
|
+
/** = EventBridge detail-type, `<entity>.<action>` (or `<verb>.<noun>` for commands) */
|
|
30
|
+
type: z.ZodString;
|
|
31
|
+
entityType: z.ZodString;
|
|
32
|
+
/** id of the affected entity (the survivor id for merge events) */
|
|
33
|
+
entityId: z.ZodString;
|
|
34
|
+
/**
|
|
35
|
+
* Monotonic per-entity version for consumer-side ordering (§5).
|
|
36
|
+
* Optional in 0.x because master-data does not yet track one; becomes
|
|
37
|
+
* required before 1.0.0 — producers derive it (e.g. updatedAt epoch ms)
|
|
38
|
+
* if they have no version column. See plan Phase 2.
|
|
39
|
+
*/
|
|
40
|
+
entityVersion: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
/** null only for platform-global entities (region, part_family) */
|
|
42
|
+
tenantId: z.ZodNullable<z.ZodString>;
|
|
43
|
+
/** acting user, when the change was user-initiated */
|
|
44
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
45
|
+
/** commands only: the app whose inbox this is routed to */
|
|
46
|
+
recipient: z.ZodOptional<z.ZodEnum<["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"]>>;
|
|
47
|
+
/** commands only: async request/reply correlation */
|
|
48
|
+
correlationId: z.ZodOptional<z.ZodString>;
|
|
49
|
+
/** commands only: sender's inbox identifier for the reply */
|
|
50
|
+
replyTo: z.ZodOptional<z.ZodString>;
|
|
51
|
+
/** saga correlation — auto-propagated by the SDK across a choreographed chain */
|
|
52
|
+
sagaId: z.ZodOptional<z.ZodString>;
|
|
53
|
+
/** eventId of the message whose handling caused this one */
|
|
54
|
+
causationId: z.ZodOptional<z.ZodString>;
|
|
55
|
+
/** marks a saga compensating ("undo") event */
|
|
56
|
+
compensation: z.ZodOptional<z.ZodBoolean>;
|
|
57
|
+
/** stamped by the replay runbook's transformer; non-idempotent handlers drop these */
|
|
58
|
+
replay: z.ZodOptional<z.ZodBoolean>;
|
|
59
|
+
/** distributed-tracing id, producer → bus → queue → consumer → browser */
|
|
60
|
+
traceId: z.ZodString;
|
|
61
|
+
} & {
|
|
62
|
+
payload: z.ZodUnknown;
|
|
63
|
+
}, "strip", z.ZodTypeAny, {
|
|
64
|
+
type: string;
|
|
65
|
+
schemaVersion: 2;
|
|
66
|
+
eventId: string;
|
|
67
|
+
occurredAt: string;
|
|
68
|
+
producer: "master-data" | "cpq" | "omsf" | "pim" | "erfq" | "forecast" | "identity";
|
|
69
|
+
entityType: string;
|
|
70
|
+
entityId: string;
|
|
71
|
+
tenantId: string | null;
|
|
72
|
+
traceId: string;
|
|
73
|
+
entityVersion?: number | undefined;
|
|
74
|
+
userId?: string | undefined;
|
|
75
|
+
recipient?: "master-data" | "cpq" | "omsf" | "pim" | "erfq" | "forecast" | "identity" | undefined;
|
|
76
|
+
correlationId?: string | undefined;
|
|
77
|
+
replyTo?: string | undefined;
|
|
78
|
+
sagaId?: string | undefined;
|
|
79
|
+
causationId?: string | undefined;
|
|
80
|
+
compensation?: boolean | undefined;
|
|
81
|
+
replay?: boolean | undefined;
|
|
82
|
+
payload?: unknown;
|
|
83
|
+
}, {
|
|
84
|
+
type: string;
|
|
85
|
+
schemaVersion: 2;
|
|
86
|
+
eventId: string;
|
|
87
|
+
occurredAt: string;
|
|
88
|
+
producer: "master-data" | "cpq" | "omsf" | "pim" | "erfq" | "forecast" | "identity";
|
|
89
|
+
entityType: string;
|
|
90
|
+
entityId: string;
|
|
91
|
+
tenantId: string | null;
|
|
92
|
+
traceId: string;
|
|
93
|
+
entityVersion?: number | undefined;
|
|
94
|
+
userId?: string | undefined;
|
|
95
|
+
recipient?: "master-data" | "cpq" | "omsf" | "pim" | "erfq" | "forecast" | "identity" | undefined;
|
|
96
|
+
correlationId?: string | undefined;
|
|
97
|
+
replyTo?: string | undefined;
|
|
98
|
+
sagaId?: string | undefined;
|
|
99
|
+
causationId?: string | undefined;
|
|
100
|
+
compensation?: boolean | undefined;
|
|
101
|
+
replay?: boolean | undefined;
|
|
102
|
+
payload?: unknown;
|
|
103
|
+
}>;
|
|
104
|
+
export type Envelope<TPayload = unknown> = Omit<z.infer<typeof envelopeSchema>, 'payload'> & {
|
|
105
|
+
payload: TPayload;
|
|
106
|
+
};
|
|
107
|
+
/** Envelope schema with a typed, validated payload. */
|
|
108
|
+
export declare function envelopeFor<TSchema extends z.ZodTypeAny>(payload: TSchema): z.ZodObject<{
|
|
109
|
+
schemaVersion: z.ZodLiteral<2>;
|
|
110
|
+
/** uuid — the idempotency/dedupe key */
|
|
111
|
+
eventId: z.ZodString;
|
|
112
|
+
/** ISO-8601 instant the state change committed at the producer */
|
|
113
|
+
occurredAt: z.ZodString;
|
|
114
|
+
producer: z.ZodEnum<["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"]>;
|
|
115
|
+
/** = EventBridge detail-type, `<entity>.<action>` (or `<verb>.<noun>` for commands) */
|
|
116
|
+
type: z.ZodString;
|
|
117
|
+
entityType: z.ZodString;
|
|
118
|
+
/** id of the affected entity (the survivor id for merge events) */
|
|
119
|
+
entityId: z.ZodString;
|
|
120
|
+
/**
|
|
121
|
+
* Monotonic per-entity version for consumer-side ordering (§5).
|
|
122
|
+
* Optional in 0.x because master-data does not yet track one; becomes
|
|
123
|
+
* required before 1.0.0 — producers derive it (e.g. updatedAt epoch ms)
|
|
124
|
+
* if they have no version column. See plan Phase 2.
|
|
125
|
+
*/
|
|
126
|
+
entityVersion: z.ZodOptional<z.ZodNumber>;
|
|
127
|
+
/** null only for platform-global entities (region, part_family) */
|
|
128
|
+
tenantId: z.ZodNullable<z.ZodString>;
|
|
129
|
+
/** acting user, when the change was user-initiated */
|
|
130
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
131
|
+
/** commands only: the app whose inbox this is routed to */
|
|
132
|
+
recipient: z.ZodOptional<z.ZodEnum<["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"]>>;
|
|
133
|
+
/** commands only: async request/reply correlation */
|
|
134
|
+
correlationId: z.ZodOptional<z.ZodString>;
|
|
135
|
+
/** commands only: sender's inbox identifier for the reply */
|
|
136
|
+
replyTo: z.ZodOptional<z.ZodString>;
|
|
137
|
+
/** saga correlation — auto-propagated by the SDK across a choreographed chain */
|
|
138
|
+
sagaId: z.ZodOptional<z.ZodString>;
|
|
139
|
+
/** eventId of the message whose handling caused this one */
|
|
140
|
+
causationId: z.ZodOptional<z.ZodString>;
|
|
141
|
+
/** marks a saga compensating ("undo") event */
|
|
142
|
+
compensation: z.ZodOptional<z.ZodBoolean>;
|
|
143
|
+
/** stamped by the replay runbook's transformer; non-idempotent handlers drop these */
|
|
144
|
+
replay: z.ZodOptional<z.ZodBoolean>;
|
|
145
|
+
/** distributed-tracing id, producer → bus → queue → consumer → browser */
|
|
146
|
+
traceId: z.ZodString;
|
|
147
|
+
} & {
|
|
148
|
+
payload: TSchema;
|
|
149
|
+
}, "strip", z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<{
|
|
150
|
+
schemaVersion: z.ZodLiteral<2>;
|
|
151
|
+
/** uuid — the idempotency/dedupe key */
|
|
152
|
+
eventId: z.ZodString;
|
|
153
|
+
/** ISO-8601 instant the state change committed at the producer */
|
|
154
|
+
occurredAt: z.ZodString;
|
|
155
|
+
producer: z.ZodEnum<["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"]>;
|
|
156
|
+
/** = EventBridge detail-type, `<entity>.<action>` (or `<verb>.<noun>` for commands) */
|
|
157
|
+
type: z.ZodString;
|
|
158
|
+
entityType: z.ZodString;
|
|
159
|
+
/** id of the affected entity (the survivor id for merge events) */
|
|
160
|
+
entityId: z.ZodString;
|
|
161
|
+
/**
|
|
162
|
+
* Monotonic per-entity version for consumer-side ordering (§5).
|
|
163
|
+
* Optional in 0.x because master-data does not yet track one; becomes
|
|
164
|
+
* required before 1.0.0 — producers derive it (e.g. updatedAt epoch ms)
|
|
165
|
+
* if they have no version column. See plan Phase 2.
|
|
166
|
+
*/
|
|
167
|
+
entityVersion: z.ZodOptional<z.ZodNumber>;
|
|
168
|
+
/** null only for platform-global entities (region, part_family) */
|
|
169
|
+
tenantId: z.ZodNullable<z.ZodString>;
|
|
170
|
+
/** acting user, when the change was user-initiated */
|
|
171
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
172
|
+
/** commands only: the app whose inbox this is routed to */
|
|
173
|
+
recipient: z.ZodOptional<z.ZodEnum<["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"]>>;
|
|
174
|
+
/** commands only: async request/reply correlation */
|
|
175
|
+
correlationId: z.ZodOptional<z.ZodString>;
|
|
176
|
+
/** commands only: sender's inbox identifier for the reply */
|
|
177
|
+
replyTo: z.ZodOptional<z.ZodString>;
|
|
178
|
+
/** saga correlation — auto-propagated by the SDK across a choreographed chain */
|
|
179
|
+
sagaId: z.ZodOptional<z.ZodString>;
|
|
180
|
+
/** eventId of the message whose handling caused this one */
|
|
181
|
+
causationId: z.ZodOptional<z.ZodString>;
|
|
182
|
+
/** marks a saga compensating ("undo") event */
|
|
183
|
+
compensation: z.ZodOptional<z.ZodBoolean>;
|
|
184
|
+
/** stamped by the replay runbook's transformer; non-idempotent handlers drop these */
|
|
185
|
+
replay: z.ZodOptional<z.ZodBoolean>;
|
|
186
|
+
/** distributed-tracing id, producer → bus → queue → consumer → browser */
|
|
187
|
+
traceId: z.ZodString;
|
|
188
|
+
} & {
|
|
189
|
+
payload: TSchema;
|
|
190
|
+
}>, any> extends infer T ? { [k in keyof T]: T[k]; } : never, z.baseObjectInputType<{
|
|
191
|
+
schemaVersion: z.ZodLiteral<2>;
|
|
192
|
+
/** uuid — the idempotency/dedupe key */
|
|
193
|
+
eventId: z.ZodString;
|
|
194
|
+
/** ISO-8601 instant the state change committed at the producer */
|
|
195
|
+
occurredAt: z.ZodString;
|
|
196
|
+
producer: z.ZodEnum<["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"]>;
|
|
197
|
+
/** = EventBridge detail-type, `<entity>.<action>` (or `<verb>.<noun>` for commands) */
|
|
198
|
+
type: z.ZodString;
|
|
199
|
+
entityType: z.ZodString;
|
|
200
|
+
/** id of the affected entity (the survivor id for merge events) */
|
|
201
|
+
entityId: z.ZodString;
|
|
202
|
+
/**
|
|
203
|
+
* Monotonic per-entity version for consumer-side ordering (§5).
|
|
204
|
+
* Optional in 0.x because master-data does not yet track one; becomes
|
|
205
|
+
* required before 1.0.0 — producers derive it (e.g. updatedAt epoch ms)
|
|
206
|
+
* if they have no version column. See plan Phase 2.
|
|
207
|
+
*/
|
|
208
|
+
entityVersion: z.ZodOptional<z.ZodNumber>;
|
|
209
|
+
/** null only for platform-global entities (region, part_family) */
|
|
210
|
+
tenantId: z.ZodNullable<z.ZodString>;
|
|
211
|
+
/** acting user, when the change was user-initiated */
|
|
212
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
213
|
+
/** commands only: the app whose inbox this is routed to */
|
|
214
|
+
recipient: z.ZodOptional<z.ZodEnum<["master-data", "cpq", "omsf", "pim", "erfq", "forecast", "identity"]>>;
|
|
215
|
+
/** commands only: async request/reply correlation */
|
|
216
|
+
correlationId: z.ZodOptional<z.ZodString>;
|
|
217
|
+
/** commands only: sender's inbox identifier for the reply */
|
|
218
|
+
replyTo: z.ZodOptional<z.ZodString>;
|
|
219
|
+
/** saga correlation — auto-propagated by the SDK across a choreographed chain */
|
|
220
|
+
sagaId: z.ZodOptional<z.ZodString>;
|
|
221
|
+
/** eventId of the message whose handling caused this one */
|
|
222
|
+
causationId: z.ZodOptional<z.ZodString>;
|
|
223
|
+
/** marks a saga compensating ("undo") event */
|
|
224
|
+
compensation: z.ZodOptional<z.ZodBoolean>;
|
|
225
|
+
/** stamped by the replay runbook's transformer; non-idempotent handlers drop these */
|
|
226
|
+
replay: z.ZodOptional<z.ZodBoolean>;
|
|
227
|
+
/** distributed-tracing id, producer → bus → queue → consumer → browser */
|
|
228
|
+
traceId: z.ZodString;
|
|
229
|
+
} & {
|
|
230
|
+
payload: TSchema;
|
|
231
|
+
}> extends infer T_1 ? { [k_1 in keyof T_1]: T_1[k_1]; } : never>;
|
package/dist/envelope.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.envelopeSchema = exports.ENVELOPE_SCHEMA_VERSION = exports.producerSchema = exports.PRODUCERS = void 0;
|
|
4
|
+
exports.envelopeFor = envelopeFor;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
/**
|
|
7
|
+
* Envelope v2 — ADR 2026-06-05 §6 ("A mandatory event/command envelope").
|
|
8
|
+
*
|
|
9
|
+
* schemaVersion 1 is the shape master-data publishes on the legacy
|
|
10
|
+
* `master-data-events` bus ({ schemaVersion: 1, eventId, tenantId,
|
|
11
|
+
* entityType, action, actor, timestamp, payload }). It is NOT modeled
|
|
12
|
+
* here: v1 keeps flowing untouched on the legacy bus during the
|
|
13
|
+
* dual-publish window, and consumers migrate envelope shape at the same
|
|
14
|
+
* moment they migrate buses (plan decision 2).
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Known producers. Matches master-data's deployed `EventCallerService`
|
|
18
|
+
* union — hyphenated `master-data`, settled at ratification (plan
|
|
19
|
+
* decision 3; the ADR uses this spelling).
|
|
20
|
+
*/
|
|
21
|
+
exports.PRODUCERS = [
|
|
22
|
+
'master-data',
|
|
23
|
+
'cpq',
|
|
24
|
+
'omsf',
|
|
25
|
+
'pim',
|
|
26
|
+
'erfq',
|
|
27
|
+
'forecast',
|
|
28
|
+
'identity',
|
|
29
|
+
];
|
|
30
|
+
exports.producerSchema = zod_1.z.enum(exports.PRODUCERS);
|
|
31
|
+
exports.ENVELOPE_SCHEMA_VERSION = 2;
|
|
32
|
+
const envelopeBase = zod_1.z.object({
|
|
33
|
+
schemaVersion: zod_1.z.literal(exports.ENVELOPE_SCHEMA_VERSION),
|
|
34
|
+
/** uuid — the idempotency/dedupe key */
|
|
35
|
+
eventId: zod_1.z.string().uuid(),
|
|
36
|
+
/** ISO-8601 instant the state change committed at the producer */
|
|
37
|
+
occurredAt: zod_1.z.string().datetime(),
|
|
38
|
+
producer: exports.producerSchema,
|
|
39
|
+
/** = EventBridge detail-type, `<entity>.<action>` (or `<verb>.<noun>` for commands) */
|
|
40
|
+
type: zod_1.z.string().min(1),
|
|
41
|
+
entityType: zod_1.z.string().min(1),
|
|
42
|
+
/** id of the affected entity (the survivor id for merge events) */
|
|
43
|
+
entityId: zod_1.z.string().min(1),
|
|
44
|
+
/**
|
|
45
|
+
* Monotonic per-entity version for consumer-side ordering (§5).
|
|
46
|
+
* Optional in 0.x because master-data does not yet track one; becomes
|
|
47
|
+
* required before 1.0.0 — producers derive it (e.g. updatedAt epoch ms)
|
|
48
|
+
* if they have no version column. See plan Phase 2.
|
|
49
|
+
*/
|
|
50
|
+
entityVersion: zod_1.z.number().int().nonnegative().optional(),
|
|
51
|
+
/** null only for platform-global entities (region, part_family) */
|
|
52
|
+
tenantId: zod_1.z.string().nullable(),
|
|
53
|
+
/** acting user, when the change was user-initiated */
|
|
54
|
+
userId: zod_1.z.string().optional(),
|
|
55
|
+
/** commands only: the app whose inbox this is routed to */
|
|
56
|
+
recipient: exports.producerSchema.optional(),
|
|
57
|
+
/** commands only: async request/reply correlation */
|
|
58
|
+
correlationId: zod_1.z.string().optional(),
|
|
59
|
+
/** commands only: sender's inbox identifier for the reply */
|
|
60
|
+
replyTo: zod_1.z.string().optional(),
|
|
61
|
+
/** saga correlation — auto-propagated by the SDK across a choreographed chain */
|
|
62
|
+
sagaId: zod_1.z.string().optional(),
|
|
63
|
+
/** eventId of the message whose handling caused this one */
|
|
64
|
+
causationId: zod_1.z.string().optional(),
|
|
65
|
+
/** marks a saga compensating ("undo") event */
|
|
66
|
+
compensation: zod_1.z.boolean().optional(),
|
|
67
|
+
/** stamped by the replay runbook's transformer; non-idempotent handlers drop these */
|
|
68
|
+
replay: zod_1.z.boolean().optional(),
|
|
69
|
+
/** distributed-tracing id, producer → bus → queue → consumer → browser */
|
|
70
|
+
traceId: zod_1.z.string().min(1),
|
|
71
|
+
});
|
|
72
|
+
/** Envelope with an unvalidated payload — use `envelopeFor(schema)` when the payload shape is known. */
|
|
73
|
+
exports.envelopeSchema = envelopeBase.extend({ payload: zod_1.z.unknown() });
|
|
74
|
+
/** Envelope schema with a typed, validated payload. */
|
|
75
|
+
function envelopeFor(payload) {
|
|
76
|
+
return envelopeBase.extend({ payload });
|
|
77
|
+
}
|