@sguild/dispatcher 2.0.0 → 2.0.1

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.
Files changed (96) hide show
  1. package/README.md +4 -1
  2. package/contracts/README.md +30 -0
  3. package/contracts/coach-availability/README.md +355 -0
  4. package/contracts/coach-availability/README.v2.md +263 -0
  5. package/contracts/coach-availability/schema/payloads/coach.assigned-v1.json +91 -0
  6. package/contracts/coach-availability/validation/delivery-assignment.md +89 -0
  7. package/contracts/coach-availability/validation/sales-offer-construction.md +76 -0
  8. package/contracts/coaching-confirmation/README.md +96 -0
  9. package/contracts/coaching-confirmation/schema/payloads/coaching.lesson.confirmation_decided-v1.json +142 -0
  10. package/contracts/coaching-confirmation/schema/payloads/lead.coach.confirmation.requested-v1.json +124 -0
  11. package/contracts/credit-reservation-funding-state/README.md +147 -0
  12. package/contracts/credit-reservation-lock/README.md +433 -0
  13. package/contracts/credit-reservation-lock/delivery-state-vocabulary.md +73 -0
  14. package/contracts/credit-reservation-lock/reservation-create-api.md +191 -0
  15. package/contracts/credit-reservation-lock/reservation-release-api.md +171 -0
  16. package/contracts/credit-reservation-lock/schema/payloads/credit.locked-v1.json +1 -1
  17. package/contracts/credit-reservation-lock/schema/payloads/credit.reserved-v1.json +2 -3
  18. package/contracts/credit-reservation-lock/validation/lesson-lifecycle.md +318 -0
  19. package/contracts/event-envelope/README.md +205 -0
  20. package/contracts/event-envelope/schema/envelope-v1.json +2 -2
  21. package/contracts/event-envelope/validation/event-vocabulary.md +270 -0
  22. package/contracts/event-types-registry.json +337 -24
  23. package/contracts/external-actions/README.md +338 -0
  24. package/contracts/finance-mart/README.md +238 -0
  25. package/contracts/finance-mart/cac-payback.md +113 -0
  26. package/contracts/finance-mart/cash-position.md +98 -0
  27. package/contracts/finance-mart/cohort-summary.md +72 -0
  28. package/contracts/finance-mart/customer-journey-audit.md +92 -0
  29. package/contracts/finance-mart/ltv.md +92 -0
  30. package/contracts/finance-mart/margin.md +87 -0
  31. package/contracts/finance-mart/pnl.md +83 -0
  32. package/contracts/finance-mart/reconciliation.md +98 -0
  33. package/contracts/finance-mart/revenue-recognition-rollup.md +87 -0
  34. package/contracts/finance-mart/unit-economics.md +94 -0
  35. package/contracts/growth-warehouse-api/README.md +162 -0
  36. package/contracts/identity/README.md +184 -0
  37. package/contracts/identity/person-canonical-fields.md +120 -0
  38. package/contracts/identity/person-externals.md +267 -0
  39. package/contracts/identity/person-resolution-semantics.md +144 -0
  40. package/contracts/identity/person-role-taxonomy.md +120 -0
  41. package/contracts/identity/schema/payloads/intake.captured-v2.json +60 -0
  42. package/contracts/identity/schema/payloads/intake.matched-v2.json +123 -0
  43. package/contracts/identity/schema/payloads/person.updated-v1.json +8 -2
  44. package/contracts/identity/schema/payloads/role.assigned-v1.json +50 -0
  45. package/contracts/identity/schema/payloads/role.retired-v1.json +54 -0
  46. package/contracts/identity/validation/client-table.md +131 -0
  47. package/contracts/identity/validation/coach-handling.md +100 -0
  48. package/contracts/identity/validation/person-graph.md +140 -0
  49. package/contracts/lead-lifecycle/README.md +187 -0
  50. package/contracts/lead-lifecycle/schema/payloads/lead.handoff.context.recorded-v1.json +108 -0
  51. package/contracts/lead-lifecycle/schema/payloads/lead.qualified-v1.json +54 -0
  52. package/contracts/lead-lifecycle/schema/payloads/sales.lead.onboarded-v1.json +120 -0
  53. package/contracts/lesson-lifecycle/README.md +118 -0
  54. package/contracts/lesson-lifecycle/schema/payloads/lesson.cancelled-v1.json +30 -0
  55. package/contracts/lesson-lifecycle/schema/payloads/lesson.delivered-v1.json +29 -0
  56. package/contracts/lesson-lifecycle/schema/payloads/lesson.rescheduled-v1.json +157 -0
  57. package/contracts/lesson-lifecycle/schema/payloads/lesson.scheduled-v1.json +107 -0
  58. package/contracts/lesson-lifecycle/validation/README.md +5 -0
  59. package/contracts/mart-consumer-api/README.md +108 -0
  60. package/contracts/order-flow/README.md +106 -0
  61. package/contracts/order-flow/schema/payloads/order.created-v1.json +58 -0
  62. package/contracts/order-flow/schema/payloads/order.updated-v1.json +63 -0
  63. package/contracts/payment-flow/README.md +157 -0
  64. package/contracts/platform-comms/README.md +84 -0
  65. package/contracts/platform-comms/schema/payloads/platform.comms.inbound-v1.json +83 -0
  66. package/contracts/platform-geography-snapshot/README.md +205 -0
  67. package/contracts/platform-geography-snapshot/schema/payloads/geography.market.archived-v1.json +36 -0
  68. package/contracts/platform-geography-snapshot/schema/payloads/geography.market.upserted-v1.json +59 -0
  69. package/contracts/platform-geography-snapshot/schema/payloads/geography.service-area.archived-v1.json +36 -0
  70. package/contracts/platform-geography-snapshot/schema/payloads/geography.service-area.upserted-v1.json +65 -0
  71. package/contracts/portfolio-mart/README.md +133 -0
  72. package/contracts/portfolio-mart/cohort-funnel-panel.md +76 -0
  73. package/contracts/portfolio-mart/cross-discipline-performance.md +91 -0
  74. package/contracts/portfolio-mart/cross-market-performance.md +84 -0
  75. package/contracts/portfolio-mart/health-composites.md +88 -0
  76. package/contracts/portfolio-mart/org-topology.md +70 -0
  77. package/contracts/portfolio-mart/portfolio-level-funnel-health.md +92 -0
  78. package/contracts/portfolio-mart/validation/consumer-isolation.md +33 -0
  79. package/contracts/portfolio-mart/validation/decoupling-discipline.md +34 -0
  80. package/contracts/refund-flow/README.md +136 -0
  81. package/contracts/refund-flow/sales-callable-refund-initiation-api.md +218 -0
  82. package/contracts/sales-scheduling-surface/README.md +532 -0
  83. package/contracts/sales-scheduling-surface/schema/payloads/delivery.lesson-hold.cancelled-v1.json +42 -0
  84. package/contracts/sales-scheduling-surface/schema/payloads/delivery.lesson-hold.created-v1.json +115 -0
  85. package/contracts/sales-scheduling-surface/validation/composite-hold-create.md +97 -0
  86. package/contracts/sales-scheduling-surface/validation/lock-state-machine-conformance.md +84 -0
  87. package/contracts/sales-scheduling-surface/validation/sales-close-orchestration.md +77 -0
  88. package/contracts/warehouse-silver/README.md +118 -0
  89. package/contracts/warehouse-silver/coaching-utilization-columns.md +105 -0
  90. package/dist/events.d.ts +63 -0
  91. package/dist/events.js +293 -0
  92. package/dist/index.d.ts +2 -0
  93. package/dist/index.js +7 -1
  94. package/dist/postgres-consumer.js +2 -1
  95. package/dist/validator.js +1 -0
  96. package/package.json +1 -1
