@thebookingkit/server 0.1.1 → 0.1.2

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 (104) hide show
  1. package/README.md +50 -0
  2. package/package.json +1 -1
  3. package/src/__tests__/api.test.ts +6 -6
  4. package/src/adapters/email-adapter.ts +2 -2
  5. package/src/adapters/job-adapter.ts +10 -10
  6. package/src/api.ts +5 -5
  7. package/.turbo/turbo-build.log +0 -6
  8. package/.turbo/turbo-test.log +0 -20
  9. package/dist/__tests__/api.test.d.ts +0 -2
  10. package/dist/__tests__/api.test.d.ts.map +0 -1
  11. package/dist/__tests__/api.test.js +0 -280
  12. package/dist/__tests__/api.test.js.map +0 -1
  13. package/dist/__tests__/auth.test.d.ts +0 -2
  14. package/dist/__tests__/auth.test.d.ts.map +0 -1
  15. package/dist/__tests__/auth.test.js +0 -78
  16. package/dist/__tests__/auth.test.js.map +0 -1
  17. package/dist/__tests__/concurrent-booking.test.d.ts +0 -2
  18. package/dist/__tests__/concurrent-booking.test.d.ts.map +0 -1
  19. package/dist/__tests__/concurrent-booking.test.js +0 -111
  20. package/dist/__tests__/concurrent-booking.test.js.map +0 -1
  21. package/dist/__tests__/multi-tenancy.test.d.ts +0 -2
  22. package/dist/__tests__/multi-tenancy.test.d.ts.map +0 -1
  23. package/dist/__tests__/multi-tenancy.test.js +0 -196
  24. package/dist/__tests__/multi-tenancy.test.js.map +0 -1
  25. package/dist/__tests__/serialization-retry.test.d.ts +0 -2
  26. package/dist/__tests__/serialization-retry.test.d.ts.map +0 -1
  27. package/dist/__tests__/serialization-retry.test.js +0 -53
  28. package/dist/__tests__/serialization-retry.test.js.map +0 -1
  29. package/dist/__tests__/webhooks.test.d.ts +0 -2
  30. package/dist/__tests__/webhooks.test.d.ts.map +0 -1
  31. package/dist/__tests__/webhooks.test.js +0 -286
  32. package/dist/__tests__/webhooks.test.js.map +0 -1
  33. package/dist/__tests__/workflows.test.d.ts +0 -2
  34. package/dist/__tests__/workflows.test.d.ts.map +0 -1
  35. package/dist/__tests__/workflows.test.js +0 -299
  36. package/dist/__tests__/workflows.test.js.map +0 -1
  37. package/dist/adapters/calendar-adapter.d.ts +0 -47
  38. package/dist/adapters/calendar-adapter.d.ts.map +0 -1
  39. package/dist/adapters/calendar-adapter.js +0 -2
  40. package/dist/adapters/calendar-adapter.js.map +0 -1
  41. package/dist/adapters/email-adapter.d.ts +0 -65
  42. package/dist/adapters/email-adapter.d.ts.map +0 -1
  43. package/dist/adapters/email-adapter.js +0 -40
  44. package/dist/adapters/email-adapter.js.map +0 -1
  45. package/dist/adapters/index.d.ts +0 -9
  46. package/dist/adapters/index.d.ts.map +0 -1
  47. package/dist/adapters/index.js +0 -3
  48. package/dist/adapters/index.js.map +0 -1
  49. package/dist/adapters/job-adapter.d.ts +0 -26
  50. package/dist/adapters/job-adapter.d.ts.map +0 -1
  51. package/dist/adapters/job-adapter.js +0 -13
  52. package/dist/adapters/job-adapter.js.map +0 -1
  53. package/dist/adapters/payment-adapter.d.ts +0 -106
  54. package/dist/adapters/payment-adapter.d.ts.map +0 -1
  55. package/dist/adapters/payment-adapter.js +0 -8
  56. package/dist/adapters/payment-adapter.js.map +0 -1
  57. package/dist/adapters/sms-adapter.d.ts +0 -33
  58. package/dist/adapters/sms-adapter.d.ts.map +0 -1
  59. package/dist/adapters/sms-adapter.js +0 -8
  60. package/dist/adapters/sms-adapter.js.map +0 -1
  61. package/dist/adapters/storage-adapter.d.ts +0 -12
  62. package/dist/adapters/storage-adapter.d.ts.map +0 -1
  63. package/dist/adapters/storage-adapter.js +0 -2
  64. package/dist/adapters/storage-adapter.js.map +0 -1
  65. package/dist/api.d.ts +0 -223
  66. package/dist/api.d.ts.map +0 -1
  67. package/dist/api.js +0 -271
  68. package/dist/api.js.map +0 -1
  69. package/dist/auth.d.ts +0 -71
  70. package/dist/auth.d.ts.map +0 -1
  71. package/dist/auth.js +0 -81
  72. package/dist/auth.js.map +0 -1
  73. package/dist/booking-tokens.d.ts +0 -23
  74. package/dist/booking-tokens.d.ts.map +0 -1
  75. package/dist/booking-tokens.js +0 -52
  76. package/dist/booking-tokens.js.map +0 -1
  77. package/dist/email-templates.d.ts +0 -36
  78. package/dist/email-templates.d.ts.map +0 -1
  79. package/dist/email-templates.js +0 -112
  80. package/dist/email-templates.js.map +0 -1
  81. package/dist/index.d.ts +0 -13
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/index.js +0 -22
  84. package/dist/index.js.map +0 -1
  85. package/dist/multi-tenancy.d.ts +0 -132
  86. package/dist/multi-tenancy.d.ts.map +0 -1
  87. package/dist/multi-tenancy.js +0 -188
  88. package/dist/multi-tenancy.js.map +0 -1
  89. package/dist/notification-jobs.d.ts +0 -143
  90. package/dist/notification-jobs.d.ts.map +0 -1
  91. package/dist/notification-jobs.js +0 -278
  92. package/dist/notification-jobs.js.map +0 -1
  93. package/dist/serialization-retry.d.ts +0 -28
  94. package/dist/serialization-retry.d.ts.map +0 -1
  95. package/dist/serialization-retry.js +0 -71
  96. package/dist/serialization-retry.js.map +0 -1
  97. package/dist/webhooks.d.ts +0 -164
  98. package/dist/webhooks.d.ts.map +0 -1
  99. package/dist/webhooks.js +0 -228
  100. package/dist/webhooks.js.map +0 -1
  101. package/dist/workflows.d.ts +0 -169
  102. package/dist/workflows.d.ts.map +0 -1
  103. package/dist/workflows.js +0 -251
  104. package/dist/workflows.js.map +0 -1
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @thebookingkit/server
2
+
3
+ Auth, webhooks, API keys, workflows, and adapter interfaces for booking system backends.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@thebookingkit/server)](https://www.npmjs.com/package/@thebookingkit/server)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue)](https://www.typescriptlang.org/)
8
+
9
+ Part of [The Booking Kit](https://thebookingkit.dev) — The Headless Booking Primitive.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @thebookingkit/server
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```ts
20
+ import { withAuth, signWebhookPayload, generateApiKey } from "@thebookingkit/server";
21
+
22
+ // Protect a route with auth middleware
23
+ const handler = withAuth(async (req) => {
24
+ const user = req.auth; // typed AuthUser
25
+ return Response.json({ ok: true });
26
+ });
27
+
28
+ // Sign and verify webhook payloads
29
+ const signature = signWebhookPayload(payload, secret);
30
+ ```
31
+
32
+ ## Key Features
33
+
34
+ - **Auth Middleware** — `withAuth`, ownership assertions, pluggable `AuthAdapter` (NextAuth.js, Clerk, Supabase, Lucia)
35
+ - **Webhooks** — HMAC-SHA256 signing/verification, retry with exponential backoff, subscription matching
36
+ - **API Key Management** — Generation, hashing, verification, scopes, expiration, rate limiting
37
+ - **Booking Tokens** — Secure token generation and verification for confirmation/cancellation links
38
+ - **Workflows** — Trigger-based automation with conditions, template variables, and action routing
39
+ - **Notifications** — Email templates (confirmation, reminder, cancellation, reschedule) and calendar sync
40
+ - **Multi-Tenancy** — Role-based permissions, settings inheritance (org > provider > event type), tenant scoping
41
+ - **Serialization Retry** — `withSerializableRetry` for SERIALIZABLE transaction conflict handling
42
+ - **Adapter Interfaces** — Swappable `EmailAdapter`, `CalendarAdapter`, `JobAdapter`, `StorageAdapter`, `SmsAdapter`, `PaymentAdapter`
43
+
44
+ ## Documentation
45
+
46
+ [**Full Documentation**](https://thebookingkit.dev/features/webhooks/)
47
+
48
+ ## License
49
+
50
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thebookingkit/server",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -18,7 +18,7 @@ import {
18
18
  } from "../api.js";
19
19
 
20
20
  // Set the required env var for API key hashing in tests
21
- process.env.SLOTKIT_API_KEY_SECRET = "test-secret-for-unit-tests-only";
21
+ process.env.THEBOOKINGKIT_API_KEY_SECRET = "test-secret-for-unit-tests-only";
22
22
 
23
23
  // ---------------------------------------------------------------------------
24
24
  // Response Helpers
@@ -106,15 +106,15 @@ describe("generateApiKey", () => {
106
106
  });
107
107
 
108
108
  describe("hashApiKey", () => {
109
- it("throws when SLOTKIT_API_KEY_SECRET is missing", () => {
110
- const original = process.env.SLOTKIT_API_KEY_SECRET;
111
- delete process.env.SLOTKIT_API_KEY_SECRET;
109
+ it("throws when THEBOOKINGKIT_API_KEY_SECRET is missing", () => {
110
+ const original = process.env.THEBOOKINGKIT_API_KEY_SECRET;
111
+ delete process.env.THEBOOKINGKIT_API_KEY_SECRET;
112
112
  try {
113
113
  expect(() => hashApiKey("sk_live_test")).toThrow(
114
- "SLOTKIT_API_KEY_SECRET environment variable is required",
114
+ "THEBOOKINGKIT_API_KEY_SECRET environment variable is required",
115
115
  );
116
116
  } finally {
117
- process.env.SLOTKIT_API_KEY_SECRET = original;
117
+ process.env.THEBOOKINGKIT_API_KEY_SECRET = original;
118
118
  }
119
119
  });
120
120
 
@@ -73,11 +73,11 @@ export function generateICSAttachment(booking: {
73
73
  const ics = [
74
74
  "BEGIN:VCALENDAR",
75
75
  "VERSION:2.0",
76
- "PRODID:-//SlotKit//EN",
76
+ "PRODID:-//TheBookingKit//EN",
77
77
  "CALSCALE:GREGORIAN",
78
78
  "METHOD:PUBLISH",
79
79
  "BEGIN:VEVENT",
80
- `UID:${booking.id}@slotkit`,
80
+ `UID:${booking.id}@thebookingkit`,
81
81
  `DTSTART:${formatICSDate(booking.startsAt)}`,
82
82
  `DTEND:${formatICSDate(booking.endsAt)}`,
83
83
  `SUMMARY:${escapeICS(booking.title)}`,
@@ -12,15 +12,15 @@ export interface JobAdapter {
12
12
  cancel(jobId: string): Promise<void>;
13
13
  }
14
14
 
15
- /** Common job names used by SlotKit */
15
+ /** Common job names used by The Booking Kit */
16
16
  export const JOB_NAMES = {
17
- SEND_CONFIRMATION_EMAIL: "slotkit/send-confirmation-email",
18
- SEND_REMINDER_EMAIL: "slotkit/send-reminder-email",
19
- SEND_CANCELLATION_EMAIL: "slotkit/send-cancellation-email",
20
- SEND_RESCHEDULE_EMAIL: "slotkit/send-reschedule-email",
21
- SYNC_CALENDAR_EVENT: "slotkit/sync-calendar-event",
22
- DELETE_CALENDAR_EVENT: "slotkit/delete-calendar-event",
23
- CHECK_CALENDAR_CONFLICTS: "slotkit/check-calendar-conflicts",
24
- AUTO_REJECT_PENDING: "slotkit/auto-reject-pending-booking",
25
- PROCESS_WEBHOOK: "slotkit/process-webhook",
17
+ SEND_CONFIRMATION_EMAIL: "thebookingkit/send-confirmation-email",
18
+ SEND_REMINDER_EMAIL: "thebookingkit/send-reminder-email",
19
+ SEND_CANCELLATION_EMAIL: "thebookingkit/send-cancellation-email",
20
+ SEND_RESCHEDULE_EMAIL: "thebookingkit/send-reschedule-email",
21
+ SYNC_CALENDAR_EVENT: "thebookingkit/sync-calendar-event",
22
+ DELETE_CALENDAR_EVENT: "thebookingkit/delete-calendar-event",
23
+ CHECK_CALENDAR_CONFLICTS: "thebookingkit/check-calendar-conflicts",
24
+ AUTO_REJECT_PENDING: "thebookingkit/auto-reject-pending-booking",
25
+ PROCESS_WEBHOOK: "thebookingkit/process-webhook",
26
26
  } as const;
package/src/api.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * REST API utilities for SlotKit.
2
+ * REST API utilities for The Booking Kit.
3
3
  *
4
4
  * Provides standardized response formatting, API key management,
5
5
  * rate limiting, pagination, and request validation helpers
@@ -195,19 +195,19 @@ export function generateApiKey(prefix: string = "sk_live_"): GeneratedApiKey {
195
195
  /**
196
196
  * Hash an API key for secure storage.
197
197
  *
198
- * Uses HMAC-SHA256 with a secret from the SLOTKIT_API_KEY_SECRET
198
+ * Uses HMAC-SHA256 with a secret from the THEBOOKINGKIT_API_KEY_SECRET
199
199
  * environment variable. Throws if the secret is not configured.
200
200
  *
201
201
  * @param key - The full API key
202
- * @param secret - Optional HMAC secret (defaults to SLOTKIT_API_KEY_SECRET env var)
202
+ * @param secret - Optional HMAC secret (defaults to THEBOOKINGKIT_API_KEY_SECRET env var)
203
203
  * @returns The hex-encoded hash
204
204
  */
205
205
  export function hashApiKey(key: string, secret?: string): string {
206
206
  const hmacSecret =
207
- secret ?? process.env.SLOTKIT_API_KEY_SECRET;
207
+ secret ?? process.env.THEBOOKINGKIT_API_KEY_SECRET;
208
208
  if (!hmacSecret) {
209
209
  throw new Error(
210
- "SLOTKIT_API_KEY_SECRET environment variable is required for API key hashing. " +
210
+ "THEBOOKINGKIT_API_KEY_SECRET environment variable is required for API key hashing. " +
211
211
  "Set it to a random 32+ character string.",
212
212
  );
213
213
  }
@@ -1,6 +0,0 @@
1
-
2
- 
3
- > @thebookingkit/server@0.1.0 build
4
- > tsc
5
-
6
- ⠙
@@ -1,20 +0,0 @@
1
-
2
- > @thebookingkit/server@0.1.0 test
3
- > vitest run
4
-
5
-
6
- RUN v3.2.4 /Users/zain/Desktop/slotkit/packages/server
7
-
8
- ✓ src/__tests__/multi-tenancy.test.ts (26 tests) 7ms
9
- ✓ src/__tests__/webhooks.test.ts (35 tests) 10ms
10
- ✓ src/__tests__/workflows.test.ts (37 tests) 14ms
11
- ✓ src/__tests__/api.test.ts (40 tests) 15ms
12
- ✓ src/__tests__/auth.test.ts (8 tests) 15ms
13
- ✓ src/__tests__/serialization-retry.test.ts (6 tests) 106ms
14
- ✓ src/__tests__/concurrent-booking.test.ts (5 tests) 167ms
15
-
16
- Test Files 7 passed (7)
17
- Tests 157 passed (157)
18
- Start at 01:24:25
19
- Duration 700ms (transform 315ms, setup 0ms, collect 1.10s, tests 335ms, environment 1ms, prepare 475ms)
20
-
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=api.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/api.test.ts"],"names":[],"mappings":""}
@@ -1,280 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { createErrorResponse, createSuccessResponse, createPaginatedResponse, generateApiKey, hashApiKey, verifyApiKey, hasScope, isKeyExpired, checkRateLimit, encodeCursor, decodeCursor, validateSlotQueryParams, parseSortParam, API_ERROR_CODES, } from "../api.js";
3
- // Set the required env var for API key hashing in tests
4
- process.env.SLOTKIT_API_KEY_SECRET = "test-secret-for-unit-tests-only";
5
- // ---------------------------------------------------------------------------
6
- // Response Helpers
7
- // ---------------------------------------------------------------------------
8
- describe("createErrorResponse", () => {
9
- it("creates a standard error envelope", () => {
10
- const response = createErrorResponse("NOT_FOUND", "Booking not found");
11
- expect(response).toEqual({
12
- error: { code: "NOT_FOUND", message: "Booking not found" },
13
- });
14
- });
15
- it("includes details when provided", () => {
16
- const response = createErrorResponse("VALIDATION_ERROR", "Invalid input", { field: "email" });
17
- expect(response.error.details).toEqual({ field: "email" });
18
- });
19
- it("omits details when not provided", () => {
20
- const response = createErrorResponse("UNAUTHORIZED", "Invalid key");
21
- expect(response.error.details).toBeUndefined();
22
- });
23
- });
24
- describe("createSuccessResponse", () => {
25
- it("wraps data in a success envelope", () => {
26
- const response = createSuccessResponse({ id: "bk-1" });
27
- expect(response).toEqual({ data: { id: "bk-1" } });
28
- });
29
- it("includes meta when provided", () => {
30
- const response = createSuccessResponse([], {
31
- nextCursor: "abc",
32
- hasMore: true,
33
- });
34
- expect(response.meta?.nextCursor).toBe("abc");
35
- });
36
- });
37
- describe("createPaginatedResponse", () => {
38
- it("creates paginated response with cursor", () => {
39
- const response = createPaginatedResponse(["a", "b"], "cursor123", 10);
40
- expect(response.data).toEqual(["a", "b"]);
41
- expect(response.meta.nextCursor).toBe("cursor123");
42
- expect(response.meta.hasMore).toBe(true);
43
- expect(response.meta.total).toBe(10);
44
- });
45
- it("marks hasMore false when nextCursor is null", () => {
46
- const response = createPaginatedResponse(["a"], null);
47
- expect(response.meta.hasMore).toBe(false);
48
- expect(response.meta.nextCursor).toBeNull();
49
- });
50
- });
51
- // ---------------------------------------------------------------------------
52
- // API Key Management
53
- // ---------------------------------------------------------------------------
54
- describe("generateApiKey", () => {
55
- it("generates a key with the given prefix", () => {
56
- const { key } = generateApiKey("sk_live_");
57
- expect(key).toMatch(/^sk_live_[0-9a-f]{64}$/);
58
- });
59
- it("generates a display prefix", () => {
60
- const { prefix } = generateApiKey("sk_live_");
61
- expect(prefix).toContain("...");
62
- });
63
- it("generates a 64-char hex hash", () => {
64
- const { hash } = generateApiKey();
65
- expect(hash).toMatch(/^[0-9a-f]{64}$/);
66
- });
67
- it("generates unique keys each time", () => {
68
- const { key: k1 } = generateApiKey();
69
- const { key: k2 } = generateApiKey();
70
- expect(k1).not.toBe(k2);
71
- });
72
- });
73
- describe("hashApiKey", () => {
74
- it("throws when SLOTKIT_API_KEY_SECRET is missing", () => {
75
- const original = process.env.SLOTKIT_API_KEY_SECRET;
76
- delete process.env.SLOTKIT_API_KEY_SECRET;
77
- try {
78
- expect(() => hashApiKey("sk_live_test")).toThrow("SLOTKIT_API_KEY_SECRET environment variable is required");
79
- }
80
- finally {
81
- process.env.SLOTKIT_API_KEY_SECRET = original;
82
- }
83
- });
84
- it("accepts an explicit secret parameter", () => {
85
- const hash = hashApiKey("sk_live_test", "my-explicit-secret");
86
- expect(hash).toMatch(/^[0-9a-f]{64}$/);
87
- });
88
- });
89
- describe("verifyApiKey", () => {
90
- it("returns true for correct key", () => {
91
- const { key, hash } = generateApiKey();
92
- expect(verifyApiKey(key, hash)).toBe(true);
93
- });
94
- it("returns false for tampered key", () => {
95
- const { hash } = generateApiKey();
96
- expect(verifyApiKey("sk_live_wrong", hash)).toBe(false);
97
- });
98
- it("returns false for wrong hash", () => {
99
- const { key } = generateApiKey();
100
- const { hash: otherHash } = generateApiKey();
101
- expect(verifyApiKey(key, otherHash)).toBe(false);
102
- });
103
- });
104
- describe("hasScope", () => {
105
- it("returns true when scope is present", () => {
106
- const scopes = ["read:bookings", "write:bookings"];
107
- expect(hasScope(scopes, "read:bookings")).toBe(true);
108
- });
109
- it("returns false when scope is absent", () => {
110
- const scopes = ["read:bookings"];
111
- expect(hasScope(scopes, "write:bookings")).toBe(false);
112
- });
113
- it("admin scope grants all permissions", () => {
114
- const scopes = ["admin"];
115
- expect(hasScope(scopes, "read:bookings")).toBe(true);
116
- expect(hasScope(scopes, "write:event-types")).toBe(true);
117
- expect(hasScope(scopes, "write:webhooks")).toBe(true);
118
- });
119
- });
120
- describe("isKeyExpired", () => {
121
- it("returns false for no expiry", () => {
122
- expect(isKeyExpired(undefined)).toBe(false);
123
- expect(isKeyExpired(null)).toBe(false);
124
- });
125
- it("returns true for past expiry", () => {
126
- expect(isKeyExpired(new Date(Date.now() - 1000))).toBe(true);
127
- });
128
- it("returns false for future expiry", () => {
129
- expect(isKeyExpired(new Date(Date.now() + 86400000))).toBe(false);
130
- });
131
- });
132
- // ---------------------------------------------------------------------------
133
- // Rate Limiting
134
- // ---------------------------------------------------------------------------
135
- describe("checkRateLimit", () => {
136
- beforeEach(() => {
137
- vi.useFakeTimers();
138
- vi.setSystemTime(new Date("2026-03-15T14:00:00.000Z"));
139
- });
140
- afterEach(() => {
141
- vi.useRealTimers();
142
- });
143
- it("allows first request", () => {
144
- const { result } = checkRateLimit(null, 120);
145
- expect(result.allowed).toBe(true);
146
- expect(result.remaining).toBe(119);
147
- expect(result.limit).toBe(120);
148
- });
149
- it("tracks requests within a window", () => {
150
- const { newState } = checkRateLimit(null, 120);
151
- const { result } = checkRateLimit(newState, 120);
152
- expect(result.allowed).toBe(true);
153
- expect(result.remaining).toBe(118);
154
- });
155
- it("blocks requests when limit exceeded", () => {
156
- let state = checkRateLimit(null, 2).newState;
157
- state = checkRateLimit(state, 2).newState;
158
- const { result } = checkRateLimit(state, 2);
159
- expect(result.allowed).toBe(false);
160
- expect(result.remaining).toBe(0);
161
- });
162
- it("resets counter in new window", () => {
163
- let state = checkRateLimit(null, 2).newState;
164
- state = checkRateLimit(state, 2).newState;
165
- // Advance to next minute
166
- vi.advanceTimersByTime(60 * 1000);
167
- const { result } = checkRateLimit(state, 2);
168
- expect(result.allowed).toBe(true);
169
- expect(result.remaining).toBe(1);
170
- });
171
- });
172
- // ---------------------------------------------------------------------------
173
- // Cursor Pagination
174
- // ---------------------------------------------------------------------------
175
- describe("encodeCursor / decodeCursor", () => {
176
- it("round-trips cursor data", () => {
177
- const data = { id: "bk-1", createdAt: "2026-03-15T14:00:00Z" };
178
- const cursor = encodeCursor(data);
179
- const decoded = decodeCursor(cursor);
180
- expect(decoded).toEqual(data);
181
- });
182
- it("returns null for invalid cursor", () => {
183
- expect(decodeCursor("not-valid-base64!!!")).toBeNull();
184
- });
185
- it("returns null for non-JSON cursor", () => {
186
- const cursor = Buffer.from("not json").toString("base64url");
187
- expect(decodeCursor(cursor)).toBeNull();
188
- });
189
- });
190
- // ---------------------------------------------------------------------------
191
- // validateSlotQueryParams
192
- // ---------------------------------------------------------------------------
193
- describe("validateSlotQueryParams", () => {
194
- const validParams = {
195
- providerId: "prov-1",
196
- start: "2026-03-15",
197
- end: "2026-04-15",
198
- timezone: "America/New_York",
199
- };
200
- it("accepts valid params", () => {
201
- const result = validateSlotQueryParams(validParams);
202
- expect(result.valid).toBe(true);
203
- expect(result.errors).toHaveLength(0);
204
- });
205
- it("accepts teamId instead of providerId", () => {
206
- const result = validateSlotQueryParams({
207
- teamId: "team-1",
208
- start: "2026-03-15",
209
- end: "2026-04-15",
210
- });
211
- expect(result.valid).toBe(true);
212
- });
213
- it("rejects missing providerId and teamId", () => {
214
- const result = validateSlotQueryParams({
215
- start: "2026-03-15",
216
- end: "2026-04-15",
217
- });
218
- expect(result.valid).toBe(false);
219
- expect(result.errors[0].field).toBe("providerId");
220
- });
221
- it("rejects missing start date", () => {
222
- const result = validateSlotQueryParams({
223
- providerId: "p1",
224
- end: "2026-04-15",
225
- });
226
- expect(result.valid).toBe(false);
227
- expect(result.errors.some((e) => e.field === "start")).toBe(true);
228
- });
229
- it("rejects invalid start date", () => {
230
- const result = validateSlotQueryParams({
231
- providerId: "p1",
232
- start: "not-a-date",
233
- end: "2026-04-15",
234
- });
235
- expect(result.valid).toBe(false);
236
- });
237
- it("rejects end before start", () => {
238
- const result = validateSlotQueryParams({
239
- providerId: "p1",
240
- start: "2026-04-15",
241
- end: "2026-03-15",
242
- });
243
- expect(result.valid).toBe(false);
244
- expect(result.errors.some((e) => e.message.includes("after start"))).toBe(true);
245
- });
246
- });
247
- // ---------------------------------------------------------------------------
248
- // parseSortParam
249
- // ---------------------------------------------------------------------------
250
- describe("parseSortParam", () => {
251
- const allowedFields = ["createdAt", "startsAt", "status"];
252
- it("parses ascending sort", () => {
253
- const result = parseSortParam("startsAt", allowedFields);
254
- expect(result).toEqual({ field: "startsAt", direction: "asc" });
255
- });
256
- it("parses descending sort with leading minus", () => {
257
- const result = parseSortParam("-createdAt", allowedFields);
258
- expect(result).toEqual({ field: "createdAt", direction: "desc" });
259
- });
260
- it("returns null for invalid field", () => {
261
- expect(parseSortParam("unknownField", allowedFields)).toBeNull();
262
- });
263
- it("returns null for undefined input", () => {
264
- expect(parseSortParam(undefined, allowedFields)).toBeNull();
265
- });
266
- });
267
- // ---------------------------------------------------------------------------
268
- // API_ERROR_CODES
269
- // ---------------------------------------------------------------------------
270
- describe("API_ERROR_CODES", () => {
271
- it("exports all standard error codes", () => {
272
- expect(API_ERROR_CODES.NOT_FOUND).toBe("NOT_FOUND");
273
- expect(API_ERROR_CODES.UNAUTHORIZED).toBe("UNAUTHORIZED");
274
- expect(API_ERROR_CODES.FORBIDDEN).toBe("FORBIDDEN");
275
- expect(API_ERROR_CODES.VALIDATION_ERROR).toBe("VALIDATION_ERROR");
276
- expect(API_ERROR_CODES.RATE_LIMITED).toBe("RATE_LIMITED");
277
- expect(API_ERROR_CODES.CONFLICT).toBe("CONFLICT");
278
- });
279
- });
280
- //# sourceMappingURL=api.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.test.js","sourceRoot":"","sources":["../../src/__tests__/api.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=auth.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":""}
@@ -1,78 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { withAuth, assertProviderOwnership, assertCustomerAccess, ForbiddenError, } from "../index.js";
3
- function mockAdapter(user) {
4
- return {
5
- getCurrentUser: vi.fn().mockResolvedValue(user),
6
- getSession: vi.fn().mockResolvedValue(user ? { user, expires: new Date() } : null),
7
- verifyToken: vi.fn().mockResolvedValue(user),
8
- };
9
- }
10
- function mockRequest(headers = {}) {
11
- return new Request("http://localhost/api/test", {
12
- headers: new Headers(headers),
13
- });
14
- }
15
- describe("withAuth", () => {
16
- it("injects user and calls handler on valid session", async () => {
17
- const user = { id: "user1", email: "test@test.com" };
18
- const adapter = mockAdapter(user);
19
- const handler = vi.fn().mockResolvedValue(Response.json({ ok: true }));
20
- const wrapped = withAuth(adapter, handler);
21
- const response = await wrapped(mockRequest());
22
- expect(response.status).toBe(200);
23
- expect(handler).toHaveBeenCalledTimes(1);
24
- const calledReq = handler.mock.calls[0][0];
25
- expect(calledReq.user).toEqual(user);
26
- });
27
- it("returns 401 when no user is authenticated", async () => {
28
- const adapter = mockAdapter(null);
29
- // verifyToken also returns null
30
- adapter.verifyToken = vi.fn().mockResolvedValue(null);
31
- const handler = vi.fn();
32
- const wrapped = withAuth(adapter, handler);
33
- const response = await wrapped(mockRequest());
34
- expect(response.status).toBe(401);
35
- expect(handler).not.toHaveBeenCalled();
36
- });
37
- it("falls back to Bearer token when session returns null", async () => {
38
- const user = { id: "user2", email: "api@test.com" };
39
- const adapter = mockAdapter(null);
40
- adapter.getCurrentUser = vi.fn().mockResolvedValue(null);
41
- adapter.verifyToken = vi.fn().mockResolvedValue(user);
42
- const handler = vi.fn().mockResolvedValue(Response.json({ ok: true }));
43
- const wrapped = withAuth(adapter, handler);
44
- const response = await wrapped(mockRequest({ Authorization: "Bearer test-token-123" }));
45
- expect(response.status).toBe(200);
46
- expect(adapter.verifyToken).toHaveBeenCalledWith("test-token-123");
47
- });
48
- it("returns 403 when role does not match", async () => {
49
- const user = {
50
- id: "user3",
51
- email: "member@test.com",
52
- role: "customer",
53
- };
54
- const adapter = mockAdapter(user);
55
- const handler = vi.fn();
56
- const wrapped = withAuth(adapter, handler, { requiredRole: "admin" });
57
- const response = await wrapped(mockRequest());
58
- expect(response.status).toBe(403);
59
- expect(handler).not.toHaveBeenCalled();
60
- });
61
- });
62
- describe("assertProviderOwnership", () => {
63
- it("does not throw when user IDs match", () => {
64
- expect(() => assertProviderOwnership("user1", "user1")).not.toThrow();
65
- });
66
- it("throws ForbiddenError when user IDs do not match", () => {
67
- expect(() => assertProviderOwnership("user1", "user2")).toThrow(ForbiddenError);
68
- });
69
- });
70
- describe("assertCustomerAccess", () => {
71
- it("does not throw when emails match", () => {
72
- expect(() => assertCustomerAccess("a@test.com", "a@test.com")).not.toThrow();
73
- });
74
- it("throws ForbiddenError when emails do not match", () => {
75
- expect(() => assertCustomerAccess("a@test.com", "b@test.com")).toThrow(ForbiddenError);
76
- });
77
- });
78
- //# sourceMappingURL=auth.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=concurrent-booking.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"concurrent-booking.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/concurrent-booking.test.ts"],"names":[],"mappings":""}