@thebookingkit/server 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +40 -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 +271 -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 +81 -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 +36 -0
  43. package/dist/email-templates.d.ts.map +1 -0
  44. package/{src/email-templates.ts → dist/email-templates.js} +6 -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 +132 -0
  51. package/dist/multi-tenancy.d.ts.map +1 -0
  52. package/dist/multi-tenancy.js +188 -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 +228 -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 +251 -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
package/dist/api.d.ts ADDED
@@ -0,0 +1,223 @@
1
+ /**
2
+ * REST API utilities for The Booking Kit.
3
+ *
4
+ * Provides standardized response formatting, API key management,
5
+ * rate limiting, pagination, and request validation helpers
6
+ * for Next.js API routes.
7
+ */
8
+ /** Standard API error */
9
+ export interface ApiError {
10
+ code: string;
11
+ message: string;
12
+ details?: Record<string, unknown>;
13
+ }
14
+ /** Standard error response envelope */
15
+ export interface ApiErrorResponse {
16
+ error: ApiError;
17
+ }
18
+ /** Standard success response envelope */
19
+ export interface ApiSuccessResponse<T> {
20
+ data: T;
21
+ meta?: ApiMeta;
22
+ }
23
+ /** Pagination metadata */
24
+ export interface ApiMeta {
25
+ total?: number;
26
+ nextCursor?: string | null;
27
+ prevCursor?: string | null;
28
+ hasMore?: boolean;
29
+ }
30
+ /** Paginated list response */
31
+ export interface PaginatedResponse<T> {
32
+ data: T[];
33
+ meta: {
34
+ nextCursor: string | null;
35
+ hasMore: boolean;
36
+ total?: number;
37
+ };
38
+ }
39
+ /** Standard API error codes */
40
+ export declare const API_ERROR_CODES: {
41
+ readonly NOT_FOUND: "NOT_FOUND";
42
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
43
+ readonly FORBIDDEN: "FORBIDDEN";
44
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
45
+ readonly CONFLICT: "CONFLICT";
46
+ readonly RATE_LIMITED: "RATE_LIMITED";
47
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
48
+ readonly PAYMENT_REQUIRED: "PAYMENT_REQUIRED";
49
+ };
50
+ export type ApiErrorCode = (typeof API_ERROR_CODES)[keyof typeof API_ERROR_CODES];
51
+ /**
52
+ * Create a standardized API error response.
53
+ *
54
+ * @param code - Error code from API_ERROR_CODES
55
+ * @param message - Human-readable error message
56
+ * @param details - Optional additional details
57
+ * @returns Standardized error response object
58
+ */
59
+ export declare function createErrorResponse(code: ApiErrorCode | string, message: string, details?: Record<string, unknown>): ApiErrorResponse;
60
+ /**
61
+ * Create a standardized API success response.
62
+ *
63
+ * @param data - The response data
64
+ * @param meta - Optional pagination metadata
65
+ * @returns Standardized success response object
66
+ */
67
+ export declare function createSuccessResponse<T>(data: T, meta?: ApiMeta): ApiSuccessResponse<T>;
68
+ /**
69
+ * Create a paginated list response.
70
+ *
71
+ * @param items - The page of items
72
+ * @param nextCursor - Cursor for the next page (null if last page)
73
+ * @param total - Optional total count
74
+ * @returns Paginated response
75
+ */
76
+ export declare function createPaginatedResponse<T>(items: T[], nextCursor: string | null, total?: number): PaginatedResponse<T>;
77
+ /** An API key record */
78
+ export interface ApiKeyRecord {
79
+ id: string;
80
+ name: string;
81
+ keyHash: string;
82
+ keyPrefix: string;
83
+ providerId?: string;
84
+ teamId?: string;
85
+ scopes: ApiKeyScope[];
86
+ rateLimit: number;
87
+ createdAt: Date;
88
+ expiresAt?: Date;
89
+ lastUsedAt?: Date;
90
+ }
91
+ /** API key scope */
92
+ export type ApiKeyScope = "read:bookings" | "write:bookings" | "read:availability" | "write:availability" | "read:event-types" | "write:event-types" | "read:webhooks" | "write:webhooks" | "read:analytics" | "admin";
93
+ /** Result of generating a new API key */
94
+ export interface GeneratedApiKey {
95
+ /** The full key (only shown once) */
96
+ key: string;
97
+ /** The prefix for display (e.g., "sk_live_abc123...") */
98
+ prefix: string;
99
+ /** The hash stored in the database */
100
+ hash: string;
101
+ }
102
+ /**
103
+ * Generate a new API key.
104
+ *
105
+ * The full key is only returned once and should be displayed to the user.
106
+ * Only the hash is stored in the database.
107
+ *
108
+ * @param prefix - Key prefix (e.g., "sk_live_" or "sk_test_")
109
+ * @returns Generated key with hash for storage
110
+ */
111
+ export declare function generateApiKey(prefix?: string): GeneratedApiKey;
112
+ /**
113
+ * Hash an API key for secure storage.
114
+ *
115
+ * Uses HMAC-SHA256 with a secret from the THEBOOKINGKIT_API_KEY_SECRET
116
+ * environment variable. Throws if the secret is not configured.
117
+ *
118
+ * @param key - The full API key
119
+ * @param secret - Optional HMAC secret (defaults to THEBOOKINGKIT_API_KEY_SECRET env var)
120
+ * @returns The hex-encoded hash
121
+ */
122
+ export declare function hashApiKey(key: string, secret?: string): string;
123
+ /**
124
+ * Verify an API key against a stored hash.
125
+ *
126
+ * @param key - The key to verify
127
+ * @param storedHash - The hash stored in the database
128
+ * @returns Whether the key is valid
129
+ */
130
+ export declare function verifyApiKey(key: string, storedHash: string): boolean;
131
+ /**
132
+ * Check if an API key has a required scope.
133
+ *
134
+ * Admin scope grants all permissions.
135
+ *
136
+ * @param keyScopes - The scopes granted to the API key
137
+ * @param requiredScope - The scope to check for
138
+ * @returns Whether the key has the required scope
139
+ */
140
+ export declare function hasScope(keyScopes: ApiKeyScope[], requiredScope: ApiKeyScope): boolean;
141
+ /**
142
+ * Check if an API key has expired.
143
+ *
144
+ * @param expiresAt - Optional expiry timestamp
145
+ * @returns Whether the key is expired
146
+ */
147
+ export declare function isKeyExpired(expiresAt: Date | undefined | null): boolean;
148
+ /** Rate limit state for a key */
149
+ export interface RateLimitState {
150
+ count: number;
151
+ windowStartMs: number;
152
+ }
153
+ /** Rate limit check result */
154
+ export interface RateLimitResult {
155
+ allowed: boolean;
156
+ remaining: number;
157
+ resetMs: number;
158
+ limit: number;
159
+ }
160
+ /**
161
+ * Check if a request should be allowed under rate limiting.
162
+ *
163
+ * Uses a fixed 1-minute window.
164
+ *
165
+ * @param state - Current rate limit state
166
+ * @param limit - Maximum requests per minute
167
+ * @param nowMs - Current timestamp in milliseconds
168
+ * @returns Rate limit check result
169
+ */
170
+ export declare function checkRateLimit(state: RateLimitState | null, limit: number, nowMs?: number): {
171
+ result: RateLimitResult;
172
+ newState: RateLimitState;
173
+ };
174
+ /**
175
+ * Encode a cursor for pagination (base64 JSON).
176
+ *
177
+ * @param data - The cursor data to encode
178
+ * @returns Base64-encoded cursor string
179
+ */
180
+ export declare function encodeCursor(data: Record<string, unknown>): string;
181
+ /**
182
+ * Decode a pagination cursor.
183
+ *
184
+ * @param cursor - The base64-encoded cursor string
185
+ * @returns The decoded cursor data, or null if invalid
186
+ */
187
+ export declare function decodeCursor(cursor: string): Record<string, unknown> | null;
188
+ /** Validation error detail */
189
+ export interface ValidationDetail {
190
+ field: string;
191
+ message: string;
192
+ }
193
+ /** Result of input validation */
194
+ export interface ValidationResult {
195
+ valid: boolean;
196
+ errors: ValidationDetail[];
197
+ }
198
+ /**
199
+ * Validate API request parameters for slot queries.
200
+ *
201
+ * @param params - Query parameters
202
+ * @returns Validation result
203
+ */
204
+ export declare function validateSlotQueryParams(params: {
205
+ providerId?: string;
206
+ teamId?: string;
207
+ eventTypeId?: string;
208
+ start?: string;
209
+ end?: string;
210
+ timezone?: string;
211
+ }): ValidationResult;
212
+ /**
213
+ * Parse and validate a sort parameter.
214
+ *
215
+ * @param sortParam - Sort string (e.g., "-createdAt" or "startsAt")
216
+ * @param allowedFields - Fields that can be sorted by
217
+ * @returns Parsed sort field and direction
218
+ */
219
+ export declare function parseSortParam(sortParam: string | undefined, allowedFields: string[]): {
220
+ field: string;
221
+ direction: "asc" | "desc";
222
+ } | null;
223
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,yBAAyB;AACzB,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,QAAQ,CAAC;CACjB;AAED,yCAAyC;AACzC,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,IAAI,EAAE,CAAC,CAAC;IACR,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,0BAA0B;AAC1B,MAAM,WAAW,OAAO;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,8BAA8B;AAC9B,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,IAAI,EAAE;QACJ,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAMD,+BAA+B;AAC/B,eAAO,MAAM,eAAe;;;;;;;;;CASlB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAMlF;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,GAAG,MAAM,EAC3B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,gBAAgB,CAQlB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,IAAI,EAAE,CAAC,EACP,IAAI,CAAC,EAAE,OAAO,GACb,kBAAkB,CAAC,CAAC,CAAC,CAKvB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EACvC,KAAK,EAAE,CAAC,EAAE,EACV,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,KAAK,CAAC,EAAE,MAAM,GACb,iBAAiB,CAAC,CAAC,CAAC,CAStB;AAMD,wBAAwB;AACxB,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,UAAU,CAAC,EAAE,IAAI,CAAC;CACnB;AAED,oBAAoB;AACpB,MAAM,MAAM,WAAW,GACnB,eAAe,GACf,gBAAgB,GAChB,mBAAmB,GACnB,oBAAoB,GACpB,kBAAkB,GAClB,mBAAmB,GACnB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,OAAO,CAAC;AAEZ,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,MAAmB,GAAG,eAAe,CAO3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAU/D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAerE;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,SAAS,EAAE,WAAW,EAAE,EACxB,aAAa,EAAE,WAAW,GACzB,OAAO,CAET;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAGxE;AAMD,iCAAiC;AACjC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,GAAG,IAAI,EAC5B,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAmB,GACzB;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAA;CAAE,CA6BvD;AAMD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAElE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAOhC;AAMD,8BAA8B;AAC9B,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iCAAiC;AACjC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,gBAAgB,CAkCnB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,aAAa,EAAE,MAAM,EAAE,GACtB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,GAAG,IAAI,CASrD"}
package/dist/api.js ADDED
@@ -0,0 +1,271 @@
1
+ /**
2
+ * REST API utilities for The Booking Kit.
3
+ *
4
+ * Provides standardized response formatting, API key management,
5
+ * rate limiting, pagination, and request validation helpers
6
+ * for Next.js API routes.
7
+ */
8
+ import { createHmac, randomBytes } from "node:crypto";
9
+ // ---------------------------------------------------------------------------
10
+ // Error Codes
11
+ // ---------------------------------------------------------------------------
12
+ /** Standard API error codes */
13
+ export const API_ERROR_CODES = {
14
+ NOT_FOUND: "NOT_FOUND",
15
+ UNAUTHORIZED: "UNAUTHORIZED",
16
+ FORBIDDEN: "FORBIDDEN",
17
+ VALIDATION_ERROR: "VALIDATION_ERROR",
18
+ CONFLICT: "CONFLICT",
19
+ RATE_LIMITED: "RATE_LIMITED",
20
+ INTERNAL_ERROR: "INTERNAL_ERROR",
21
+ PAYMENT_REQUIRED: "PAYMENT_REQUIRED",
22
+ };
23
+ // ---------------------------------------------------------------------------
24
+ // Response Helpers
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Create a standardized API error response.
28
+ *
29
+ * @param code - Error code from API_ERROR_CODES
30
+ * @param message - Human-readable error message
31
+ * @param details - Optional additional details
32
+ * @returns Standardized error response object
33
+ */
34
+ export function createErrorResponse(code, message, details) {
35
+ return {
36
+ error: {
37
+ code,
38
+ message,
39
+ ...(details ? { details } : {}),
40
+ },
41
+ };
42
+ }
43
+ /**
44
+ * Create a standardized API success response.
45
+ *
46
+ * @param data - The response data
47
+ * @param meta - Optional pagination metadata
48
+ * @returns Standardized success response object
49
+ */
50
+ export function createSuccessResponse(data, meta) {
51
+ return {
52
+ data,
53
+ ...(meta ? { meta } : {}),
54
+ };
55
+ }
56
+ /**
57
+ * Create a paginated list response.
58
+ *
59
+ * @param items - The page of items
60
+ * @param nextCursor - Cursor for the next page (null if last page)
61
+ * @param total - Optional total count
62
+ * @returns Paginated response
63
+ */
64
+ export function createPaginatedResponse(items, nextCursor, total) {
65
+ return {
66
+ data: items,
67
+ meta: {
68
+ nextCursor,
69
+ hasMore: nextCursor !== null,
70
+ ...(total !== undefined ? { total } : {}),
71
+ },
72
+ };
73
+ }
74
+ /**
75
+ * Generate a new API key.
76
+ *
77
+ * The full key is only returned once and should be displayed to the user.
78
+ * Only the hash is stored in the database.
79
+ *
80
+ * @param prefix - Key prefix (e.g., "sk_live_" or "sk_test_")
81
+ * @returns Generated key with hash for storage
82
+ */
83
+ export function generateApiKey(prefix = "sk_live_") {
84
+ const rawKey = randomBytes(32).toString("hex");
85
+ const key = `${prefix}${rawKey}`;
86
+ const hash = hashApiKey(key);
87
+ const displayPrefix = key.slice(0, prefix.length + 8) + "...";
88
+ return { key, prefix: displayPrefix, hash };
89
+ }
90
+ /**
91
+ * Hash an API key for secure storage.
92
+ *
93
+ * Uses HMAC-SHA256 with a secret from the THEBOOKINGKIT_API_KEY_SECRET
94
+ * environment variable. Throws if the secret is not configured.
95
+ *
96
+ * @param key - The full API key
97
+ * @param secret - Optional HMAC secret (defaults to THEBOOKINGKIT_API_KEY_SECRET env var)
98
+ * @returns The hex-encoded hash
99
+ */
100
+ export function hashApiKey(key, secret) {
101
+ const hmacSecret = secret ?? process.env.THEBOOKINGKIT_API_KEY_SECRET;
102
+ if (!hmacSecret) {
103
+ throw new Error("THEBOOKINGKIT_API_KEY_SECRET environment variable is required for API key hashing. " +
104
+ "Set it to a random 32+ character string.");
105
+ }
106
+ return createHmac("sha256", hmacSecret).update(key).digest("hex");
107
+ }
108
+ /**
109
+ * Verify an API key against a stored hash.
110
+ *
111
+ * @param key - The key to verify
112
+ * @param storedHash - The hash stored in the database
113
+ * @returns Whether the key is valid
114
+ */
115
+ export function verifyApiKey(key, storedHash) {
116
+ const hash = hashApiKey(key);
117
+ if (hash.length !== storedHash.length)
118
+ return false;
119
+ // Constant-time comparison
120
+ const a = Buffer.from(hash, "hex");
121
+ const b = Buffer.from(storedHash, "hex");
122
+ if (a.length !== b.length)
123
+ return false;
124
+ let mismatch = 0;
125
+ for (let i = 0; i < a.length; i++) {
126
+ mismatch |= a[i] ^ b[i];
127
+ }
128
+ return mismatch === 0;
129
+ }
130
+ /**
131
+ * Check if an API key has a required scope.
132
+ *
133
+ * Admin scope grants all permissions.
134
+ *
135
+ * @param keyScopes - The scopes granted to the API key
136
+ * @param requiredScope - The scope to check for
137
+ * @returns Whether the key has the required scope
138
+ */
139
+ export function hasScope(keyScopes, requiredScope) {
140
+ return keyScopes.includes("admin") || keyScopes.includes(requiredScope);
141
+ }
142
+ /**
143
+ * Check if an API key has expired.
144
+ *
145
+ * @param expiresAt - Optional expiry timestamp
146
+ * @returns Whether the key is expired
147
+ */
148
+ export function isKeyExpired(expiresAt) {
149
+ if (!expiresAt)
150
+ return false;
151
+ return expiresAt.getTime() < Date.now();
152
+ }
153
+ /**
154
+ * Check if a request should be allowed under rate limiting.
155
+ *
156
+ * Uses a fixed 1-minute window.
157
+ *
158
+ * @param state - Current rate limit state
159
+ * @param limit - Maximum requests per minute
160
+ * @param nowMs - Current timestamp in milliseconds
161
+ * @returns Rate limit check result
162
+ */
163
+ export function checkRateLimit(state, limit, nowMs = Date.now()) {
164
+ const windowMs = 60 * 1000; // 1 minute
165
+ const windowStart = nowMs - (nowMs % windowMs);
166
+ // New window or no existing state
167
+ if (!state || state.windowStartMs !== windowStart) {
168
+ return {
169
+ result: {
170
+ allowed: true,
171
+ remaining: limit - 1,
172
+ resetMs: windowStart + windowMs,
173
+ limit,
174
+ },
175
+ newState: { count: 1, windowStartMs: windowStart },
176
+ };
177
+ }
178
+ const newCount = state.count + 1;
179
+ const allowed = newCount <= limit;
180
+ return {
181
+ result: {
182
+ allowed,
183
+ remaining: Math.max(0, limit - newCount),
184
+ resetMs: windowStart + windowMs,
185
+ limit,
186
+ },
187
+ newState: { count: newCount, windowStartMs: windowStart },
188
+ };
189
+ }
190
+ // ---------------------------------------------------------------------------
191
+ // Pagination
192
+ // ---------------------------------------------------------------------------
193
+ /**
194
+ * Encode a cursor for pagination (base64 JSON).
195
+ *
196
+ * @param data - The cursor data to encode
197
+ * @returns Base64-encoded cursor string
198
+ */
199
+ export function encodeCursor(data) {
200
+ return Buffer.from(JSON.stringify(data)).toString("base64url");
201
+ }
202
+ /**
203
+ * Decode a pagination cursor.
204
+ *
205
+ * @param cursor - The base64-encoded cursor string
206
+ * @returns The decoded cursor data, or null if invalid
207
+ */
208
+ export function decodeCursor(cursor) {
209
+ try {
210
+ const json = Buffer.from(cursor, "base64url").toString("utf-8");
211
+ return JSON.parse(json);
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
217
+ /**
218
+ * Validate API request parameters for slot queries.
219
+ *
220
+ * @param params - Query parameters
221
+ * @returns Validation result
222
+ */
223
+ export function validateSlotQueryParams(params) {
224
+ const errors = [];
225
+ if (!params.providerId && !params.teamId) {
226
+ errors.push({
227
+ field: "providerId",
228
+ message: "Either providerId or teamId is required",
229
+ });
230
+ }
231
+ if (!params.start) {
232
+ errors.push({ field: "start", message: "start date is required" });
233
+ }
234
+ else if (isNaN(Date.parse(params.start))) {
235
+ errors.push({
236
+ field: "start",
237
+ message: `Invalid date: "${params.start}"`,
238
+ });
239
+ }
240
+ if (!params.end) {
241
+ errors.push({ field: "end", message: "end date is required" });
242
+ }
243
+ else if (isNaN(Date.parse(params.end))) {
244
+ errors.push({ field: "end", message: `Invalid date: "${params.end}"` });
245
+ }
246
+ if (params.start && params.end) {
247
+ const start = new Date(params.start);
248
+ const end = new Date(params.end);
249
+ if (!isNaN(start.getTime()) && !isNaN(end.getTime()) && end <= start) {
250
+ errors.push({ field: "end", message: "end must be after start" });
251
+ }
252
+ }
253
+ return { valid: errors.length === 0, errors };
254
+ }
255
+ /**
256
+ * Parse and validate a sort parameter.
257
+ *
258
+ * @param sortParam - Sort string (e.g., "-createdAt" or "startsAt")
259
+ * @param allowedFields - Fields that can be sorted by
260
+ * @returns Parsed sort field and direction
261
+ */
262
+ export function parseSortParam(sortParam, allowedFields) {
263
+ if (!sortParam)
264
+ return null;
265
+ const descending = sortParam.startsWith("-");
266
+ const field = descending ? sortParam.slice(1) : sortParam;
267
+ if (!allowedFields.includes(field))
268
+ return null;
269
+ return { field, direction: descending ? "desc" : "asc" };
270
+ }
271
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,71 @@
1
+ /** Represents an authenticated user in the system */
2
+ export interface AuthUser {
3
+ id: string;
4
+ email: string;
5
+ name?: string;
6
+ role?: "admin" | "provider" | "customer";
7
+ }
8
+ /** Session returned by the auth adapter */
9
+ export interface AuthSession {
10
+ user: AuthUser;
11
+ expires: Date;
12
+ }
13
+ /**
14
+ * Pluggable authentication adapter interface.
15
+ * Default implementation uses NextAuth.js.
16
+ * Swap to Supabase Auth, Clerk, or Lucia by implementing this interface.
17
+ */
18
+ export interface AuthAdapter {
19
+ /** Get the currently authenticated user from the request */
20
+ getCurrentUser(request: Request): Promise<AuthUser | null>;
21
+ /** Get the full session */
22
+ getSession(request: Request): Promise<AuthSession | null>;
23
+ /** Verify an API token or signed booking token */
24
+ verifyToken(token: string): Promise<AuthUser | null>;
25
+ }
26
+ /** Request with injected auth context */
27
+ export interface AuthenticatedRequest extends Request {
28
+ user: AuthUser;
29
+ }
30
+ /** Options for the withAuth middleware */
31
+ export interface WithAuthOptions {
32
+ /** Require a specific role */
33
+ requiredRole?: "admin" | "provider" | "customer";
34
+ }
35
+ /**
36
+ * Middleware wrapper that injects the authenticated user into every request.
37
+ *
38
+ * - Rejects unauthenticated requests with 401.
39
+ * - Optionally checks user role.
40
+ * - Passes the authenticated user to the handler.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // In a Next.js API route
45
+ * export const GET = withAuth(authAdapter, async (req) => {
46
+ * const userId = req.user.id;
47
+ * // Provider can only access their own data
48
+ * const bookings = await db.query.bookings.findMany({
49
+ * where: eq(bookings.providerId, userId)
50
+ * });
51
+ * return Response.json(bookings);
52
+ * });
53
+ * ```
54
+ */
55
+ export declare function withAuth(adapter: AuthAdapter, handler: (req: AuthenticatedRequest) => Promise<Response>, options?: WithAuthOptions): (req: Request) => Promise<Response>;
56
+ /**
57
+ * Helper to scope database queries to the authenticated user.
58
+ * Providers can only access their own rows (user_id matches).
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const provider = await assertOwnership(db, providers, req.user.id, providerId);
63
+ * ```
64
+ */
65
+ export declare function assertProviderOwnership(userId: string, resourceUserId: string): void;
66
+ /**
67
+ * Helper to verify customer access to their own bookings.
68
+ * Customers can only access bookings where customer_email matches.
69
+ */
70
+ export declare function assertCustomerAccess(userEmail: string, bookingCustomerEmail: string): void;
71
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAEA,qDAAqD;AACrD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;CAC1C;AAED,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,IAAI,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAC3D,2BAA2B;IAC3B,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC1D,kDAAkD;IAClD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;CACtD;AAED,yCAAyC;AACzC,MAAM,WAAW,oBAAqB,SAAQ,OAAO;IACnD,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,YAAY,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;CAClD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC,QAAQ,CAAC,EACzD,OAAO,CAAC,EAAE,eAAe,GACxB,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA6CrC;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,IAAI,CAMN;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,oBAAoB,EAAE,MAAM,GAC3B,IAAI,CAMN"}
package/dist/auth.js ADDED
@@ -0,0 +1,81 @@
1
+ import { UnauthorizedError, ForbiddenError } from "@thebookingkit/core";
2
+ /**
3
+ * Middleware wrapper that injects the authenticated user into every request.
4
+ *
5
+ * - Rejects unauthenticated requests with 401.
6
+ * - Optionally checks user role.
7
+ * - Passes the authenticated user to the handler.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // In a Next.js API route
12
+ * export const GET = withAuth(authAdapter, async (req) => {
13
+ * const userId = req.user.id;
14
+ * // Provider can only access their own data
15
+ * const bookings = await db.query.bookings.findMany({
16
+ * where: eq(bookings.providerId, userId)
17
+ * });
18
+ * return Response.json(bookings);
19
+ * });
20
+ * ```
21
+ */
22
+ export function withAuth(adapter, handler, options) {
23
+ return async (req) => {
24
+ try {
25
+ // Try to get user from session first
26
+ let user = await adapter.getCurrentUser(req);
27
+ // If no session, try Bearer token
28
+ if (!user) {
29
+ const authHeader = req.headers.get("authorization");
30
+ if (authHeader?.startsWith("Bearer ")) {
31
+ const token = authHeader.slice(7);
32
+ user = await adapter.verifyToken(token);
33
+ }
34
+ }
35
+ if (!user) {
36
+ throw new UnauthorizedError();
37
+ }
38
+ // Check required role if specified
39
+ if (options?.requiredRole && user.role !== options.requiredRole) {
40
+ throw new ForbiddenError();
41
+ }
42
+ // Inject user into request
43
+ const authReq = req;
44
+ authReq.user = user;
45
+ return await handler(authReq);
46
+ }
47
+ catch (error) {
48
+ if (error instanceof UnauthorizedError) {
49
+ return Response.json({ error: error.message, code: error.code }, { status: 401 });
50
+ }
51
+ if (error instanceof ForbiddenError) {
52
+ return Response.json({ error: error.message, code: error.code }, { status: 403 });
53
+ }
54
+ throw error;
55
+ }
56
+ };
57
+ }
58
+ /**
59
+ * Helper to scope database queries to the authenticated user.
60
+ * Providers can only access their own rows (user_id matches).
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const provider = await assertOwnership(db, providers, req.user.id, providerId);
65
+ * ```
66
+ */
67
+ export function assertProviderOwnership(userId, resourceUserId) {
68
+ if (userId !== resourceUserId) {
69
+ throw new ForbiddenError("You do not have permission to access this provider's data.");
70
+ }
71
+ }
72
+ /**
73
+ * Helper to verify customer access to their own bookings.
74
+ * Customers can only access bookings where customer_email matches.
75
+ */
76
+ export function assertCustomerAccess(userEmail, bookingCustomerEmail) {
77
+ if (userEmail !== bookingCustomerEmail) {
78
+ throw new ForbiddenError("You do not have permission to access this booking.");
79
+ }
80
+ }
81
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAyCxE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,QAAQ,CACtB,OAAoB,EACpB,OAAyD,EACzD,OAAyB;IAEzB,OAAO,KAAK,EAAE,GAAY,EAAqB,EAAE;QAC/C,IAAI,CAAC;YACH,qCAAqC;YACrC,IAAI,IAAI,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;YAE7C,kCAAkC;YAClC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;gBACpD,IAAI,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;oBACtC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAClC,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gBAC1C,CAAC;YACH,CAAC;YAED,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,iBAAiB,EAAE,CAAC;YAChC,CAAC;YAED,mCAAmC;YACnC,IAAI,OAAO,EAAE,YAAY,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC;gBAChE,MAAM,IAAI,cAAc,EAAE,CAAC;YAC7B,CAAC;YAED,2BAA2B;YAC3B,MAAM,OAAO,GAAG,GAA2B,CAAC;YAC5C,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;YAEpB,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;gBACvC,OAAO,QAAQ,CAAC,IAAI,CAClB,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,EAC1C,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAC;YACJ,CAAC;YACD,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;gBACpC,OAAO,QAAQ,CAAC,IAAI,CAClB,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,EAC1C,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAc,EACd,cAAsB;IAEtB,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;QAC9B,MAAM,IAAI,cAAc,CACtB,4DAA4D,CAC7D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAAiB,EACjB,oBAA4B;IAE5B,IAAI,SAAS,KAAK,oBAAoB,EAAE,CAAC;QACvC,MAAM,IAAI,cAAc,CACtB,oDAAoD,CACrD,CAAC;IACJ,CAAC;AACH,CAAC"}