@@ -0,0 +1,63 @@
1
+ import type { Prisma } from "@prisma/client";
2
+ export type DispatcherEventWindow = "30m" | "6h" | "24h" | "7d";
3
+ export type DispatcherDeliveryState = "published" | "delivered" | "resolved" | "dead-lettered";
4
+ export type DispatcherEventCursor = {
5
+ inserted_at: string;
6
+ seq: string;
7
+ producer: string;
8
+ };
9
+ export type DispatcherEventSummary = {
10
+ producer: string;
11
+ event_id: string;
12
+ seq: string;
13
+ event_type: string;
14
+ schema_version: number;
15
+ tenant_id: string;
16
+ subject: string | null;
17
+ actor: string;
18
+ occurred_at: string;
19
+ inserted_at: string;
20
+ correlation_id: string | null;
21
+ causation_id: string | null;
22
+ delivery_state: DispatcherDeliveryState;
23
+ dedup_consumers: string[];
24
+ active_dead_letter_count: number;
25
+ resolved_dead_letter_count: number;
26
+ };
27
+ export type DispatcherDeadLetterEvidence = {
28
+ dead_letter_id: string;
29
+ consumer: string;
30
+ attempt_count: number;
31
+ last_error: string;
32
+ created_at: string;
33
+ resolved_at: string | null;
34
+ };
35
+ export type DispatcherEventDetail = DispatcherEventSummary & {
36
+ payload: Prisma.JsonValue;
37
+ metadata: Prisma.JsonValue | null;
38
+ dead_letters: DispatcherDeadLetterEvidence[];
39
+ };
40
+ export type ListDispatcherEventsFilter = {
41
+ window?: DispatcherEventWindow;
42
+ producer?: string;
43
+ eventType?: string;
44
+ tenantId?: string;
45
+ subject?: string;
46
+ q?: string;
47
+ limit?: number;
48
+ cursor?: string;
49
+ now?: Date;
50
+ };
51
+ export type ListDispatcherEventsResult = {
52
+ events: DispatcherEventSummary[];
53
+ next_cursor: string | null;
54
+ has_more: boolean;
55
+ };
56
+ export type GetDispatcherEventOptions = {
57
+ producer?: string;
58
+ };
59
+ export declare const DISPATCHER_EVENT_WINDOWS: DispatcherEventWindow[];
60
+ export declare function knownEventTypes(): string[];
61
+ export declare function parseDispatcherEventWindow(value: string | null | undefined): DispatcherEventWindow;
62
+ export declare function listDispatcherEvents(filter?: ListDispatcherEventsFilter): Promise<ListDispatcherEventsResult>;
63
+ export declare function getDispatcherEvent(eventId: string, options?: GetDispatcherEventOptions): Promise<DispatcherEventDetail | null>;
package/dist/events.js ADDED
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DISPATCHER_EVENT_WINDOWS = void 0;
4
+ exports.knownEventTypes = knownEventTypes;
5
+ exports.parseDispatcherEventWindow = parseDispatcherEventWindow;
6
+ exports.listDispatcherEvents = listDispatcherEvents;
7
+ exports.getDispatcherEvent = getDispatcherEvent;
8
+ const producer_db_1 = require("./producer-db");
9
+ const registry_1 = require("./registry");
10
+ const dlq_1 = require("./dlq");
11
+ exports.DISPATCHER_EVENT_WINDOWS = [
12
+ "30m",
13
+ "6h",
14
+ "24h",
15
+ "7d",
16
+ ];
17
+ const WINDOW_MS = {
18
+ "30m": 30 * 60 * 1000,
19
+ "6h": 6 * 60 * 60 * 1000,
20
+ "24h": 24 * 60 * 60 * 1000,
21
+ "7d": 7 * 24 * 60 * 60 * 1000,
22
+ };
23
+ const DEFAULT_LIST_LIMIT = 100;
24
+ const MAX_LIST_LIMIT = 500;
25
+ function knownEventTypes() {
26
+ return Object.keys((0, registry_1.loadRegistry)().events).sort();
27
+ }
28
+ function parseDispatcherEventWindow(value) {
29
+ if (!value)
30
+ return "30m";
31
+ if (exports.DISPATCHER_EVENT_WINDOWS.includes(value)) {
32
+ return value;
33
+ }
34
+ throw new Error(`Unsupported dispatcher event window '${value}'`);
35
+ }
36
+ async function listDispatcherEvents(filter = {}) {
37
+ const window = filter.window ?? "30m";
38
+ const now = filter.now ?? new Date();
39
+ const since = new Date(now.getTime() - WINDOW_MS[window]);
40
+ const limit = normalizeLimit(filter.limit);
41
+ const producers = filter.producer ? [filter.producer] : knownEventProducers();
42
+ const aggregateRead = !filter.producer;
43
+ const cursor = decodeCursor(filter.cursor);
44
+ const merged = [];
45
+ for (const producer of producers) {
46
+ const db = (0, producer_db_1.getProducerDb)(producer);
47
+ if (!db) {
48
+ if (filter.producer)
49
+ throw new dlq_1.ProducerDbNotConfiguredError(producer);
50
+ continue;
51
+ }
52
+ const tablePrefix = db.schema ? `"${db.schema}".` : "";
53
+ const whereClauses = ["inserted_at >= $1"];
54
+ const params = [since];
55
+ if (filter.eventType?.trim()) {
56
+ params.push(filter.eventType.trim());
57
+ whereClauses.push(`event_type = $${params.length}`);
58
+ }
59
+ if (filter.tenantId?.trim()) {
60
+ params.push(filter.tenantId.trim());
61
+ whereClauses.push(`tenant_id = $${params.length}`);
62
+ }
63
+ if (filter.subject?.trim()) {
64
+ params.push(filter.subject.trim());
65
+ whereClauses.push(`subject = $${params.length}`);
66
+ }
67
+ if (filter.q?.trim()) {
68
+ params.push(`%${filter.q.trim()}%`);
69
+ const qParam = `$${params.length}`;
70
+ whereClauses.push(`(id ILIKE ${qParam} OR event_type ILIKE ${qParam} OR producer ILIKE ${qParam} OR tenant_id ILIKE ${qParam} OR subject ILIKE ${qParam} OR actor ILIKE ${qParam} OR correlation_id ILIKE ${qParam} OR causation_id ILIKE ${qParam})`);
71
+ }
72
+ if (cursor) {
73
+ params.push(cursor.inserted_at, cursor.seq, producer, cursor.producer);
74
+ const insertedParam = `$${params.length - 3}`;
75
+ const seqParam = `$${params.length - 2}`;
76
+ const producerParam = `$${params.length - 1}`;
77
+ const cursorProducerParam = `$${params.length}`;
78
+ whereClauses.push(`(inserted_at < ${insertedParam} OR (inserted_at = ${insertedParam} AND (seq < ${seqParam}::bigint OR (seq = ${seqParam}::bigint AND ${producerParam} > ${cursorProducerParam}))))`);
79
+ }
80
+ params.push(limit + 1);
81
+ const sql = `SELECT id, seq, event_type, schema_version, tenant_id,
82
+ producer, subject, actor, occurred_at, inserted_at,
83
+ correlation_id, causation_id
84
+ FROM ${tablePrefix}dispatcher_event
85
+ WHERE ${whereClauses.join(" AND ")}
86
+ ORDER BY inserted_at DESC, seq DESC
87
+ LIMIT $${params.length}`;
88
+ let rows;
89
+ try {
90
+ const result = await db.pool.query(sql, params);
91
+ rows = result.rows;
92
+ }
93
+ catch (e) {
94
+ if (!aggregateRead)
95
+ throw e;
96
+ console.warn(`[dispatcher.events] producer ${producer} excluded from aggregate read: ${e instanceof Error ? e.message : String(e)}`);
97
+ continue;
98
+ }
99
+ const evidence = await deliveryEvidenceForEvents(producer, tablePrefix, rows.map((row) => String(row.id)));
100
+ for (const row of rows) {
101
+ merged.push(toEventSummary(producer, row, evidence.get(String(row.id))));
102
+ }
103
+ }
104
+ merged.sort(compareEventsDesc);
105
+ const page = merged.slice(0, limit);
106
+ const hasMore = merged.length > limit;
107
+ const last = page[page.length - 1];
108
+ return {
109
+ events: page,
110
+ next_cursor: hasMore && last ? encodeCursor(last) : null,
111
+ has_more: hasMore,
112
+ };
113
+ }
114
+ async function getDispatcherEvent(eventId, options = {}) {
115
+ if (!eventId)
116
+ return null;
117
+ const producers = options.producer ? [options.producer] : knownEventProducers();
118
+ const aggregateRead = !options.producer;
119
+ for (const producer of producers) {
120
+ const db = (0, producer_db_1.getProducerDb)(producer);
121
+ if (!db) {
122
+ if (options.producer)
123
+ throw new dlq_1.ProducerDbNotConfiguredError(producer);
124
+ continue;
125
+ }
126
+ const tablePrefix = db.schema ? `"${db.schema}".` : "";
127
+ const sql = `SELECT id, seq, event_type, schema_version, tenant_id,
128
+ producer, subject, actor, occurred_at, inserted_at,
129
+ payload, metadata, correlation_id, causation_id
130
+ FROM ${tablePrefix}dispatcher_event
131
+ WHERE id = $1
132
+ LIMIT 1`;
133
+ let rows;
134
+ try {
135
+ const result = await db.pool.query(sql, [eventId]);
136
+ rows = result.rows;
137
+ }
138
+ catch (e) {
139
+ if (!aggregateRead)
140
+ throw e;
141
+ console.warn(`[dispatcher.events] producer ${producer} excluded from aggregate detail read: ${e instanceof Error ? e.message : String(e)}`);
142
+ continue;
143
+ }
144
+ const row = rows[0];
145
+ if (!row)
146
+ continue;
147
+ const evidence = await deliveryEvidenceForEvents(producer, tablePrefix, [eventId]);
148
+ const summary = toEventSummary(producer, row, evidence.get(eventId));
149
+ const deadLetters = await deadLettersForEvent(producer, tablePrefix, eventId);
150
+ return {
151
+ ...summary,
152
+ payload: row.payload ?? null,
153
+ metadata: row.metadata ?? null,
154
+ dead_letters: deadLetters,
155
+ };
156
+ }
157
+ return null;
158
+ }
159
+ function knownEventProducers() {
160
+ const manifest = (0, registry_1.loadRegistry)();
161
+ const set = new Set();
162
+ for (const entry of Object.values(manifest.events)) {
163
+ set.add(entry.producer);
164
+ }
165
+ return Array.from(set);
166
+ }
167
+ async function deliveryEvidenceForEvents(producer, tablePrefix, eventIds) {
168
+ const map = new Map();
169
+ if (eventIds.length === 0)
170
+ return map;
171
+ const db = (0, producer_db_1.getProducerDb)(producer);
172
+ if (!db)
173
+ return map;
174
+ const dedupResult = await db.pool.query(`SELECT event_id, array_agg(consumer ORDER BY consumer) AS consumers
175
+ FROM ${tablePrefix}dispatcher_dedup
176
+ WHERE event_id = ANY($1::text[])
177
+ GROUP BY event_id`, [eventIds]);
178
+ for (const row of dedupResult.rows) {
179
+ map.set(row.event_id, {
180
+ dedup_consumers: row.consumers ?? [],
181
+ active_dead_letter_count: 0,
182
+ resolved_dead_letter_count: 0,
183
+ });
184
+ }
185
+ const dlqResult = await db.pool.query(`SELECT event_id,
186
+ COUNT(*) FILTER (WHERE resolved_at IS NULL) AS active_count,
187
+ COUNT(*) FILTER (WHERE resolved_at IS NOT NULL) AS resolved_count
188
+ FROM ${tablePrefix}dispatcher_dead_letter
189
+ WHERE event_id = ANY($1::text[])
190
+ GROUP BY event_id`, [eventIds]);
191
+ for (const row of dlqResult.rows) {
192
+ const current = map.get(row.event_id) ?? {
193
+ dedup_consumers: [],
194
+ active_dead_letter_count: 0,
195
+ resolved_dead_letter_count: 0,
196
+ };
197
+ current.active_dead_letter_count = Number(row.active_count);
198
+ current.resolved_dead_letter_count = Number(row.resolved_count);
199
+ map.set(row.event_id, current);
200
+ }
201
+ return map;
202
+ }
203
+ async function deadLettersForEvent(producer, tablePrefix, eventId) {
204
+ const db = (0, producer_db_1.getProducerDb)(producer);
205
+ if (!db)
206
+ return [];
207
+ const result = await db.pool.query(`SELECT id, consumer, attempt_count, last_error, created_at, resolved_at
208
+ FROM ${tablePrefix}dispatcher_dead_letter
209
+ WHERE event_id = $1
210
+ ORDER BY created_at DESC`, [eventId]);
211
+ return result.rows.map((row) => ({
212
+ dead_letter_id: row.id,
213
+ consumer: row.consumer,
214
+ attempt_count: Number(row.attempt_count),
215
+ last_error: row.last_error,
216
+ created_at: toIso(row.created_at),
217
+ resolved_at: row.resolved_at ? toIso(row.resolved_at) : null,
218
+ }));
219
+ }
220
+ function toEventSummary(fallbackProducer, row, evidence) {
221
+ const dedupConsumers = evidence?.dedup_consumers ?? [];
222
+ const activeDeadLetterCount = evidence?.active_dead_letter_count ?? 0;
223
+ const resolvedDeadLetterCount = evidence?.resolved_dead_letter_count ?? 0;
224
+ return {
225
+ producer: row.producer || fallbackProducer,
226
+ event_id: row.id,
227
+ seq: String(row.seq),
228
+ event_type: row.event_type,
229
+ schema_version: Number(row.schema_version),
230
+ tenant_id: row.tenant_id,
231
+ subject: row.subject ?? null,
232
+ actor: row.actor,
233
+ occurred_at: toIso(row.occurred_at),
234
+ inserted_at: toIso(row.inserted_at),
235
+ correlation_id: row.correlation_id ?? null,
236
+ causation_id: row.causation_id ?? null,
237
+ delivery_state: deliveryState(dedupConsumers, activeDeadLetterCount, resolvedDeadLetterCount),
238
+ dedup_consumers: dedupConsumers,
239
+ active_dead_letter_count: activeDeadLetterCount,
240
+ resolved_dead_letter_count: resolvedDeadLetterCount,
241
+ };
242
+ }
243
+ function deliveryState(dedupConsumers, activeDeadLetterCount, resolvedDeadLetterCount) {
244
+ if (activeDeadLetterCount > 0)
245
+ return "dead-lettered";
246
+ if (resolvedDeadLetterCount > 0)
247
+ return "resolved";
248
+ if (dedupConsumers.length > 0)
249
+ return "delivered";
250
+ return "published";
251
+ }
252
+ function compareEventsDesc(a, b) {
253
+ const inserted = b.inserted_at.localeCompare(a.inserted_at);
254
+ if (inserted !== 0)
255
+ return inserted;
256
+ const seq = BigInt(b.seq) > BigInt(a.seq) ? 1 : BigInt(b.seq) < BigInt(a.seq) ? -1 : 0;
257
+ if (seq !== 0)
258
+ return seq;
259
+ return a.producer.localeCompare(b.producer);
260
+ }
261
+ function normalizeLimit(limit) {
262
+ if (limit === undefined)
263
+ return DEFAULT_LIST_LIMIT;
264
+ return Math.min(limit, MAX_LIST_LIMIT);
265
+ }
266
+ function encodeCursor(event) {
267
+ const cursor = {
268
+ inserted_at: event.inserted_at,
269
+ seq: event.seq,
270
+ producer: event.producer,
271
+ };
272
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
273
+ }
274
+ function decodeCursor(value) {
275
+ if (!value)
276
+ return null;
277
+ try {
278
+ const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
279
+ if (!parsed.inserted_at || !parsed.seq || !parsed.producer)
280
+ return null;
281
+ return {
282
+ inserted_at: parsed.inserted_at,
283
+ seq: parsed.seq,
284
+ producer: parsed.producer,
285
+ };
286
+ }
287
+ catch {
288
+ return null;
289
+ }
290
+ }
291
+ function toIso(value) {
292
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
293
+ }
package/dist/index.d.ts CHANGED
@@ -25,6 +25,8 @@ export type { ConsumerLoopOptions } from "./postgres-consumer";
25
25
  export { EnvelopeValidationError, PayloadValidationError, PayloadSchemaUnavailableError, } from "./validator";
