effect-browser 0.3.0-beta.7 → 0.3.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -702,25 +702,93 @@ its first glide starts mid-viewport, and each later one where the one before end
702
702
  input on another tab does meanwhile. Every sent move updates the position, including a partially
703
703
  cancelled glide. A later viewport clamps the starting point to its bounds if necessary.
704
704
 
705
- `Supervisor.make` keeps a browser open across losses and session ends, as generations, each a new
706
- browser from the provider's `open`. Opens run in the supervisor's own scope, so a caller that stops
707
- waiting never interrupts a half-open browser. `browser` gives the current generation, waiting up to
708
- `waitTimeout` while one opens. A lost browser is published at once, `Lost` with its browser's
709
- `disconnected` cause, and the next one opens on the `reopen` schedule, which also retries a failed
710
- open until it gives up, unless the provider deems the failure `definite`, such as a refused key:
711
- then the generation is `Down` at once, with its cause, which `Unavailable` also gives anyone
712
- waiting, because a schedule would only hide a configuration error. `rotate`, or the time
713
- `rotateBefore` ahead of a generation's browser's `expiresAt`, opens the next generation before it
714
- releases the current one, unless generations are `exclusive`: then the current one is released
715
- first.
716
- `retire` stops reopening at once and releases what is open, asking the provider's `release` before
717
- it closes a generation's scope; closing the scope retires too, unless the supervisor `keep`s: then
718
- the generation serving is left running, `Kept`, its scope closed but its provider never asked to
719
- release it, for a provider whose scope only disconnects. `states` streams each generation's
720
- `Opening`, `Reopening`, `Open`, `Lost`, `Down`, `Closed`, the last with the provider's release
721
- outcome, `Settled` or `Unconfirmed`, and `Kept`, so time open is a subtraction of their stamps.
722
- Pages don't carry over from one generation to the next. `effect-browserbase`'s
723
- `Browserbase.supervise` supervises hosted sessions, and with `keep` adopts the one it kept.
705
+ ## Supervising browser generations
706
+
707
+ `Supervisor.make` keeps a browser open across losses and session ends. Its `open` builds each
708
+ generation in a scope of its own and can return `{ browser, value }`: the application's pages,
709
+ tools or other resources belong to that generation. A caller that stops waiting never interrupts
710
+ the shared acquisition. Browser-only providers can continue returning `{ browser }`, with a
711
+ `void` value. Pages and values do not carry over when a new generation opens.
712
+
713
+ ```ts
714
+ import { Effect } from "effect";
715
+ import * as Chromium from "effect-browser/Chromium";
716
+ import * as Supervisor from "effect-browser/Supervisor";
717
+
718
+ const program = Effect.gen(function* () {
719
+ const browsers = yield* Supervisor.make({
720
+ open: Effect.gen(function* () {
721
+ const browser = yield* Chromium.open();
722
+ const page = yield* browser.firstPage;
723
+
724
+ return { browser, value: page };
725
+ }),
726
+ });
727
+ return yield* browsers.use(({ value: page }) => page.snapshot());
728
+ });
729
+ ```
730
+
731
+ `use(work, { wait })` atomically borrows the serving generation and runs `work` in a scope of its
732
+ own. The lease gives its `generation`, `browser`, `value`, an `ending` notice and `retain`.
733
+ `wait` defaults to `waitTimeout` (two minutes); zero either borrows immediately or fails
734
+ `Unavailable`. Cancelling the caller closes its work scope and lets its hold go. Ordinary work
735
+ errors stay ordinary work errors. `browser` still returns an unheld browser: use it only when the
736
+ caller can tolerate that generation being released underneath it.
737
+
738
+ A retained scope transfers a hold beyond the `use` call. Acquire anything that must travel with
739
+ that hold in its scope, and close it when the receiver is finished. Within a scoped program with
740
+ `browsers` as above and `Exit` and `Scope` imported from `effect`:
741
+
742
+ ```ts
743
+ const prepared =
744
+ yield *
745
+ browsers.use((lease) =>
746
+ Effect.gen(function* () {
747
+ const hold = yield* lease.retain;
748
+ const page = yield* Effect.acquireRelease(lease.browser.newPage(), (page) =>
749
+ page.close.pipe(Effect.ignore),
750
+ ).pipe(Scope.provide(hold));
751
+
752
+ return { page, hold, ending: lease.ending };
753
+ }),
754
+ );
755
+ // Hand `prepared` to its next owner; that owner eventually lets it go.
756
+ yield * Scope.close(prepared.hold, Exit.void);
757
+ ```
758
+
759
+ Rotation publishes `Draining` for the outgoing generation. It stays alive until its last hold
760
+ closes or `handover` expires, 30 seconds by default. `ending` is the first notice that it stopped
761
+ serving, with a `Superseded` reason and monotonic `revokeAt`; loss or retirement can revoke sooner.
762
+ At revocation, `use` interrupts work still running, waits for its finalizers and fails
763
+ `Superseded`. New `retain` calls refuse a revoked or releasing generation, and a retained scope
764
+ cannot extend the deadline. Retained scopes and work scopes close before the provider's
765
+ `release`; the generation's own scope closes last. Cleanup can therefore extend the time taken to
766
+ finish revocation. `Superseded` does not say whether an input reached the site: use the action's
767
+ receipt and plan stopping point to resolve that uncertainty.
768
+
769
+ Nonexclusive `rotate`, including `rotateBefore` timed rotation, opens the next generation while
770
+ old holds drain. Handover does not delay later rotations; multiple old generations can remain
771
+ owned, so the consumer chooses its rotation and overlap budget. Calls share one acquisition in
772
+ progress. Under `exclusive`, the current generation drains and finishes its provider release
773
+ before acquisition starts. Call `rotate` outside `use` for a clean cut: an exclusive rotation
774
+ inside `use` waits for its own hold to end or be revoked.
775
+
776
+ A loss publishes `Lost` with the browser's `disconnected` cause and revokes its holds immediately.
777
+ It reopens on `reopen`, which also retries an open failure until the schedule ends. A provider's
778
+ `definite` failure goes `Down` at once, with the cause also available through `Unavailable`.
779
+ `retire` revokes immediately, stops acquisition and waits for every generation to release.
780
+ Closing the supervisor's scope does the same unless `keep` is set: then only the serving
781
+ generation skips provider release, `Kept`; its application scopes still close. Other generations
782
+ are released normally.
783
+
784
+ `states` streams `Opening`, `Reopening`, `Open`, `Draining`, `Lost`, `Down`, `Closed` and `Kept`.
785
+ `Closed.released` preserves `Settled` or `Unconfirmed`; the latter does not establish that a
786
+ provider stopped the browser or its billing. `Generation.at` is wall-clock epoch milliseconds;
787
+ `hostTime` and `revokeAt` are host monotonic milliseconds from the supervisor's Effect `Clock`.
788
+ They can be compared with browser event and frame times when those use that same clock and host,
789
+ not across processes or against a provider's wall-clock timestamps. A generation event is
790
+ lifetime evidence, not proof that a frame was shown. `Browserbase.supervise` supplies hosted
791
+ generations and, with `keep`, can adopt one it previously kept.
724
792
 
