mailery 0.8.1 → 0.10.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/admin/spa/index-BTww-4AA.js +41 -0
- package/dist/admin/spa/index-BTww-4AA.js.map +1 -0
- package/dist/admin/spa/index.html +1 -1
- package/dist/admin/spa/{template-editor-DwMKy5sw.js → template-editor-CrOKLpSw.js} +2 -2
- package/dist/admin/spa/{template-editor-DwMKy5sw.js.map → template-editor-CrOKLpSw.js.map} +1 -1
- package/dist/index.cjs +82 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +82 -12
- package/dist/index.js.map +1 -1
- package/dist/{null-CwXmiVLe.d.cts → null-CU_EApC6.d.cts} +24 -0
- package/dist/{null-CwXmiVLe.d.ts → null-CU_EApC6.d.ts} +24 -0
- package/dist/testing.cjs +75 -11
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +75 -11
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/admin/spa/index-B3ndbezm.js +0 -41
- package/dist/admin/spa/index-B3ndbezm.js.map +0 -1
package/dist/testing.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Db } from 'mongodb';
|
|
2
|
-
import { C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, N as NormalizedEvent, F as FlowStep, a2 as FlowTrigger, m as FlowGoal, U as TemplateKind, T as TemplateDoc, l as FlowDoc, D as DeliveryWindow, R as RunnerContext, d as Mailer, a3 as QueueDriverConfig } from './null-
|
|
3
|
-
export { s as NullProvider } from './null-
|
|
2
|
+
import { C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, N as NormalizedEvent, F as FlowStep, a2 as FlowTrigger, m as FlowGoal, U as TemplateKind, T as TemplateDoc, l as FlowDoc, D as DeliveryWindow, R as RunnerContext, d as Mailer, a3 as QueueDriverConfig } from './null-CU_EApC6.cjs';
|
|
3
|
+
export { s as NullProvider } from './null-CU_EApC6.cjs';
|
|
4
4
|
import 'zod';
|
|
5
5
|
import 'handlebars';
|
|
6
6
|
import 'ioredis';
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Db } from 'mongodb';
|
|
2
|
-
import { C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, N as NormalizedEvent, F as FlowStep, a2 as FlowTrigger, m as FlowGoal, U as TemplateKind, T as TemplateDoc, l as FlowDoc, D as DeliveryWindow, R as RunnerContext, d as Mailer, a3 as QueueDriverConfig } from './null-
|
|
3
|
-
export { s as NullProvider } from './null-
|
|
2
|
+
import { C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, N as NormalizedEvent, F as FlowStep, a2 as FlowTrigger, m as FlowGoal, U as TemplateKind, T as TemplateDoc, l as FlowDoc, D as DeliveryWindow, R as RunnerContext, d as Mailer, a3 as QueueDriverConfig } from './null-CU_EApC6.js';
|
|
3
|
+
export { s as NullProvider } from './null-CU_EApC6.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
import 'handlebars';
|
|
6
6
|
import 'ioredis';
|
package/dist/testing.js
CHANGED
|
@@ -14195,7 +14195,25 @@ var tagInputSchema = z.object({
|
|
|
14195
14195
|
var abortFlowInputSchema = z.object({
|
|
14196
14196
|
flowSlug: slugSchema,
|
|
14197
14197
|
externalId: externalIdSchema,
|
|
14198
|
-
reason: z.string().min(1).max(200).optional()
|
|
14198
|
+
reason: z.string().min(1).max(200).optional(),
|
|
14199
|
+
/**
|
|
14200
|
+
* Restrict the abort to runs whose trigger event carried these properties —
|
|
14201
|
+
* e.g. `{ accountId }` to cancel one account's series while the same
|
|
14202
|
+
* contact's other accounts keep running. Omit to abort every active run for
|
|
14203
|
+
* the contact on this flow.
|
|
14204
|
+
*
|
|
14205
|
+
* Keys and values are both constrained because these go straight into a
|
|
14206
|
+
* Mongo query. Values are primitives only: an object value like
|
|
14207
|
+
* `{ $ne: null }` would reach the query as an OPERATOR and match every
|
|
14208
|
+
* scoped run, turning a one-account abort into abort-everything. Hosts
|
|
14209
|
+
* typically pass an id from a request body, so treat it as untrusted. The
|
|
14210
|
+
* key regex likewise blocks `$`-prefixed keys and dots (a dot would silently
|
|
14211
|
+
* extend the path and change match semantics).
|
|
14212
|
+
*/
|
|
14213
|
+
matchTriggerProperties: z.record(
|
|
14214
|
+
z.string().regex(/^[A-Za-z0-9_]+$/),
|
|
14215
|
+
z.union([z.string(), z.number(), z.boolean(), z.null()])
|
|
14216
|
+
).optional()
|
|
14199
14217
|
});
|
|
14200
14218
|
var abortAllFlowsInputSchema = abortFlowInputSchema.omit({ flowSlug: true });
|
|
14201
14219
|
var sendOneOffInputSchema = z.object({
|
|
@@ -14264,6 +14282,13 @@ var predicateSchema = z.lazy(
|
|
|
14264
14282
|
z.object({ notHasTag: z.string() }),
|
|
14265
14283
|
z.object({ fieldEquals: z.object({ field: z.string(), value: z.unknown() }) }),
|
|
14266
14284
|
z.object({ fieldExists: z.string() }),
|
|
14285
|
+
z.object({
|
|
14286
|
+
triggerPropertyEquals: z.object({
|
|
14287
|
+
key: z.string().min(1),
|
|
14288
|
+
value: z.union([z.string(), z.number(), z.boolean(), z.null()])
|
|
14289
|
+
})
|
|
14290
|
+
}),
|
|
14291
|
+
z.object({ triggerPropertyTruthy: z.string().min(1) }),
|
|
14267
14292
|
z.object({
|
|
14268
14293
|
hasFiredEvent: z.string(),
|
|
14269
14294
|
sinceFlowStart: z.boolean().optional(),
|
|
@@ -14623,7 +14648,7 @@ var BullDriver = class _BullDriver {
|
|
|
14623
14648
|
bullQueues;
|
|
14624
14649
|
workers = null;
|
|
14625
14650
|
bull;
|
|
14626
|
-
static async create(redisConfig) {
|
|
14651
|
+
static async create(redisConfig, prefix) {
|
|
14627
14652
|
let bull;
|
|
14628
14653
|
try {
|
|
14629
14654
|
bull = await import('bullmq');
|
|
@@ -14632,14 +14657,22 @@ var BullDriver = class _BullDriver {
|
|
|
14632
14657
|
"mailery: queue driver 'bull' requires the 'bullmq' peer dependency. Run `npm install bullmq ioredis`."
|
|
14633
14658
|
);
|
|
14634
14659
|
}
|
|
14660
|
+
if (prefix?.includes(":")) {
|
|
14661
|
+
throw new Error(
|
|
14662
|
+
`mailery: queue prefix "${prefix}" must not contain ':' \u2014 BullMQ uses it as the Redis key separator.`
|
|
14663
|
+
);
|
|
14664
|
+
}
|
|
14635
14665
|
const redis = isRedisLike(redisConfig) ? redisConfig : connect(redisConfig);
|
|
14636
|
-
return new _BullDriver(bull, redis);
|
|
14666
|
+
return new _BullDriver(bull, redis, prefix);
|
|
14637
14667
|
}
|
|
14638
|
-
|
|
14668
|
+
prefix;
|
|
14669
|
+
constructor(bull, redis, prefix) {
|
|
14639
14670
|
this.bull = bull;
|
|
14640
14671
|
this.redis = redis;
|
|
14672
|
+
this.prefix = prefix;
|
|
14641
14673
|
const opts = {
|
|
14642
14674
|
connection: redis,
|
|
14675
|
+
prefix,
|
|
14643
14676
|
defaultJobOptions: {
|
|
14644
14677
|
removeOnComplete: { age: 24 * 3600, count: 1e3 },
|
|
14645
14678
|
removeOnFail: { age: 7 * 24 * 3600 }
|
|
@@ -14671,7 +14704,7 @@ var BullDriver = class _BullDriver {
|
|
|
14671
14704
|
}
|
|
14672
14705
|
async startWorkers(opts) {
|
|
14673
14706
|
if (this.workers) return;
|
|
14674
|
-
const base = { connection: this.redis };
|
|
14707
|
+
const base = { connection: this.redis, prefix: this.prefix };
|
|
14675
14708
|
const { Worker } = this.bull;
|
|
14676
14709
|
const tick = new Worker(
|
|
14677
14710
|
QUEUE_NAMES.tick,
|
|
@@ -14923,7 +14956,7 @@ var NoopDriver = class {
|
|
|
14923
14956
|
async function createQueueDriver(config, fallbackDb) {
|
|
14924
14957
|
switch (config.driver) {
|
|
14925
14958
|
case "bull":
|
|
14926
|
-
return BullDriver.create(config.redis);
|
|
14959
|
+
return BullDriver.create(config.redis, config.prefix);
|
|
14927
14960
|
case "agenda":
|
|
14928
14961
|
return AgendaDriver.create({
|
|
14929
14962
|
db: config.db ?? fallbackDb,
|
|
@@ -15346,6 +15379,12 @@ async function evaluatePredicate(predicate, ctx) {
|
|
|
15346
15379
|
if ("fieldExists" in p) {
|
|
15347
15380
|
return ctx.contact.fields[p.fieldExists] !== void 0;
|
|
15348
15381
|
}
|
|
15382
|
+
if ("triggerPropertyEquals" in p) {
|
|
15383
|
+
return (ctx.run.triggerEvent?.properties ?? {})[p.triggerPropertyEquals.key] === p.triggerPropertyEquals.value;
|
|
15384
|
+
}
|
|
15385
|
+
if ("triggerPropertyTruthy" in p) {
|
|
15386
|
+
return Boolean((ctx.run.triggerEvent?.properties ?? {})[p.triggerPropertyTruthy]);
|
|
15387
|
+
}
|
|
15349
15388
|
if ("subscriptionStatus" in p) {
|
|
15350
15389
|
const sub = await ctx.collections.subscriptions.findOne({ externalId: ctx.contact.externalId });
|
|
15351
15390
|
return sub?.status === p.subscriptionStatus;
|
|
@@ -16092,7 +16131,14 @@ async function handleFireEvent(run, step2, ctx) {
|
|
|
16092
16131
|
await ctx.collections.events.insertOne({
|
|
16093
16132
|
externalId: run.externalId,
|
|
16094
16133
|
name: step2.eventName,
|
|
16095
|
-
|
|
16134
|
+
// Inherit the triggering event's properties so a handoff carries the
|
|
16135
|
+
// context that identifies what the run is ABOUT (which account, order,
|
|
16136
|
+
// subscription, ...). A step's `properties` are static — authored once in
|
|
16137
|
+
// the flow definition — so without this a fired event can only ever say
|
|
16138
|
+
// "this contact", losing the scope the originating event supplied, and
|
|
16139
|
+
// the receiving flow has nothing to resolve variables against. Explicit
|
|
16140
|
+
// step.properties win on conflict.
|
|
16141
|
+
properties: { ...run.triggerEvent?.properties ?? {}, ...step2.properties ?? {} },
|
|
16096
16142
|
dedupeKey,
|
|
16097
16143
|
occurredAt: /* @__PURE__ */ new Date(),
|
|
16098
16144
|
createdAt: /* @__PURE__ */ new Date()
|
|
@@ -17351,6 +17397,7 @@ var Mailer = class _Mailer {
|
|
|
17351
17397
|
* MAILER_MONGODB_URI — Mongo connection string (required)
|
|
17352
17398
|
* MAILER_MONGODB_DB — database name (optional; defaults to the URI default)
|
|
17353
17399
|
* MAILER_REDIS_URL — Redis connection URL (required)
|
|
17400
|
+
* MAILER_QUEUE_PREFIX — Redis key prefix to namespace this instance (optional)
|
|
17354
17401
|
* MAILER_PUBLIC_URL — base for tracking/unsub URLs (required)
|
|
17355
17402
|
* MAILER_UNSUBSCRIBE_SECRET — HMAC key (required)
|
|
17356
17403
|
* MAILER_SENDER_ADDRESS — postal address for CAN-SPAM (optional)
|
|
@@ -17399,7 +17446,11 @@ var Mailer = class _Mailer {
|
|
|
17399
17446
|
}
|
|
17400
17447
|
const defaultProvider = env.MAILER_DEFAULT_PROVIDER ?? Object.keys(providers)[0];
|
|
17401
17448
|
const driverEnv = env.MAILER_QUEUE_DRIVER ?? "bull";
|
|
17402
|
-
const queue = driverEnv === "agenda" ? { driver: "agenda" } : driverEnv === "noop" ? { driver: "noop" } : {
|
|
17449
|
+
const queue = driverEnv === "agenda" ? { driver: "agenda" } : driverEnv === "noop" ? { driver: "noop" } : {
|
|
17450
|
+
driver: "bull",
|
|
17451
|
+
redis: { url: required("MAILER_REDIS_URL") },
|
|
17452
|
+
prefix: env.MAILER_QUEUE_PREFIX
|
|
17453
|
+
};
|
|
17403
17454
|
return _Mailer.init({
|
|
17404
17455
|
db,
|
|
17405
17456
|
adapter,
|
|
@@ -17650,14 +17701,25 @@ var Mailer = class _Mailer {
|
|
|
17650
17701
|
* same handler that processes the business event ("user upgraded").
|
|
17651
17702
|
*/
|
|
17652
17703
|
async abortFlow(flowSlug, externalId, opts = {}) {
|
|
17653
|
-
const parsed = abortFlowInputSchema.parse({
|
|
17704
|
+
const parsed = abortFlowInputSchema.parse({
|
|
17705
|
+
flowSlug,
|
|
17706
|
+
externalId,
|
|
17707
|
+
reason: opts.reason,
|
|
17708
|
+
matchTriggerProperties: opts.matchTriggerProperties
|
|
17709
|
+
});
|
|
17654
17710
|
const flow = await this.collections.flows.findOne(
|
|
17655
17711
|
{ slug: parsed.flowSlug },
|
|
17656
17712
|
{ projection: { _id: 1 } }
|
|
17657
17713
|
);
|
|
17658
17714
|
if (!flow) throw new Error(`abortFlow: unknown flow slug "${parsed.flowSlug}"`);
|
|
17715
|
+
const triggerMatch = Object.fromEntries(
|
|
17716
|
+
Object.entries(parsed.matchTriggerProperties ?? {}).map(([k, v]) => [
|
|
17717
|
+
`triggerEvent.properties.${k}`,
|
|
17718
|
+
v
|
|
17719
|
+
])
|
|
17720
|
+
);
|
|
17659
17721
|
const result = await this.abortActiveRuns(
|
|
17660
|
-
{ externalId: parsed.externalId, flowId: flow._id },
|
|
17722
|
+
{ externalId: parsed.externalId, flowId: flow._id, ...triggerMatch },
|
|
17661
17723
|
parsed.reason ? `aborted_by_host:${parsed.reason}` : "aborted_by_host"
|
|
17662
17724
|
);
|
|
17663
17725
|
if (result.abortedRuns > 0 || result.cancelledSends > 0) {
|
|
@@ -17665,7 +17727,9 @@ var Mailer = class _Mailer {
|
|
|
17665
17727
|
actor: "host",
|
|
17666
17728
|
action: "flow.abort",
|
|
17667
17729
|
resource: { collection: "mailer_flow_runs", slug: parsed.flowSlug },
|
|
17668
|
-
|
|
17730
|
+
// Record the scope: without it a one-account abort and an abort-every-
|
|
17731
|
+
// run-for-this-contact are indistinguishable in the audit trail.
|
|
17732
|
+
diffSummary: `abortFlow slug=${parsed.flowSlug} externalId=${parsed.externalId} scope=${parsed.matchTriggerProperties ? JSON.stringify(parsed.matchTriggerProperties) : "all"} runs=${result.abortedRuns} sends=${result.cancelledSends}${parsed.reason ? ` reason=${parsed.reason}` : ""}`
|
|
17669
17733
|
});
|
|
17670
17734
|
}
|
|
17671
17735
|
return result;
|