26
26
  export { DeadLetterAlreadyResolvedError, DeadLetterNotFoundError, ProducerDbNotConfiguredError, ReplayConsumerUnknownError, ReplayDeliveryFailedError, getDeadLetter, knownProducers, listDeadLetters, replayDeadLetter, resolveDeadLetter, } from "./dlq";
27
27
  export type { DeadLetter, GetDeadLetterOptions, ListDeadLettersFilter, ReplayDeadLetterInput, ReplayDeadLetterOutcome, ResolveDeadLetterInput, } from "./dlq";
28
+ export { DISPATCHER_EVENT_WINDOWS, getDispatcherEvent, knownEventTypes, listDispatcherEvents, parseDispatcherEventWindow, } from "./events";
29
+ export type { DispatcherDeadLetterEvidence, DispatcherDeliveryState, DispatcherEventDetail, DispatcherEventSummary, DispatcherEventWindow, GetDispatcherEventOptions, ListDispatcherEventsFilter, ListDispatcherEventsResult, } from "./events";
28
30
  export { configureDispatcherObservability, resetDispatcherObservabilityForTests, } from "./observability";
29
31
  export type { DispatcherHistogramName, DispatcherMetricLabels, DispatcherMetricName, DispatcherObservabilityHooks, } from "./observability";
