@zucker-framework/rule-engine 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/dist/index.js ADDED
@@ -0,0 +1,1648 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ AbstractTaskExecutor: () => AbstractTaskExecutor,
35
+ BranchEvaluator: () => BranchEvaluator,
36
+ DataMappingAction: () => DataMappingAction,
37
+ DelayAction: () => DelayAction,
38
+ EventTrigger: () => EventTrigger,
39
+ ExpressionEvaluator: () => ExpressionEvaluator,
40
+ HttpAction: () => HttpAction,
41
+ NotifyAction: () => NotifyAction,
42
+ RuleActionType: () => RuleActionType,
43
+ RuleConditionType: () => RuleConditionType,
44
+ RuleConstants: () => RuleConstants,
45
+ RuleData: () => RuleData,
46
+ RuleEngineService: () => RuleEngineService,
47
+ RuleInstanceState: () => RuleInstanceState,
48
+ RuleModel: () => RuleModel,
49
+ RuleTestRunner: () => RuleTestRunner,
50
+ RuleTriggerType: () => RuleTriggerType,
51
+ RuleVersionManager: () => RuleVersionManager,
52
+ ScriptAction: () => ScriptAction,
53
+ TaskExecutorRegistry: () => TaskExecutorRegistry,
54
+ TaskState: () => TaskState,
55
+ TermsEvaluator: () => TermsEvaluator,
56
+ TimerTrigger: () => TimerTrigger,
57
+ ZuckerRuleEngineModule: () => ZuckerRuleEngineModule,
58
+ createShakeLimit: () => createShakeLimit
59
+ });
60
+ module.exports = __toCommonJS(index_exports);
61
+
62
+ // src/rule-engine.service.ts
63
+ var import_common9 = require("@nestjs/common");
64
+ var import_event = require("@zucker-framework/event");
65
+
66
+ // src/condition/terms-evaluator.ts
67
+ var OPERATORS = {
68
+ eq: /* @__PURE__ */ __name((a, b) => a === b, "eq"),
69
+ neq: /* @__PURE__ */ __name((a, b) => a !== b, "neq"),
70
+ gt: /* @__PURE__ */ __name((a, b) => Number(a) > Number(b), "gt"),
71
+ gte: /* @__PURE__ */ __name((a, b) => Number(a) >= Number(b), "gte"),
72
+ lt: /* @__PURE__ */ __name((a, b) => Number(a) < Number(b), "lt"),
73
+ lte: /* @__PURE__ */ __name((a, b) => Number(a) <= Number(b), "lte"),
74
+ like: /* @__PURE__ */ __name((a, b) => typeof a === "string" && typeof b === "string" && a.includes(b), "like"),
75
+ nlike: /* @__PURE__ */ __name((a, b) => typeof a === "string" && typeof b === "string" && !a.includes(b), "nlike"),
76
+ in: /* @__PURE__ */ __name((a, b) => Array.isArray(b) && b.includes(a), "in"),
77
+ nin: /* @__PURE__ */ __name((a, b) => Array.isArray(b) && !b.includes(a), "nin"),
78
+ isnull: /* @__PURE__ */ __name((a) => a === null || a === void 0, "isnull"),
79
+ notnull: /* @__PURE__ */ __name((a) => a !== null && a !== void 0, "notnull"),
80
+ between: /* @__PURE__ */ __name((a, b) => {
81
+ if (!Array.isArray(b) || b.length !== 2) return false;
82
+ const v = Number(a);
83
+ return v >= Number(b[0]) && v <= Number(b[1]);
84
+ }, "between"),
85
+ // 新增操作符 — 对齐 jetlinks TermType 扩展
86
+ empty: /* @__PURE__ */ __name((a) => a === null || a === void 0 || a === "" || Array.isArray(a) && a.length === 0, "empty"),
87
+ nempty: /* @__PURE__ */ __name((a) => a !== null && a !== void 0 && a !== "" && !(Array.isArray(a) && a.length === 0), "nempty"),
88
+ starts_with: /* @__PURE__ */ __name((a, b) => typeof a === "string" && typeof b === "string" && a.startsWith(b), "starts_with"),
89
+ ends_with: /* @__PURE__ */ __name((a, b) => typeof a === "string" && typeof b === "string" && a.endsWith(b), "ends_with"),
90
+ contains_all: /* @__PURE__ */ __name((a, b) => Array.isArray(a) && Array.isArray(b) && b.every((item) => a.includes(item)), "contains_all"),
91
+ contains_any: /* @__PURE__ */ __name((a, b) => Array.isArray(a) && Array.isArray(b) && b.some((item) => a.includes(item)), "contains_any")
92
+ };
93
+ var TermsEvaluator = class {
94
+ static {
95
+ __name(this, "TermsEvaluator");
96
+ }
97
+ /**
98
+ * 注册自定义操作符 — 对齐 jetlinks 可扩展的 TermType
99
+ */
100
+ registerOperator(name, fn) {
101
+ OPERATORS[name] = fn;
102
+ }
103
+ /**
104
+ * 获取已注册的所有操作符名称
105
+ */
106
+ getOperatorNames() {
107
+ return Object.keys(OPERATORS);
108
+ }
109
+ evaluate(data, condition) {
110
+ return this.evaluateTerms(data, condition.terms, condition.type);
111
+ }
112
+ evaluateTerms(data, terms, type) {
113
+ const results = terms.map((term) => {
114
+ if (term.terms && term.terms.length > 0) {
115
+ return this.evaluateTerms(data, term.terms, term.termsType ?? "and");
116
+ }
117
+ const fieldValue = getNestedValue(data, term.column);
118
+ const operator = OPERATORS[term.termType];
119
+ if (!operator) {
120
+ throw new Error(`Unknown term type: ${term.termType}`);
121
+ }
122
+ return operator(fieldValue, term.value);
123
+ });
124
+ return type === "and" ? results.every(Boolean) : results.some(Boolean);
125
+ }
126
+ };
127
+ function getNestedValue(data, path) {
128
+ if (!path.includes(".")) {
129
+ return data[path];
130
+ }
131
+ const parts = path.split(".");
132
+ let current = data;
133
+ for (const part of parts) {
134
+ if (current === null || current === void 0) return void 0;
135
+ if (typeof current !== "object") return void 0;
136
+ current = current[part];
137
+ }
138
+ return current;
139
+ }
140
+ __name(getNestedValue, "getNestedValue");
141
+
142
+ // src/condition/expression-evaluator.ts
143
+ var import_node_vm = __toESM(require("vm"));
144
+ var BLOCKED_GLOBALS = [
145
+ "process",
146
+ "require",
147
+ "module",
148
+ "exports",
149
+ "__filename",
150
+ "__dirname",
151
+ "globalThis",
152
+ "global",
153
+ "root",
154
+ "GLOBAL",
155
+ "setTimeout",
156
+ "setInterval",
157
+ "setImmediate",
158
+ "clearTimeout",
159
+ "clearInterval",
160
+ "clearImmediate",
161
+ "queueMicrotask"
162
+ ];
163
+ var ExpressionEvaluator = class _ExpressionEvaluator {
164
+ static {
165
+ __name(this, "ExpressionEvaluator");
166
+ }
167
+ /** Default timeout in milliseconds for expression evaluation */
168
+ static EVAL_TIMEOUT_MS = 1e3;
169
+ evaluate(data, condition) {
170
+ const sandbox = {
171
+ ...data
172
+ };
173
+ for (const key of BLOCKED_GLOBALS) {
174
+ sandbox[key] = void 0;
175
+ }
176
+ const context = import_node_vm.default.createContext(sandbox);
177
+ import_node_vm.default.runInContext(`(function() {
178
+ 'use strict';
179
+ var ObjProto = Object.getPrototypeOf({});
180
+ var FuncProto = Object.getPrototypeOf(function(){});
181
+ Object.defineProperty(ObjProto, 'constructor', { value: undefined, writable: false, configurable: false });
182
+ Object.defineProperty(FuncProto, 'constructor', { value: undefined, writable: false, configurable: false });
183
+ Object.freeze(ObjProto);
184
+ Object.freeze(FuncProto);
185
+ })()`, context, {
186
+ timeout: _ExpressionEvaluator.EVAL_TIMEOUT_MS
187
+ });
188
+ const wrappedExpression = `'use strict'; (${condition.expression})`;
189
+ const result = import_node_vm.default.runInContext(wrappedExpression, context, {
190
+ timeout: _ExpressionEvaluator.EVAL_TIMEOUT_MS,
191
+ filename: "expression-eval.js"
192
+ });
193
+ return Boolean(result);
194
+ }
195
+ };
196
+
197
+ // src/condition/branch-evaluator.ts
198
+ var import_common = require("@nestjs/common");
199
+
200
+ // src/core/shake-limit.ts
201
+ function createShakeLimit(config) {
202
+ if (!config.enabled) {
203
+ return {
204
+ tryAcquire: /* @__PURE__ */ __name(() => true, "tryAcquire"),
205
+ reset: /* @__PURE__ */ __name(() => {
206
+ }, "reset"),
207
+ getCurrentCount: /* @__PURE__ */ __name(() => 0, "getCurrentCount")
208
+ };
209
+ }
210
+ const safeTime = Math.max(1, config.time);
211
+ const safeThreshold = Math.max(1, config.threshold);
212
+ let count = 0;
213
+ let windowStart = Date.now();
214
+ let alarmFired = false;
215
+ return {
216
+ tryAcquire() {
217
+ const now = Date.now();
218
+ if (now - windowStart >= safeTime) {
219
+ windowStart = now;
220
+ count = 1;
221
+ alarmFired = false;
222
+ return !config.alarmFirst;
223
+ }
224
+ count++;
225
+ if (config.alarmFirst) {
226
+ if (count >= safeThreshold && !alarmFired) {
227
+ alarmFired = true;
228
+ return true;
229
+ }
230
+ return false;
231
+ }
232
+ return count <= safeThreshold;
233
+ },
234
+ reset() {
235
+ count = 0;
236
+ windowStart = Date.now();
237
+ alarmFired = false;
238
+ },
239
+ getCurrentCount() {
240
+ return count;
241
+ }
242
+ };
243
+ }
244
+ __name(createShakeLimit, "createShakeLimit");
245
+
246
+ // src/condition/branch-evaluator.ts
247
+ var BranchEvaluator = class _BranchEvaluator {
248
+ static {
249
+ __name(this, "BranchEvaluator");
250
+ }
251
+ logger = new import_common.Logger(_BranchEvaluator.name);
252
+ termsEvaluator = new TermsEvaluator();
253
+ expressionEvaluator = new ExpressionEvaluator();
254
+ shakeLimitMap = /* @__PURE__ */ new Map();
255
+ /**
256
+ * Evaluate branches against data.
257
+ * Implements if/else-if/else semantics with executeAnyway support.
258
+ *
259
+ * Returns all branches that should fire (respecting executeAnyway and
260
+ * if/else-if ordering).
261
+ */
262
+ evaluate(branches, data) {
263
+ const results = [];
264
+ let previousMatched = false;
265
+ for (let i = 0; i < branches.length; i++) {
266
+ const branch = branches[i];
267
+ if (previousMatched && !branch.executeAnyway) {
268
+ continue;
269
+ }
270
+ const conditionsMet = this.evaluateConditions(branch.when, data);
271
+ if (conditionsMet) {
272
+ if (branch.shakeLimit?.enabled) {
273
+ if (!this.shakeLimitMap.has(i)) {
274
+ this.shakeLimitMap.set(i, createShakeLimit(branch.shakeLimit));
275
+ }
276
+ const limiter = this.shakeLimitMap.get(i);
277
+ if (!limiter.tryAcquire()) {
278
+ this.logger.debug(`Branch ${i} suppressed by shake limit`);
279
+ continue;
280
+ }
281
+ }
282
+ results.push({
283
+ branchIndex: i,
284
+ branchId: branch.id,
285
+ matched: true,
286
+ actionGroups: branch.then
287
+ });
288
+ previousMatched = true;
289
+ }
290
+ }
291
+ return results;
292
+ }
293
+ evaluateConditions(conditions, data) {
294
+ if (!conditions || conditions.length === 0) {
295
+ return true;
296
+ }
297
+ return conditions.every((condition) => {
298
+ if (condition.type === "terms") {
299
+ return this.termsEvaluator.evaluate(data, condition.config);
300
+ }
301
+ if (condition.type === "expression") {
302
+ return this.expressionEvaluator.evaluate(data, condition.config);
303
+ }
304
+ return false;
305
+ });
306
+ }
307
+ /** Reset all shake limiters. */
308
+ resetShakeLimits() {
309
+ for (const limiter of this.shakeLimitMap.values()) {
310
+ limiter.reset();
311
+ }
312
+ }
313
+ };
314
+
315
+ // src/action/notify-action.ts
316
+ var import_common2 = require("@nestjs/common");
317
+ var NotifyAction = class _NotifyAction {
318
+ static {
319
+ __name(this, "NotifyAction");
320
+ }
321
+ logger = new import_common2.Logger(_NotifyAction.name);
322
+ sender;
323
+ /**
324
+ * 设置通知发送器(由模块初始化时注入)
325
+ */
326
+ setSender(sender) {
327
+ this.sender = sender;
328
+ }
329
+ async execute(config, data) {
330
+ this.validate(config);
331
+ const resolvedVariables = {};
332
+ if (config.variables) {
333
+ for (const [key, path] of Object.entries(config.variables)) {
334
+ resolvedVariables[key] = this.resolveVariable(path, data);
335
+ }
336
+ }
337
+ const title = config.title ? this.interpolate(config.title, data) : void 0;
338
+ const message = config.message ? this.interpolate(config.message, data) : void 0;
339
+ const event = {
340
+ type: config.notifyType,
341
+ templateId: config.templateId,
342
+ recipients: config.recipients,
343
+ title,
344
+ message,
345
+ data,
346
+ resolvedVariables
347
+ };
348
+ this.logger.log(`Sending notification: type=${config.notifyType}, recipients=${config.recipients.join(",")}`);
349
+ if (this.sender) {
350
+ await this.sender(event);
351
+ } else {
352
+ this.logger.debug(`Notification event (no sender configured): ${JSON.stringify(event)}`);
353
+ }
354
+ }
355
+ /**
356
+ * 验证通知配置
357
+ */
358
+ validate(config) {
359
+ if (!config.notifyType || config.notifyType.trim().length === 0) {
360
+ throw new Error("Notify type is required");
361
+ }
362
+ if (!config.recipients || config.recipients.length === 0) {
363
+ throw new Error("At least one recipient is required");
364
+ }
365
+ }
366
+ /**
367
+ * 解析变量路径 — 对齐 jetlinks VariableSource 解析
368
+ */
369
+ resolveVariable(path, data) {
370
+ if (!path.includes(".")) {
371
+ return data[path];
372
+ }
373
+ const parts = path.split(".");
374
+ let current = data;
375
+ for (const part of parts) {
376
+ if (current === null || current === void 0) return void 0;
377
+ if (typeof current !== "object") return void 0;
378
+ current = current[part];
379
+ }
380
+ return current;
381
+ }
382
+ interpolate(template, data) {
383
+ return template.replace(/\$\{([^}]+)}/g, (_, path) => {
384
+ const value = this.resolveVariable(path.trim(), data);
385
+ return value !== void 0 && value !== null ? String(value) : "";
386
+ });
387
+ }
388
+ };
389
+
390
+ // src/action/http-action.ts
391
+ var import_common3 = require("@nestjs/common");
392
+ var HttpAction = class _HttpAction {
393
+ static {
394
+ __name(this, "HttpAction");
395
+ }
396
+ logger = new import_common3.Logger(_HttpAction.name);
397
+ async execute(config, data) {
398
+ this.validate(config);
399
+ const url = this.interpolate(config.url, data);
400
+ const body = config.body ? JSON.parse(this.interpolate(JSON.stringify(config.body), data)) : void 0;
401
+ const headers = {
402
+ "Content-Type": "application/json"
403
+ };
404
+ if (config.headers) {
405
+ for (const [key, value] of Object.entries(config.headers)) {
406
+ headers[key] = this.interpolate(value, data);
407
+ }
408
+ }
409
+ this.logger.log(`HTTP action: ${config.method} ${url}`);
410
+ const maxAttempts = (config.retryCount ?? 0) + 1;
411
+ const retryDelay = config.retryDelay ?? 1e3;
412
+ let lastError = null;
413
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
414
+ try {
415
+ return await this.doRequest(config, url, headers, body);
416
+ } catch (error) {
417
+ lastError = error instanceof Error ? error : new Error(String(error));
418
+ if (attempt < maxAttempts) {
419
+ this.logger.warn(`HTTP action attempt ${attempt} failed, retrying in ${retryDelay}ms: ${lastError.message}`);
420
+ await this.delay(retryDelay);
421
+ }
422
+ }
423
+ }
424
+ throw lastError ?? new Error("HTTP action failed");
425
+ }
426
+ /**
427
+ * 验证配置有效性
428
+ */
429
+ validate(config) {
430
+ if (!config.url || config.url.trim().length === 0) {
431
+ throw new Error("HTTP action URL is required");
432
+ }
433
+ const validMethods = [
434
+ "GET",
435
+ "POST",
436
+ "PUT",
437
+ "DELETE",
438
+ "PATCH"
439
+ ];
440
+ if (!validMethods.includes(config.method)) {
441
+ throw new Error(`Invalid HTTP method: ${config.method}`);
442
+ }
443
+ }
444
+ async doRequest(config, url, headers, body) {
445
+ const controller = new AbortController();
446
+ const timeout = config.timeout ?? 3e4;
447
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
448
+ try {
449
+ const response = await fetch(url, {
450
+ method: config.method,
451
+ headers,
452
+ body: body ? JSON.stringify(body) : void 0,
453
+ signal: controller.signal
454
+ });
455
+ if (!response.ok) {
456
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
457
+ }
458
+ const responseData = await response.text();
459
+ try {
460
+ return JSON.parse(responseData);
461
+ } catch {
462
+ return responseData;
463
+ }
464
+ } finally {
465
+ clearTimeout(timeoutId);
466
+ }
467
+ }
468
+ /**
469
+ * 模板插值 — 对齐 jetlinks TemplateParser / applyValueExpression
470
+ * 支持 ${key} 和嵌套路径 ${a.b.c}
471
+ */
472
+ interpolate(template, data) {
473
+ return template.replace(/\$\{([^}]+)}/g, (_, path) => {
474
+ const value = this.getNestedValue(data, path.trim());
475
+ return value !== void 0 && value !== null ? String(value) : "";
476
+ });
477
+ }
478
+ getNestedValue(data, path) {
479
+ const parts = path.split(".");
480
+ let current = data;
481
+ for (const part of parts) {
482
+ if (current === null || current === void 0) return void 0;
483
+ if (typeof current !== "object") return void 0;
484
+ current = current[part];
485
+ }
486
+ return current;
487
+ }
488
+ delay(ms) {
489
+ return new Promise((resolve) => setTimeout(resolve, ms));
490
+ }
491
+ };
492
+
493
+ // src/action/script-action.ts
494
+ var import_common4 = require("@nestjs/common");
495
+ var import_script = require("@zucker-framework/script");
496
+ var ScriptAction = class _ScriptAction {
497
+ static {
498
+ __name(this, "ScriptAction");
499
+ }
500
+ logger = new import_common4.Logger(_ScriptAction.name);
501
+ async execute(config, data) {
502
+ this.validate(config);
503
+ this.logger.debug("Executing script action");
504
+ const sandbox = (0, import_script.createSandbox)({
505
+ timeout: config.timeout ?? 5e3,
506
+ allowedModules: [],
507
+ name: "rule-script"
508
+ });
509
+ let messageHandler = null;
510
+ const handler = {
511
+ onMessage(fn) {
512
+ messageHandler = fn;
513
+ }
514
+ };
515
+ const result = sandbox.execute(config.script, {
516
+ data,
517
+ context: data,
518
+ handler
519
+ });
520
+ if (messageHandler) {
521
+ const handlerResult = messageHandler(data);
522
+ return this.normalizeResult(handlerResult);
523
+ }
524
+ return this.normalizeResult(result);
525
+ }
526
+ /**
527
+ * 验证脚本配置 — 对齐 jetlinks executor validate()
528
+ */
529
+ validate(config) {
530
+ if (!config.script || config.script.trim().length === 0) {
531
+ throw new Error("Script content is required");
532
+ }
533
+ const lang = config.lang ?? "js";
534
+ if (lang !== "js" && lang !== "javascript") {
535
+ throw new Error(`Unsupported script language: ${lang}`);
536
+ }
537
+ }
538
+ /**
539
+ * 规范化脚本返回值 — 对齐 jetlinks convertToJavaType
540
+ * 确保返回的是可序列化的纯对象
541
+ */
542
+ normalizeResult(result) {
543
+ if (result === void 0 || result === null) {
544
+ return null;
545
+ }
546
+ if (typeof result === "object" && result !== null) {
547
+ if (result instanceof Map) {
548
+ const obj = {};
549
+ for (const [key, value] of result) {
550
+ obj[String(key)] = this.normalizeResult(value);
551
+ }
552
+ return obj;
553
+ }
554
+ if (result instanceof Set) {
555
+ return Array.from(result).map((v) => this.normalizeResult(v));
556
+ }
557
+ }
558
+ return result;
559
+ }
560
+ };
561
+
562
+ // src/action/delay-action.ts
563
+ var import_common5 = require("@nestjs/common");
564
+ function toMs(value, unit) {
565
+ switch (unit) {
566
+ case "milliseconds":
567
+ return value;
568
+ case "seconds":
569
+ return value * 1e3;
570
+ case "minutes":
571
+ return value * 6e4;
572
+ case "hours":
573
+ return value * 36e5;
574
+ default:
575
+ return value;
576
+ }
577
+ }
578
+ __name(toMs, "toMs");
579
+ var DelayAction = class _DelayAction {
580
+ static {
581
+ __name(this, "DelayAction");
582
+ }
583
+ logger = new import_common5.Logger(_DelayAction.name);
584
+ /**
585
+ * Execute the delay according to the configured pause type.
586
+ * For 'delayv', the delay duration comes from `data.delay` (string parseable as ms).
587
+ * Returns the data after the delay completes.
588
+ */
589
+ async execute(config, data) {
590
+ const delayMs = this.calculateDelay(config, data);
591
+ if (delayMs > 0) {
592
+ this.logger.debug(`Delaying execution for ${delayMs}ms (type=${config.pauseType})`);
593
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
594
+ }
595
+ return data;
596
+ }
597
+ calculateDelay(config, data) {
598
+ switch (config.pauseType) {
599
+ case "delay":
600
+ return toMs(config.timeout ?? 0, config.timeoutUnits ?? "seconds");
601
+ case "delayv": {
602
+ const raw = data["delay"];
603
+ if (raw === void 0 || raw === null) return 0;
604
+ const parsed = Number(raw);
605
+ return Number.isFinite(parsed) ? parsed : 0;
606
+ }
607
+ case "random": {
608
+ const min = toMs(config.randomFirst ?? 0, config.randomUnits ?? "seconds");
609
+ const max = toMs(config.randomLast ?? 0, config.randomUnits ?? "seconds");
610
+ return min + Math.random() * (max - min);
611
+ }
612
+ case "rate":
613
+ return 0;
614
+ default:
615
+ return 0;
616
+ }
617
+ }
618
+ };
619
+
620
+ // src/action/data-mapping-action.ts
621
+ var import_common6 = require("@nestjs/common");
622
+ var TYPE_CONVERTERS = {
623
+ string: /* @__PURE__ */ __name((v) => String(v), "string"),
624
+ number: /* @__PURE__ */ __name((v) => Number(v), "number"),
625
+ integer: /* @__PURE__ */ __name((v) => {
626
+ const n = Number(v);
627
+ return Number.isFinite(n) ? Math.trunc(n) : 0;
628
+ }, "integer"),
629
+ decimal: /* @__PURE__ */ __name((v) => Number(v), "decimal"),
630
+ boolean: /* @__PURE__ */ __name((v) => {
631
+ if (typeof v === "string") {
632
+ return v === "true" || v === "1";
633
+ }
634
+ return Boolean(v);
635
+ }, "boolean"),
636
+ date: /* @__PURE__ */ __name((v) => {
637
+ if (v instanceof Date) return v;
638
+ const d = new Date(v);
639
+ return Number.isNaN(d.getTime()) ? null : d;
640
+ }, "date")
641
+ };
642
+ var DataMappingAction = class _DataMappingAction {
643
+ static {
644
+ __name(this, "DataMappingAction");
645
+ }
646
+ logger = new import_common6.Logger(_DataMappingAction.name);
647
+ execute(config, data) {
648
+ if (Array.isArray(data)) {
649
+ return data.map((item) => this.doMapping(config, item));
650
+ }
651
+ return this.doMapping(config, data);
652
+ }
653
+ doMapping(config, source) {
654
+ const result = config.keepSourceData ? {
655
+ ...source
656
+ } : {};
657
+ for (const mapping of config.mappings) {
658
+ let value = this.resolveSource(mapping.source, source);
659
+ if (value === void 0 || value === null) continue;
660
+ if (mapping.type) {
661
+ const converter = TYPE_CONVERTERS[mapping.type.toLowerCase()];
662
+ if (converter) {
663
+ value = converter(value);
664
+ }
665
+ }
666
+ result[mapping.target] = value;
667
+ }
668
+ return result;
669
+ }
670
+ resolveSource(source, data) {
671
+ if (data[source] !== void 0) {
672
+ return data[source];
673
+ }
674
+ if (source.includes("${")) {
675
+ const interpolated = source.replace(/\$\{(\w+)}/g, (_, key) => {
676
+ const val = data[key];
677
+ return val !== void 0 ? String(val) : "";
678
+ });
679
+ return interpolated;
680
+ }
681
+ if (source.includes(".")) {
682
+ return this.getNestedValue(data, source);
683
+ }
684
+ return void 0;
685
+ }
686
+ getNestedValue(obj, path) {
687
+ const parts = path.split(".");
688
+ let current = obj;
689
+ for (const part of parts) {
690
+ if (current === null || current === void 0) return void 0;
691
+ if (typeof current !== "object") return void 0;
692
+ current = current[part];
693
+ }
694
+ return current;
695
+ }
696
+ };
697
+
698
+ // src/trigger/event-trigger.ts
699
+ var import_common7 = require("@nestjs/common");
700
+ var EventTrigger = class _EventTrigger {
701
+ static {
702
+ __name(this, "EventTrigger");
703
+ }
704
+ logger = new import_common7.Logger(_EventTrigger.name);
705
+ subscription;
706
+ start(config, eventBus, handler) {
707
+ this.logger.log(`Event trigger started: topic=${config.topic}`);
708
+ this.subscription = eventBus.subscribe(config.topic, (msg) => handler(msg.payload));
709
+ }
710
+ stop() {
711
+ this.subscription?.unsubscribe();
712
+ this.subscription = void 0;
713
+ this.logger.log("Event trigger stopped");
714
+ }
715
+ isRunning() {
716
+ return this.subscription !== void 0;
717
+ }
718
+ };
719
+
720
+ // src/trigger/timer-trigger.ts
721
+ var import_common8 = require("@nestjs/common");
722
+ var TimerTrigger = class _TimerTrigger {
723
+ static {
724
+ __name(this, "TimerTrigger");
725
+ }
726
+ logger = new import_common8.Logger(_TimerTrigger.name);
727
+ intervalId;
728
+ running = false;
729
+ executionCount = 0;
730
+ lastExecutionTime;
731
+ config;
732
+ start(config, handler) {
733
+ this.validate(config);
734
+ this.config = config;
735
+ this.logger.log(`Timer trigger started: cron=${config.cron}`);
736
+ const intervalMs = this.parseCronToInterval(config.cron);
737
+ this.running = true;
738
+ this.executionCount = 0;
739
+ this.intervalId = setInterval(async () => {
740
+ if (!this.running) return;
741
+ if (config.maxTimes && config.maxTimes > 0 && this.executionCount >= config.maxTimes) {
742
+ this.stop();
743
+ return;
744
+ }
745
+ try {
746
+ this.executionCount++;
747
+ this.lastExecutionTime = /* @__PURE__ */ new Date();
748
+ await handler();
749
+ } catch (error) {
750
+ this.logger.error(`Timer trigger handler error: ${error}`);
751
+ }
752
+ }, intervalMs);
753
+ }
754
+ stop() {
755
+ this.running = false;
756
+ if (this.intervalId) {
757
+ clearInterval(this.intervalId);
758
+ this.intervalId = void 0;
759
+ }
760
+ this.logger.log("Timer trigger stopped");
761
+ }
762
+ isRunning() {
763
+ return this.running;
764
+ }
765
+ /** 获取已执行次数 — 对齐 jetlinks TimerTaskExecutor 中的 times 传递 */
766
+ getExecutionCount() {
767
+ return this.executionCount;
768
+ }
769
+ /** 获取最后执行时间 */
770
+ getLastExecutionTime() {
771
+ return this.lastExecutionTime;
772
+ }
773
+ /**
774
+ * 重新加载配置 — 对齐 jetlinks TimerTaskExecutor.reload()
775
+ */
776
+ reload(config, handler) {
777
+ this.stop();
778
+ this.start(config, handler);
779
+ }
780
+ /**
781
+ * 验证配置 — 对齐 jetlinks TimerTaskExecutor.validate()
782
+ */
783
+ validate(config) {
784
+ const cfg = config ?? this.config;
785
+ if (!cfg) {
786
+ throw new Error("Timer trigger config is required");
787
+ }
788
+ if (!cfg.cron || cfg.cron.trim().length === 0) {
789
+ throw new Error("Cron expression is required");
790
+ }
791
+ const parts = cfg.cron.trim().split(/\s+/);
792
+ if (parts.length < 5 || parts.length > 7) {
793
+ throw new Error(`Invalid cron expression: expected 5-7 fields, got ${parts.length}`);
794
+ }
795
+ this.parseCronToInterval(cfg.cron);
796
+ }
797
+ parseCronToInterval(cron) {
798
+ const parts = cron.trim().split(/\s+/);
799
+ if (parts.length >= 6) {
800
+ const seconds = parts[0];
801
+ if (seconds.startsWith("*/")) {
802
+ const interval = parseInt(seconds.slice(2));
803
+ if (isNaN(interval) || interval <= 0) {
804
+ throw new Error(`Invalid cron second interval: ${seconds}`);
805
+ }
806
+ return interval * 1e3;
807
+ }
808
+ return 1e3;
809
+ }
810
+ if (parts.length >= 5) {
811
+ const minutes = parts[0];
812
+ if (minutes.startsWith("*/")) {
813
+ const interval = parseInt(minutes.slice(2));
814
+ if (isNaN(interval) || interval <= 0) {
815
+ throw new Error(`Invalid cron minute interval: ${minutes}`);
816
+ }
817
+ return interval * 60 * 1e3;
818
+ }
819
+ return 60 * 1e3;
820
+ }
821
+ return 60 * 1e3;
822
+ }
823
+ };
824
+
825
+ // src/core/rule-instance-state.ts
826
+ var RuleInstanceState = /* @__PURE__ */ (function(RuleInstanceState2) {
827
+ RuleInstanceState2["STARTED"] = "started";
828
+ RuleInstanceState2["DISABLED"] = "disabled";
829
+ RuleInstanceState2["PAUSED"] = "paused";
830
+ return RuleInstanceState2;
831
+ })({});
832
+
833
+ // src/core/rule-data.ts
834
+ var RuleData = class _RuleData {
835
+ static {
836
+ __name(this, "RuleData");
837
+ }
838
+ id;
839
+ _data;
840
+ _headers;
841
+ _contextMap;
842
+ constructor(data, headers) {
843
+ this.id = _RuleData.generateId();
844
+ this._data = data;
845
+ this._headers = {
846
+ ...headers
847
+ };
848
+ this._contextMap = /* @__PURE__ */ new Map();
849
+ }
850
+ static of(data) {
851
+ return new _RuleData(data);
852
+ }
853
+ /** Create a new RuleData sharing headers but replacing the payload. */
854
+ newData(data) {
855
+ const rd = new _RuleData(data, this._headers);
856
+ for (const [k, v] of this._contextMap) {
857
+ rd._contextMap.set(k, v);
858
+ }
859
+ return rd;
860
+ }
861
+ getData() {
862
+ return this._data;
863
+ }
864
+ setData(data) {
865
+ this._data = data;
866
+ }
867
+ getHeader(key) {
868
+ return this._headers[key];
869
+ }
870
+ setHeader(key, value) {
871
+ this._headers[key] = value;
872
+ }
873
+ getHeaders() {
874
+ return this._headers;
875
+ }
876
+ /** Record current data into headers under `key` (used for serial chaining). */
877
+ recordToHeader(key) {
878
+ this._headers[key] = this._data;
879
+ }
880
+ /** Get a context map entry. */
881
+ getContextEntry(key) {
882
+ return this._contextMap.get(key);
883
+ }
884
+ /** Set a context map entry — mirrors jetlinks RuleDataHelper.setContextData(). */
885
+ setContextEntry(key, value) {
886
+ this._contextMap.set(key, value);
887
+ }
888
+ /** Retrieve the full context map (plain object view of data).
889
+ * Merges data + headers + contextMap — aligned with jetlinks RuleDataHelper.toContextMap(). */
890
+ toContextMap() {
891
+ const base = typeof this._data === "object" && this._data !== null ? {
892
+ ...this._data
893
+ } : {
894
+ value: this._data
895
+ };
896
+ for (const [k, v] of this._contextMap) {
897
+ if (v !== void 0 && !(k in base)) {
898
+ base[k] = v;
899
+ }
900
+ }
901
+ for (const [k, v] of Object.entries(this._headers)) {
902
+ if (v !== void 0) {
903
+ base[k] = v;
904
+ }
905
+ }
906
+ return base;
907
+ }
908
+ static _counter = 0;
909
+ static generateId() {
910
+ return `rd_${Date.now()}_${++_RuleData._counter}`;
911
+ }
912
+ };
913
+
914
+ // src/rule-engine.service.ts
915
+ function _ts_decorate(decorators, target, key, desc) {
916
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
917
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
918
+ r = Reflect.decorate(decorators, target, key, desc);
919
+ } else {
920
+ for (var i = decorators.length - 1; i >= 0; i--) {
921
+ if (d = decorators[i]) {
922
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
923
+ }
924
+ }
925
+ }
926
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
927
+ }
928
+ __name(_ts_decorate, "_ts_decorate");
929
+ function _ts_metadata(metadataKey, metadataValue) {
930
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
931
+ return Reflect.metadata(metadataKey, metadataValue);
932
+ }
933
+ }
934
+ __name(_ts_metadata, "_ts_metadata");
935
+ function _ts_param(paramIndex, decorator) {
936
+ return function(target, key) {
937
+ decorator(target, key, paramIndex);
938
+ };
939
+ }
940
+ __name(_ts_param, "_ts_param");
941
+ var RuleConditionType = /* @__PURE__ */ (function(RuleConditionType2) {
942
+ RuleConditionType2["TERMS"] = "TERMS";
943
+ RuleConditionType2["EXPRESSION"] = "EXPRESSION";
944
+ return RuleConditionType2;
945
+ })({});
946
+ var RuleTriggerType = /* @__PURE__ */ (function(RuleTriggerType2) {
947
+ RuleTriggerType2["EVENT"] = "EVENT";
948
+ RuleTriggerType2["TIMER"] = "TIMER";
949
+ RuleTriggerType2["MANUAL"] = "MANUAL";
950
+ return RuleTriggerType2;
951
+ })({});
952
+ var RuleActionType = /* @__PURE__ */ (function(RuleActionType2) {
953
+ RuleActionType2["NOTIFY"] = "NOTIFY";
954
+ RuleActionType2["HTTP"] = "HTTP";
955
+ RuleActionType2["SCRIPT"] = "SCRIPT";
956
+ RuleActionType2["DELAY"] = "DELAY";
957
+ RuleActionType2["DATA_MAPPING"] = "DATA_MAPPING";
958
+ return RuleActionType2;
959
+ })({});
960
+ var RuleEngineService = class _RuleEngineService {
961
+ static {
962
+ __name(this, "RuleEngineService");
963
+ }
964
+ eventBus;
965
+ logger = new import_common9.Logger(_RuleEngineService.name);
966
+ activeRules = /* @__PURE__ */ new Map();
967
+ termsEvaluator = new TermsEvaluator();
968
+ expressionEvaluator = new ExpressionEvaluator();
969
+ notifyAction = new NotifyAction();
970
+ httpAction = new HttpAction();
971
+ scriptAction = new ScriptAction();
972
+ delayAction = new DelayAction();
973
+ dataMappingAction = new DataMappingAction();
974
+ /** Strategy map for action execution — replaces switch/case in executeAction. */
975
+ actionHandlers = {
976
+ ["NOTIFY"]: /* @__PURE__ */ __name((config, data) => this.notifyAction.execute(config, data).then(() => void 0), "NOTIFY"),
977
+ ["HTTP"]: /* @__PURE__ */ __name((config, data) => this.httpAction.execute(config, data), "HTTP"),
978
+ ["SCRIPT"]: /* @__PURE__ */ __name((config, data) => this.scriptAction.execute(config, data), "SCRIPT"),
979
+ ["DELAY"]: /* @__PURE__ */ __name((config, data) => this.delayAction.execute(config, data), "DELAY"),
980
+ ["DATA_MAPPING"]: /* @__PURE__ */ __name((config, data) => this.dataMappingAction.execute(config, data), "DATA_MAPPING")
981
+ };
982
+ constructor(eventBus) {
983
+ this.eventBus = eventBus;
984
+ }
985
+ onModuleDestroy() {
986
+ this.stopAll();
987
+ }
988
+ async startRule(definition) {
989
+ if (this.activeRules.has(definition.id)) {
990
+ await this.stopRule(definition.id);
991
+ }
992
+ const activeRule = {
993
+ definition,
994
+ state: RuleInstanceState.STARTED,
995
+ eventTriggers: [],
996
+ timerTriggers: []
997
+ };
998
+ if (definition.branches && definition.branches.length > 0) {
999
+ activeRule.branchEvaluator = new BranchEvaluator();
1000
+ }
1001
+ for (const trigger of definition.triggers) {
1002
+ this.setupTrigger(trigger, definition, activeRule);
1003
+ }
1004
+ this.activeRules.set(definition.id, activeRule);
1005
+ this.logger.log(`Rule started: ${definition.name} (${definition.id})`);
1006
+ }
1007
+ async stopRule(id) {
1008
+ const activeRule = this.activeRules.get(id);
1009
+ if (!activeRule) return;
1010
+ activeRule.eventTriggers.forEach((t) => t.stop());
1011
+ activeRule.timerTriggers.forEach((t) => t.stop());
1012
+ activeRule.state = RuleInstanceState.DISABLED;
1013
+ this.activeRules.delete(id);
1014
+ this.logger.log(`Rule stopped: ${id}`);
1015
+ }
1016
+ async pauseRule(id) {
1017
+ const activeRule = this.activeRules.get(id);
1018
+ if (!activeRule) return;
1019
+ activeRule.state = RuleInstanceState.PAUSED;
1020
+ this.logger.log(`Rule paused: ${id}`);
1021
+ }
1022
+ async resumeRule(id) {
1023
+ const activeRule = this.activeRules.get(id);
1024
+ if (!activeRule || activeRule.state !== RuleInstanceState.PAUSED) return;
1025
+ activeRule.state = RuleInstanceState.STARTED;
1026
+ this.logger.log(`Rule resumed: ${id}`);
1027
+ }
1028
+ stopAll() {
1029
+ for (const id of this.activeRules.keys()) {
1030
+ this.stopRule(id);
1031
+ }
1032
+ }
1033
+ async executeRule(definition, data) {
1034
+ const activeRule = this.activeRules.get(definition.id);
1035
+ if (activeRule?.state === RuleInstanceState.PAUSED) {
1036
+ this.logger.debug(`Rule is paused, skipping: ${definition.name}`);
1037
+ return;
1038
+ }
1039
+ this.logger.debug(`Evaluating rule: ${definition.name}`);
1040
+ if (!this.evaluateConditions(definition.conditions, data)) {
1041
+ this.logger.debug(`Rule conditions not met: ${definition.name}`);
1042
+ return;
1043
+ }
1044
+ if (definition.branches && definition.branches.length > 0 && activeRule?.branchEvaluator) {
1045
+ await this.executeBranches(definition, activeRule.branchEvaluator, data);
1046
+ return;
1047
+ }
1048
+ if (definition.parallel) {
1049
+ await this.executeActionsParallel(definition.actions, data);
1050
+ } else {
1051
+ await this.executeActionsSerial(definition.actions, data);
1052
+ }
1053
+ }
1054
+ /** Execute branch conditions and their associated action groups. */
1055
+ async executeBranches(definition, branchEvaluator, data) {
1056
+ const results = branchEvaluator.evaluate(definition.branches, data);
1057
+ for (const result of results) {
1058
+ this.logger.debug(`Branch ${result.branchIndex} matched for rule ${definition.name}`);
1059
+ for (const group of result.actionGroups) {
1060
+ await this.executeActionGroup(group, data);
1061
+ }
1062
+ }
1063
+ }
1064
+ /** Execute an action group (parallel or serial). */
1065
+ async executeActionGroup(group, data) {
1066
+ const actions = group.actions.map((a) => ({
1067
+ type: a.type,
1068
+ config: a.config
1069
+ }));
1070
+ if (group.parallel) {
1071
+ await this.executeActionsParallel(actions, data);
1072
+ } else {
1073
+ await this.executeActionsSerial(actions, data);
1074
+ }
1075
+ }
1076
+ /** Execute actions in parallel. */
1077
+ async executeActionsParallel(actions, data) {
1078
+ const promises = actions.map(async (action) => {
1079
+ try {
1080
+ await this.executeAction(action, data);
1081
+ } catch (error) {
1082
+ this.logger.error(`Action failed: ${action.type}: ${error}`);
1083
+ }
1084
+ });
1085
+ await Promise.all(promises);
1086
+ }
1087
+ /** Execute actions serially, passing output from one to the next. */
1088
+ async executeActionsSerial(actions, data) {
1089
+ const ruleData = RuleData.of(data);
1090
+ let currentData = data;
1091
+ for (let i = 0; i < actions.length; i++) {
1092
+ const action = actions[i];
1093
+ if (i > 0 && actions[i - 1].terms) {
1094
+ const previousTerms = actions[i - 1].terms;
1095
+ if (!this.termsEvaluator.evaluate(currentData, previousTerms)) {
1096
+ this.logger.debug(`Serial action ${i} skipped: output filter not met`);
1097
+ break;
1098
+ }
1099
+ }
1100
+ try {
1101
+ const result = await this.executeAction(action, currentData);
1102
+ ruleData.setHeader(`action_${i + 1}`, result);
1103
+ if (result && typeof result === "object" && !Array.isArray(result)) {
1104
+ currentData = {
1105
+ ...currentData,
1106
+ ...result
1107
+ };
1108
+ }
1109
+ } catch (error) {
1110
+ this.logger.error(`Rule action failed: ${action.type} in serial chain at index ${i}: ${error}`);
1111
+ break;
1112
+ }
1113
+ }
1114
+ }
1115
+ async executeAction(action, data) {
1116
+ const handler = this.actionHandlers[action.type];
1117
+ if (!handler) {
1118
+ this.logger.warn(`Unknown action type: ${action.type}`);
1119
+ return void 0;
1120
+ }
1121
+ return handler(action.config, data);
1122
+ }
1123
+ /** Set up a single trigger (event or timer) for a rule. */
1124
+ setupTrigger(trigger, definition, activeRule) {
1125
+ if (trigger.type === "EVENT") {
1126
+ if (!this.eventBus) {
1127
+ this.logger.warn(`Event trigger skipped for rule "${definition.name}": no TopicEventBus provided`);
1128
+ return;
1129
+ }
1130
+ const eventTrigger = new EventTrigger();
1131
+ eventTrigger.start(trigger.config, this.eventBus, (payload) => this.executeRule(definition, payload));
1132
+ activeRule.eventTriggers.push(eventTrigger);
1133
+ } else if (trigger.type === "TIMER") {
1134
+ const timerTrigger = new TimerTrigger();
1135
+ timerTrigger.start(trigger.config, () => this.executeRule(definition, {}));
1136
+ activeRule.timerTriggers.push(timerTrigger);
1137
+ }
1138
+ }
1139
+ /** Evaluate all conditions for a rule — returns true if all pass. */
1140
+ evaluateConditions(conditions, data) {
1141
+ return conditions.every((condition) => this.evaluateSingleCondition(condition, data));
1142
+ }
1143
+ /** Evaluate a single condition by type. */
1144
+ evaluateSingleCondition(condition, data) {
1145
+ if (condition.type === "TERMS") {
1146
+ return this.termsEvaluator.evaluate(data, condition.config);
1147
+ }
1148
+ if (condition.type === "EXPRESSION") {
1149
+ return this.expressionEvaluator.evaluate(data, condition.config);
1150
+ }
1151
+ return false;
1152
+ }
1153
+ getActiveRules() {
1154
+ return Array.from(this.activeRules.values()).map((r) => r.definition);
1155
+ }
1156
+ isRuleActive(id) {
1157
+ return this.activeRules.has(id);
1158
+ }
1159
+ getRuleState(id) {
1160
+ return this.activeRules.get(id)?.state;
1161
+ }
1162
+ };
1163
+ RuleEngineService = _ts_decorate([
1164
+ (0, import_common9.Injectable)(),
1165
+ _ts_param(0, (0, import_common9.Optional)()),
1166
+ _ts_param(0, (0, import_common9.Inject)(import_event.TOPIC_EVENT_BUS)),
1167
+ _ts_metadata("design:type", Function),
1168
+ _ts_metadata("design:paramtypes", [
1169
+ typeof TopicEventBus === "undefined" ? Object : TopicEventBus
1170
+ ])
1171
+ ], RuleEngineService);
1172
+
1173
+ // src/core/task-executor.ts
1174
+ var RuleConstants = {
1175
+ Event: {
1176
+ result: "result",
1177
+ error: "error",
1178
+ complete: "complete"
1179
+ }
1180
+ };
1181
+ var TaskState = /* @__PURE__ */ (function(TaskState2) {
1182
+ TaskState2["IDLE"] = "idle";
1183
+ TaskState2["RUNNING"] = "running";
1184
+ TaskState2["PAUSED"] = "paused";
1185
+ TaskState2["SHUTDOWN"] = "shutdown";
1186
+ return TaskState2;
1187
+ })({});
1188
+ var AbstractTaskExecutor = class {
1189
+ static {
1190
+ __name(this, "AbstractTaskExecutor");
1191
+ }
1192
+ context;
1193
+ state = "idle";
1194
+ disposables = [];
1195
+ constructor(context) {
1196
+ this.context = context;
1197
+ }
1198
+ getState() {
1199
+ return this.state;
1200
+ }
1201
+ async init() {
1202
+ }
1203
+ async start() {
1204
+ if (this.state === "shutdown") {
1205
+ throw new Error("Cannot start a shutdown executor");
1206
+ }
1207
+ this.state = "running";
1208
+ const cleanup = await this.doStart();
1209
+ if (typeof cleanup === "function") {
1210
+ this.addDisposable(cleanup);
1211
+ }
1212
+ }
1213
+ async pause() {
1214
+ this.state = "paused";
1215
+ }
1216
+ async reload() {
1217
+ await this.shutdown();
1218
+ this.state = "idle";
1219
+ await this.init();
1220
+ await this.start();
1221
+ }
1222
+ async shutdown() {
1223
+ this.state = "shutdown";
1224
+ for (const dispose of this.disposables) {
1225
+ try {
1226
+ dispose();
1227
+ } catch {
1228
+ }
1229
+ }
1230
+ this.disposables = [];
1231
+ }
1232
+ validate() {
1233
+ }
1234
+ /** Register a cleanup function to be called on shutdown. */
1235
+ addDisposable(fn) {
1236
+ this.disposables.push(fn);
1237
+ }
1238
+ };
1239
+
1240
+ // src/core/task-executor-registry.ts
1241
+ var import_common10 = require("@nestjs/common");
1242
+ function _ts_decorate2(decorators, target, key, desc) {
1243
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1244
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1245
+ r = Reflect.decorate(decorators, target, key, desc);
1246
+ } else {
1247
+ for (var i = decorators.length - 1; i >= 0; i--) {
1248
+ if (d = decorators[i]) {
1249
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1250
+ }
1251
+ }
1252
+ }
1253
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1254
+ }
1255
+ __name(_ts_decorate2, "_ts_decorate");
1256
+ var TaskExecutorRegistry = class _TaskExecutorRegistry {
1257
+ static {
1258
+ __name(this, "TaskExecutorRegistry");
1259
+ }
1260
+ logger = new import_common10.Logger(_TaskExecutorRegistry.name);
1261
+ providers = /* @__PURE__ */ new Map();
1262
+ /**
1263
+ * 注册任务执行器提供者
1264
+ */
1265
+ register(provider) {
1266
+ const executor = provider.getExecutor();
1267
+ if (this.providers.has(executor)) {
1268
+ this.logger.warn(`Overriding existing task executor provider: ${executor}`);
1269
+ }
1270
+ this.providers.set(executor, provider);
1271
+ this.logger.log(`Registered task executor provider: ${executor}`);
1272
+ }
1273
+ /**
1274
+ * 注销任务执行器提供者
1275
+ */
1276
+ unregister(executorType) {
1277
+ this.providers.delete(executorType);
1278
+ }
1279
+ /**
1280
+ * 根据执行器类型创建任务
1281
+ */
1282
+ async createTask(executorType, context) {
1283
+ const provider = this.providers.get(executorType);
1284
+ if (!provider) {
1285
+ throw new Error(`No task executor provider found for type: ${executorType}`);
1286
+ }
1287
+ return provider.createTask(context);
1288
+ }
1289
+ /**
1290
+ * 获取所有已注册的执行器类型
1291
+ */
1292
+ getRegisteredTypes() {
1293
+ return Array.from(this.providers.keys());
1294
+ }
1295
+ /**
1296
+ * 检查是否存在指定类型的执行器
1297
+ */
1298
+ hasProvider(executorType) {
1299
+ return this.providers.has(executorType);
1300
+ }
1301
+ };
1302
+ TaskExecutorRegistry = _ts_decorate2([
1303
+ (0, import_common10.Injectable)()
1304
+ ], TaskExecutorRegistry);
1305
+
1306
+ // src/core/rule-model.ts
1307
+ var RuleModel = class {
1308
+ static {
1309
+ __name(this, "RuleModel");
1310
+ }
1311
+ id;
1312
+ name;
1313
+ type;
1314
+ /** All nodes in the graph. */
1315
+ nodes = [];
1316
+ /** All links in the graph. */
1317
+ links = [];
1318
+ /** Default execution mode for action groups. */
1319
+ executionMode = "serial";
1320
+ constructor(id, name, type = "scene") {
1321
+ this.id = id;
1322
+ this.name = name;
1323
+ this.type = type;
1324
+ }
1325
+ /** Add a node to the model. */
1326
+ addNode(node) {
1327
+ this.nodes.push(node);
1328
+ }
1329
+ /** Create a link between two nodes and add it to the model. Returns the link. */
1330
+ link(source, target, type = "output") {
1331
+ const link = {
1332
+ id: `${source.id}->${target.id}`,
1333
+ source: source.id,
1334
+ target: target.id,
1335
+ type
1336
+ };
1337
+ this.links.push(link);
1338
+ source.outputs = source.outputs ?? [];
1339
+ source.outputs.push(link);
1340
+ target.inputs = target.inputs ?? [];
1341
+ target.inputs.push(link);
1342
+ return link;
1343
+ }
1344
+ /** Find a node by id. */
1345
+ getNode(id) {
1346
+ return this.nodes.find((n) => n.id === id);
1347
+ }
1348
+ /** Remove a node and all its connected links — mirrors jetlinks RuleModel management. */
1349
+ removeNode(nodeId) {
1350
+ const idx = this.nodes.findIndex((n) => n.id === nodeId);
1351
+ if (idx < 0) return false;
1352
+ this.nodes.splice(idx, 1);
1353
+ for (let i = this.links.length - 1; i >= 0; i--) {
1354
+ if (this.links[i].source === nodeId || this.links[i].target === nodeId) {
1355
+ this.links.splice(i, 1);
1356
+ }
1357
+ }
1358
+ for (const node of this.nodes) {
1359
+ if (node.inputs) {
1360
+ node.inputs = node.inputs.filter((l) => l.source !== nodeId && l.target !== nodeId);
1361
+ }
1362
+ if (node.outputs) {
1363
+ node.outputs = node.outputs.filter((l) => l.source !== nodeId && l.target !== nodeId);
1364
+ }
1365
+ }
1366
+ return true;
1367
+ }
1368
+ /** Remove a specific link by id. */
1369
+ removeLink(linkId) {
1370
+ const idx = this.links.findIndex((l) => l.id === linkId);
1371
+ if (idx < 0) return false;
1372
+ const link = this.links[idx];
1373
+ this.links.splice(idx, 1);
1374
+ const srcNode = this.getNode(link.source);
1375
+ if (srcNode?.outputs) {
1376
+ srcNode.outputs = srcNode.outputs.filter((l) => l.id !== linkId);
1377
+ }
1378
+ const tgtNode = this.getNode(link.target);
1379
+ if (tgtNode?.inputs) {
1380
+ tgtNode.inputs = tgtNode.inputs.filter((l) => l.id !== linkId);
1381
+ }
1382
+ return true;
1383
+ }
1384
+ /** Get all downstream node ids from a given node. */
1385
+ getDownstream(nodeId) {
1386
+ return this.links.filter((l) => l.source === nodeId).map((l) => l.target);
1387
+ }
1388
+ /** Get all upstream node ids from a given node. */
1389
+ getUpstream(nodeId) {
1390
+ return this.links.filter((l) => l.target === nodeId).map((l) => l.source);
1391
+ }
1392
+ /** Get topologically sorted node ids (for serial execution ordering). */
1393
+ topologicalSort() {
1394
+ const inDegree = /* @__PURE__ */ new Map();
1395
+ const adjacency = /* @__PURE__ */ new Map();
1396
+ for (const node of this.nodes) {
1397
+ inDegree.set(node.id, 0);
1398
+ adjacency.set(node.id, []);
1399
+ }
1400
+ for (const link of this.links) {
1401
+ adjacency.get(link.source)?.push(link.target);
1402
+ inDegree.set(link.target, (inDegree.get(link.target) ?? 0) + 1);
1403
+ }
1404
+ const queue = [];
1405
+ for (const [id, deg] of inDegree) {
1406
+ if (deg === 0) queue.push(id);
1407
+ }
1408
+ const sorted = [];
1409
+ while (queue.length > 0) {
1410
+ const current = queue.shift();
1411
+ sorted.push(current);
1412
+ for (const next of adjacency.get(current) ?? []) {
1413
+ const newDeg = (inDegree.get(next) ?? 1) - 1;
1414
+ inDegree.set(next, newDeg);
1415
+ if (newDeg === 0) queue.push(next);
1416
+ }
1417
+ }
1418
+ return sorted;
1419
+ }
1420
+ /** Validate the model (no cycles, all link targets exist). */
1421
+ validate() {
1422
+ const errors = [];
1423
+ const nodeIds = new Set(this.nodes.map((n) => n.id));
1424
+ for (const link of this.links) {
1425
+ if (!nodeIds.has(link.source)) {
1426
+ errors.push(`Link ${link.id} references unknown source node: ${link.source}`);
1427
+ }
1428
+ if (!nodeIds.has(link.target)) {
1429
+ errors.push(`Link ${link.id} references unknown target node: ${link.target}`);
1430
+ }
1431
+ }
1432
+ const sorted = this.topologicalSort();
1433
+ if (sorted.length !== this.nodes.length) {
1434
+ errors.push("Rule model contains a cycle");
1435
+ }
1436
+ return errors;
1437
+ }
1438
+ };
1439
+
1440
+ // src/rule-version.ts
1441
+ var import_common11 = require("@nestjs/common");
1442
+ function _ts_decorate3(decorators, target, key, desc) {
1443
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1444
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1445
+ r = Reflect.decorate(decorators, target, key, desc);
1446
+ } else {
1447
+ for (var i = decorators.length - 1; i >= 0; i--) {
1448
+ if (d = decorators[i]) {
1449
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1450
+ }
1451
+ }
1452
+ }
1453
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1454
+ }
1455
+ __name(_ts_decorate3, "_ts_decorate");
1456
+ var RuleVersionManager = class _RuleVersionManager {
1457
+ static {
1458
+ __name(this, "RuleVersionManager");
1459
+ }
1460
+ logger = new import_common11.Logger(_RuleVersionManager.name);
1461
+ versions = /* @__PURE__ */ new Map();
1462
+ /** 保存规则新版本 */
1463
+ saveVersion(ruleId, definition, options) {
1464
+ const history = this.versions.get(ruleId) ?? [];
1465
+ for (const v of history) {
1466
+ v.active = false;
1467
+ }
1468
+ const version = {
1469
+ ruleId,
1470
+ version: history.length + 1,
1471
+ definition: JSON.parse(JSON.stringify(definition)),
1472
+ createdAt: Date.now(),
1473
+ createdBy: options?.createdBy,
1474
+ changeLog: options?.changeLog,
1475
+ active: true
1476
+ };
1477
+ history.push(version);
1478
+ this.versions.set(ruleId, history);
1479
+ this.logger.log(`Rule version saved: ${ruleId} v${version.version}`);
1480
+ return version;
1481
+ }
1482
+ /** 获取规则的所有历史版本 */
1483
+ getVersions(ruleId) {
1484
+ return [
1485
+ ...this.versions.get(ruleId) ?? []
1486
+ ];
1487
+ }
1488
+ /** 获取规则的当前活跃版本 */
1489
+ getActiveVersion(ruleId) {
1490
+ return this.versions.get(ruleId)?.find((v) => v.active);
1491
+ }
1492
+ /** 获取指定版本 */
1493
+ getVersion(ruleId, version) {
1494
+ return this.versions.get(ruleId)?.find((v) => v.version === version);
1495
+ }
1496
+ /** 回滚到指定版本 */
1497
+ rollback(ruleId, targetVersion) {
1498
+ const history = this.versions.get(ruleId);
1499
+ if (!history) return void 0;
1500
+ const target = history.find((v) => v.version === targetVersion);
1501
+ if (!target) return void 0;
1502
+ for (const v of history) {
1503
+ v.active = v.version === targetVersion;
1504
+ }
1505
+ this.logger.log(`Rule rolled back: ${ruleId} \u2192 v${targetVersion}`);
1506
+ return target;
1507
+ }
1508
+ /** 比较两个版本的差异 */
1509
+ diff(ruleId, v1, v2) {
1510
+ const ver1 = this.getVersion(ruleId, v1);
1511
+ const ver2 = this.getVersion(ruleId, v2);
1512
+ if (!ver1 || !ver2) return void 0;
1513
+ const keys1 = new Set(Object.keys(ver1.definition));
1514
+ const keys2 = new Set(Object.keys(ver2.definition));
1515
+ const added = [
1516
+ ...keys2
1517
+ ].filter((k) => !keys1.has(k));
1518
+ const removed = [
1519
+ ...keys1
1520
+ ].filter((k) => !keys2.has(k));
1521
+ const modified = [
1522
+ ...keys1
1523
+ ].filter((k) => keys2.has(k) && JSON.stringify(ver1.definition[k]) !== JSON.stringify(ver2.definition[k]));
1524
+ return {
1525
+ added,
1526
+ removed,
1527
+ modified
1528
+ };
1529
+ }
1530
+ };
1531
+ RuleVersionManager = _ts_decorate3([
1532
+ (0, import_common11.Injectable)()
1533
+ ], RuleVersionManager);
1534
+ var RuleTestRunner = class _RuleTestRunner {
1535
+ static {
1536
+ __name(this, "RuleTestRunner");
1537
+ }
1538
+ logger = new import_common11.Logger(_RuleTestRunner.name);
1539
+ /** 运行单个测试用例 */
1540
+ async runTest(definition, testCase, executor) {
1541
+ const start = Date.now();
1542
+ try {
1543
+ const actual = await executor.execute(testCase.input);
1544
+ const passed = this.compareResults(actual, testCase.expected);
1545
+ return {
1546
+ testCase: testCase.name,
1547
+ passed,
1548
+ actual,
1549
+ expected: testCase.expected,
1550
+ duration: Date.now() - start
1551
+ };
1552
+ } catch (err) {
1553
+ return {
1554
+ testCase: testCase.name,
1555
+ passed: false,
1556
+ actual: {},
1557
+ expected: testCase.expected,
1558
+ error: err instanceof Error ? err.message : String(err),
1559
+ duration: Date.now() - start
1560
+ };
1561
+ }
1562
+ }
1563
+ /** 运行多个测试用例 */
1564
+ async runTests(definition, testCases, executor) {
1565
+ const results = [];
1566
+ for (const tc of testCases) {
1567
+ results.push(await this.runTest(definition, tc, executor));
1568
+ }
1569
+ const passed = results.filter((r) => r.passed).length;
1570
+ return {
1571
+ results,
1572
+ passed,
1573
+ failed: results.length - passed,
1574
+ total: results.length
1575
+ };
1576
+ }
1577
+ compareResults(actual, expected) {
1578
+ for (const [key, value] of Object.entries(expected)) {
1579
+ if (JSON.stringify(actual[key]) !== JSON.stringify(value)) return false;
1580
+ }
1581
+ return true;
1582
+ }
1583
+ };
1584
+ RuleTestRunner = _ts_decorate3([
1585
+ (0, import_common11.Injectable)()
1586
+ ], RuleTestRunner);
1587
+
1588
+ // src/rule-engine.module.ts
1589
+ var import_common12 = require("@nestjs/common");
1590
+ function _ts_decorate4(decorators, target, key, desc) {
1591
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1592
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1593
+ r = Reflect.decorate(decorators, target, key, desc);
1594
+ } else {
1595
+ for (var i = decorators.length - 1; i >= 0; i--) {
1596
+ if (d = decorators[i]) {
1597
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1598
+ }
1599
+ }
1600
+ }
1601
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1602
+ }
1603
+ __name(_ts_decorate4, "_ts_decorate");
1604
+ var ZuckerRuleEngineModule = class {
1605
+ static {
1606
+ __name(this, "ZuckerRuleEngineModule");
1607
+ }
1608
+ };
1609
+ ZuckerRuleEngineModule = _ts_decorate4([
1610
+ (0, import_common12.Module)({
1611
+ providers: [
1612
+ RuleEngineService,
1613
+ TaskExecutorRegistry
1614
+ ],
1615
+ exports: [
1616
+ RuleEngineService,
1617
+ TaskExecutorRegistry
1618
+ ]
1619
+ })
1620
+ ], ZuckerRuleEngineModule);
1621
+ // Annotate the CommonJS export names for ESM import in node:
1622
+ 0 && (module.exports = {
1623
+ AbstractTaskExecutor,
1624
+ BranchEvaluator,
1625
+ DataMappingAction,
1626
+ DelayAction,
1627
+ EventTrigger,
1628
+ ExpressionEvaluator,
1629
+ HttpAction,
1630
+ NotifyAction,
1631
+ RuleActionType,
1632
+ RuleConditionType,
1633
+ RuleConstants,
1634
+ RuleData,
1635
+ RuleEngineService,
1636
+ RuleInstanceState,
1637
+ RuleModel,
1638
+ RuleTestRunner,
1639
+ RuleTriggerType,
1640
+ RuleVersionManager,
1641
+ ScriptAction,
1642
+ TaskExecutorRegistry,
1643
+ TaskState,
1644
+ TermsEvaluator,
1645
+ TimerTrigger,
1646
+ ZuckerRuleEngineModule,
1647
+ createShakeLimit
1648
+ });