725
793
  Every module is also an entry point, such as `effect-browser/Page`. The
726
794
  [repository README](https://github.com/mannyc2/effect-agent-browserbase#readme) has examples.
@@ -2,7 +2,7 @@ import { c as DisconnectCause } from "./BrowserEvent-BRu2juEz.mjs";
2
2
  import { Service, t as Browser_d_exports } from "./Browser.mjs";
3
3
  import { Cause, Duration, Effect, Schedule, Schema, Scope, Stream } from "effect";
4
4
  declare namespace lifecycle_d_exports {
5
- export { Closed, Command, Down, GenerationState, Input, Kept, Live, Lost, Open, Opening, Released, Reopening, Resolution, Settled, State, Step, Unconfirmed, resolve, start, transition };
5
+ export { Closed, Command, Down, Drain, Draining, GenerationState, Held, Input, Kept, Live, Lost, Open, Opening, Released, Reopening, Resolution, Settled, State, Step, Unconfirmed, lookup, resolve, start, transition };
6
6
  }
7
7
  declare const Settled_base: Schema.Class<Settled, Schema.TaggedStruct<"Settled", {}>, {}>;
8
8
  /** The provider confirmed that the generation's browser ended. */
@@ -23,6 +23,12 @@ declare class Reopening extends Reopening_base {}
23
23
  declare const Open_base: Schema.Class<Open, Schema.TaggedStruct<"Open", {}>, {}>;
24
24
  /** Open: `browser` gives this generation until the next one opens. */
25
25
  declare class Open extends Open_base {}
26
+ declare const Draining_base: Schema.Class<Draining, Schema.TaggedStruct<"Draining", {
27
+ readonly reason: Schema.Literals<readonly ["rotated", "lost", "retired"]>;
28
+ readonly revokeAt: Schema.Finite;
29
+ }>, {}>;
30
+ /** No longer serving; holds end at this host-monotonic deadline or when their owners let go. */
31
+ declare class Draining extends Draining_base {}
26
32
  declare const Lost_base: Schema.Class<Lost, Schema.TaggedStruct<"Lost", {
27
33
  readonly cause: Schema.Literals<readonly ["connection", "session", "released"]>;
28
34
  }>, {}>;
@@ -48,99 +54,115 @@ declare const Kept_base: Schema.Class<Kept, Schema.TaggedStruct<"Kept", {}>, {}>
48
54
  * closed, and its provider runs it on, and bills, until something adopts and releases it.
49
55
  */
50
56
  declare class Kept extends Kept_base {}
51
- declare const GenerationState: Schema.Union<readonly [typeof Opening, typeof Reopening, typeof Open, typeof Lost, typeof Down, typeof Closed, typeof Kept]>;
57
+ declare const GenerationState: Schema.Union<readonly [typeof Opening, typeof Reopening, typeof Open, typeof Draining, typeof Lost, typeof Down, typeof Closed, typeof Kept]>;
52
58
  type GenerationState = typeof GenerationState.Type;
53
59
  /** A generation that opened: its number, and what the runtime keeps to serve and end it. */
54
60
  interface Live<A> {
55
61
  readonly number: number;
56
62
  readonly value: A;
57
63
  }
58
- /**
59
- * At most one generation opens and at most one serves. Both do while a rotation makes the next
60
- * before it breaks the current, and `Down` keeps serving one that a rotation failed to replace.
61
- */
62
- type State<A> = {
64
+ interface Held<A, H> extends Live<A> {
65
+ readonly holds: ReadonlySet<H>;
66
+ }
67
+ /** Release ownership remains here until the provider and generation scope have finished. */
68
+ interface Drain<A, H> {
69
+ readonly _tag: "Draining" | "Releasing" | "Leaving";
70
+ readonly live: Held<A, H>;
71
+ readonly ending: Draining;
72
+ }
73
+ /** One serving generation, one shared acquisition, and prior generations still owned. */
74
+ type State<A, H = unknown> = ({
63
75
  readonly _tag: "Opening";
64
76
  readonly number: number;
65
- readonly serving: Live<A> | undefined;
77
+ readonly serving: Held<A, H> | undefined;
66
78
  } | {
67
79
  readonly _tag: "Open";
68
- readonly serving: Live<A>;
80
+ readonly serving: Held<A, H>;
69
81
  } | {
70
82
  readonly _tag: "Down";
71
83
  readonly number: number;
72
84
  readonly detail: string;
73
85
  readonly cause: unknown;
74
- readonly serving: Live<A> | undefined;
86
+ readonly serving: Held<A, H> | undefined;
75
87
  } | {
76
88
  readonly _tag: "Retired";
89
+ }) & {
90
+ readonly draining: ReadonlyMap<number, Drain<A, H>>;
77
91
  };
78
- type Input<A> = {
92
+ type Input<A, H = unknown> = {
79
93
  readonly _tag: "Opened";
80
94
  readonly live: Live<A>;
81
- } |
82
- /** The open of `number` failed for good. */
83
- {
95
+ } | {
84
96
  readonly _tag: "Failed";
85
97
  readonly number: number;
86
98
  readonly detail: string;
87
99
  readonly cause: unknown;
88
- } |
89
- /** The open of `number` was stopped, as on retiring, before it opened. */
90
- {
100
+ } | {
91
101
  readonly _tag: "Abandoned";
92
102
  readonly number: number;
93
103
  } | {
94
104
  readonly _tag: "Lost";
95
105
  readonly number: number;
96
106
  readonly cause: DisconnectCause;
97
- } |
98
- /** A caller asks for the next generation, or the rotation time of `due` came. */
99
- {
107
+ } | {
100
108
  readonly _tag: "Rotate";
101
109
  readonly due?: number | undefined;
102
110
  } | {
103
111
  readonly _tag: "Retire";
104
- } |
105
- /** Stop as `Retire` does, but leave the serving generation running. */
106
- {
112
+ } | {
107
113
  readonly _tag: "Keep";
114
+ } | {
115
+ readonly _tag: "Hold";
116
+ readonly number?: number | undefined;
117
+ readonly hold: H;
118
+ } | {
119
+ readonly _tag: "Let";
120
+ readonly number: number;
121
+ readonly hold: H;
122
+ } | {
123
+ readonly _tag: "Revoke";
124
+ readonly number: number;
108
125
  } | {
109
126
  readonly _tag: "Released";
110
127
  readonly number: number;
111
- readonly released: Released;
128
+ readonly released?: Released | undefined;
112
129
  };
113
- type Command<A> =
114
- /** Open `number`, after the release of `after` has finished. */
115
- {
130
+ type Command<A> = {
116
131
  readonly _tag: "Open";
117
132
  readonly number: number;
118
133
  readonly after: number | undefined;
119
- } |
120
- /** Watch a generation that opened for its loss and its rotation time. */
121
- {
134
+ } | {
122
135
  readonly _tag: "Watch";
123
136
  readonly live: Live<A>;
124
- } | {
125
- readonly _tag: "Release";
137
+ } |
138
+ /** Signal ending and arrange revocation. Repeated only when loss/retirement accelerates it. */
139
+ {
140
+ readonly _tag: "Drain";
126
141
  readonly live: Live<A>;
142
+ readonly ending: Draining;
127
143
  } |
128
- /** Close its scope without asking its provider to release it. */
144
+ /** Revoke/close holds, await their Let inputs, release the provider, then close its scope. */
129
145
  {
146
+ readonly _tag: "Release";
147
+ readonly live: Live<A>;
148
+ readonly ending: Draining;
149
+ } | {
130
150
  readonly _tag: "Leave";
131
151
  readonly live: Live<A>;
152
+ readonly ending: Draining;
132
153
  };
133
- interface Step<A> {
134
- readonly state: State<A>;
135
- /** Generation numbers and their new states, to publish in this order. */
154
+ interface Step<A, H = unknown> {
155
+ readonly state: State<A, H>;
136
156
  readonly events: ReadonlyArray<readonly [number, GenerationState]>;
137
157
  readonly commands: ReadonlyArray<Command<A>>;
138
- /** For `Rotate`, the generation to wait for, unless retired. */
139
158
  readonly reply?: number | undefined;
159
+ readonly borrowed?: Live<A> | undefined;
140
160
  }
141
- declare const start: <A>() => Step<A>;
142
- /** The state after `input`, with the events to publish and the commands to run. */
143
- declare const transition: <A>(state: State<A>, input: Input<A>, exclusive: boolean) => Step<A>;
161
+ declare const start: <A, H = unknown>() => Step<A, H>;
162
+ /** The holds still owned for a generation, including one whose release has begun. */
163
+ declare const lookup: <A, H>(state: State<A, H>, number: number) => Held<A, H> | undefined;
164
+ /** Pure admission and lifecycle changes. A rejected hold preserves state identity. */
165
+ declare const transition: <A, H = unknown>(state: State<A, H>, input: Input<A, H>, exclusive: boolean, now?: number, handover?: number) => Step<A, H>;
144
166
  type Resolution<A> = {
145
167
  readonly _tag: "Serve";
146
168
  readonly live: Live<A>;
@@ -150,18 +172,16 @@ type Resolution<A> = {
150
172
  readonly detail: string;
151
173
  readonly cause?: unknown;
152
174
  };
153
- /**
154
- * What a caller waiting for generation `target`, or for any when it is undefined, gets in
155
- * `state`; undefined while it should go on waiting.
156
- */
157
- declare const resolve: <A>(state: State<A>, target: number | undefined) => Resolution<A> | undefined;
175
+ /** An unheld browser resolution; use obtains its hold atomically through Hold instead. */
176
+ declare const resolve: <A, H>(state: State<A, H>, target: number | undefined) => Resolution<A> | undefined;
158
177
  declare namespace Supervisor_d_exports {
159
- export { Closed, Down, Generation, GenerationState, Kept, Lost, Open, Opened, Opening, Options, Released, Reopening, Settled, Supervisor, Unavailable, Unconfirmed, make };
178
+ export { Closed, Down, Draining, Generation, GenerationState, Kept, Lease, Lost, Open, Opened, Opening, Options, Released, Reopening, Settled, Superseded, Supervisor, Unavailable, Unconfirmed, UseOptions, make };
160
179
  }
161
180
  declare const Generation_base: Schema.Class<Generation, Schema.Struct<{
162
181
  readonly number: Schema.Int;
163
182
  readonly at: Schema.Finite;
164
- readonly state: Schema.Union<readonly [typeof Opening, typeof Reopening, typeof Open, typeof Lost, typeof Down, typeof Closed, typeof Kept]>;
183
+ readonly hostTime: Schema.Finite;
184
+ readonly state: Schema.Union<readonly [typeof Opening, typeof Reopening, typeof Open, typeof Draining, typeof Lost, typeof Down, typeof Closed, typeof Kept]>;
165
185
  }>, {}>;
166
186
  /** One change in a generation's life. `at` is wall-clock epoch milliseconds from the Effect `Clock`. */
167
187
  declare class Generation extends Generation_base {}
@@ -178,7 +198,8 @@ declare class Unavailable extends Unavailable_base {
178
198
  get message(): string;
179
199
  }
180
200
  /** One generation, as a provider opened it in the scope the supervisor gave it. */
181
- interface Opened {
201
+ interface Opened<A = void> {
202
+ readonly value: A;
182
203
  readonly browser: Service;
183
204
  /**
184
205
  * End the generation, and say whether the provider confirmed its end. The supervisor asks it
@@ -187,9 +208,9 @@ interface Opened {
187
208
  */
188
209
  readonly release?: Effect.Effect<Released> | undefined;
189
210
  }
190
- interface Options<E, R> {
211
+ interface Options<E, R, A = void> {
191
212
  /** Opens one generation in the scope it is given. */
192
- readonly open: Effect.Effect<Opened, E, R>;
213
+ readonly open: Effect.Effect<Opened<A>, E, R>;
193
214
  /** When to try a failed open again. Defaults to exponential from 1 second, for 5 minutes. */
194
215
  readonly reopen?: Schedule.Schedule<unknown, E> | undefined;
195
216
  /**
@@ -204,6 +225,8 @@ interface Options<E, R> {
204
225
  readonly rotateBefore?: Duration.Input | undefined;
205
226
  /** Generations must not overlap: a rotation releases the current one before it opens the next. */
206
227
  readonly exclusive?: boolean | undefined;
228
+ /** Finite, nonnegative time outgoing holds may finish; defaults to 30 seconds. */
229
+ readonly handover?: Duration.Input | undefined;
207
230
  /**
208
231
  * Closing the scope leaves the serving generation running, `Kept`: it closes the generation's
209
232
  * scope but never asks `release`. For providers whose scope only disconnects, such as
@@ -213,7 +236,29 @@ interface Options<E, R> {
213
236
  /** How long `browser` and `rotate` wait for a generation to open. Defaults to 2 minutes. */
214
237
  readonly waitTimeout?: Duration.Input | undefined;
215
238
  }
216
- interface Supervisor {
239
+ declare const Superseded_base: Schema.Class<Superseded, Schema.TaggedStruct<"Superseded", {
240
+ readonly generation: Schema.Int;
241
+ readonly reason: Schema.Literals<readonly ["rotated", "lost", "retired"]>;
242
+ readonly revokeAt: Schema.Finite;
243
+ }>, Cause.YieldableError>;
244
+ /** Work whose generation was revoked; input receipts still determine whether input reached a site. */
245
+ declare class Superseded extends Superseded_base {}
246
+ interface Lease<A> {
247
+ readonly generation: number;
248
+ readonly browser: Service;
249
+ readonly value: A;
250
+ /** First notice that serving ended; loss or retirement may revoke before its deadline. */
251
+ readonly ending: Effect.Effect<Superseded>;
252
+ /** An independent scope; its resources close before provider release. Fails once revoked. */
253
+ readonly retain: Effect.Effect<Scope.Closeable, Superseded>;
254
+ }
255
+ interface UseOptions {
256
+ /** Finite, nonnegative wait for a serving generation. Zero makes an immediate attempt. */
257
+ readonly wait?: Duration.Input | undefined;
258
+ }
259
+ interface Supervisor<A = void> {
260
+ /** Scoped work on the current generation. Revocation interrupts it and waits for its cleanup. */
261
+ readonly use: <B, E, R>(work: (lease: Lease<A>) => Effect.Effect<B, E, R>, options?: UseOptions) => Effect.Effect<B, E | Unavailable | Superseded, Exclude<R, Scope.Scope>>;
217
262
  /** The current generation's browser, waiting up to `waitTimeout` while none is open. */
218
263
  readonly browser: Effect.Effect<Service, Unavailable>;
219
264
  /** Each generation's changes, in order. A new reader first gets the latest 64; ends on retiring. */
@@ -223,8 +268,13 @@ interface Supervisor {
223
268
  /** Stop opening at once, release every generation, and wait until each release has finished. */
224
269
  readonly retire: Effect.Effect<void>;
225
270
  }
226
- /** Supervise generations of a provider's browser for as long as the scope is open. */
227
- declare const make: <E, R>(options: Options<E, R>) => Effect.Effect<Supervisor, never, Scope.Scope | Exclude<R, Scope.Scope>>;
271
+ type Provider = Omit<Opened, "value">;
272
+ type MakeOptions<E, R, A> = Omit<Options<E, R, A>, "open"> & {
273
+ readonly open: Effect.Effect<Opened<A> | Provider, E, R>;
274
+ };
275
+ /** Supervise scoped generation values. Browser-only providers retain their existing void value. */
276
+ declare function make<E, R, A = void>(options: Options<E, R, A>): Effect.Effect<Supervisor<A>, never, Scope.Scope | Exclude<R, Scope.Scope>>;
277
+ declare function make<E, R>(options: MakeOptions<E, R, never>): Effect.Effect<Supervisor<void>, never, Scope.Scope | Exclude<R, Scope.Scope>>;
228
278
  //#endregion
229
- export { Settled as _, Supervisor_d_exports as a, Closed as c, Kept as d, Lost as f, Reopening as g, Released as h, Supervisor as i, Down as l, Opening as m, Opened as n, Unavailable as o, Open as p, Options as r, make as s, Generation as t, GenerationState as u, Unconfirmed as v };
230
- //# sourceMappingURL=Supervisor-CLJaT7rG.d.mts.map
279
+ export { Unconfirmed as S, Open as _, Superseded as a, Reopening as b, Unavailable as c, Closed as d, Down as f, Lost as g, Kept as h, Options as i, UseOptions as l, GenerationState as m, Lease as n, Supervisor as o, Draining as p, Opened as r, Supervisor_d_exports as s, Generation as t, make as u, Opening as v, Settled as x, Released as y };
280
+ //# sourceMappingURL=Supervisor-CYWFK3vM.d.mts.map