@estifanos-sh/convex-auth 0.0.2 → 0.0.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.
Files changed (298) hide show
  1. package/README.md +5 -5
  2. package/dist/_generated/component.d.ts +130 -61
  3. package/dist/_generated/component.d.ts.map +1 -1
  4. package/dist/_generated/dataModel.d.ts +28 -0
  5. package/dist/_generated/dataModel.d.ts.map +1 -0
  6. package/dist/bin.js +1 -2
  7. package/dist/browser/index.d.ts +2 -2
  8. package/dist/browser/index.d.ts.map +1 -1
  9. package/dist/browser/index.js.map +1 -1
  10. package/dist/client/core/types.d.ts +40 -79
  11. package/dist/client/core/types.d.ts.map +1 -1
  12. package/dist/client/core/types.js.map +1 -1
  13. package/dist/client/errors.d.ts +15 -0
  14. package/dist/client/errors.d.ts.map +1 -0
  15. package/dist/client/errors.js +8 -1
  16. package/dist/client/errors.js.map +1 -1
  17. package/dist/client/index.d.ts +6 -3
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +3 -2
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/component/_generated/component.d.ts +130 -61
  22. package/dist/component/_generated/component.d.ts.map +1 -1
  23. package/dist/component/_generated/dataModel.d.ts +1 -1
  24. package/dist/component/_generated/server.d.ts +1 -1
  25. package/dist/component/account.d.ts +43 -37
  26. package/dist/component/account.d.ts.map +1 -1
  27. package/dist/component/account.js +14 -16
  28. package/dist/component/account.js.map +1 -1
  29. package/dist/component/connection/audit.d.ts +29 -29
  30. package/dist/component/connection/audit.d.ts.map +1 -1
  31. package/dist/component/connection/audit.js +2 -1
  32. package/dist/component/connection/audit.js.map +1 -1
  33. package/dist/component/connection/cache.d.ts +6 -6
  34. package/dist/component/connection/domain/verification.d.ts +19 -19
  35. package/dist/component/connection/domain.d.ts +20 -20
  36. package/dist/component/connection/saml/assertion.d.ts +4 -4
  37. package/dist/component/connection/saml/request.d.ts +9 -9
  38. package/dist/component/connection/saml/request.d.ts.map +1 -1
  39. package/dist/component/connection/scim/config.d.ts +13 -13
  40. package/dist/component/connection/scim/identity.d.ts +102 -59
  41. package/dist/component/connection/scim/identity.d.ts.map +1 -1
  42. package/dist/component/connection/scim/identity.js +357 -56
  43. package/dist/component/connection/scim/identity.js.map +1 -1
  44. package/dist/component/connection/secret.d.ts +15 -15
  45. package/dist/component/connection/secret.d.ts.map +1 -1
  46. package/dist/component/connection/webhook/delivery.d.ts +69 -53
  47. package/dist/component/connection/webhook/delivery.d.ts.map +1 -1
  48. package/dist/component/connection/webhook/delivery.js +127 -170
  49. package/dist/component/connection/webhook/delivery.js.map +1 -1
  50. package/dist/component/connection/webhook/endpoint.d.ts +21 -21
  51. package/dist/component/connection/webhook/events.d.ts +26 -0
  52. package/dist/component/connection/webhook/events.d.ts.map +1 -0
  53. package/dist/component/connection/webhook/events.js +39 -0
  54. package/dist/component/connection/webhook/events.js.map +1 -0
  55. package/dist/component/connection.d.ts +38 -38
  56. package/dist/component/connection.d.ts.map +1 -1
  57. package/dist/component/context.d.ts +115 -0
  58. package/dist/component/context.d.ts.map +1 -0
  59. package/dist/component/context.js +50 -0
  60. package/dist/component/context.js.map +1 -0
  61. package/dist/component/convex.config.d.ts +2 -2
  62. package/dist/component/crons.d.ts +2 -2
  63. package/dist/component/event.d.ts +134 -138
  64. package/dist/component/event.d.ts.map +1 -1
  65. package/dist/component/event.js +88 -117
  66. package/dist/component/event.js.map +1 -1
  67. package/dist/component/eventstream.d.ts +30 -0
  68. package/dist/component/eventstream.d.ts.map +1 -0
  69. package/dist/component/eventstream.js +51 -0
  70. package/dist/component/eventstream.js.map +1 -0
  71. package/dist/component/factor/device.d.ts +25 -24
  72. package/dist/component/factor/device.d.ts.map +1 -1
  73. package/dist/component/factor/device.js +5 -2
  74. package/dist/component/factor/device.js.map +1 -1
  75. package/dist/component/factor/passkey.d.ts +84 -75
  76. package/dist/component/factor/passkey.d.ts.map +1 -1
  77. package/dist/component/factor/passkey.js +6 -6
  78. package/dist/component/factor/passkey.js.map +1 -1
  79. package/dist/component/factor/totp.d.ts +54 -49
  80. package/dist/component/factor/totp.d.ts.map +1 -1
  81. package/dist/component/factor/totp.js +3 -3
  82. package/dist/component/factor/totp.js.map +1 -1
  83. package/dist/component/group/active.d.ts +326 -22
  84. package/dist/component/group/active.d.ts.map +1 -1
  85. package/dist/component/group/active.js +21 -11
  86. package/dist/component/group/active.js.map +1 -1
  87. package/dist/component/group/invite.d.ts +28 -28
  88. package/dist/component/group/invite.d.ts.map +1 -1
  89. package/dist/component/group/member.d.ts +34 -34
  90. package/dist/component/group.d.ts +47 -62
  91. package/dist/component/group.d.ts.map +1 -1
  92. package/dist/component/http.d.ts +2 -2
  93. package/dist/component/limits.d.ts +4 -4
  94. package/dist/component/maintenance.d.ts +2 -2
  95. package/dist/component/model.d.ts +1362 -1027
  96. package/dist/component/model.d.ts.map +1 -1
  97. package/dist/component/model.js +23 -6
  98. package/dist/component/model.js.map +1 -1
  99. package/dist/component/modules.d.ts +8 -2
  100. package/dist/component/modules.d.ts.map +1 -1
  101. package/dist/component/modules.js +3 -0
  102. package/dist/component/modules.js.map +1 -1
  103. package/dist/component/oauth/client.d.ts +20 -20
  104. package/dist/component/oauth/code.d.ts +14 -14
  105. package/dist/component/oauth/refresh.d.ts +19 -19
  106. package/dist/component/schema.d.ts +1100 -611
  107. package/dist/component/schema.d.ts.map +1 -1
  108. package/dist/component/schema.js +17 -4
  109. package/dist/component/schema.js.map +1 -1
  110. package/dist/component/session.d.ts +72 -32
  111. package/dist/component/session.d.ts.map +1 -1
  112. package/dist/component/session.js +120 -32
  113. package/dist/component/session.js.map +1 -1
  114. package/dist/component/token/continuation.d.ts +40 -18
  115. package/dist/component/token/continuation.d.ts.map +1 -1
  116. package/dist/component/token/continuation.js +57 -11
  117. package/dist/component/token/continuation.js.map +1 -1
  118. package/dist/component/token/pkce.d.ts +22 -22
  119. package/dist/component/token/pkce.d.ts.map +1 -1
  120. package/dist/component/token/refresh.d.ts +39 -35
  121. package/dist/component/token/refresh.d.ts.map +1 -1
  122. package/dist/component/token/refresh.js +11 -6
  123. package/dist/component/token/refresh.js.map +1 -1
  124. package/dist/component/token/verification.d.ts +36 -32
  125. package/dist/component/token/verification.d.ts.map +1 -1
  126. package/dist/component/token/verification.js +3 -3
  127. package/dist/component/token/verification.js.map +1 -1
  128. package/dist/component/user/email.d.ts +21 -21
  129. package/dist/component/user/key.d.ts +35 -35
  130. package/dist/component/user/key.d.ts.map +1 -1
  131. package/dist/component/user.d.ts +51 -45
  132. package/dist/component/user.d.ts.map +1 -1
  133. package/dist/core/index.d.ts +54 -90
  134. package/dist/core/index.d.ts.map +1 -1
  135. package/dist/core/index.js +1 -4
  136. package/dist/core/index.js.map +1 -1
  137. package/dist/expo/index.d.ts +3 -3
  138. package/dist/expo/index.d.ts.map +1 -1
  139. package/dist/expo/index.js.map +1 -1
  140. package/dist/model.js.map +1 -1
  141. package/dist/providers/anonymous.d.ts +11 -5
  142. package/dist/providers/anonymous.d.ts.map +1 -1
  143. package/dist/providers/anonymous.js +4 -1
  144. package/dist/providers/anonymous.js.map +1 -1
  145. package/dist/providers/apple.d.ts +1 -1
  146. package/dist/providers/credentials.d.ts +21 -13
  147. package/dist/providers/credentials.d.ts.map +1 -1
  148. package/dist/providers/credentials.js +2 -0
  149. package/dist/providers/credentials.js.map +1 -1
  150. package/dist/providers/custom.d.ts +3 -3
  151. package/dist/providers/custom.d.ts.map +1 -1
  152. package/dist/providers/custom.js.map +1 -1
  153. package/dist/providers/email.d.ts +3 -3
  154. package/dist/providers/email.d.ts.map +1 -1
  155. package/dist/providers/email.js.map +1 -1
  156. package/dist/providers/github.d.ts +1 -1
  157. package/dist/providers/google.d.ts +1 -1
  158. package/dist/providers/microsoft.d.ts +1 -1
  159. package/dist/providers/password.d.ts +103 -8
  160. package/dist/providers/password.d.ts.map +1 -1
  161. package/dist/providers/password.js +71 -26
  162. package/dist/providers/password.js.map +1 -1
  163. package/dist/providers/phone.d.ts +4 -3
  164. package/dist/providers/phone.d.ts.map +1 -1
  165. package/dist/providers/phone.js.map +1 -1
  166. package/dist/react/index.d.ts +40 -41
  167. package/dist/react/index.d.ts.map +1 -1
  168. package/dist/react/index.js +27 -52
  169. package/dist/react/index.js.map +1 -1
  170. package/dist/server/auth.d.ts +157 -117
  171. package/dist/server/auth.d.ts.map +1 -1
  172. package/dist/server/auth.js +41 -22
  173. package/dist/server/auth.js.map +1 -1
  174. package/dist/server/component/factor/db.js +4 -4
  175. package/dist/server/component/factor/db.js.map +1 -1
  176. package/dist/server/config.d.ts +2 -1
  177. package/dist/server/connection/domain.d.ts +4 -66
  178. package/dist/server/connection/domain.d.ts.map +1 -1
  179. package/dist/server/connection/domain.js +22 -4
  180. package/dist/server/connection/domain.js.map +1 -1
  181. package/dist/server/connection/group/service.js +6 -4
  182. package/dist/server/connection/group/service.js.map +1 -1
  183. package/dist/server/connection/http.js +94 -261
  184. package/dist/server/connection/http.js.map +1 -1
  185. package/dist/server/connection/oidc.js +8 -17
  186. package/dist/server/connection/oidc.js.map +1 -1
  187. package/dist/server/connection/policy/domain.js +4 -1
  188. package/dist/server/connection/policy/domain.js.map +1 -1
  189. package/dist/server/connection/policy.js +14 -13
  190. package/dist/server/connection/policy.js.map +1 -1
  191. package/dist/server/connection/provision.js +5 -19
  192. package/dist/server/connection/provision.js.map +1 -1
  193. package/dist/server/connection/webhook.js +4 -36
  194. package/dist/server/connection/webhook.js.map +1 -1
  195. package/dist/server/context.d.ts +2 -2
  196. package/dist/server/context.d.ts.map +1 -1
  197. package/dist/server/context.js +35 -24
  198. package/dist/server/context.js.map +1 -1
  199. package/dist/server/contract.d.ts.map +1 -1
  200. package/dist/server/contract.js +6 -14
  201. package/dist/server/contract.js.map +1 -1
  202. package/dist/server/core.js +6 -0
  203. package/dist/server/core.js.map +1 -1
  204. package/dist/server/cors.d.ts +2 -2
  205. package/dist/server/cors.d.ts.map +1 -1
  206. package/dist/server/crypto.js.map +1 -1
  207. package/dist/server/db.js +2 -1
  208. package/dist/server/db.js.map +1 -1
  209. package/dist/server/domains/context.js +34 -0
  210. package/dist/server/domains/context.js.map +1 -0
  211. package/dist/server/domains/factor.d.ts +3 -2
  212. package/dist/server/domains/factor.d.ts.map +1 -1
  213. package/dist/server/domains/factor.js.map +1 -1
  214. package/dist/server/domains/group.js.map +1 -1
  215. package/dist/server/domains/invite.js.map +1 -1
  216. package/dist/server/domains/key.js.map +1 -1
  217. package/dist/server/domains/member.js.map +1 -1
  218. package/dist/server/domains/session.js.map +1 -1
  219. package/dist/server/domains/user.js.map +1 -1
  220. package/dist/server/env.js +2 -22
  221. package/dist/server/env.js.map +1 -1
  222. package/dist/server/events.d.ts +0 -1
  223. package/dist/server/events.d.ts.map +1 -1
  224. package/dist/server/events.js +6 -6
  225. package/dist/server/events.js.map +1 -1
  226. package/dist/server/http.d.ts +1 -0
  227. package/dist/server/http.d.ts.map +1 -1
  228. package/dist/server/http.js +4 -4
  229. package/dist/server/http.js.map +1 -1
  230. package/dist/server/identity/claims.js +1 -1
  231. package/dist/server/identity/convex.d.ts +2 -0
  232. package/dist/server/identity/convex.d.ts.map +1 -1
  233. package/dist/server/index.d.ts +2 -3
  234. package/dist/server/index.js +1 -2
  235. package/dist/server/limits.js +21 -1
  236. package/dist/server/limits.js.map +1 -1
  237. package/dist/server/mutations/credentials/signin.js +6 -3
  238. package/dist/server/mutations/credentials/signin.js.map +1 -1
  239. package/dist/server/mutations/invalidate.js +5 -10
  240. package/dist/server/mutations/invalidate.js.map +1 -1
  241. package/dist/server/mutations/oauth.js +1 -1
  242. package/dist/server/mutations/oauth.js.map +1 -1
  243. package/dist/server/mutations/retrieve.js +4 -1
  244. package/dist/server/mutations/retrieve.js.map +1 -1
  245. package/dist/server/mutations/signin.js +2 -2
  246. package/dist/server/mutations/signin.js.map +1 -1
  247. package/dist/server/mutations/verify.js +4 -3
  248. package/dist/server/mutations/verify.js.map +1 -1
  249. package/dist/server/oauth/factory.js.map +1 -1
  250. package/dist/server/runtime.d.ts +107 -127
  251. package/dist/server/runtime.d.ts.map +1 -1
  252. package/dist/server/runtime.js +43 -28
  253. package/dist/server/runtime.js.map +1 -1
  254. package/dist/server/session/lifecycle.d.ts.map +1 -1
  255. package/dist/server/session/lifecycle.js +18 -3
  256. package/dist/server/session/lifecycle.js.map +1 -1
  257. package/dist/server/signin/flow.js +1 -1
  258. package/dist/server/signin/flow.js.map +1 -1
  259. package/dist/server/tokens.js +1 -0
  260. package/dist/server/tokens.js.map +1 -1
  261. package/dist/server/totp.js +3 -3
  262. package/dist/server/totp.js.map +1 -1
  263. package/dist/server/types.d.ts +63 -43
  264. package/dist/server/types.d.ts.map +1 -1
  265. package/dist/server/user/account.js +0 -1
  266. package/dist/server/user/account.js.map +1 -1
  267. package/dist/server/validators.d.ts +874 -917
  268. package/dist/server/validators.d.ts.map +1 -1
  269. package/dist/server/validators.js +33 -32
  270. package/dist/server/validators.js.map +1 -1
  271. package/dist/server/webauthn.js +4 -4
  272. package/dist/server/webauthn.js.map +1 -1
  273. package/dist/shared/brand.d.ts +3 -7
  274. package/dist/shared/brand.d.ts.map +1 -1
  275. package/dist/shared/params.d.ts +78 -0
  276. package/dist/shared/params.d.ts.map +1 -0
  277. package/dist/shared/results.d.ts +7 -1
  278. package/dist/shared/results.d.ts.map +1 -1
  279. package/dist/svelte/context.svelte.d.ts +23 -12
  280. package/dist/svelte/context.svelte.d.ts.map +1 -1
  281. package/dist/svelte/context.svelte.js +20 -13
  282. package/dist/svelte/index.d.ts +1 -4
  283. package/dist/svelte/index.d.ts.map +1 -1
  284. package/dist/svelte/index.js +1 -4
  285. package/dist/test.d.ts +1105 -611
  286. package/dist/test.d.ts.map +1 -1
  287. package/package.json +2 -1
  288. package/dist/server/env.d.ts +0 -28
  289. package/dist/server/env.d.ts.map +0 -1
  290. package/dist/svelte/AuthLoading.svelte +0 -13
  291. package/dist/svelte/AuthLoading.svelte.d.ts +0 -8
  292. package/dist/svelte/AuthLoading.svelte.d.ts.map +0 -1
  293. package/dist/svelte/SignedIn.svelte +0 -14
  294. package/dist/svelte/SignedIn.svelte.d.ts +0 -8
  295. package/dist/svelte/SignedIn.svelte.d.ts.map +0 -1
  296. package/dist/svelte/SignedOut.svelte +0 -13
  297. package/dist/svelte/SignedOut.svelte.d.ts +0 -8
  298. package/dist/svelte/SignedOut.svelte.d.ts.map +0 -1
