effect-browser 0.2.0-beta.8 → 0.3.0-beta.5

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 (202) hide show
  1. package/README.md +705 -1166
  2. package/dist/Browser-CM65Uo7f.mjs +437 -0
  3. package/dist/Browser-CM65Uo7f.mjs.map +1 -0
  4. package/dist/Browser.d.mts +115 -3
  5. package/dist/Browser.mjs +2 -22
  6. package/dist/BrowserError-DXXIA6Rk.d.mts +135 -0
  7. package/dist/BrowserError.d.mts +2 -0
  8. package/dist/BrowserError.mjs +202 -0
  9. package/dist/BrowserError.mjs.map +1 -0
  10. package/dist/BrowserEvent-DzdNdvoh.d.mts +277 -0
  11. package/dist/BrowserEvent.d.mts +2 -0
  12. package/dist/BrowserEvent.mjs +295 -0
  13. package/dist/BrowserEvent.mjs.map +1 -0
  14. package/dist/Cdp.d.mts +32 -0
  15. package/dist/Cdp.mjs +61 -0
  16. package/dist/Cdp.mjs.map +1 -0
  17. package/dist/Change-DNvztNLu.mjs +138 -0
  18. package/dist/Change-DNvztNLu.mjs.map +1 -0
  19. package/dist/Change-DVP2IJWm.d.mts +71 -0
  20. package/dist/Change.d.mts +2 -0
  21. package/dist/Change.mjs +2 -0
  22. package/dist/Chromium.d.mts +29 -81
  23. package/dist/Chromium.mjs +57 -392
  24. package/dist/Chromium.mjs.map +1 -1
  25. package/dist/Expect.d.mts +62 -0
  26. package/dist/Expect.mjs +114 -0
  27. package/dist/Expect.mjs.map +1 -0
  28. package/dist/Frame-BERmf_gM.d.mts +117 -0
  29. package/dist/Frame.d.mts +2 -0
  30. package/dist/Frame.mjs +123 -0
  31. package/dist/Frame.mjs.map +1 -0
  32. package/dist/Moment.d.mts +2 -0
  33. package/dist/Moment.mjs +288 -0
  34. package/dist/Moment.mjs.map +1 -0
  35. package/dist/Motion.d.mts +40 -0
  36. package/dist/Motion.mjs +122 -0
  37. package/dist/Motion.mjs.map +1 -0
  38. package/dist/Page-D4281dIi.mjs +618 -0
  39. package/dist/Page-D4281dIi.mjs.map +1 -0
  40. package/dist/Page-DYTdVhYa.d.mts +593 -0
  41. package/dist/Page.d.mts +2 -0
  42. package/dist/Page.mjs +2 -0
  43. package/dist/Plan-CNNOU1Tx.d.mts +112 -0
  44. package/dist/Plan-Dtrb-8im.mjs +311 -0
  45. package/dist/Plan-Dtrb-8im.mjs.map +1 -0
  46. package/dist/Plan.d.mts +2 -3
  47. package/dist/Plan.mjs +2 -2
  48. package/dist/Policy.d.mts +76 -0
  49. package/dist/Policy.mjs +237 -0
  50. package/dist/Policy.mjs.map +1 -0
  51. package/dist/Presentation.d.mts +48 -0
  52. package/dist/Presentation.mjs +117 -0
  53. package/dist/Presentation.mjs.map +1 -0
  54. package/dist/Snapshot-B34hZ4Jl.d.mts +68 -0
  55. package/dist/Snapshot.d.mts +2 -0
  56. package/dist/Snapshot.mjs +79 -0
  57. package/dist/Snapshot.mjs.map +1 -0
  58. package/dist/Stage.d.mts +54 -0
  59. package/dist/Stage.mjs +133 -0
  60. package/dist/Stage.mjs.map +1 -0
  61. package/dist/Supervisor-BtEG93gR.mjs +420 -0
  62. package/dist/Supervisor-BtEG93gR.mjs.map +1 -0
  63. package/dist/Supervisor-Im6-wbg5.d.mts +230 -0
  64. package/dist/Supervisor.d.mts +2 -0
  65. package/dist/Supervisor.mjs +2 -0
  66. package/dist/context-DAYjATgC.mjs +137 -0
  67. package/dist/context-DAYjATgC.mjs.map +1 -0
  68. package/dist/index.d.mts +17 -14
  69. package/dist/index.mjs +18 -13
  70. package/dist/match.inpage-xOe9LNO0.mjs +48 -0
  71. package/dist/match.inpage-xOe9LNO0.mjs.map +1 -0
  72. package/dist/page-Banfm_zc.mjs +5097 -0
  73. package/dist/page-Banfm_zc.mjs.map +1 -0
  74. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  75. package/dist/url-CcL0K7S0.mjs +262 -0
  76. package/dist/url-CcL0K7S0.mjs.map +1 -0
  77. package/package.json +96 -61
  78. package/src/Browser.ts +625 -0
  79. package/src/BrowserError.ts +225 -0
  80. package/src/BrowserEvent.ts +294 -0
  81. package/src/Cdp.ts +101 -0
  82. package/src/Change.ts +88 -0
  83. package/src/Chromium.ts +102 -0
  84. package/src/Expect.ts +180 -0
  85. package/src/Frame.ts +130 -0
  86. package/src/Moment.ts +479 -0
  87. package/src/Motion.ts +166 -0
  88. package/src/Page.ts +520 -0
  89. package/src/Plan.ts +350 -0
  90. package/src/Policy.ts +325 -0
  91. package/src/Presentation.ts +195 -0
  92. package/src/Snapshot.ts +89 -0
  93. package/src/Stage.ts +223 -0
  94. package/src/Supervisor.ts +353 -0
  95. package/src/index.ts +20 -0
  96. package/src/internal/input/actions.ts +607 -0
  97. package/src/internal/input/dispatch.ts +161 -0
  98. package/src/internal/input/edit.inpage.ts +162 -0
  99. package/src/internal/input/evidence.inpage.ts +164 -0
  100. package/src/internal/input/guard.inpage.ts +605 -0
  101. package/src/internal/input/guard.ts +183 -0
  102. package/src/internal/input/keyboard.ts +112 -0
  103. package/src/internal/input/keys.ts +127 -0
  104. package/src/internal/input/perform.ts +322 -0
  105. package/src/internal/input/pointer.ts +153 -0
  106. package/src/internal/input/replies.ts +159 -0
  107. package/src/internal/input/settle.ts +50 -0
  108. package/src/internal/input/style.ts +186 -0
  109. package/src/internal/input/targets.inpage.ts +293 -0
  110. package/src/internal/input/targets.ts +148 -0
  111. package/src/internal/page/bridge.ts +386 -0
  112. package/src/internal/page/context.ts +302 -0
  113. package/src/internal/page/lane.ts +301 -0
  114. package/src/internal/page/navigation.ts +183 -0
  115. package/src/internal/page/openings.ts +87 -0
  116. package/src/internal/page/page.ts +137 -0
  117. package/src/internal/page/url.ts +100 -0
  118. package/src/internal/page/viewport.ts +43 -0
  119. package/src/internal/pictures/capture.ts +778 -0
  120. package/src/internal/pictures/clock.ts +141 -0
  121. package/src/internal/pictures/pictures.ts +520 -0
  122. package/src/internal/pictures/transport.ts +242 -0
  123. package/src/internal/reading/choose.ts +100 -0
  124. package/src/internal/reading/context.inpage.ts +201 -0
  125. package/src/internal/reading/controls.inpage.ts +158 -0
  126. package/src/internal/reading/expect.inpage.ts +70 -0
  127. package/src/internal/reading/match.inpage.ts +92 -0
  128. package/src/internal/reading/names.inpage.ts +306 -0
  129. package/src/internal/reading/outline.inpage.ts +260 -0
  130. package/src/internal/reading/reading.ts +223 -0
  131. package/src/internal/reading/ready.inpage.ts +154 -0
  132. package/src/internal/reading/ready.ts +105 -0
  133. package/src/internal/reading/subjects.inpage.ts +188 -0
  134. package/src/internal/reading/text.inpage.ts +135 -0
  135. package/src/internal/reading/walk.inpage.ts +220 -0
  136. package/src/internal/supervisor/lifecycle.ts +265 -0
  137. package/src/internal/timeline/changes.inpage.ts +216 -0
  138. package/src/internal/timeline/changes.ts +100 -0
  139. package/src/internal/timeline/events.ts +93 -0
  140. package/src/internal/timeline/fold.inpage.ts +123 -0
  141. package/src/internal/timeline/history.inpage.ts +229 -0
  142. package/src/internal/timeline/marks.inpage.ts +207 -0
  143. package/src/internal/timeline/record.inpage.ts +133 -0
  144. package/src/internal/timeline/sight.inpage.ts +122 -0
  145. package/src/internal/timeline/window.ts +96 -0
  146. package/src/internal/timeline/words.ts +65 -0
  147. package/dist/Bootstrap-B9UUXsvF.d.mts +0 -183
  148. package/dist/Bootstrap-CeQOWBy6.mjs +0 -189
  149. package/dist/Bootstrap-CeQOWBy6.mjs.map +0 -1
  150. package/dist/Bootstrap.d.mts +0 -3
  151. package/dist/Bootstrap.mjs +0 -2
  152. package/dist/Browser-n-bKgBNN.d.mts +0 -372
  153. package/dist/Browser.mjs.map +0 -1
  154. package/dist/BrowserData-CPIIIHol.d.mts +0 -585
  155. package/dist/BrowserData.d.mts +0 -3
  156. package/dist/BrowserData.mjs +0 -695
  157. package/dist/BrowserData.mjs.map +0 -1
  158. package/dist/BrowserRuntime-CFe9uUTv.d.mts +0 -148
  159. package/dist/BrowserRuntime-HJ1yYlFS.mjs +0 -6741
  160. package/dist/BrowserRuntime-HJ1yYlFS.mjs.map +0 -1
  161. package/dist/BrowserRuntime.d.mts +0 -3
  162. package/dist/BrowserRuntime.mjs +0 -3
  163. package/dist/Capture.d.mts +0 -56
  164. package/dist/Capture.mjs +0 -678
  165. package/dist/Capture.mjs.map +0 -1
  166. package/dist/CaptureData-CYriZHX1.mjs +0 -201
  167. package/dist/CaptureData-CYriZHX1.mjs.map +0 -1
  168. package/dist/CaptureData.d.mts +0 -219
  169. package/dist/CaptureData.mjs +0 -2
  170. package/dist/Errors.d.mts +0 -158
  171. package/dist/Errors.mjs +0 -207
  172. package/dist/Errors.mjs.map +0 -1
  173. package/dist/Images-CaaPNeed.mjs +0 -76
  174. package/dist/Images-CaaPNeed.mjs.map +0 -1
  175. package/dist/OperationOptions-DAeRS6O9.mjs +0 -55
  176. package/dist/OperationOptions-DAeRS6O9.mjs.map +0 -1
  177. package/dist/PageControl.d.mts +0 -15
  178. package/dist/PageControl.mjs +0 -34
  179. package/dist/PageControl.mjs.map +0 -1
  180. package/dist/PageRegistry-B3q9DS3g.mjs +0 -164
  181. package/dist/PageRegistry-B3q9DS3g.mjs.map +0 -1
  182. package/dist/Plan-D8qQ48Ic.mjs +0 -512
  183. package/dist/Plan-D8qQ48Ic.mjs.map +0 -1
  184. package/dist/PlanData-B668RxbQ.d.mts +0 -4831
  185. package/dist/PlanData.d.mts +0 -3
  186. package/dist/PlanData.mjs +0 -268
  187. package/dist/PlanData.mjs.map +0 -1
  188. package/dist/PlanInput-BJcPPncs.mjs +0 -75
  189. package/dist/PlanInput-BJcPPncs.mjs.map +0 -1
  190. package/dist/PublicSession-Bs61Eal8.mjs +0 -5601
  191. package/dist/PublicSession-Bs61Eal8.mjs.map +0 -1
  192. package/dist/Testing.d.mts +0 -479
  193. package/dist/Testing.mjs +0 -2147
  194. package/dist/Testing.mjs.map +0 -1
  195. package/dist/Timeline-fV22Xk-S.d.mts +0 -29
  196. package/dist/Timeline.d.mts +0 -4
  197. package/dist/Timeline.mjs +0 -20
  198. package/dist/Timeline.mjs.map +0 -1
  199. package/dist/TimelineData-BsOn-YGZ.d.mts +0 -3818
  200. package/dist/TimelineData.d.mts +0 -3
  201. package/dist/TimelineData.mjs +0 -482
  202. package/dist/TimelineData.mjs.map +0 -1
