@tummycrypt/acuity-middleware 0.1.0
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.
- package/.github/workflows/build-paper.yml +39 -0
- package/.github/workflows/ci.yml +37 -0
- package/Dockerfile +53 -0
- package/README.md +103 -0
- package/docs/blog-post.mdx +240 -0
- package/docs/paper/IEEEtran.bst +2409 -0
- package/docs/paper/IEEEtran.cls +6347 -0
- package/docs/paper/acuity-middleware-paper.tex +375 -0
- package/docs/paper/balance.sty +87 -0
- package/docs/paper/references.bib +231 -0
- package/docs/paper.md +400 -0
- package/flake.nix +32 -0
- package/modal-app.py +82 -0
- package/package.json +48 -0
- package/src/adapters/acuity-scraper.ts +543 -0
- package/src/adapters/types.ts +193 -0
- package/src/core/types.ts +325 -0
- package/src/index.ts +75 -0
- package/src/middleware/acuity-wizard.ts +456 -0
- package/src/middleware/browser-service.ts +183 -0
- package/src/middleware/errors.ts +70 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/remote-adapter.ts +246 -0
- package/src/middleware/selectors.ts +308 -0
- package/src/middleware/server.ts +372 -0
- package/src/middleware/steps/bypass-payment.ts +226 -0
- package/src/middleware/steps/extract.ts +174 -0
- package/src/middleware/steps/fill-form.ts +359 -0
- package/src/middleware/steps/index.ts +27 -0
- package/src/middleware/steps/navigate.ts +537 -0
- package/src/middleware/steps/read-availability.ts +399 -0
- package/src/middleware/steps/read-slots.ts +405 -0
- package/src/middleware/steps/submit.ts +168 -0
- package/src/server.ts +5 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduling Adapter Interface
|
|
3
|
+
* Backend-agnostic contract for scheduling providers (Acuity, Cal.com, etc.)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
SchedulingResult,
|
|
8
|
+
Service,
|
|
9
|
+
Provider,
|
|
10
|
+
TimeSlot,
|
|
11
|
+
AvailableDate,
|
|
12
|
+
Booking,
|
|
13
|
+
BookingRequest,
|
|
14
|
+
SlotReservation,
|
|
15
|
+
ClientInfo,
|
|
16
|
+
} from '../core/types.js';
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// SCHEDULING ADAPTER INTERFACE
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The core abstraction for scheduling backends.
|
|
24
|
+
* Implement this interface for Acuity, Cal.com, or any other provider.
|
|
25
|
+
*/
|
|
26
|
+
export interface SchedulingAdapter {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Services (Appointment Types)
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get all available services/appointment types
|
|
35
|
+
*/
|
|
36
|
+
getServices(): SchedulingResult<Service[]>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get a specific service by ID
|
|
40
|
+
*/
|
|
41
|
+
getService(serviceId: string): SchedulingResult<Service>;
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Providers (Calendars/Staff)
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get all providers/calendars
|
|
49
|
+
*/
|
|
50
|
+
getProviders(): SchedulingResult<Provider[]>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get a specific provider by ID
|
|
54
|
+
*/
|
|
55
|
+
getProvider(providerId: string): SchedulingResult<Provider>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get providers available for a specific service
|
|
59
|
+
*/
|
|
60
|
+
getProvidersForService(serviceId: string): SchedulingResult<Provider[]>;
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Availability
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get available dates for a service (and optionally a provider)
|
|
68
|
+
*/
|
|
69
|
+
getAvailableDates(params: {
|
|
70
|
+
serviceId: string;
|
|
71
|
+
providerId?: string;
|
|
72
|
+
startDate: string; // YYYY-MM-DD
|
|
73
|
+
endDate: string; // YYYY-MM-DD
|
|
74
|
+
}): SchedulingResult<AvailableDate[]>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get available time slots for a specific date
|
|
78
|
+
*/
|
|
79
|
+
getAvailableSlots(params: {
|
|
80
|
+
serviceId: string;
|
|
81
|
+
providerId?: string;
|
|
82
|
+
date: string; // YYYY-MM-DD
|
|
83
|
+
}): SchedulingResult<TimeSlot[]>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if a specific datetime is still available
|
|
87
|
+
*/
|
|
88
|
+
checkSlotAvailability(params: {
|
|
89
|
+
serviceId: string;
|
|
90
|
+
providerId?: string;
|
|
91
|
+
datetime: string; // ISO 8601
|
|
92
|
+
}): SchedulingResult<boolean>;
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Slot Reservation (Hold)
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create a temporary hold on a time slot while payment is processing.
|
|
100
|
+
* This prevents double-booking during the checkout flow.
|
|
101
|
+
*/
|
|
102
|
+
createReservation(params: {
|
|
103
|
+
serviceId: string;
|
|
104
|
+
providerId?: string;
|
|
105
|
+
datetime: string;
|
|
106
|
+
duration: number;
|
|
107
|
+
expirationMinutes?: number;
|
|
108
|
+
notes?: string;
|
|
109
|
+
}): SchedulingResult<SlotReservation>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Release a slot reservation (after successful booking or timeout)
|
|
113
|
+
*/
|
|
114
|
+
releaseReservation(reservationId: string): SchedulingResult<void>;
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Bookings
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create a new booking/appointment
|
|
122
|
+
*/
|
|
123
|
+
createBooking(request: BookingRequest): SchedulingResult<Booking>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create a booking with a payment reference stored in notes/metadata
|
|
127
|
+
* This is the key method for alt-payment integration
|
|
128
|
+
*/
|
|
129
|
+
createBookingWithPaymentRef(
|
|
130
|
+
request: BookingRequest,
|
|
131
|
+
paymentRef: string,
|
|
132
|
+
paymentProcessor: string
|
|
133
|
+
): SchedulingResult<Booking>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get a booking by ID
|
|
137
|
+
*/
|
|
138
|
+
getBooking(bookingId: string): SchedulingResult<Booking>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Cancel a booking
|
|
142
|
+
*/
|
|
143
|
+
cancelBooking(bookingId: string, reason?: string): SchedulingResult<void>;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Reschedule a booking to a new datetime
|
|
147
|
+
*/
|
|
148
|
+
rescheduleBooking(
|
|
149
|
+
bookingId: string,
|
|
150
|
+
newDatetime: string
|
|
151
|
+
): SchedulingResult<Booking>;
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Clients
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Find or create a client
|
|
159
|
+
*/
|
|
160
|
+
findOrCreateClient(client: ClientInfo): SchedulingResult<{ id: string; isNew: boolean }>;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get client by email
|
|
164
|
+
*/
|
|
165
|
+
getClientByEmail(email: string): SchedulingResult<ClientInfo | null>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// =============================================================================
|
|
169
|
+
// ADAPTER CONFIGURATION
|
|
170
|
+
// =============================================================================
|
|
171
|
+
|
|
172
|
+
export interface AcuityAdapterConfig {
|
|
173
|
+
readonly type: 'acuity';
|
|
174
|
+
readonly userId: string;
|
|
175
|
+
readonly apiKey: string;
|
|
176
|
+
readonly baseUrl?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface CalComAdapterConfig {
|
|
180
|
+
readonly type: 'calcom';
|
|
181
|
+
readonly apiKey: string;
|
|
182
|
+
readonly baseUrl?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export type SchedulingAdapterConfig = AcuityAdapterConfig | CalComAdapterConfig;
|
|
186
|
+
|
|
187
|
+
// =============================================================================
|
|
188
|
+
// ADAPTER FACTORY
|
|
189
|
+
// =============================================================================
|
|
190
|
+
|
|
191
|
+
export type CreateSchedulingAdapter = (
|
|
192
|
+
config: SchedulingAdapterConfig
|
|
193
|
+
) => SchedulingAdapter;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for scheduling-kit
|
|
3
|
+
* Backend-agnostic scheduling with fp-ts monadic composition
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TaskEither } from 'fp-ts/TaskEither';
|
|
7
|
+
import type { ReaderTaskEither } from 'fp-ts/ReaderTaskEither';
|
|
8
|
+
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// ERROR TYPES (Discriminated Union)
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
export interface AcuityError {
|
|
14
|
+
readonly _tag: 'AcuityError';
|
|
15
|
+
readonly code: string;
|
|
16
|
+
readonly message: string;
|
|
17
|
+
readonly statusCode?: number;
|
|
18
|
+
readonly endpoint?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CalComError {
|
|
22
|
+
readonly _tag: 'CalComError';
|
|
23
|
+
readonly code: string;
|
|
24
|
+
readonly message: string;
|
|
25
|
+
readonly statusCode?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PaymentError {
|
|
29
|
+
readonly _tag: 'PaymentError';
|
|
30
|
+
readonly code: string;
|
|
31
|
+
readonly message: string;
|
|
32
|
+
readonly processor: string;
|
|
33
|
+
readonly transactionId?: string;
|
|
34
|
+
readonly recoverable: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ValidationError {
|
|
38
|
+
readonly _tag: 'ValidationError';
|
|
39
|
+
readonly field: string;
|
|
40
|
+
readonly message: string;
|
|
41
|
+
readonly value?: unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ReservationError {
|
|
45
|
+
readonly _tag: 'ReservationError';
|
|
46
|
+
readonly code: 'SLOT_TAKEN' | 'BLOCK_FAILED' | 'TIMEOUT';
|
|
47
|
+
readonly message: string;
|
|
48
|
+
readonly datetime?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface IdempotencyError {
|
|
52
|
+
readonly _tag: 'IdempotencyError';
|
|
53
|
+
readonly key: string;
|
|
54
|
+
readonly existingResult?: unknown;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface InfrastructureError {
|
|
58
|
+
readonly _tag: 'InfrastructureError';
|
|
59
|
+
readonly code: 'NETWORK' | 'TIMEOUT' | 'REDIS' | 'UNKNOWN';
|
|
60
|
+
readonly message: string;
|
|
61
|
+
readonly cause?: Error;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type SchedulingError =
|
|
65
|
+
| AcuityError
|
|
66
|
+
| CalComError
|
|
67
|
+
| PaymentError
|
|
68
|
+
| ValidationError
|
|
69
|
+
| ReservationError
|
|
70
|
+
| IdempotencyError
|
|
71
|
+
| InfrastructureError;
|
|
72
|
+
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// ERROR CONSTRUCTORS
|
|
75
|
+
// =============================================================================
|
|
76
|
+
|
|
77
|
+
export const Errors = {
|
|
78
|
+
acuity: (code: string, message: string, statusCode?: number, endpoint?: string): AcuityError => ({
|
|
79
|
+
_tag: 'AcuityError',
|
|
80
|
+
code,
|
|
81
|
+
message,
|
|
82
|
+
statusCode,
|
|
83
|
+
endpoint,
|
|
84
|
+
}),
|
|
85
|
+
|
|
86
|
+
calcom: (code: string, message: string, statusCode?: number): CalComError => ({
|
|
87
|
+
_tag: 'CalComError',
|
|
88
|
+
code,
|
|
89
|
+
message,
|
|
90
|
+
statusCode,
|
|
91
|
+
}),
|
|
92
|
+
|
|
93
|
+
payment: (
|
|
94
|
+
code: string,
|
|
95
|
+
message: string,
|
|
96
|
+
processor: string,
|
|
97
|
+
recoverable = false,
|
|
98
|
+
transactionId?: string
|
|
99
|
+
): PaymentError => ({
|
|
100
|
+
_tag: 'PaymentError',
|
|
101
|
+
code,
|
|
102
|
+
message,
|
|
103
|
+
processor,
|
|
104
|
+
recoverable,
|
|
105
|
+
transactionId,
|
|
106
|
+
}),
|
|
107
|
+
|
|
108
|
+
validation: (field: string, message: string, value?: unknown): ValidationError => ({
|
|
109
|
+
_tag: 'ValidationError',
|
|
110
|
+
field,
|
|
111
|
+
message,
|
|
112
|
+
value,
|
|
113
|
+
}),
|
|
114
|
+
|
|
115
|
+
reservation: (
|
|
116
|
+
code: 'SLOT_TAKEN' | 'BLOCK_FAILED' | 'TIMEOUT',
|
|
117
|
+
message: string,
|
|
118
|
+
datetime?: string
|
|
119
|
+
): ReservationError => ({
|
|
120
|
+
_tag: 'ReservationError',
|
|
121
|
+
code,
|
|
122
|
+
message,
|
|
123
|
+
datetime,
|
|
124
|
+
}),
|
|
125
|
+
|
|
126
|
+
idempotency: (key: string, existingResult?: unknown): IdempotencyError => ({
|
|
127
|
+
_tag: 'IdempotencyError',
|
|
128
|
+
key,
|
|
129
|
+
existingResult,
|
|
130
|
+
}),
|
|
131
|
+
|
|
132
|
+
infrastructure: (
|
|
133
|
+
code: 'NETWORK' | 'TIMEOUT' | 'REDIS' | 'UNKNOWN',
|
|
134
|
+
message: string,
|
|
135
|
+
cause?: Error
|
|
136
|
+
): InfrastructureError => ({
|
|
137
|
+
_tag: 'InfrastructureError',
|
|
138
|
+
code,
|
|
139
|
+
message,
|
|
140
|
+
cause,
|
|
141
|
+
}),
|
|
142
|
+
} as const;
|
|
143
|
+
|
|
144
|
+
// =============================================================================
|
|
145
|
+
// MONADIC TYPES
|
|
146
|
+
// =============================================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The core result type - TaskEither for async operations with typed errors
|
|
150
|
+
*/
|
|
151
|
+
export type SchedulingResult<A> = TaskEither<SchedulingError, A>;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Reader variant for dependency injection
|
|
155
|
+
*/
|
|
156
|
+
export type SchedulingReader<Env, A> = ReaderTaskEither<Env, SchedulingError, A>;
|
|
157
|
+
|
|
158
|
+
// =============================================================================
|
|
159
|
+
// DOMAIN TYPES
|
|
160
|
+
// =============================================================================
|
|
161
|
+
|
|
162
|
+
export interface Service {
|
|
163
|
+
readonly id: string;
|
|
164
|
+
readonly name: string;
|
|
165
|
+
readonly description?: string;
|
|
166
|
+
readonly duration: number; // minutes
|
|
167
|
+
readonly price: number; // cents
|
|
168
|
+
readonly currency: string;
|
|
169
|
+
readonly category?: string;
|
|
170
|
+
readonly color?: string;
|
|
171
|
+
readonly active: boolean;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface Provider {
|
|
175
|
+
readonly id: string;
|
|
176
|
+
readonly name: string;
|
|
177
|
+
readonly email?: string;
|
|
178
|
+
readonly description?: string;
|
|
179
|
+
readonly image?: string;
|
|
180
|
+
readonly timezone: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface TimeSlot {
|
|
184
|
+
readonly datetime: string; // ISO 8601
|
|
185
|
+
readonly available: boolean;
|
|
186
|
+
readonly providerId?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface AvailableDate {
|
|
190
|
+
readonly date: string; // YYYY-MM-DD
|
|
191
|
+
readonly slots: number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface ClientInfo {
|
|
195
|
+
readonly firstName: string;
|
|
196
|
+
readonly lastName: string;
|
|
197
|
+
readonly email: string;
|
|
198
|
+
readonly phone?: string;
|
|
199
|
+
readonly notes?: string;
|
|
200
|
+
readonly customFields?: Record<string, string>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface BookingRequest {
|
|
204
|
+
readonly serviceId: string;
|
|
205
|
+
readonly providerId?: string;
|
|
206
|
+
readonly datetime: string;
|
|
207
|
+
readonly client: ClientInfo;
|
|
208
|
+
readonly paymentMethod?: string;
|
|
209
|
+
readonly idempotencyKey: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface Booking {
|
|
213
|
+
readonly id: string;
|
|
214
|
+
readonly serviceId: string;
|
|
215
|
+
readonly serviceName: string;
|
|
216
|
+
readonly providerId?: string;
|
|
217
|
+
readonly providerName?: string;
|
|
218
|
+
readonly datetime: string;
|
|
219
|
+
readonly endTime: string;
|
|
220
|
+
readonly duration: number;
|
|
221
|
+
readonly price: number;
|
|
222
|
+
readonly currency: string;
|
|
223
|
+
readonly client: ClientInfo;
|
|
224
|
+
readonly status: BookingStatus;
|
|
225
|
+
readonly confirmationCode?: string;
|
|
226
|
+
readonly paymentStatus: PaymentStatus;
|
|
227
|
+
readonly paymentRef?: string;
|
|
228
|
+
readonly createdAt: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export type BookingStatus = 'confirmed' | 'pending' | 'cancelled' | 'completed' | 'no-show';
|
|
232
|
+
export type PaymentStatus = 'pending' | 'paid' | 'refunded' | 'failed';
|
|
233
|
+
|
|
234
|
+
export interface SlotReservation {
|
|
235
|
+
readonly id: string;
|
|
236
|
+
readonly datetime: string;
|
|
237
|
+
readonly duration: number;
|
|
238
|
+
readonly expiresAt: string;
|
|
239
|
+
readonly providerId?: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// =============================================================================
|
|
243
|
+
// PAYMENT TYPES
|
|
244
|
+
// =============================================================================
|
|
245
|
+
|
|
246
|
+
export interface PaymentIntent {
|
|
247
|
+
readonly id: string;
|
|
248
|
+
readonly amount: number; // cents
|
|
249
|
+
readonly currency: string;
|
|
250
|
+
readonly status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
|
|
251
|
+
readonly processor: string;
|
|
252
|
+
readonly processorTransactionId?: string;
|
|
253
|
+
readonly metadata?: Record<string, unknown>;
|
|
254
|
+
readonly createdAt: string;
|
|
255
|
+
readonly expiresAt?: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface PaymentResult {
|
|
259
|
+
readonly success: boolean;
|
|
260
|
+
readonly transactionId: string;
|
|
261
|
+
readonly processor: string;
|
|
262
|
+
readonly amount: number;
|
|
263
|
+
readonly currency: string;
|
|
264
|
+
readonly timestamp: string;
|
|
265
|
+
readonly receiptUrl?: string;
|
|
266
|
+
readonly metadata?: Record<string, unknown>;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface RefundResult {
|
|
270
|
+
readonly success: boolean;
|
|
271
|
+
readonly refundId: string;
|
|
272
|
+
readonly originalTransactionId: string;
|
|
273
|
+
readonly amount: number;
|
|
274
|
+
readonly currency: string;
|
|
275
|
+
readonly timestamp: string;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// =============================================================================
|
|
279
|
+
// CHECKOUT STATE TYPES
|
|
280
|
+
// =============================================================================
|
|
281
|
+
|
|
282
|
+
export type CheckoutStep =
|
|
283
|
+
| 'service'
|
|
284
|
+
| 'provider'
|
|
285
|
+
| 'datetime'
|
|
286
|
+
| 'details'
|
|
287
|
+
| 'payment'
|
|
288
|
+
| 'confirm'
|
|
289
|
+
| 'complete'
|
|
290
|
+
| 'error';
|
|
291
|
+
|
|
292
|
+
export interface CheckoutState {
|
|
293
|
+
readonly step: CheckoutStep;
|
|
294
|
+
readonly service?: Service;
|
|
295
|
+
readonly provider?: Provider;
|
|
296
|
+
readonly datetime?: string;
|
|
297
|
+
readonly client?: ClientInfo;
|
|
298
|
+
readonly paymentIntent?: PaymentIntent;
|
|
299
|
+
readonly paymentResult?: PaymentResult;
|
|
300
|
+
readonly booking?: Booking;
|
|
301
|
+
readonly reservation?: SlotReservation;
|
|
302
|
+
readonly error?: SchedulingError;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// =============================================================================
|
|
306
|
+
// CONFIGURATION TYPES
|
|
307
|
+
// =============================================================================
|
|
308
|
+
|
|
309
|
+
export interface SchedulingConfig {
|
|
310
|
+
readonly timezone: string;
|
|
311
|
+
readonly currency: string;
|
|
312
|
+
readonly locale: string;
|
|
313
|
+
readonly minAdvanceHours: number;
|
|
314
|
+
readonly maxAdvanceDays: number;
|
|
315
|
+
readonly slotReservationMinutes: number;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export const DEFAULT_CONFIG: SchedulingConfig = {
|
|
319
|
+
timezone: 'America/New_York',
|
|
320
|
+
currency: 'USD',
|
|
321
|
+
locale: 'en-US',
|
|
322
|
+
minAdvanceHours: 2,
|
|
323
|
+
maxAdvanceDays: 60,
|
|
324
|
+
slotReservationMinutes: 15,
|
|
325
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tummycrypt/acuity-middleware
|
|
3
|
+
*
|
|
4
|
+
* Playwright-based Acuity booking middleware server.
|
|
5
|
+
* Proxies booking operations through browser automation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Core types
|
|
9
|
+
export type {
|
|
10
|
+
Service,
|
|
11
|
+
Provider,
|
|
12
|
+
TimeSlot,
|
|
13
|
+
AvailableDate,
|
|
14
|
+
Booking,
|
|
15
|
+
BookingRequest,
|
|
16
|
+
ClientInfo,
|
|
17
|
+
SchedulingError,
|
|
18
|
+
SchedulingResult,
|
|
19
|
+
BookingStatus,
|
|
20
|
+
PaymentStatus,
|
|
21
|
+
} from './core/types.js';
|
|
22
|
+
export { Errors } from './core/types.js';
|
|
23
|
+
|
|
24
|
+
// Adapter interface
|
|
25
|
+
export type { SchedulingAdapter } from './adapters/types.js';
|
|
26
|
+
|
|
27
|
+
// Middleware exports
|
|
28
|
+
export {
|
|
29
|
+
createWizardAdapter,
|
|
30
|
+
type WizardAdapterConfig,
|
|
31
|
+
} from './middleware/acuity-wizard.js';
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
createRemoteWizardAdapter,
|
|
35
|
+
type RemoteAdapterConfig,
|
|
36
|
+
} from './middleware/remote-adapter.js';
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
BrowserService,
|
|
40
|
+
BrowserServiceLive,
|
|
41
|
+
BrowserServiceTest,
|
|
42
|
+
defaultBrowserConfig,
|
|
43
|
+
type BrowserConfig,
|
|
44
|
+
type BrowserServiceShape,
|
|
45
|
+
} from './middleware/browser-service.js';
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
BrowserError,
|
|
49
|
+
SelectorError,
|
|
50
|
+
WizardStepError,
|
|
51
|
+
CouponError,
|
|
52
|
+
toSchedulingError,
|
|
53
|
+
type MiddlewareError,
|
|
54
|
+
} from './middleware/errors.js';
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
Selectors,
|
|
58
|
+
resolveSelector,
|
|
59
|
+
resolve,
|
|
60
|
+
probeSelector,
|
|
61
|
+
probe,
|
|
62
|
+
healthCheck,
|
|
63
|
+
type SelectorKey,
|
|
64
|
+
type ResolvedSelector,
|
|
65
|
+
} from './middleware/selectors.js';
|
|
66
|
+
|
|
67
|
+
// Scraper
|
|
68
|
+
export {
|
|
69
|
+
createScraperAdapter,
|
|
70
|
+
AcuityScraper,
|
|
71
|
+
type ScraperConfig,
|
|
72
|
+
} from './adapters/acuity-scraper.js';
|
|
73
|
+
|
|
74
|
+
// Server
|
|
75
|
+
export { server } from './middleware/server.js';
|