@hexaijs/core 0.9.0 → 0.9.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.
- package/dist/domain/aggregate-root.d.ts +12 -0
- package/dist/domain/aggregate-root.d.ts.map +1 -0
- package/dist/domain/aggregate-root.js +22 -0
- package/dist/domain/aggregate-root.js.map +1 -0
- package/dist/domain/domain-error.d.ts +11 -0
- package/dist/domain/domain-error.d.ts.map +1 -0
- package/dist/domain/domain-error.js +19 -0
- package/dist/domain/domain-error.js.map +1 -0
- package/dist/domain/domain-event.d.ts +5 -0
- package/dist/domain/domain-event.d.ts.map +1 -0
- package/dist/domain/domain-event.js +7 -0
- package/dist/domain/domain-event.js.map +1 -0
- package/dist/domain/identifiable.d.ts +11 -0
- package/dist/domain/identifiable.d.ts.map +1 -0
- package/dist/domain/identifiable.js +14 -0
- package/dist/domain/identifiable.js.map +1 -0
- package/dist/domain/index.d.ts +6 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +6 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/repository.d.ts +16 -0
- package/dist/domain/repository.d.ts.map +1 -0
- package/dist/domain/repository.js +19 -0
- package/dist/domain/repository.js.map +1 -0
- package/dist/event-store.d.ts +13 -0
- package/dist/event-store.d.ts.map +1 -0
- package/dist/event-store.js +2 -0
- package/dist/event-store.js.map +1 -0
- package/dist/index.d.ts +6 -86
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -280
- package/dist/index.js.map +1 -1
- package/dist/{event-store-UbTJSE4H.d.ts → message.d.ts} +9 -21
- package/dist/message.d.ts.map +1 -0
- package/dist/message.js +144 -0
- package/dist/message.js.map +1 -0
- package/dist/test/dummy-message.d.ts +8 -0
- package/dist/test/dummy-message.d.ts.map +1 -0
- package/dist/test/dummy-message.js +15 -0
- package/dist/test/dummy-message.js.map +1 -0
- package/dist/test/in-memory-event-store.d.ts +11 -0
- package/dist/test/in-memory-event-store.d.ts.map +1 -0
- package/dist/test/in-memory-event-store.js +34 -0
- package/dist/test/in-memory-event-store.js.map +1 -0
- package/dist/test/index.d.ts +6 -36
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +5 -326
- package/dist/test/index.js.map +1 -1
- package/dist/test/matchers.d.ts +5 -0
- package/dist/test/matchers.d.ts.map +1 -0
- package/dist/test/matchers.js +69 -0
- package/dist/test/matchers.js.map +1 -0
- package/dist/test/partial-match.d.ts +4 -0
- package/dist/test/partial-match.d.ts.map +1 -0
- package/dist/test/partial-match.js +26 -0
- package/dist/test/partial-match.js.map +1 -0
- package/dist/test/utils.d.ts +5 -0
- package/dist/test/utils.d.ts.map +1 -0
- package/dist/test/utils.js +18 -0
- package/dist/test/utils.js.map +1 -0
- package/dist/transaction-hooks.d.ts +13 -0
- package/dist/transaction-hooks.d.ts.map +1 -0
- package/dist/transaction-hooks.js +47 -0
- package/dist/transaction-hooks.js.map +1 -0
- package/dist/unit-of-work.d.ts +19 -0
- package/dist/unit-of-work.d.ts.map +1 -0
- package/dist/unit-of-work.js +7 -0
- package/dist/unit-of-work.js.map +1 -0
- package/package.json +3 -2
package/dist/test/index.js
CHANGED
|
@@ -1,327 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
// src/test/matchers.ts
|
|
12
|
-
var matchers_exports = {};
|
|
13
|
-
__export(matchers_exports, {
|
|
14
|
-
expectMessageToMatch: () => expectMessageToMatch,
|
|
15
|
-
expectMessagesToBeFullyEqual: () => expectMessagesToBeFullyEqual,
|
|
16
|
-
expectMessagesToContain: () => expectMessagesToContain
|
|
17
|
-
});
|
|
18
|
-
var anyString = /* @__PURE__ */ Symbol.for("string");
|
|
19
|
-
var anyDate = /* @__PURE__ */ Symbol.for("date");
|
|
20
|
-
function partialMatch(source, target) {
|
|
21
|
-
for (const key of Object.keys(target)) {
|
|
22
|
-
const shouldRecurse = _.isObject(source[key]) && _.isObject(target[key]);
|
|
23
|
-
if (shouldRecurse) {
|
|
24
|
-
if (!partialMatch(source[key], target[key])) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
} else {
|
|
28
|
-
if (target[key] === anyString && typeof source[key] === "string") {
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (target[key] === anyDate && source[key] instanceof Date) {
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (!_.isEqual(source[key], target[key])) {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// src/test/utils.ts
|
|
43
|
-
async function waitForTicks(number = 10) {
|
|
44
|
-
for (let i = 0; i < number; i++) {
|
|
45
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async function waitForMs(number = 10) {
|
|
49
|
-
await new Promise((resolve) => setTimeout(resolve, number));
|
|
50
|
-
}
|
|
51
|
-
async function waitFor(type = "ticks", number = 10) {
|
|
52
|
-
if (type === "ticks") {
|
|
53
|
-
await waitForTicks(number);
|
|
54
|
-
} else {
|
|
55
|
-
await waitForMs(number);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function expectMessagesToBeFullyEqual(messages, expectedMessages) {
|
|
59
|
-
const actualSummary = formatMessagesSummary(messages);
|
|
60
|
-
const expectedSummary = formatMessagesSummary(expectedMessages);
|
|
61
|
-
expect(actualSummary, "Messages should match").toEqual(expectedSummary);
|
|
62
|
-
for (let i = 0; i < Math.max(messages.length, expectedMessages.length); i++) {
|
|
63
|
-
const actual = messages[i]?.serialize();
|
|
64
|
-
const expected = expectedMessages[i]?.serialize();
|
|
65
|
-
expect(actual, `message[${i}]`).toEqual(expected);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function expectMessagesToContain(messages, expectedMessages) {
|
|
69
|
-
for (const message of expectedMessages) {
|
|
70
|
-
expectMessageToMatch(
|
|
71
|
-
messages,
|
|
72
|
-
message.getMessageType(),
|
|
73
|
-
message.getPayload()
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function expectMessageToMatch(messages, messageType, payload = {}) {
|
|
78
|
-
const resolvedMessageType = typeof messageType === "string" ? messageType : messageType.getType();
|
|
79
|
-
const sameTypeMessages = messages.filter(
|
|
80
|
-
(msg) => msg.getMessageType() === resolvedMessageType
|
|
81
|
-
);
|
|
82
|
-
if (sameTypeMessages.length === 0) {
|
|
83
|
-
const availableTypes = [...new Set(messages.map((m) => m.getMessageType()))];
|
|
84
|
-
expect.fail(
|
|
85
|
-
`Message not found: "${resolvedMessageType}"
|
|
86
|
-
|
|
87
|
-
Available message types:
|
|
88
|
-
` + (availableTypes.length === 0 ? " (none)" : availableTypes.map((t) => ` - ${t}`).join("\n"))
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
const found = sameTypeMessages.find(
|
|
92
|
-
(msg) => partialMatch(msg.getPayload(), payload)
|
|
93
|
-
);
|
|
94
|
-
if (!found) {
|
|
95
|
-
const closestMatch = findClosestMatch(sameTypeMessages, payload);
|
|
96
|
-
expect(
|
|
97
|
-
closestMatch.payload,
|
|
98
|
-
`Found ${sameTypeMessages.length} message(s) of type "${resolvedMessageType}", but payload did not match.
|
|
99
|
-
Showing closest match (${closestMatch.matchedKeys}/${closestMatch.totalKeys} keys matched):`
|
|
100
|
-
).toMatchObject(payload);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function formatMessagesSummary(messages) {
|
|
104
|
-
return messages.map((msg) => ({
|
|
105
|
-
type: msg.getMessageType(),
|
|
106
|
-
payload: msg.getPayload()
|
|
107
|
-
}));
|
|
108
|
-
}
|
|
109
|
-
function findClosestMatch(messages, expectedPayload) {
|
|
110
|
-
const expectedKeys = Object.keys(expectedPayload);
|
|
111
|
-
let bestMatch = {
|
|
112
|
-
payload: messages[0]?.getPayload() ?? {},
|
|
113
|
-
matchedKeys: 0,
|
|
114
|
-
totalKeys: expectedKeys.length
|
|
115
|
-
};
|
|
116
|
-
for (const msg of messages) {
|
|
117
|
-
const actualPayload = msg.getPayload();
|
|
118
|
-
const matchedKeys = countMatchedKeys(actualPayload, expectedPayload);
|
|
119
|
-
if (matchedKeys > bestMatch.matchedKeys) {
|
|
120
|
-
bestMatch = { payload: actualPayload, matchedKeys, totalKeys: expectedKeys.length };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return bestMatch;
|
|
124
|
-
}
|
|
125
|
-
function countMatchedKeys(actual, expected) {
|
|
126
|
-
let matched = 0;
|
|
127
|
-
for (const key of Object.keys(expected)) {
|
|
128
|
-
if (_.isEqual(actual[key], expected[key])) {
|
|
129
|
-
matched++;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return matched;
|
|
133
|
-
}
|
|
134
|
-
var Message = class {
|
|
135
|
-
constructor(payload, options) {
|
|
136
|
-
this.payload = payload;
|
|
137
|
-
this.headers = Object.freeze(
|
|
138
|
-
this.constructor.mergeHeaders(options?.headers ?? {})
|
|
139
|
-
);
|
|
140
|
-
if (payload && typeof payload === "object") {
|
|
141
|
-
Object.freeze(payload);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
headers;
|
|
145
|
-
static getSchemaVersion() {
|
|
146
|
-
return this.schemaVersion ?? void 0;
|
|
147
|
-
}
|
|
148
|
-
static getType() {
|
|
149
|
-
return this.type ?? this.name;
|
|
150
|
-
}
|
|
151
|
-
static getIntent() {
|
|
152
|
-
return this.intent ?? void 0;
|
|
153
|
-
}
|
|
154
|
-
static newHeaders(...excludes) {
|
|
155
|
-
return generateHeaderFor(this, ...excludes);
|
|
156
|
-
}
|
|
157
|
-
static from(rawPayload, headers) {
|
|
158
|
-
const payload = this.deserializeRawPayload(rawPayload);
|
|
159
|
-
return new this(payload, {
|
|
160
|
-
headers: headers ? this.deserializeRawHeaders(headers) : this.newHeaders()
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
static deserializeRawPayload(rawPayload) {
|
|
164
|
-
return rawPayload;
|
|
165
|
-
}
|
|
166
|
-
static deserializeRawHeaders(headers) {
|
|
167
|
-
headers.createdAt = new Date(headers.createdAt);
|
|
168
|
-
return headers;
|
|
169
|
-
}
|
|
170
|
-
static mergeHeaders(headers) {
|
|
171
|
-
return {
|
|
172
|
-
...this.newHeaders(...Object.keys(headers)),
|
|
173
|
-
...headers
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
withHeader(field, value) {
|
|
177
|
-
const newHeaders = { ...this.headers, [field]: value };
|
|
178
|
-
return this.cloneWithHeaders(newHeaders);
|
|
179
|
-
}
|
|
180
|
-
clone() {
|
|
181
|
-
const cloned = Object.create(Object.getPrototypeOf(this));
|
|
182
|
-
Object.assign(cloned, this);
|
|
183
|
-
return cloned;
|
|
184
|
-
}
|
|
185
|
-
cloneWithHeaders(headers) {
|
|
186
|
-
const cloned = this.clone();
|
|
187
|
-
Object.defineProperty(cloned, "headers", {
|
|
188
|
-
value: Object.freeze(headers),
|
|
189
|
-
writable: false,
|
|
190
|
-
configurable: true
|
|
191
|
-
});
|
|
192
|
-
return cloned;
|
|
193
|
-
}
|
|
194
|
-
getHeader(field) {
|
|
195
|
-
return this.headers[field];
|
|
196
|
-
}
|
|
197
|
-
getHeaders() {
|
|
198
|
-
return Object.freeze({ ...this.headers });
|
|
199
|
-
}
|
|
200
|
-
getPayload() {
|
|
201
|
-
return this.payload;
|
|
202
|
-
}
|
|
203
|
-
getMessageId() {
|
|
204
|
-
return this.headers.id;
|
|
205
|
-
}
|
|
206
|
-
getMessageType() {
|
|
207
|
-
return this.headers.type;
|
|
208
|
-
}
|
|
209
|
-
getSchemaVersion() {
|
|
210
|
-
return this.headers.schemaVersion;
|
|
211
|
-
}
|
|
212
|
-
getTimestamp() {
|
|
213
|
-
return this.headers.createdAt;
|
|
214
|
-
}
|
|
215
|
-
getIntent() {
|
|
216
|
-
return this.constructor.getIntent();
|
|
217
|
-
}
|
|
218
|
-
toJSON() {
|
|
219
|
-
return {
|
|
220
|
-
headers: { ...this.headers },
|
|
221
|
-
payload: this.serializePayload(this.payload)
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
serialize() {
|
|
225
|
-
return JSON.parse(JSON.stringify(this.toJSON()));
|
|
226
|
-
}
|
|
227
|
-
serializePayload(payload) {
|
|
228
|
-
return payload;
|
|
229
|
-
}
|
|
230
|
-
asType(cls) {
|
|
231
|
-
const { headers, payload } = this.serialize();
|
|
232
|
-
return cls.from(payload, headers);
|
|
233
|
-
}
|
|
234
|
-
asTrace() {
|
|
235
|
-
return { id: this.getMessageId(), type: this.getMessageType() };
|
|
236
|
-
}
|
|
237
|
-
getCausation() {
|
|
238
|
-
return this.getHeader("causation");
|
|
239
|
-
}
|
|
240
|
-
getCorrelation() {
|
|
241
|
-
return this.getHeader("correlation");
|
|
242
|
-
}
|
|
243
|
-
withCausation(trace) {
|
|
244
|
-
return this.withHeader("causation", trace);
|
|
245
|
-
}
|
|
246
|
-
withCorrelation(trace) {
|
|
247
|
-
return this.withHeader("correlation", trace);
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
function generateHeaderFor(cls, ...excludes) {
|
|
251
|
-
const headers = {};
|
|
252
|
-
if (!excludes.includes("id")) {
|
|
253
|
-
headers.id = v4();
|
|
254
|
-
}
|
|
255
|
-
if (!excludes.includes("type")) {
|
|
256
|
-
headers.type = cls.getType();
|
|
257
|
-
}
|
|
258
|
-
if (!excludes.includes("intent")) {
|
|
259
|
-
const intent = cls.getIntent();
|
|
260
|
-
if (intent !== void 0) {
|
|
261
|
-
headers.intent = intent;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (!excludes.includes("schemaVersion")) {
|
|
265
|
-
const schemaVersion = cls.getSchemaVersion();
|
|
266
|
-
if (schemaVersion !== void 0) {
|
|
267
|
-
headers.schemaVersion = schemaVersion;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
if (!excludes.includes("createdAt")) {
|
|
271
|
-
headers.createdAt = /* @__PURE__ */ new Date();
|
|
272
|
-
}
|
|
273
|
-
return headers;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// src/test/dummy-message.ts
|
|
277
|
-
var DummyMessage = class extends Message {
|
|
278
|
-
static type = "test.dummy-message";
|
|
279
|
-
static create() {
|
|
280
|
-
return new this({});
|
|
281
|
-
}
|
|
282
|
-
static createMany(number) {
|
|
283
|
-
return _.times(number, () => this.create());
|
|
284
|
-
}
|
|
285
|
-
static from(_4, headers) {
|
|
286
|
-
return new this({}, { headers });
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
// src/test/in-memory-event-store.ts
|
|
291
|
-
var InMemoryEventStore = class {
|
|
292
|
-
events = [];
|
|
293
|
-
async store(event) {
|
|
294
|
-
const position = this.events.length + 1;
|
|
295
|
-
const storedEvent = { position, event };
|
|
296
|
-
this.events.push(storedEvent);
|
|
297
|
-
return storedEvent;
|
|
298
|
-
}
|
|
299
|
-
async storeAll(events) {
|
|
300
|
-
const storedEvents = [];
|
|
301
|
-
for (const event of events) {
|
|
302
|
-
storedEvents.push(await this.store(event));
|
|
303
|
-
}
|
|
304
|
-
return storedEvents;
|
|
305
|
-
}
|
|
306
|
-
async fetch(afterPosition, limit) {
|
|
307
|
-
const lastPosition = await this.getLastPosition();
|
|
308
|
-
let filtered = this.events.filter((e) => e.position > afterPosition);
|
|
309
|
-
if (limit !== void 0) {
|
|
310
|
-
filtered = filtered.slice(0, limit);
|
|
311
|
-
}
|
|
312
|
-
return {
|
|
313
|
-
events: filtered,
|
|
314
|
-
lastPosition
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
async getLastPosition() {
|
|
318
|
-
return this.events.length;
|
|
319
|
-
}
|
|
320
|
-
clear() {
|
|
321
|
-
this.events = [];
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
export { DummyMessage, InMemoryEventStore, expectMessageToMatch, expectMessagesToBeFullyEqual, expectMessagesToContain, matchers_exports as matchers, partialMatch, waitFor, waitForMs, waitForTicks };
|
|
326
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export * as matchers from "./matchers.js";
|
|
2
|
+
export * from "./matchers.js";
|
|
3
|
+
export * from "./dummy-message.js";
|
|
4
|
+
export * from "./utils.js";
|
|
5
|
+
export * from "./in-memory-event-store.js";
|
|
327
6
|
//# sourceMappingURL=index.js.map
|
package/dist/test/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/test/matchers.ts","../../src/test/partial-match.ts","../../src/test/utils.ts","../../src/message.ts","../../src/test/dummy-message.ts","../../src/test/in-memory-event-store.ts"],"names":["_","uuid"],"mappings":";;;;;;;;;;;AAAA,IAAA,gBAAA,GAAA;AAAA,QAAA,CAAA,gBAAA,EAAA;AAAA,EAAA,oBAAA,EAAA,MAAA,oBAAA;AAAA,EAAA,4BAAA,EAAA,MAAA,4BAAA;AAAA,EAAA,uBAAA,EAAA,MAAA;AAAA,CAAA,CAAA;ACEO,IAAM,SAAA,mBAAY,MAAA,CAAO,GAAA,CAAI,QAAQ,CAAA;AAErC,IAAM,OAAA,mBAAU,MAAA,CAAO,GAAA,CAAI,MAAM,CAAA;AAEjC,SAAS,YAAA,CACZ,QACA,MAAA,EACO;AACP,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,EAAG;AACnC,IAAA,MAAM,aAAA,GACF,CAAA,CAAE,QAAA,CAAS,MAAA,CAAO,GAAG,CAAC,CAAA,IAAK,CAAA,CAAE,QAAA,CAAS,MAAA,CAAO,GAAG,CAAC,CAAA;AAErD,IAAA,IAAI,aAAA,EAAe;AACf,MAAA,IAAI,CAAC,aAAa,MAAA,CAAO,GAAG,GAAG,MAAA,CAAO,GAAG,CAAC,CAAA,EAAG;AACzC,QAAA,OAAO,KAAA;AAAA,MACX;AAAA,IACJ,CAAA,MAAO;AACH,MAAA,IAAI,MAAA,CAAO,GAAG,CAAA,KAAM,SAAA,IAAa,OAAO,MAAA,CAAO,GAAG,MAAM,QAAA,EAAU;AAC9D,QAAA;AAAA,MACJ;AAEA,MAAA,IAAI,OAAO,GAAG,CAAA,KAAM,WAAW,MAAA,CAAO,GAAG,aAAa,IAAA,EAAM;AACxD,QAAA;AAAA,MACJ;AAEA,MAAA,IAAI,CAAC,EAAE,OAAA,CAAQ,MAAA,CAAO,GAAG,CAAA,EAAG,MAAA,CAAO,GAAG,CAAC,CAAA,EAAG;AACtC,QAAA,OAAO,KAAA;AAAA,MACX;AAAA,IACJ;AAAA,EACJ;AAEA,EAAA,OAAO,IAAA;AACX;;;AChCA,eAAsB,YAAA,CAAa,SAAS,EAAA,EAAmB;AAC3D,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC7B,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,YAAY,UAAA,CAAW,OAAA,EAAS,CAAC,CAAC,CAAA;AAAA,EACzD;AACJ;AAEA,eAAsB,SAAA,CAAU,SAAS,EAAA,EAAmB;AACxD,EAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,YAAY,UAAA,CAAW,OAAA,EAAS,MAAM,CAAC,CAAA;AAC9D;AAEA,eAAsB,OAAA,CAClB,IAAA,GAAuB,OAAA,EACvB,MAAA,GAAS,EAAA,EACI;AACb,EAAA,IAAI,SAAS,OAAA,EAAS;AAClB,IAAA,MAAM,aAAa,MAAM,CAAA;AAAA,EAC7B,CAAA,MAAO;AACH,IAAA,MAAM,UAAU,MAAM,CAAA;AAAA,EAC1B;AACJ;AFfO,SAAS,4BAAA,CACZ,UACA,gBAAA,EACI;AACJ,EAAA,MAAM,aAAA,GAAgB,sBAAsB,QAAQ,CAAA;AACpD,EAAA,MAAM,eAAA,GAAkB,sBAAsB,gBAAgB,CAAA;AAE9D,EAAA,MAAA,CAAO,aAAA,EAAe,uBAAuB,CAAA,CAAE,OAAA,CAAQ,eAAe,CAAA;AAEtE,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,SAAS,MAAA,EAAQ,gBAAA,CAAiB,MAAM,CAAA,EAAG,CAAA,EAAA,EAAK;AACzE,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,CAAC,CAAA,EAAG,SAAA,EAAU;AACtC,IAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,CAAC,CAAA,EAAG,SAAA,EAAU;AAEhD,IAAA,MAAA,CAAO,QAAQ,CAAA,QAAA,EAAW,CAAC,CAAA,CAAA,CAAG,CAAA,CAAE,QAAQ,QAAQ,CAAA;AAAA,EACpD;AACJ;AAEO,SAAS,uBAAA,CACZ,UACA,gBAAA,EACI;AACJ,EAAA,KAAA,MAAW,WAAW,gBAAA,EAAkB;AACpC,IAAA,oBAAA;AAAA,MACI,QAAA;AAAA,MACA,QAAQ,cAAA,EAAe;AAAA,MACvB,QAAQ,UAAA;AAAW,KACvB;AAAA,EACJ;AACJ;AAEO,SAAS,oBAAA,CACZ,QAAA,EACA,WAAA,EACA,OAAA,GAAmC,EAAC,EAChC;AACJ,EAAA,MAAM,sBACF,OAAO,WAAA,KAAgB,QAAA,GAAW,WAAA,GAAc,YAAY,OAAA,EAAQ;AACxE,EAAA,MAAM,mBAAmB,QAAA,CAAS,MAAA;AAAA,IAC9B,CAAC,GAAA,KAAQ,GAAA,CAAI,cAAA,EAAe,KAAM;AAAA,GACtC;AAEA,EAAA,IAAI,gBAAA,CAAiB,WAAW,CAAA,EAAG;AAC/B,IAAA,MAAM,cAAA,GAAiB,CAAC,GAAG,IAAI,GAAA,CAAI,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,cAAA,EAAgB,CAAC,CAAC,CAAA;AAC3E,IAAA,MAAA,CAAO,IAAA;AAAA,MACH,uBAAuB,mBAAmB,CAAA;;AAAA;AAAA,CAAA,IAEzC,cAAA,CAAe,MAAA,KAAW,CAAA,GACrB,UAAA,GACA,cAAA,CAAe,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,IAAA,EAAO,CAAC,CAAA,CAAE,CAAA,CAAE,KAAK,IAAI,CAAA;AAAA,KACzD;AAAA,EACJ;AAEA,EAAA,MAAM,QAAQ,gBAAA,CAAiB,IAAA;AAAA,IAAK,CAAC,GAAA,KACjC,YAAA,CAAa,GAAA,CAAI,UAAA,IAAc,OAAO;AAAA,GAC1C;AAEA,EAAA,IAAI,CAAC,KAAA,EAAO;AACR,IAAA,MAAM,YAAA,GAAe,gBAAA,CAAiB,gBAAA,EAAkB,OAAO,CAAA;AAE/D,IAAA,MAAA;AAAA,MACI,YAAA,CAAa,OAAA;AAAA,MACb,CAAA,MAAA,EAAS,gBAAA,CAAiB,MAAM,CAAA,qBAAA,EAAwB,mBAAmB,CAAA;AAAA,uBAAA,EAEjD,YAAA,CAAa,WAAW,CAAA,CAAA,EAAI,YAAA,CAAa,SAAS,CAAA,eAAA;AAAA,KAChF,CAAE,cAAc,OAAO,CAAA;AAAA,EAC3B;AACJ;AAEA,SAAS,sBAAsB,QAAA,EAAgE;AAC3F,EAAA,OAAO,QAAA,CAAS,GAAA,CAAI,CAAC,GAAA,MAAS;AAAA,IAC1B,IAAA,EAAM,IAAI,cAAA,EAAe;AAAA,IACzB,OAAA,EAAS,IAAI,UAAA;AAAW,GAC5B,CAAE,CAAA;AACN;AAEA,SAAS,gBAAA,CACL,UACA,eAAA,EAC4E;AAC5E,EAAA,MAAM,YAAA,GAAe,MAAA,CAAO,IAAA,CAAK,eAAe,CAAA;AAChD,EAAA,IAAI,SAAA,GAAY;AAAA,IACZ,SAAS,QAAA,CAAS,CAAC,CAAA,EAAG,UAAA,MAAgB,EAAC;AAAA,IACvC,WAAA,EAAa,CAAA;AAAA,IACb,WAAW,YAAA,CAAa;AAAA,GAC5B;AAEA,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AACxB,IAAA,MAAM,aAAA,GAAgB,IAAI,UAAA,EAAW;AACrC,IAAA,MAAM,WAAA,GAAc,gBAAA,CAAiB,aAAA,EAAe,eAAe,CAAA;AAEnE,IAAA,IAAI,WAAA,GAAc,UAAU,WAAA,EAAa;AACrC,MAAA,SAAA,GAAY,EAAE,OAAA,EAAS,aAAA,EAAe,WAAA,EAAa,SAAA,EAAW,aAAa,MAAA,EAAO;AAAA,IACtF;AAAA,EACJ;AAEA,EAAA,OAAO,SAAA;AACX;AAEA,SAAS,gBAAA,CACL,QACA,QAAA,EACM;AACN,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA,EAAG;AACrC,IAAA,IAAIA,CAAAA,CAAE,QAAQ,MAAA,CAAO,GAAG,GAAG,QAAA,CAAS,GAAG,CAAC,CAAA,EAAG;AACvC,MAAA,OAAA,EAAA;AAAA,IACJ;AAAA,EACJ;AACA,EAAA,OAAO,OAAA;AACX;AGnFO,IAAM,UAAN,MAA6B;AAAA,EA2ChC,WAAA,CACuB,SACnB,OAAA,EACF;AAFqB,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAGnB,IAAA,IAAA,CAAK,UAAU,MAAA,CAAO,MAAA;AAAA,MACjB,KAAK,WAAA,CAAoB,YAAA,CAAa,OAAA,EAAS,OAAA,IAAW,EAAE;AAAA,KACjE;AAEA,IAAA,IAAI,OAAA,IAAW,OAAO,OAAA,KAAY,QAAA,EAAU;AACxC,MAAA,MAAA,CAAO,OAAO,OAAO,CAAA;AAAA,IACzB;AAAA,EACJ;AAAA,EArDU,OAAA;AAAA,EAEV,OAAc,gBAAA,GAA4B;AACtC,IAAA,OAAQ,KAAa,aAAA,IAAiB,MAAA;AAAA,EAC1C;AAAA,EAEA,OAAc,OAAA,GAAkB;AAC5B,IAAA,OAAQ,IAAA,CAAa,QAAQ,IAAA,CAAK,IAAA;AAAA,EACtC;AAAA,EAEA,OAAc,SAAA,GAAgC;AAC1C,IAAA,OAAQ,KAAa,MAAA,IAAU,MAAA;AAAA,EACnC;AAAA,EAEA,OAAiB,cAAc,QAAA,EAAoC;AAC/D,IAAA,OAAO,iBAAA,CAAkB,IAAA,EAAa,GAAG,QAAQ,CAAA;AAAA,EACrD;AAAA,EAEA,OAAc,IAAA,CACV,UAAA,EACA,OAAA,EACO;AACP,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,qBAAA,CAAsB,UAAU,CAAA;AACrD,IAAA,OAAO,IAAI,KAAK,OAAA,EAAS;AAAA,MACrB,SAAS,OAAA,GACH,IAAA,CAAK,sBAAsB,OAAO,CAAA,GAClC,KAAK,UAAA;AAAW,KACzB,CAAA;AAAA,EACL;AAAA,EAEA,OAAiB,sBAAsB,UAAA,EAAsB;AACzD,IAAA,OAAO,UAAA;AAAA,EACX;AAAA,EAEA,OAAiB,sBACb,OAAA,EACc;AACd,IAAA,OAAA,CAAQ,SAAA,GAAY,IAAI,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA;AAE9C,IAAA,OAAO,OAAA;AAAA,EACX;AAAA,EAeA,OAAiB,aACb,OAAA,EACc;AACd,IAAA,OAAO;AAAA,MACH,GAAG,IAAA,CAAK,UAAA,CAAW,GAAG,MAAA,CAAO,IAAA,CAAK,OAAO,CAAC,CAAA;AAAA,MAC1C,GAAG;AAAA,KACP;AAAA,EACJ;AAAA,EAEO,UAAA,CAAW,OAAyB,KAAA,EAAsB;AAC7D,IAAA,MAAM,UAAA,GAAa,EAAE,GAAG,IAAA,CAAK,SAAS,CAAC,KAAK,GAAG,KAAA,EAAM;AACrD,IAAA,OAAO,IAAA,CAAK,iBAAiB,UAAU,CAAA;AAAA,EAC3C;AAAA,EAEU,KAAA,GAAc;AACpB,IAAA,MAAM,SAAS,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,cAAA,CAAe,IAAI,CAAC,CAAA;AACxD,IAAA,MAAA,CAAO,MAAA,CAAO,QAAQ,IAAI,CAAA;AAC1B,IAAA,OAAO,MAAA;AAAA,EACX;AAAA,EAEU,iBAAiB,OAAA,EAAwC;AAC/D,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,EAAM;AAC1B,IAAA,MAAA,CAAO,cAAA,CAAe,QAAQ,SAAA,EAAW;AAAA,MACrC,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,OAAO,CAAA;AAAA,MAC5B,QAAA,EAAU,KAAA;AAAA,MACV,YAAA,EAAc;AAAA,KACjB,CAAA;AACD,IAAA,OAAO,MAAA;AAAA,EACX;AAAA,EAEO,UAAsB,KAAA,EAA8B;AACvD,IAAA,OAAO,IAAA,CAAK,QAAQ,KAAK,CAAA;AAAA,EAC7B;AAAA,EAEO,UAAA,GAA6B;AAChC,IAAA,OAAO,OAAO,MAAA,CAAO,EAAE,GAAG,IAAA,CAAK,SAAS,CAAA;AAAA,EAC5C;AAAA,EAEO,UAAA,GAAsB;AACzB,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EAChB;AAAA,EAEO,YAAA,GAAuB;AAC1B,IAAA,OAAO,KAAK,OAAA,CAAQ,EAAA;AAAA,EACxB;AAAA,EAEO,cAAA,GAAyB;AAC5B,IAAA,OAAO,KAAK,OAAA,CAAQ,IAAA;AAAA,EACxB;AAAA,EAEO,gBAAA,GAAwC;AAC3C,IAAA,OAAO,KAAK,OAAA,CAAQ,aAAA;AAAA,EACxB;AAAA,EAEO,YAAA,GAAqB;AACxB,IAAA,OAAO,KAAK,OAAA,CAAQ,SAAA;AAAA,EACxB;AAAA,EAEO,SAAA,GAAgC;AACnC,IAAA,OAAQ,IAAA,CAAK,YAA6B,SAAA,EAAU;AAAA,EACxD;AAAA,EAEO,MAAA,GAGL;AACE,IAAA,OAAO;AAAA,MACH,OAAA,EAAS,EAAE,GAAG,IAAA,CAAK,OAAA,EAAQ;AAAA,MAC3B,OAAA,EAAS,IAAA,CAAK,gBAAA,CAAiB,IAAA,CAAK,OAAO;AAAA,KAC/C;AAAA,EACJ;AAAA,EAEO,SAAA,GAGL;AACE,IAAA,OAAO,KAAK,KAAA,CAAM,IAAA,CAAK,UAAU,IAAA,CAAK,MAAA,EAAQ,CAAC,CAAA;AAAA,EACnD;AAAA,EAEU,iBAAiB,OAAA,EAA2B;AAClD,IAAA,OAAO,OAAA;AAAA,EACX;AAAA,EAEO,OAA+B,GAAA,EAAyB;AAC3D,IAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAQ,GAAI,KAAK,SAAA,EAAU;AAC5C,IAAA,OAAO,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,OAAO,CAAA;AAAA,EACpC;AAAA,EAEO,OAAA,GAAwB;AAC3B,IAAA,OAAO,EAAE,IAAI,IAAA,CAAK,YAAA,IAAgB,IAAA,EAAM,IAAA,CAAK,gBAAe,EAAE;AAAA,EAClE;AAAA,EAEO,YAAA,GAAyC;AAC5C,IAAA,OAAO,IAAA,CAAK,UAAwB,WAAW,CAAA;AAAA,EACnD;AAAA,EAEO,cAAA,GAA2C;AAC9C,IAAA,OAAO,IAAA,CAAK,UAAwB,aAAa,CAAA;AAAA,EACrD;AAAA,EAEO,cAAc,KAAA,EAA2B;AAC5C,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,WAAA,EAAa,KAAK,CAAA;AAAA,EAC7C;AAAA,EAEO,gBAAgB,KAAA,EAA2B;AAC9C,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,aAAA,EAAe,KAAK,CAAA;AAAA,EAC/C;AACJ,CAAA;AAYA,SAAS,iBAAA,CACL,QACG,QAAA,EACW;AACd,EAAA,MAAM,UAAmC,EAAC;AAE1C,EAAA,IAAI,CAAC,QAAA,CAAS,QAAA,CAAS,IAAI,CAAA,EAAG;AAC1B,IAAA,OAAA,CAAQ,KAAKC,EAAA,EAAK;AAAA,EACtB;AAEA,EAAA,IAAI,CAAC,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,EAAG;AAC5B,IAAA,OAAA,CAAQ,IAAA,GAAO,IAAI,OAAA,EAAQ;AAAA,EAC/B;AAEA,EAAA,IAAI,CAAC,QAAA,CAAS,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC9B,IAAA,MAAM,MAAA,GAAS,IAAI,SAAA,EAAU;AAC7B,IAAA,IAAI,WAAW,MAAA,EAAW;AACtB,MAAA,OAAA,CAAQ,MAAA,GAAS,MAAA;AAAA,IACrB;AAAA,EACJ;AAEA,EAAA,IAAI,CAAC,QAAA,CAAS,QAAA,CAAS,eAAe,CAAA,EAAG;AACrC,IAAA,MAAM,aAAA,GAAgB,IAAI,gBAAA,EAAiB;AAC3C,IAAA,IAAI,kBAAkB,MAAA,EAAW;AAC7B,MAAA,OAAA,CAAQ,aAAA,GAAgB,aAAA;AAAA,IAC5B;AAAA,EACJ;AAEA,EAAA,IAAI,CAAC,QAAA,CAAS,QAAA,CAAS,WAAW,CAAA,EAAG;AACjC,IAAA,OAAA,CAAQ,SAAA,uBAAgB,IAAA,EAAK;AAAA,EACjC;AAEA,EAAA,OAAO,OAAA;AACX;;;AC5OO,IAAM,YAAA,GAAN,cAA2B,OAAA,CAA8B;AAAA,EAC5D,OAAO,IAAA,GAAO,oBAAA;AAAA,EAEd,OAAc,MAAA,GAAS;AACnB,IAAA,OAAO,IAAI,IAAA,CAAK,EAAE,CAAA;AAAA,EACtB;AAAA,EAEA,OAAc,WAAW,MAAA,EAAgB;AACrC,IAAA,OAAOD,EAAE,KAAA,CAAM,MAAA,EAAQ,MAAM,IAAA,CAAK,QAAQ,CAAA;AAAA,EAC9C;AAAA,EAEA,OAAc,IAAA,CACVA,EAAAA,EACA,OAAA,EACY;AACZ,IAAA,OAAO,IAAI,IAAA,CAAK,EAAC,EAAG,EAAE,SAAS,CAAA;AAAA,EACnC;AACJ;;;AClBO,IAAM,qBAAN,MAA+C;AAAA,EAC1C,SAAwB,EAAC;AAAA,EAEjC,MAAM,MAAM,KAAA,EAAsC;AAC9C,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,MAAA,GAAS,CAAA;AACtC,IAAA,MAAM,WAAA,GAA2B,EAAE,QAAA,EAAU,KAAA,EAAM;AACnD,IAAA,IAAA,CAAK,MAAA,CAAO,KAAK,WAAW,CAAA;AAC5B,IAAA,OAAO,WAAA;AAAA,EACX;AAAA,EAEA,MAAM,SAAS,MAAA,EAA2C;AACtD,IAAA,MAAM,eAA8B,EAAC;AACrC,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AACxB,MAAA,YAAA,CAAa,IAAA,CAAK,MAAM,IAAA,CAAK,KAAA,CAAM,KAAK,CAAC,CAAA;AAAA,IAC7C;AACA,IAAA,OAAO,YAAA;AAAA,EACX;AAAA,EAEA,MAAM,KAAA,CACF,aAAA,EACA,KAAA,EAC8B;AAC9B,IAAA,MAAM,YAAA,GAAe,MAAM,IAAA,CAAK,eAAA,EAAgB;AAChD,IAAA,IAAI,QAAA,GAAW,KAAK,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,aAAa,CAAA;AAEnE,IAAA,IAAI,UAAU,MAAA,EAAW;AACrB,MAAA,QAAA,GAAW,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA;AAAA,IACtC;AAEA,IAAA,OAAO;AAAA,MACH,MAAA,EAAQ,QAAA;AAAA,MACR;AAAA,KACJ;AAAA,EACJ;AAAA,EAEA,MAAM,eAAA,GAAmC;AACrC,IAAA,OAAO,KAAK,MAAA,CAAO,MAAA;AAAA,EACvB;AAAA,EAEA,KAAA,GAAc;AACV,IAAA,IAAA,CAAK,SAAS,EAAC;AAAA,EACnB;AACJ","file":"index.js","sourcesContent":["import _ from \"lodash\";\n\nimport { Message, MessageClass } from \"@/message\";\nimport { partialMatch } from \"./utils\";\nimport { expect } from \"vitest\";\n\nexport function expectMessagesToBeFullyEqual(\n messages: Message[],\n expectedMessages: Message[]\n): void {\n const actualSummary = formatMessagesSummary(messages);\n const expectedSummary = formatMessagesSummary(expectedMessages);\n\n expect(actualSummary, \"Messages should match\").toEqual(expectedSummary);\n\n for (let i = 0; i < Math.max(messages.length, expectedMessages.length); i++) {\n const actual = messages[i]?.serialize();\n const expected = expectedMessages[i]?.serialize();\n\n expect(actual, `message[${i}]`).toEqual(expected);\n }\n}\n\nexport function expectMessagesToContain(\n messages: Message[],\n expectedMessages: Message[]\n): void {\n for (const message of expectedMessages) {\n expectMessageToMatch(\n messages,\n message.getMessageType(),\n message.getPayload()\n );\n }\n}\n\nexport function expectMessageToMatch(\n messages: Message[],\n messageType: string | MessageClass<any>,\n payload: Record<string, unknown> = {}\n): void {\n const resolvedMessageType =\n typeof messageType === \"string\" ? messageType : messageType.getType();\n const sameTypeMessages = messages.filter(\n (msg) => msg.getMessageType() === resolvedMessageType\n );\n\n if (sameTypeMessages.length === 0) {\n const availableTypes = [...new Set(messages.map((m) => m.getMessageType()))];\n expect.fail(\n `Message not found: \"${resolvedMessageType}\"\\n\\n` +\n `Available message types:\\n` +\n (availableTypes.length === 0\n ? \" (none)\"\n : availableTypes.map((t) => ` - ${t}`).join(\"\\n\"))\n );\n }\n\n const found = sameTypeMessages.find((msg) =>\n partialMatch(msg.getPayload(), payload)\n );\n\n if (!found) {\n const closestMatch = findClosestMatch(sameTypeMessages, payload);\n\n expect(\n closestMatch.payload,\n `Found ${sameTypeMessages.length} message(s) of type \"${resolvedMessageType}\", ` +\n `but payload did not match.\\n` +\n `Showing closest match (${closestMatch.matchedKeys}/${closestMatch.totalKeys} keys matched):`\n ).toMatchObject(payload);\n }\n}\n\nfunction formatMessagesSummary(messages: Message[]): Array<{ type: string; payload: unknown }> {\n return messages.map((msg) => ({\n type: msg.getMessageType(),\n payload: msg.getPayload(),\n }));\n}\n\nfunction findClosestMatch(\n messages: Message[],\n expectedPayload: Record<string, unknown>\n): { payload: Record<string, unknown>; matchedKeys: number; totalKeys: number } {\n const expectedKeys = Object.keys(expectedPayload);\n let bestMatch = {\n payload: messages[0]?.getPayload() ?? {},\n matchedKeys: 0,\n totalKeys: expectedKeys.length,\n };\n\n for (const msg of messages) {\n const actualPayload = msg.getPayload();\n const matchedKeys = countMatchedKeys(actualPayload, expectedPayload);\n\n if (matchedKeys > bestMatch.matchedKeys) {\n bestMatch = { payload: actualPayload, matchedKeys, totalKeys: expectedKeys.length };\n }\n }\n\n return bestMatch;\n}\n\nfunction countMatchedKeys(\n actual: Record<string, unknown>,\n expected: Record<string, unknown>\n): number {\n let matched = 0;\n for (const key of Object.keys(expected)) {\n if (_.isEqual(actual[key], expected[key])) {\n matched++;\n }\n }\n return matched;\n}\n","import _ from \"lodash\";\n\nexport const anyString = Symbol.for(\"string\");\n\nexport const anyDate = Symbol.for(\"date\");\n\nexport function partialMatch(\n source: Record<any, any>,\n target: Record<any, any>\n): boolean {\n for (const key of Object.keys(target)) {\n const shouldRecurse =\n _.isObject(source[key]) && _.isObject(target[key]);\n\n if (shouldRecurse) {\n if (!partialMatch(source[key], target[key])) {\n return false;\n }\n } else {\n if (target[key] === anyString && typeof source[key] === \"string\") {\n continue;\n }\n\n if (target[key] === anyDate && source[key] instanceof Date) {\n continue;\n }\n\n if (!_.isEqual(source[key], target[key])) {\n return false;\n }\n }\n }\n\n return true;\n}\n","export { partialMatch } from \"./partial-match\";\n\nexport async function waitForTicks(number = 10): Promise<void> {\n for (let i = 0; i < number; i++) {\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n}\n\nexport async function waitForMs(number = 10): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, number));\n}\n\nexport async function waitFor(\n type: \"ticks\" | \"ms\" = \"ticks\",\n number = 10\n): Promise<void> {\n if (type === \"ticks\") {\n await waitForTicks(number);\n } else {\n await waitForMs(number);\n }\n}\n","import { v4 as uuid } from \"uuid\";\n\ntype Version = string | number | undefined;\n\nexport interface MessageTrace {\n id: string;\n type: string;\n}\n\nexport interface MessageHeaders {\n id: string;\n type: string;\n intent?: string;\n schemaVersion?: Version;\n createdAt: Date;\n\n [key: string]: unknown;\n}\n\ntype ExtraHeaderField = Exclude<\n keyof MessageHeaders,\n \"id\" | \"type\" | \"intent\" | \"schemaVersion\" | \"createdAt\"\n>;\n\ntype RawMessageHeaders = Omit<MessageHeaders, \"createdAt\"> & {\n createdAt: string | Date;\n};\n\nexport interface MessageOptions {\n headers?: Record<string, unknown>;\n}\n\nexport class Message<Payload = any> {\n protected headers!: MessageHeaders;\n\n public static getSchemaVersion(): Version {\n return (this as any).schemaVersion ?? undefined;\n }\n\n public static getType(): string {\n return (this as any).type ?? this.name;\n }\n\n public static getIntent(): string | undefined {\n return (this as any).intent ?? undefined;\n }\n\n protected static newHeaders(...excludes: string[]): MessageHeaders {\n return generateHeaderFor(this as any, ...excludes);\n }\n\n public static from(\n rawPayload: Record<string, unknown>,\n headers?: RawMessageHeaders\n ): Message {\n const payload = this.deserializeRawPayload(rawPayload);\n return new this(payload, {\n headers: headers\n ? this.deserializeRawHeaders(headers)\n : this.newHeaders(),\n });\n }\n\n protected static deserializeRawPayload(rawPayload: any): any {\n return rawPayload;\n }\n\n protected static deserializeRawHeaders(\n headers: RawMessageHeaders\n ): MessageHeaders {\n headers.createdAt = new Date(headers.createdAt);\n\n return headers as MessageHeaders;\n }\n\n constructor(\n protected readonly payload: Payload,\n options?: MessageOptions\n ) {\n this.headers = Object.freeze(\n (this.constructor as any).mergeHeaders(options?.headers ?? {})\n );\n\n if (payload && typeof payload === \"object\") {\n Object.freeze(payload);\n }\n }\n\n protected static mergeHeaders(\n headers: Record<string, unknown>\n ): MessageHeaders {\n return {\n ...this.newHeaders(...Object.keys(headers)),\n ...headers,\n };\n }\n\n public withHeader(field: ExtraHeaderField, value: unknown): this {\n const newHeaders = { ...this.headers, [field]: value };\n return this.cloneWithHeaders(newHeaders);\n }\n\n protected clone(): this {\n const cloned = Object.create(Object.getPrototypeOf(this));\n Object.assign(cloned, this);\n return cloned;\n }\n\n protected cloneWithHeaders(headers: Record<string, unknown>): this {\n const cloned = this.clone();\n Object.defineProperty(cloned, \"headers\", {\n value: Object.freeze(headers),\n writable: false,\n configurable: true,\n });\n return cloned;\n }\n\n public getHeader<T = string>(field: string): T | undefined {\n return this.headers[field] as any;\n }\n\n public getHeaders(): MessageHeaders {\n return Object.freeze({ ...this.headers });\n }\n\n public getPayload(): Payload {\n return this.payload;\n }\n\n public getMessageId(): string {\n return this.headers.id;\n }\n\n public getMessageType(): string {\n return this.headers.type;\n }\n\n public getSchemaVersion(): Version | undefined {\n return this.headers.schemaVersion;\n }\n\n public getTimestamp(): Date {\n return this.headers.createdAt;\n }\n\n public getIntent(): string | undefined {\n return (this.constructor as MessageClass).getIntent();\n }\n\n public toJSON(): {\n headers: MessageHeaders;\n payload: unknown;\n } {\n return {\n headers: { ...this.headers },\n payload: this.serializePayload(this.payload),\n };\n }\n\n public serialize(): {\n headers: MessageHeaders;\n payload: Record<string, unknown>;\n } {\n return JSON.parse(JSON.stringify(this.toJSON()));\n }\n\n protected serializePayload(payload: Payload): unknown {\n return payload;\n }\n\n public asType<M extends MessageClass>(cls: M): InstanceType<M> {\n const { headers, payload } = this.serialize();\n return cls.from(payload, headers) as InstanceType<M>;\n }\n\n public asTrace(): MessageTrace {\n return { id: this.getMessageId(), type: this.getMessageType() };\n }\n\n public getCausation(): MessageTrace | undefined {\n return this.getHeader<MessageTrace>(\"causation\");\n }\n\n public getCorrelation(): MessageTrace | undefined {\n return this.getHeader<MessageTrace>(\"correlation\");\n }\n\n public withCausation(trace: MessageTrace): this {\n return this.withHeader(\"causation\", trace);\n }\n\n public withCorrelation(trace: MessageTrace): this {\n return this.withHeader(\"correlation\", trace);\n }\n}\n\nexport type AnyMessage = Message;\n\nexport type MessageClass<T extends Message = Message> = {\n getSchemaVersion(): Version;\n getType(): string;\n getIntent(): string | undefined;\n from: (rawPayload: any, header?: MessageHeaders) => T;\n new (...args: any[]): T;\n};\n\nfunction generateHeaderFor(\n cls: MessageClass,\n ...excludes: string[]\n): MessageHeaders {\n const headers: Partial<MessageHeaders> = {};\n\n if (!excludes.includes(\"id\")) {\n headers.id = uuid();\n }\n\n if (!excludes.includes(\"type\")) {\n headers.type = cls.getType();\n }\n\n if (!excludes.includes(\"intent\")) {\n const intent = cls.getIntent();\n if (intent !== undefined) {\n headers.intent = intent;\n }\n }\n\n if (!excludes.includes(\"schemaVersion\")) {\n const schemaVersion = cls.getSchemaVersion();\n if (schemaVersion !== undefined) {\n headers.schemaVersion = schemaVersion;\n }\n }\n\n if (!excludes.includes(\"createdAt\")) {\n headers.createdAt = new Date();\n }\n\n return headers as MessageHeaders;\n}\n\nexport type PayloadOf<M> = M extends Message<infer P> ? P : never;\n","import _ from \"lodash\";\n\nimport { Message, MessageHeaders } from \"@/message\";\n\nexport class DummyMessage extends Message<Record<never, never>> {\n static type = \"test.dummy-message\";\n\n public static create() {\n return new this({});\n }\n\n public static createMany(number: number) {\n return _.times(number, () => this.create());\n }\n\n public static from(\n _: Record<never, never>,\n headers?: MessageHeaders\n ): DummyMessage {\n return new this({}, { headers });\n }\n}\n","import { EventStore, EventStoreFetchResult, StoredEvent } from \"@/event-store\";\nimport { Message } from \"@/message\";\n\nexport class InMemoryEventStore implements EventStore {\n private events: StoredEvent[] = [];\n\n async store(event: Message): Promise<StoredEvent> {\n const position = this.events.length + 1;\n const storedEvent: StoredEvent = { position, event };\n this.events.push(storedEvent);\n return storedEvent;\n }\n\n async storeAll(events: Message[]): Promise<StoredEvent[]> {\n const storedEvents: StoredEvent[] = [];\n for (const event of events) {\n storedEvents.push(await this.store(event));\n }\n return storedEvents;\n }\n\n async fetch(\n afterPosition: number,\n limit?: number\n ): Promise<EventStoreFetchResult> {\n const lastPosition = await this.getLastPosition();\n let filtered = this.events.filter((e) => e.position > afterPosition);\n\n if (limit !== undefined) {\n filtered = filtered.slice(0, limit);\n }\n\n return {\n events: filtered,\n lastPosition,\n };\n }\n\n async getLastPosition(): Promise<number> {\n return this.events.length;\n }\n\n clear(): void {\n this.events = [];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Message, MessageClass } from "../message.js";
|
|
2
|
+
export declare function expectMessagesToBeFullyEqual(messages: Message[], expectedMessages: Message[]): void;
|
|
3
|
+
export declare function expectMessagesToContain(messages: Message[], expectedMessages: Message[]): void;
|
|
4
|
+
export declare function expectMessageToMatch(messages: Message[], messageType: string | MessageClass<any>, payload?: Record<string, unknown>): void;
|
|
5
|
+
//# sourceMappingURL=matchers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/test/matchers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAItD,wBAAgB,4BAA4B,CACxC,QAAQ,EAAE,OAAO,EAAE,EACnB,gBAAgB,EAAE,OAAO,EAAE,GAC5B,IAAI,CAYN;AAED,wBAAgB,uBAAuB,CACnC,QAAQ,EAAE,OAAO,EAAE,EACnB,gBAAgB,EAAE,OAAO,EAAE,GAC5B,IAAI,CAQN;AAED,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,OAAO,EAAE,EACnB,WAAW,EAAE,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,EACvC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACtC,IAAI,CAgCN"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import { partialMatch } from "./utils.js";
|
|
3
|
+
import { expect } from "vitest";
|
|
4
|
+
export function expectMessagesToBeFullyEqual(messages, expectedMessages) {
|
|
5
|
+
const actualSummary = formatMessagesSummary(messages);
|
|
6
|
+
const expectedSummary = formatMessagesSummary(expectedMessages);
|
|
7
|
+
expect(actualSummary, "Messages should match").toEqual(expectedSummary);
|
|
8
|
+
for (let i = 0; i < Math.max(messages.length, expectedMessages.length); i++) {
|
|
9
|
+
const actual = messages[i]?.serialize();
|
|
10
|
+
const expected = expectedMessages[i]?.serialize();
|
|
11
|
+
expect(actual, `message[${i}]`).toEqual(expected);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function expectMessagesToContain(messages, expectedMessages) {
|
|
15
|
+
for (const message of expectedMessages) {
|
|
16
|
+
expectMessageToMatch(messages, message.getMessageType(), message.getPayload());
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function expectMessageToMatch(messages, messageType, payload = {}) {
|
|
20
|
+
const resolvedMessageType = typeof messageType === "string" ? messageType : messageType.getType();
|
|
21
|
+
const sameTypeMessages = messages.filter((msg) => msg.getMessageType() === resolvedMessageType);
|
|
22
|
+
if (sameTypeMessages.length === 0) {
|
|
23
|
+
const availableTypes = [...new Set(messages.map((m) => m.getMessageType()))];
|
|
24
|
+
expect.fail(`Message not found: "${resolvedMessageType}"\n\n` +
|
|
25
|
+
`Available message types:\n` +
|
|
26
|
+
(availableTypes.length === 0
|
|
27
|
+
? " (none)"
|
|
28
|
+
: availableTypes.map((t) => ` - ${t}`).join("\n")));
|
|
29
|
+
}
|
|
30
|
+
const found = sameTypeMessages.find((msg) => partialMatch(msg.getPayload(), payload));
|
|
31
|
+
if (!found) {
|
|
32
|
+
const closestMatch = findClosestMatch(sameTypeMessages, payload);
|
|
33
|
+
expect(closestMatch.payload, `Found ${sameTypeMessages.length} message(s) of type "${resolvedMessageType}", ` +
|
|
34
|
+
`but payload did not match.\n` +
|
|
35
|
+
`Showing closest match (${closestMatch.matchedKeys}/${closestMatch.totalKeys} keys matched):`).toMatchObject(payload);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function formatMessagesSummary(messages) {
|
|
39
|
+
return messages.map((msg) => ({
|
|
40
|
+
type: msg.getMessageType(),
|
|
41
|
+
payload: msg.getPayload(),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
function findClosestMatch(messages, expectedPayload) {
|
|
45
|
+
const expectedKeys = Object.keys(expectedPayload);
|
|
46
|
+
let bestMatch = {
|
|
47
|
+
payload: messages[0]?.getPayload() ?? {},
|
|
48
|
+
matchedKeys: 0,
|
|
49
|
+
totalKeys: expectedKeys.length,
|
|
50
|
+
};
|
|
51
|
+
for (const msg of messages) {
|
|
52
|
+
const actualPayload = msg.getPayload();
|
|
53
|
+
const matchedKeys = countMatchedKeys(actualPayload, expectedPayload);
|
|
54
|
+
if (matchedKeys > bestMatch.matchedKeys) {
|
|
55
|
+
bestMatch = { payload: actualPayload, matchedKeys, totalKeys: expectedKeys.length };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return bestMatch;
|
|
59
|
+
}
|
|
60
|
+
function countMatchedKeys(actual, expected) {
|
|
61
|
+
let matched = 0;
|
|
62
|
+
for (const key of Object.keys(expected)) {
|
|
63
|
+
if (_.isEqual(actual[key], expected[key])) {
|
|
64
|
+
matched++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return matched;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=matchers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matchers.js","sourceRoot":"","sources":["../../src/test/matchers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partial-match.d.ts","sourceRoot":"","sources":["../../src/test/partial-match.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,eAAuB,CAAC;AAE9C,eAAO,MAAM,OAAO,eAAqB,CAAC;AAE1C,wBAAgB,YAAY,CACxB,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,EACxB,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GACzB,OAAO,CAyBT"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
export const anyString = Symbol.for("string");
|
|
3
|
+
export const anyDate = Symbol.for("date");
|
|
4
|
+
export function partialMatch(source, target) {
|
|
5
|
+
for (const key of Object.keys(target)) {
|
|
6
|
+
const shouldRecurse = _.isObject(source[key]) && _.isObject(target[key]);
|
|
7
|
+
if (shouldRecurse) {
|
|
8
|
+
if (!partialMatch(source[key], target[key])) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
if (target[key] === anyString && typeof source[key] === "string") {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (target[key] === anyDate && source[key] instanceof Date) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (!_.isEqual(source[key], target[key])) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=partial-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partial-match.js","sourceRoot":"","sources":["../../src/test/partial-match.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,QAAQ,CAAC;AAEvB,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAE9C,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAE1C,MAAM,UAAU,YAAY,CACxB,MAAwB,EACxB,MAAwB;IAExB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,MAAM,aAAa,GACf,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAEvD,IAAI,aAAa,EAAE,CAAC;YAChB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC1C,OAAO,KAAK,CAAC;YACjB,CAAC;QACL,CAAC;aAAM,CAAC;YACJ,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC/D,SAAS;YACb,CAAC;YAED,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;gBACzD,SAAS;YACb,CAAC;YAED,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBACvC,OAAO,KAAK,CAAC;YACjB,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { partialMatch } from "./partial-match.js";
|
|
2
|
+
export declare function waitForTicks(number?: number): Promise<void>;
|
|
3
|
+
export declare function waitForMs(number?: number): Promise<void>;
|
|
4
|
+
export declare function waitFor(type?: "ticks" | "ms", number?: number): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,wBAAsB,YAAY,CAAC,MAAM,SAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D;AAED,wBAAsB,SAAS,CAAC,MAAM,SAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1D;AAED,wBAAsB,OAAO,CACzB,IAAI,GAAE,OAAO,GAAG,IAAc,EAC9B,MAAM,SAAK,GACZ,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { partialMatch } from "./partial-match.js";
|
|
2
|
+
export async function waitForTicks(number = 10) {
|
|
3
|
+
for (let i = 0; i < number; i++) {
|
|
4
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export async function waitForMs(number = 10) {
|
|
8
|
+
await new Promise((resolve) => setTimeout(resolve, number));
|
|
9
|
+
}
|
|
10
|
+
export async function waitFor(type = "ticks", number = 10) {
|
|
11
|
+
if (type === "ticks") {
|
|
12
|
+
await waitForTicks(number);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
await waitForMs(number);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAM,GAAG,EAAE;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAM,GAAG,EAAE;IACvC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CACzB,IAAI,GAAmB,OAAO,EAC9B,MAAM,GAAG,EAAE;IAEX,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACnB,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;SAAM,CAAC;QACJ,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TransactionHook } from "./unit-of-work.js";
|
|
2
|
+
export declare class TransactionHooks {
|
|
3
|
+
private beforeCommitHooks;
|
|
4
|
+
private afterCommitHooks;
|
|
5
|
+
private afterRollbackHooks;
|
|
6
|
+
addBeforeCommit(hook: TransactionHook): void;
|
|
7
|
+
addAfterCommit(hook: TransactionHook): void;
|
|
8
|
+
addAfterRollback(hook: TransactionHook): void;
|
|
9
|
+
executeCommit(commitFn: () => Promise<void>, rollbackFn: () => Promise<void>): Promise<void>;
|
|
10
|
+
executeRollback(rollbackFn: () => Promise<void>, cause?: unknown): Promise<void>;
|
|
11
|
+
private runBestEffort;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=transaction-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-hooks.d.ts","sourceRoot":"","sources":["../src/transaction-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,qBAAa,gBAAgB;IACzB,OAAO,CAAC,iBAAiB,CAAyB;IAClD,OAAO,CAAC,gBAAgB,CAAyB;IACjD,OAAO,CAAC,kBAAkB,CAAyB;IAEnD,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAE3C;IAED,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAE1C;IAED,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAE5C;IAEK,aAAa,CACf,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAC7B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAChC,OAAO,CAAC,IAAI,CAAC,CAUf;IAEK,eAAe,CACjB,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAC/B,KAAK,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,IAAI,CAAC,CAGf;YAEa,aAAa;CAsB9B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export class TransactionHooks {
|
|
2
|
+
beforeCommitHooks = [];
|
|
3
|
+
afterCommitHooks = [];
|
|
4
|
+
afterRollbackHooks = [];
|
|
5
|
+
addBeforeCommit(hook) {
|
|
6
|
+
this.beforeCommitHooks.push(hook);
|
|
7
|
+
}
|
|
8
|
+
addAfterCommit(hook) {
|
|
9
|
+
this.afterCommitHooks.push(hook);
|
|
10
|
+
}
|
|
11
|
+
addAfterRollback(hook) {
|
|
12
|
+
this.afterRollbackHooks.push(hook);
|
|
13
|
+
}
|
|
14
|
+
async executeCommit(commitFn, rollbackFn) {
|
|
15
|
+
try {
|
|
16
|
+
for (const hook of this.beforeCommitHooks)
|
|
17
|
+
await hook();
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
await this.executeRollback(rollbackFn, e);
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
await commitFn();
|
|
24
|
+
await this.runBestEffort(this.afterCommitHooks);
|
|
25
|
+
}
|
|
26
|
+
async executeRollback(rollbackFn, cause) {
|
|
27
|
+
await rollbackFn();
|
|
28
|
+
await this.runBestEffort(this.afterRollbackHooks, cause);
|
|
29
|
+
}
|
|
30
|
+
async runBestEffort(hooks, cause) {
|
|
31
|
+
const errors = [];
|
|
32
|
+
for (const hook of hooks) {
|
|
33
|
+
try {
|
|
34
|
+
await hook();
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
errors.push(e);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (errors.length > 0) {
|
|
41
|
+
throw cause !== undefined
|
|
42
|
+
? new AggregateError(errors, "Transaction hook(s) failed", { cause })
|
|
43
|
+
: new AggregateError(errors);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=transaction-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-hooks.js","sourceRoot":"","sources":["../src/transaction-hooks.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,gBAAgB;IACjB,iBAAiB,GAAsB,EAAE,CAAC;IAC1C,gBAAgB,GAAsB,EAAE,CAAC;IACzC,kBAAkB,GAAsB,EAAE,CAAC;IAEnD,eAAe,CAAC,IAAqB;QACjC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,cAAc,CAAC,IAAqB;QAChC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,gBAAgB,CAAC,IAAqB;QAClC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,aAAa,CACf,QAA6B,EAC7B,UAA+B;QAE/B,IAAI,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,iBAAiB;gBAAE,MAAM,IAAI,EAAE,CAAC;QAC5D,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,MAAM,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YAC1C,MAAM,CAAC,CAAC;QACZ,CAAC;QAED,MAAM,QAAQ,EAAE,CAAC;QACjB,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,eAAe,CACjB,UAA+B,EAC/B,KAAe;QAEf,MAAM,UAAU,EAAE,CAAC;QACnB,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;IAC7D,CAAC;IAEO,KAAK,CAAC,aAAa,CACvB,KAAwB,EACxB,KAAe;QAEf,MAAM,MAAM,GAAc,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,CAAC;gBACD,MAAM,IAAI,EAAE,CAAC;YACjB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACnB,CAAC;QACL,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,KAAK,KAAK,SAAS;gBACrB,CAAC,CAAC,IAAI,cAAc,CACd,MAAM,EACN,4BAA4B,EAC5B,EAAE,KAAK,EAAE,CACZ;gBACH,CAAC,CAAC,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;IACL,CAAC;CACJ"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare enum Propagation {
|
|
2
|
+
NEW = "new",
|
|
3
|
+
EXISTING = "existing",
|
|
4
|
+
NESTED = "nested"
|
|
5
|
+
}
|
|
6
|
+
export type TransactionHook = () => void | Promise<void>;
|
|
7
|
+
export interface BaseUnitOfWorkOptions {
|
|
8
|
+
propagation: Propagation;
|
|
9
|
+
}
|
|
10
|
+
export interface UnitOfWork<Client = unknown, Options extends BaseUnitOfWorkOptions = BaseUnitOfWorkOptions> {
|
|
11
|
+
getClient(): Client;
|
|
12
|
+
scope<T>(fn: () => Promise<T>, options?: Partial<Options>): Promise<T>;
|
|
13
|
+
/** @deprecated Use scope() for transaction boundaries and withClient() for client access. */
|
|
14
|
+
wrap<T>(fn: (client: Client) => Promise<T>, options?: Partial<Options>): Promise<T>;
|
|
15
|
+
beforeCommit(hook: TransactionHook): void;
|
|
16
|
+
afterCommit(hook: TransactionHook): void;
|
|
17
|
+
afterRollback(hook: TransactionHook): void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=unit-of-work.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-of-work.d.ts","sourceRoot":"","sources":["../src/unit-of-work.ts"],"names":[],"mappings":"AAAA,oBAAY,WAAW;IACnB,GAAG,QAAQ;IACX,QAAQ,aAAa;IACrB,MAAM,WAAW;CACpB;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEzD,MAAM,WAAW,qBAAqB;IAClC,WAAW,EAAE,WAAW,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU,CACvB,MAAM,GAAG,OAAO,EAChB,OAAO,SAAS,qBAAqB,GAAG,qBAAqB;IAE7D,SAAS,IAAI,MAAM,CAAC;IAEpB,KAAK,CAAC,CAAC,EACH,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,CAAC,CAAC,CAAC;IAEd,6FAA6F;IAC7F,IAAI,CAAC,CAAC,EACF,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAClC,OAAO,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,CAAC,CAAC,CAAC;IAEd,YAAY,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IAC1C,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IACzC,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;CAC9C"}
|