@astralibx/email-rule-engine 12.10.0 → 12.11.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/README.md +1 -0
- package/dist/index.cjs +60 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.mjs +60 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -205,7 +205,7 @@ function createEmailRuleSchema(platformValues, audienceValues, collectionPrefix)
|
|
|
205
205
|
},
|
|
206
206
|
conditions: [RuleConditionSchema],
|
|
207
207
|
identifiers: [{ type: String }],
|
|
208
|
-
|
|
208
|
+
collectionName: { type: String }
|
|
209
209
|
}, { _id: false });
|
|
210
210
|
const RuleRunStatsSchema = createRunStatsSchema();
|
|
211
211
|
const schema = new mongoose.Schema(
|
|
@@ -375,7 +375,16 @@ function createEmailThrottleConfigSchema(collectionPrefix) {
|
|
|
375
375
|
maxPerUserPerDay: { type: Number, default: 1 },
|
|
376
376
|
maxPerUserPerWeek: { type: Number, default: 2 },
|
|
377
377
|
minGapDays: { type: Number, default: 3 },
|
|
378
|
-
throttleWindow: { type: String, enum: Object.values(THROTTLE_WINDOW), default: THROTTLE_WINDOW.Rolling }
|
|
378
|
+
throttleWindow: { type: String, enum: Object.values(THROTTLE_WINDOW), default: THROTTLE_WINDOW.Rolling },
|
|
379
|
+
sendWindow: {
|
|
380
|
+
type: {
|
|
381
|
+
startHour: { type: Number, min: 0, max: 23 },
|
|
382
|
+
endHour: { type: Number, min: 0, max: 23 },
|
|
383
|
+
timezone: { type: String }
|
|
384
|
+
},
|
|
385
|
+
_id: false,
|
|
386
|
+
default: void 0
|
|
387
|
+
}
|
|
379
388
|
},
|
|
380
389
|
{
|
|
381
390
|
timestamps: true,
|
|
@@ -1093,8 +1102,8 @@ var RuleService = class {
|
|
|
1093
1102
|
throw new RuleTemplateIncompatibleError("target.identifiers must be a non-empty array for list mode, validation failed");
|
|
1094
1103
|
}
|
|
1095
1104
|
}
|
|
1096
|
-
if (isQueryTarget(input.target) && input.target.
|
|
1097
|
-
const condErrors = validateConditions(input.target.conditions, input.target.
|
|
1105
|
+
if (isQueryTarget(input.target) && input.target.collectionName && this.config.collections?.length) {
|
|
1106
|
+
const condErrors = validateConditions(input.target.conditions, input.target.collectionName, this.config.collections);
|
|
1098
1107
|
if (condErrors.length > 0) {
|
|
1099
1108
|
throw new RuleTemplateIncompatibleError(
|
|
1100
1109
|
`Invalid conditions: ${condErrors.map((e) => e.message).join("; ")}`
|
|
@@ -1132,8 +1141,8 @@ var RuleService = class {
|
|
|
1132
1141
|
}
|
|
1133
1142
|
if (isQueryTarget(effectiveTarget)) {
|
|
1134
1143
|
const qt = effectiveTarget;
|
|
1135
|
-
if (qt.
|
|
1136
|
-
const condErrors = validateConditions(qt.conditions || [], qt.
|
|
1144
|
+
if (qt.collectionName && this.config.collections?.length) {
|
|
1145
|
+
const condErrors = validateConditions(qt.conditions || [], qt.collectionName, this.config.collections);
|
|
1137
1146
|
if (condErrors.length > 0) {
|
|
1138
1147
|
throw new RuleTemplateIncompatibleError(
|
|
1139
1148
|
`Invalid conditions: ${condErrors.map((e) => e.message).join("; ")}`
|
|
@@ -1191,7 +1200,7 @@ var RuleService = class {
|
|
|
1191
1200
|
return { matchedCount: matchedCount2, effectiveLimit, willProcess: willProcess2, ruleId: id, sample: sample2 };
|
|
1192
1201
|
}
|
|
1193
1202
|
const queryTarget = rule.target;
|
|
1194
|
-
const collectionName = queryTarget.
|
|
1203
|
+
const collectionName = queryTarget.collectionName;
|
|
1195
1204
|
const collectionSchema = collectionName ? this.config.collections?.find((c) => c.name === collectionName) : void 0;
|
|
1196
1205
|
const users = await this.config.adapters.queryUsers(rule.target, 5e4, collectionSchema ? { collectionSchema } : void 0);
|
|
1197
1206
|
const matchedCount = users.length;
|
|
@@ -1279,16 +1288,6 @@ var RuleRunnerService = class {
|
|
|
1279
1288
|
async runAllRules(triggeredBy = RUN_TRIGGER.Cron, runId) {
|
|
1280
1289
|
if (!runId) runId = crypto__default.default.randomUUID();
|
|
1281
1290
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1282
|
-
if (this.config.options?.sendWindow) {
|
|
1283
|
-
const { startHour, endHour, timezone } = this.config.options.sendWindow;
|
|
1284
|
-
const now = /* @__PURE__ */ new Date();
|
|
1285
|
-
const formatter = new Intl.DateTimeFormat("en-US", { hour: "numeric", hour12: false, timeZone: timezone });
|
|
1286
|
-
const currentHour = parseInt(formatter.format(now), 10);
|
|
1287
|
-
if (currentHour < startHour || currentHour >= endHour) {
|
|
1288
|
-
this.logger.info("Outside send window, skipping run", { currentHour, startHour, endHour, timezone });
|
|
1289
|
-
return { runId };
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
1291
|
const lockAcquired = await this.lock.acquire();
|
|
1293
1292
|
if (!lockAcquired) {
|
|
1294
1293
|
this.logger.warn("Rule runner already executing, skipping");
|
|
@@ -1307,9 +1306,20 @@ var RuleRunnerService = class {
|
|
|
1307
1306
|
let runStatus = "completed";
|
|
1308
1307
|
try {
|
|
1309
1308
|
const throttleConfig = await this.EmailThrottleConfig.getConfig();
|
|
1309
|
+
const sendWindow = throttleConfig.sendWindow ?? this.config.options?.sendWindow;
|
|
1310
|
+
if (sendWindow) {
|
|
1311
|
+
const { startHour, endHour, timezone } = sendWindow;
|
|
1312
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
1313
|
+
const formatter = new Intl.DateTimeFormat("en-US", { hour: "numeric", hour12: false, timeZone: timezone });
|
|
1314
|
+
const currentHour = parseInt(formatter.format(now2), 10);
|
|
1315
|
+
if (currentHour < startHour || currentHour >= endHour) {
|
|
1316
|
+
this.logger.info("Outside send window, skipping run", { currentHour, startHour, endHour, timezone });
|
|
1317
|
+
return { runId };
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1310
1320
|
const allActiveRules = await this.EmailRule.findActive();
|
|
1311
1321
|
const now = /* @__PURE__ */ new Date();
|
|
1312
|
-
const tz = this.config.options?.sendWindow?.timezone;
|
|
1322
|
+
const tz = sendWindow?.timezone ?? this.config.options?.sendWindow?.timezone;
|
|
1313
1323
|
const activeRules = allActiveRules.filter((rule) => {
|
|
1314
1324
|
if (rule.validFrom) {
|
|
1315
1325
|
const localNow = getLocalDate(now, tz);
|
|
@@ -1710,7 +1720,7 @@ var RuleRunnerService = class {
|
|
|
1710
1720
|
const limit = rule.maxPerRun || this.config.options?.defaultMaxPerRun || 500;
|
|
1711
1721
|
let users;
|
|
1712
1722
|
try {
|
|
1713
|
-
const collectionName = rule.target?.
|
|
1723
|
+
const collectionName = rule.target?.collectionName;
|
|
1714
1724
|
const collectionSchema = collectionName ? this.config.collections?.find((c) => c.name === collectionName) : void 0;
|
|
1715
1725
|
users = await this.config.adapters.queryUsers(rule.target, limit, collectionSchema ? { collectionSchema } : void 0);
|
|
1716
1726
|
} catch (err) {
|
|
@@ -2211,7 +2221,7 @@ function createSettingsController(EmailThrottleConfig) {
|
|
|
2211
2221
|
res.json({ success: true, data: { config } });
|
|
2212
2222
|
});
|
|
2213
2223
|
const updateThrottleConfig = asyncHandler(async (req, res) => {
|
|
2214
|
-
const { maxPerUserPerDay, maxPerUserPerWeek, minGapDays } = req.body;
|
|
2224
|
+
const { maxPerUserPerDay, maxPerUserPerWeek, minGapDays, sendWindow } = req.body;
|
|
2215
2225
|
const updates = {};
|
|
2216
2226
|
if (maxPerUserPerDay !== void 0) {
|
|
2217
2227
|
if (!Number.isInteger(maxPerUserPerDay) || maxPerUserPerDay < 1) {
|
|
@@ -2231,6 +2241,23 @@ function createSettingsController(EmailThrottleConfig) {
|
|
|
2231
2241
|
}
|
|
2232
2242
|
updates.minGapDays = minGapDays;
|
|
2233
2243
|
}
|
|
2244
|
+
if (sendWindow !== void 0) {
|
|
2245
|
+
if (sendWindow === null) {
|
|
2246
|
+
updates.sendWindow = void 0;
|
|
2247
|
+
} else {
|
|
2248
|
+
const { startHour, endHour, timezone } = sendWindow;
|
|
2249
|
+
if (!Number.isInteger(startHour) || startHour < 0 || startHour > 23) {
|
|
2250
|
+
return res.status(400).json({ success: false, error: "sendWindow.startHour must be an integer 0-23" });
|
|
2251
|
+
}
|
|
2252
|
+
if (!Number.isInteger(endHour) || endHour < 0 || endHour > 23) {
|
|
2253
|
+
return res.status(400).json({ success: false, error: "sendWindow.endHour must be an integer 0-23" });
|
|
2254
|
+
}
|
|
2255
|
+
if (typeof timezone !== "string" || !timezone.trim()) {
|
|
2256
|
+
return res.status(400).json({ success: false, error: "sendWindow.timezone must be a non-empty string" });
|
|
2257
|
+
}
|
|
2258
|
+
updates.sendWindow = { startHour, endHour, timezone: timezone.trim() };
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2234
2261
|
if (Object.keys(updates).length === 0) {
|
|
2235
2262
|
return res.status(400).json({ success: false, error: "No valid fields to update" });
|
|
2236
2263
|
}
|
|
@@ -2240,9 +2267,21 @@ function createSettingsController(EmailThrottleConfig) {
|
|
|
2240
2267
|
if (finalWeekly < finalDaily) {
|
|
2241
2268
|
return res.status(400).json({ success: false, error: "maxPerUserPerWeek must be >= maxPerUserPerDay" });
|
|
2242
2269
|
}
|
|
2270
|
+
const setFields = {};
|
|
2271
|
+
const unsetFields = {};
|
|
2272
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
2273
|
+
if (value === void 0) {
|
|
2274
|
+
unsetFields[key] = "";
|
|
2275
|
+
} else {
|
|
2276
|
+
setFields[key] = value;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
const updateOp = {};
|
|
2280
|
+
if (Object.keys(setFields).length > 0) updateOp["$set"] = setFields;
|
|
2281
|
+
if (Object.keys(unsetFields).length > 0) updateOp["$unset"] = unsetFields;
|
|
2243
2282
|
const updated = await EmailThrottleConfig.findByIdAndUpdate(
|
|
2244
2283
|
config._id,
|
|
2245
|
-
|
|
2284
|
+
updateOp,
|
|
2246
2285
|
{ new: true }
|
|
2247
2286
|
);
|
|
2248
2287
|
res.json({ success: true, data: { config: updated } });
|