@tummycrypt/acuity-middleware 0.1.0 → 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 (148) hide show
  1. package/dist/adapters/acuity-scraper.d.ts +8 -0
  2. package/dist/adapters/acuity-scraper.d.ts.map +1 -0
  3. package/dist/adapters/acuity-scraper.js +8 -0
  4. package/dist/adapters/acuity-scraper.js.map +1 -0
  5. package/dist/adapters/types.d.ts +8 -0
  6. package/dist/adapters/types.d.ts.map +1 -0
  7. package/dist/adapters/types.js +8 -0
  8. package/dist/adapters/types.js.map +1 -0
  9. package/dist/core/types.d.ts +10 -0
  10. package/dist/core/types.d.ts.map +1 -0
  11. package/dist/core/types.js +2 -0
  12. package/dist/core/types.js.map +1 -0
  13. package/dist/index.d.ts +17 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +18 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/middleware/acuity-wizard.d.ts +49 -0
  18. package/dist/middleware/acuity-wizard.d.ts.map +1 -0
  19. package/dist/middleware/acuity-wizard.js +265 -0
  20. package/dist/middleware/acuity-wizard.js.map +1 -0
  21. package/dist/middleware/browser-service.d.ts +53 -0
  22. package/dist/middleware/browser-service.d.ts.map +1 -0
  23. package/dist/middleware/browser-service.js +105 -0
  24. package/dist/middleware/browser-service.js.map +1 -0
  25. package/dist/middleware/errors.d.ts +58 -0
  26. package/dist/middleware/errors.d.ts.map +1 -0
  27. package/dist/middleware/errors.js +43 -0
  28. package/dist/middleware/errors.js.map +1 -0
  29. package/{src/middleware/index.ts → dist/middleware/index.d.ts} +5 -52
  30. package/dist/middleware/index.d.ts.map +1 -0
  31. package/dist/middleware/index.js +38 -0
  32. package/dist/middleware/index.js.map +1 -0
  33. package/dist/middleware/logger.d.ts +26 -0
  34. package/dist/middleware/logger.d.ts.map +1 -0
  35. package/dist/middleware/logger.js +65 -0
  36. package/dist/middleware/logger.js.map +1 -0
  37. package/dist/middleware/remote-adapter.d.ts +45 -0
  38. package/dist/middleware/remote-adapter.d.ts.map +1 -0
  39. package/dist/middleware/remote-adapter.js +178 -0
  40. package/dist/middleware/remote-adapter.js.map +1 -0
  41. package/dist/middleware/selector-health.d.ts +44 -0
  42. package/dist/middleware/selector-health.d.ts.map +1 -0
  43. package/dist/middleware/selector-health.js +144 -0
  44. package/dist/middleware/selector-health.js.map +1 -0
  45. package/dist/middleware/selectors.d.ts +108 -0
  46. package/dist/middleware/selectors.d.ts.map +1 -0
  47. package/dist/middleware/selectors.js +249 -0
  48. package/dist/middleware/selectors.js.map +1 -0
  49. package/dist/middleware/server.d.ts +34 -0
  50. package/dist/middleware/server.d.ts.map +1 -0
  51. package/dist/middleware/server.js +377 -0
  52. package/dist/middleware/server.js.map +1 -0
  53. package/dist/middleware/service-resolver.d.ts +46 -0
  54. package/dist/middleware/service-resolver.d.ts.map +1 -0
  55. package/dist/middleware/service-resolver.js +274 -0
  56. package/dist/middleware/service-resolver.js.map +1 -0
  57. package/dist/middleware/slot-parser.d.ts +29 -0
  58. package/dist/middleware/slot-parser.d.ts.map +1 -0
  59. package/dist/middleware/slot-parser.js +50 -0
  60. package/dist/middleware/slot-parser.js.map +1 -0
  61. package/dist/middleware/steps/__tests__/fixtures.d.ts +14 -0
  62. package/dist/middleware/steps/__tests__/fixtures.d.ts.map +1 -0
  63. package/dist/middleware/steps/__tests__/fixtures.js +204 -0
  64. package/dist/middleware/steps/__tests__/fixtures.js.map +1 -0
  65. package/dist/middleware/steps/bypass-payment.d.ts +54 -0
  66. package/dist/middleware/steps/bypass-payment.d.ts.map +1 -0
  67. package/dist/middleware/steps/bypass-payment.js +164 -0
  68. package/dist/middleware/steps/bypass-payment.js.map +1 -0
  69. package/dist/middleware/steps/extract-business.d.ts +93 -0
  70. package/dist/middleware/steps/extract-business.d.ts.map +1 -0
  71. package/dist/middleware/steps/extract-business.js +170 -0
  72. package/dist/middleware/steps/extract-business.js.map +1 -0
  73. package/dist/middleware/steps/extract.d.ts +41 -0
  74. package/dist/middleware/steps/extract.d.ts.map +1 -0
  75. package/dist/middleware/steps/extract.js +128 -0
  76. package/dist/middleware/steps/extract.js.map +1 -0
  77. package/dist/middleware/steps/fill-form.d.ts +45 -0
  78. package/dist/middleware/steps/fill-form.d.ts.map +1 -0
  79. package/dist/middleware/steps/fill-form.js +262 -0
  80. package/dist/middleware/steps/fill-form.js.map +1 -0
  81. package/dist/middleware/steps/index.d.ts +12 -0
  82. package/dist/middleware/steps/index.d.ts.map +1 -0
  83. package/dist/middleware/steps/index.js +12 -0
  84. package/dist/middleware/steps/index.js.map +1 -0
  85. package/dist/middleware/steps/navigate.d.ts +51 -0
  86. package/dist/middleware/steps/navigate.d.ts.map +1 -0
  87. package/dist/middleware/steps/navigate.js +391 -0
  88. package/dist/middleware/steps/navigate.js.map +1 -0
  89. package/dist/middleware/steps/read-availability.d.ts +37 -0
  90. package/dist/middleware/steps/read-availability.d.ts.map +1 -0
  91. package/dist/middleware/steps/read-availability.js +298 -0
  92. package/dist/middleware/steps/read-availability.js.map +1 -0
  93. package/dist/middleware/steps/read-slots.d.ts +33 -0
  94. package/dist/middleware/steps/read-slots.d.ts.map +1 -0
  95. package/dist/middleware/steps/read-slots.js +295 -0
  96. package/dist/middleware/steps/read-slots.js.map +1 -0
  97. package/dist/middleware/steps/read-via-url.d.ts +39 -0
  98. package/dist/middleware/steps/read-via-url.d.ts.map +1 -0
  99. package/dist/middleware/steps/read-via-url.js +141 -0
  100. package/dist/middleware/steps/read-via-url.js.map +1 -0
  101. package/dist/middleware/steps/submit.d.ts +22 -0
  102. package/dist/middleware/steps/submit.d.ts.map +1 -0
  103. package/dist/middleware/steps/submit.js +112 -0
  104. package/dist/middleware/steps/submit.js.map +1 -0
  105. package/dist/middleware/wizard-calendar.d.ts +37 -0
  106. package/dist/middleware/wizard-calendar.d.ts.map +1 -0
  107. package/dist/middleware/wizard-calendar.js +177 -0
  108. package/dist/middleware/wizard-calendar.js.map +1 -0
  109. package/dist/middleware/wizard-service.d.ts +30 -0
  110. package/dist/middleware/wizard-service.d.ts.map +1 -0
  111. package/dist/middleware/wizard-service.js +89 -0
  112. package/dist/middleware/wizard-service.js.map +1 -0
  113. package/dist/server.d.ts +6 -0
  114. package/dist/server.d.ts.map +1 -0
  115. package/{src/server.ts → dist/server.js} +1 -0
  116. package/dist/server.js.map +1 -0
  117. package/package.json +16 -4
  118. package/.github/workflows/build-paper.yml +0 -39
  119. package/.github/workflows/ci.yml +0 -37
  120. package/Dockerfile +0 -53
  121. package/docs/blog-post.mdx +0 -240
  122. package/docs/paper/IEEEtran.bst +0 -2409
  123. package/docs/paper/IEEEtran.cls +0 -6347
  124. package/docs/paper/acuity-middleware-paper.tex +0 -375
  125. package/docs/paper/balance.sty +0 -87
  126. package/docs/paper/references.bib +0 -231
  127. package/docs/paper.md +0 -400
  128. package/flake.nix +0 -32
  129. package/modal-app.py +0 -82
  130. package/src/adapters/acuity-scraper.ts +0 -543
  131. package/src/adapters/types.ts +0 -193
  132. package/src/core/types.ts +0 -325
  133. package/src/index.ts +0 -75
  134. package/src/middleware/acuity-wizard.ts +0 -456
  135. package/src/middleware/browser-service.ts +0 -183
  136. package/src/middleware/errors.ts +0 -70
  137. package/src/middleware/remote-adapter.ts +0 -246
  138. package/src/middleware/selectors.ts +0 -308
  139. package/src/middleware/server.ts +0 -372
  140. package/src/middleware/steps/bypass-payment.ts +0 -226
  141. package/src/middleware/steps/extract.ts +0 -174
  142. package/src/middleware/steps/fill-form.ts +0 -359
  143. package/src/middleware/steps/index.ts +0 -27
  144. package/src/middleware/steps/navigate.ts +0 -537
  145. package/src/middleware/steps/read-availability.ts +0 -399
  146. package/src/middleware/steps/read-slots.ts +0 -405
  147. package/src/middleware/steps/submit.ts +0 -168
  148. package/tsconfig.json +0 -25
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Middleware HTTP Server
3
+ *
4
+ * Standalone Node.js HTTP server wrapping the Effect TS wizard programs.
5
+ * Designed to run inside a Docker container with Playwright + Chromium
6
+ * on Modal Labs, Fly.io, or any host.
7
+ *
8
+ * Endpoints:
9
+ * GET /health - Health check
10
+ * GET /services - List services (scraper)
11
+ * GET /services/:id - Get service by ID
12
+ * POST /availability/dates - Available dates for a service
13
+ * POST /availability/slots - Time slots for a date
14
+ * POST /availability/check - Check if a slot is available
15
+ * POST /booking/create - Create booking (standard)
16
+ * POST /booking/create-with-payment - Create booking with payment ref (coupon bypass)
17
+ *
18
+ * Environment variables:
19
+ * PORT - Server port (default: 3001)
20
+ * ACUITY_BASE_URL - Acuity scheduling URL
21
+ * ACUITY_BYPASS_COUPON - 100% coupon code
22
+ * AUTH_TOKEN - Required Bearer token for all endpoints
23
+ * PLAYWRIGHT_HEADLESS - Browser headless mode (default: true)
24
+ * PLAYWRIGHT_TIMEOUT - Page timeout in ms (default: 30000)
25
+ *
26
+ * Usage:
27
+ * node --import tsx/esm src/middleware/server.ts
28
+ * # or after build:
29
+ * node dist/middleware/server.js
30
+ */
31
+ import { createServer } from 'node:http';
32
+ import { Effect, Exit, Cause } from 'effect';
33
+ import { createScraperAdapter } from '../adapters/acuity-scraper.js';
34
+ import { BrowserServiceLive, defaultBrowserConfig } from './browser-service.js';
35
+ import { toSchedulingError } from './errors.js';
36
+ import { navigateToBooking, fillFormFields, bypassPayment, submitBooking, extractConfirmation, toBooking, readAvailableDates, readTimeSlots, fetchBusinessData, businessToServices, } from './steps/index.js';
37
+ // =============================================================================
38
+ // CONFIGURATION
39
+ // =============================================================================
40
+ const PORT = Number(process.env.PORT ?? 3001);
41
+ const AUTH_TOKEN = process.env.AUTH_TOKEN;
42
+ const ACUITY_BASE_URL = process.env.ACUITY_BASE_URL ?? 'https://MassageIthaca.as.me';
43
+ const COUPON_CODE = process.env.ACUITY_BYPASS_COUPON;
44
+ const browserConfig = {
45
+ ...defaultBrowserConfig,
46
+ baseUrl: ACUITY_BASE_URL,
47
+ headless: process.env.PLAYWRIGHT_HEADLESS !== 'false',
48
+ timeout: Number(process.env.PLAYWRIGHT_TIMEOUT ?? 30000),
49
+ executablePath: process.env.CHROMIUM_EXECUTABLE_PATH,
50
+ launchArgs: process.env.CHROMIUM_LAUNCH_ARGS?.split(','),
51
+ };
52
+ const scraperConfig = {
53
+ baseUrl: ACUITY_BASE_URL,
54
+ headless: browserConfig.headless,
55
+ timeout: browserConfig.timeout,
56
+ userAgent: browserConfig.userAgent,
57
+ executablePath: browserConfig.executablePath,
58
+ launchArgs: browserConfig.launchArgs ? [...browserConfig.launchArgs] : undefined,
59
+ };
60
+ const sendJson = (res, status, body) => {
61
+ res.writeHead(status, { 'Content-Type': 'application/json' });
62
+ res.end(JSON.stringify(body));
63
+ };
64
+ const sendSuccess = (res, data) => sendJson(res, 200, { success: true, data });
65
+ const sendError = (res, status, err) => sendJson(res, status, {
66
+ success: false,
67
+ error: {
68
+ tag: err._tag,
69
+ code: 'code' in err ? err.code : err._tag,
70
+ message: 'message' in err ? err.message : 'Unknown error',
71
+ },
72
+ });
73
+ const parseBody = async (req) => {
74
+ const chunks = [];
75
+ for await (const chunk of req) {
76
+ chunks.push(chunk);
77
+ }
78
+ const raw = Buffer.concat(chunks).toString('utf8');
79
+ return raw ? JSON.parse(raw) : {};
80
+ };
81
+ // =============================================================================
82
+ // EFFECT RUNNER
83
+ // =============================================================================
84
+ const layer = BrowserServiceLive(browserConfig);
85
+ const runEffect = async (effect) => {
86
+ const exit = await Effect.runPromiseExit(Effect.scoped(effect.pipe(Effect.provide(layer))));
87
+ if (Exit.isSuccess(exit)) {
88
+ return { ok: true, value: exit.value };
89
+ }
90
+ const failure = Cause.failureOption(exit.cause);
91
+ if (failure._tag === 'Some') {
92
+ return { ok: false, error: toSchedulingError(failure.value) };
93
+ }
94
+ return { ok: false, error: { _tag: 'InfrastructureError', code: 'UNKNOWN', message: Cause.pretty(exit.cause) } };
95
+ };
96
+ /** Run a SchedulingResult (Effect) and return Result */
97
+ const runSchedulingEffect = async (effect) => {
98
+ const exit = await Effect.runPromiseExit(effect);
99
+ if (Exit.isSuccess(exit)) {
100
+ return { ok: true, value: exit.value };
101
+ }
102
+ const failure = Cause.failureOption(exit.cause);
103
+ if (failure._tag === 'Some') {
104
+ return { ok: false, error: failure.value };
105
+ }
106
+ return { ok: false, error: { _tag: 'InfrastructureError', code: 'UNKNOWN', message: Cause.pretty(exit.cause) } };
107
+ };
108
+ // =============================================================================
109
+ // SCRAPER (cached)
110
+ // =============================================================================
111
+ let scraper = null;
112
+ const getScraper = () => {
113
+ if (!scraper) {
114
+ scraper = createScraperAdapter(scraperConfig);
115
+ }
116
+ return scraper;
117
+ };
118
+ let cachedServices = null;
119
+ // Static services from SERVICES_JSON env var (avoids scraper dependency)
120
+ const STATIC_SERVICES = (() => {
121
+ const raw = process.env.SERVICES_JSON;
122
+ if (!raw)
123
+ return null;
124
+ try {
125
+ return JSON.parse(raw);
126
+ }
127
+ catch (e) {
128
+ console.error('[middleware-server] Failed to parse SERVICES_JSON:', e);
129
+ return null;
130
+ }
131
+ })();
132
+ if (STATIC_SERVICES) {
133
+ cachedServices = STATIC_SERVICES;
134
+ console.log(`[middleware-server] Loaded ${STATIC_SERVICES.length} static services from SERVICES_JSON`);
135
+ }
136
+ // =============================================================================
137
+ // ROUTE HANDLERS
138
+ // =============================================================================
139
+ const handleHealth = (_req, res) => {
140
+ sendSuccess(res, {
141
+ status: 'ok',
142
+ baseUrl: ACUITY_BASE_URL,
143
+ hasCoupon: !!COUPON_CODE,
144
+ headless: browserConfig.headless,
145
+ staticServices: STATIC_SERVICES ? STATIC_SERVICES.length : 0,
146
+ timestamp: new Date().toISOString(),
147
+ });
148
+ };
149
+ const handleGetServices = async (_req, res) => {
150
+ // 1. Prefer static services from env (always reliable, no network needed)
151
+ if (STATIC_SERVICES) {
152
+ console.log('[services] source: SERVICES_JSON env');
153
+ cachedServices = STATIC_SERVICES;
154
+ return sendSuccess(res, STATIC_SERVICES);
155
+ }
156
+ // 2. Extract from BUSINESS object (HTTP fetch + regex, no browser)
157
+ try {
158
+ const business = await fetchBusinessData(ACUITY_BASE_URL);
159
+ if (business) {
160
+ const services = businessToServices(business);
161
+ if (services.length > 0) {
162
+ console.log(`[services] source: BUSINESS object (${services.length} services)`);
163
+ cachedServices = services;
164
+ return sendSuccess(res, services);
165
+ }
166
+ console.warn('[services] BUSINESS object found but 0 active services');
167
+ }
168
+ }
169
+ catch (e) {
170
+ console.warn('[services] BUSINESS extraction failed:', e instanceof Error ? e.message : e);
171
+ }
172
+ // 3. Deprecated fallback: DOM scraper (may return [] — Acuity is React SPA)
173
+ console.warn('[services] falling back to DOM scraper (deprecated)');
174
+ const result = await runSchedulingEffect(getScraper().getServices());
175
+ if (!result.ok)
176
+ return sendError(res, 500, result.error);
177
+ if (result.value.length === 0) {
178
+ console.error('[services] all sources exhausted — returning empty []');
179
+ }
180
+ cachedServices = result.value;
181
+ sendSuccess(res, result.value);
182
+ };
183
+ const handleGetService = async (serviceId, res) => {
184
+ if (!cachedServices) {
185
+ const result = await runSchedulingEffect(getScraper().getServices());
186
+ if (!result.ok)
187
+ return sendError(res, 500, result.error);
188
+ cachedServices = result.value;
189
+ }
190
+ const found = cachedServices.find((s) => s.id === serviceId);
191
+ if (!found) {
192
+ return sendJson(res, 404, {
193
+ success: false,
194
+ error: { tag: 'AcuityError', code: 'NOT_FOUND', message: `Service ${serviceId} not found` },
195
+ });
196
+ }
197
+ sendSuccess(res, found);
198
+ };
199
+ const handleAvailableDates = async (req, res) => {
200
+ const body = (await parseBody(req));
201
+ // Use service name for wizard navigation (click-based, not URL param)
202
+ const serviceName = body.serviceName ?? cachedServices?.find((s) => s.id === body.serviceId)?.name ?? body.serviceId;
203
+ console.log(`[availability/dates] serviceName="${serviceName}" from serviceId="${body.serviceId}"`);
204
+ const result = await runEffect(readAvailableDates({
205
+ serviceName,
206
+ targetMonth: body.startDate?.slice(0, 7),
207
+ monthsToScan: 2,
208
+ }));
209
+ if (!result.ok) {
210
+ const err = result.error;
211
+ console.error(`[availability/dates] error:`, err);
212
+ return sendJson(res, 500, {
213
+ success: false,
214
+ error: { tag: err._tag ?? 'InfrastructureError', code: 'code' in err ? err.code : 'UNKNOWN', message: 'message' in err ? err.message : 'Availability lookup failed' },
215
+ });
216
+ }
217
+ sendSuccess(res, result.value);
218
+ };
219
+ const handleAvailableSlots = async (req, res) => {
220
+ const body = (await parseBody(req));
221
+ const serviceName = body.serviceName ?? cachedServices?.find((s) => s.id === body.serviceId)?.name ?? body.serviceId;
222
+ console.log(`[availability/slots] serviceName="${serviceName}" date="${body.date}"`);
223
+ const result = await runEffect(readTimeSlots({
224
+ serviceName,
225
+ date: body.date,
226
+ }));
227
+ if (!result.ok) {
228
+ const err = result.error;
229
+ console.error(`[availability/slots] error:`, err);
230
+ return sendJson(res, 500, {
231
+ success: false,
232
+ error: { tag: err._tag ?? 'InfrastructureError', code: 'code' in err ? err.code : 'UNKNOWN', message: 'message' in err ? err.message : 'Slot lookup failed' },
233
+ });
234
+ }
235
+ sendSuccess(res, result.value);
236
+ };
237
+ const handleCheckSlot = async (req, res) => {
238
+ const body = (await parseBody(req));
239
+ const date = body.datetime.split('T')[0];
240
+ const serviceName = body.serviceName ?? cachedServices?.find((s) => s.id === body.serviceId)?.name ?? body.serviceId;
241
+ const result = await runEffect(readTimeSlots({
242
+ serviceName,
243
+ date,
244
+ }));
245
+ if (!result.ok) {
246
+ const err = result.error;
247
+ return sendJson(res, 500, {
248
+ success: false,
249
+ error: { tag: err._tag ?? 'InfrastructureError', code: 'code' in err ? err.code : 'UNKNOWN', message: 'message' in err ? err.message : 'Slot check failed' },
250
+ });
251
+ }
252
+ const available = result.value.some((s) => s.datetime === body.datetime && s.available);
253
+ sendSuccess(res, available);
254
+ };
255
+ const handleCreateBooking = async (req, res) => {
256
+ const body = (await parseBody(req));
257
+ const { request } = body;
258
+ const serviceName = cachedServices?.find((s) => s.id === request.serviceId)?.name;
259
+ const result = await runEffect(Effect.gen(function* () {
260
+ yield* navigateToBooking({
261
+ serviceName: serviceName ?? request.serviceId,
262
+ datetime: request.datetime,
263
+ client: request.client,
264
+ appointmentTypeId: request.serviceId,
265
+ });
266
+ yield* fillFormFields({ client: request.client, customFields: request.client.customFields });
267
+ yield* submitBooking();
268
+ const confirmation = yield* extractConfirmation();
269
+ return toBooking(confirmation, request, '', 'acuity');
270
+ }));
271
+ if (!result.ok)
272
+ return sendError(res, 500, result.error);
273
+ sendSuccess(res, result.value);
274
+ };
275
+ const handleCreateBookingWithPayment = async (req, res) => {
276
+ const body = (await parseBody(req));
277
+ const { request, paymentRef, paymentProcessor } = body;
278
+ const coupon = body.couponCode ?? COUPON_CODE;
279
+ if (!coupon) {
280
+ return sendJson(res, 400, {
281
+ success: false,
282
+ error: { tag: 'ValidationError', code: 'couponCode', message: 'Coupon code is required for payment bypass' },
283
+ });
284
+ }
285
+ // Try to get service details for richer booking data
286
+ const service = cachedServices?.find((s) => s.id === request.serviceId);
287
+ const serviceName = service?.name ?? request.serviceId;
288
+ const result = await runEffect(Effect.gen(function* () {
289
+ yield* navigateToBooking({
290
+ serviceName,
291
+ datetime: request.datetime,
292
+ client: request.client,
293
+ appointmentTypeId: request.serviceId,
294
+ });
295
+ yield* fillFormFields({ client: request.client, customFields: request.client.customFields });
296
+ yield* bypassPayment(coupon);
297
+ yield* submitBooking();
298
+ const confirmation = yield* extractConfirmation();
299
+ return toBooking(confirmation, request, paymentRef, paymentProcessor, service ? { name: service.name, duration: service.duration, price: service.price, currency: service.currency } : undefined);
300
+ }));
301
+ if (!result.ok)
302
+ return sendError(res, 500, result.error);
303
+ sendSuccess(res, result.value);
304
+ };
305
+ // =============================================================================
306
+ // SERVER
307
+ // =============================================================================
308
+ const server = createServer(async (req, res) => {
309
+ const url = new URL(req.url ?? '/', `http://localhost:${PORT}`);
310
+ const path = url.pathname;
311
+ const method = req.method?.toUpperCase() ?? 'GET';
312
+ // Auth check (skip health endpoint)
313
+ if (AUTH_TOKEN && path !== '/health') {
314
+ const auth = req.headers.authorization;
315
+ if (auth !== `Bearer ${AUTH_TOKEN}`) {
316
+ return sendJson(res, 401, {
317
+ success: false,
318
+ error: { tag: 'InfrastructureError', code: 'UNAUTHORIZED', message: 'Invalid auth token' },
319
+ });
320
+ }
321
+ }
322
+ try {
323
+ // Route matching
324
+ if (path === '/health' && method === 'GET') {
325
+ return handleHealth(req, res);
326
+ }
327
+ if (path === '/services' && method === 'GET') {
328
+ return await handleGetServices(req, res);
329
+ }
330
+ if (path.startsWith('/services/') && method === 'GET') {
331
+ const serviceId = decodeURIComponent(path.slice('/services/'.length));
332
+ return await handleGetService(serviceId, res);
333
+ }
334
+ if (path === '/availability/dates' && method === 'POST') {
335
+ return await handleAvailableDates(req, res);
336
+ }
337
+ if (path === '/availability/slots' && method === 'POST') {
338
+ return await handleAvailableSlots(req, res);
339
+ }
340
+ if (path === '/availability/check' && method === 'POST') {
341
+ return await handleCheckSlot(req, res);
342
+ }
343
+ if (path === '/booking/create' && method === 'POST') {
344
+ return await handleCreateBooking(req, res);
345
+ }
346
+ if (path === '/booking/create-with-payment' && method === 'POST') {
347
+ return await handleCreateBookingWithPayment(req, res);
348
+ }
349
+ sendJson(res, 404, {
350
+ success: false,
351
+ error: { tag: 'InfrastructureError', code: 'NOT_FOUND', message: `Unknown route: ${method} ${path}` },
352
+ });
353
+ }
354
+ catch (e) {
355
+ console.error(`[middleware-server] Unhandled error on ${method} ${path}:`, e);
356
+ sendJson(res, 500, {
357
+ success: false,
358
+ error: {
359
+ tag: 'InfrastructureError',
360
+ code: 'UNKNOWN',
361
+ message: e instanceof Error ? e.message : 'Internal server error',
362
+ },
363
+ });
364
+ }
365
+ });
366
+ // Only start listening when this file is executed directly (not imported)
367
+ if (process.argv[1]?.match(/server\.(ts|js|mjs)$/)) {
368
+ server.listen(PORT, '0.0.0.0', () => {
369
+ console.log(`[middleware-server] Listening on port ${PORT}`);
370
+ console.log(`[middleware-server] Acuity URL: ${ACUITY_BASE_URL}`);
371
+ console.log(`[middleware-server] Coupon: ${COUPON_CODE ? 'configured' : 'NOT SET'}`);
372
+ console.log(`[middleware-server] Auth: ${AUTH_TOKEN ? 'enabled' : 'disabled'}`);
373
+ console.log(`[middleware-server] Headless: ${browserConfig.headless}`);
374
+ });
375
+ }
376
+ export { server };
377
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/middleware/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ServiceResolver — Multi-Strategy Service Name Matching
3
+ *
4
+ * Effect Context.Tag providing resilient service resolution with
5
+ * cascading fallback strategies and confidence scoring.
6
+ *
7
+ * Strategies (tried in order via Effect.orElse):
8
+ * 1. ID match (confidence 1.0) — match by Acuity numeric ID in BUSINESS object
9
+ * 2. Normalized (confidence 0.95) — strip punctuation, collapse whitespace, exact match
10
+ * 3. Token overlap (0.5-0.9) — word-level intersection scoring
11
+ * 4. Fuzzy/Levenshtein (0.3-0.7) — edit-distance based matching
12
+ */
13
+ import { Context, Effect, Layer } from 'effect';
14
+ import type { Page, ElementHandle } from 'playwright-core';
15
+ import { ServiceResolverError } from './errors.js';
16
+ export interface ServiceResolution {
17
+ /** The matched DOM element (the .select-item container) */
18
+ readonly element: ElementHandle;
19
+ /** Confidence score 0-1 */
20
+ readonly confidence: number;
21
+ /** Which strategy produced the match */
22
+ readonly strategy: 'id-match' | 'normalized-exact' | 'token-overlap' | 'fuzzy';
23
+ /** The name as it appears on the page */
24
+ readonly matchedName: string;
25
+ }
26
+ export interface ServiceResolverShape {
27
+ readonly resolve: (page: Page, serviceName: string, appointmentTypeId?: string) => Effect.Effect<ServiceResolution, ServiceResolverError>;
28
+ }
29
+ declare const ServiceResolver_base: Context.TagClass<ServiceResolver, "scheduling-kit/ServiceResolver", ServiceResolverShape>;
30
+ export declare class ServiceResolver extends ServiceResolver_base {
31
+ }
32
+ /** Normalize a string: lowercase, strip non-alphanumeric (keep spaces), collapse whitespace. */
33
+ export declare const normalize: (s: string) => string;
34
+ /** Token overlap score: |intersection| / max(|a|, |b|). */
35
+ export declare const tokenOverlap: (a: string, b: string) => number;
36
+ /** Levenshtein edit distance between two strings. */
37
+ export declare const levenshtein: (a: string, b: string) => number;
38
+ /** Fuzzy match confidence: 1 - (distance / maxLen). */
39
+ export declare const fuzzyConfidence: (a: string, b: string) => number;
40
+ export declare const ServiceResolverLive: Layer.Layer<ServiceResolver>;
41
+ /**
42
+ * A static ServiceResolver for tests that always returns a mock resolution.
43
+ */
44
+ export declare const ServiceResolverTest: (mockResolution?: Partial<ServiceResolution>) => Layer.Layer<ServiceResolver>;
45
+ export {};
46
+ //# sourceMappingURL=service-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-resolver.d.ts","sourceRoot":"","sources":["../../src/middleware/service-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAQnD,MAAM,WAAW,iBAAiB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,2BAA2B;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,kBAAkB,GAAG,eAAe,GAAG,OAAO,CAAC;IAC/E,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,CACjB,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,iBAAiB,CAAC,EAAE,MAAM,KACtB,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,CAAC;CAC5D;;AAMD,qBAAa,eAAgB,SAAQ,oBAGlC;CAAG;AAMN,gGAAgG;AAChG,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,MAC8B,CAAC;AAMrE,2DAA2D;AAC3D,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAWnD,CAAC;AAEF,qDAAqD;AACrD,eAAO,MAAM,WAAW,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MA2BlD,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,eAAe,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAQtD,CAAC;AAmMF,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,KAAK,CAAC,eAAe,CAyE5D,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC/B,iBAAiB,OAAO,CAAC,iBAAiB,CAAC,KACzC,KAAK,CAAC,KAAK,CAAC,eAAe,CAU3B,CAAC"}