@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,228 @@
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
+ import { createHmac } from "node:crypto";
8
+ // ---------------------------------------------------------------------------
9
+ // Constants
10
+ // ---------------------------------------------------------------------------
11
+ /** Default retry configuration */
12
+ export const DEFAULT_RETRY_CONFIG = {
13
+ maxRetries: 3,
14
+ backoffSeconds: [10, 60, 300],
15
+ };
16
+ /** All valid webhook triggers */
17
+ export const WEBHOOK_TRIGGERS = [
18
+ "BOOKING_CREATED",
19
+ "BOOKING_CONFIRMED",
20
+ "BOOKING_CANCELLED",
21
+ "BOOKING_RESCHEDULED",
22
+ "BOOKING_REJECTED",
23
+ "BOOKING_PAID",
24
+ "BOOKING_NO_SHOW",
25
+ "FORM_SUBMITTED",
26
+ "OOO_CREATED",
27
+ ];
28
+ /** Signature header name */
29
+ export const SIGNATURE_HEADER = "X-SlotKit-Signature";
30
+ /** Timestamp header name */
31
+ export const TIMESTAMP_HEADER = "X-SlotKit-Timestamp";
32
+ /** Default tolerance window in seconds (5 minutes) */
33
+ export const DEFAULT_TOLERANCE_SECONDS = 300;
34
+ // ---------------------------------------------------------------------------
35
+ // Errors
36
+ // ---------------------------------------------------------------------------
37
+ /** Error thrown when webhook validation fails */
38
+ export class WebhookValidationError extends Error {
39
+ constructor(message) {
40
+ super(message);
41
+ this.name = "WebhookValidationError";
42
+ }
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // Signing & Verification
46
+ // ---------------------------------------------------------------------------
47
+ /**
48
+ * Create an HMAC-SHA256 signature for a webhook payload.
49
+ *
50
+ * Signature = HMAC-SHA256(secret, timestamp + '.' + rawBody)
51
+ *
52
+ * @param rawBody - The raw JSON string of the payload
53
+ * @param secret - The webhook secret key
54
+ * @param timestampSeconds - Unix timestamp in seconds
55
+ * @returns The hex-encoded HMAC signature
56
+ */
57
+ export function signWebhookPayload(rawBody, secret, timestampSeconds) {
58
+ const message = `${timestampSeconds}.${rawBody}`;
59
+ return createHmac("sha256", secret).update(message).digest("hex");
60
+ }
61
+ /**
62
+ * Verify a webhook signature with replay protection.
63
+ *
64
+ * @param rawBody - The raw JSON string of the received payload
65
+ * @param signature - The value of the X-SlotKit-Signature header
66
+ * @param timestampSeconds - The value of the X-SlotKit-Timestamp header (Unix seconds)
67
+ * @param secret - The webhook secret key
68
+ * @param options - Optional configuration
69
+ * @param options.toleranceSeconds - Maximum age of the timestamp in seconds (default: 300)
70
+ * @returns Verification result with reason if invalid
71
+ */
72
+ export function verifyWebhookSignature(rawBody, signature, timestampSeconds, secret, options) {
73
+ const tolerance = options?.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
74
+ const nowSeconds = Math.floor(Date.now() / 1000);
75
+ const age = nowSeconds - timestampSeconds;
76
+ // Check replay protection
77
+ if (age > tolerance || age < -tolerance) {
78
+ return { valid: false, reason: "timestamp_expired" };
79
+ }
80
+ // Verify HMAC
81
+ const expectedSignature = signWebhookPayload(rawBody, secret, timestampSeconds);
82
+ // Constant-time comparison
83
+ if (expectedSignature.length !== signature.length) {
84
+ return { valid: false, reason: "signature_mismatch" };
85
+ }
86
+ const a = Buffer.from(expectedSignature, "hex");
87
+ const b = Buffer.from(signature, "hex");
88
+ if (a.length !== b.length) {
89
+ return { valid: false, reason: "signature_mismatch" };
90
+ }
91
+ let mismatch = 0;
92
+ for (let i = 0; i < a.length; i++) {
93
+ mismatch |= a[i] ^ b[i];
94
+ }
95
+ if (mismatch !== 0) {
96
+ return { valid: false, reason: "signature_mismatch" };
97
+ }
98
+ return { valid: true };
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // Envelope Construction
102
+ // ---------------------------------------------------------------------------
103
+ /**
104
+ * Create a standard webhook envelope.
105
+ *
106
+ * @param trigger - The trigger event
107
+ * @param payload - The webhook payload data
108
+ * @returns The full webhook envelope
109
+ */
110
+ export function createWebhookEnvelope(trigger, payload) {
111
+ return {
112
+ triggerEvent: trigger,
113
+ createdAt: new Date().toISOString(),
114
+ payload,
115
+ };
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // Custom Payload Templates
119
+ // ---------------------------------------------------------------------------
120
+ /**
121
+ * Resolve a custom payload template with webhook data.
122
+ *
123
+ * Replaces `{{variable}}` placeholders with values from the envelope.
124
+ * Supported variables: triggerEvent, createdAt, bookingId, eventType,
125
+ * startTime, endTime, status, and any workflow template variables.
126
+ *
127
+ * @param template - The JSON template string with {{variable}} placeholders
128
+ * @param envelope - The webhook envelope
129
+ * @returns The resolved JSON string
130
+ */
131
+ export function resolvePayloadTemplate(template, envelope) {
132
+ const vars = {
133
+ "{{triggerEvent}}": envelope.triggerEvent,
134
+ "{{createdAt}}": envelope.createdAt,
135
+ "{{bookingId}}": envelope.payload.bookingId,
136
+ "{{eventType}}": envelope.payload.eventType,
137
+ "{{startTime}}": envelope.payload.startTime,
138
+ "{{endTime}}": envelope.payload.endTime,
139
+ "{{status}}": envelope.payload.status,
140
+ "{{organizerName}}": envelope.payload.organizer.name,
141
+ "{{organizerEmail}}": envelope.payload.organizer.email,
142
+ };
143
+ let result = template;
144
+ for (const [key, value] of Object.entries(vars)) {
145
+ result = result.replaceAll(key, value);
146
+ }
147
+ return result;
148
+ }
149
+ // ---------------------------------------------------------------------------
150
+ // Subscription Matching
151
+ // ---------------------------------------------------------------------------
152
+ /**
153
+ * Find all active webhook subscriptions that match a trigger and optional scope.
154
+ *
155
+ * @param subscriptions - All available webhook subscriptions
156
+ * @param trigger - The trigger event that occurred
157
+ * @param scope - Optional scope filters (eventTypeId, teamId)
158
+ * @returns Matching subscriptions
159
+ */
160
+ export function matchWebhookSubscriptions(subscriptions, trigger, scope) {
161
+ return subscriptions.filter((sub) => {
162
+ if (!sub.isActive)
163
+ return false;
164
+ if (!sub.triggers.includes(trigger))
165
+ return false;
166
+ // Scope filtering: subscription must match if it has a scope
167
+ if (sub.eventTypeId && scope?.eventTypeId !== sub.eventTypeId) {
168
+ return false;
169
+ }
170
+ if (sub.teamId && scope?.teamId !== sub.teamId) {
171
+ return false;
172
+ }
173
+ return true;
174
+ });
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Retry Logic
178
+ // ---------------------------------------------------------------------------
179
+ /**
180
+ * Determine the delay before the next retry attempt.
181
+ *
182
+ * @param attempt - The current attempt number (0-indexed)
183
+ * @param config - Retry configuration
184
+ * @returns Delay in seconds, or null if max retries exceeded
185
+ */
186
+ export function getRetryDelay(attempt, config = DEFAULT_RETRY_CONFIG) {
187
+ if (attempt >= config.maxRetries)
188
+ return null;
189
+ return config.backoffSeconds[attempt] ?? config.backoffSeconds[config.backoffSeconds.length - 1];
190
+ }
191
+ /**
192
+ * Determine if a response code indicates success (2xx).
193
+ *
194
+ * @param statusCode - HTTP response status code
195
+ * @returns Whether the delivery was successful
196
+ */
197
+ export function isSuccessResponse(statusCode) {
198
+ return statusCode >= 200 && statusCode < 300;
199
+ }
200
+ // ---------------------------------------------------------------------------
201
+ // Validation
202
+ // ---------------------------------------------------------------------------
203
+ /**
204
+ * Validate a webhook subscription.
205
+ *
206
+ * @param subscription - The subscription to validate
207
+ * @throws {WebhookValidationError} If the subscription is invalid
208
+ */
209
+ export function validateWebhookSubscription(subscription) {
210
+ if (!subscription.subscriberUrl) {
211
+ throw new WebhookValidationError("Subscriber URL is required");
212
+ }
213
+ try {
214
+ new URL(subscription.subscriberUrl);
215
+ }
216
+ catch {
217
+ throw new WebhookValidationError(`Invalid subscriber URL: "${subscription.subscriberUrl}"`);
218
+ }
219
+ if (!Array.isArray(subscription.triggers) || subscription.triggers.length === 0) {
220
+ throw new WebhookValidationError("At least one trigger is required");
221
+ }
222
+ for (const trigger of subscription.triggers) {
223
+ if (!WEBHOOK_TRIGGERS.includes(trigger)) {
224
+ throw new WebhookValidationError(`Invalid trigger: "${trigger}". Must be one of: ${WEBHOOK_TRIGGERS.join(", ")}`);
225
+ }
226
+ }
227
+ }
228
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Workflow automation engine.
3
+ *
4
+ * Trigger-condition-action framework that automates tasks
5
+ * based on booking lifecycle events.
6
+ */
7
+ /** Supported workflow trigger events */
8
+ export type WorkflowTrigger = "booking_created" | "booking_confirmed" | "booking_cancelled" | "booking_rescheduled" | "before_event" | "after_event" | "payment_received" | "payment_failed" | "no_show_confirmed" | "form_submitted";
9
+ /** Supported workflow action types */
10
+ export type WorkflowActionType = "send_email" | "send_sms" | "fire_webhook" | "update_status" | "create_calendar_event";
11
+ /** Condition operator for filtering */
12
+ export type ConditionOperator = "equals" | "not_equals" | "contains" | "in";
13
+ /** A single workflow condition */
14
+ export interface WorkflowCondition {
15
+ /** The field to check (e.g., "event_type_id", "status", "customer_email") */
16
+ field: string;
17
+ /** Comparison operator */
18
+ operator: ConditionOperator;
19
+ /** Value(s) to compare against */
20
+ value: string | string[];
21
+ }
22
+ /** Email action configuration */
23
+ export interface EmailActionConfig {
24
+ type: "send_email";
25
+ /** Recipient: "customer", "host", or a specific email address */
26
+ to: string;
27
+ /** Email subject (supports template variables) */
28
+ subject: string;
29
+ /** Email body template (supports template variables) */
30
+ body: string;
31
+ }
32
+ /** SMS action configuration */
33
+ export interface SmsActionConfig {
34
+ type: "send_sms";
35
+ /** Phone number field key or literal number */
36
+ to: string;
37
+ /** SMS body template (supports template variables) */
38
+ body: string;
39
+ }
40
+ /** Webhook action configuration */
41
+ export interface WebhookActionConfig {
42
+ type: "fire_webhook";
43
+ /** Target URL */
44
+ url: string;
45
+ /** Payload template (JSON string with template variables) */
46
+ payload?: string;
47
+ /** HTTP method (default: POST) */
48
+ method?: "POST" | "PUT" | "PATCH";
49
+ /** Custom headers */
50
+ headers?: Record<string, string>;
51
+ }
52
+ /** Status update action configuration */
53
+ export interface StatusUpdateActionConfig {
54
+ type: "update_status";
55
+ /** New status to set */
56
+ status: string;
57
+ }
58
+ /** Calendar event action configuration */
59
+ export interface CalendarEventActionConfig {
60
+ type: "create_calendar_event";
61
+ /** Additional notes for the calendar event */
62
+ notes?: string;
63
+ }
64
+ /** Union of all action configurations */
65
+ export type WorkflowAction = EmailActionConfig | SmsActionConfig | WebhookActionConfig | StatusUpdateActionConfig | CalendarEventActionConfig;
66
+ /** A complete workflow definition */
67
+ export interface WorkflowDefinition {
68
+ id: string;
69
+ name: string;
70
+ trigger: WorkflowTrigger;
71
+ conditions: WorkflowCondition[];
72
+ actions: WorkflowAction[];
73
+ isActive: boolean;
74
+ }
75
+ /** Context data passed when evaluating a workflow */
76
+ export interface WorkflowContext {
77
+ bookingId?: string;
78
+ eventTypeId?: string;
79
+ providerId?: string;
80
+ customerEmail?: string;
81
+ customerName?: string;
82
+ customerPhone?: string;
83
+ status?: string;
84
+ startsAt?: Date;
85
+ endsAt?: Date;
86
+ hostName?: string;
87
+ eventTitle?: string;
88
+ eventDuration?: number;
89
+ eventLocation?: string;
90
+ managementUrl?: string;
91
+ /** Additional fields for condition evaluation */
92
+ [key: string]: unknown;
93
+ }
94
+ /** Result of a workflow execution log entry */
95
+ export interface WorkflowLogEntry {
96
+ workflowId: string;
97
+ bookingId?: string;
98
+ actionType: WorkflowActionType;
99
+ status: "success" | "error" | "skipped";
100
+ error?: string;
101
+ executedAt: Date;
102
+ }
103
+ /** Error thrown when workflow validation fails */
104
+ export declare class WorkflowValidationError extends Error {
105
+ constructor(message: string);
106
+ }
107
+ /** Standard template variables available in workflow messages */
108
+ export declare const TEMPLATE_VARIABLES: readonly ["{booking.title}", "{booking.startTime}", "{booking.endTime}", "{booking.date}", "{attendee.name}", "{attendee.email}", "{host.name}", "{event.location}", "{event.duration}", "{booking.managementUrl}"];
109
+ /**
110
+ * Resolve template variables in a string using workflow context.
111
+ *
112
+ * Missing variables are replaced with empty strings.
113
+ *
114
+ * @param template - The template string with `{variable}` placeholders
115
+ * @param context - The workflow context with booking/event data
116
+ * @returns The resolved string
117
+ */
118
+ export declare function resolveTemplateVariables(template: string, context: WorkflowContext): string;
119
+ /** Default workflow templates for common scenarios */
120
+ export declare const DEFAULT_TEMPLATES: {
121
+ readonly confirmation: {
122
+ readonly subject: "Booking Confirmed: {booking.title}";
123
+ readonly body: "Hi {attendee.name},\n\nYour booking for {booking.title} on {booking.date} at {booking.startTime} has been confirmed.\n\nDuration: {event.duration}\nLocation: {event.location}\n\nManage your booking: {booking.managementUrl}\n\nBest regards,\n{host.name}";
124
+ };
125
+ readonly reminder_24h: {
126
+ readonly subject: "Reminder: {booking.title} tomorrow";
127
+ readonly body: "Hi {attendee.name},\n\nThis is a reminder that you have a booking for {booking.title} tomorrow at {booking.startTime}.\n\nLocation: {event.location}\n\nManage your booking: {booking.managementUrl}\n\nSee you soon,\n{host.name}";
128
+ };
129
+ readonly reminder_1h: {
130
+ readonly subject: "Reminder: {booking.title} in 1 hour";
131
+ readonly body: "Hi {attendee.name},\n\nYour booking for {booking.title} starts in 1 hour at {booking.startTime}.\n\nLocation: {event.location}\n\nSee you soon,\n{host.name}";
132
+ };
133
+ readonly cancellation: {
134
+ readonly subject: "Booking Cancelled: {booking.title}";
135
+ readonly body: "Hi {attendee.name},\n\nYour booking for {booking.title} on {booking.date} at {booking.startTime} has been cancelled.\n\nIf you'd like to rebook, please visit our booking page.\n\nBest regards,\n{host.name}";
136
+ };
137
+ readonly followup: {
138
+ readonly subject: "How was your {booking.title}?";
139
+ readonly body: "Hi {attendee.name},\n\nThank you for your recent {booking.title} with {host.name}.\n\nWe hope you had a great experience! If you'd like to book again, we'd love to see you.\n\nBest regards,\n{host.name}";
140
+ };
141
+ };
142
+ /**
143
+ * Evaluate whether a workflow's conditions are met for the given context.
144
+ *
145
+ * If no conditions are defined, returns true (unconditional trigger).
146
+ * All conditions must match (AND logic).
147
+ *
148
+ * @param conditions - Array of workflow conditions
149
+ * @param context - The workflow context data
150
+ * @returns Whether all conditions are satisfied
151
+ */
152
+ export declare function evaluateConditions(conditions: WorkflowCondition[], context: WorkflowContext): boolean;
153
+ /**
154
+ * Validate a workflow definition.
155
+ *
156
+ * @param workflow - The workflow to validate
157
+ * @throws {WorkflowValidationError} If the workflow is invalid
158
+ */
159
+ export declare function validateWorkflow(workflow: WorkflowDefinition): void;
160
+ /**
161
+ * Find all active workflows that match a given trigger and context.
162
+ *
163
+ * @param workflows - All available workflows
164
+ * @param trigger - The trigger event that occurred
165
+ * @param context - The workflow context data
166
+ * @returns Workflows that should be executed
167
+ */
168
+ export declare function matchWorkflows(workflows: WorkflowDefinition[], trigger: WorkflowTrigger, context: WorkflowContext): WorkflowDefinition[];
169
+ //# sourceMappingURL=workflows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Workflow automation engine.
3
+ *
4
+ * Trigger-condition-action framework that automates tasks
5
+ * based on booking lifecycle events.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Errors
9
+ // ---------------------------------------------------------------------------
10
+ /** Error thrown when workflow validation fails */
11
+ export class WorkflowValidationError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "WorkflowValidationError";
15
+ }
16
+ }
17
+ // ---------------------------------------------------------------------------
18
+ // Template Variables
19
+ // ---------------------------------------------------------------------------
20
+ /** Standard template variables available in workflow messages */
21
+ export const TEMPLATE_VARIABLES = [
22
+ "{booking.title}",
23
+ "{booking.startTime}",
24
+ "{booking.endTime}",
25
+ "{booking.date}",
26
+ "{attendee.name}",
27
+ "{attendee.email}",
28
+ "{host.name}",
29
+ "{event.location}",
30
+ "{event.duration}",
31
+ "{booking.managementUrl}",
32
+ ];
33
+ /**
34
+ * Resolve template variables in a string using workflow context.
35
+ *
36
+ * Missing variables are replaced with empty strings.
37
+ *
38
+ * @param template - The template string with `{variable}` placeholders
39
+ * @param context - The workflow context with booking/event data
40
+ * @returns The resolved string
41
+ */
42
+ export function resolveTemplateVariables(template, context) {
43
+ const vars = {
44
+ "{booking.title}": context.eventTitle ?? "",
45
+ "{booking.startTime}": context.startsAt
46
+ ? formatTime(context.startsAt)
47
+ : "",
48
+ "{booking.endTime}": context.endsAt ? formatTime(context.endsAt) : "",
49
+ "{booking.date}": context.startsAt ? formatDate(context.startsAt) : "",
50
+ "{attendee.name}": context.customerName ?? "",
51
+ "{attendee.email}": context.customerEmail ?? "",
52
+ "{host.name}": context.hostName ?? "",
53
+ "{event.location}": context.eventLocation ?? "",
54
+ "{event.duration}": context.eventDuration
55
+ ? `${context.eventDuration} minutes`
56
+ : "",
57
+ "{booking.managementUrl}": context.managementUrl ?? "",
58
+ };
59
+ let result = template;
60
+ for (const [key, value] of Object.entries(vars)) {
61
+ result = result.replaceAll(key, value);
62
+ }
63
+ return result;
64
+ }
65
+ function formatTime(date) {
66
+ return date.toLocaleTimeString("en-US", {
67
+ hour: "numeric",
68
+ minute: "2-digit",
69
+ hour12: true,
70
+ });
71
+ }
72
+ function formatDate(date) {
73
+ return date.toLocaleDateString("en-US", {
74
+ weekday: "long",
75
+ year: "numeric",
76
+ month: "long",
77
+ day: "numeric",
78
+ });
79
+ }
80
+ // ---------------------------------------------------------------------------
81
+ // Default Templates
82
+ // ---------------------------------------------------------------------------
83
+ /** Default workflow templates for common scenarios */
84
+ export const DEFAULT_TEMPLATES = {
85
+ confirmation: {
86
+ subject: "Booking Confirmed: {booking.title}",
87
+ body: "Hi {attendee.name},\n\nYour booking for {booking.title} on {booking.date} at {booking.startTime} has been confirmed.\n\nDuration: {event.duration}\nLocation: {event.location}\n\nManage your booking: {booking.managementUrl}\n\nBest regards,\n{host.name}",
88
+ },
89
+ reminder_24h: {
90
+ subject: "Reminder: {booking.title} tomorrow",
91
+ body: "Hi {attendee.name},\n\nThis is a reminder that you have a booking for {booking.title} tomorrow at {booking.startTime}.\n\nLocation: {event.location}\n\nManage your booking: {booking.managementUrl}\n\nSee you soon,\n{host.name}",
92
+ },
93
+ reminder_1h: {
94
+ subject: "Reminder: {booking.title} in 1 hour",
95
+ body: "Hi {attendee.name},\n\nYour booking for {booking.title} starts in 1 hour at {booking.startTime}.\n\nLocation: {event.location}\n\nSee you soon,\n{host.name}",
96
+ },
97
+ cancellation: {
98
+ subject: "Booking Cancelled: {booking.title}",
99
+ body: "Hi {attendee.name},\n\nYour booking for {booking.title} on {booking.date} at {booking.startTime} has been cancelled.\n\nIf you'd like to rebook, please visit our booking page.\n\nBest regards,\n{host.name}",
100
+ },
101
+ followup: {
102
+ subject: "How was your {booking.title}?",
103
+ body: "Hi {attendee.name},\n\nThank you for your recent {booking.title} with {host.name}.\n\nWe hope you had a great experience! If you'd like to book again, we'd love to see you.\n\nBest regards,\n{host.name}",
104
+ },
105
+ };
106
+ // ---------------------------------------------------------------------------
107
+ // Condition Evaluation
108
+ // ---------------------------------------------------------------------------
109
+ /**
110
+ * Evaluate whether a workflow's conditions are met for the given context.
111
+ *
112
+ * If no conditions are defined, returns true (unconditional trigger).
113
+ * All conditions must match (AND logic).
114
+ *
115
+ * @param conditions - Array of workflow conditions
116
+ * @param context - The workflow context data
117
+ * @returns Whether all conditions are satisfied
118
+ */
119
+ export function evaluateConditions(conditions, context) {
120
+ if (conditions.length === 0)
121
+ return true;
122
+ return conditions.every((condition) => {
123
+ const fieldValue = String(context[condition.field] ?? "");
124
+ switch (condition.operator) {
125
+ case "equals":
126
+ return fieldValue === String(condition.value);
127
+ case "not_equals":
128
+ return fieldValue !== String(condition.value);
129
+ case "contains":
130
+ return fieldValue
131
+ .toLowerCase()
132
+ .includes(String(condition.value).toLowerCase());
133
+ case "in": {
134
+ const values = Array.isArray(condition.value)
135
+ ? condition.value
136
+ : [condition.value];
137
+ return values.includes(fieldValue);
138
+ }
139
+ default:
140
+ return false;
141
+ }
142
+ });
143
+ }
144
+ // ---------------------------------------------------------------------------
145
+ // Workflow Validation
146
+ // ---------------------------------------------------------------------------
147
+ /** Valid triggers for workflows */
148
+ const VALID_TRIGGERS = [
149
+ "booking_created",
150
+ "booking_confirmed",
151
+ "booking_cancelled",
152
+ "booking_rescheduled",
153
+ "before_event",
154
+ "after_event",
155
+ "payment_received",
156
+ "payment_failed",
157
+ "no_show_confirmed",
158
+ "form_submitted",
159
+ ];
160
+ /** Valid action types */
161
+ const VALID_ACTION_TYPES = [
162
+ "send_email",
163
+ "send_sms",
164
+ "fire_webhook",
165
+ "update_status",
166
+ "create_calendar_event",
167
+ ];
168
+ /**
169
+ * Validate a workflow definition.
170
+ *
171
+ * @param workflow - The workflow to validate
172
+ * @throws {WorkflowValidationError} If the workflow is invalid
173
+ */
174
+ export function validateWorkflow(workflow) {
175
+ if (!workflow.name || workflow.name.trim().length === 0) {
176
+ throw new WorkflowValidationError("Workflow name is required");
177
+ }
178
+ if (!VALID_TRIGGERS.includes(workflow.trigger)) {
179
+ throw new WorkflowValidationError(`Invalid trigger: "${workflow.trigger}". Must be one of: ${VALID_TRIGGERS.join(", ")}`);
180
+ }
181
+ if (!Array.isArray(workflow.actions) || workflow.actions.length === 0) {
182
+ throw new WorkflowValidationError("Workflow must have at least one action");
183
+ }
184
+ for (const action of workflow.actions) {
185
+ if (!VALID_ACTION_TYPES.includes(action.type)) {
186
+ throw new WorkflowValidationError(`Invalid action type: "${action.type}"`);
187
+ }
188
+ validateAction(action);
189
+ }
190
+ for (const condition of workflow.conditions) {
191
+ if (!condition.field || condition.field.trim().length === 0) {
192
+ throw new WorkflowValidationError("Condition field is required");
193
+ }
194
+ if (!["equals", "not_equals", "contains", "in"].includes(condition.operator)) {
195
+ throw new WorkflowValidationError(`Invalid condition operator: "${condition.operator}"`);
196
+ }
197
+ }
198
+ }
199
+ function validateAction(action) {
200
+ switch (action.type) {
201
+ case "send_email":
202
+ if (!action.to) {
203
+ throw new WorkflowValidationError("Email action requires 'to' field");
204
+ }
205
+ if (!action.subject) {
206
+ throw new WorkflowValidationError("Email action requires 'subject' field");
207
+ }
208
+ if (!action.body) {
209
+ throw new WorkflowValidationError("Email action requires 'body' field");
210
+ }
211
+ break;
212
+ case "send_sms":
213
+ if (!action.to) {
214
+ throw new WorkflowValidationError("SMS action requires 'to' field");
215
+ }
216
+ if (!action.body) {
217
+ throw new WorkflowValidationError("SMS action requires 'body' field");
218
+ }
219
+ break;
220
+ case "fire_webhook":
221
+ if (!action.url) {
222
+ throw new WorkflowValidationError("Webhook action requires 'url' field");
223
+ }
224
+ break;
225
+ case "update_status":
226
+ if (!action.status) {
227
+ throw new WorkflowValidationError("Status update action requires 'status' field");
228
+ }
229
+ break;
230
+ case "create_calendar_event":
231
+ // No required fields
232
+ break;
233
+ }
234
+ }
235
+ // ---------------------------------------------------------------------------
236
+ // Workflow Matching
237
+ // ---------------------------------------------------------------------------
238
+ /**
239
+ * Find all active workflows that match a given trigger and context.
240
+ *
241
+ * @param workflows - All available workflows
242
+ * @param trigger - The trigger event that occurred
243
+ * @param context - The workflow context data
244
+ * @returns Workflows that should be executed
245
+ */
246
+ export function matchWorkflows(workflows, trigger, context) {
247
+ return workflows.filter((w) => w.isActive &&
248
+ w.trigger === trigger &&
249
+ evaluateConditions(w.conditions, context));
250
+ }
251
+ //# sourceMappingURL=workflows.js.map