30
32
  export type { ActorRef, CanonicalEntityId, EventEmit, EventEnvelope, LiveProducerDomain, ProducerDomain, PublishResult, SubscribeHandler, } from "./types";
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@
19
19
  * (Slice 3b) and the per-consumer DLQ read API (Slice 5) remain.
20
20
  */
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.WebhookDispatchError = exports.verifyEnvelopeSignature = exports.signEnvelope = exports.SIGNATURE_HEADER = exports.EVENT_TYPE_HEADER = exports.EVENT_ID_HEADER = exports.verifyInboxRequest = exports.tryInsertOrDetectConflict = exports.loadRegistry = exports.getEventTypeRegistration = exports.getActiveSchemaVersion = exports.resetDispatcherObservabilityForTests = exports.configureDispatcherObservability = exports.resolveDeadLetter = exports.replayDeadLetter = exports.listDeadLetters = exports.knownProducers = exports.getDeadLetter = exports.ReplayDeliveryFailedError = exports.ReplayConsumerUnknownError = exports.ProducerDbNotConfiguredError = exports.DeadLetterNotFoundError = exports.DeadLetterAlreadyResolvedError = exports.PayloadSchemaUnavailableError = exports.PayloadValidationError = exports.EnvelopeValidationError = exports.configureDispatcher = exports.UnregisteredSchemaVersionError = exports.UnregisteredEventTypeError = exports.DispatcherNotImplementedError = exports.dispatcher = void 0;
22
+ exports.WebhookDispatchError = exports.verifyEnvelopeSignature = exports.signEnvelope = exports.SIGNATURE_HEADER = exports.EVENT_TYPE_HEADER = exports.EVENT_ID_HEADER = exports.verifyInboxRequest = exports.tryInsertOrDetectConflict = exports.loadRegistry = exports.getEventTypeRegistration = exports.getActiveSchemaVersion = exports.resetDispatcherObservabilityForTests = exports.configureDispatcherObservability = exports.parseDispatcherEventWindow = exports.listDispatcherEvents = exports.knownEventTypes = exports.getDispatcherEvent = exports.DISPATCHER_EVENT_WINDOWS = exports.resolveDeadLetter = exports.replayDeadLetter = exports.listDeadLetters = exports.knownProducers = exports.getDeadLetter = exports.ReplayDeliveryFailedError = exports.ReplayConsumerUnknownError = exports.ProducerDbNotConfiguredError = exports.DeadLetterNotFoundError = exports.DeadLetterAlreadyResolvedError = exports.PayloadSchemaUnavailableError = exports.PayloadValidationError = exports.EnvelopeValidationError = exports.configureDispatcher = exports.UnregisteredSchemaVersionError = exports.UnregisteredEventTypeError = exports.DispatcherNotImplementedError = exports.dispatcher = void 0;
23
23
  var dispatcher_1 = require("./dispatcher");
