@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,8 @@
1
+ /**
2
+ * Re-export scraper from @tummycrypt/scheduling-kit.
3
+ *
4
+ * @deprecated — see scheduling-kit's acuity-scraper.ts for deprecation details.
5
+ * Prefer extract-business.ts (BUSINESS object) and wizard steps (readAvailableDates, readTimeSlots).
6
+ */
7
+ export { AcuityScraper, createScraperAdapter, scrapeServicesOnce, scrapeAvailabilityOnce, type ScraperConfig, type ScrapedService, type ScrapedAvailability, type ScrapedTimeSlot, } from '@tummycrypt/scheduling-kit/adapters';
8
+ //# sourceMappingURL=acuity-scraper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acuity-scraper.d.ts","sourceRoot":"","sources":["../../src/adapters/acuity-scraper.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,qCAAqC,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Re-export scraper from @tummycrypt/scheduling-kit.
3
+ *
4
+ * @deprecated — see scheduling-kit's acuity-scraper.ts for deprecation details.
5
+ * Prefer extract-business.ts (BUSINESS object) and wizard steps (readAvailableDates, readTimeSlots).
6
+ */
7
+ export { AcuityScraper, createScraperAdapter, scrapeServicesOnce, scrapeAvailabilityOnce, } from '@tummycrypt/scheduling-kit/adapters';
8
+ //# sourceMappingURL=acuity-scraper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acuity-scraper.js","sourceRoot":"","sources":["../../src/adapters/acuity-scraper.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,GAKvB,MAAM,qCAAqC,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Re-export adapter types from @tummycrypt/scheduling-kit.
3
+ *
4
+ * Previously this file was a full copy of scheduling-kit's adapter interface.
5
+ * Consolidated to prevent type drift.
6
+ */
7
+ export * from '@tummycrypt/scheduling-kit/adapters';
8
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,qCAAqC,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Re-export adapter types from @tummycrypt/scheduling-kit.
3
+ *
4
+ * Previously this file was a full copy of scheduling-kit's adapter interface.
5
+ * Consolidated to prevent type drift.
6
+ */
7
+ export * from '@tummycrypt/scheduling-kit/adapters';
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,qCAAqC,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Re-export core types from @tummycrypt/scheduling-kit.
3
+ *
4
+ * We selectively re-export only the types module (not utils/pipelines)
5
+ * to avoid pulling in fp-ts runtime code that has ESM import issues
6
+ * in the published package.
7
+ */
8
+ export type { AcuityError, CalComError, PaymentError, ValidationError, ReservationError, IdempotencyError, InfrastructureError, SchedulingError, SchedulingResult, SchedulingReader, Service, Provider, TimeSlot, AvailableDate, ClientInfo, BookingRequest, Booking, BookingStatus, PaymentStatus, SlotReservation, PaymentIntent, PaymentResult, RefundResult, SchedulingConfig, } from '@tummycrypt/scheduling-kit/core';
9
+ export { Errors } from '@tummycrypt/scheduling-kit/core';
10
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,YAAY,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,UAAU,EACV,cAAc,EACd,OAAO,EACP,aAAa,EACb,aAAa,EACb,eAAe,EACf,aAAa,EACb,aAAa,EACb,YAAY,EACZ,gBAAgB,GACjB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { Errors } from '@tummycrypt/scheduling-kit/core';
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @tummycrypt/acuity-middleware
3
+ *
4
+ * Playwright-based Acuity booking middleware server.
5
+ * Proxies booking operations through browser automation.
6
+ */
7
+ export type { Service, Provider, TimeSlot, AvailableDate, Booking, BookingRequest, ClientInfo, SchedulingError, SchedulingResult, BookingStatus, PaymentStatus, } from './core/types.js';
8
+ export { Errors } from './core/types.js';
9
+ export type { SchedulingAdapter } from './adapters/types.js';
10
+ export { createWizardAdapter, type WizardAdapterConfig, } from './middleware/acuity-wizard.js';
11
+ export { createRemoteWizardAdapter, type RemoteAdapterConfig, } from './middleware/remote-adapter.js';
12
+ export { BrowserService, BrowserServiceLive, BrowserServiceTest, defaultBrowserConfig, type BrowserConfig, type BrowserServiceShape, } from './middleware/browser-service.js';
13
+ export { BrowserError, SelectorError, WizardStepError, CouponError, toSchedulingError, type MiddlewareError, } from './middleware/errors.js';
14
+ export { Selectors, resolveSelector, resolve, probeSelector, probe, healthCheck, type SelectorKey, type ResolvedSelector, } from './middleware/selectors.js';
15
+ export { createScraperAdapter, AcuityScraper, type ScraperConfig, } from './adapters/acuity-scraper.js';
16
+ export { server } from './middleware/server.js';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACX,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,OAAO,EACP,cAAc,EACd,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,GACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAGzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAG7D,OAAO,EACN,mBAAmB,EACnB,KAAK,mBAAmB,GACxB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACN,yBAAyB,EACzB,KAAK,mBAAmB,GACxB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACN,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,mBAAmB,GACxB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACN,YAAY,EACZ,aAAa,EACb,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,KAAK,eAAe,GACpB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACN,SAAS,EACT,eAAe,EACf,OAAO,EACP,aAAa,EACb,KAAK,EACL,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACrB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACN,oBAAoB,EACpB,aAAa,EACb,KAAK,aAAa,GAClB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @tummycrypt/acuity-middleware
3
+ *
4
+ * Playwright-based Acuity booking middleware server.
5
+ * Proxies booking operations through browser automation.
6
+ */
7
+ export { Errors } from './core/types.js';
8
+ // Middleware exports
9
+ export { createWizardAdapter, } from './middleware/acuity-wizard.js';
10
+ export { createRemoteWizardAdapter, } from './middleware/remote-adapter.js';
11
+ export { BrowserService, BrowserServiceLive, BrowserServiceTest, defaultBrowserConfig, } from './middleware/browser-service.js';
12
+ export { BrowserError, SelectorError, WizardStepError, CouponError, toSchedulingError, } from './middleware/errors.js';
13
+ export { Selectors, resolveSelector, resolve, probeSelector, probe, healthCheck, } from './middleware/selectors.js';
14
+ // Scraper
15
+ export { createScraperAdapter, AcuityScraper, } from './adapters/acuity-scraper.js';
16
+ // Server
17
+ export { server } from './middleware/server.js';
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAKzC,qBAAqB;AACrB,OAAO,EACN,mBAAmB,GAEnB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACN,yBAAyB,GAEzB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACN,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,GAGpB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACN,YAAY,EACZ,aAAa,EACb,eAAe,EACf,WAAW,EACX,iBAAiB,GAEjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACN,SAAS,EACT,eAAe,EACf,OAAO,EACP,aAAa,EACb,KAAK,EACL,WAAW,GAGX,MAAM,2BAA2B,CAAC;AAEnC,UAAU;AACV,OAAO,EACN,oBAAoB,EACpB,aAAa,GAEb,MAAM,8BAA8B,CAAC;AAEtC,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Acuity Wizard Adapter
3
+ *
4
+ * Full SchedulingAdapter implementation that uses Effect TS middleware
5
+ * to puppeteer the Acuity booking wizard.
6
+ *
7
+ * Read operations:
8
+ * - getServices: returns static service catalog (if provided) or falls back to scraper
9
+ * - getAvailableDates: Effect program navigates wizard calendar (verified selectors)
10
+ * - getAvailableSlots: Effect program clicks day tile and reads time buttons
11
+ *
12
+ * Write operations:
13
+ * - createBooking/createBookingWithPaymentRef: Effect TS middleware via Playwright
14
+ *
15
+ * This is the bridge between Effect TS (middleware layer) and
16
+ * fp-ts (pipeline/adapter layer).
17
+ */
18
+ import type { SchedulingAdapter } from '../adapters/types.js';
19
+ import type { Service } from '../core/types.js';
20
+ import { type BrowserConfig } from './browser-service.js';
21
+ import { type RemoteAdapterConfig } from './remote-adapter.js';
22
+ export interface WizardAdapterConfig extends Partial<BrowserConfig> {
23
+ /** Base URL for the Acuity scheduling page */
24
+ baseUrl: string;
25
+ /** Pre-configured coupon code for payment bypass (from ACUITY_BYPASS_COUPON env) */
26
+ couponCode?: string;
27
+ /**
28
+ * Execution mode:
29
+ * - 'local': Run Playwright in-process (default, requires Chromium installed)
30
+ * - 'remote': Proxy all operations to a remote middleware server via HTTP
31
+ */
32
+ mode?: 'local' | 'remote';
33
+ /** Remote adapter config (required when mode is 'remote') */
34
+ remote?: RemoteAdapterConfig;
35
+ /**
36
+ * Static service catalog. If provided, getServices() returns this list
37
+ * without launching a browser. Recommended for known service sets to avoid
38
+ * expensive browser launches for read-only operations.
39
+ */
40
+ services?: readonly Service[];
41
+ }
42
+ /**
43
+ * Create a full SchedulingAdapter that puppeteers the Acuity wizard.
44
+ *
45
+ * Read operations delegate to the existing scraper.
46
+ * Write operations use Effect TS middleware via Playwright.
47
+ */
48
+ export declare const createWizardAdapter: (config: WizardAdapterConfig) => SchedulingAdapter;
49
+ //# sourceMappingURL=acuity-wizard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acuity-wizard.d.ts","sourceRoot":"","sources":["../../src/middleware/acuity-wizard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,KAAK,EAGX,OAAO,EAEP,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAsB,KAAK,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAEpG,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAmB1F,MAAM,WAAW,mBAAoB,SAAQ,OAAO,CAAC,aAAa,CAAC;IAClE,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC9B;AAgHD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ,mBAAmB,KAAG,iBAuPjE,CAAC"}
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Acuity Wizard Adapter
3
+ *
4
+ * Full SchedulingAdapter implementation that uses Effect TS middleware
5
+ * to puppeteer the Acuity booking wizard.
6
+ *
7
+ * Read operations:
8
+ * - getServices: returns static service catalog (if provided) or falls back to scraper
9
+ * - getAvailableDates: Effect program navigates wizard calendar (verified selectors)
10
+ * - getAvailableSlots: Effect program clicks day tile and reads time buttons
11
+ *
12
+ * Write operations:
13
+ * - createBooking/createBookingWithPaymentRef: Effect TS middleware via Playwright
14
+ *
15
+ * This is the bridge between Effect TS (middleware layer) and
16
+ * fp-ts (pipeline/adapter layer).
17
+ */
18
+ import { Effect, pipe } from 'effect';
19
+ import { createScraperAdapter } from '../adapters/acuity-scraper.js';
20
+ import { Errors } from '../core/types.js';
21
+ import { BrowserServiceLive, defaultBrowserConfig } from './browser-service.js';
22
+ import { toSchedulingError } from './errors.js';
23
+ import { createRemoteWizardAdapter } from './remote-adapter.js';
24
+ import { navigateToBooking, fillFormFields, bypassPayment, generateCouponCode, submitBooking, extractConfirmation, toBooking, readAvailableDates, readTimeSlots, } from './steps/index.js';
25
+ // =============================================================================
26
+ // EFFECT PROGRAMS
27
+ // =============================================================================
28
+ /**
29
+ * Full booking creation via the Acuity wizard.
30
+ *
31
+ * Flow:
32
+ * 1. Click through wizard: service → Book → calendar → time → continue
33
+ * 2. Fill client form (standard + intake fields) → "Continue to Payment" → /payment page
34
+ * 3. Apply 100% gift certificate on payment page (coupon toggle → enter code → Apply)
35
+ * 4. Click "PAY & CONFIRM" at $0 total
36
+ * 5. Extract confirmation data
37
+ */
38
+ const createBookingWithPaymentRefProgram = (request, paymentRef, paymentProcessor, couponCode, service) => Effect.scoped(Effect.gen(function* () {
39
+ // Step 1: Navigate through wizard to client form
40
+ const serviceName = service?.name ?? request.serviceId;
41
+ const nav = yield* navigateToBooking({
42
+ serviceName,
43
+ datetime: request.datetime,
44
+ client: request.client,
45
+ });
46
+ if (nav.landingStep !== 'client-form') {
47
+ return yield* Effect.fail({
48
+ _tag: 'WizardStepError',
49
+ step: 'navigate',
50
+ message: `Wizard landed on '${nav.landingStep}' instead of client form. Service or datetime may be unavailable.`,
51
+ });
52
+ }
53
+ // Step 2: Fill form fields (standard + intake) and advance to /payment page
54
+ yield* fillFormFields({
55
+ client: request.client,
56
+ customFields: request.client.customFields,
57
+ });
58
+ // Step 3: Apply gift certificate on the payment page (total → $0)
59
+ yield* bypassPayment(couponCode);
60
+ // Step 4: Click "PAY & CONFIRM" at $0 total
61
+ yield* submitBooking();
62
+ // Step 5: Extract confirmation
63
+ const confirmation = yield* extractConfirmation();
64
+ return toBooking(confirmation, request, paymentRef, paymentProcessor, service
65
+ ? {
66
+ name: service.name,
67
+ duration: service.duration,
68
+ price: service.price,
69
+ currency: service.currency,
70
+ }
71
+ : undefined);
72
+ }));
73
+ /**
74
+ * Simple booking creation (no payment bypass needed).
75
+ * For card payments that go through Acuity's normal flow.
76
+ */
77
+ const createBookingProgram = (request, serviceName) => Effect.scoped(Effect.gen(function* () {
78
+ const nav = yield* navigateToBooking({
79
+ serviceName: serviceName ?? request.serviceId,
80
+ datetime: request.datetime,
81
+ client: request.client,
82
+ });
83
+ if (nav.landingStep !== 'client-form') {
84
+ return yield* Effect.fail({
85
+ _tag: 'WizardStepError',
86
+ step: 'navigate',
87
+ message: `Wizard landed on '${nav.landingStep}' instead of client form.`,
88
+ });
89
+ }
90
+ yield* fillFormFields({
91
+ client: request.client,
92
+ customFields: request.client.customFields,
93
+ });
94
+ // No payment bypass - form fills and advances to payment page
95
+ // where Acuity handles card payment normally
96
+ yield* submitBooking();
97
+ const confirmation = yield* extractConfirmation();
98
+ return toBooking(confirmation, request, '', 'acuity');
99
+ }));
100
+ // =============================================================================
101
+ // ADAPTER FACTORY
102
+ // =============================================================================
103
+ /**
104
+ * Create a full SchedulingAdapter that puppeteers the Acuity wizard.
105
+ *
106
+ * Read operations delegate to the existing scraper.
107
+ * Write operations use Effect TS middleware via Playwright.
108
+ */
109
+ export const createWizardAdapter = (config) => {
110
+ // Remote mode: delegate everything to the middleware server
111
+ if (config.mode === 'remote') {
112
+ if (!config.remote) {
113
+ throw new Error('WizardAdapterConfig.remote is required when mode is "remote"');
114
+ }
115
+ return createRemoteWizardAdapter({
116
+ ...config.remote,
117
+ couponCode: config.remote.couponCode ?? config.couponCode,
118
+ services: config.services,
119
+ });
120
+ }
121
+ // Local mode: run Playwright in-process
122
+ const browserConfig = {
123
+ ...defaultBrowserConfig,
124
+ ...config,
125
+ };
126
+ const layer = BrowserServiceLive(browserConfig);
127
+ // Provide browser layer and map MiddlewareError → SchedulingError
128
+ const runWizard = (effect) => pipe(Effect.scoped(effect), Effect.provide(layer), Effect.mapError(toSchedulingError));
129
+ // Static services from config (avoids browser launch for service listing)
130
+ const staticServices = config.services ? [...config.services] : null;
131
+ // Scraper fallback for services if no static list provided
132
+ const scraperConfig = {
133
+ baseUrl: config.baseUrl,
134
+ headless: browserConfig.headless,
135
+ timeout: browserConfig.timeout,
136
+ userAgent: browserConfig.userAgent,
137
+ executablePath: browserConfig.executablePath,
138
+ launchArgs: browserConfig.launchArgs ? [...browserConfig.launchArgs] : undefined,
139
+ };
140
+ const scraper = createScraperAdapter(scraperConfig);
141
+ // Cache services for getService lookups
142
+ let cachedServices = staticServices;
143
+ // Helper: resolve service name from ID using cached services
144
+ const resolveServiceName = (serviceId) => cachedServices?.find((s) => s.id === serviceId)?.name;
145
+ return {
146
+ name: 'acuity-wizard',
147
+ // -----------------------------------------------------------------------
148
+ // Read operations
149
+ // -----------------------------------------------------------------------
150
+ getServices: () => {
151
+ if (staticServices) {
152
+ return Effect.succeed(staticServices);
153
+ }
154
+ // Fallback to scraper (may fail with broken selectors)
155
+ return pipe(scraper.getServices(), Effect.tap((services) => Effect.sync(() => {
156
+ cachedServices = services;
157
+ })));
158
+ },
159
+ getService: (serviceId) => {
160
+ if (cachedServices) {
161
+ const found = cachedServices.find((s) => s.id === serviceId);
162
+ return found
163
+ ? Effect.succeed(found)
164
+ : Effect.fail(Errors.acuity('NOT_FOUND', `Service ${serviceId} not found`));
165
+ }
166
+ return pipe(scraper.getServices(), Effect.flatMap((services) => {
167
+ cachedServices = services;
168
+ const found = services.find((s) => s.id === serviceId);
169
+ return found
170
+ ? Effect.succeed(found)
171
+ : Effect.fail(Errors.acuity('NOT_FOUND', `Service ${serviceId} not found`));
172
+ }));
173
+ },
174
+ getProviders: () => Effect.succeed([
175
+ {
176
+ id: '1',
177
+ name: 'Default Provider',
178
+ email: 'provider@example.com',
179
+ description: 'Primary provider',
180
+ timezone: 'America/New_York',
181
+ },
182
+ ]),
183
+ getProvider: () => Effect.succeed({
184
+ id: '1',
185
+ name: 'Default Provider',
186
+ email: 'provider@example.com',
187
+ description: 'Primary provider',
188
+ timezone: 'America/New_York',
189
+ }),
190
+ getProvidersForService: () => Effect.succeed([
191
+ {
192
+ id: '1',
193
+ name: 'Default Provider',
194
+ email: 'provider@example.com',
195
+ description: 'Primary provider',
196
+ timezone: 'America/New_York',
197
+ },
198
+ ]),
199
+ getAvailableDates: (params) => {
200
+ const serviceName = resolveServiceName(params.serviceId);
201
+ if (!serviceName) {
202
+ return Effect.fail(Errors.acuity('NOT_FOUND', `Cannot resolve service name for ID ${params.serviceId}. Provide static services in config.`));
203
+ }
204
+ return runWizard(Effect.scoped(readAvailableDates({
205
+ serviceName,
206
+ // Don't pass appointmentTypeId — our internal IDs don't match Acuity URL IDs
207
+ targetMonth: params.startDate?.slice(0, 7),
208
+ monthsToScan: 2,
209
+ })));
210
+ },
211
+ getAvailableSlots: (params) => {
212
+ const serviceName = resolveServiceName(params.serviceId);
213
+ if (!serviceName) {
214
+ return Effect.fail(Errors.acuity('NOT_FOUND', `Cannot resolve service name for ID ${params.serviceId}. Provide static services in config.`));
215
+ }
216
+ return runWizard(Effect.scoped(readTimeSlots({
217
+ serviceName,
218
+ date: params.date,
219
+ })));
220
+ },
221
+ checkSlotAvailability: (params) => {
222
+ const serviceName = resolveServiceName(params.serviceId);
223
+ if (!serviceName) {
224
+ return Effect.fail(Errors.acuity('NOT_FOUND', `Cannot resolve service name for ID ${params.serviceId}`));
225
+ }
226
+ return pipe(runWizard(Effect.scoped(readTimeSlots({
227
+ serviceName,
228
+ date: params.datetime.split('T')[0],
229
+ }))), Effect.map((slots) => {
230
+ // Slots return local time (no TZ suffix: "2026-03-07T14:00:00").
231
+ // Request datetime should also be local, but normalize both by
232
+ // stripping any trailing Z or offset for comparison.
233
+ const normalize = (dt) => dt.replace(/([+-]\d{2}:\d{2}|Z)$/, '');
234
+ const requestNorm = normalize(params.datetime);
235
+ return slots.some((s) => s.available && normalize(s.datetime) === requestNorm);
236
+ }));
237
+ },
238
+ // -----------------------------------------------------------------------
239
+ // Reservation - not supported (pipeline has graceful fallback)
240
+ // -----------------------------------------------------------------------
241
+ createReservation: () => Effect.fail(Errors.reservation('BLOCK_FAILED', 'Reservations not supported by wizard adapter')),
242
+ releaseReservation: () => Effect.succeed(undefined),
243
+ // -----------------------------------------------------------------------
244
+ // Write operations - Effect TS middleware
245
+ // -----------------------------------------------------------------------
246
+ createBooking: (request) => {
247
+ const serviceName = cachedServices?.find((s) => s.id === request.serviceId)?.name;
248
+ return runWizard(createBookingProgram(request, serviceName));
249
+ },
250
+ createBookingWithPaymentRef: (request, paymentRef, paymentProcessor) => {
251
+ const coupon = config.couponCode ?? generateCouponCode(paymentRef, paymentProcessor);
252
+ const service = cachedServices?.find((s) => s.id === request.serviceId);
253
+ return runWizard(createBookingWithPaymentRefProgram(request, paymentRef, paymentProcessor, coupon, service));
254
+ },
255
+ getBooking: () => Effect.fail(Errors.acuity('NOT_IMPLEMENTED', 'Get booking not yet supported via wizard')),
256
+ cancelBooking: () => Effect.fail(Errors.acuity('NOT_IMPLEMENTED', 'Cancel not yet supported via wizard')),
257
+ rescheduleBooking: () => Effect.fail(Errors.acuity('NOT_IMPLEMENTED', 'Reschedule not yet supported via wizard')),
258
+ // -----------------------------------------------------------------------
259
+ // Client - pass-through (client data comes from our form, not Acuity)
260
+ // -----------------------------------------------------------------------
261
+ findOrCreateClient: (client) => Effect.succeed({ id: `local-${client.email}`, isNew: true }),
262
+ getClientByEmail: () => Effect.succeed(null),
263
+ };
264
+ };
265
+ //# sourceMappingURL=acuity-wizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acuity-wizard.js","sourceRoot":"","sources":["../../src/middleware/acuity-wizard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * BrowserService Layer
3
+ *
4
+ * Effect TS Layer providing managed Playwright browser lifecycle.
5
+ * The browser and pages are acquired/released via Effect's Scope,
6
+ * ensuring proper cleanup even on errors or interruptions.
7
+ */
8
+ import { Context, Effect, Layer, Scope } from 'effect';
9
+ import type { Page } from 'playwright-core';
10
+ import { BrowserError } from './errors.js';
11
+ export interface BrowserConfig {
12
+ /** Base URL for the Acuity scheduling page */
13
+ readonly baseUrl: string;
14
+ /** Run browser in headless mode (default: true) */
15
+ readonly headless: boolean;
16
+ /** Default timeout for page operations in ms (default: 30000) */
17
+ readonly timeout: number;
18
+ /** User agent string */
19
+ readonly userAgent: string;
20
+ /** Take screenshot on failure (default: true) */
21
+ readonly screenshotOnFailure: boolean;
22
+ /** Directory for failure screenshots */
23
+ readonly screenshotDir: string;
24
+ /** Path to Chromium executable (for Lambda/serverless) */
25
+ readonly executablePath?: string;
26
+ /** Additional chromium.launch() args (e.g., Lambda sandbox flags) */
27
+ readonly launchArgs?: readonly string[];
28
+ }
29
+ export declare const defaultBrowserConfig: BrowserConfig;
30
+ export interface BrowserServiceShape {
31
+ /** Get a managed Page (scoped - auto-closed when scope ends) */
32
+ readonly acquirePage: Effect.Effect<Page, BrowserError, Scope.Scope>;
33
+ /** Take a screenshot of the most recently created page */
34
+ readonly screenshot: (label: string) => Effect.Effect<Buffer, BrowserError>;
35
+ /** The browser configuration */
36
+ readonly config: BrowserConfig;
37
+ }
38
+ declare const BrowserService_base: Context.TagClass<BrowserService, "scheduling-kit/BrowserService", BrowserServiceShape>;
39
+ export declare class BrowserService extends BrowserService_base {
40
+ }
41
+ /**
42
+ * Create a live BrowserService layer backed by Playwright Chromium.
43
+ * The browser is launched once when the layer is created and closed
44
+ * when the layer scope ends.
45
+ */
46
+ export declare const BrowserServiceLive: (config?: Partial<BrowserConfig>) => Layer.Layer<BrowserService, BrowserError>;
47
+ /**
48
+ * A mock BrowserService for unit tests.
49
+ * Does not launch a real browser.
50
+ */
51
+ export declare const BrowserServiceTest: (config?: Partial<BrowserConfig>) => Layer.Layer<BrowserService>;
52
+ export {};
53
+ //# sourceMappingURL=browser-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-service.d.ts","sourceRoot":"","sources":["../../src/middleware/browser-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvD,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,MAAM,WAAW,aAAa;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wBAAwB;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,wCAAwC;IACxC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,0DAA0D;IAC1D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,qEAAqE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,eAAO,MAAM,oBAAoB,EAAE,aAQlC,CAAC;AAMF,MAAM,WAAW,mBAAmB;IACnC,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACrE,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC5E,gCAAgC;IAChC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAC/B;;AAED,qBAAa,cAAe,SAAQ,mBAGjC;CAAG;AAMN;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAQ,OAAO,CAAC,aAAa,CAAM,KACjC,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,YAAY,CAqF1C,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAQ,OAAO,CAAC,aAAa,CAAM,KACjC,KAAK,CAAC,KAAK,CAAC,cAAc,CAW5B,CAAC"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * BrowserService Layer
3
+ *
4
+ * Effect TS Layer providing managed Playwright browser lifecycle.
5
+ * The browser and pages are acquired/released via Effect's Scope,
6
+ * ensuring proper cleanup even on errors or interruptions.
7
+ */
8
+ import { Context, Effect, Layer } from 'effect';
9
+ import { BrowserError } from './errors.js';
10
+ export const defaultBrowserConfig = {
11
+ baseUrl: 'https://MassageIthaca.as.me',
12
+ headless: true,
13
+ timeout: 30000,
14
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
15
+ screenshotOnFailure: true,
16
+ screenshotDir: '/tmp/scheduling-kit-screenshots',
17
+ };
18
+ export class BrowserService extends Context.Tag('scheduling-kit/BrowserService')() {
19
+ }
20
+ // =============================================================================
21
+ // LIVE IMPLEMENTATION
22
+ // =============================================================================
23
+ /**
24
+ * Create a live BrowserService layer backed by Playwright Chromium.
25
+ * The browser is launched once when the layer is created and closed
26
+ * when the layer scope ends.
27
+ */
28
+ export const BrowserServiceLive = (config = {}) => {
29
+ const cfg = { ...defaultBrowserConfig, ...config };
30
+ return Layer.scoped(BrowserService, Effect.gen(function* () {
31
+ // Dynamic import - try playwright-core first (lighter, no bundled browsers),
32
+ // fall back to full playwright which includes its own Chromium
33
+ const chromium = yield* Effect.tryPromise({
34
+ try: async () => {
35
+ try {
36
+ const pw = await import('playwright-core');
37
+ return pw.chromium;
38
+ }
39
+ catch {
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ const pw = await import('playwright-core');
42
+ return pw.chromium;
43
+ }
44
+ },
45
+ catch: () => new BrowserError({
46
+ reason: 'PLAYWRIGHT_MISSING',
47
+ cause: new Error('playwright-core or playwright is required for the wizard middleware. ' +
48
+ 'Install with: pnpm add playwright-core'),
49
+ }),
50
+ });
51
+ // Launch browser (released when scope ends)
52
+ const browser = yield* Effect.acquireRelease(Effect.tryPromise({
53
+ try: () => chromium.launch({
54
+ headless: cfg.headless,
55
+ executablePath: cfg.executablePath,
56
+ args: cfg.launchArgs ? [...cfg.launchArgs] : undefined,
57
+ }),
58
+ catch: (e) => new BrowserError({ reason: 'LAUNCH_FAILED', cause: e }),
59
+ }), (browser) => Effect.promise(() => browser.close()).pipe(Effect.ignoreLogged));
60
+ // Create a single managed page for the layer's lifetime.
61
+ // All step programs share this page within a scope.
62
+ const page = yield* Effect.acquireRelease(Effect.tryPromise({
63
+ try: async () => {
64
+ const p = await browser.newPage({ userAgent: cfg.userAgent });
65
+ p.setDefaultTimeout(cfg.timeout);
66
+ return p;
67
+ },
68
+ catch: (e) => new BrowserError({ reason: 'PAGE_FAILED', cause: e }),
69
+ }), (p) => Effect.promise(() => p.close()).pipe(Effect.ignoreLogged));
70
+ // acquirePage returns the singleton page — cleanup is handled by the
71
+ // layer scope above. The no-op release keeps the Scope type requirement
72
+ // so callers still need Effect.scoped.
73
+ const acquirePage = Effect.acquireRelease(Effect.succeed(page), () => Effect.void);
74
+ const screenshot = (label) => Effect.tryPromise({
75
+ try: async () => {
76
+ if (page.isClosed()) {
77
+ throw new Error('No active page for screenshot');
78
+ }
79
+ const buffer = await page.screenshot({
80
+ path: `${cfg.screenshotDir}/${label}-${Date.now()}.png`,
81
+ fullPage: true,
82
+ });
83
+ return buffer;
84
+ },
85
+ catch: (e) => new BrowserError({ reason: 'SCREENSHOT_FAILED', cause: e }),
86
+ });
87
+ return { acquirePage, screenshot, config: cfg };
88
+ }));
89
+ };
90
+ // =============================================================================
91
+ // TEST IMPLEMENTATION
92
+ // =============================================================================
93
+ /**
94
+ * A mock BrowserService for unit tests.
95
+ * Does not launch a real browser.
96
+ */
97
+ export const BrowserServiceTest = (config = {}) => {
98
+ const cfg = { ...defaultBrowserConfig, ...config };
99
+ return Layer.succeed(BrowserService, {
100
+ acquirePage: Effect.die(new Error('BrowserServiceTest: acquirePage called - use a real browser for integration tests')),
101
+ screenshot: () => Effect.succeed(Buffer.from('mock-screenshot')),
102
+ config: cfg,
103
+ });
104
+ };
105
+ //# sourceMappingURL=browser-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-service.js","sourceRoot":"","sources":["../../src/middleware/browser-service.ts"],"names":[],"mappings":"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"}