@thebookingkit/server 0.1.1

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 (126) hide show
  1. package/.turbo/turbo-build.log +6 -0
  2. package/.turbo/turbo-test.log +20 -0
  3. package/CHANGELOG.md +9 -0
  4. package/dist/__tests__/api.test.d.ts +2 -0
  5. package/dist/__tests__/api.test.d.ts.map +1 -0
  6. package/dist/__tests__/api.test.js +280 -0
  7. package/dist/__tests__/api.test.js.map +1 -0
  8. package/dist/__tests__/auth.test.d.ts +2 -0
  9. package/dist/__tests__/auth.test.d.ts.map +1 -0
  10. package/dist/__tests__/auth.test.js +78 -0
  11. package/dist/__tests__/auth.test.js.map +1 -0
  12. package/dist/__tests__/concurrent-booking.test.d.ts +2 -0
  13. package/dist/__tests__/concurrent-booking.test.d.ts.map +1 -0
  14. package/dist/__tests__/concurrent-booking.test.js +111 -0
  15. package/dist/__tests__/concurrent-booking.test.js.map +1 -0
  16. package/dist/__tests__/multi-tenancy.test.d.ts +2 -0
  17. package/dist/__tests__/multi-tenancy.test.d.ts.map +1 -0
  18. package/dist/__tests__/multi-tenancy.test.js +196 -0
  19. package/dist/__tests__/multi-tenancy.test.js.map +1 -0
  20. package/dist/__tests__/serialization-retry.test.d.ts +2 -0
  21. package/dist/__tests__/serialization-retry.test.d.ts.map +1 -0
  22. package/dist/__tests__/serialization-retry.test.js +53 -0
  23. package/dist/__tests__/serialization-retry.test.js.map +1 -0
  24. package/dist/__tests__/webhooks.test.d.ts +2 -0
  25. package/dist/__tests__/webhooks.test.d.ts.map +1 -0
  26. package/dist/__tests__/webhooks.test.js +286 -0
  27. package/dist/__tests__/webhooks.test.js.map +1 -0
  28. package/dist/__tests__/workflows.test.d.ts +2 -0
  29. package/dist/__tests__/workflows.test.d.ts.map +1 -0
  30. package/dist/__tests__/workflows.test.js +299 -0
  31. package/dist/__tests__/workflows.test.js.map +1 -0
  32. package/dist/adapters/calendar-adapter.d.ts +47 -0
  33. package/dist/adapters/calendar-adapter.d.ts.map +1 -0
  34. package/dist/adapters/calendar-adapter.js +2 -0
  35. package/dist/adapters/calendar-adapter.js.map +1 -0
  36. package/dist/adapters/email-adapter.d.ts +65 -0
  37. package/dist/adapters/email-adapter.d.ts.map +1 -0
  38. package/dist/adapters/email-adapter.js +40 -0
  39. package/dist/adapters/email-adapter.js.map +1 -0
  40. package/dist/adapters/index.d.ts +9 -0
  41. package/dist/adapters/index.d.ts.map +1 -0
  42. package/dist/adapters/index.js +3 -0
  43. package/dist/adapters/index.js.map +1 -0
  44. package/dist/adapters/job-adapter.d.ts +26 -0
  45. package/dist/adapters/job-adapter.d.ts.map +1 -0
  46. package/dist/adapters/job-adapter.js +13 -0
  47. package/dist/adapters/job-adapter.js.map +1 -0
  48. package/dist/adapters/payment-adapter.d.ts +106 -0
  49. package/dist/adapters/payment-adapter.d.ts.map +1 -0
  50. package/dist/adapters/payment-adapter.js +8 -0
  51. package/dist/adapters/payment-adapter.js.map +1 -0
  52. package/dist/adapters/sms-adapter.d.ts +33 -0
  53. package/dist/adapters/sms-adapter.d.ts.map +1 -0
  54. package/dist/adapters/sms-adapter.js +8 -0
  55. package/dist/adapters/sms-adapter.js.map +1 -0
  56. package/dist/adapters/storage-adapter.d.ts +12 -0
  57. package/dist/adapters/storage-adapter.d.ts.map +1 -0
  58. package/dist/adapters/storage-adapter.js +2 -0
  59. package/dist/adapters/storage-adapter.js.map +1 -0
  60. package/dist/api.d.ts +223 -0
  61. package/dist/api.d.ts.map +1 -0
  62. package/dist/api.js +271 -0
  63. package/dist/api.js.map +1 -0
  64. package/dist/auth.d.ts +71 -0
  65. package/dist/auth.d.ts.map +1 -0
  66. package/dist/auth.js +81 -0
  67. package/dist/auth.js.map +1 -0
  68. package/dist/booking-tokens.d.ts +23 -0
  69. package/dist/booking-tokens.d.ts.map +1 -0
  70. package/dist/booking-tokens.js +52 -0
  71. package/dist/booking-tokens.js.map +1 -0
  72. package/dist/email-templates.d.ts +36 -0
  73. package/dist/email-templates.d.ts.map +1 -0
  74. package/dist/email-templates.js +112 -0
  75. package/dist/email-templates.js.map +1 -0
  76. package/dist/index.d.ts +13 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +22 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/multi-tenancy.d.ts +132 -0
  81. package/dist/multi-tenancy.d.ts.map +1 -0
  82. package/dist/multi-tenancy.js +188 -0
  83. package/dist/multi-tenancy.js.map +1 -0
  84. package/dist/notification-jobs.d.ts +143 -0
  85. package/dist/notification-jobs.d.ts.map +1 -0
  86. package/dist/notification-jobs.js +278 -0
  87. package/dist/notification-jobs.js.map +1 -0
  88. package/dist/serialization-retry.d.ts +28 -0
  89. package/dist/serialization-retry.d.ts.map +1 -0
  90. package/dist/serialization-retry.js +71 -0
  91. package/dist/serialization-retry.js.map +1 -0
  92. package/dist/webhooks.d.ts +164 -0
  93. package/dist/webhooks.d.ts.map +1 -0
  94. package/dist/webhooks.js +228 -0
  95. package/dist/webhooks.js.map +1 -0
  96. package/dist/workflows.d.ts +169 -0
  97. package/dist/workflows.d.ts.map +1 -0
  98. package/dist/workflows.js +251 -0
  99. package/dist/workflows.js.map +1 -0
  100. package/package.json +32 -0
  101. package/src/__tests__/api.test.ts +354 -0
  102. package/src/__tests__/auth.test.ts +111 -0
  103. package/src/__tests__/concurrent-booking.test.ts +170 -0
  104. package/src/__tests__/multi-tenancy.test.ts +267 -0
  105. package/src/__tests__/serialization-retry.test.ts +76 -0
  106. package/src/__tests__/webhooks.test.ts +412 -0
  107. package/src/__tests__/workflows.test.ts +422 -0
  108. package/src/adapters/calendar-adapter.ts +49 -0
  109. package/src/adapters/email-adapter.ts +108 -0
  110. package/src/adapters/index.ts +36 -0
  111. package/src/adapters/job-adapter.ts +26 -0
  112. package/src/adapters/payment-adapter.ts +118 -0
  113. package/src/adapters/sms-adapter.ts +35 -0
  114. package/src/adapters/storage-adapter.ts +11 -0
  115. package/src/api.ts +446 -0
  116. package/src/auth.ts +146 -0
  117. package/src/booking-tokens.ts +61 -0
  118. package/src/email-templates.ts +140 -0
  119. package/src/index.ts +192 -0
  120. package/src/multi-tenancy.ts +301 -0
  121. package/src/notification-jobs.ts +428 -0
  122. package/src/serialization-retry.ts +94 -0
  123. package/src/webhooks.ts +378 -0
  124. package/src/workflows.ts +441 -0
  125. package/tsconfig.json +9 -0
  126. package/vitest.config.ts +7 -0
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Notification job payload types and helper functions for E-06.
3
+ *
4
+ * These types are used by background job functions (Inngest, Trigger.dev, BullMQ, etc.)
5
+ * when sending booking notification emails and syncing calendar events.
6
+ *
7
+ * The actual job implementation lives in the consumer app (or a provided Inngest
8
+ * functions file), but the payload shapes and builder functions are framework-agnostic.
9
+ */
10
+ import { generateICSAttachment } from "./adapters/index.js";
11
+ import { interpolateTemplate, CONFIRMATION_EMAIL_HTML, CONFIRMATION_EMAIL_TEXT, REMINDER_EMAIL_HTML, CANCELLATION_EMAIL_HTML, RESCHEDULE_EMAIL_HTML, } from "./email-templates.js";
12
+ import { JOB_NAMES } from "./adapters/job-adapter.js";
13
+ // ---------------------------------------------------------------------------
14
+ // Helper functions for building notification payloads
15
+ // ---------------------------------------------------------------------------
16
+ /**
17
+ * Format a UTC datetime string in a given timezone for email templates.
18
+ *
19
+ * @returns `{ date, time }` formatted for the email template variables
20
+ */
21
+ export function formatDateTimeForEmail(isoString, timezone) {
22
+ const dt = new Date(isoString);
23
+ const date = dt.toLocaleDateString("en-US", {
24
+ weekday: "long",
25
+ year: "numeric",
26
+ month: "long",
27
+ day: "numeric",
28
+ timeZone: timezone,
29
+ });
30
+ const time = dt.toLocaleTimeString("en-US", {
31
+ hour: "numeric",
32
+ minute: "2-digit",
33
+ hour12: true,
34
+ timeZone: timezone,
35
+ });
36
+ return { date, time };
37
+ }
38
+ /**
39
+ * Calculate duration in human-readable form from start/end ISO strings.
40
+ */
41
+ export function formatDurationForEmail(startsAt, endsAt) {
42
+ const ms = new Date(endsAt).getTime() - new Date(startsAt).getTime();
43
+ const mins = Math.round(ms / 60000);
44
+ if (mins < 60)
45
+ return `${mins} minutes`;
46
+ const hours = Math.floor(mins / 60);
47
+ const remainder = mins % 60;
48
+ return remainder === 0
49
+ ? `${hours} hour${hours !== 1 ? "s" : ""}`
50
+ : `${hours}h ${remainder}m`;
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // Job execution helpers (framework-agnostic)
54
+ // ---------------------------------------------------------------------------
55
+ /**
56
+ * Send a booking confirmation email to the customer (and optionally the provider).
57
+ *
58
+ * Call this from your Inngest/Trigger.dev/BullMQ job handler.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * // In your Inngest function:
63
+ * export const sendConfirmation = inngest.createFunction(
64
+ * { id: "send-confirmation-email" },
65
+ * { event: JOB_NAMES.SEND_CONFIRMATION_EMAIL },
66
+ * async ({ event }) => {
67
+ * await sendConfirmationEmail(event.data, emailAdapter);
68
+ * },
69
+ * );
70
+ * ```
71
+ */
72
+ export async function sendConfirmationEmail(payload, emailAdapter) {
73
+ const { date, time } = formatDateTimeForEmail(payload.startsAt, payload.timezone);
74
+ const duration = formatDurationForEmail(payload.startsAt, payload.endsAt);
75
+ const vars = {
76
+ bookingId: payload.bookingId,
77
+ eventTitle: payload.eventTitle,
78
+ providerName: payload.providerName,
79
+ customerName: payload.customerName,
80
+ customerEmail: payload.customerEmail,
81
+ date,
82
+ time,
83
+ duration,
84
+ timezone: payload.timezone,
85
+ location: payload.location,
86
+ managementUrl: payload.managementUrl,
87
+ unsubscribeUrl: payload.unsubscribeUrl,
88
+ };
89
+ const icsAttachment = generateBookingICS(payload);
90
+ await emailAdapter.send({
91
+ to: payload.customerEmail,
92
+ subject: `Booking Confirmed: ${payload.eventTitle} on ${date}`,
93
+ html: interpolateTemplate(CONFIRMATION_EMAIL_HTML, vars),
94
+ text: interpolateTemplate(CONFIRMATION_EMAIL_TEXT, vars),
95
+ attachments: icsAttachment ? [icsAttachment] : undefined,
96
+ headers: buildEmailHeaders(payload.unsubscribeUrl),
97
+ });
98
+ if (payload.notifyProvider) {
99
+ await emailAdapter.send({
100
+ to: payload.providerEmail,
101
+ subject: `New Booking: ${payload.customerName} — ${payload.eventTitle}`,
102
+ html: interpolateTemplate(CONFIRMATION_EMAIL_HTML, {
103
+ ...vars,
104
+ customerName: payload.customerName,
105
+ }),
106
+ text: interpolateTemplate(CONFIRMATION_EMAIL_TEXT, vars),
107
+ headers: buildEmailHeaders(),
108
+ });
109
+ }
110
+ }
111
+ /**
112
+ * Send a reminder email to the customer before their appointment.
113
+ */
114
+ export async function sendReminderEmail(payload, emailAdapter) {
115
+ const { date, time } = formatDateTimeForEmail(payload.startsAt, payload.timezone);
116
+ const vars = {
117
+ bookingId: payload.bookingId,
118
+ eventTitle: payload.eventTitle,
119
+ providerName: payload.providerName,
120
+ customerName: payload.customerName,
121
+ customerEmail: payload.customerEmail,
122
+ date,
123
+ time,
124
+ duration: formatDurationForEmail(payload.startsAt, payload.endsAt),
125
+ timezone: payload.timezone,
126
+ location: payload.location,
127
+ managementUrl: payload.managementUrl,
128
+ unsubscribeUrl: payload.unsubscribeUrl,
129
+ };
130
+ const label = payload.reminderHours >= 24
131
+ ? `${payload.reminderHours / 24} day`
132
+ : `${payload.reminderHours} hour`;
133
+ await emailAdapter.send({
134
+ to: payload.customerEmail,
135
+ subject: `Reminder: ${payload.eventTitle} in ${label}${Number(label.split(" ")[0]) !== 1 ? "s" : ""}`,
136
+ html: interpolateTemplate(REMINDER_EMAIL_HTML, vars),
137
+ headers: buildEmailHeaders(payload.unsubscribeUrl),
138
+ });
139
+ }
140
+ /**
141
+ * Send cancellation notification emails to customer and provider.
142
+ */
143
+ export async function sendCancellationEmail(payload, emailAdapter) {
144
+ const { date, time } = formatDateTimeForEmail(payload.startsAt, payload.timezone);
145
+ const vars = {
146
+ bookingId: payload.bookingId,
147
+ eventTitle: payload.eventTitle,
148
+ providerName: payload.providerName,
149
+ customerName: payload.customerName,
150
+ customerEmail: payload.customerEmail,
151
+ date,
152
+ time,
153
+ duration: formatDurationForEmail(payload.startsAt, payload.endsAt),
154
+ timezone: payload.timezone,
155
+ location: payload.location,
156
+ unsubscribeUrl: payload.unsubscribeUrl,
157
+ cancelReason: payload.reason,
158
+ };
159
+ await emailAdapter.send({
160
+ to: payload.customerEmail,
161
+ subject: `Booking Cancelled: ${payload.eventTitle}`,
162
+ html: interpolateTemplate(CANCELLATION_EMAIL_HTML, vars),
163
+ headers: buildEmailHeaders(payload.unsubscribeUrl),
164
+ });
165
+ await emailAdapter.send({
166
+ to: payload.providerEmail,
167
+ subject: `Booking Cancelled by ${payload.cancelledBy}: ${payload.customerName}`,
168
+ html: interpolateTemplate(CANCELLATION_EMAIL_HTML, vars),
169
+ headers: buildEmailHeaders(),
170
+ });
171
+ }
172
+ /**
173
+ * Send reschedule notification emails to customer and provider.
174
+ */
175
+ export async function sendRescheduleEmail(payload, emailAdapter) {
176
+ const { date: newDate, time: newTime } = formatDateTimeForEmail(payload.startsAt, payload.timezone);
177
+ const { date: oldDate, time: oldTime } = formatDateTimeForEmail(payload.oldStartsAt, payload.timezone);
178
+ const vars = {
179
+ bookingId: payload.bookingId,
180
+ eventTitle: payload.eventTitle,
181
+ providerName: payload.providerName,
182
+ customerName: payload.customerName,
183
+ customerEmail: payload.customerEmail,
184
+ date: newDate,
185
+ time: newTime,
186
+ duration: formatDurationForEmail(payload.startsAt, payload.endsAt),
187
+ timezone: payload.timezone,
188
+ location: payload.location,
189
+ managementUrl: payload.managementUrl,
190
+ unsubscribeUrl: payload.unsubscribeUrl,
191
+ oldDate,
192
+ oldTime,
193
+ newDate,
194
+ newTime,
195
+ };
196
+ await emailAdapter.send({
197
+ to: payload.customerEmail,
198
+ subject: `Booking Rescheduled: ${payload.eventTitle}`,
199
+ html: interpolateTemplate(RESCHEDULE_EMAIL_HTML, vars),
200
+ headers: buildEmailHeaders(payload.unsubscribeUrl),
201
+ });
202
+ await emailAdapter.send({
203
+ to: payload.providerEmail,
204
+ subject: `Booking Rescheduled: ${payload.customerName} — ${payload.eventTitle}`,
205
+ html: interpolateTemplate(RESCHEDULE_EMAIL_HTML, vars),
206
+ headers: buildEmailHeaders(),
207
+ });
208
+ }
209
+ /**
210
+ * Schedule the auto-rejection of a pending booking using the job adapter.
211
+ *
212
+ * Call this immediately after creating a booking that requires confirmation.
213
+ *
214
+ * @param bookingId - ID of the pending booking
215
+ * @param deadline - Date at which to auto-reject (from `getAutoRejectDeadline`)
216
+ * @param jobs - Your `JobAdapter` instance
217
+ * @returns The scheduled job ID (store it if you need to cancel on manual confirm/reject)
218
+ */
219
+ export async function scheduleAutoReject(bookingId, deadline, jobs) {
220
+ const payload = { bookingId, actor: "system" };
221
+ return jobs.schedule(JOB_NAMES.AUTO_REJECT_PENDING, payload, deadline);
222
+ }
223
+ /**
224
+ * Sync a confirmed booking to the provider's connected calendar.
225
+ */
226
+ export async function syncBookingToCalendar(payload, calendarAdapter) {
227
+ const result = await calendarAdapter.createEvent({
228
+ title: `${payload.eventTitle} — ${payload.customerName}`,
229
+ description: `Customer: ${payload.customerName} (${payload.customerEmail})`,
230
+ startsAt: new Date(payload.startsAt),
231
+ endsAt: new Date(payload.endsAt),
232
+ timezone: payload.timezone,
233
+ location: payload.location,
234
+ attendees: [payload.customerEmail],
235
+ });
236
+ return result.eventId;
237
+ }
238
+ /**
239
+ * Delete a calendar event when a booking is cancelled.
240
+ */
241
+ export async function deleteBookingFromCalendar(externalEventId, calendarAdapter) {
242
+ await calendarAdapter.deleteEvent(externalEventId);
243
+ }
244
+ // ---------------------------------------------------------------------------
245
+ // Internal helpers
246
+ // ---------------------------------------------------------------------------
247
+ function buildEmailHeaders(unsubscribeUrl) {
248
+ if (!unsubscribeUrl)
249
+ return undefined;
250
+ return {
251
+ "List-Unsubscribe": `<${unsubscribeUrl}>`,
252
+ "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
253
+ };
254
+ }
255
+ function generateBookingICS(payload) {
256
+ try {
257
+ const attachment = generateICSAttachment({
258
+ id: payload.bookingId,
259
+ title: payload.eventTitle,
260
+ startsAt: new Date(payload.startsAt),
261
+ endsAt: new Date(payload.endsAt),
262
+ location: payload.location,
263
+ description: `Booking with ${payload.providerName}`,
264
+ organizerEmail: payload.providerEmail,
265
+ attendeeEmail: payload.customerEmail,
266
+ });
267
+ return {
268
+ filename: attachment.filename,
269
+ content: attachment.content,
270
+ contentType: attachment.contentType ?? "text/calendar",
271
+ };
272
+ }
273
+ catch {
274
+ return null;
275
+ }
276
+ }
277
+ export { JOB_NAMES };
278
+ //# sourceMappingURL=notification-jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-jobs.js","sourceRoot":"","sources":["../src/notification-jobs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /** Options for the serialization retry wrapper */
2
+ export interface SerializableRetryOptions {
3
+ /** Maximum number of retries on serialization failure. Default: 3 */
4
+ maxRetries?: number;
5
+ /** Base delay in milliseconds for exponential backoff. Default: 50 */
6
+ baseDelayMs?: number;
7
+ }
8
+ /**
9
+ * Wraps a database operation in serialization retry logic.
10
+ *
11
+ * If the operation fails with SQLSTATE 40001 (serialization_failure),
12
+ * retries up to `maxRetries` times with jittered exponential backoff.
13
+ *
14
+ * If the operation fails with SQLSTATE 23P01 (exclusion_violation),
15
+ * immediately throws a `BookingConflictError` (no retry — the slot is taken).
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const booking = await withSerializableRetry(
20
+ * () => db.transaction(async (tx) => {
21
+ * // insert booking in SERIALIZABLE isolation
22
+ * }),
23
+ * { maxRetries: 3 }
24
+ * );
25
+ * ```
26
+ */
27
+ export declare function withSerializableRetry<T>(fn: () => Promise<T>, options?: SerializableRetryOptions): Promise<T>;
28
+ //# sourceMappingURL=serialization-retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization-retry.d.ts","sourceRoot":"","sources":["../src/serialization-retry.ts"],"names":[],"mappings":"AAKA,kDAAkD;AAClD,MAAM,WAAW,wBAAwB;IACvC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAyBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,CAAC,CAAC,CAmCZ"}
@@ -0,0 +1,71 @@
1
+ import { BookingConflictError, SerializationRetryExhaustedError, } from "@thebookingkit/core";
2
+ /**
3
+ * Postgres SQLSTATE codes we handle:
4
+ * - 40001: serialization_failure (SERIALIZABLE transaction contention)
5
+ * - 23P01: exclusion_violation (EXCLUDE constraint — slot already taken)
6
+ */
7
+ const SERIALIZATION_FAILURE = "40001";
8
+ const EXCLUSION_VIOLATION = "23P01";
9
+ /**
10
+ * Check if an error is a Postgres error with a specific code.
11
+ */
12
+ function getPostgresErrorCode(error) {
13
+ if (error &&
14
+ typeof error === "object" &&
15
+ "code" in error &&
16
+ typeof error.code === "string") {
17
+ return error.code;
18
+ }
19
+ return undefined;
20
+ }
21
+ /**
22
+ * Wraps a database operation in serialization retry logic.
23
+ *
24
+ * If the operation fails with SQLSTATE 40001 (serialization_failure),
25
+ * retries up to `maxRetries` times with jittered exponential backoff.
26
+ *
27
+ * If the operation fails with SQLSTATE 23P01 (exclusion_violation),
28
+ * immediately throws a `BookingConflictError` (no retry — the slot is taken).
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const booking = await withSerializableRetry(
33
+ * () => db.transaction(async (tx) => {
34
+ * // insert booking in SERIALIZABLE isolation
35
+ * }),
36
+ * { maxRetries: 3 }
37
+ * );
38
+ * ```
39
+ */
40
+ export async function withSerializableRetry(fn, options) {
41
+ const maxRetries = options?.maxRetries ?? 3;
42
+ const baseDelayMs = options?.baseDelayMs ?? 50;
43
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
44
+ try {
45
+ return await fn();
46
+ }
47
+ catch (error) {
48
+ const code = getPostgresErrorCode(error);
49
+ // Exclusion violation — slot is taken, do not retry
50
+ if (code === EXCLUSION_VIOLATION) {
51
+ throw new BookingConflictError();
52
+ }
53
+ // Serialization failure — retry with backoff
54
+ if (code === SERIALIZATION_FAILURE && attempt < maxRetries) {
55
+ const delay = baseDelayMs * Math.pow(2, attempt);
56
+ const jitter = Math.random() * delay;
57
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
58
+ continue;
59
+ }
60
+ // All retries exhausted for serialization failure
61
+ if (code === SERIALIZATION_FAILURE) {
62
+ throw new SerializationRetryExhaustedError(maxRetries);
63
+ }
64
+ // Unknown error — rethrow as-is
65
+ throw error;
66
+ }
67
+ }
68
+ // Should never reach here, but TypeScript needs it
69
+ throw new SerializationRetryExhaustedError(maxRetries);
70
+ }
71
+ //# sourceMappingURL=serialization-retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization-retry.js","sourceRoot":"","sources":["../src/serialization-retry.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,gCAAgC,GACjC,MAAM,qBAAqB,CAAC;AAU7B;;;;GAIG;AACH,MAAM,qBAAqB,GAAG,OAAO,CAAC;AACtC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAEpC;;GAEG;AACH,SAAS,oBAAoB,CAAC,KAAc;IAC1C,IACE,KAAK;QACL,OAAO,KAAK,KAAK,QAAQ;QACzB,MAAM,IAAI,KAAK;QACf,OAAQ,KAA2B,CAAC,IAAI,KAAK,QAAQ,EACrD,CAAC;QACD,OAAQ,KAA0B,CAAC,IAAI,CAAC;IAC1C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,EAAoB,EACpB,OAAkC;IAElC,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,CAAC,CAAC;IAC5C,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,EAAE,CAAC;IAE/C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAEzC,oDAAoD;YACpD,IAAI,IAAI,KAAK,mBAAmB,EAAE,CAAC;gBACjC,MAAM,IAAI,oBAAoB,EAAE,CAAC;YACnC,CAAC;YAED,6CAA6C;YAC7C,IAAI,IAAI,KAAK,qBAAqB,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;gBAC3D,MAAM,KAAK,GAAG,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBACjD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC;gBACrC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;gBACpE,SAAS;YACX,CAAC;YAED,kDAAkD;YAClD,IAAI,IAAI,KAAK,qBAAqB,EAAE,CAAC;gBACnC,MAAM,IAAI,gCAAgC,CAAC,UAAU,CAAC,CAAC;YACzD,CAAC;YAED,gCAAgC;YAChC,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,mDAAmD;IACnD,MAAM,IAAI,gCAAgC,CAAC,UAAU,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Webhook infrastructure for event-driven integrations.
3
+ *
4
+ * Includes typed payloads, HMAC-SHA256 signing with replay protection,
5
+ * retry logic, and custom payload templates.
6
+ */
7
+ /** Supported webhook trigger events */
8
+ export type WebhookTrigger = "BOOKING_CREATED" | "BOOKING_CONFIRMED" | "BOOKING_CANCELLED" | "BOOKING_RESCHEDULED" | "BOOKING_REJECTED" | "BOOKING_PAID" | "BOOKING_NO_SHOW" | "FORM_SUBMITTED" | "OOO_CREATED";
9
+ /** Attendee in a webhook payload */
10
+ export interface WebhookAttendee {
11
+ email: string;
12
+ name: string;
13
+ phone?: string;
14
+ }
15
+ /** Standard webhook payload body */
16
+ export interface WebhookPayload {
17
+ bookingId: string;
18
+ eventType: string;
19
+ startTime: string;
20
+ endTime: string;
21
+ organizer: {
22
+ name: string;
23
+ email: string;
24
+ };
25
+ attendees: WebhookAttendee[];
26
+ status: string;
27
+ responses?: Record<string, unknown>;
28
+ metadata?: Record<string, unknown>;
29
+ }
30
+ /** Full webhook envelope */
31
+ export interface WebhookEnvelope {
32
+ triggerEvent: WebhookTrigger;
33
+ createdAt: string;
34
+ payload: WebhookPayload;
35
+ }
36
+ /** A webhook subscription definition */
37
+ export interface WebhookSubscription {
38
+ id: string;
39
+ subscriberUrl: string;
40
+ triggers: WebhookTrigger[];
41
+ secret?: string;
42
+ isActive: boolean;
43
+ /** Optional scope */
44
+ eventTypeId?: string;
45
+ teamId?: string;
46
+ /** Optional custom payload template (JSON with {{variable}} placeholders) */
47
+ payloadTemplate?: string;
48
+ }
49
+ /** Result of a webhook delivery attempt */
50
+ export interface WebhookDeliveryResult {
51
+ webhookId: string;
52
+ trigger: WebhookTrigger;
53
+ responseCode: number | null;
54
+ success: boolean;
55
+ attempt: number;
56
+ deliveredAt: Date;
57
+ error?: string;
58
+ }
59
+ /** Retry configuration for webhook delivery */
60
+ export interface WebhookRetryConfig {
61
+ /** Maximum number of retry attempts (default: 3) */
62
+ maxRetries: number;
63
+ /** Backoff delays in seconds for each retry (default: [10, 60, 300]) */
64
+ backoffSeconds: number[];
65
+ }
66
+ /** Result of signature verification */
67
+ export interface WebhookVerificationResult {
68
+ valid: boolean;
69
+ reason?: "timestamp_expired" | "signature_mismatch";
70
+ }
71
+ /** Default retry configuration */
72
+ export declare const DEFAULT_RETRY_CONFIG: WebhookRetryConfig;
73
+ /** All valid webhook triggers */
74
+ export declare const WEBHOOK_TRIGGERS: WebhookTrigger[];
75
+ /** Signature header name */
76
+ export declare const SIGNATURE_HEADER = "X-SlotKit-Signature";
77
+ /** Timestamp header name */
78
+ export declare const TIMESTAMP_HEADER = "X-SlotKit-Timestamp";
79
+ /** Default tolerance window in seconds (5 minutes) */
80
+ export declare const DEFAULT_TOLERANCE_SECONDS = 300;
81
+ /** Error thrown when webhook validation fails */
82
+ export declare class WebhookValidationError extends Error {
83
+ constructor(message: string);
84
+ }
85
+ /**
86
+ * Create an HMAC-SHA256 signature for a webhook payload.
87
+ *
88
+ * Signature = HMAC-SHA256(secret, timestamp + '.' + rawBody)
89
+ *
90
+ * @param rawBody - The raw JSON string of the payload
91
+ * @param secret - The webhook secret key
92
+ * @param timestampSeconds - Unix timestamp in seconds
93
+ * @returns The hex-encoded HMAC signature
94
+ */
95
+ export declare function signWebhookPayload(rawBody: string, secret: string, timestampSeconds: number): string;
96
+ /**
97
+ * Verify a webhook signature with replay protection.
98
+ *
99
+ * @param rawBody - The raw JSON string of the received payload
100
+ * @param signature - The value of the X-SlotKit-Signature header
101
+ * @param timestampSeconds - The value of the X-SlotKit-Timestamp header (Unix seconds)
102
+ * @param secret - The webhook secret key
103
+ * @param options - Optional configuration
104
+ * @param options.toleranceSeconds - Maximum age of the timestamp in seconds (default: 300)
105
+ * @returns Verification result with reason if invalid
106
+ */
107
+ export declare function verifyWebhookSignature(rawBody: string, signature: string, timestampSeconds: number, secret: string, options?: {
108
+ toleranceSeconds?: number;
109
+ }): WebhookVerificationResult;
110
+ /**
111
+ * Create a standard webhook envelope.
112
+ *
113
+ * @param trigger - The trigger event
114
+ * @param payload - The webhook payload data
115
+ * @returns The full webhook envelope
116
+ */
117
+ export declare function createWebhookEnvelope(trigger: WebhookTrigger, payload: WebhookPayload): WebhookEnvelope;
118
+ /**
119
+ * Resolve a custom payload template with webhook data.
120
+ *
121
+ * Replaces `{{variable}}` placeholders with values from the envelope.
122
+ * Supported variables: triggerEvent, createdAt, bookingId, eventType,
123
+ * startTime, endTime, status, and any workflow template variables.
124
+ *
125
+ * @param template - The JSON template string with {{variable}} placeholders
126
+ * @param envelope - The webhook envelope
127
+ * @returns The resolved JSON string
128
+ */
129
+ export declare function resolvePayloadTemplate(template: string, envelope: WebhookEnvelope): string;
130
+ /**
131
+ * Find all active webhook subscriptions that match a trigger and optional scope.
132
+ *
133
+ * @param subscriptions - All available webhook subscriptions
134
+ * @param trigger - The trigger event that occurred
135
+ * @param scope - Optional scope filters (eventTypeId, teamId)
136
+ * @returns Matching subscriptions
137
+ */
138
+ export declare function matchWebhookSubscriptions(subscriptions: WebhookSubscription[], trigger: WebhookTrigger, scope?: {
139
+ eventTypeId?: string;
140
+ teamId?: string;
141
+ }): WebhookSubscription[];
142
+ /**
143
+ * Determine the delay before the next retry attempt.
144
+ *
145
+ * @param attempt - The current attempt number (0-indexed)
146
+ * @param config - Retry configuration
147
+ * @returns Delay in seconds, or null if max retries exceeded
148
+ */
149
+ export declare function getRetryDelay(attempt: number, config?: WebhookRetryConfig): number | null;
150
+ /**
151
+ * Determine if a response code indicates success (2xx).
152
+ *
153
+ * @param statusCode - HTTP response status code
154
+ * @returns Whether the delivery was successful
155
+ */
156
+ export declare function isSuccessResponse(statusCode: number): boolean;
157
+ /**
158
+ * Validate a webhook subscription.
159
+ *
160
+ * @param subscription - The subscription to validate
161
+ * @throws {WebhookValidationError} If the subscription is invalid
162
+ */
163
+ export declare function validateWebhookSubscription(subscription: Omit<WebhookSubscription, "id">): void;
164
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"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"}