@thebookingkit/server 0.1.2 → 0.1.5

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 (95) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/calendar-adapter.d.ts +47 -0
  3. package/dist/adapters/calendar-adapter.d.ts.map +1 -0
  4. package/dist/adapters/calendar-adapter.js +2 -0
  5. package/dist/adapters/calendar-adapter.js.map +1 -0
  6. package/dist/adapters/email-adapter.d.ts +65 -0
  7. package/dist/adapters/email-adapter.d.ts.map +1 -0
  8. package/dist/adapters/email-adapter.js +41 -0
  9. package/dist/adapters/email-adapter.js.map +1 -0
  10. package/dist/adapters/index.d.ts +9 -0
  11. package/dist/adapters/index.d.ts.map +1 -0
  12. package/dist/adapters/index.js +3 -0
  13. package/dist/adapters/index.js.map +1 -0
  14. package/dist/adapters/job-adapter.d.ts +26 -0
  15. package/dist/adapters/job-adapter.d.ts.map +1 -0
  16. package/dist/adapters/job-adapter.js +13 -0
  17. package/dist/adapters/job-adapter.js.map +1 -0
  18. package/dist/adapters/payment-adapter.d.ts +106 -0
  19. package/dist/adapters/payment-adapter.d.ts.map +1 -0
  20. package/dist/adapters/payment-adapter.js +8 -0
  21. package/dist/adapters/payment-adapter.js.map +1 -0
  22. package/dist/adapters/sms-adapter.d.ts +33 -0
  23. package/dist/adapters/sms-adapter.d.ts.map +1 -0
  24. package/dist/adapters/sms-adapter.js +8 -0
  25. package/dist/adapters/sms-adapter.js.map +1 -0
  26. package/{src/adapters/storage-adapter.ts → dist/adapters/storage-adapter.d.ts} +5 -4
  27. package/dist/adapters/storage-adapter.d.ts.map +1 -0
  28. package/dist/adapters/storage-adapter.js +2 -0
  29. package/dist/adapters/storage-adapter.js.map +1 -0
  30. package/dist/api.d.ts +223 -0
  31. package/dist/api.d.ts.map +1 -0
  32. package/dist/api.js +286 -0
  33. package/dist/api.js.map +1 -0
  34. package/dist/auth.d.ts +71 -0
  35. package/dist/auth.d.ts.map +1 -0
  36. package/dist/auth.js +90 -0
  37. package/dist/auth.js.map +1 -0
  38. package/dist/booking-tokens.d.ts +23 -0
  39. package/dist/booking-tokens.d.ts.map +1 -0
  40. package/dist/booking-tokens.js +52 -0
  41. package/dist/booking-tokens.js.map +1 -0
  42. package/dist/email-templates.d.ts +32 -0
  43. package/dist/email-templates.d.ts.map +1 -0
  44. package/{src/email-templates.ts → dist/email-templates.js} +14 -34
  45. package/dist/email-templates.js.map +1 -0
  46. package/dist/index.d.ts +13 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +22 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/multi-tenancy.d.ts +123 -0
  51. package/dist/multi-tenancy.d.ts.map +1 -0
  52. package/dist/multi-tenancy.js +197 -0
  53. package/dist/multi-tenancy.js.map +1 -0
  54. package/dist/notification-jobs.d.ts +143 -0
  55. package/dist/notification-jobs.d.ts.map +1 -0
  56. package/dist/notification-jobs.js +278 -0
  57. package/dist/notification-jobs.js.map +1 -0
  58. package/dist/serialization-retry.d.ts +28 -0
  59. package/dist/serialization-retry.d.ts.map +1 -0
  60. package/dist/serialization-retry.js +71 -0
  61. package/dist/serialization-retry.js.map +1 -0
  62. package/dist/webhooks.d.ts +164 -0
  63. package/dist/webhooks.d.ts.map +1 -0
  64. package/dist/webhooks.js +239 -0
  65. package/dist/webhooks.js.map +1 -0
  66. package/dist/workflows.d.ts +169 -0
  67. package/dist/workflows.d.ts.map +1 -0
  68. package/dist/workflows.js +271 -0
  69. package/dist/workflows.js.map +1 -0
  70. package/package.json +21 -2
  71. package/CHANGELOG.md +0 -9
  72. package/src/__tests__/api.test.ts +0 -354
  73. package/src/__tests__/auth.test.ts +0 -111
  74. package/src/__tests__/concurrent-booking.test.ts +0 -170
  75. package/src/__tests__/multi-tenancy.test.ts +0 -267
  76. package/src/__tests__/serialization-retry.test.ts +0 -76
  77. package/src/__tests__/webhooks.test.ts +0 -412
  78. package/src/__tests__/workflows.test.ts +0 -422
  79. package/src/adapters/calendar-adapter.ts +0 -49
  80. package/src/adapters/email-adapter.ts +0 -108
  81. package/src/adapters/index.ts +0 -36
  82. package/src/adapters/job-adapter.ts +0 -26
  83. package/src/adapters/payment-adapter.ts +0 -118
  84. package/src/adapters/sms-adapter.ts +0 -35
  85. package/src/api.ts +0 -446
  86. package/src/auth.ts +0 -146
  87. package/src/booking-tokens.ts +0 -61
  88. package/src/index.ts +0 -192
  89. package/src/multi-tenancy.ts +0 -301
  90. package/src/notification-jobs.ts +0 -428
  91. package/src/serialization-retry.ts +0 -94
  92. package/src/webhooks.ts +0 -378
  93. package/src/workflows.ts +0 -441
  94. package/tsconfig.json +0 -9
  95. package/vitest.config.ts +0 -7
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Multi-tenancy utilities for organization-scoped deployments.
3
+ *
4
+ * Provides organization settings resolution, cascading defaults,
5
+ * role-based access control, and tenant authorization helpers.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Errors
9
+ // ---------------------------------------------------------------------------
10
+ /** Error thrown for multi-tenancy authorization violations */
11
+ export class TenantAuthorizationError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "TenantAuthorizationError";
15
+ }
16
+ }
17
+ // ---------------------------------------------------------------------------
18
+ // Global Defaults
19
+ // ---------------------------------------------------------------------------
20
+ /** System-wide defaults used as the base for cascading resolution */
21
+ export const GLOBAL_DEFAULTS = {
22
+ timezone: "UTC",
23
+ currency: "USD",
24
+ bufferMinutes: 0,
25
+ };
26
+ // ---------------------------------------------------------------------------
27
+ // Settings Resolution
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Resolve effective settings via the cascade:
31
+ * `event_type > provider > organization > global defaults`
32
+ *
33
+ * @param orgSettings - Organization-level settings
34
+ * @param providerSettings - Provider-level settings (overrides org)
35
+ * @param eventTypeSettings - Event type settings (overrides provider)
36
+ * @returns Fully resolved effective settings
37
+ */
38
+ export function resolveEffectiveSettings(orgSettings, providerSettings, eventTypeSettings) {
39
+ // Start with global defaults
40
+ let timezone = GLOBAL_DEFAULTS.timezone;
41
+ let currency = GLOBAL_DEFAULTS.currency;
42
+ let bufferMinutes = GLOBAL_DEFAULTS.bufferMinutes;
43
+ let branding = {};
44
+ let bookingLimits = {};
45
+ // Apply org settings
46
+ if (orgSettings) {
47
+ if (orgSettings.defaultTimezone)
48
+ timezone = orgSettings.defaultTimezone;
49
+ if (orgSettings.defaultCurrency)
50
+ currency = orgSettings.defaultCurrency;
51
+ if (orgSettings.defaultBufferMinutes !== undefined)
52
+ bufferMinutes = orgSettings.defaultBufferMinutes;
53
+ if (orgSettings.branding)
54
+ branding = { ...branding, ...orgSettings.branding };
55
+ if (orgSettings.defaultBookingLimits)
56
+ bookingLimits = { ...orgSettings.defaultBookingLimits };
57
+ }
58
+ // Apply provider settings (override org)
59
+ if (providerSettings) {
60
+ if (providerSettings.timezone)
61
+ timezone = providerSettings.timezone;
62
+ if (providerSettings.currency)
63
+ currency = providerSettings.currency;
64
+ if (providerSettings.bufferMinutes !== undefined)
65
+ bufferMinutes = providerSettings.bufferMinutes;
66
+ if (providerSettings.branding)
67
+ branding = { ...branding, ...providerSettings.branding };
68
+ if (providerSettings.bookingLimits)
69
+ bookingLimits = { ...bookingLimits, ...providerSettings.bookingLimits };
70
+ }
71
+ // Apply event type settings (override provider)
72
+ if (eventTypeSettings) {
73
+ if (eventTypeSettings.timezone)
74
+ timezone = eventTypeSettings.timezone;
75
+ if (eventTypeSettings.currency)
76
+ currency = eventTypeSettings.currency;
77
+ if (eventTypeSettings.bufferBefore !== undefined)
78
+ bufferMinutes = eventTypeSettings.bufferBefore;
79
+ if (eventTypeSettings.bookingLimits)
80
+ bookingLimits = { ...bookingLimits, ...eventTypeSettings.bookingLimits };
81
+ }
82
+ return { timezone, currency, bufferMinutes, branding, bookingLimits };
83
+ }
84
+ // ---------------------------------------------------------------------------
85
+ // Role-Based Access Control
86
+ // ---------------------------------------------------------------------------
87
+ /** Permissions granted to each role */
88
+ const ROLE_PERMISSIONS = {
89
+ owner: [
90
+ "manage:members",
91
+ "manage:teams",
92
+ "manage:event-types",
93
+ "view:all-bookings",
94
+ "view:own-bookings",
95
+ "manage:own-availability",
96
+ "view:analytics",
97
+ "manage:organization",
98
+ ],
99
+ admin: [
100
+ "manage:teams",
101
+ "manage:event-types",
102
+ "view:all-bookings",
103
+ "view:own-bookings",
104
+ "manage:own-availability",
105
+ "view:analytics",
106
+ ],
107
+ member: [
108
+ "view:own-bookings",
109
+ "manage:own-availability",
110
+ ],
111
+ };
112
+ /**
113
+ * Get all permissions granted to a role.
114
+ *
115
+ * @param role - The organization role
116
+ * @returns Array of permissions
117
+ */
118
+ export function getRolePermissions(role) {
119
+ return ROLE_PERMISSIONS[role] ?? [];
120
+ }
121
+ /**
122
+ * Check if a role has a specific permission.
123
+ *
124
+ * @param role - The organization role
125
+ * @param permission - The permission to check
126
+ * @returns Whether the role has the permission
127
+ */
128
+ export function roleHasPermission(role, permission) {
129
+ return getRolePermissions(role).includes(permission);
130
+ }
131
+ /**
132
+ * Assert that an org member has a required permission.
133
+ *
134
+ * @param member - The org member
135
+ * @param permission - The required permission
136
+ * @throws {TenantAuthorizationError} If the member lacks the permission
137
+ */
138
+ export function assertOrgPermission(member, permission) {
139
+ if (!roleHasPermission(member.role, permission)) {
140
+ throw new TenantAuthorizationError(`Role "${member.role}" does not have permission: "${permission}"`);
141
+ }
142
+ }
143
+ // ---------------------------------------------------------------------------
144
+ // Tenant Scoping
145
+ // ---------------------------------------------------------------------------
146
+ /**
147
+ * Validate that a resource belongs to the expected organization.
148
+ *
149
+ * @param resourceOrgId - Organization ID on the resource
150
+ * @param expectedOrgId - The org ID from the authenticated context
151
+ * @throws {TenantAuthorizationError} If there is a tenant mismatch
152
+ */
153
+ export function assertTenantScope(resourceOrgId, expectedOrgId) {
154
+ if (resourceOrgId == null) {
155
+ throw new TenantAuthorizationError("Resource has no organization scope");
156
+ }
157
+ if (resourceOrgId !== expectedOrgId) {
158
+ throw new TenantAuthorizationError("Resource does not belong to the current organization");
159
+ }
160
+ }
161
+ /**
162
+ * Generate the public booking URL for an organization's provider/event type.
163
+ *
164
+ * @param orgSlug - Organization slug
165
+ * @param providerSlug - Provider slug or ID
166
+ * @param eventTypeSlug - Event type slug
167
+ * @param baseUrl - Base URL of the application
168
+ * @returns Full booking URL
169
+ */
170
+ const SLUG_RE = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
171
+ export function buildOrgBookingUrl(orgSlug, providerSlug, eventTypeSlug, baseUrl) {
172
+ for (const [name, slug] of [["orgSlug", orgSlug], ["providerSlug", providerSlug], ["eventTypeSlug", eventTypeSlug]]) {
173
+ if (!SLUG_RE.test(slug)) {
174
+ throw new TenantAuthorizationError(`Invalid ${name}: "${slug}"`);
175
+ }
176
+ }
177
+ return `${baseUrl}/${orgSlug}/${providerSlug}/${eventTypeSlug}`;
178
+ }
179
+ /**
180
+ * Parse an organization slug from a booking URL path.
181
+ *
182
+ * Expected format: `/{orgSlug}/{providerSlug}/{eventTypeSlug}`
183
+ *
184
+ * @param pathname - URL pathname
185
+ * @returns Parsed segments, or null if format is invalid
186
+ */
187
+ export function parseOrgBookingPath(pathname) {
188
+ const match = pathname.match(/^\/([^/]+)\/([^/]+)\/([^/]+)$/);
189
+ if (!match)
190
+ return null;
191
+ return {
192
+ orgSlug: match[1],
193
+ providerSlug: match[2],
194
+ eventTypeSlug: match[3],
195
+ };
196
+ }
197
+ //# sourceMappingURL=multi-tenancy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-tenancy.js","sourceRoot":"","sources":["../src/multi-tenancy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
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 type { EmailAdapter, CalendarAdapter, JobAdapter } from "./adapters/index.js";
11
+ import { JOB_NAMES } from "./adapters/job-adapter.js";
12
+ /** Common booking data included in every notification payload */
13
+ export interface NotificationBookingData {
14
+ bookingId: string;
15
+ eventTitle: string;
16
+ providerName: string;
17
+ providerEmail: string;
18
+ customerName: string;
19
+ customerEmail: string;
20
+ /** ISO datetime strings */
21
+ startsAt: string;
22
+ endsAt: string;
23
+ timezone: string;
24
+ location?: string;
25
+ /** Signed management URL for the customer */
26
+ managementUrl?: string;
27
+ /** Unsubscribe URL */
28
+ unsubscribeUrl?: string;
29
+ }
30
+ /** Payload for SEND_CONFIRMATION_EMAIL job */
31
+ export interface ConfirmationEmailPayload extends NotificationBookingData {
32
+ /** Whether to also send a notification to the provider */
33
+ notifyProvider?: boolean;
34
+ }
35
+ /** Payload for SEND_REMINDER_EMAIL job */
36
+ export interface ReminderEmailPayload extends NotificationBookingData {
37
+ /** How many hours before the appointment this reminder is for (e.g., 24 or 1) */
38
+ reminderHours: number;
39
+ }
40
+ /** Payload for SEND_CANCELLATION_EMAIL job */
41
+ export interface CancellationEmailPayload extends NotificationBookingData {
42
+ /** Who initiated the cancellation */
43
+ cancelledBy: "customer" | "provider" | "system";
44
+ /** Optional reason for cancellation */
45
+ reason?: string;
46
+ }
47
+ /** Payload for SEND_RESCHEDULE_EMAIL job */
48
+ export interface RescheduleEmailPayload extends NotificationBookingData {
49
+ /** Original booking datetime strings */
50
+ oldStartsAt: string;
51
+ oldEndsAt: string;
52
+ }
53
+ /** Payload for SYNC_CALENDAR_EVENT job */
54
+ export interface CalendarSyncPayload {
55
+ bookingId: string;
56
+ providerId: string;
57
+ /** External calendar event ID (for updates/deletes) */
58
+ externalEventId?: string;
59
+ eventTitle: string;
60
+ customerName: string;
61
+ customerEmail: string;
62
+ startsAt: string;
63
+ endsAt: string;
64
+ timezone: string;
65
+ location?: string;
66
+ description?: string;
67
+ }
68
+ /** Payload for DELETE_CALENDAR_EVENT job */
69
+ export interface CalendarDeletePayload {
70
+ bookingId: string;
71
+ providerId: string;
72
+ externalEventId: string;
73
+ }
74
+ /** Payload for AUTO_REJECT_PENDING job */
75
+ export interface AutoRejectPendingPayload {
76
+ bookingId: string;
77
+ /** Actor to record in the booking_event */
78
+ actor?: string;
79
+ }
80
+ /**
81
+ * Format a UTC datetime string in a given timezone for email templates.
82
+ *
83
+ * @returns `{ date, time }` formatted for the email template variables
84
+ */
85
+ export declare function formatDateTimeForEmail(isoString: string, timezone: string): {
86
+ date: string;
87
+ time: string;
88
+ };
89
+ /**
90
+ * Calculate duration in human-readable form from start/end ISO strings.
91
+ */
92
+ export declare function formatDurationForEmail(startsAt: string, endsAt: string): string;
93
+ /**
94
+ * Send a booking confirmation email to the customer (and optionally the provider).
95
+ *
96
+ * Call this from your Inngest/Trigger.dev/BullMQ job handler.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * // In your Inngest function:
101
+ * export const sendConfirmation = inngest.createFunction(
102
+ * { id: "send-confirmation-email" },
103
+ * { event: JOB_NAMES.SEND_CONFIRMATION_EMAIL },
104
+ * async ({ event }) => {
105
+ * await sendConfirmationEmail(event.data, emailAdapter);
106
+ * },
107
+ * );
108
+ * ```
109
+ */
110
+ export declare function sendConfirmationEmail(payload: ConfirmationEmailPayload, emailAdapter: EmailAdapter): Promise<void>;
111
+ /**
112
+ * Send a reminder email to the customer before their appointment.
113
+ */
114
+ export declare function sendReminderEmail(payload: ReminderEmailPayload, emailAdapter: EmailAdapter): Promise<void>;
115
+ /**
116
+ * Send cancellation notification emails to customer and provider.
117
+ */
118
+ export declare function sendCancellationEmail(payload: CancellationEmailPayload, emailAdapter: EmailAdapter): Promise<void>;
119
+ /**
120
+ * Send reschedule notification emails to customer and provider.
121
+ */
122
+ export declare function sendRescheduleEmail(payload: RescheduleEmailPayload, emailAdapter: EmailAdapter): Promise<void>;
123
+ /**
124
+ * Schedule the auto-rejection of a pending booking using the job adapter.
125
+ *
126
+ * Call this immediately after creating a booking that requires confirmation.
127
+ *
128
+ * @param bookingId - ID of the pending booking
129
+ * @param deadline - Date at which to auto-reject (from `getAutoRejectDeadline`)
130
+ * @param jobs - Your `JobAdapter` instance
131
+ * @returns The scheduled job ID (store it if you need to cancel on manual confirm/reject)
132
+ */
133
+ export declare function scheduleAutoReject(bookingId: string, deadline: Date, jobs: JobAdapter): Promise<string>;
134
+ /**
135
+ * Sync a confirmed booking to the provider's connected calendar.
136
+ */
137
+ export declare function syncBookingToCalendar(payload: CalendarSyncPayload, calendarAdapter: CalendarAdapter): Promise<string | undefined>;
138
+ /**
139
+ * Delete a calendar event when a booking is cancelled.
140
+ */
141
+ export declare function deleteBookingFromCalendar(externalEventId: string, calendarAdapter: CalendarAdapter): Promise<void>;
142
+ export { JOB_NAMES };
143
+ //# sourceMappingURL=notification-jobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-jobs.d.ts","sourceRoot":"","sources":["../src/notification-jobs.ts"],"names":[],"mappings":"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"}
@@ -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