@@ -0,0 +1,353 @@
1
+ /**
2
+ * A browser kept open across losses and session ends, as a series of generations, each a new
3
+ * browser from the same provider. Pages don't carry over from one generation to the next.
4
+ *
5
+ * `make` opens the first generation at once with the provider's `open`, in a scope of its own, so
6
+ * a caller that stops waiting never interrupts a half-open browser. `browser` gives the current
7
+ * generation; callers wait, bounded, and share one open. A loss is published at once and the next
8
+ * generation opens on the `reopen` schedule, which also retries a failed open, unless the provider
9
+ * deems its failure `definite`: that goes `Down` at once, with its cause. `rotate` opens the
10
+ * next generation now, and so does the time `rotateBefore` ahead of a generation's `expiresAt`. A
11
+ * rotation makes the next generation before it breaks the current one, unless generations are
12
+ * `exclusive`, as sessions saving to one stored context must be: then the current one is released
13
+ * first. `retire` stops reopening at once and releases what is open; the scope's close retires
14
+ * too, unless the supervisor `keep`s: then it leaves the serving generation running, `Kept`. `states`
15
+ * publishes each generation's changes, with the provider's release outcome last.
16
+ *
17
+ * Providers supply the specifics: `Browserbase.supervise` opens hosted sessions this way.
18
+ *
19
+ * @since 0.3.0
20
+ */
21
+ import {
22
+ Cause,
23
+ Clock,
24
+ DateTime,
25
+ Duration,
26
+ Effect,
27
+ Exit,
28
+ Fiber,
29
+ FiberHandle,
30
+ FiberMap,
31
+ Option,
32
+ Predicate,
33
+ PubSub,
34
+ Schedule,
35
+ Schema,
36
+ Scope,
37
+ Stream,
38
+ SubscriptionRef,
39
+ type Take,
40
+ } from "effect";
41
+
42
+ import type * as Browser from "./Browser.ts";
43
+ import * as Lifecycle from "./internal/supervisor/lifecycle.ts";
44
+
45
+ export {
46
+ Closed,
47
+ Down,
48
+ GenerationState,
49
+ Kept,
50
+ Lost,
51
+ Open,
52
+ Opening,
53
+ Released,
54
+ Reopening,
55
+ Settled,
56
+ Unconfirmed,
57
+ } from "./internal/supervisor/lifecycle.ts";
58
+
59
+ /** One change in a generation's life. `at` is wall-clock epoch milliseconds from the Effect `Clock`. */
60
+ export class Generation extends Schema.Class<Generation>("effect-browser/Generation")({
61
+ number: Schema.Int.check(Schema.isGreaterThan(0)),
62
+ at: Schema.Finite,
63
+ state: Lifecycle.GenerationState,
64
+ }) {}
65
+
66
+ /**
67
+ * No browser to give: none opened within `waitTimeout` (`opening`), the latest generation failed
68
+ * to open (`down`, with what it failed with as `cause`), or the supervisor was retired.
69
+ */
70
+ export class Unavailable extends Schema.TaggedError<Unavailable>()("Unavailable", {
71
+ reason: Schema.Literals(["opening", "down", "retired"]),
72
+ detail: Schema.String,
73
+ cause: Schema.optional(Schema.Defect()),
74
+ }) {
75
+ override get message() {
76
+ return `no browser: ${this.detail}`;
77
+ }
78
+ }
79
+
80
+ /** One generation, as a provider opened it in the scope the supervisor gave it. */
81
+ export interface Opened {
82
+ readonly browser: Browser.Service;
83
+ /**
84
+ * End the generation, and say whether the provider confirmed its end. The supervisor asks it
85
+ * before it closes the generation's scope; without it, closing the scope ends the generation,
86
+ * which counts as `Settled`.
87
+ */
88
+ readonly release?: Effect.Effect<Lifecycle.Released> | undefined;
89
+ }
90
+
91
+ export interface Options<E, R> {
92
+ /** Opens one generation in the scope it is given. */
93
+ readonly open: Effect.Effect<Opened, E, R>;
94
+ /** When to try a failed open again. Defaults to exponential from 1 second, for 5 minutes. */
95
+ readonly reopen?: Schedule.Schedule<unknown, E> | undefined;
96
+ /**
97
+ * Failures no new try mends, such as a refused key: an open that fails so is `Down` at once.
98
+ * Defaults to none.
99
+ */
100
+ readonly definite?: ((error: E) => boolean) | undefined;
101
+ /**
102
+ * Rotate this long ahead of a generation's `expiresAt`, but no sooner than halfway through its
103
+ * life. Without it, a generation's end is a loss.
104
+ */
105
+ readonly rotateBefore?: Duration.Input | undefined;
106
+ /** Generations must not overlap: a rotation releases the current one before it opens the next. */
107
+ readonly exclusive?: boolean | undefined;
108
+ /**
109
+ * Closing the scope leaves the serving generation running, `Kept`: it closes the generation's
110
+ * scope but never asks `release`. For providers whose scope only disconnects, such as
111
+ * `Browserbase.supervise` with `keep`. `retire` still releases it.
112
+ */
113
+ readonly keep?: boolean | undefined;
114
+ /** How long `browser` and `rotate` wait for a generation to open. Defaults to 2 minutes. */
115
+ readonly waitTimeout?: Duration.Input | undefined;
116
+ }
117
+
118
+ export interface Supervisor {
119
+ /** The current generation's browser, waiting up to `waitTimeout` while none is open. */
120
+ readonly browser: Effect.Effect<Browser.Service, Unavailable>;
121
+ /** Each generation's changes, in order. A new reader first gets the latest 64; ends on retiring. */
122
+ readonly states: Stream.Stream<Generation>;
123
+ /** Open the next generation now, or join the one opening, and wait for it. */
124
+ readonly rotate: Effect.Effect<Browser.Service, Unavailable>;
125
+ /** Stop opening at once, release every generation, and wait until each release has finished. */
126
+ readonly retire: Effect.Effect<void>;
127
+ }
128
+
129
+ interface Held {
130
+ readonly opened: Opened;
131
+ readonly scope: Scope.Closeable;
132
+ }
133
+
134
+ const retired = () => new Unavailable({ reason: "retired", detail: "the supervisor was retired" });
135
+
136
+ const describe = (cause: Cause.Cause<unknown>) => {
137
+ const error = Cause.squash(cause);
138
+
139
+ return Predicate.isError(error) ? error.message : String(error);
140
+ };
141
+
142
+ /**
143
+ * Wait up to `waitTimeout` for generation `target`, or for any when it is undefined, as `state`
144
+ * changes.
145
+ */
146
+ const waitFor = <A extends { readonly opened: Opened }>(
147
+ state: SubscriptionRef.SubscriptionRef<Lifecycle.State<A>>,
148
+ waitTimeout: Duration.Duration,
149
+ target: number | undefined,
150
+ ) =>
151
+ SubscriptionRef.changes(state).pipe(
152
+ Stream.map((current) => Lifecycle.resolve(current, target)),
153
+ Stream.filter(Predicate.isNotUndefined),
154
+ Stream.runHead,
155
+ // The state's changes never end, so the first resolution always comes.
156
+ Effect.flatMap(
157
+ Option.match({
158
+ onNone: () => Effect.fail(retired()),
159
+ onSome: (resolution) =>
160
+ resolution._tag === "Serve"
161
+ ? Effect.succeed(resolution.live.value.opened.browser)
162
+ : Effect.fail(
163
+ new Unavailable({
164
+ reason: resolution.reason,
165
+ detail: resolution.detail,
166
+ cause: resolution.cause,
167
+ }),
168
+ ),
169
+ }),
170
+ ),
171
+ Effect.timeoutOrElse({
172
+ duration: waitTimeout,
173
+ orElse: () =>
174
+ Effect.fail(
175
+ new Unavailable({
176
+ reason: "opening",
177
+ detail: `no generation opened within ${Duration.format(waitTimeout)}`,
178
+ }),
179
+ ),
180
+ }),
181
+ );
182
+
183
+ /** Sleep until `before` ahead of `expiresAt`, but no sooner than halfway there from now. */
184
+ const untilRotation = (expiresAt: DateTime.Utc, before: Duration.Duration) =>
185
+ Clock.currentTimeMillis.pipe(
186
+ Effect.flatMap((now) => {
187
+ const left = DateTime.toEpochMillis(expiresAt) - now;
188
+
189
+ return Effect.sleep(Duration.millis(Math.max(left - Duration.toMillis(before), left / 2, 0)));
190
+ }),
191
+ );
192
+
193
+ /** Supervise generations of a provider's browser for as long as the scope is open. */
194
+ export const make = Effect.fn("Supervisor.make")(function* <E, R>(
195
+ options: Options<E, R>,
196
+ ): Effect.fn.Return<Supervisor, never, Scope.Scope | Exclude<R, Scope.Scope>> {
197
+ const clock = yield* Clock.Clock;
198
+ const context = yield* Effect.context<Exclude<R, Scope.Scope>>();
199
+ const own = yield* Scope.fork(yield* Effect.scope);
200
+ const exclusive = options.exclusive ?? false;
201
+ const definite = options.definite ?? (() => false);
202
+
203
+ const reopen: Schedule.Schedule<unknown, E> =
204
+ options.reopen ??
205
+ Schedule.exponential("1 second").pipe(Schedule.upTo({ duration: Duration.minutes(5) }));
206
+
207
+ const rotateBefore =
208
+ options.rotateBefore === undefined ? undefined : Duration.fromInputUnsafe(options.rotateBefore);
209
+
210
+ const waitTimeout = Duration.fromInputUnsafe(options.waitTimeout ?? Duration.minutes(2));
211
+ const started = Lifecycle.start<Held>();
212
+ const state = yield* SubscriptionRef.make(started.state);
213
+ const events = yield* PubSub.unbounded<Take.Take<Generation>>({ replay: 64 });
214
+ const opening = yield* FiberHandle.make<unknown, never>();
215
+ const releases = yield* FiberMap.make<number, unknown, never>();
216
+ const runOpen = yield* FiberHandle.runtime(opening)();
217
+ const runRelease = yield* FiberMap.runtime(releases)();
218
+
219
+ // Each input changes the state under one lock, which also publishes its events and starts its
220
+ // work, so neither can be reordered against another input's.
221
+ const apply = (input: Lifecycle.Input<Held>): Effect.Effect<number | undefined> =>
222
+ SubscriptionRef.modifyEffect(state, (current) => {
223
+ const step = Lifecycle.transition(current, input, exclusive);
224
+
225
+ return Effect.as(perform(step), [step.reply, step.state] as const);
226
+ }).pipe(Effect.uninterruptible);
227
+
228
+ const perform = (step: Lifecycle.Step<Held>) =>
229
+ Effect.gen(function* () {
230
+ for (const [number, generation] of step.events)
231
+ PubSub.publishUnsafe(events, [
232
+ new Generation({ number, at: clock.currentTimeMillisUnsafe(), state: generation }),
233
+ ]);
234
+ for (const command of step.commands)
235
+ switch (command._tag) {
236
+ case "Open":
237
+ runOpen(openGeneration(command.number, command.after));
238
+ break;
239
+ case "Watch":
240
+ yield* Effect.forkIn(
241
+ Effect.provideContext(watch(command.live), context),
242
+ command.live.value.scope,
243
+ );
244
+ break;
245
+ case "Release":
246
+ runRelease(command.live.number, release(command.live));
247
+ break;
248
+ case "Leave":
249
+ runRelease(command.live.number, Scope.close(command.live.value.scope, Exit.void));
250
+ }
251
+ });
252
+
253
+ const openOnce = Effect.uninterruptibleMask((restore) =>
254
+ Effect.flatMap(Scope.fork(own), (scope) =>
255
+ restore(options.open.pipe(Scope.provide(scope), Effect.provideContext(context))).pipe(
256
+ Effect.onError((cause) => Scope.close(scope, Exit.failCause(cause))),
257
+ Effect.map((opened): Held => ({ opened, scope })),
258
+ ),
259
+ ),
260
+ );
261
+
262
+ // Retiring interrupts the open between tries or during one; the provider releases what an
263
+ // interrupted try had made, as its scope closes.
264
+ const openGeneration = (number: number, after: number | undefined) =>
265
+ Effect.uninterruptibleMask((restore) => {
266
+ const previous =
267
+ after === undefined
268
+ ? Effect.void
269
+ : FiberMap.get(releases, after).pipe(
270
+ Effect.flatMap(Option.match({ onNone: () => Effect.void, onSome: Fiber.await })),
271
+ );
272
+
273
+ const tries = Effect.retry(openOnce, {
274
+ schedule: reopen,
275
+ while: (error) => !definite(error),
276
+ });
277
+
278
+ return restore(previous.pipe(Effect.andThen(tries))).pipe(
279
+ Effect.exit,
280
+ Effect.flatMap((exit) => {
281
+ if (Exit.isSuccess(exit))
282
+ return apply({ _tag: "Opened", live: { number, value: exit.value } });
283
+
284
+ return Cause.hasInterruptsOnly(exit.cause)
285
+ ? apply({ _tag: "Abandoned", number })
286
+ : apply({
287
+ _tag: "Failed",
288
+ number,
289
+ detail: describe(exit.cause),
290
+ cause: Cause.squash(exit.cause),
291
+ });
292
+ }),
293
+ );
294
+ });
295
+
296
+ // A generation's own release loses its browser too, which the transition ignores.
297
+ const watch = (live: Lifecycle.Live<Held>) => {
298
+ const { browser } = live.value.opened;
299
+
300
+ const loss = browser.disconnected.pipe(
301
+ Effect.flatMap((cause) => apply({ _tag: "Lost", number: live.number, cause })),
302
+ );
303
+
304
+ const rotation =
305
+ browser.expiresAt === undefined || rotateBefore === undefined
306
+ ? Effect.void
307
+ : untilRotation(browser.expiresAt, rotateBefore).pipe(
308
+ Effect.andThen(apply({ _tag: "Rotate", due: live.number })),
309
+ );
310
+
311
+ return Effect.all([loss, rotation], { concurrency: 2, discard: true });
312
+ };
313
+
314
+ // The provider releases before the scope closes, so what the scope holds, such as a stored
315
+ // context's lease, goes only once the release has reported.
316
+ const release = (live: Lifecycle.Live<Held>) =>
317
+ (live.value.opened.release ?? Effect.succeed(new Lifecycle.Settled())).pipe(
318
+ Effect.catchCause((cause) =>
319
+ Effect.succeed(
320
+ new Lifecycle.Unconfirmed({ detail: `the release failed: ${describe(cause)}` }),
321
+ ),
322
+ ),
323
+ Effect.ensuring(Scope.close(live.value.scope, Exit.void)),
324
+ Effect.flatMap((released) => apply({ _tag: "Released", number: live.number, released })),
325
+ );
326
+
327
+ const end = yield* Effect.cached(PubSub.publish(events, Exit.void));
328
+
329
+ // Stop at once, end or leave what is open, and wait for every release.
330
+ const stop = (input: Lifecycle.Input<Held>) =>
331
+ apply(input).pipe(
332
+ Effect.andThen(FiberHandle.clear(opening)),
333
+ Effect.andThen(FiberMap.awaitEmpty(releases)),
334
+ Effect.andThen(end),
335
+ Effect.asVoid,
336
+ );
337
+
338
+ const retire = yield* Effect.cached(stop({ _tag: "Retire" }));
339
+
340
+ yield* Effect.addFinalizer(() => (options.keep === true ? stop({ _tag: "Keep" }) : retire));
341
+ yield* perform(started);
342
+
343
+ return {
344
+ browser: waitFor(state, waitTimeout, undefined),
345
+ states: Stream.fromPubSubTake(events),
346
+ rotate: apply({ _tag: "Rotate" }).pipe(
347
+ Effect.flatMap((target) =>
348
+ target === undefined ? Effect.fail(retired()) : waitFor(state, waitTimeout, target),
349
+ ),
350
+ ),
351
+ retire,
352
+ } satisfies Supervisor;
353
+ });
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @since 0.3.0
3
+ */
4
+ export * as Browser from "./Browser.ts";
5
+ export * as BrowserError from "./BrowserError.ts";
6
+ export * as BrowserEvent from "./BrowserEvent.ts";
7
+ export * as Cdp from "./Cdp.ts";
8
+ export * as Change from "./Change.ts";
9
+ export * as Chromium from "./Chromium.ts";
10
+ export * as Expect from "./Expect.ts";
11
+ export * as Frame from "./Frame.ts";
12
+ export * as Moment from "./Moment.ts";
13
+ export * as Motion from "./Motion.ts";
14
+ export * as Page from "./Page.ts";
15
+ export * as Plan from "./Plan.ts";
16
+ export * as Policy from "./Policy.ts";
17
+ export * as Presentation from "./Presentation.ts";
18
+ export * as Snapshot from "./Snapshot.ts";
19
+ export * as Stage from "./Stage.ts";
20
+ export * as Supervisor from "./Supervisor.ts";