24
24
  Object.defineProperty(exports, "dispatcher", { enumerable: true, get: function () { return dispatcher_1.dispatcher; } });
25
25
  var dispatcher_2 = require("./dispatcher");
@@ -43,6 +43,12 @@ Object.defineProperty(exports, "knownProducers", { enumerable: true, get: functi
43
43
  Object.defineProperty(exports, "listDeadLetters", { enumerable: true, get: function () { return dlq_1.listDeadLetters; } });
44
44
  Object.defineProperty(exports, "replayDeadLetter", { enumerable: true, get: function () { return dlq_1.replayDeadLetter; } });
45
45
  Object.defineProperty(exports, "resolveDeadLetter", { enumerable: true, get: function () { return dlq_1.resolveDeadLetter; } });
46
+ var events_1 = require("./events");
47
+ Object.defineProperty(exports, "DISPATCHER_EVENT_WINDOWS", { enumerable: true, get: function () { return events_1.DISPATCHER_EVENT_WINDOWS; } });
48
+ Object.defineProperty(exports, "getDispatcherEvent", { enumerable: true, get: function () { return events_1.getDispatcherEvent; } });
49
+ Object.defineProperty(exports, "knownEventTypes", { enumerable: true, get: function () { return events_1.knownEventTypes; } });
50
+ Object.defineProperty(exports, "listDispatcherEvents", { enumerable: true, get: function () { return events_1.listDispatcherEvents; } });
51
+ Object.defineProperty(exports, "parseDispatcherEventWindow", { enumerable: true, get: function () { return events_1.parseDispatcherEventWindow; } });
46
52
  var observability_1 = require("./observability");
47
53
  Object.defineProperty(exports, "configureDispatcherObservability", { enumerable: true, get: function () { return observability_1.configureDispatcherObservability; } });
48
54
  Object.defineProperty(exports, "resetDispatcherObservabilityForTests", { enumerable: true, get: function () { return observability_1.resetDispatcherObservabilityForTests; } });
@@ -257,7 +257,8 @@ class ConsumerLoop {
257
257
  try {
258
258
  await client.query("BEGIN");
259
259
  await client.query(`INSERT INTO ${this.tablePrefix}dispatcher_dedup (consumer, event_id)
260
- VALUES ($1, $2)`, [this.options.consumer, eventId]);
260
+ VALUES ($1, $2)
261
+ ON CONFLICT (consumer, event_id) DO NOTHING`, [this.options.consumer, eventId]);
261
262
  await client.query(`INSERT INTO ${this.tablePrefix}dispatcher_cursor (consumer, event_type, last_seq, updated_at)
262
263
  VALUES ($1, $2, $3, CURRENT_TIMESTAMP)
263
264
  ON CONFLICT (consumer, event_type)
package/dist/validator.js CHANGED
@@ -132,6 +132,7 @@ function validatePayload(eventType, schemaVersion, payload) {
132
132
  function __resetValidatorCachesForTests() {
133
133
  cachedEnvelopeValidator = null;
134
134
  payloadValidatorCache.clear();
135
+ ajv.removeSchema();
135
136
  }
136
137
  // =============================================================================
137
138
  // Internal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sguild/dispatcher",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Cross-domain event dispatcher SDK for Sguild domains, per the Sguild Event Envelope contract and ADR-0009. Producer-side transactional emit, consumer-side polling worker with dedup/retry/dead-letter, envelope and payload validation against the bundled contracts tree.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {