@10x-media/webhooks 0.1.0-beta.1 → 0.1.0-beta.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @10x-media/webhooks
2
2
 
3
+ ## 0.1.0-beta.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Correct `payload` and `@payloadcms/ui` peer ranges to `^3.83.0`. The plugin uses `definePlugin`, which shipped in Payload 3.83.0, so 3.82.x installs satisfied the old range but failed at import.
8
+
9
+ ## 0.1.0-beta.2
10
+
11
+ ### Patch Changes
12
+
13
+ - `transform` now also applies to `previousData` on update deliveries, closing a gap where the prior document shipped unredacted when `includePreviousData` and a redacting `transform` were combined. The transform args gain a `target` field (`'data'` or `'previousData'`) naming the slot being built; on the `previousData` call `doc` is the prior document. Existing transforms need no change and now redact both slots.
14
+
15
+ Redelivery (and queued delivery attempts) short-circuit to a `dead` row when the subscription has been disabled, matching the dispatch path. Docs now state that redelivery targets the subscription's current URL.
16
+
17
+ - Redelivered delivery rows now store the subscription's current URL as `endpoint` instead of the stale URL captured on the original delivery. The subscription is resolved before the new row is created (in both queue and inline modes), so `endpoint` reflects where the retry actually targets. Missing and disabled subscriptions still dead-row without sending; a missing subscription falls back to the original endpoint, while a disabled one stores its live URL.
18
+
3
19
  ## 0.1.0-beta.1
4
20
 
5
21
  ### Minor Changes
@@ -7,7 +7,8 @@ const buildPayload = (args) => {
7
7
  doc,
8
8
  previousDoc,
9
9
  operation,
10
- req
10
+ req,
11
+ target: "data"
11
12
  }) : doc;
12
13
  const body = {
13
14
  id: deliveryId,
@@ -17,7 +18,12 @@ const buildPayload = (args) => {
17
18
  occurredAt,
18
19
  data
19
20
  };
20
- if (operation === "update" && config?.includePreviousData && previousDoc) body.previousData = previousDoc;
21
+ if (operation === "update" && config?.includePreviousData && previousDoc) body.previousData = config?.transform ? config.transform({
22
+ doc: previousDoc,
23
+ operation,
24
+ req,
25
+ target: "previousData"
26
+ }) : previousDoc;
21
27
  return body;
22
28
  };
23
29
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"buildPayload.js","names":[],"sources":["../../src/delivery/buildPayload.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { eventId } from '../events/eventTypes'\nimport type { CollectionWebhookConfig, WebhookOperation } from '../options'\n\n/** The JSON body POSTed to a subscriber. */\nexport type WebhookBody = {\n\tid: string\n\tevent: string\n\tcollection: string\n\toperation: WebhookOperation\n\toccurredAt: string\n\tdata: unknown\n\tpreviousData?: unknown\n}\n\n/** Build the outbound body, applying any per-collection transform/redaction. */\nexport const buildPayload = (args: {\n\tdeliveryId: string\n\tcollection: string\n\toperation: WebhookOperation\n\tdoc: Record<string, unknown>\n\tpreviousDoc?: Record<string, unknown>\n\toccurredAt: string\n\tconfig?: CollectionWebhookConfig\n\treq: PayloadRequest\n}): WebhookBody => {\n\tconst { deliveryId, collection, operation, doc, previousDoc, occurredAt, config, req } = args\n\tconst data = config?.transform ? config.transform({ doc, previousDoc, operation, req }) : doc\n\tconst body: WebhookBody = {\n\t\tid: deliveryId,\n\t\tevent: eventId(collection, operation),\n\t\tcollection,\n\t\toperation,\n\t\toccurredAt,\n\t\tdata,\n\t}\n\tif (operation === 'update' && config?.includePreviousData && previousDoc) {\n\t\tbody.previousData = previousDoc\n\t}\n\treturn body\n}\n"],"mappings":";;;AAiBA,MAAa,gBAAgB,SASV;CAClB,MAAM,EAAE,YAAY,YAAY,WAAW,KAAK,aAAa,YAAY,QAAQ,QAAQ;CACzF,MAAM,OAAO,QAAQ,YAAY,OAAO,UAAU;EAAE;EAAK;EAAa;EAAW;CAAI,CAAC,IAAI;CAC1F,MAAM,OAAoB;EACzB,IAAI;EACJ,OAAO,QAAQ,YAAY,SAAS;EACpC;EACA;EACA;EACA;CACD;CACA,IAAI,cAAc,YAAY,QAAQ,uBAAuB,aAC5D,KAAK,eAAe;CAErB,OAAO;AACR"}
1
+ {"version":3,"file":"buildPayload.js","names":[],"sources":["../../src/delivery/buildPayload.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { eventId } from '../events/eventTypes'\nimport type { CollectionWebhookConfig, WebhookOperation } from '../options'\n\n/** The JSON body POSTed to a subscriber. */\nexport type WebhookBody = {\n\tid: string\n\tevent: string\n\tcollection: string\n\toperation: WebhookOperation\n\toccurredAt: string\n\tdata: unknown\n\tpreviousData?: unknown\n}\n\n/** Build the outbound body, applying any per-collection transform/redaction. */\nexport const buildPayload = (args: {\n\tdeliveryId: string\n\tcollection: string\n\toperation: WebhookOperation\n\tdoc: Record<string, unknown>\n\tpreviousDoc?: Record<string, unknown>\n\toccurredAt: string\n\tconfig?: CollectionWebhookConfig\n\treq: PayloadRequest\n}): WebhookBody => {\n\tconst { deliveryId, collection, operation, doc, previousDoc, occurredAt, config, req } = args\n\tconst data = config?.transform\n\t\t? config.transform({ doc, previousDoc, operation, req, target: 'data' })\n\t\t: doc\n\tconst body: WebhookBody = {\n\t\tid: deliveryId,\n\t\tevent: eventId(collection, operation),\n\t\tcollection,\n\t\toperation,\n\t\toccurredAt,\n\t\tdata,\n\t}\n\tif (operation === 'update' && config?.includePreviousData && previousDoc) {\n\t\tbody.previousData = config?.transform\n\t\t\t? config.transform({ doc: previousDoc, operation, req, target: 'previousData' })\n\t\t\t: previousDoc\n\t}\n\treturn body\n}\n"],"mappings":";;;AAiBA,MAAa,gBAAgB,SASV;CAClB,MAAM,EAAE,YAAY,YAAY,WAAW,KAAK,aAAa,YAAY,QAAQ,QAAQ;CACzF,MAAM,OAAO,QAAQ,YAClB,OAAO,UAAU;EAAE;EAAK;EAAa;EAAW;EAAK,QAAQ;CAAO,CAAC,IACrE;CACH,MAAM,OAAoB;EACzB,IAAI;EACJ,OAAO,QAAQ,YAAY,SAAS;EACpC;EACA;EACA;EACA;CACD;CACA,IAAI,cAAc,YAAY,QAAQ,uBAAuB,aAC5D,KAAK,eAAe,QAAQ,YACzB,OAAO,UAAU;EAAE,KAAK;EAAa;EAAW;EAAK,QAAQ;CAAe,CAAC,IAC7E;CAEJ,OAAO;AACR"}
@@ -28,13 +28,13 @@ const buildDeliverTask = (deps) => ({
28
28
  payload,
29
29
  req
30
30
  });
31
- if (!subscription) {
31
+ if (!subscription?.enabled) {
32
32
  await payload.update({
33
33
  collection: deps.deliveriesSlug,
34
34
  id: deliveryId,
35
35
  data: {
36
36
  status: "dead",
37
- error: "subscription not found"
37
+ error: subscription ? "subscription disabled" : "subscription not found"
38
38
  },
39
39
  overrideAccess: true,
40
40
  req
@@ -1 +1 @@
1
- {"version":3,"file":"deliverTask.js","names":[],"sources":["../../src/delivery/deliverTask.ts"],"sourcesContent":["import type { TaskConfig } from 'payload'\n\nimport { WEBHOOK_DELIVER_TASK } from '../constants'\nimport type { CodeSubscription } from '../options'\nimport { resolveSubscriptionById } from '../plugin/resolveSubscriptions'\nimport { deriveDeliveryStatus } from './deriveDeliveryStatus'\nimport { sendDelivery } from './sendDelivery'\n\n/** Shared dependencies the delivery task closes over. */\nexport type DeliverTaskDeps = {\n\tdeliveriesSlug: string\n\tsubscriptionsSlug: string\n\tcodeSubscriptions: CodeSubscription[]\n\ttimeoutMs: number\n\tretries: number\n}\n\n/** Native Payload jobs task that performs one queued delivery attempt. */\nexport const buildDeliverTask = (deps: DeliverTaskDeps): TaskConfig =>\n\t({\n\t\tslug: WEBHOOK_DELIVER_TASK,\n\t\tretries: deps.retries,\n\t\tinputSchema: [{ name: 'deliveryId', type: 'text', required: true }],\n\t\thandler: async ({ input, job, req }) => {\n\t\t\tconst { payload } = req\n\t\t\tconst deliveryId = (input as { deliveryId: string }).deliveryId\n\t\t\tconst delivery = await payload.findByID({\n\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\tid: deliveryId,\n\t\t\t\toverrideAccess: true,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tconst subscription = await resolveSubscriptionById({\n\t\t\t\tid: String(delivery.subscriptionId),\n\t\t\t\tcodeSubscriptions: deps.codeSubscriptions,\n\t\t\t\tsubscriptionsSlug: deps.subscriptionsSlug,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!subscription) {\n\t\t\t\tawait payload.update({\n\t\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\t\tid: deliveryId,\n\t\t\t\t\tdata: { status: 'dead', error: 'subscription not found' },\n\t\t\t\t\toverrideAccess: true,\n\t\t\t\t\treq,\n\t\t\t\t})\n\t\t\t\treturn { output: {} }\n\t\t\t}\n\n\t\t\tconst attempt = Number(job.totalTried ?? 0) + 1\n\t\t\tconst result = await sendDelivery({\n\t\t\t\tsubscription,\n\t\t\t\tdeliveryId,\n\t\t\t\tevent: String(delivery.event),\n\t\t\t\tbody: JSON.stringify(delivery.payload),\n\t\t\t\ttimeoutMs: deps.timeoutMs,\n\t\t\t\tnow: Date.now(),\n\t\t\t})\n\t\t\tconst status = deriveDeliveryStatus({ ok: result.ok, attempt, maxRetries: deps.retries })\n\t\t\tawait payload.update({\n\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\tid: deliveryId,\n\t\t\t\tdata: {\n\t\t\t\t\tstatus,\n\t\t\t\t\tattempt,\n\t\t\t\t\tresponseStatus: result.responseStatus,\n\t\t\t\t\tresponseBody: result.responseBody,\n\t\t\t\t\terror: result.error,\n\t\t\t\t\tdurationMs: result.durationMs,\n\t\t\t\t\tjobId: String(job.id),\n\t\t\t\t},\n\t\t\t\toverrideAccess: true,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!result.ok) {\n\t\t\t\tthrow new Error(`Webhook delivery failed: ${result.error ?? result.responseStatus}`)\n\t\t\t}\n\t\t\treturn { output: {} }\n\t\t},\n\t}) as TaskConfig\n"],"mappings":";;;;;;AAkBA,MAAa,oBAAoB,UAC/B;CACA,MAAM;CACN,SAAS,KAAK;CACd,aAAa,CAAC;EAAE,MAAM;EAAc,MAAM;EAAQ,UAAU;CAAK,CAAC;CAClE,SAAS,OAAO,EAAE,OAAO,KAAK,UAAU;EACvC,MAAM,EAAE,YAAY;EACpB,MAAM,aAAc,MAAiC;EACrD,MAAM,WAAW,MAAM,QAAQ,SAAS;GACvC,YAAY,KAAK;GACjB,IAAI;GACJ,gBAAgB;GAChB;EACD,CAAC;EACD,MAAM,eAAe,MAAM,wBAAwB;GAClD,IAAI,OAAO,SAAS,cAAc;GAClC,mBAAmB,KAAK;GACxB,mBAAmB,KAAK;GACxB;GACA;EACD,CAAC;EACD,IAAI,CAAC,cAAc;GAClB,MAAM,QAAQ,OAAO;IACpB,YAAY,KAAK;IACjB,IAAI;IACJ,MAAM;KAAE,QAAQ;KAAQ,OAAO;IAAyB;IACxD,gBAAgB;IAChB;GACD,CAAC;GACD,OAAO,EAAE,QAAQ,CAAC,EAAE;EACrB;EAEA,MAAM,UAAU,OAAO,IAAI,cAAc,CAAC,IAAI;EAC9C,MAAM,SAAS,MAAM,aAAa;GACjC;GACA;GACA,OAAO,OAAO,SAAS,KAAK;GAC5B,MAAM,KAAK,UAAU,SAAS,OAAO;GACrC,WAAW,KAAK;GAChB,KAAK,KAAK,IAAI;EACf,CAAC;EACD,MAAM,SAAS,qBAAqB;GAAE,IAAI,OAAO;GAAI;GAAS,YAAY,KAAK;EAAQ,CAAC;EACxF,MAAM,QAAQ,OAAO;GACpB,YAAY,KAAK;GACjB,IAAI;GACJ,MAAM;IACL;IACA;IACA,gBAAgB,OAAO;IACvB,cAAc,OAAO;IACrB,OAAO,OAAO;IACd,YAAY,OAAO;IACnB,OAAO,OAAO,IAAI,EAAE;GACrB;GACA,gBAAgB;GAChB;EACD,CAAC;EACD,IAAI,CAAC,OAAO,IACX,MAAM,IAAI,MAAM,4BAA4B,OAAO,SAAS,OAAO,gBAAgB;EAEpF,OAAO,EAAE,QAAQ,CAAC,EAAE;CACrB;AACD"}
1
+ {"version":3,"file":"deliverTask.js","names":[],"sources":["../../src/delivery/deliverTask.ts"],"sourcesContent":["import type { TaskConfig } from 'payload'\n\nimport { WEBHOOK_DELIVER_TASK } from '../constants'\nimport type { CodeSubscription } from '../options'\nimport { resolveSubscriptionById } from '../plugin/resolveSubscriptions'\nimport { deriveDeliveryStatus } from './deriveDeliveryStatus'\nimport { sendDelivery } from './sendDelivery'\n\n/** Shared dependencies the delivery task closes over. */\nexport type DeliverTaskDeps = {\n\tdeliveriesSlug: string\n\tsubscriptionsSlug: string\n\tcodeSubscriptions: CodeSubscription[]\n\ttimeoutMs: number\n\tretries: number\n}\n\n/** Native Payload jobs task that performs one queued delivery attempt. */\nexport const buildDeliverTask = (deps: DeliverTaskDeps): TaskConfig =>\n\t({\n\t\tslug: WEBHOOK_DELIVER_TASK,\n\t\tretries: deps.retries,\n\t\tinputSchema: [{ name: 'deliveryId', type: 'text', required: true }],\n\t\thandler: async ({ input, job, req }) => {\n\t\t\tconst { payload } = req\n\t\t\tconst deliveryId = (input as { deliveryId: string }).deliveryId\n\t\t\tconst delivery = await payload.findByID({\n\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\tid: deliveryId,\n\t\t\t\toverrideAccess: true,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tconst subscription = await resolveSubscriptionById({\n\t\t\t\tid: String(delivery.subscriptionId),\n\t\t\t\tcodeSubscriptions: deps.codeSubscriptions,\n\t\t\t\tsubscriptionsSlug: deps.subscriptionsSlug,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!subscription?.enabled) {\n\t\t\t\tawait payload.update({\n\t\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\t\tid: deliveryId,\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tstatus: 'dead',\n\t\t\t\t\t\terror: subscription ? 'subscription disabled' : 'subscription not found',\n\t\t\t\t\t},\n\t\t\t\t\toverrideAccess: true,\n\t\t\t\t\treq,\n\t\t\t\t})\n\t\t\t\treturn { output: {} }\n\t\t\t}\n\n\t\t\tconst attempt = Number(job.totalTried ?? 0) + 1\n\t\t\tconst result = await sendDelivery({\n\t\t\t\tsubscription,\n\t\t\t\tdeliveryId,\n\t\t\t\tevent: String(delivery.event),\n\t\t\t\tbody: JSON.stringify(delivery.payload),\n\t\t\t\ttimeoutMs: deps.timeoutMs,\n\t\t\t\tnow: Date.now(),\n\t\t\t})\n\t\t\tconst status = deriveDeliveryStatus({ ok: result.ok, attempt, maxRetries: deps.retries })\n\t\t\tawait payload.update({\n\t\t\t\tcollection: deps.deliveriesSlug,\n\t\t\t\tid: deliveryId,\n\t\t\t\tdata: {\n\t\t\t\t\tstatus,\n\t\t\t\t\tattempt,\n\t\t\t\t\tresponseStatus: result.responseStatus,\n\t\t\t\t\tresponseBody: result.responseBody,\n\t\t\t\t\terror: result.error,\n\t\t\t\t\tdurationMs: result.durationMs,\n\t\t\t\t\tjobId: String(job.id),\n\t\t\t\t},\n\t\t\t\toverrideAccess: true,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!result.ok) {\n\t\t\t\tthrow new Error(`Webhook delivery failed: ${result.error ?? result.responseStatus}`)\n\t\t\t}\n\t\t\treturn { output: {} }\n\t\t},\n\t}) as TaskConfig\n"],"mappings":";;;;;;AAkBA,MAAa,oBAAoB,UAC/B;CACA,MAAM;CACN,SAAS,KAAK;CACd,aAAa,CAAC;EAAE,MAAM;EAAc,MAAM;EAAQ,UAAU;CAAK,CAAC;CAClE,SAAS,OAAO,EAAE,OAAO,KAAK,UAAU;EACvC,MAAM,EAAE,YAAY;EACpB,MAAM,aAAc,MAAiC;EACrD,MAAM,WAAW,MAAM,QAAQ,SAAS;GACvC,YAAY,KAAK;GACjB,IAAI;GACJ,gBAAgB;GAChB;EACD,CAAC;EACD,MAAM,eAAe,MAAM,wBAAwB;GAClD,IAAI,OAAO,SAAS,cAAc;GAClC,mBAAmB,KAAK;GACxB,mBAAmB,KAAK;GACxB;GACA;EACD,CAAC;EACD,IAAI,CAAC,cAAc,SAAS;GAC3B,MAAM,QAAQ,OAAO;IACpB,YAAY,KAAK;IACjB,IAAI;IACJ,MAAM;KACL,QAAQ;KACR,OAAO,eAAe,0BAA0B;IACjD;IACA,gBAAgB;IAChB;GACD,CAAC;GACD,OAAO,EAAE,QAAQ,CAAC,EAAE;EACrB;EAEA,MAAM,UAAU,OAAO,IAAI,cAAc,CAAC,IAAI;EAC9C,MAAM,SAAS,MAAM,aAAa;GACjC;GACA;GACA,OAAO,OAAO,SAAS,KAAK;GAC5B,MAAM,KAAK,UAAU,SAAS,OAAO;GACrC,WAAW,KAAK;GAChB,KAAK,KAAK,IAAI;EACf,CAAC;EACD,MAAM,SAAS,qBAAqB;GAAE,IAAI,OAAO;GAAI;GAAS,YAAY,KAAK;EAAQ,CAAC;EACxF,MAAM,QAAQ,OAAO;GACpB,YAAY,KAAK;GACjB,IAAI;GACJ,MAAM;IACL;IACA;IACA,gBAAgB,OAAO;IACvB,cAAc,OAAO;IACrB,OAAO,OAAO;IACd,YAAY,OAAO;IACnB,OAAO,OAAO,IAAI,EAAE;GACrB;GACA,gBAAgB;GAChB;EACD,CAAC;EACD,IAAI,CAAC,OAAO,IACX,MAAM,IAAI,MAAM,4BAA4B,OAAO,SAAS,OAAO,gBAAgB;EAEpF,OAAO,EAAE,QAAQ,CAAC,EAAE;CACrB;AACD"}
@@ -11,11 +11,18 @@ const redeliverDelivery = async (args) => {
11
11
  overrideAccess: true,
12
12
  req
13
13
  });
14
+ const subscription = await resolveSubscriptionById({
15
+ id: String(original.subscriptionId),
16
+ codeSubscriptions: deps.codeSubscriptions,
17
+ subscriptionsSlug: deps.subscriptionsSlug,
18
+ payload,
19
+ req
20
+ });
14
21
  const created = await payload.create({
15
22
  collection: deps.deliveriesSlug,
16
23
  data: {
17
24
  subscriptionId: original.subscriptionId,
18
- endpoint: original.endpoint,
25
+ endpoint: subscription?.url ?? original.endpoint,
19
26
  event: original.event,
20
27
  payload: original.payload,
21
28
  status: "pending",
@@ -33,20 +40,13 @@ const redeliverDelivery = async (args) => {
33
40
  });
34
41
  return { id: newId };
35
42
  }
36
- const subscription = await resolveSubscriptionById({
37
- id: String(original.subscriptionId),
38
- codeSubscriptions: deps.codeSubscriptions,
39
- subscriptionsSlug: deps.subscriptionsSlug,
40
- payload,
41
- req
42
- });
43
- if (!subscription) {
43
+ if (!subscription?.enabled) {
44
44
  await payload.update({
45
45
  collection: deps.deliveriesSlug,
46
46
  id: newId,
47
47
  data: {
48
48
  status: "dead",
49
- error: "subscription not found"
49
+ error: subscription ? "subscription disabled" : "subscription not found"
50
50
  },
51
51
  overrideAccess: true,
52
52
  req
@@ -1 +1 @@
1
- {"version":3,"file":"redeliver.js","names":[],"sources":["../../src/delivery/redeliver.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\nimport { WEBHOOK_DELIVER_TASK } from '../constants'\nimport type { CodeSubscription } from '../options'\nimport { resolveSubscriptionById } from '../plugin/resolveSubscriptions'\nimport { sendDelivery } from './sendDelivery'\n\n/** Dependencies for re-dispatching a stored delivery. */\nexport type RedeliverDeps = {\n\tdeliveriesSlug: string\n\tsubscriptionsSlug: string\n\tcodeSubscriptions: CodeSubscription[]\n\tmode: 'queue' | 'inline'\n\ttimeoutMs: number\n\tqueue: string\n}\n\n/** Re-dispatch a past delivery from its stored payload, creating a new linked row. */\nexport const redeliverDelivery = async (args: {\n\tdeps: RedeliverDeps\n\tdeliveryId: string\n\tpayload: Payload\n\treq: PayloadRequest\n}): Promise<{ id: string }> => {\n\tconst { deps, deliveryId, payload, req } = args\n\tconst original = await payload.findByID({\n\t\tcollection: deps.deliveriesSlug,\n\t\tid: deliveryId,\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\tconst created = await payload.create({\n\t\tcollection: deps.deliveriesSlug,\n\t\tdata: {\n\t\t\tsubscriptionId: original.subscriptionId,\n\t\t\tendpoint: original.endpoint,\n\t\t\tevent: original.event,\n\t\t\tpayload: original.payload,\n\t\t\tstatus: 'pending',\n\t\t\tattempt: 0,\n\t\t},\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\tconst newId = String(created.id)\n\n\tif (deps.mode === 'queue') {\n\t\tawait payload.jobs.queue({\n\t\t\ttask: WEBHOOK_DELIVER_TASK,\n\t\t\tinput: { deliveryId: newId },\n\t\t\tqueue: deps.queue,\n\t\t})\n\t\treturn { id: newId }\n\t}\n\n\tconst subscription = await resolveSubscriptionById({\n\t\tid: String(original.subscriptionId),\n\t\tcodeSubscriptions: deps.codeSubscriptions,\n\t\tsubscriptionsSlug: deps.subscriptionsSlug,\n\t\tpayload,\n\t\treq,\n\t})\n\tif (!subscription) {\n\t\tawait payload.update({\n\t\t\tcollection: deps.deliveriesSlug,\n\t\t\tid: newId,\n\t\t\tdata: { status: 'dead', error: 'subscription not found' },\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\treturn { id: newId }\n\t}\n\tconst result = await sendDelivery({\n\t\tsubscription,\n\t\tdeliveryId: newId,\n\t\tevent: String(original.event),\n\t\tbody: JSON.stringify(original.payload),\n\t\ttimeoutMs: deps.timeoutMs,\n\t\tnow: Date.now(),\n\t})\n\tawait payload.update({\n\t\tcollection: deps.deliveriesSlug,\n\t\tid: newId,\n\t\tdata: {\n\t\t\tstatus: result.ok ? 'success' : 'dead',\n\t\t\tattempt: 1,\n\t\t\tresponseStatus: result.responseStatus,\n\t\t\tresponseBody: result.responseBody,\n\t\t\terror: result.error,\n\t\t\tdurationMs: result.durationMs,\n\t\t},\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\treturn { id: newId }\n}\n"],"mappings":";;;;;AAkBA,MAAa,oBAAoB,OAAO,SAKT;CAC9B,MAAM,EAAE,MAAM,YAAY,SAAS,QAAQ;CAC3C,MAAM,WAAW,MAAM,QAAQ,SAAS;EACvC,YAAY,KAAK;EACjB,IAAI;EACJ,gBAAgB;EAChB;CACD,CAAC;CACD,MAAM,UAAU,MAAM,QAAQ,OAAO;EACpC,YAAY,KAAK;EACjB,MAAM;GACL,gBAAgB,SAAS;GACzB,UAAU,SAAS;GACnB,OAAO,SAAS;GAChB,SAAS,SAAS;GAClB,QAAQ;GACR,SAAS;EACV;EACA,gBAAgB;EAChB;CACD,CAAC;CACD,MAAM,QAAQ,OAAO,QAAQ,EAAE;CAE/B,IAAI,KAAK,SAAS,SAAS;EAC1B,MAAM,QAAQ,KAAK,MAAM;GACxB,MAAM;GACN,OAAO,EAAE,YAAY,MAAM;GAC3B,OAAO,KAAK;EACb,CAAC;EACD,OAAO,EAAE,IAAI,MAAM;CACpB;CAEA,MAAM,eAAe,MAAM,wBAAwB;EAClD,IAAI,OAAO,SAAS,cAAc;EAClC,mBAAmB,KAAK;EACxB,mBAAmB,KAAK;EACxB;EACA;CACD,CAAC;CACD,IAAI,CAAC,cAAc;EAClB,MAAM,QAAQ,OAAO;GACpB,YAAY,KAAK;GACjB,IAAI;GACJ,MAAM;IAAE,QAAQ;IAAQ,OAAO;GAAyB;GACxD,gBAAgB;GAChB;EACD,CAAC;EACD,OAAO,EAAE,IAAI,MAAM;CACpB;CACA,MAAM,SAAS,MAAM,aAAa;EACjC;EACA,YAAY;EACZ,OAAO,OAAO,SAAS,KAAK;EAC5B,MAAM,KAAK,UAAU,SAAS,OAAO;EACrC,WAAW,KAAK;EAChB,KAAK,KAAK,IAAI;CACf,CAAC;CACD,MAAM,QAAQ,OAAO;EACpB,YAAY,KAAK;EACjB,IAAI;EACJ,MAAM;GACL,QAAQ,OAAO,KAAK,YAAY;GAChC,SAAS;GACT,gBAAgB,OAAO;GACvB,cAAc,OAAO;GACrB,OAAO,OAAO;GACd,YAAY,OAAO;EACpB;EACA,gBAAgB;EAChB;CACD,CAAC;CACD,OAAO,EAAE,IAAI,MAAM;AACpB"}
1
+ {"version":3,"file":"redeliver.js","names":[],"sources":["../../src/delivery/redeliver.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\nimport { WEBHOOK_DELIVER_TASK } from '../constants'\nimport type { CodeSubscription } from '../options'\nimport { resolveSubscriptionById } from '../plugin/resolveSubscriptions'\nimport { sendDelivery } from './sendDelivery'\n\n/** Dependencies for re-dispatching a stored delivery. */\nexport type RedeliverDeps = {\n\tdeliveriesSlug: string\n\tsubscriptionsSlug: string\n\tcodeSubscriptions: CodeSubscription[]\n\tmode: 'queue' | 'inline'\n\ttimeoutMs: number\n\tqueue: string\n}\n\n/** Re-dispatch a past delivery from its stored payload, creating a new linked row. */\nexport const redeliverDelivery = async (args: {\n\tdeps: RedeliverDeps\n\tdeliveryId: string\n\tpayload: Payload\n\treq: PayloadRequest\n}): Promise<{ id: string }> => {\n\tconst { deps, deliveryId, payload, req } = args\n\tconst original = await payload.findByID({\n\t\tcollection: deps.deliveriesSlug,\n\t\tid: deliveryId,\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\t// Resolved up front (cheap) so the new row's endpoint reflects the subscription's current URL\n\t// rather than whatever was stored at the time of the original delivery.\n\tconst subscription = await resolveSubscriptionById({\n\t\tid: String(original.subscriptionId),\n\t\tcodeSubscriptions: deps.codeSubscriptions,\n\t\tsubscriptionsSlug: deps.subscriptionsSlug,\n\t\tpayload,\n\t\treq,\n\t})\n\tconst created = await payload.create({\n\t\tcollection: deps.deliveriesSlug,\n\t\tdata: {\n\t\t\tsubscriptionId: original.subscriptionId,\n\t\t\tendpoint: subscription?.url ?? original.endpoint,\n\t\t\tevent: original.event,\n\t\t\tpayload: original.payload,\n\t\t\tstatus: 'pending',\n\t\t\tattempt: 0,\n\t\t},\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\tconst newId = String(created.id)\n\n\tif (deps.mode === 'queue') {\n\t\t// deliverTask re-resolves the subscription (and re-checks enabled) when it runs, so no\n\t\t// missing/disabled gate is needed here.\n\t\tawait payload.jobs.queue({\n\t\t\ttask: WEBHOOK_DELIVER_TASK,\n\t\t\tinput: { deliveryId: newId },\n\t\t\tqueue: deps.queue,\n\t\t})\n\t\treturn { id: newId }\n\t}\n\n\tif (!subscription?.enabled) {\n\t\tawait payload.update({\n\t\t\tcollection: deps.deliveriesSlug,\n\t\t\tid: newId,\n\t\t\tdata: {\n\t\t\t\tstatus: 'dead',\n\t\t\t\terror: subscription ? 'subscription disabled' : 'subscription not found',\n\t\t\t},\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\treturn { id: newId }\n\t}\n\tconst result = await sendDelivery({\n\t\tsubscription,\n\t\tdeliveryId: newId,\n\t\tevent: String(original.event),\n\t\tbody: JSON.stringify(original.payload),\n\t\ttimeoutMs: deps.timeoutMs,\n\t\tnow: Date.now(),\n\t})\n\tawait payload.update({\n\t\tcollection: deps.deliveriesSlug,\n\t\tid: newId,\n\t\tdata: {\n\t\t\tstatus: result.ok ? 'success' : 'dead',\n\t\t\tattempt: 1,\n\t\t\tresponseStatus: result.responseStatus,\n\t\t\tresponseBody: result.responseBody,\n\t\t\terror: result.error,\n\t\t\tdurationMs: result.durationMs,\n\t\t},\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\treturn { id: newId }\n}\n"],"mappings":";;;;;AAkBA,MAAa,oBAAoB,OAAO,SAKT;CAC9B,MAAM,EAAE,MAAM,YAAY,SAAS,QAAQ;CAC3C,MAAM,WAAW,MAAM,QAAQ,SAAS;EACvC,YAAY,KAAK;EACjB,IAAI;EACJ,gBAAgB;EAChB;CACD,CAAC;CAGD,MAAM,eAAe,MAAM,wBAAwB;EAClD,IAAI,OAAO,SAAS,cAAc;EAClC,mBAAmB,KAAK;EACxB,mBAAmB,KAAK;EACxB;EACA;CACD,CAAC;CACD,MAAM,UAAU,MAAM,QAAQ,OAAO;EACpC,YAAY,KAAK;EACjB,MAAM;GACL,gBAAgB,SAAS;GACzB,UAAU,cAAc,OAAO,SAAS;GACxC,OAAO,SAAS;GAChB,SAAS,SAAS;GAClB,QAAQ;GACR,SAAS;EACV;EACA,gBAAgB;EAChB;CACD,CAAC;CACD,MAAM,QAAQ,OAAO,QAAQ,EAAE;CAE/B,IAAI,KAAK,SAAS,SAAS;EAG1B,MAAM,QAAQ,KAAK,MAAM;GACxB,MAAM;GACN,OAAO,EAAE,YAAY,MAAM;GAC3B,OAAO,KAAK;EACb,CAAC;EACD,OAAO,EAAE,IAAI,MAAM;CACpB;CAEA,IAAI,CAAC,cAAc,SAAS;EAC3B,MAAM,QAAQ,OAAO;GACpB,YAAY,KAAK;GACjB,IAAI;GACJ,MAAM;IACL,QAAQ;IACR,OAAO,eAAe,0BAA0B;GACjD;GACA,gBAAgB;GAChB;EACD,CAAC;EACD,OAAO,EAAE,IAAI,MAAM;CACpB;CACA,MAAM,SAAS,MAAM,aAAa;EACjC;EACA,YAAY;EACZ,OAAO,OAAO,SAAS,KAAK;EAC5B,MAAM,KAAK,UAAU,SAAS,OAAO;EACrC,WAAW,KAAK;EAChB,KAAK,KAAK,IAAI;CACf,CAAC;CACD,MAAM,QAAQ,OAAO;EACpB,YAAY,KAAK;EACjB,IAAI;EACJ,MAAM;GACL,QAAQ,OAAO,KAAK,YAAY;GAChC,SAAS;GACT,gBAAgB,OAAO;GACvB,cAAc,OAAO;GACrB,OAAO,OAAO;GACd,YAAY,OAAO;EACpB;EACA,gBAAgB;EAChB;CACD,CAAC;CACD,OAAO,EAAE,IAAI,MAAM;AACpB"}
package/dist/options.d.ts CHANGED
@@ -6,11 +6,18 @@ type WebhookOperation = 'create' | 'update' | 'delete';
6
6
  type CollectionWebhookConfig = {
7
7
  operations?: WebhookOperation[];
8
8
  includePreviousData?: boolean;
9
+ /**
10
+ * Reshape or redact a document before it is sent. Applied to the body's `data` and,
11
+ * when `includePreviousData` is set, to `previousData` as well, so redaction cannot
12
+ * be bypassed through the prior document. `target` names the slot being built; on
13
+ * the `previousData` call `doc` is the prior document and `previousDoc` is undefined.
14
+ */
9
15
  transform?: (args: {
10
16
  doc: Record<string, unknown>;
11
17
  previousDoc?: Record<string, unknown>;
12
18
  operation: WebhookOperation;
13
19
  req: PayloadRequest;
20
+ target: 'data' | 'previousData';
14
21
  }) => unknown;
15
22
  };
16
23
  type CodeSubscription = {
@@ -1 +1 @@
1
- {"version":3,"file":"options.js","names":[],"sources":["../src/options.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { DEFAULT_DELIVERY_QUEUE, DEFAULT_RETRIES, DEFAULT_TIMEOUT_MS } from './constants'\nimport type { TranslationsOption } from './translations'\n\nexport type WebhookOperation = 'create' | 'update' | 'delete'\n\nexport type CollectionWebhookConfig = {\n\toperations?: WebhookOperation[]\n\tincludePreviousData?: boolean\n\ttransform?: (args: {\n\t\tdoc: Record<string, unknown>\n\t\tpreviousDoc?: Record<string, unknown>\n\t\toperation: WebhookOperation\n\t\treq: PayloadRequest\n\t}) => unknown\n}\n\nexport type CodeSubscription = {\n\tid: string\n\turl: string\n\tevents: string[]\n\tsecret?: string\n\theaders?: Record<string, string>\n\tenabled?: boolean\n}\n\nexport type DeliveryMode = 'auto' | 'queue' | 'inline'\n\nexport type DeliveryOptions = {\n\tmode?: DeliveryMode\n\ttimeoutMs?: number\n\tretries?: number\n\tqueue?: string\n}\n\nexport type WebhooksPluginOptions = {\n\tdisabled?: boolean\n\t/**\n\t * Per-locale overrides for this plugin's UI strings, keyed by the typed\n\t * translation keys exported from `@10x-media/webhooks/i18n`. Values win over\n\t * the built-in locales key-by-key; locales the plugin does not ship are added\n\t * whole. App-level `i18n.translations` still wins over both.\n\t */\n\ttranslations?: TranslationsOption\n\tcollections?: Record<string, true | CollectionWebhookConfig>\n\tsubscriptions?: CodeSubscription[]\n\tdelivery?: DeliveryMode | DeliveryOptions\n\tsubscriptionsCollection?: { slug?: string; hidden?: boolean }\n\tdeliveriesLog?: { slug?: string; hidden?: boolean }\n}\n\nexport type ResolvedDeliveryOptions = {\n\tmode: DeliveryMode\n\ttimeoutMs: number\n\tretries: number\n\tqueue: string\n}\n\nexport const resolveDeliveryOptions = (\n\tdelivery: WebhooksPluginOptions['delivery']\n): ResolvedDeliveryOptions => {\n\tconst opts: DeliveryOptions =\n\t\tdelivery === undefined ? {} : typeof delivery === 'string' ? { mode: delivery } : delivery\n\treturn {\n\t\tmode: opts.mode ?? 'auto',\n\t\ttimeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n\t\tretries: opts.retries ?? DEFAULT_RETRIES,\n\t\tqueue: opts.queue ?? DEFAULT_DELIVERY_QUEUE,\n\t}\n}\n"],"mappings":";;AA2DA,MAAa,0BACZ,aAC6B;CAC7B,MAAM,OACL,aAAa,KAAA,IAAY,CAAC,IAAI,OAAO,aAAa,WAAW,EAAE,MAAM,SAAS,IAAI;CACnF,OAAO;EACN,MAAM,KAAK,QAAQ;EACnB,WAAW,KAAK,aAAA;EAChB,SAAS,KAAK,WAAA;EACd,OAAO,KAAK,SAAA;CACb;AACD"}
1
+ {"version":3,"file":"options.js","names":[],"sources":["../src/options.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { DEFAULT_DELIVERY_QUEUE, DEFAULT_RETRIES, DEFAULT_TIMEOUT_MS } from './constants'\nimport type { TranslationsOption } from './translations'\n\nexport type WebhookOperation = 'create' | 'update' | 'delete'\n\nexport type CollectionWebhookConfig = {\n\toperations?: WebhookOperation[]\n\tincludePreviousData?: boolean\n\t/**\n\t * Reshape or redact a document before it is sent. Applied to the body's `data` and,\n\t * when `includePreviousData` is set, to `previousData` as well, so redaction cannot\n\t * be bypassed through the prior document. `target` names the slot being built; on\n\t * the `previousData` call `doc` is the prior document and `previousDoc` is undefined.\n\t */\n\ttransform?: (args: {\n\t\tdoc: Record<string, unknown>\n\t\tpreviousDoc?: Record<string, unknown>\n\t\toperation: WebhookOperation\n\t\treq: PayloadRequest\n\t\ttarget: 'data' | 'previousData'\n\t}) => unknown\n}\n\nexport type CodeSubscription = {\n\tid: string\n\turl: string\n\tevents: string[]\n\tsecret?: string\n\theaders?: Record<string, string>\n\tenabled?: boolean\n}\n\nexport type DeliveryMode = 'auto' | 'queue' | 'inline'\n\nexport type DeliveryOptions = {\n\tmode?: DeliveryMode\n\ttimeoutMs?: number\n\tretries?: number\n\tqueue?: string\n}\n\nexport type WebhooksPluginOptions = {\n\tdisabled?: boolean\n\t/**\n\t * Per-locale overrides for this plugin's UI strings, keyed by the typed\n\t * translation keys exported from `@10x-media/webhooks/i18n`. Values win over\n\t * the built-in locales key-by-key; locales the plugin does not ship are added\n\t * whole. App-level `i18n.translations` still wins over both.\n\t */\n\ttranslations?: TranslationsOption\n\tcollections?: Record<string, true | CollectionWebhookConfig>\n\tsubscriptions?: CodeSubscription[]\n\tdelivery?: DeliveryMode | DeliveryOptions\n\tsubscriptionsCollection?: { slug?: string; hidden?: boolean }\n\tdeliveriesLog?: { slug?: string; hidden?: boolean }\n}\n\nexport type ResolvedDeliveryOptions = {\n\tmode: DeliveryMode\n\ttimeoutMs: number\n\tretries: number\n\tqueue: string\n}\n\nexport const resolveDeliveryOptions = (\n\tdelivery: WebhooksPluginOptions['delivery']\n): ResolvedDeliveryOptions => {\n\tconst opts: DeliveryOptions =\n\t\tdelivery === undefined ? {} : typeof delivery === 'string' ? { mode: delivery } : delivery\n\treturn {\n\t\tmode: opts.mode ?? 'auto',\n\t\ttimeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n\t\tretries: opts.retries ?? DEFAULT_RETRIES,\n\t\tqueue: opts.queue ?? DEFAULT_DELIVERY_QUEUE,\n\t}\n}\n"],"mappings":";;AAkEA,MAAa,0BACZ,aAC6B;CAC7B,MAAM,OACL,aAAa,KAAA,IAAY,CAAC,IAAI,OAAO,aAAa,WAAW,EAAE,MAAM,SAAS,IAAI;CACnF,OAAO;EACN,MAAM,KAAK,QAAQ;EACnB,WAAW,KAAK,aAAA;EAChB,SAAS,KAAK,WAAA;EACd,OAAO,KAAK,SAAA;CACb;AACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@10x-media/webhooks",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.3",
4
4
  "description": "Outbound webhook subscriptions for Payload v3",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -50,8 +50,8 @@
50
50
  "LICENSE"
51
51
  ],
52
52
  "peerDependencies": {
53
- "@payloadcms/ui": "^3.82.0",
54
- "payload": "^3.82.0",
53
+ "@payloadcms/ui": "^3.83.0",
54
+ "payload": "^3.83.0",
55
55
  "react": "^19.0.0",
56
56
  "react-dom": "^19.0.0"
57
57
  },
@@ -72,8 +72,8 @@
72
72
  "vitest": "4.1.7",
73
73
  "@10x-media/payload-test-harness": "0.0.0",
74
74
  "@10x-media/tsconfig": "0.0.0",
75
- "@10x-media/tsdown-config": "0.0.0",
76
- "@10x-media/vitest-config": "0.0.0"
75
+ "@10x-media/vitest-config": "0.0.0",
76
+ "@10x-media/tsdown-config": "0.0.0"
77
77
  },
78
78
  "publishConfig": {
79
79
  "access": "public"