mailery 0.9.0 → 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.
@@ -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-BRSHAxvg.cjs';
3
- export { s as NullProvider } from './null-BRSHAxvg.cjs';
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-BRSHAxvg.js';
3
- export { s as NullProvider } from './null-BRSHAxvg.js';
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(),
@@ -15354,6 +15379,12 @@ async function evaluatePredicate(predicate, ctx) {
15354
15379
  if ("fieldExists" in p) {
15355
15380
  return ctx.contact.fields[p.fieldExists] !== void 0;
15356
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
+ }
15357
15388
  if ("subscriptionStatus" in p) {
15358
15389
  const sub = await ctx.collections.subscriptions.findOne({ externalId: ctx.contact.externalId });
15359
15390
  return sub?.status === p.subscriptionStatus;
@@ -16100,7 +16131,14 @@ async function handleFireEvent(run, step2, ctx) {
16100
16131
  await ctx.collections.events.insertOne({
16101
16132
  externalId: run.externalId,
16102
16133
  name: step2.eventName,
16103
- properties: step2.properties ?? {},
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 ?? {} },
16104
16142
  dedupeKey,
16105
16143
  occurredAt: /* @__PURE__ */ new Date(),
16106
16144
  createdAt: /* @__PURE__ */ new Date()
@@ -17663,14 +17701,25 @@ var Mailer = class _Mailer {
17663
17701
  * same handler that processes the business event ("user upgraded").
17664
17702
  */
17665
17703
  async abortFlow(flowSlug, externalId, opts = {}) {
17666
- const parsed = abortFlowInputSchema.parse({ flowSlug, externalId, reason: opts.reason });
17704
+ const parsed = abortFlowInputSchema.parse({
17705
+ flowSlug,
17706
+ externalId,
17707
+ reason: opts.reason,
17708
+ matchTriggerProperties: opts.matchTriggerProperties
17709
+ });
17667
17710
  const flow = await this.collections.flows.findOne(
17668
17711
  { slug: parsed.flowSlug },
17669
17712
  { projection: { _id: 1 } }
17670
17713
  );
17671
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
+ );
17672
17721
  const result = await this.abortActiveRuns(
17673
- { externalId: parsed.externalId, flowId: flow._id },
17722
+ { externalId: parsed.externalId, flowId: flow._id, ...triggerMatch },
17674
17723
  parsed.reason ? `aborted_by_host:${parsed.reason}` : "aborted_by_host"
17675
17724
  );
17676
17725
  if (result.abortedRuns > 0 || result.cancelledSends > 0) {
@@ -17678,7 +17727,9 @@ var Mailer = class _Mailer {
17678
17727
  actor: "host",
17679
17728
  action: "flow.abort",
17680
17729
  resource: { collection: "mailer_flow_runs", slug: parsed.flowSlug },
17681
- diffSummary: `abortFlow slug=${parsed.flowSlug} externalId=${parsed.externalId} runs=${result.abortedRuns} sends=${result.cancelledSends}${parsed.reason ? ` reason=${parsed.reason}` : ""}`
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}` : ""}`
17682
17733
  });
17683
17734
  }
17684
17735
  return result;