@zucker-framework/event 1.0.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/LICENSE +201 -0
- package/NOTICE +22 -0
- package/THIRD_PARTY_NOTICES +4 -0
- package/dist/index.d.mts +813 -0
- package/dist/index.d.ts +813 -0
- package/dist/index.js +1101 -0
- package/dist/index.mjs +1034 -0
- package/package.json +57 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1034 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/entity-events.ts
|
|
5
|
+
var EntityEventType = /* @__PURE__ */ (function(EntityEventType2) {
|
|
6
|
+
EntityEventType2["CREATE"] = "create";
|
|
7
|
+
EntityEventType2["MODIFY"] = "modify";
|
|
8
|
+
EntityEventType2["DELETE"] = "delete";
|
|
9
|
+
EntityEventType2["SAVE"] = "save";
|
|
10
|
+
EntityEventType2["QUERY"] = "query";
|
|
11
|
+
EntityEventType2["UPDATE"] = "modify";
|
|
12
|
+
return EntityEventType2;
|
|
13
|
+
})({});
|
|
14
|
+
var EntityEventPhase = /* @__PURE__ */ (function(EntityEventPhase2) {
|
|
15
|
+
EntityEventPhase2["PREPARE"] = "prepare";
|
|
16
|
+
EntityEventPhase2["BEFORE"] = "before";
|
|
17
|
+
EntityEventPhase2["AFTER"] = "after";
|
|
18
|
+
return EntityEventPhase2;
|
|
19
|
+
})({});
|
|
20
|
+
var ALL_EVENT_PHASES = [
|
|
21
|
+
"prepare",
|
|
22
|
+
"before",
|
|
23
|
+
"after"
|
|
24
|
+
];
|
|
25
|
+
function createEntityEvent(type, phase, entityType, entity, before) {
|
|
26
|
+
let _cancelled = false;
|
|
27
|
+
let _cancelReason;
|
|
28
|
+
return {
|
|
29
|
+
type,
|
|
30
|
+
phase,
|
|
31
|
+
entityType,
|
|
32
|
+
entity,
|
|
33
|
+
timestamp: Date.now(),
|
|
34
|
+
before,
|
|
35
|
+
get cancelled() {
|
|
36
|
+
return _cancelled;
|
|
37
|
+
},
|
|
38
|
+
cancel(reason) {
|
|
39
|
+
_cancelled = true;
|
|
40
|
+
_cancelReason = reason;
|
|
41
|
+
},
|
|
42
|
+
get cancelReason() {
|
|
43
|
+
return _cancelReason;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
__name(createEntityEvent, "createEntityEvent");
|
|
48
|
+
function getEntityEventName(entityType, type, phase) {
|
|
49
|
+
return `entity/${entityType}/${type}/${phase}`;
|
|
50
|
+
}
|
|
51
|
+
__name(getEntityEventName, "getEntityEventName");
|
|
52
|
+
|
|
53
|
+
// src/entity-event-listener.ts
|
|
54
|
+
var CreatorEventListener = class {
|
|
55
|
+
static {
|
|
56
|
+
__name(this, "CreatorEventListener");
|
|
57
|
+
}
|
|
58
|
+
getCurrentUser;
|
|
59
|
+
order = -Infinity;
|
|
60
|
+
constructor(getCurrentUser) {
|
|
61
|
+
this.getCurrentUser = getCurrentUser;
|
|
62
|
+
}
|
|
63
|
+
async onEvent(event) {
|
|
64
|
+
if (event.phase !== EntityEventPhase.PREPARE) return;
|
|
65
|
+
const user = this.getCurrentUser();
|
|
66
|
+
if (!user) return;
|
|
67
|
+
const entities = Array.isArray(event.entity) ? event.entity : [
|
|
68
|
+
event.entity
|
|
69
|
+
];
|
|
70
|
+
const now = /* @__PURE__ */ new Date();
|
|
71
|
+
const isCreate = event.type === EntityEventType.CREATE || event.type === EntityEventType.SAVE;
|
|
72
|
+
const isModify = event.type === EntityEventType.MODIFY || event.type === EntityEventType.SAVE;
|
|
73
|
+
for (const entity of entities) {
|
|
74
|
+
if (typeof entity !== "object" || entity === null) continue;
|
|
75
|
+
const rec = entity;
|
|
76
|
+
if (isCreate) {
|
|
77
|
+
rec.creatorId ??= user.id;
|
|
78
|
+
if (user.name !== void 0) {
|
|
79
|
+
rec.creatorName ??= user.name;
|
|
80
|
+
}
|
|
81
|
+
rec.createdAt ??= now;
|
|
82
|
+
}
|
|
83
|
+
if (isModify || isCreate) {
|
|
84
|
+
if (rec.modifierId == null || rec.modifierId === "" || rec.modifierId === void 0) {
|
|
85
|
+
rec.modifierId = user.id;
|
|
86
|
+
}
|
|
87
|
+
if (user.name !== void 0) {
|
|
88
|
+
if (rec.modifierName == null || rec.modifierName === "" || rec.modifierName === void 0) {
|
|
89
|
+
rec.modifierName = user.name;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
rec.updatedAt ??= now;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var CompositeEventListener = class {
|
|
98
|
+
static {
|
|
99
|
+
__name(this, "CompositeEventListener");
|
|
100
|
+
}
|
|
101
|
+
listeners = [];
|
|
102
|
+
addListener(listener) {
|
|
103
|
+
this.listeners.push(listener);
|
|
104
|
+
this.listeners.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
105
|
+
}
|
|
106
|
+
removeListener(listener) {
|
|
107
|
+
const idx = this.listeners.indexOf(listener);
|
|
108
|
+
if (idx >= 0) this.listeners.splice(idx, 1);
|
|
109
|
+
}
|
|
110
|
+
async onEvent(event) {
|
|
111
|
+
for (const listener of this.listeners) {
|
|
112
|
+
await listener.onEvent(event);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
getListeners() {
|
|
116
|
+
return this.listeners;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// src/topic-event-bus.ts
|
|
121
|
+
import { Logger } from "@nestjs/common";
|
|
122
|
+
|
|
123
|
+
// src/subscribe.decorator.ts
|
|
124
|
+
var SUBSCRIBE_KEY = "zucker:event:subscribe";
|
|
125
|
+
var SUBSCRIBE_METHODS_KEY = "zucker:event:subscribeMethods";
|
|
126
|
+
var SubscribeFeature = /* @__PURE__ */ (function(SubscribeFeature2) {
|
|
127
|
+
SubscribeFeature2["LOCAL"] = "local";
|
|
128
|
+
SubscribeFeature2["CLUSTER"] = "cluster";
|
|
129
|
+
SubscribeFeature2["SHARED"] = "shared";
|
|
130
|
+
SubscribeFeature2["BROKER"] = "broker";
|
|
131
|
+
return SubscribeFeature2;
|
|
132
|
+
})({});
|
|
133
|
+
function Subscribe(topicsOrOptions) {
|
|
134
|
+
let options;
|
|
135
|
+
if (typeof topicsOrOptions === "string") {
|
|
136
|
+
options = {
|
|
137
|
+
topics: [
|
|
138
|
+
topicsOrOptions
|
|
139
|
+
]
|
|
140
|
+
};
|
|
141
|
+
} else if (Array.isArray(topicsOrOptions)) {
|
|
142
|
+
options = {
|
|
143
|
+
topics: topicsOrOptions
|
|
144
|
+
};
|
|
145
|
+
} else {
|
|
146
|
+
options = topicsOrOptions;
|
|
147
|
+
}
|
|
148
|
+
return (target, propertyKey, _descriptor) => {
|
|
149
|
+
const metadata = {
|
|
150
|
+
...options,
|
|
151
|
+
id: options.id ?? String(propertyKey),
|
|
152
|
+
features: options.features ?? [
|
|
153
|
+
"local"
|
|
154
|
+
],
|
|
155
|
+
priority: options.priority ?? Infinity
|
|
156
|
+
};
|
|
157
|
+
Reflect.defineMetadata(SUBSCRIBE_KEY, metadata, target, propertyKey);
|
|
158
|
+
const existing = Reflect.getMetadata(SUBSCRIBE_METHODS_KEY, target.constructor) ?? [];
|
|
159
|
+
existing.push({
|
|
160
|
+
propertyKey: String(propertyKey),
|
|
161
|
+
options: metadata
|
|
162
|
+
});
|
|
163
|
+
Reflect.defineMetadata(SUBSCRIBE_METHODS_KEY, existing, target.constructor);
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
__name(Subscribe, "Subscribe");
|
|
167
|
+
function getSubscriptions(target) {
|
|
168
|
+
return Reflect.getMetadata(SUBSCRIBE_METHODS_KEY, target) ?? [];
|
|
169
|
+
}
|
|
170
|
+
__name(getSubscriptions, "getSubscriptions");
|
|
171
|
+
|
|
172
|
+
// src/topic-event-bus.ts
|
|
173
|
+
function createTopicPayload(topic, payload, headers) {
|
|
174
|
+
const headerMap = headers instanceof Map ? headers : new Map(Object.entries(headers ?? {}));
|
|
175
|
+
return {
|
|
176
|
+
topic,
|
|
177
|
+
payload,
|
|
178
|
+
headers: headerMap
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
__name(createTopicPayload, "createTopicPayload");
|
|
182
|
+
var SubscriptionBuilder = {
|
|
183
|
+
/**
|
|
184
|
+
* Quick factory — 参考 jetlinks Subscription.of(subscriberId, topic, ...features)
|
|
185
|
+
*/
|
|
186
|
+
of(subscriberId, topics, ...features) {
|
|
187
|
+
return {
|
|
188
|
+
subscriberId,
|
|
189
|
+
topics: Array.isArray(topics) ? topics : [
|
|
190
|
+
topics
|
|
191
|
+
],
|
|
192
|
+
features: features.length > 0 ? features : [
|
|
193
|
+
SubscribeFeature.LOCAL
|
|
194
|
+
]
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* Builder pattern — 参考 jetlinks Subscription.builder()
|
|
199
|
+
*/
|
|
200
|
+
builder() {
|
|
201
|
+
const info = {
|
|
202
|
+
subscriberId: "",
|
|
203
|
+
topics: [],
|
|
204
|
+
features: [
|
|
205
|
+
SubscribeFeature.LOCAL
|
|
206
|
+
]
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
subscriberId(id) {
|
|
210
|
+
info.subscriberId = id;
|
|
211
|
+
return this;
|
|
212
|
+
},
|
|
213
|
+
topics(...topics) {
|
|
214
|
+
info.topics = topics;
|
|
215
|
+
return this;
|
|
216
|
+
},
|
|
217
|
+
features(...features) {
|
|
218
|
+
info.features = features;
|
|
219
|
+
return this;
|
|
220
|
+
},
|
|
221
|
+
priority(p) {
|
|
222
|
+
info.priority = p;
|
|
223
|
+
return this;
|
|
224
|
+
},
|
|
225
|
+
build() {
|
|
226
|
+
return {
|
|
227
|
+
...info
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
function normalizeTopic(topic) {
|
|
234
|
+
if (topic.includes("#") || topic.includes("+")) {
|
|
235
|
+
return topic.replace(/#/g, "**").replace(/\+/g, "*");
|
|
236
|
+
}
|
|
237
|
+
return topic;
|
|
238
|
+
}
|
|
239
|
+
__name(normalizeTopic, "normalizeTopic");
|
|
240
|
+
var DefaultTopicEventBus = class _DefaultTopicEventBus {
|
|
241
|
+
static {
|
|
242
|
+
__name(this, "DefaultTopicEventBus");
|
|
243
|
+
}
|
|
244
|
+
logger = new Logger(_DefaultTopicEventBus.name);
|
|
245
|
+
handlersByPattern = /* @__PURE__ */ new Map();
|
|
246
|
+
async publish(topic, payload, headers) {
|
|
247
|
+
if (!topic) {
|
|
248
|
+
throw new Error("Topic must not be empty");
|
|
249
|
+
}
|
|
250
|
+
const topicPayload = createTopicPayload(topic, payload, headers);
|
|
251
|
+
const matchingHandlers = [];
|
|
252
|
+
for (const [pattern, handlers] of this.handlersByPattern) {
|
|
253
|
+
if (matchesTopic(pattern, topic)) {
|
|
254
|
+
matchingHandlers.push(...handlers);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
matchingHandlers.sort((a, b) => a.priority - b.priority);
|
|
258
|
+
for (const { handler } of matchingHandlers) {
|
|
259
|
+
try {
|
|
260
|
+
await handler(topicPayload);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
263
|
+
this.logger.error(`Subscriber error on topic "${topic}": ${errorMsg}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
subscribe(subscriptionOrPattern, handler, priority) {
|
|
268
|
+
if (typeof subscriptionOrPattern === "string") {
|
|
269
|
+
if (!subscriptionOrPattern) {
|
|
270
|
+
throw new Error("Topic pattern must not be empty");
|
|
271
|
+
}
|
|
272
|
+
return this._subscribeSingle(normalizeTopic(subscriptionOrPattern), wrapLegacyHandler(handler), priority ?? Infinity);
|
|
273
|
+
}
|
|
274
|
+
const sub = subscriptionOrPattern;
|
|
275
|
+
if (!sub.subscriberId) {
|
|
276
|
+
throw new Error("SubscriptionInfo.subscriberId must not be empty");
|
|
277
|
+
}
|
|
278
|
+
if (!sub.topics || sub.topics.length === 0) {
|
|
279
|
+
throw new Error("SubscriptionInfo.topics must not be empty");
|
|
280
|
+
}
|
|
281
|
+
const resolvedPriority = sub.priority ?? Infinity;
|
|
282
|
+
const subscriptions = [];
|
|
283
|
+
for (const rawTopic of sub.topics) {
|
|
284
|
+
const topic = normalizeTopic(rawTopic);
|
|
285
|
+
subscriptions.push(this._subscribeSingle(topic, handler, resolvedPriority));
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
unsubscribe: /* @__PURE__ */ __name(() => {
|
|
289
|
+
for (const s of subscriptions) {
|
|
290
|
+
s.unsubscribe();
|
|
291
|
+
}
|
|
292
|
+
}, "unsubscribe")
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
_subscribeSingle(topicPattern, handler, priority) {
|
|
296
|
+
const entry = {
|
|
297
|
+
handler,
|
|
298
|
+
priority
|
|
299
|
+
};
|
|
300
|
+
const handlers = this.handlersByPattern.get(topicPattern) ?? [];
|
|
301
|
+
handlers.push(entry);
|
|
302
|
+
this.handlersByPattern.set(topicPattern, handlers);
|
|
303
|
+
return {
|
|
304
|
+
unsubscribe: /* @__PURE__ */ __name(() => {
|
|
305
|
+
const list = this.handlersByPattern.get(topicPattern);
|
|
306
|
+
if (list) {
|
|
307
|
+
const idx = list.indexOf(entry);
|
|
308
|
+
if (idx >= 0) list.splice(idx, 1);
|
|
309
|
+
if (list.length === 0) this.handlersByPattern.delete(topicPattern);
|
|
310
|
+
}
|
|
311
|
+
}, "unsubscribe")
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Remove all handlers — used during module shutdown to prevent leaks.
|
|
316
|
+
*/
|
|
317
|
+
removeAllListeners() {
|
|
318
|
+
this.handlersByPattern.clear();
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
function wrapLegacyHandler(handler) {
|
|
322
|
+
if (handler.length >= 2) {
|
|
323
|
+
return (msg) => handler(msg.payload, msg.topic);
|
|
324
|
+
}
|
|
325
|
+
return handler;
|
|
326
|
+
}
|
|
327
|
+
__name(wrapLegacyHandler, "wrapLegacyHandler");
|
|
328
|
+
var MATCH_CACHE_MAX_SIZE = 1024;
|
|
329
|
+
var matchCache = /* @__PURE__ */ new Map();
|
|
330
|
+
function matchesTopic(pattern, topic) {
|
|
331
|
+
if (!pattern || !topic) return false;
|
|
332
|
+
const cacheKey = pattern + "\0" + topic;
|
|
333
|
+
const cached = matchCache.get(cacheKey);
|
|
334
|
+
if (cached !== void 0) {
|
|
335
|
+
matchCache.delete(cacheKey);
|
|
336
|
+
matchCache.set(cacheKey, cached);
|
|
337
|
+
return cached;
|
|
338
|
+
}
|
|
339
|
+
const patternParts = pattern.split("/");
|
|
340
|
+
const topicParts = topic.split("/");
|
|
341
|
+
const result = matchParts(patternParts, 0, topicParts, 0);
|
|
342
|
+
if (matchCache.size >= MATCH_CACHE_MAX_SIZE) {
|
|
343
|
+
const firstKey = matchCache.keys().next().value;
|
|
344
|
+
matchCache.delete(firstKey);
|
|
345
|
+
}
|
|
346
|
+
matchCache.set(cacheKey, result);
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
__name(matchesTopic, "matchesTopic");
|
|
350
|
+
function matchParts(pattern, pi, topic, ti) {
|
|
351
|
+
while (pi < pattern.length && ti < topic.length) {
|
|
352
|
+
if (pattern[pi] === "**") {
|
|
353
|
+
if (pi === pattern.length - 1) return true;
|
|
354
|
+
for (let i = ti; i <= topic.length; i++) {
|
|
355
|
+
if (matchParts(pattern, pi + 1, topic, i)) return true;
|
|
356
|
+
}
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
if (pattern[pi] === "*") {
|
|
360
|
+
pi++;
|
|
361
|
+
ti++;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (pattern[pi].includes(",")) {
|
|
365
|
+
const alternatives = pattern[pi].split(",");
|
|
366
|
+
if (!alternatives.includes(topic[ti])) {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
pi++;
|
|
370
|
+
ti++;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (pattern[pi] !== topic[ti]) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
pi++;
|
|
377
|
+
ti++;
|
|
378
|
+
}
|
|
379
|
+
while (pi < pattern.length && pattern[pi] === "**") pi++;
|
|
380
|
+
return pi === pattern.length && ti === topic.length;
|
|
381
|
+
}
|
|
382
|
+
__name(matchParts, "matchParts");
|
|
383
|
+
|
|
384
|
+
// src/async-event.ts
|
|
385
|
+
function createAsyncEvent(payload) {
|
|
386
|
+
const firstTasks = [];
|
|
387
|
+
const asyncTasks = [];
|
|
388
|
+
let _hasListener = false;
|
|
389
|
+
let _firstTransform;
|
|
390
|
+
let _asyncTransform;
|
|
391
|
+
const event = {
|
|
392
|
+
payload,
|
|
393
|
+
timestamp: Date.now(),
|
|
394
|
+
handled: false,
|
|
395
|
+
markHandled() {
|
|
396
|
+
this.handled = true;
|
|
397
|
+
},
|
|
398
|
+
first(fn) {
|
|
399
|
+
_hasListener = true;
|
|
400
|
+
firstTasks.unshift(fn);
|
|
401
|
+
},
|
|
402
|
+
async(fn) {
|
|
403
|
+
_hasListener = true;
|
|
404
|
+
asyncTasks.push(fn);
|
|
405
|
+
},
|
|
406
|
+
transformFirst(mapper) {
|
|
407
|
+
_hasListener = true;
|
|
408
|
+
const prev = _firstTransform;
|
|
409
|
+
_firstTransform = prev ? (p) => mapper(prev(p)) : mapper;
|
|
410
|
+
},
|
|
411
|
+
transform(mapper) {
|
|
412
|
+
_hasListener = true;
|
|
413
|
+
const prev = _asyncTransform;
|
|
414
|
+
_asyncTransform = prev ? (p) => mapper(prev(p)) : mapper;
|
|
415
|
+
},
|
|
416
|
+
get hasListener() {
|
|
417
|
+
return _hasListener;
|
|
418
|
+
},
|
|
419
|
+
async getAsync() {
|
|
420
|
+
let firstChain = (async () => {
|
|
421
|
+
for (const fn of firstTasks) {
|
|
422
|
+
await fn();
|
|
423
|
+
}
|
|
424
|
+
})();
|
|
425
|
+
if (_firstTransform) {
|
|
426
|
+
firstChain = _firstTransform(firstChain);
|
|
427
|
+
}
|
|
428
|
+
await firstChain;
|
|
429
|
+
let asyncChain = (async () => {
|
|
430
|
+
for (const fn of asyncTasks) {
|
|
431
|
+
await fn();
|
|
432
|
+
}
|
|
433
|
+
})();
|
|
434
|
+
if (_asyncTransform) {
|
|
435
|
+
asyncChain = _asyncTransform(asyncChain);
|
|
436
|
+
}
|
|
437
|
+
await asyncChain;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
return event;
|
|
441
|
+
}
|
|
442
|
+
__name(createAsyncEvent, "createAsyncEvent");
|
|
443
|
+
|
|
444
|
+
// src/validate-event-listener.ts
|
|
445
|
+
import { validate } from "class-validator";
|
|
446
|
+
import { plainToInstance } from "class-transformer";
|
|
447
|
+
var CREATE_GROUP = "create";
|
|
448
|
+
var UPDATE_GROUP = "update";
|
|
449
|
+
var SAVE_GROUP = "save";
|
|
450
|
+
var ValidateEventListener = class {
|
|
451
|
+
static {
|
|
452
|
+
__name(this, "ValidateEventListener");
|
|
453
|
+
}
|
|
454
|
+
entityClass;
|
|
455
|
+
options;
|
|
456
|
+
constructor(entityClass, options) {
|
|
457
|
+
this.entityClass = entityClass;
|
|
458
|
+
this.options = options;
|
|
459
|
+
}
|
|
460
|
+
async onEvent(event) {
|
|
461
|
+
if (event.phase !== EntityEventPhase.PREPARE) return;
|
|
462
|
+
if (this.options?.skipTypes?.includes(event.type)) return;
|
|
463
|
+
const groups = this.options?.groups ?? this.resolveGroups(event.type);
|
|
464
|
+
const skipMissing = this.options?.skipMissingProperties ?? event.type === EntityEventType.MODIFY;
|
|
465
|
+
const entities = Array.isArray(event.entity) ? event.entity : [
|
|
466
|
+
event.entity
|
|
467
|
+
];
|
|
468
|
+
for (const entity of entities) {
|
|
469
|
+
if (typeof entity !== "object" || entity === null) continue;
|
|
470
|
+
const instance = plainToInstance(this.entityClass, entity);
|
|
471
|
+
const errors = await validate(instance, {
|
|
472
|
+
skipMissingProperties: skipMissing,
|
|
473
|
+
groups: groups.length > 0 ? groups : void 0,
|
|
474
|
+
whitelist: false,
|
|
475
|
+
forbidNonWhitelisted: false
|
|
476
|
+
});
|
|
477
|
+
if (errors.length > 0) {
|
|
478
|
+
const message = formatValidationErrors(errors);
|
|
479
|
+
event.cancel(`Validation failed: ${message}`);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* 根据事件类型解析默认的校验分组
|
|
486
|
+
*/
|
|
487
|
+
resolveGroups(type) {
|
|
488
|
+
switch (type) {
|
|
489
|
+
case EntityEventType.CREATE:
|
|
490
|
+
return [
|
|
491
|
+
CREATE_GROUP
|
|
492
|
+
];
|
|
493
|
+
case EntityEventType.SAVE:
|
|
494
|
+
return [
|
|
495
|
+
CREATE_GROUP,
|
|
496
|
+
SAVE_GROUP
|
|
497
|
+
];
|
|
498
|
+
case EntityEventType.MODIFY:
|
|
499
|
+
return [
|
|
500
|
+
UPDATE_GROUP
|
|
501
|
+
];
|
|
502
|
+
default:
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
function formatValidationErrors(errors) {
|
|
508
|
+
return errors.map((e) => {
|
|
509
|
+
const constraints = e.constraints ? Object.values(e.constraints).join(", ") : "invalid";
|
|
510
|
+
return `${e.property}: ${constraints}`;
|
|
511
|
+
}).join("; ");
|
|
512
|
+
}
|
|
513
|
+
__name(formatValidationErrors, "formatValidationErrors");
|
|
514
|
+
|
|
515
|
+
// src/enable-entity-event.ts
|
|
516
|
+
var ENTITY_EVENT_CONFIG_KEY = "zucker:event:entityEventConfig";
|
|
517
|
+
var DEFAULT_CONFIG = {
|
|
518
|
+
[EntityEventType.CREATE]: true,
|
|
519
|
+
[EntityEventType.MODIFY]: true,
|
|
520
|
+
[EntityEventType.DELETE]: true,
|
|
521
|
+
[EntityEventType.SAVE]: true,
|
|
522
|
+
[EntityEventType.QUERY]: false
|
|
523
|
+
};
|
|
524
|
+
function EnableEntityEvent(config) {
|
|
525
|
+
return function(target) {
|
|
526
|
+
const merged = {
|
|
527
|
+
...DEFAULT_CONFIG,
|
|
528
|
+
...config
|
|
529
|
+
};
|
|
530
|
+
Reflect.defineMetadata(ENTITY_EVENT_CONFIG_KEY, merged, target);
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
__name(EnableEntityEvent, "EnableEntityEvent");
|
|
534
|
+
function getEntityEventConfig(target) {
|
|
535
|
+
return Reflect.getMetadata(ENTITY_EVENT_CONFIG_KEY, target) ?? DEFAULT_CONFIG;
|
|
536
|
+
}
|
|
537
|
+
__name(getEntityEventConfig, "getEntityEventConfig");
|
|
538
|
+
function isEntityEventEnabled(target, type) {
|
|
539
|
+
const config = getEntityEventConfig(target);
|
|
540
|
+
return config[type] !== false;
|
|
541
|
+
}
|
|
542
|
+
__name(isEntityEventEnabled, "isEntityEventEnabled");
|
|
543
|
+
|
|
544
|
+
// src/entity-event-listener-configure.ts
|
|
545
|
+
import { Injectable } from "@nestjs/common";
|
|
546
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
547
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
548
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
549
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
550
|
+
} else {
|
|
551
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
552
|
+
if (d = decorators[i]) {
|
|
553
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
558
|
+
}
|
|
559
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
560
|
+
function toKey(entityType, eventType, phase) {
|
|
561
|
+
if (!eventType) return entityType;
|
|
562
|
+
return phase ? `${entityType}:${eventType}:${phase}` : `${entityType}:${eventType}`;
|
|
563
|
+
}
|
|
564
|
+
__name(toKey, "toKey");
|
|
565
|
+
var EntityEventListenerConfigure = class {
|
|
566
|
+
static {
|
|
567
|
+
__name(this, "EntityEventListenerConfigure");
|
|
568
|
+
}
|
|
569
|
+
enabled = /* @__PURE__ */ new Map();
|
|
570
|
+
disabled = /* @__PURE__ */ new Map();
|
|
571
|
+
enable(entityType, eventType, ...phases) {
|
|
572
|
+
if (!eventType) {
|
|
573
|
+
const key = toKey(entityType);
|
|
574
|
+
this.enabled.set(key, true);
|
|
575
|
+
this.disabled.delete(key);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const targetPhases = phases.length > 0 ? phases : ALL_EVENT_PHASES;
|
|
579
|
+
for (const phase of targetPhases) {
|
|
580
|
+
const key = toKey(entityType, eventType, phase);
|
|
581
|
+
this.enabled.set(key, true);
|
|
582
|
+
this.disabled.delete(key);
|
|
583
|
+
}
|
|
584
|
+
const typeKey = toKey(entityType, eventType);
|
|
585
|
+
this.enabled.set(typeKey, true);
|
|
586
|
+
this.disabled.delete(typeKey);
|
|
587
|
+
}
|
|
588
|
+
disable(entityType, eventType, ...phases) {
|
|
589
|
+
if (!eventType) {
|
|
590
|
+
const key = toKey(entityType);
|
|
591
|
+
this.disabled.set(key, true);
|
|
592
|
+
this.enabled.delete(key);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const targetPhases = phases.length > 0 ? phases : ALL_EVENT_PHASES;
|
|
596
|
+
for (const phase of targetPhases) {
|
|
597
|
+
const key = toKey(entityType, eventType, phase);
|
|
598
|
+
this.disabled.set(key, true);
|
|
599
|
+
this.enabled.delete(key);
|
|
600
|
+
}
|
|
601
|
+
if (phases.length === 0) {
|
|
602
|
+
const typeKey = toKey(entityType, eventType);
|
|
603
|
+
this.disabled.set(typeKey, true);
|
|
604
|
+
this.enabled.delete(typeKey);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
isEnabled(entityType, eventType, phase) {
|
|
608
|
+
const entityKey = toKey(entityType);
|
|
609
|
+
if (this.disabled.has(entityKey)) return false;
|
|
610
|
+
if (!eventType) {
|
|
611
|
+
return this.enabled.has(entityKey) || !this.disabled.has(entityKey);
|
|
612
|
+
}
|
|
613
|
+
if (phase) {
|
|
614
|
+
const phaseKey = toKey(entityType, eventType, phase);
|
|
615
|
+
if (this.disabled.has(phaseKey)) return false;
|
|
616
|
+
if (this.enabled.has(phaseKey)) return true;
|
|
617
|
+
}
|
|
618
|
+
const typeKey = toKey(entityType, eventType);
|
|
619
|
+
if (this.disabled.has(typeKey)) return false;
|
|
620
|
+
if (this.enabled.has(typeKey)) return true;
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* 清除指定实体的运行时覆盖配置
|
|
625
|
+
*/
|
|
626
|
+
reset(entityType, eventType, phase) {
|
|
627
|
+
const key = toKey(entityType, eventType, phase);
|
|
628
|
+
this.enabled.delete(key);
|
|
629
|
+
this.disabled.delete(key);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* 清除所有运行时覆盖配置
|
|
633
|
+
*/
|
|
634
|
+
resetAll() {
|
|
635
|
+
this.enabled.clear();
|
|
636
|
+
this.disabled.clear();
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
EntityEventListenerConfigure = _ts_decorate([
|
|
640
|
+
Injectable()
|
|
641
|
+
], EntityEventListenerConfigure);
|
|
642
|
+
|
|
643
|
+
// src/entity-event-listener-customizer.ts
|
|
644
|
+
var ENTITY_EVENT_LISTENER_CUSTOMIZER = "ENTITY_EVENT_LISTENER_CUSTOMIZER";
|
|
645
|
+
|
|
646
|
+
// src/entity-event-helper.ts
|
|
647
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
648
|
+
var fireEventStorage = new AsyncLocalStorage();
|
|
649
|
+
function isDoFireEvent(defaultIfEmpty = true) {
|
|
650
|
+
const store = fireEventStorage.getStore();
|
|
651
|
+
return store ? store.flag : defaultIfEmpty;
|
|
652
|
+
}
|
|
653
|
+
__name(isDoFireEvent, "isDoFireEvent");
|
|
654
|
+
async function withoutFireEvent(fn) {
|
|
655
|
+
return fireEventStorage.run({
|
|
656
|
+
flag: false
|
|
657
|
+
}, fn);
|
|
658
|
+
}
|
|
659
|
+
__name(withoutFireEvent, "withoutFireEvent");
|
|
660
|
+
async function withFireEvent(fn) {
|
|
661
|
+
return fireEventStorage.run({
|
|
662
|
+
flag: true
|
|
663
|
+
}, fn);
|
|
664
|
+
}
|
|
665
|
+
__name(withFireEvent, "withFireEvent");
|
|
666
|
+
async function tryFireEvent(task) {
|
|
667
|
+
if (isDoFireEvent(true)) {
|
|
668
|
+
await task();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
__name(tryFireEvent, "tryFireEvent");
|
|
672
|
+
function createSingleEntityEvent(type, phase, entityType, entity) {
|
|
673
|
+
return createEntityEvent(type, phase, entityType, entity);
|
|
674
|
+
}
|
|
675
|
+
__name(createSingleEntityEvent, "createSingleEntityEvent");
|
|
676
|
+
function createBatchEntityEvent(type, phase, entityType, entities) {
|
|
677
|
+
return createEntityEvent(type, phase, entityType, entities);
|
|
678
|
+
}
|
|
679
|
+
__name(createBatchEntityEvent, "createBatchEntityEvent");
|
|
680
|
+
function createModifyEntityEvent(phase, entityType, before, after) {
|
|
681
|
+
if (before.length === 0) {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
return createEntityEvent(EntityEventType.MODIFY, phase, entityType, after, before);
|
|
685
|
+
}
|
|
686
|
+
__name(createModifyEntityEvent, "createModifyEntityEvent");
|
|
687
|
+
function createCreatedEntityEvent(phase, entityType, entities) {
|
|
688
|
+
return createEntityEvent(EntityEventType.CREATE, phase, entityType, entities);
|
|
689
|
+
}
|
|
690
|
+
__name(createCreatedEntityEvent, "createCreatedEntityEvent");
|
|
691
|
+
function createDeletedEntityEvent(phase, entityType, entities) {
|
|
692
|
+
return createEntityEvent(EntityEventType.DELETE, phase, entityType, entities);
|
|
693
|
+
}
|
|
694
|
+
__name(createDeletedEntityEvent, "createDeletedEntityEvent");
|
|
695
|
+
function createSavedEntityEvent(phase, entityType, entities) {
|
|
696
|
+
return createEntityEvent(EntityEventType.SAVE, phase, entityType, entities);
|
|
697
|
+
}
|
|
698
|
+
__name(createSavedEntityEvent, "createSavedEntityEvent");
|
|
699
|
+
|
|
700
|
+
// src/event.module.ts
|
|
701
|
+
import { Module, Global, Inject, Optional } from "@nestjs/common";
|
|
702
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
703
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
704
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
705
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
706
|
+
} else {
|
|
707
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
708
|
+
if (d = decorators[i]) {
|
|
709
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
714
|
+
}
|
|
715
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
716
|
+
function _ts_metadata(metadataKey, metadataValue) {
|
|
717
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
718
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
722
|
+
function _ts_param(paramIndex, decorator) {
|
|
723
|
+
return function(target, key) {
|
|
724
|
+
decorator(target, key, paramIndex);
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
__name(_ts_param, "_ts_param");
|
|
728
|
+
var TOPIC_EVENT_BUS = "TOPIC_EVENT_BUS";
|
|
729
|
+
var ZuckerEventModule = class _ZuckerEventModule {
|
|
730
|
+
static {
|
|
731
|
+
__name(this, "ZuckerEventModule");
|
|
732
|
+
}
|
|
733
|
+
listenerConfigure;
|
|
734
|
+
eventBus;
|
|
735
|
+
customizers;
|
|
736
|
+
constructor(listenerConfigure, eventBus, customizers) {
|
|
737
|
+
this.listenerConfigure = listenerConfigure;
|
|
738
|
+
this.eventBus = eventBus;
|
|
739
|
+
this.customizers = customizers;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* 模块初始化时执行所有 EntityEventListenerCustomizer
|
|
743
|
+
* 通过 EntityEventListenerCustomizer 进行运行时配置
|
|
744
|
+
*/
|
|
745
|
+
onModuleInit() {
|
|
746
|
+
if (this.customizers) {
|
|
747
|
+
for (const customizer of this.customizers) {
|
|
748
|
+
customizer.customize(this.listenerConfigure);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
onModuleDestroy() {
|
|
753
|
+
if (this.eventBus instanceof DefaultTopicEventBus) {
|
|
754
|
+
this.eventBus.removeAllListeners();
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
static forRoot() {
|
|
758
|
+
return {
|
|
759
|
+
module: _ZuckerEventModule,
|
|
760
|
+
global: true,
|
|
761
|
+
providers: [
|
|
762
|
+
{
|
|
763
|
+
provide: TOPIC_EVENT_BUS,
|
|
764
|
+
useClass: DefaultTopicEventBus
|
|
765
|
+
},
|
|
766
|
+
EntityEventListenerConfigure
|
|
767
|
+
],
|
|
768
|
+
exports: [
|
|
769
|
+
TOPIC_EVENT_BUS,
|
|
770
|
+
EntityEventListenerConfigure
|
|
771
|
+
]
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
ZuckerEventModule = _ts_decorate2([
|
|
776
|
+
Global(),
|
|
777
|
+
Module({}),
|
|
778
|
+
_ts_param(1, Inject(TOPIC_EVENT_BUS)),
|
|
779
|
+
_ts_param(2, Optional()),
|
|
780
|
+
_ts_param(2, Inject(ENTITY_EVENT_LISTENER_CUSTOMIZER)),
|
|
781
|
+
_ts_metadata("design:type", Function),
|
|
782
|
+
_ts_metadata("design:paramtypes", [
|
|
783
|
+
typeof EntityEventListenerConfigure === "undefined" ? Object : EntityEventListenerConfigure,
|
|
784
|
+
typeof TopicEventBus === "undefined" ? Object : TopicEventBus,
|
|
785
|
+
Array
|
|
786
|
+
])
|
|
787
|
+
], ZuckerEventModule);
|
|
788
|
+
|
|
789
|
+
// src/event-store.ts
|
|
790
|
+
var EVENT_STORE = "EVENT_STORE";
|
|
791
|
+
|
|
792
|
+
// src/unified-event-bus.ts
|
|
793
|
+
import { Injectable as Injectable2, Optional as Optional2, Inject as Inject2, Logger as Logger2 } from "@nestjs/common";
|
|
794
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
795
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
796
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
797
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
798
|
+
} else {
|
|
799
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
800
|
+
if (d = decorators[i]) {
|
|
801
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
806
|
+
}
|
|
807
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
808
|
+
function _ts_metadata2(metadataKey, metadataValue) {
|
|
809
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
810
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
814
|
+
function _ts_param2(paramIndex, decorator) {
|
|
815
|
+
return function(target, key) {
|
|
816
|
+
decorator(target, key, paramIndex);
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
__name(_ts_param2, "_ts_param");
|
|
820
|
+
var CLUSTER_TRANSPORT = /* @__PURE__ */ Symbol("CLUSTER_TRANSPORT");
|
|
821
|
+
var UnifiedEventBus = class _UnifiedEventBus {
|
|
822
|
+
static {
|
|
823
|
+
__name(this, "UnifiedEventBus");
|
|
824
|
+
}
|
|
825
|
+
cluster;
|
|
826
|
+
logger = new Logger2(_UnifiedEventBus.name);
|
|
827
|
+
localBus;
|
|
828
|
+
constructor(localBus, cluster) {
|
|
829
|
+
this.cluster = cluster;
|
|
830
|
+
this.localBus = localBus;
|
|
831
|
+
if (this.cluster) {
|
|
832
|
+
this.cluster.onRemoteEvent((topic, payload) => {
|
|
833
|
+
this.localBus.publish(topic, payload).catch((err) => {
|
|
834
|
+
this.logger.error(`Failed to replay remote event [${topic}]: ${err}`);
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* 发布事件(本地 + 集群)
|
|
841
|
+
* @param topic 事件主题
|
|
842
|
+
* @param payload 事件负载
|
|
843
|
+
*/
|
|
844
|
+
async publish(topic, payload) {
|
|
845
|
+
await this.localBus.publish(topic, payload);
|
|
846
|
+
if (this.cluster) {
|
|
847
|
+
this.cluster.broadcast(topic, payload).catch((err) => {
|
|
848
|
+
this.logger.warn(`Cluster broadcast failed for [${topic}]: ${err}`);
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* 仅发布到本地(不广播到集群)
|
|
854
|
+
* 适用于访问日志等高频、无需跨节点的事件
|
|
855
|
+
*/
|
|
856
|
+
async publishLocal(topic, payload) {
|
|
857
|
+
await this.localBus.publish(topic, payload);
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* 订阅事件(支持 wildcard 模式)
|
|
861
|
+
* @param pattern 主题匹配模式(支持 * 和 **)
|
|
862
|
+
* @param handler 事件处理函数
|
|
863
|
+
*/
|
|
864
|
+
subscribe(pattern, handler) {
|
|
865
|
+
return this.localBus.subscribe(pattern, handler);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* 发布并等待所有处理完成
|
|
869
|
+
* 用于需要确保所有 listener 执行完毕的场景(如事务内事件)
|
|
870
|
+
*/
|
|
871
|
+
async publishAndWait(topic, payload) {
|
|
872
|
+
await this.localBus.publish(topic, payload);
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
UnifiedEventBus = _ts_decorate3([
|
|
876
|
+
Injectable2(),
|
|
877
|
+
_ts_param2(0, Inject2(TOPIC_EVENT_BUS)),
|
|
878
|
+
_ts_param2(1, Optional2()),
|
|
879
|
+
_ts_param2(1, Inject2(CLUSTER_TRANSPORT)),
|
|
880
|
+
_ts_metadata2("design:type", Function),
|
|
881
|
+
_ts_metadata2("design:paramtypes", [
|
|
882
|
+
typeof TopicEventBus === "undefined" ? Object : TopicEventBus,
|
|
883
|
+
typeof ClusterTransport === "undefined" ? Object : ClusterTransport
|
|
884
|
+
])
|
|
885
|
+
], UnifiedEventBus);
|
|
886
|
+
|
|
887
|
+
// src/operation-hooks.ts
|
|
888
|
+
import { Logger as Logger3 } from "@nestjs/common";
|
|
889
|
+
function matchesOperation(pattern, operation) {
|
|
890
|
+
if (pattern === "*") return true;
|
|
891
|
+
if (pattern === operation) return true;
|
|
892
|
+
if (pattern.endsWith(".*")) {
|
|
893
|
+
const prefix = pattern.slice(0, -2);
|
|
894
|
+
return operation.startsWith(prefix + ".") || operation === prefix;
|
|
895
|
+
}
|
|
896
|
+
if (pattern.endsWith(".**")) {
|
|
897
|
+
const prefix = pattern.slice(0, -3);
|
|
898
|
+
return operation.startsWith(prefix + ".") || operation === prefix;
|
|
899
|
+
}
|
|
900
|
+
return false;
|
|
901
|
+
}
|
|
902
|
+
__name(matchesOperation, "matchesOperation");
|
|
903
|
+
var OperationHookRegistry = class _OperationHookRegistry {
|
|
904
|
+
static {
|
|
905
|
+
__name(this, "OperationHookRegistry");
|
|
906
|
+
}
|
|
907
|
+
logger = new Logger3(_OperationHookRegistry.name);
|
|
908
|
+
hooks = [];
|
|
909
|
+
/**
|
|
910
|
+
* 注册操作 hook
|
|
911
|
+
* @param operation 操作名称模式(支持 * 和 ** 通配符)
|
|
912
|
+
* @param phase 执行阶段
|
|
913
|
+
* @param handler hook 处理函数
|
|
914
|
+
* @param options 可选配置
|
|
915
|
+
* @returns 取消注册的函数
|
|
916
|
+
*/
|
|
917
|
+
register(operation, phase, handler, options) {
|
|
918
|
+
const hook = {
|
|
919
|
+
operation,
|
|
920
|
+
phase,
|
|
921
|
+
handler,
|
|
922
|
+
priority: options?.priority ?? 0,
|
|
923
|
+
name: options?.name
|
|
924
|
+
};
|
|
925
|
+
this.hooks.push(hook);
|
|
926
|
+
this.hooks.sort((a, b) => b.priority - a.priority);
|
|
927
|
+
return () => {
|
|
928
|
+
const idx = this.hooks.indexOf(hook);
|
|
929
|
+
if (idx !== -1) this.hooks.splice(idx, 1);
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* 创建操作上下文
|
|
934
|
+
*/
|
|
935
|
+
createContext(operation, data) {
|
|
936
|
+
return {
|
|
937
|
+
operation,
|
|
938
|
+
data,
|
|
939
|
+
cancelled: false,
|
|
940
|
+
cancelReason: void 0,
|
|
941
|
+
result: void 0,
|
|
942
|
+
attributes: /* @__PURE__ */ new Map(),
|
|
943
|
+
cancel(reason) {
|
|
944
|
+
this.cancelled = true;
|
|
945
|
+
this.cancelReason = reason;
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* 执行 before 阶段的所有 hook
|
|
951
|
+
* 如果任意 hook 取消了操作,后续 hook 不再执行
|
|
952
|
+
*/
|
|
953
|
+
async executeBefore(ctx) {
|
|
954
|
+
await this.executePhase(ctx, "before");
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* 执行 after 阶段的所有 hook
|
|
958
|
+
*/
|
|
959
|
+
async executeAfter(ctx) {
|
|
960
|
+
await this.executePhase(ctx, "after");
|
|
961
|
+
}
|
|
962
|
+
async executePhase(ctx, phase) {
|
|
963
|
+
const matching = this.hooks.filter((h) => h.phase === phase && matchesOperation(h.operation, ctx.operation));
|
|
964
|
+
for (const hook of matching) {
|
|
965
|
+
if (phase === "before" && ctx.cancelled) break;
|
|
966
|
+
try {
|
|
967
|
+
await hook.handler(ctx);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
const hookName = hook.name ?? hook.operation;
|
|
970
|
+
this.logger.error(`Hook "${hookName}" (${phase}) failed for operation "${ctx.operation}": ${error}`);
|
|
971
|
+
if (phase === "before") {
|
|
972
|
+
ctx.cancel(`Hook "${hookName}" failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* 获取已注册的 hook 数量
|
|
979
|
+
*/
|
|
980
|
+
get size() {
|
|
981
|
+
return this.hooks.length;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* 清除所有 hook
|
|
985
|
+
*/
|
|
986
|
+
clear() {
|
|
987
|
+
this.hooks.length = 0;
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
export {
|
|
991
|
+
ALL_EVENT_PHASES,
|
|
992
|
+
CLUSTER_TRANSPORT,
|
|
993
|
+
CREATE_GROUP,
|
|
994
|
+
CompositeEventListener,
|
|
995
|
+
CreatorEventListener,
|
|
996
|
+
DefaultTopicEventBus,
|
|
997
|
+
ENTITY_EVENT_CONFIG_KEY,
|
|
998
|
+
ENTITY_EVENT_LISTENER_CUSTOMIZER,
|
|
999
|
+
EVENT_STORE,
|
|
1000
|
+
EnableEntityEvent,
|
|
1001
|
+
EntityEventListenerConfigure,
|
|
1002
|
+
EntityEventPhase,
|
|
1003
|
+
EntityEventType,
|
|
1004
|
+
OperationHookRegistry,
|
|
1005
|
+
SAVE_GROUP,
|
|
1006
|
+
SUBSCRIBE_KEY,
|
|
1007
|
+
SUBSCRIBE_METHODS_KEY,
|
|
1008
|
+
Subscribe,
|
|
1009
|
+
SubscribeFeature,
|
|
1010
|
+
SubscriptionBuilder,
|
|
1011
|
+
TOPIC_EVENT_BUS,
|
|
1012
|
+
UPDATE_GROUP,
|
|
1013
|
+
UnifiedEventBus,
|
|
1014
|
+
ValidateEventListener,
|
|
1015
|
+
ZuckerEventModule,
|
|
1016
|
+
createAsyncEvent,
|
|
1017
|
+
createBatchEntityEvent,
|
|
1018
|
+
createCreatedEntityEvent,
|
|
1019
|
+
createDeletedEntityEvent,
|
|
1020
|
+
createEntityEvent,
|
|
1021
|
+
createModifyEntityEvent,
|
|
1022
|
+
createSavedEntityEvent,
|
|
1023
|
+
createSingleEntityEvent,
|
|
1024
|
+
createTopicPayload,
|
|
1025
|
+
getEntityEventConfig,
|
|
1026
|
+
getEntityEventName,
|
|
1027
|
+
getSubscriptions,
|
|
1028
|
+
isDoFireEvent,
|
|
1029
|
+
isEntityEventEnabled,
|
|
1030
|
+
matchesTopic,
|
|
1031
|
+
tryFireEvent,
|
|
1032
|
+
withFireEvent,
|
|
1033
|
+
withoutFireEvent
|
|
1034
|
+
};
|