@@ -1,10 +1,10 @@
1
- import { logMessage } from "../../../shared/log.js";
2
- import { vAuthEventKind, vGroupWebhookDeliveryDoc, vGroupWebhookDeliveryPublicDoc, vPaginated, vWebhookDeliveryStatus } from "../../model.js";
1
+ import { vAuthEventKind, vGroupWebhookDeliveryDoc, vGroupWebhookDeliveryPublicDoc, vPaginated } from "../../model.js";
3
2
  import { unsafeFetchUrlReason } from "../../../shared/fetch/guard.js";
4
3
  import schema_default from "../../schema.js";
5
4
  import { api, components, internal } from "../../_generated/api.js";
6
5
  import { internalAction, internalMutation, internalQuery, mutation, query } from "../../_generated/server.js";
7
6
  import "../../functions.js";
7
+ import { appendDeliveryEvent } from "./events.js";
8
8
  import { v } from "convex/values";
9
9
  import { paginationOptsValidator } from "convex/server";
10
10
  import { paginator } from "convex-helpers/server/pagination";
@@ -40,44 +40,6 @@ const workpool = new Workpool(components.webhookWorkpool, {
40
40
  function isRetriableStatus(status) {
41
41
  return status >= 500 || status === 408 || status === 429;
42
42
  }
43
- async function appendDeliveryEvent(ctx, args) {
44
- const attemptPart = typeof args.data?.attemptCount === "number" ? `:${args.data.attemptCount}` : "";
45
- const event = {
46
- eventId: `${args.kind}:${args.deliveryId}${attemptPart}`,
47
- kind: args.kind,
48
- category: "webhook",
49
- occurredAt: args.occurredAt,
50
- actor: {
51
- type: "webhook",
52
- id: args.endpointId
53
- },
54
- subject: {
55
- type: "webhook_delivery",
56
- id: args.deliveryId
57
- },
58
- targets: [{
59
- kind: "connection",
60
- id: args.connectionId
61
- }],
62
- outcome: args.outcome,
63
- data: {
64
- sourceEventId: args.sourceEventId,
65
- sourceEventType: args.sourceEventType,
66
- endpointId: args.endpointId,
67
- deliveryId: args.deliveryId,
68
- ...args.data
69
- }
70
- };
71
- try {
72
- await ctx.runMutation(api.event.append, {
73
- event,
74
- targets: event.targets,
75
- idempotencyKey: event.eventId
76
- });
77
- } catch (error) {
78
- logMessage("connection.webhook.delivery", "WARN", [`audit event ${args.kind} emit failed (best-effort)`, error]);
79
- }
80
- }
81
43
  function publicDelivery(delivery) {
82
44
  return {
83
45
  _id: delivery._id,
@@ -133,12 +95,85 @@ const dueForDispatch = query({
133
95
  return await ctx.db.query("GroupWebhookDelivery").withIndex("status_next_attempt_at", (idx) => idx.eq("status", "pending").lte("nextAttemptAt", now)).take(take);
134
96
  }
135
97
  });
98
+ /** Mark a pending delivery as in progress and record its attempt atomically. */
99
+ const begin = internalMutation({
100
+ args: {
101
+ id: v.id("GroupWebhookDelivery"),
102
+ occurredAt: v.number()
103
+ },
104
+ returns: v.union(vGroupWebhookDeliveryDoc, v.null()),
105
+ handler: async (ctx, { id: deliveryId, occurredAt }) => {
106
+ const delivery = await ctx.db.get("GroupWebhookDelivery", deliveryId);
107
+ if (delivery === null || delivery.status !== "pending") return null;
108
+ const attemptCount = delivery.attemptCount + 1;
109
+ await ctx.db.patch("GroupWebhookDelivery", deliveryId, {
110
+ status: "processing",
111
+ attemptCount,
112
+ lastAttemptAt: occurredAt
113
+ });
114
+ await appendDeliveryEvent(ctx, {
115
+ deliveryId,
116
+ connectionId: delivery.connectionId,
117
+ endpointId: delivery.endpointId,
118
+ sourceEventId: delivery.eventId,
119
+ sourceEventType: delivery.kind,
120
+ kind: "webhook.delivery.attempted",
121
+ outcome: "success",
122
+ occurredAt,
123
+ data: { attemptCount }
124
+ });
125
+ return {
126
+ ...delivery,
127
+ status: "processing",
128
+ attemptCount,
129
+ lastAttemptAt: occurredAt
130
+ };
131
+ }
132
+ });
133
+ /** Settle an in-progress delivery and record its outcome atomically. */
134
+ const settle = internalMutation({
135
+ args: {
136
+ id: v.id("GroupWebhookDelivery"),
137
+ occurredAt: v.number(),
138
+ outcome: v.union(v.literal("success"), v.literal("failure")),
139
+ retry: v.boolean(),
140
+ responseStatus: v.optional(v.number()),
141
+ error: v.optional(v.string())
142
+ },
143
+ returns: v.null(),
144
+ handler: async (ctx, args) => {
145
+ const delivery = await ctx.db.get("GroupWebhookDelivery", args.id);
146
+ if (delivery === null || delivery.status !== "processing") return null;
147
+ const succeeded = args.outcome === "success";
148
+ await ctx.db.patch("GroupWebhookDelivery", args.id, {
149
+ status: succeeded ? "delivered" : args.retry ? "pending" : "failed",
150
+ nextAttemptAt: args.retry ? args.occurredAt : delivery.nextAttemptAt,
151
+ lastAttemptAt: args.occurredAt,
152
+ lastResponseStatus: args.responseStatus,
153
+ lastError: args.error
154
+ });
155
+ await appendDeliveryEvent(ctx, {
156
+ deliveryId: args.id,
157
+ connectionId: delivery.connectionId,
158
+ endpointId: delivery.endpointId,
159
+ sourceEventId: delivery.eventId,
160
+ sourceEventType: delivery.kind,
161
+ kind: succeeded ? "webhook.delivery.succeeded" : "webhook.delivery.failed",
162
+ outcome: args.outcome,
163
+ occurredAt: args.occurredAt,
164
+ data: {
165
+ attemptCount: delivery.attemptCount,
166
+ status: args.responseStatus,
167
+ error: args.error
168
+ }
169
+ });
170
+ return null;
171
+ }
172
+ });
136
173
  /**
137
174
  * Queue a delivery for an endpoint. Idempotent on `(eventId, endpointId)` —
138
- * returns the existing id if already queued. On insert, emits a
139
- * `webhook.delivery.created` audit event and enqueues the `dispatch` action on
140
- * the workpool (running at `nextAttemptAt`, with `onDispatchComplete` as the
141
- * completion hook).
175
+ * returns the existing id if already queued. The row, its audit event, and the
176
+ * durable workpool entry commit together; an audit failure aborts all three.
142
177
  */
143
178
  const create = mutation({
144
179
  args: {
@@ -192,7 +227,21 @@ const onDispatchComplete = internalMutation({
192
227
  handler: async (ctx, { context, result }) => {
193
228
  if (result.kind === "success") return null;
194
229
  const delivery = await ctx.db.get("GroupWebhookDelivery", context.deliveryId);
195
- if (delivery === null || delivery.status === "delivered" || delivery.status === "failed") return null;
230
+ if (delivery === null || delivery.status !== "processing") return null;
231
+ await appendDeliveryEvent(ctx, {
232
+ deliveryId: context.deliveryId,
233
+ connectionId: delivery.connectionId,
234
+ endpointId: delivery.endpointId,
235
+ sourceEventId: delivery.eventId,
236
+ sourceEventType: delivery.kind,
237
+ kind: "webhook.delivery.failed",
238
+ outcome: "failure",
239
+ occurredAt: Date.now(),
240
+ data: {
241
+ attemptCount: delivery.attemptCount,
242
+ error: result.kind === "failed" ? result.error : "delivery canceled"
243
+ }
244
+ });
196
245
  await ctx.db.patch("GroupWebhookDelivery", context.deliveryId, {
197
246
  status: "failed",
198
247
  lastError: result.kind === "failed" ? result.error : "delivery canceled"
@@ -200,25 +249,6 @@ const onDispatchComplete = internalMutation({
200
249
  return null;
201
250
  }
202
251
  });
203
- /** Patch fields on a delivery (attempt bookkeeping). */
204
- const update = mutation({
205
- args: {
206
- id: v.id("GroupWebhookDelivery"),
207
- patch: v.object({
208
- status: v.optional(vWebhookDeliveryStatus),
209
- attemptCount: v.optional(v.number()),
210
- nextAttemptAt: v.optional(v.number()),
211
- lastAttemptAt: v.optional(v.number()),
212
- lastResponseStatus: v.optional(v.number()),
213
- lastError: v.optional(v.string())
214
- })
215
- },
216
- returns: v.null(),
217
- handler: async (ctx, { id: deliveryId, patch }) => {
218
- await ctx.db.patch("GroupWebhookDelivery", deliveryId, patch);
219
- return null;
220
- }
221
- });
222
252
  /**
223
253
  * POST a delivery's signed payload to its endpoint and settle the row. Skips
224
254
  * (failing the delivery) when the endpoint is missing or disabled. Marks
@@ -231,49 +261,23 @@ const dispatch = internalAction({
231
261
  args: { id: v.id("GroupWebhookDelivery") },
232
262
  returns: v.null(),
233
263
  handler: async (ctx, { id: deliveryId }) => {
234
- const delivery = await ctx.runQuery(internal.connection.webhook.delivery.get, { id: deliveryId });
235
- if (!delivery) return null;
236
- if (delivery.status === "delivered" || delivery.status === "failed") return null;
264
+ const startedAt = Date.now();
265
+ const delivery = await ctx.runMutation(internal.connection.webhook.delivery.begin, {
266
+ id: deliveryId,
267
+ occurredAt: startedAt
268
+ });
269
+ if (delivery === null) return null;
237
270
  const endpoint = await ctx.runQuery(api.connection.webhook.endpoint.get, { id: delivery.endpointId });
238
271
  if (!endpoint || endpoint.status !== "active") {
239
- const failedAt = Date.now();
240
- await appendDeliveryEvent(ctx, {
241
- deliveryId,
242
- connectionId: delivery.connectionId,
243
- endpointId: delivery.endpointId,
244
- sourceEventId: delivery.eventId,
245
- sourceEventType: delivery.kind,
246
- kind: "webhook.delivery.failed",
247
- outcome: "failure",
248
- occurredAt: failedAt,
249
- data: {
250
- attemptCount: delivery.attemptCount + 1,
251
- error: "endpoint missing or disabled"
252
- }
253
- });
254
- await ctx.runMutation(api.connection.webhook.delivery.update, {
272
+ await ctx.runMutation(internal.connection.webhook.delivery.settle, {
255
273
  id: deliveryId,
256
- patch: {
257
- status: "failed",
258
- lastError: "endpoint missing or disabled",
259
- lastAttemptAt: failedAt,
260
- attemptCount: delivery.attemptCount + 1
261
- }
274
+ occurredAt: startedAt,
275
+ outcome: "failure",
276
+ retry: false,
277
+ error: "endpoint missing or disabled"
262
278
  });
263
279
  return null;
264
280
  }
265
- const startedAt = Date.now();
266
- await appendDeliveryEvent(ctx, {
267
- deliveryId,
268
- connectionId: delivery.connectionId,
269
- endpointId: delivery.endpointId,
270
- sourceEventId: delivery.eventId,
271
- sourceEventType: delivery.kind,
272
- kind: "webhook.delivery.attempted",
273
- outcome: "success",
274
- occurredAt: startedAt,
275
- data: { attemptCount: delivery.attemptCount + 1 }
276
- });
277
281
  const body = JSON.stringify({
278
282
  kind: delivery.kind,
279
283
  payload: delivery.payload
@@ -296,89 +300,42 @@ const dispatch = internalAction({
296
300
  signal: AbortSignal.timeout(15e3)
297
301
  });
298
302
  } catch (err) {
299
- const exhausted = delivery.attemptCount + 1 >= MAX_ATTEMPTS;
300
- await appendDeliveryEvent(ctx, {
301
- deliveryId,
302
- connectionId: delivery.connectionId,
303
- endpointId: delivery.endpointId,
304
- sourceEventId: delivery.eventId,
305
- sourceEventType: delivery.kind,
306
- kind: "webhook.delivery.failed",
307
- outcome: "failure",
308
- occurredAt: startedAt,
309
- data: {
310
- attemptCount: delivery.attemptCount + 1,
311
- error: err instanceof Error ? err.message : String(err)
312
- }
313
- });
314
- await ctx.runMutation(api.connection.webhook.delivery.update, {
303
+ const error = err instanceof Error ? err.message : String(err);
304
+ const retry = delivery.attemptCount < MAX_ATTEMPTS;
305
+ await ctx.runMutation(internal.connection.webhook.delivery.settle, {
315
306
  id: deliveryId,
316
- patch: {
317
- status: exhausted ? "failed" : "processing",
318
- lastError: err instanceof Error ? err.message : String(err),
319
- lastAttemptAt: startedAt,
320
- attemptCount: delivery.attemptCount + 1
321
- }
307
+ occurredAt: startedAt,
308
+ outcome: "failure",
309
+ retry,
310
+ error
322
311
  });
323
- if (!exhausted) throw err;
312
+ if (retry) throw err;
324
313
  return null;
325
314
  }
326
315
  if (!response.ok) {
327
- const terminal = !isRetriableStatus(response.status) || delivery.attemptCount + 1 >= MAX_ATTEMPTS;
328
- await appendDeliveryEvent(ctx, {
329
- deliveryId,
330
- connectionId: delivery.connectionId,
331
- endpointId: delivery.endpointId,
332
- sourceEventId: delivery.eventId,
333
- sourceEventType: delivery.kind,
334
- kind: "webhook.delivery.failed",
335
- outcome: "failure",
336
- occurredAt: startedAt,
337
- data: {
338
- attemptCount: delivery.attemptCount + 1,
339
- status: response.status
340
- }
341
- });
342
- await ctx.runMutation(api.connection.webhook.delivery.update, {
316
+ const retry = isRetriableStatus(response.status) && delivery.attemptCount < MAX_ATTEMPTS;
317
+ await ctx.runMutation(internal.connection.webhook.delivery.settle, {
343
318
  id: deliveryId,
344
- patch: {
345
- status: terminal ? "failed" : "processing",
346
- lastResponseStatus: response.status,
347
- lastError: `HTTP ${response.status}`,
348
- lastAttemptAt: startedAt,
349
- attemptCount: delivery.attemptCount + 1
350
- }
319
+ occurredAt: startedAt,
320
+ outcome: "failure",
321
+ retry,
322
+ responseStatus: response.status,
323
+ error: `HTTP ${response.status}`
351
324
  });
352
- if (!terminal) throw new Error(`Webhook delivery failed: HTTP ${response.status}`);
325
+ if (retry) throw new Error(`Webhook delivery failed: HTTP ${response.status}`);
353
326
  return null;
354
327
  }
355
- await appendDeliveryEvent(ctx, {
356
- deliveryId,
357
- connectionId: delivery.connectionId,
358
- endpointId: delivery.endpointId,
359
- sourceEventId: delivery.eventId,
360
- sourceEventType: delivery.kind,
361
- kind: "webhook.delivery.succeeded",
362
- outcome: "success",
363
- occurredAt: startedAt,
364
- data: {
365
- attemptCount: delivery.attemptCount + 1,
366
- status: response.status
367
- }
368
- });
369
- await ctx.runMutation(api.connection.webhook.delivery.update, {
328
+ await ctx.runMutation(internal.connection.webhook.delivery.settle, {
370
329
  id: deliveryId,
371
- patch: {
372
- status: "delivered",
373
- lastResponseStatus: response.status,
374
- lastAttemptAt: startedAt,
375
- attemptCount: delivery.attemptCount + 1
376
- }
330
+ occurredAt: startedAt,
331
+ outcome: "success",
332
+ retry: false,
333
+ responseStatus: response.status
377
334
  });
378
335
  return null;
379
336
  }
380
337
  });
381
338
 
382
339
  //#endregion
383
- export { create, dispatch, dueForDispatch, get, list, onDispatchComplete, update };
340
+ export { begin, create, dispatch, dueForDispatch, get, list, onDispatchComplete, settle };
384
341
  //# sourceMappingURL=delivery.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"delivery.js","names":["schema"],"sources":["../../../../src/component/connection/webhook/delivery.ts"],"sourcesContent":["/**\n * `component.connection.webhook.delivery.*` — queued webhook delivery attempts\n * (sub-resource of webhook).\n *\n * `list` is overloaded (`{ connectionId }` history or\n * `{ now }` ready-for-dispatch).\n *\n * @module\n */\n\nimport { Workpool, vOnCompleteArgs } from \"@convex-dev/workpool\";\nimport { paginationOptsValidator } from \"convex/server\";\nimport { v, type Infer } from \"convex/values\";\nimport { paginator } from \"convex-helpers/server/pagination\";\n\nimport { api, components, internal } from \"../../_generated/api\";\nimport type { Id } from \"../../_generated/dataModel\";\nimport { internalAction, internalMutation, internalQuery, mutation, query } from \"../../functions\";\nimport { unsafeFetchUrlReason } from \"../../../shared/fetch/guard\";\nimport { logMessage } from \"../../../shared/log\";\nimport {\n vAuthEventKind,\n vGroupWebhookDeliveryDoc,\n vGroupWebhookDeliveryPublicDoc,\n vPaginated,\n vWebhookDeliveryStatus,\n} from \"../../model\";\nimport schema from \"../../schema\";\n\nconst MAX_ATTEMPTS = 5;\n\nconst workpool = new Workpool(components.webhookWorkpool, {\n maxParallelism: 5,\n defaultRetryBehavior: { maxAttempts: MAX_ATTEMPTS, initialBackoffMs: 1_000, base: 2 },\n retryActionsByDefault: true,\n});\n\n/**\n * Whether an HTTP response status warrants a retry. Only transient failures\n * retry — `5xx`, `408 Request Timeout`, and `429 Too Many Requests`. Every\n * other non-2xx (the rest of `4xx`, plus unfollowed `3xx`) is a permanent\n * rejection that re-sending the identical payload cannot fix, so it fails\n * immediately instead of burning the full retry budget.\n */\nfunction isRetriableStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nasync function appendDeliveryEvent(\n ctx: any,\n args: {\n deliveryId: string;\n connectionId: string;\n endpointId: string;\n sourceEventId: string;\n sourceEventType: Infer<typeof vAuthEventKind>;\n kind:\n | \"webhook.delivery.created\"\n | \"webhook.delivery.attempted\"\n | \"webhook.delivery.succeeded\"\n | \"webhook.delivery.failed\";\n outcome: \"success\" | \"failure\";\n occurredAt: number;\n data?: Record<string, unknown>;\n },\n) {\n const attemptPart =\n typeof args.data?.attemptCount === \"number\" ? `:${args.data.attemptCount}` : \"\";\n const event = {\n eventId: `${args.kind}:${args.deliveryId}${attemptPart}`,\n kind: args.kind,\n category: \"webhook\" as const,\n occurredAt: args.occurredAt,\n actor: { type: \"webhook\" as const, id: args.endpointId },\n subject: { type: \"webhook_delivery\" as const, id: args.deliveryId },\n targets: [{ kind: \"connection\" as const, id: args.connectionId }],\n outcome: args.outcome,\n data: {\n sourceEventId: args.sourceEventId,\n sourceEventType: args.sourceEventType,\n endpointId: args.endpointId,\n deliveryId: args.deliveryId,\n ...args.data,\n },\n };\n try {\n await ctx.runMutation(api.event.append, {\n event,\n targets: event.targets,\n idempotencyKey: event.eventId,\n });\n } catch (error) {\n logMessage(\"connection.webhook.delivery\", \"WARN\", [\n `audit event ${args.kind} emit failed (best-effort)`,\n error,\n ]);\n }\n}\n\nfunction publicDelivery(\n delivery: Infer<typeof vGroupWebhookDeliveryDoc>,\n): Infer<typeof vGroupWebhookDeliveryPublicDoc> {\n return {\n _id: delivery._id,\n _creationTime: delivery._creationTime,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n eventId: delivery.eventId,\n kind: delivery.kind,\n status: delivery.status,\n attemptCount: delivery.attemptCount,\n nextAttemptAt: delivery.nextAttemptAt,\n lastAttemptAt: delivery.lastAttemptAt,\n lastResponseStatus: delivery.lastResponseStatus,\n lastError: delivery.lastError,\n signedAt: delivery.signedAt,\n };\n}\n\n/** Read a delivery by id (internal — returns the full, unredacted doc). */\nexport const get = internalQuery({\n args: { id: v.id(\"GroupWebhookDelivery\") },\n returns: v.union(vGroupWebhookDeliveryDoc, v.null()),\n handler: async (ctx, { id: deliveryId }) => {\n return await ctx.db.get(\"GroupWebhookDelivery\", deliveryId);\n },\n});\n\n/** List a connection's deliveries, newest first and paginated. Rows are redacted to public fields. */\nexport const list = query({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n paginationOpts: paginationOptsValidator,\n },\n returns: vPaginated(vGroupWebhookDeliveryPublicDoc),\n handler: async (ctx, { connectionId, paginationOpts }) => {\n const result = await paginator(ctx.db, schema)\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"group_connection_id\", (idx) => idx.eq(\"connectionId\", connectionId))\n .order(\"desc\")\n .paginate(paginationOpts);\n return {\n ...result,\n page: result.page.map(publicDelivery),\n };\n },\n});\n\n/**\n * List pending deliveries whose `nextAttemptAt` is at or before `now`, ready to\n * be dispatched (`limit` clamped to 1..100, default 50).\n */\nexport const dueForDispatch = query({\n args: {\n now: v.number(),\n limit: v.optional(v.number()),\n },\n returns: v.array(vGroupWebhookDeliveryDoc),\n handler: async (ctx, { now, limit }) => {\n const take = Math.min(Math.max(limit ?? 50, 1), 100);\n return await ctx.db\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"status_next_attempt_at\", (idx) =>\n idx.eq(\"status\", \"pending\").lte(\"nextAttemptAt\", now),\n )\n .take(take);\n },\n});\n\n/**\n * Queue a delivery for an endpoint. Idempotent on `(eventId, endpointId)` —\n * returns the existing id if already queued. On insert, emits a\n * `webhook.delivery.created` audit event and enqueues the `dispatch` action on\n * the workpool (running at `nextAttemptAt`, with `onDispatchComplete` as the\n * completion hook).\n */\nexport const create = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n endpointId: v.id(\"GroupWebhookEndpoint\"),\n eventId: v.string(),\n kind: vAuthEventKind,\n payload: v.any(),\n nextAttemptAt: v.number(),\n signature: v.string(),\n signedAt: v.number(),\n },\n returns: v.id(\"GroupWebhookDelivery\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"event_id_endpoint_id\", (idx) =>\n idx.eq(\"eventId\", args.eventId).eq(\"endpointId\", args.endpointId),\n )\n .unique();\n if (existing !== null) return existing._id;\n const deliveryId = await ctx.db.insert(\"GroupWebhookDelivery\", {\n ...args,\n status: \"pending\",\n attemptCount: 0,\n });\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: args.connectionId,\n endpointId: args.endpointId,\n sourceEventId: args.eventId,\n sourceEventType: args.kind,\n kind: \"webhook.delivery.created\",\n outcome: \"success\",\n occurredAt: args.signedAt,\n });\n await workpool.enqueueAction(\n ctx,\n internal.connection.webhook.delivery.dispatch,\n { id: deliveryId },\n {\n runAt: args.nextAttemptAt,\n onComplete: internal.connection.webhook.delivery.onDispatchComplete,\n context: { deliveryId },\n },\n );\n return deliveryId;\n },\n});\n\n/**\n * Workpool completion hook for a delivery's dispatch chain.\n *\n * Guarantees a terminal row state even when `dispatch` itself never reached its\n * own terminal branch — e.g. a bookkeeping mutation threw on every attempt, so\n * the workpool exhausted its retries while the row was left non-terminal. On any\n * non-success outcome, settle a still-open delivery as `failed`.\n */\nexport const onDispatchComplete = internalMutation({\n args: vOnCompleteArgs(v.object({ deliveryId: v.id(\"GroupWebhookDelivery\") })),\n returns: v.null(),\n handler: async (ctx, { context, result }) => {\n if (result.kind === \"success\") return null;\n const delivery = await ctx.db.get(\"GroupWebhookDelivery\", context.deliveryId);\n if (delivery === null || delivery.status === \"delivered\" || delivery.status === \"failed\") {\n return null;\n }\n await ctx.db.patch(\"GroupWebhookDelivery\", context.deliveryId, {\n status: \"failed\",\n lastError: result.kind === \"failed\" ? result.error : \"delivery canceled\",\n });\n return null;\n },\n});\n\n/** Patch fields on a delivery (attempt bookkeeping). */\nexport const update = mutation({\n args: {\n id: v.id(\"GroupWebhookDelivery\"),\n patch: v.object({\n status: v.optional(vWebhookDeliveryStatus),\n attemptCount: v.optional(v.number()),\n nextAttemptAt: v.optional(v.number()),\n lastAttemptAt: v.optional(v.number()),\n lastResponseStatus: v.optional(v.number()),\n lastError: v.optional(v.string()),\n }),\n },\n returns: v.null(),\n handler: async (ctx, { id: deliveryId, patch }) => {\n await ctx.db.patch(\"GroupWebhookDelivery\", deliveryId, patch);\n return null;\n },\n});\n\n/**\n * POST a delivery's signed payload to its endpoint and settle the row. Skips\n * (failing the delivery) when the endpoint is missing or disabled. Marks\n * `delivered` on a 2xx; on failure, retries via the workpool until the attempt\n * budget is spent — only transient HTTP statuses (see `isRetriableStatus`) and\n * fetch errors are retried, everything else fails immediately. Emits\n * `attempted`/`succeeded`/`failed` audit events along the way.\n */\nexport const dispatch = internalAction({\n args: { id: v.id(\"GroupWebhookDelivery\") },\n returns: v.null(),\n handler: async (ctx, { id: deliveryId }) => {\n const delivery = (await ctx.runQuery(internal.connection.webhook.delivery.get, {\n id: deliveryId,\n })) as {\n _id: string;\n connectionId: string;\n endpointId: string;\n eventId: string;\n kind: Infer<typeof vAuthEventKind>;\n payload: unknown;\n status: Infer<typeof vWebhookDeliveryStatus>;\n attemptCount: number;\n signature: string;\n signedAt: number;\n } | null;\n if (!delivery) return null;\n\n // At-least-once delivery. The workpool can re-invoke `dispatch` for a\n // delivery whose previous attempt already settled — a retry scheduled\n // before a sibling attempt committed its terminal status, a re-enqueue\n // after a crash, or an `onDispatchComplete` that already failed the row.\n // Re-read the row and bail before POSTing again once it is terminal, so a\n // delivered/failed delivery is never re-sent. A crash in the narrow window\n // between the POST and the status write still re-delivers, so receivers get\n // at-least-once (not exactly-once) semantics and MUST dedup on the stable\n // `X-Auth-Delivery-Id` header sent below.\n if (delivery.status === \"delivered\" || delivery.status === \"failed\") {\n return null;\n }\n\n const endpoint = (await ctx.runQuery(api.connection.webhook.endpoint.get, {\n id: delivery.endpointId as Id<\"GroupWebhookEndpoint\">,\n })) as { url: string; status: string } | null;\n if (!endpoint || endpoint.status !== \"active\") {\n const failedAt = Date.now();\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.failed\",\n outcome: \"failure\",\n occurredAt: failedAt,\n data: {\n attemptCount: delivery.attemptCount + 1,\n error: \"endpoint missing or disabled\",\n },\n });\n await ctx.runMutation(api.connection.webhook.delivery.update, {\n id: deliveryId,\n patch: {\n status: \"failed\",\n lastError: \"endpoint missing or disabled\",\n lastAttemptAt: failedAt,\n attemptCount: delivery.attemptCount + 1,\n },\n });\n return null;\n }\n\n const startedAt = Date.now();\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.attempted\",\n outcome: \"success\",\n occurredAt: startedAt,\n data: { attemptCount: delivery.attemptCount + 1 },\n });\n const body = JSON.stringify({\n kind: delivery.kind,\n payload: delivery.payload,\n });\n let response: Response;\n try {\n const unsafeUrlReason = unsafeFetchUrlReason(endpoint.url);\n if (unsafeUrlReason !== null) {\n throw new Error(`Webhook ${unsafeUrlReason}`);\n }\n response = await fetch(endpoint.url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Auth-Event-Type\": delivery.kind,\n \"X-Auth-Delivery-Id\": delivery._id,\n \"X-Auth-Timestamp\": String(delivery.signedAt),\n \"X-Auth-Signature\": `sha256=${delivery.signature}`,\n },\n body,\n redirect: \"manual\",\n signal: AbortSignal.timeout(15_000),\n });\n } catch (err) {\n const exhausted = delivery.attemptCount + 1 >= MAX_ATTEMPTS;\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.failed\",\n outcome: \"failure\",\n occurredAt: startedAt,\n data: {\n attemptCount: delivery.attemptCount + 1,\n error: err instanceof Error ? err.message : String(err),\n },\n });\n await ctx.runMutation(api.connection.webhook.delivery.update, {\n id: deliveryId,\n patch: {\n status: exhausted ? \"failed\" : \"processing\",\n lastError: err instanceof Error ? err.message : String(err),\n lastAttemptAt: startedAt,\n attemptCount: delivery.attemptCount + 1,\n },\n });\n if (!exhausted) throw err;\n return null;\n }\n\n if (!response.ok) {\n const terminal =\n !isRetriableStatus(response.status) || delivery.attemptCount + 1 >= MAX_ATTEMPTS;\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.failed\",\n outcome: \"failure\",\n occurredAt: startedAt,\n data: {\n attemptCount: delivery.attemptCount + 1,\n status: response.status,\n },\n });\n await ctx.runMutation(api.connection.webhook.delivery.update, {\n id: deliveryId,\n patch: {\n status: terminal ? \"failed\" : \"processing\",\n lastResponseStatus: response.status,\n lastError: `HTTP ${response.status}`,\n lastAttemptAt: startedAt,\n attemptCount: delivery.attemptCount + 1,\n },\n });\n if (!terminal) throw new Error(`Webhook delivery failed: HTTP ${response.status}`);\n return null;\n }\n\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.succeeded\",\n outcome: \"success\",\n occurredAt: startedAt,\n data: {\n attemptCount: delivery.attemptCount + 1,\n status: response.status,\n },\n });\n await ctx.runMutation(api.connection.webhook.delivery.update, {\n id: deliveryId,\n patch: {\n status: \"delivered\",\n lastResponseStatus: response.status,\n lastAttemptAt: startedAt,\n attemptCount: delivery.attemptCount + 1,\n },\n });\n return null;\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"delivery.js","names":["schema"],"sources":["../../../../src/component/connection/webhook/delivery.ts"],"sourcesContent":["/**\n * `component.connection.webhook.delivery.*` — queued webhook delivery attempts\n * (sub-resource of webhook).\n *\n * `list` is overloaded (`{ connectionId }` history or\n * `{ now }` ready-for-dispatch).\n *\n * @module\n */\n\nimport { Workpool, vOnCompleteArgs } from \"@convex-dev/workpool\";\nimport { paginationOptsValidator } from \"convex/server\";\nimport { v, type Infer } from \"convex/values\";\nimport { paginator } from \"convex-helpers/server/pagination\";\n\nimport { api, components, internal } from \"../../_generated/api\";\nimport { internalAction, internalMutation, internalQuery, mutation, query } from \"../../functions\";\nimport { unsafeFetchUrlReason } from \"../../../shared/fetch/guard\";\nimport {\n vAuthEventKind,\n vGroupWebhookDeliveryDoc,\n vGroupWebhookDeliveryPublicDoc,\n vPaginated,\n} from \"../../model\";\nimport schema from \"../../schema\";\nimport { appendDeliveryEvent } from \"./events\";\n\nconst MAX_ATTEMPTS = 5;\n\nconst workpool = new Workpool(components.webhookWorkpool, {\n maxParallelism: 5,\n defaultRetryBehavior: { maxAttempts: MAX_ATTEMPTS, initialBackoffMs: 1_000, base: 2 },\n retryActionsByDefault: true,\n});\n\n/**\n * Whether an HTTP response status warrants a retry. Only transient failures\n * retry — `5xx`, `408 Request Timeout`, and `429 Too Many Requests`. Every\n * other non-2xx (the rest of `4xx`, plus unfollowed `3xx`) is a permanent\n * rejection that re-sending the identical payload cannot fix, so it fails\n * immediately instead of burning the full retry budget.\n */\nfunction isRetriableStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\nfunction publicDelivery(\n delivery: Infer<typeof vGroupWebhookDeliveryDoc>,\n): Infer<typeof vGroupWebhookDeliveryPublicDoc> {\n return {\n _id: delivery._id,\n _creationTime: delivery._creationTime,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n eventId: delivery.eventId,\n kind: delivery.kind,\n status: delivery.status,\n attemptCount: delivery.attemptCount,\n nextAttemptAt: delivery.nextAttemptAt,\n lastAttemptAt: delivery.lastAttemptAt,\n lastResponseStatus: delivery.lastResponseStatus,\n lastError: delivery.lastError,\n signedAt: delivery.signedAt,\n };\n}\n\n/** Read a delivery by id (internal — returns the full, unredacted doc). */\nexport const get = internalQuery({\n args: { id: v.id(\"GroupWebhookDelivery\") },\n returns: v.union(vGroupWebhookDeliveryDoc, v.null()),\n handler: async (ctx, { id: deliveryId }) => {\n return await ctx.db.get(\"GroupWebhookDelivery\", deliveryId);\n },\n});\n\n/** List a connection's deliveries, newest first and paginated. Rows are redacted to public fields. */\nexport const list = query({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n paginationOpts: paginationOptsValidator,\n },\n returns: vPaginated(vGroupWebhookDeliveryPublicDoc),\n handler: async (ctx, { connectionId, paginationOpts }) => {\n const result = await paginator(ctx.db, schema)\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"group_connection_id\", (idx) => idx.eq(\"connectionId\", connectionId))\n .order(\"desc\")\n .paginate(paginationOpts);\n return {\n ...result,\n page: result.page.map(publicDelivery),\n };\n },\n});\n\n/**\n * List pending deliveries whose `nextAttemptAt` is at or before `now`, ready to\n * be dispatched (`limit` clamped to 1..100, default 50).\n */\nexport const dueForDispatch = query({\n args: {\n now: v.number(),\n limit: v.optional(v.number()),\n },\n returns: v.array(vGroupWebhookDeliveryDoc),\n handler: async (ctx, { now, limit }) => {\n const take = Math.min(Math.max(limit ?? 50, 1), 100);\n return await ctx.db\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"status_next_attempt_at\", (idx) =>\n idx.eq(\"status\", \"pending\").lte(\"nextAttemptAt\", now),\n )\n .take(take);\n },\n});\n\n/** Mark a pending delivery as in progress and record its attempt atomically. */\nexport const begin = internalMutation({\n args: { id: v.id(\"GroupWebhookDelivery\"), occurredAt: v.number() },\n returns: v.union(vGroupWebhookDeliveryDoc, v.null()),\n handler: async (ctx, { id: deliveryId, occurredAt }) => {\n const delivery = await ctx.db.get(\"GroupWebhookDelivery\", deliveryId);\n if (delivery === null || delivery.status !== \"pending\") return null;\n\n const attemptCount = delivery.attemptCount + 1;\n await ctx.db.patch(\"GroupWebhookDelivery\", deliveryId, {\n status: \"processing\",\n attemptCount,\n lastAttemptAt: occurredAt,\n });\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.attempted\",\n outcome: \"success\",\n occurredAt,\n data: { attemptCount },\n });\n return {\n ...delivery,\n status: \"processing\" as const,\n attemptCount,\n lastAttemptAt: occurredAt,\n };\n },\n});\n\n/** Settle an in-progress delivery and record its outcome atomically. */\nexport const settle = internalMutation({\n args: {\n id: v.id(\"GroupWebhookDelivery\"),\n occurredAt: v.number(),\n outcome: v.union(v.literal(\"success\"), v.literal(\"failure\")),\n retry: v.boolean(),\n responseStatus: v.optional(v.number()),\n error: v.optional(v.string()),\n },\n returns: v.null(),\n handler: async (ctx, args) => {\n const delivery = await ctx.db.get(\"GroupWebhookDelivery\", args.id);\n if (delivery === null || delivery.status !== \"processing\") return null;\n\n const succeeded = args.outcome === \"success\";\n await ctx.db.patch(\"GroupWebhookDelivery\", args.id, {\n status: succeeded ? \"delivered\" : args.retry ? \"pending\" : \"failed\",\n nextAttemptAt: args.retry ? args.occurredAt : delivery.nextAttemptAt,\n lastAttemptAt: args.occurredAt,\n lastResponseStatus: args.responseStatus,\n lastError: args.error,\n });\n await appendDeliveryEvent(ctx, {\n deliveryId: args.id,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: succeeded ? \"webhook.delivery.succeeded\" : \"webhook.delivery.failed\",\n outcome: args.outcome,\n occurredAt: args.occurredAt,\n data: {\n attemptCount: delivery.attemptCount,\n status: args.responseStatus,\n error: args.error,\n },\n });\n return null;\n },\n});\n\n/**\n * Queue a delivery for an endpoint. Idempotent on `(eventId, endpointId)` —\n * returns the existing id if already queued. The row, its audit event, and the\n * durable workpool entry commit together; an audit failure aborts all three.\n */\nexport const create = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n endpointId: v.id(\"GroupWebhookEndpoint\"),\n eventId: v.string(),\n kind: vAuthEventKind,\n payload: v.any(),\n nextAttemptAt: v.number(),\n signature: v.string(),\n signedAt: v.number(),\n },\n returns: v.id(\"GroupWebhookDelivery\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"GroupWebhookDelivery\")\n .withIndex(\"event_id_endpoint_id\", (idx) =>\n idx.eq(\"eventId\", args.eventId).eq(\"endpointId\", args.endpointId),\n )\n .unique();\n if (existing !== null) return existing._id;\n const deliveryId = await ctx.db.insert(\"GroupWebhookDelivery\", {\n ...args,\n status: \"pending\",\n attemptCount: 0,\n });\n await appendDeliveryEvent(ctx, {\n deliveryId,\n connectionId: args.connectionId,\n endpointId: args.endpointId,\n sourceEventId: args.eventId,\n sourceEventType: args.kind,\n kind: \"webhook.delivery.created\",\n outcome: \"success\",\n occurredAt: args.signedAt,\n });\n await workpool.enqueueAction(\n ctx,\n internal.connection.webhook.delivery.dispatch,\n { id: deliveryId },\n {\n runAt: args.nextAttemptAt,\n onComplete: internal.connection.webhook.delivery.onDispatchComplete,\n context: { deliveryId },\n },\n );\n return deliveryId;\n },\n});\n\n/**\n * Workpool completion hook for a delivery's dispatch chain.\n *\n * Guarantees a terminal row state even when `dispatch` itself never reached its\n * own terminal branch — e.g. a bookkeeping mutation threw on every attempt, so\n * the workpool exhausted its retries while the row was left non-terminal. On any\n * non-success outcome, settle a still-open delivery as `failed`.\n */\nexport const onDispatchComplete = internalMutation({\n args: vOnCompleteArgs(v.object({ deliveryId: v.id(\"GroupWebhookDelivery\") })),\n returns: v.null(),\n handler: async (ctx, { context, result }) => {\n if (result.kind === \"success\") return null;\n const delivery = await ctx.db.get(\"GroupWebhookDelivery\", context.deliveryId);\n if (delivery === null || delivery.status !== \"processing\") {\n return null;\n }\n await appendDeliveryEvent(ctx, {\n deliveryId: context.deliveryId,\n connectionId: delivery.connectionId,\n endpointId: delivery.endpointId,\n sourceEventId: delivery.eventId,\n sourceEventType: delivery.kind,\n kind: \"webhook.delivery.failed\",\n outcome: \"failure\",\n occurredAt: Date.now(),\n data: {\n attemptCount: delivery.attemptCount,\n error: result.kind === \"failed\" ? result.error : \"delivery canceled\",\n },\n });\n await ctx.db.patch(\"GroupWebhookDelivery\", context.deliveryId, {\n status: \"failed\",\n lastError: result.kind === \"failed\" ? result.error : \"delivery canceled\",\n });\n return null;\n },\n});\n\n/**\n * POST a delivery's signed payload to its endpoint and settle the row. Skips\n * (failing the delivery) when the endpoint is missing or disabled. Marks\n * `delivered` on a 2xx; on failure, retries via the workpool until the attempt\n * budget is spent — only transient HTTP statuses (see `isRetriableStatus`) and\n * fetch errors are retried, everything else fails immediately. Emits\n * `attempted`/`succeeded`/`failed` audit events along the way.\n */\nexport const dispatch = internalAction({\n args: { id: v.id(\"GroupWebhookDelivery\") },\n returns: v.null(),\n handler: async (ctx, { id: deliveryId }) => {\n const startedAt = Date.now();\n const delivery = await ctx.runMutation(internal.connection.webhook.delivery.begin, {\n id: deliveryId,\n occurredAt: startedAt,\n });\n if (delivery === null) return null;\n\n const endpoint = (await ctx.runQuery(api.connection.webhook.endpoint.get, {\n id: delivery.endpointId,\n })) as { url: string; status: string } | null;\n if (!endpoint || endpoint.status !== \"active\") {\n await ctx.runMutation(internal.connection.webhook.delivery.settle, {\n id: deliveryId,\n occurredAt: startedAt,\n outcome: \"failure\",\n retry: false,\n error: \"endpoint missing or disabled\",\n });\n return null;\n }\n\n const body = JSON.stringify({\n kind: delivery.kind,\n payload: delivery.payload,\n });\n let response: Response;\n try {\n const unsafeUrlReason = unsafeFetchUrlReason(endpoint.url);\n if (unsafeUrlReason !== null) {\n throw new Error(`Webhook ${unsafeUrlReason}`);\n }\n response = await fetch(endpoint.url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Auth-Event-Type\": delivery.kind,\n \"X-Auth-Delivery-Id\": delivery._id,\n \"X-Auth-Timestamp\": String(delivery.signedAt),\n \"X-Auth-Signature\": `sha256=${delivery.signature}`,\n },\n body,\n redirect: \"manual\",\n signal: AbortSignal.timeout(15_000),\n });\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n const retry = delivery.attemptCount < MAX_ATTEMPTS;\n await ctx.runMutation(internal.connection.webhook.delivery.settle, {\n id: deliveryId,\n occurredAt: startedAt,\n outcome: \"failure\",\n retry,\n error,\n });\n if (retry) throw err;\n return null;\n }\n\n if (!response.ok) {\n const retry = isRetriableStatus(response.status) && delivery.attemptCount < MAX_ATTEMPTS;\n await ctx.runMutation(internal.connection.webhook.delivery.settle, {\n id: deliveryId,\n occurredAt: startedAt,\n outcome: \"failure\",\n retry,\n responseStatus: response.status,\n error: `HTTP ${response.status}`,\n });\n if (retry) throw new Error(`Webhook delivery failed: HTTP ${response.status}`);\n return null;\n }\n\n await ctx.runMutation(internal.connection.webhook.delivery.settle, {\n id: deliveryId,\n occurredAt: startedAt,\n outcome: \"success\",\n retry: false,\n responseStatus: response.status,\n });\n return null;\n },\n});\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import * as convex_values2855 from "convex/values";
2
- import * as convex_server215 from "convex/server";
1
+ import * as convex_values3280 from "convex/values";
2
+ import * as convex_server220 from "convex/server";
3
3
 
4
4
  //#region src/component/connection/webhook/endpoint.d.ts
5
5
  declare namespace endpoint_d_exports {
@@ -12,55 +12,55 @@ declare namespace endpoint_d_exports {
12
12
  * @module
13
13
  */
14
14
  /** Read a webhook endpoint by id. */
15
- declare const get: convex_server215.RegisteredQuery<"public", {
16
- id: convex_values2855.GenericId<"GroupWebhookEndpoint">;
15
+ declare const get: convex_server220.RegisteredQuery<"public", {
16
+ id: convex_values3280.GenericId<"GroupWebhookEndpoint">;
17
17
  }, Promise<{
18
- _id: convex_values2855.GenericId<"GroupWebhookEndpoint">;
18
+ _id: convex_values3280.GenericId<"GroupWebhookEndpoint">;
19
19
  _creationTime: number;
20
20
  extend?: any;
21
- createdByUserId?: convex_values2855.GenericId<"User"> | undefined;
21
+ createdByUserId?: convex_values3280.GenericId<"User"> | undefined;
22
22
  lastSuccessAt?: number | undefined;
23
23
  lastFailureAt?: number | undefined;
24
- connectionId: convex_values2855.GenericId<"GroupConnection">;
24
+ connectionId: convex_values3280.GenericId<"GroupConnection">;
25
+ groupId: convex_values3280.GenericId<"Group">;
25
26
  status: "active" | "disabled";
26
- groupId: convex_values2855.GenericId<"Group">;
27
27
  url: string;
28
28
  secretCiphertext: string;
29
29
  subscriptions: ("user.created" | "user.updated" | "session.signed_in" | "session.signed_out" | "session.invalidated" | "session.refresh_exchanged" | "session.refresh_reuse_detected" | "account.linked" | "account.unlinked" | "password.changed" | "passkey.added" | "passkey.removed" | "totp.enrolled" | "totp.removed" | "email.verified" | "phone.verified" | "api_key.created" | "api_key.revoked" | "oauth.client.created" | "oauth.client.revoked" | "oauth.code.created" | "oauth.token.created" | "oauth.token.exchanged" | "oauth.refresh.reuse_detected" | "oauth.refresh.revoked" | "connection.created" | "connection.updated" | "connection.removed" | "connection.login.succeeded" | "connection.login.failed" | "connection.domain.verification_requested" | "connection.domain.verified" | "connection.policy.updated" | "connection.saml.set" | "connection.saml.refreshed" | "connection.oidc.set" | "connection.scim.set" | "connection.scim.read" | "connection.scim.user.provisioned" | "connection.scim.user.updated" | "connection.scim.user.deactivated" | "connection.scim.user.reactivated" | "connection.scim.group.provisioned" | "connection.scim.group.updated" | "connection.scim.group.deactivated" | "connection.scim.group.reactivated" | "webhook.endpoint.created" | "webhook.endpoint.disabled" | "webhook.delivery.created" | "webhook.delivery.attempted" | "webhook.delivery.succeeded" | "webhook.delivery.failed")[];
30
30
  failureCount: number;
31
31
  } | null>>;
32
32
  /** List a connection's webhook endpoints. */
33
- declare const list: convex_server215.RegisteredQuery<"public", {
34
- connectionId: convex_values2855.GenericId<"GroupConnection">;
33
+ declare const list: convex_server220.RegisteredQuery<"public", {
34
+ connectionId: convex_values3280.GenericId<"GroupConnection">;
35
35
  }, Promise<{
36
- _id: convex_values2855.GenericId<"GroupWebhookEndpoint">;
36
+ _id: convex_values3280.GenericId<"GroupWebhookEndpoint">;
37
37
  _creationTime: number;
38
38
  extend?: any;
39
- createdByUserId?: convex_values2855.GenericId<"User"> | undefined;
39
+ createdByUserId?: convex_values3280.GenericId<"User"> | undefined;
40
40
  lastSuccessAt?: number | undefined;
41
41
  lastFailureAt?: number | undefined;
42
- connectionId: convex_values2855.GenericId<"GroupConnection">;
42
+ connectionId: convex_values3280.GenericId<"GroupConnection">;
43
+ groupId: convex_values3280.GenericId<"Group">;
43
44
  status: "active" | "disabled";
44
- groupId: convex_values2855.GenericId<"Group">;
45
45
  url: string;
46
46
  secretCiphertext: string;
47
47
  subscriptions: ("user.created" | "user.updated" | "session.signed_in" | "session.signed_out" | "session.invalidated" | "session.refresh_exchanged" | "session.refresh_reuse_detected" | "account.linked" | "account.unlinked" | "password.changed" | "passkey.added" | "passkey.removed" | "totp.enrolled" | "totp.removed" | "email.verified" | "phone.verified" | "api_key.created" | "api_key.revoked" | "oauth.client.created" | "oauth.client.revoked" | "oauth.code.created" | "oauth.token.created" | "oauth.token.exchanged" | "oauth.refresh.reuse_detected" | "oauth.refresh.revoked" | "connection.created" | "connection.updated" | "connection.removed" | "connection.login.succeeded" | "connection.login.failed" | "connection.domain.verification_requested" | "connection.domain.verified" | "connection.policy.updated" | "connection.saml.set" | "connection.saml.refreshed" | "connection.oidc.set" | "connection.scim.set" | "connection.scim.read" | "connection.scim.user.provisioned" | "connection.scim.user.updated" | "connection.scim.user.deactivated" | "connection.scim.user.reactivated" | "connection.scim.group.provisioned" | "connection.scim.group.updated" | "connection.scim.group.deactivated" | "connection.scim.group.reactivated" | "webhook.endpoint.created" | "webhook.endpoint.disabled" | "webhook.delivery.created" | "webhook.delivery.attempted" | "webhook.delivery.succeeded" | "webhook.delivery.failed")[];
48
48
  failureCount: number;
49
49
  }[]>>;
50
50
  /** Insert a new webhook endpoint (defaults `status` to `"active"`, `failureCount` to 0). */
51
- declare const create: convex_server215.RegisteredMutation<"public", {
51
+ declare const create: convex_server220.RegisteredMutation<"public", {
52
52
  extend?: any;
53
53
  status?: "active" | "disabled" | undefined;
54
- createdByUserId?: convex_values2855.GenericId<"User"> | undefined;
55
- connectionId: convex_values2855.GenericId<"GroupConnection">;
56
- groupId: convex_values2855.GenericId<"Group">;
54
+ createdByUserId?: convex_values3280.GenericId<"User"> | undefined;
55
+ connectionId: convex_values3280.GenericId<"GroupConnection">;
56
+ groupId: convex_values3280.GenericId<"Group">;
57
57
  url: string;
58
58
  secretCiphertext: string;
59
59
  subscriptions: ("user.created" | "user.updated" | "session.signed_in" | "session.signed_out" | "session.invalidated" | "session.refresh_exchanged" | "session.refresh_reuse_detected" | "account.linked" | "account.unlinked" | "password.changed" | "passkey.added" | "passkey.removed" | "totp.enrolled" | "totp.removed" | "email.verified" | "phone.verified" | "api_key.created" | "api_key.revoked" | "oauth.client.created" | "oauth.client.revoked" | "oauth.code.created" | "oauth.token.created" | "oauth.token.exchanged" | "oauth.refresh.reuse_detected" | "oauth.refresh.revoked" | "connection.created" | "connection.updated" | "connection.removed" | "connection.login.succeeded" | "connection.login.failed" | "connection.domain.verification_requested" | "connection.domain.verified" | "connection.policy.updated" | "connection.saml.set" | "connection.saml.refreshed" | "connection.oidc.set" | "connection.scim.set" | "connection.scim.read" | "connection.scim.user.provisioned" | "connection.scim.user.updated" | "connection.scim.user.deactivated" | "connection.scim.user.reactivated" | "connection.scim.group.provisioned" | "connection.scim.group.updated" | "connection.scim.group.deactivated" | "connection.scim.group.reactivated" | "webhook.endpoint.created" | "webhook.endpoint.disabled" | "webhook.delivery.created" | "webhook.delivery.attempted" | "webhook.delivery.succeeded" | "webhook.delivery.failed")[];
60
- }, Promise<convex_values2855.GenericId<"GroupWebhookEndpoint">>>;
60
+ }, Promise<convex_values3280.GenericId<"GroupWebhookEndpoint">>>;
61
61
  /** Patch fields on a webhook endpoint. */
62
- declare const update: convex_server215.RegisteredMutation<"public", {
63
- id: convex_values2855.GenericId<"GroupWebhookEndpoint">;
62
+ declare const update: convex_server220.RegisteredMutation<"public", {
63
+ id: convex_values3280.GenericId<"GroupWebhookEndpoint">;
64
64
  patch: {
65
65
  extend?: any;
66
66
  status?: "active" | "disabled" | undefined;
@@ -0,0 +1,26 @@
1
+ import { vAuthEventKind } from "../../model.js";
2
+ import { MutationCtx } from "../../_generated/server.js";
3
+ import { Infer } from "convex/values";
4
+
5
+ //#region src/component/connection/webhook/events.d.ts
6
+ declare namespace events_d_exports {
7
+ export { appendDeliveryEvent };
8
+ }
9
+ declare function appendDeliveryEvent(ctx: MutationCtx, args: {
10
+ deliveryId: string;
11
+ connectionId: string;
12
+ endpointId: string;
13
+ sourceEventId: string;
14
+ sourceEventType: Infer<typeof vAuthEventKind>;
15
+ kind: "webhook.delivery.created" | "webhook.delivery.attempted" | "webhook.delivery.succeeded" | "webhook.delivery.failed";
16
+ outcome: "success" | "failure";
17
+ occurredAt: number;
18
+ data?: {
19
+ attemptCount?: number;
20
+ status?: number;
21
+ error?: string;
22
+ };
23
+ }): Promise<void>;
24
+ //#endregion
25
+ export { events_d_exports };
26
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","names":[],"sources":["../../../../src/component/connection/webhook/events.ts"],"mappings":";;;;;;;;iBAQsB,mBAAA,CACpB,GAAA,EAAK,WAAA,EACL,IAAA;EACE,UAAA;EACA,YAAA;EACA,UAAA;EACA,aAAA;EACA,eAAA,EAAiB,KAAA,QAAa,cAAA;EAC9B,IAAA;EAKA,OAAA;EACA,UAAA;EACA,IAAA;IACE,YAAA;IACA,MAAA;IACA,KAAA;EAAA;AAAA,IAEH,OAAA"}
@@ -0,0 +1,39 @@
1
+ import { appendAuthEventProjection } from "../../event.js";
2
+
3
+ //#region src/component/connection/webhook/events.ts
4
+ async function appendDeliveryEvent(ctx, args) {
5
+ const attemptPart = typeof args.data?.attemptCount === "number" ? `:${args.data.attemptCount}` : "";
6
+ const event = {
7
+ eventId: `${args.kind}:${args.deliveryId}${attemptPart}`,
8
+ kind: args.kind,
9
+ occurredAt: args.occurredAt,
10
+ actor: {
11
+ type: "webhook",
12
+ id: args.endpointId
13
+ },
14
+ subject: {
15
+ type: "webhook_delivery",
16
+ id: args.deliveryId
17
+ },
18
+ targets: [{
19
+ kind: "connection",
20
+ id: args.connectionId
21
+ }],
22
+ outcome: args.outcome,
23
+ data: {
24
+ sourceEventId: args.sourceEventId,
25
+ sourceEventType: args.sourceEventType,
26
+ endpointId: args.endpointId,
27
+ deliveryId: args.deliveryId,
28
+ ...args.data
29
+ }
30
+ };
31
+ await appendAuthEventProjection(ctx, {
32
+ event,
33
+ targets: event.targets
34
+ });
35
+ }
36
+
37
+ //#endregion
38
+ export { appendDeliveryEvent };
39
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","names":[],"sources":["../../../../src/component/connection/webhook/events.ts"],"sourcesContent":["/** Internal webhook delivery audit helpers. */\n\nimport type { Infer } from \"convex/values\";\n\nimport type { MutationCtx } from \"../../_generated/server\";\nimport { appendAuthEventProjection } from \"../../event\";\nimport { vAuthEventKind } from \"../../model\";\n\nexport async function appendDeliveryEvent(\n ctx: MutationCtx,\n args: {\n deliveryId: string;\n connectionId: string;\n endpointId: string;\n sourceEventId: string;\n sourceEventType: Infer<typeof vAuthEventKind>;\n kind:\n | \"webhook.delivery.created\"\n | \"webhook.delivery.attempted\"\n | \"webhook.delivery.succeeded\"\n | \"webhook.delivery.failed\";\n outcome: \"success\" | \"failure\";\n occurredAt: number;\n data?: {\n attemptCount?: number;\n status?: number;\n error?: string;\n };\n },\n) {\n const attemptPart =\n typeof args.data?.attemptCount === \"number\" ? `:${args.data.attemptCount}` : \"\";\n const event = {\n eventId: `${args.kind}:${args.deliveryId}${attemptPart}`,\n kind: args.kind,\n occurredAt: args.occurredAt,\n actor: { type: \"webhook\" as const, id: args.endpointId },\n subject: { type: \"webhook_delivery\" as const, id: args.deliveryId },\n targets: [{ kind: \"connection\" as const, id: args.connectionId }],\n outcome: args.outcome,\n data: {\n sourceEventId: args.sourceEventId,\n sourceEventType: args.sourceEventType,\n endpointId: args.endpointId,\n deliveryId: args.deliveryId,\n ...args.data,\n },\n };\n await appendAuthEventProjection(ctx, { event, targets: event.targets });\n}\n"],"mappings":";;;AAQA,eAAsB,oBACpB,KACA,MAmBA;CACA,MAAM,cACJ,OAAO,KAAK,MAAM,iBAAiB,WAAW,IAAI,KAAK,KAAK,iBAAiB;CAC/E,MAAM,QAAQ;EACZ,SAAS,GAAG,KAAK,KAAK,GAAG,KAAK,aAAa;EAC3C,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,OAAO;GAAE,MAAM;GAAoB,IAAI,KAAK;GAAY;EACxD,SAAS;GAAE,MAAM;GAA6B,IAAI,KAAK;GAAY;EACnE,SAAS,CAAC;GAAE,MAAM;GAAuB,IAAI,KAAK;GAAc,CAAC;EACjE,SAAS,KAAK;EACd,MAAM;GACJ,eAAe,KAAK;GACpB,iBAAiB,KAAK;GACtB,YAAY,KAAK;GACjB,YAAY,KAAK;GACjB,GAAG,KAAK;GACT;EACF;AACD,OAAM,0BAA0B,KAAK;EAAE;EAAO,SAAS,MAAM;EAAS,CAAC"}