effect-browser 0.3.0-beta.14 → 0.3.0-beta.16
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 +24 -11
- package/dist/{Browser-BBSySyjT.mjs → Browser-DRPthkNO.mjs} +4 -4
- package/dist/Browser-DRPthkNO.mjs.map +1 -0
- package/dist/Browser.d.mts +2 -2
- package/dist/Browser.mjs +1 -1
- package/dist/{BrowserEvent-BRu2juEz.d.mts → BrowserEvent-dq6FRT8w.d.mts} +9 -9
- package/dist/BrowserEvent.d.mts +1 -1
- package/dist/BrowserEvent.mjs +7 -12
- package/dist/BrowserEvent.mjs.map +1 -1
- package/dist/Cdp.mjs +2 -2
- package/dist/{Change-iMlcuzVD.d.mts → Change-DqING3tr.d.mts} +2 -2
- package/dist/Change.d.mts +1 -1
- package/dist/Chromium.mjs +2 -2
- package/dist/Expect.d.mts +1 -1
- package/dist/Expect.mjs +2 -2
- package/dist/Moment.d.mts +1 -1
- package/dist/Moment.mjs +1 -1
- package/dist/Motion-DND6I-64.d.mts +40 -0
- package/dist/Motion.d.mts +2 -40
- package/dist/Motion.mjs +1 -2
- package/dist/Motion.mjs.map +1 -1
- package/dist/{Page-B5cjaUp5.mjs → Page-C_VkBVwI.mjs} +5 -9
- package/dist/Page-C_VkBVwI.mjs.map +1 -0
- package/dist/{Page-DL9eylp3.d.mts → Page-jHmSJeOS.d.mts} +6 -13
- package/dist/Page.d.mts +1 -1
- package/dist/Page.mjs +1 -1
- package/dist/{Plan-6As6vcwT.d.mts → Plan-DLtRksuu.d.mts} +3 -3
- package/dist/{Plan-CXA2BW-G.mjs → Plan-X-JkACAB.mjs} +6 -9
- package/dist/Plan-X-JkACAB.mjs.map +1 -0
- package/dist/Plan.d.mts +1 -1
- package/dist/Plan.mjs +1 -1
- package/dist/Policy.d.mts +1 -1
- package/dist/Policy.mjs +1 -1
- package/dist/Presentation.d.mts +2 -2
- package/dist/Presentation.mjs +3 -2
- package/dist/Presentation.mjs.map +1 -1
- package/dist/Stage.d.mts +1 -1
- package/dist/Stage.mjs +3 -3
- package/dist/Stage.mjs.map +1 -1
- package/dist/{Supervisor-BE2sGHBK.mjs → Supervisor-BgWP8o9t.mjs} +116 -66
- package/dist/Supervisor-BgWP8o9t.mjs.map +1 -0
- package/dist/{Supervisor-W60pZWHh.d.mts → Supervisor-mVPeEUp8.d.mts} +33 -12
- package/dist/Supervisor.d.mts +2 -2
- package/dist/Supervisor.mjs +2 -2
- package/dist/{context-BAjRj7Xo.mjs → context-rYTk-3Cv.mjs} +68 -3
- package/dist/context-rYTk-3Cv.mjs.map +1 -0
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +4 -4
- package/dist/match.inpage-xOe9LNO0.mjs.map +1 -1
- package/dist/{page-B92QnRfT.mjs → page-ClEMFnBG.mjs} +48 -110
- package/dist/page-ClEMFnBG.mjs.map +1 -0
- package/package.json +1 -1
- package/src/Browser.ts +1 -1
- package/src/BrowserEvent.ts +7 -12
- package/src/Motion.ts +1 -2
- package/src/Page.ts +5 -9
- package/src/Plan.ts +1 -2
- package/src/Presentation.ts +2 -1
- package/src/Stage.ts +2 -1
- package/src/Supervisor.ts +168 -166
- package/src/internal/input/actions.ts +25 -33
- package/src/internal/input/dispatch.ts +3 -4
- package/src/internal/input/edit.inpage.ts +3 -9
- package/src/internal/input/evidence.inpage.ts +2 -20
- package/src/internal/input/guard.inpage.ts +9 -29
- package/src/internal/input/guard.ts +12 -1
- package/src/internal/input/targets.inpage.ts +4 -8
- package/src/internal/input/targets.ts +8 -11
- package/src/internal/page/context.ts +2 -2
- package/src/internal/pictures/capture.ts +4 -4
- package/src/internal/pictures/clock.ts +8 -3
- package/src/internal/pictures/pictures.ts +2 -1
- package/src/internal/reading/controls.inpage.ts +8 -1
- package/src/internal/reading/match.inpage.ts +2 -1
- package/src/internal/reading/outline.inpage.ts +7 -4
- package/src/internal/reading/reading.ts +5 -16
- package/src/internal/supervisor/lifecycle.ts +73 -20
- package/dist/Browser-BBSySyjT.mjs.map +0 -1
- package/dist/Page-B5cjaUp5.mjs.map +0 -1
- package/dist/Plan-CXA2BW-G.mjs.map +0 -1
- package/dist/Supervisor-BE2sGHBK.mjs.map +0 -1
- package/dist/context-BAjRj7Xo.mjs.map +0 -1
- package/dist/page-B92QnRfT.mjs.map +0 -1
package/package.json
CHANGED
package/src/Browser.ts
CHANGED
|
@@ -492,7 +492,7 @@ export const make = Effect.fn("Browser.make")(function* (
|
|
|
492
492
|
) {
|
|
493
493
|
const clock = yield* Clock.Clock;
|
|
494
494
|
const connection = yield* Identity.make;
|
|
495
|
-
const now = () =>
|
|
495
|
+
const now = () => BrowserClock.monotonicMillis(clock);
|
|
496
496
|
const { settings, eventHistory, maxPages } = yield* settingsOf(options);
|
|
497
497
|
// Measured on first need: the first capture of any page measures it, and input never waits.
|
|
498
498
|
const mapping = BrowserClock.mapping(now);
|
package/src/BrowserEvent.ts
CHANGED
|
@@ -119,8 +119,7 @@ const Button = Schema.Literals(["left", "right", "middle"]);
|
|
|
119
119
|
|
|
120
120
|
/** An element's box in viewport CSS pixels; it may lie partly or wholly outside the viewport. */
|
|
121
121
|
export const Box = Schema.Struct({
|
|
122
|
-
|
|
123
|
-
y: Schema.Finite,
|
|
122
|
+
...Motion.Point.fields,
|
|
124
123
|
width: Schema.Finite,
|
|
125
124
|
height: Schema.Finite,
|
|
126
125
|
});
|
|
@@ -169,8 +168,8 @@ export class Action extends Schema.TaggedClass<Action>()("Action", {
|
|
|
169
168
|
/** For an action at a point, the box of what it found there. */
|
|
170
169
|
box: Schema.optional(Box),
|
|
171
170
|
text: Schema.optional(Schema.String),
|
|
172
|
-
x: Schema.optional(
|
|
173
|
-
y: Schema.optional(
|
|
171
|
+
x: Schema.optional(Motion.Point.fields.x),
|
|
172
|
+
y: Schema.optional(Motion.Point.fields.y),
|
|
174
173
|
ok: Schema.Boolean,
|
|
175
174
|
dispatched: Schema.Boolean,
|
|
176
175
|
error: Schema.optional(Schema.String),
|
|
@@ -196,16 +195,14 @@ export class TrackPerformed extends Schema.TaggedClass<TrackPerformed>()("TrackP
|
|
|
196
195
|
page: Schema.String,
|
|
197
196
|
plan: Sequence,
|
|
198
197
|
dispatched: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
199
|
-
|
|
200
|
-
y: Schema.Finite,
|
|
198
|
+
...Motion.Point.fields,
|
|
201
199
|
complete: Schema.Boolean,
|
|
202
200
|
}) {}
|
|
203
201
|
|
|
204
202
|
export class PointerPressed extends Schema.TaggedClass<PointerPressed>()("PointerPressed", {
|
|
205
203
|
at: Schema.Finite,
|
|
206
204
|
page: Schema.String,
|
|
207
|
-
|
|
208
|
-
y: Schema.Finite,
|
|
205
|
+
...Motion.Point.fields,
|
|
209
206
|
button: Button,
|
|
210
207
|
clickCount: Schema.Int.check(Schema.isGreaterThan(0)),
|
|
211
208
|
}) {}
|
|
@@ -213,8 +210,7 @@ export class PointerPressed extends Schema.TaggedClass<PointerPressed>()("Pointe
|
|
|
213
210
|
export class PointerReleased extends Schema.TaggedClass<PointerReleased>()("PointerReleased", {
|
|
214
211
|
at: Schema.Finite,
|
|
215
212
|
page: Schema.String,
|
|
216
|
-
|
|
217
|
-
y: Schema.Finite,
|
|
213
|
+
...Motion.Point.fields,
|
|
218
214
|
button: Button,
|
|
219
215
|
clickCount: Schema.Int.check(Schema.isGreaterThan(0)),
|
|
220
216
|
}) {}
|
|
@@ -222,8 +218,7 @@ export class PointerReleased extends Schema.TaggedClass<PointerReleased>()("Poin
|
|
|
222
218
|
export class WheelScrolled extends Schema.TaggedClass<WheelScrolled>()("WheelScrolled", {
|
|
223
219
|
at: Schema.Finite,
|
|
224
220
|
page: Schema.String,
|
|
225
|
-
|
|
226
|
-
y: Schema.Finite,
|
|
221
|
+
...Motion.Point.fields,
|
|
227
222
|
dx: Schema.Finite,
|
|
228
223
|
dy: Schema.Finite,
|
|
229
224
|
}) {}
|
package/src/Motion.ts
CHANGED
|
@@ -13,8 +13,7 @@ export const Point = Schema.Struct({ x: Schema.Finite, y: Schema.Finite });
|
|
|
13
13
|
export type Point = typeof Point.Type;
|
|
14
14
|
|
|
15
15
|
export const Sample = Schema.Struct({
|
|
16
|
-
|
|
17
|
-
y: Schema.Finite,
|
|
16
|
+
...Point.fields,
|
|
18
17
|
afterMillis: Schema.Finite.check(
|
|
19
18
|
Schema.isGreaterThanOrEqualTo(0),
|
|
20
19
|
Schema.isLessThanOrEqualTo(maximumDurationMillis),
|
package/src/Page.ts
CHANGED
|
@@ -27,12 +27,10 @@ import * as Guard from "./internal/input/guard.inpage.ts";
|
|
|
27
27
|
import { Correlation, FailFast } from "./internal/page/lane.ts";
|
|
28
28
|
import * as Url from "./internal/page/url.ts";
|
|
29
29
|
import type { Window } from "./Moment.ts";
|
|
30
|
+
import * as Motion from "./Motion.ts";
|
|
30
31
|
import { type Snapshot, type SnapshotOptions } from "./Snapshot.ts";
|
|
31
32
|
|
|
32
|
-
export
|
|
33
|
-
readonly x: number;
|
|
34
|
-
readonly y: number;
|
|
35
|
-
}
|
|
33
|
+
export type Point = Motion.Point;
|
|
36
34
|
|
|
37
35
|
/** A ref from a snapshot, such as `"e12"`, or a viewport point. */
|
|
38
36
|
export type Target = string | Point;
|
|
@@ -54,7 +52,7 @@ export class Zoom extends Schema.Class<Zoom>("effect-browser/Zoom")({
|
|
|
54
52
|
|
|
55
53
|
/** The target as it read before input. Point coordinates are never snapped to another position. */
|
|
56
54
|
export class ResolvedTarget extends Schema.Class<ResolvedTarget>("effect-browser/ResolvedTarget")({
|
|
57
|
-
point:
|
|
55
|
+
point: Motion.Point,
|
|
58
56
|
element: Schema.String,
|
|
59
57
|
/** The element's lowercase tag name. */
|
|
60
58
|
tag: Schema.String,
|
|
@@ -131,9 +129,7 @@ export interface ScreenshotOptions {
|
|
|
131
129
|
/** The oldest a reused frame may be, as for `frame`. Defaults to 250 milliseconds. */
|
|
132
130
|
readonly maxAge?: Duration.Input | undefined;
|
|
133
131
|
/** A region of the viewport, captured in CSS pixels at the viewport's own scale; always new. */
|
|
134
|
-
readonly clip?:
|
|
135
|
-
| { readonly x: number; readonly y: number; readonly width: number; readonly height: number }
|
|
136
|
-
| undefined;
|
|
132
|
+
readonly clip?: Box | undefined;
|
|
137
133
|
/** JPEG quality, 0 to 100. Defaults to 80. */
|
|
138
134
|
readonly quality?: number | undefined;
|
|
139
135
|
/** `"jpeg"`, the default, or `"png"`, which is always a new picture. */
|
|
@@ -335,7 +331,7 @@ export class InputRequest extends Schema.Class<InputRequest>("effect-browser/Inp
|
|
|
335
331
|
target: Schema.optional(Schema.String),
|
|
336
332
|
element: Schema.optional(Schema.String),
|
|
337
333
|
/** Literal pixel targets only; ref coordinates are resolved after the policy allows them. */
|
|
338
|
-
point: Schema.optional(
|
|
334
|
+
point: Schema.optional(Motion.Point),
|
|
339
335
|
text: Schema.optional(Schema.String),
|
|
340
336
|
role: Schema.optional(Schema.NullOr(Schema.String)),
|
|
341
337
|
name: Schema.optional(Schema.String),
|
package/src/Plan.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "./BrowserEvent.ts";
|
|
34
34
|
import { undispatched } from "./internal/page/context.ts";
|
|
35
35
|
import { Ambiguous, choose, Drifted, Missing } from "./internal/reading/choose.ts";
|
|
36
|
+
import { Point as ViewportPoint } from "./Motion.ts";
|
|
36
37
|
import {
|
|
37
38
|
type Found,
|
|
38
39
|
type Page,
|
|
@@ -44,8 +45,6 @@ import {
|
|
|
44
45
|
|
|
45
46
|
export { Ambiguous, Drifted, Missing } from "./internal/reading/choose.ts";
|
|
46
47
|
|
|
47
|
-
const ViewportPoint = Schema.Struct({ x: Schema.Finite, y: Schema.Finite });
|
|
48
|
-
|
|
49
48
|
export const StepAction = Schema.Literals([
|
|
50
49
|
"navigate",
|
|
51
50
|
"back",
|
package/src/Presentation.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { Clock, Duration, Effect, Fiber, MutableRef, Option, Semaphore } from "e
|
|
|
20
20
|
import type { BrowserError } from "./BrowserError.ts";
|
|
21
21
|
import * as Style from "./internal/input/style.ts";
|
|
22
22
|
import { type Internals, internalsOf, viewOf } from "./internal/page/page.ts";
|
|
23
|
+
import { monotonicMillis } from "./internal/pictures/clock.ts";
|
|
23
24
|
import * as Motion from "./Motion.ts";
|
|
24
25
|
import type { Page, Point, Target } from "./Page.ts";
|
|
25
26
|
|
|
@@ -81,7 +82,7 @@ export const make = Effect.fn("Presentation.make")(function* (options: Options =
|
|
|
81
82
|
const pacing = options.pacing ?? human;
|
|
82
83
|
const clock = yield* Clock.Clock;
|
|
83
84
|
const scope = yield* Effect.scope;
|
|
84
|
-
const now = () =>
|
|
85
|
+
const now = () => monotonicMillis(clock);
|
|
85
86
|
const pointer = MutableRef.make(Option.none<Point>());
|
|
86
87
|
const hand = yield* Semaphore.make(1);
|
|
87
88
|
|
package/src/Stage.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { type BrowserError, Closed, consequence, InvalidRequest, Timeout } from
|
|
|
26
26
|
import type { CaptureStats, Frame, ScreencastOptions } from "./Frame.ts";
|
|
27
27
|
import { failWith, undispatched } from "./internal/page/context.ts";
|
|
28
28
|
import { internalsOf } from "./internal/page/page.ts";
|
|
29
|
+
import { monotonicMillis } from "./internal/pictures/clock.ts";
|
|
29
30
|
import type { Page } from "./Page.ts";
|
|
30
31
|
|
|
31
32
|
export interface Options extends ScreencastOptions {
|
|
@@ -390,7 +391,7 @@ export const make = Effect.fn("Stage.make")(function* (
|
|
|
390
391
|
): Effect.fn.Return<Stage, BrowserError, Scope.Scope> {
|
|
391
392
|
const clock = yield* Clock.Clock;
|
|
392
393
|
const owner = yield* Effect.scope;
|
|
393
|
-
const now = () =>
|
|
394
|
+
const now = () => monotonicMillis(clock);
|
|
394
395
|
const waited = Duration.fromInput(options.firstFrame ?? Duration.seconds(5));
|
|
395
396
|
|
|
396
397
|
if (
|
package/src/Supervisor.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import {
|
|
22
22
|
Cause,
|
|
23
23
|
Clock,
|
|
24
|
+
Context,
|
|
24
25
|
DateTime,
|
|
25
26
|
Deferred,
|
|
26
27
|
Duration,
|
|
@@ -96,12 +97,24 @@ export interface Opened<A = void> {
|
|
|
96
97
|
export interface Admission {
|
|
97
98
|
readonly number: number;
|
|
98
99
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
100
|
+
* Every earlier session that stopped serving, including failed attempts and older drains.
|
|
101
|
+
* Waits for all pending releases; an Unconfirmed end remains visible to later attempts.
|
|
102
|
+
* Undefined before any release. A still-serving nonexclusive predecessor is excluded.
|
|
101
103
|
*/
|
|
102
104
|
readonly after: Effect.Effect<Lifecycle.Released> | undefined;
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Optional service supplied during `open`. Providers report failed-attempt cleanup here before
|
|
109
|
+
* their scope closes; successful generations instead return `Opened.release`. Reporting does
|
|
110
|
+
* not release resources or replace the original acquisition failure. Terminal `Down` and opening
|
|
111
|
+
* `Closed` states publish the reported outcomes of their own generation's tries.
|
|
112
|
+
*/
|
|
113
|
+
export class Releasing extends Context.Service<
|
|
114
|
+
Releasing,
|
|
115
|
+
{ readonly released: (outcome: Lifecycle.Released) => Effect.Effect<void> }
|
|
116
|
+
>()("effect-browser/Supervisor/Releasing") {}
|
|
117
|
+
|
|
105
118
|
export interface Options<E, R, A = void> {
|
|
106
119
|
/** Opens one generation in the scope it is given. */
|
|
107
120
|
readonly open: Effect.Effect<Opened<A>, E, R>;
|
|
@@ -186,9 +199,9 @@ interface Held<A> {
|
|
|
186
199
|
}
|
|
187
200
|
|
|
188
201
|
type State<A> = Lifecycle.State<Held<A>, Hold>;
|
|
189
|
-
type
|
|
190
|
-
|
|
191
|
-
) => Effect.Effect<
|
|
202
|
+
type Input<A> = Lifecycle.Input<Held<A>, Hold>;
|
|
203
|
+
type Step<A> = Lifecycle.Step<Held<A>, Hold>;
|
|
204
|
+
type Apply<A> = (input: Input<A>) => Effect.Effect<Step<A>>;
|
|
192
205
|
|
|
193
206
|
const superseded = (generation: number, { reason, revokeAt }: Lifecycle.Draining) =>
|
|
194
207
|
new Superseded({ generation, reason, revokeAt });
|
|
@@ -201,15 +214,6 @@ const describe = (cause: Cause.Cause<unknown>) => {
|
|
|
201
214
|
return Predicate.isError(error) ? error.message : String(error);
|
|
202
215
|
};
|
|
203
216
|
|
|
204
|
-
const unavailable = (
|
|
205
|
-
resolution: Extract<Lifecycle.Resolution<unknown>, { readonly _tag: "Unavailable" }>,
|
|
206
|
-
) =>
|
|
207
|
-
new Unavailable({
|
|
208
|
-
reason: resolution.reason,
|
|
209
|
-
detail: resolution.detail,
|
|
210
|
-
cause: resolution.cause,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
217
|
const timedOut = () =>
|
|
214
218
|
new Unavailable({ reason: "opening", detail: "no generation opened within the wait" });
|
|
215
219
|
|
|
@@ -234,7 +238,7 @@ const waitFor = <A, H>(
|
|
|
234
238
|
const result = (resolution: Lifecycle.Resolution<A>) =>
|
|
235
239
|
resolution._tag === "Serve"
|
|
236
240
|
? Effect.succeed(resolution.live)
|
|
237
|
-
: Effect.fail(
|
|
241
|
+
: Effect.fail(new Unavailable(resolution));
|
|
238
242
|
|
|
239
243
|
return Effect.suspend(() => {
|
|
240
244
|
const current = Lifecycle.resolve(state.value, target);
|
|
@@ -277,20 +281,19 @@ const leasing = <A>(
|
|
|
277
281
|
now: () => number,
|
|
278
282
|
clock: Clock.Clock,
|
|
279
283
|
) => {
|
|
280
|
-
const borrow = (hold: Hold, wait: Duration.Duration)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
});
|
|
284
|
+
const borrow = Effect.fnUntraced(function* (hold: Hold, wait: Duration.Duration) {
|
|
285
|
+
const until = now() + Duration.toMillis(wait);
|
|
286
|
+
|
|
287
|
+
for (;;) {
|
|
288
|
+
const step = yield* apply({ _tag: "Hold", hold });
|
|
289
|
+
|
|
290
|
+
if (step.borrowed !== undefined) return step.borrowed;
|
|
291
|
+
yield* waitFor(state, Duration.millis(Math.max(0, until - now())), clock).pipe(
|
|
292
|
+
Effect.interruptible,
|
|
293
|
+
);
|
|
294
|
+
if (now() >= until) return yield* timedOut();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
294
297
|
|
|
295
298
|
const leaseOf = (live: Lifecycle.Live<Held<A>>): Lease<A> => ({
|
|
296
299
|
generation: live.number,
|
|
@@ -373,11 +376,6 @@ const acquireGeneration = <A, E, R>(
|
|
|
373
376
|
),
|
|
374
377
|
);
|
|
375
378
|
|
|
376
|
-
const changedUntil = <A>(
|
|
377
|
-
state: SubscriptionRef.SubscriptionRef<State<A>>,
|
|
378
|
-
predicate: (current: State<A>) => boolean,
|
|
379
|
-
) => SubscriptionRef.changes(state).pipe(Stream.filter(predicate), Stream.runHead, Effect.asVoid);
|
|
380
|
-
|
|
381
379
|
const watchGeneration = <A>(
|
|
382
380
|
live: Lifecycle.Live<Held<A>>,
|
|
383
381
|
rotateBefore: Duration.Duration | undefined,
|
|
@@ -400,59 +398,61 @@ const watchGeneration = <A>(
|
|
|
400
398
|
};
|
|
401
399
|
|
|
402
400
|
// After revocation, retained resources and use finalizers let go before the provider.
|
|
403
|
-
const releaseGeneration = <A>(
|
|
401
|
+
const releaseGeneration = Effect.fnUntraced(function* <A>(
|
|
404
402
|
command: Extract<Lifecycle.Command<Held<A>>, { readonly _tag: "Release" | "Leave" }>,
|
|
405
403
|
state: SubscriptionRef.SubscriptionRef<State<A>>,
|
|
406
404
|
apply: Apply<A>,
|
|
407
|
-
)
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
{ concurrency: 8 },
|
|
417
|
-
);
|
|
405
|
+
) {
|
|
406
|
+
const { live } = command;
|
|
407
|
+
|
|
408
|
+
const cleaned = yield* Effect.forEach(
|
|
409
|
+
Lifecycle.lookup(state.value, live.number)?.holds ?? [],
|
|
410
|
+
(hold) =>
|
|
411
|
+
Effect.exit(hold._tag === "Retained" ? Scope.close(hold.scope, Exit.void) : Effect.void),
|
|
412
|
+
{ concurrency: 8 },
|
|
413
|
+
);
|
|
418
414
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
415
|
+
yield* SubscriptionRef.changes(state).pipe(
|
|
416
|
+
Stream.filter((current) => (Lifecycle.lookup(current, live.number)?.holds.size ?? 0) === 0),
|
|
417
|
+
Stream.runHead,
|
|
418
|
+
);
|
|
423
419
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
),
|
|
420
|
+
const released =
|
|
421
|
+
command._tag === "Leave"
|
|
422
|
+
? undefined
|
|
423
|
+
: yield* (live.value.opened.release ?? Effect.succeed(new Lifecycle.Settled())).pipe(
|
|
424
|
+
Effect.catchCause((cause) =>
|
|
425
|
+
Effect.succeed(
|
|
426
|
+
new Lifecycle.Unconfirmed({ detail: `the release failed: ${describe(cause)}` }),
|
|
432
427
|
),
|
|
433
|
-
)
|
|
428
|
+
),
|
|
429
|
+
);
|
|
434
430
|
|
|
435
|
-
|
|
431
|
+
const closed = yield* Effect.exit(Scope.close(live.value.scope, Exit.void));
|
|
436
432
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
433
|
+
yield* apply({ _tag: "Released", number: live.number, released });
|
|
434
|
+
if (released !== undefined) yield* Deferred.succeed(live.value.released, released);
|
|
435
|
+
yield* Exit.asVoidAll([...cleaned, closed]);
|
|
436
|
+
});
|
|
441
437
|
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
: { _tag: "Failed", number, detail: describe(exit.cause), cause: Cause.squash(exit.cause) };
|
|
451
|
-
|
|
452
|
-
const admissionAfter = <A>(previous: Lifecycle.Live<Held<A>> | undefined, state: State<A>) =>
|
|
453
|
-
previous === undefined || (state._tag !== "Retired" && state.serving?.number === previous.number)
|
|
438
|
+
const admissionAfter = <A>(state: State<A>) => {
|
|
439
|
+
const released = state.released;
|
|
440
|
+
|
|
441
|
+
const pending = [...state.draining.values()]
|
|
442
|
+
.filter((drain) => drain._tag !== "Leaving")
|
|
443
|
+
.map(({ live }) => live.value.released);
|
|
444
|
+
|
|
445
|
+
return pending.length === 0 && released === undefined
|
|
454
446
|
? undefined
|
|
455
|
-
:
|
|
447
|
+
: Effect.gen(function* () {
|
|
448
|
+
let outcome = released;
|
|
449
|
+
|
|
450
|
+
for (const release of pending)
|
|
451
|
+
outcome = Lifecycle.combine(outcome, yield* Deferred.await(release));
|
|
452
|
+
|
|
453
|
+
return outcome ?? new Lifecycle.Settled();
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
456
|
|
|
457
457
|
const makeScoped = Effect.fn("Supervisor.make")(function* <E, R, A>(
|
|
458
458
|
options: MakeOptions<E, R, A>,
|
|
@@ -462,7 +462,6 @@ const makeScoped = Effect.fn("Supervisor.make")(function* <E, R, A>(
|
|
|
462
462
|
const context = yield* Effect.context<Exclude<R, Scope.Scope>>();
|
|
463
463
|
const own = yield* Scope.fork(yield* Effect.scope);
|
|
464
464
|
const exclusive = options.exclusive ?? false;
|
|
465
|
-
const definite = options.definite ?? (() => false);
|
|
466
465
|
|
|
467
466
|
const reopen: Schedule.Schedule<unknown, E> =
|
|
468
467
|
options.reopen ??
|
|
@@ -486,113 +485,116 @@ const makeScoped = Effect.fn("Supervisor.make")(function* <E, R, A>(
|
|
|
486
485
|
const runOpen = yield* FiberHandle.runtime(opening)();
|
|
487
486
|
const runRelease = yield* FiberMap.runtime(releases)();
|
|
488
487
|
|
|
489
|
-
const apply = (
|
|
490
|
-
input:
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
.withPermit(
|
|
494
|
-
Effect.gen(function* () {
|
|
495
|
-
const current = state.value;
|
|
496
|
-
const step = Lifecycle.transition(current, input, exclusive, now(), handover);
|
|
488
|
+
const apply: Apply<A | void> = Effect.fnUntraced(
|
|
489
|
+
function* (input: Input<A | void>) {
|
|
490
|
+
const current = state.value;
|
|
491
|
+
const step = Lifecycle.transition(current, input, exclusive, now(), handover);
|
|
497
492
|
|
|
498
|
-
|
|
499
|
-
|
|
493
|
+
if (step.state !== current) yield* SubscriptionRef.set(state, step.state);
|
|
494
|
+
yield* perform(step);
|
|
495
|
+
|
|
496
|
+
return step;
|
|
497
|
+
},
|
|
498
|
+
(effect) => applying.withPermit(effect),
|
|
499
|
+
Effect.uninterruptible,
|
|
500
|
+
Effect.provideService(Clock.Clock, clock),
|
|
501
|
+
);
|
|
500
502
|
|
|
501
|
-
|
|
503
|
+
const perform = Effect.fnUntraced(function* (step: Step<A | void>): Effect.fn.Return<void> {
|
|
504
|
+
for (const [number, generation] of step.events)
|
|
505
|
+
PubSub.publishUnsafe(events, [
|
|
506
|
+
new Generation({
|
|
507
|
+
number,
|
|
508
|
+
at: clock.currentTimeMillisUnsafe(),
|
|
509
|
+
hostTime: now(),
|
|
510
|
+
state: generation,
|
|
502
511
|
}),
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
yield* Effect.forkIn(
|
|
524
|
-
Effect.provideContext(watchGeneration(command.live, rotateBefore, apply), context),
|
|
525
|
-
command.live.value.scope,
|
|
526
|
-
);
|
|
527
|
-
break;
|
|
528
|
-
case "Drain": {
|
|
529
|
-
const ending = superseded(command.live.number, command.ending);
|
|
530
|
-
|
|
531
|
-
yield* Deferred.succeed(command.live.value.ending, ending);
|
|
532
|
-
yield* Effect.forkIn(
|
|
533
|
-
until(ending.revokeAt, now).pipe(
|
|
534
|
-
Effect.andThen(
|
|
535
|
-
Effect.suspend(() => apply({ _tag: "Revoke", number: command.live.number })),
|
|
536
|
-
),
|
|
512
|
+
]);
|
|
513
|
+
for (const command of step.commands)
|
|
514
|
+
switch (command._tag) {
|
|
515
|
+
case "Open":
|
|
516
|
+
runOpen(openGeneration(command.number));
|
|
517
|
+
break;
|
|
518
|
+
case "Watch":
|
|
519
|
+
yield* Effect.forkIn(
|
|
520
|
+
Effect.provideContext(watchGeneration(command.live, rotateBefore, apply), context),
|
|
521
|
+
command.live.value.scope,
|
|
522
|
+
);
|
|
523
|
+
break;
|
|
524
|
+
case "Drain": {
|
|
525
|
+
const ending = superseded(command.live.number, command.ending);
|
|
526
|
+
|
|
527
|
+
yield* Deferred.succeed(command.live.value.ending, ending);
|
|
528
|
+
yield* Effect.forkIn(
|
|
529
|
+
until(ending.revokeAt, now).pipe(
|
|
530
|
+
Effect.andThen(
|
|
531
|
+
Effect.suspend(() => apply({ _tag: "Revoke", number: command.live.number })),
|
|
537
532
|
),
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
case "Release":
|
|
543
|
-
case "Leave":
|
|
544
|
-
yield* Deferred.succeed(
|
|
545
|
-
command.live.value.revoked,
|
|
546
|
-
superseded(command.live.number, command.ending),
|
|
547
|
-
);
|
|
548
|
-
runRelease(command.live.number, releaseGeneration(command, state, apply));
|
|
549
|
-
break;
|
|
533
|
+
),
|
|
534
|
+
command.live.value.scope,
|
|
535
|
+
);
|
|
536
|
+
break;
|
|
550
537
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
538
|
+
case "Release":
|
|
539
|
+
case "Leave":
|
|
540
|
+
yield* Deferred.succeed(
|
|
541
|
+
command.live.value.revoked,
|
|
542
|
+
superseded(command.live.number, command.ending),
|
|
543
|
+
);
|
|
544
|
+
runRelease(command.live.number, releaseGeneration(command, state, apply));
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
555
548
|
|
|
549
|
+
const openGeneration = (number: number) => {
|
|
556
550
|
const openOnce = acquireGeneration(
|
|
557
|
-
Effect.
|
|
558
|
-
()
|
|
559
|
-
|
|
560
|
-
|
|
551
|
+
Effect.gen(function* () {
|
|
552
|
+
const after = admissionAfter(state.value);
|
|
553
|
+
|
|
554
|
+
if (exclusive && after !== undefined) yield* after;
|
|
555
|
+
yield* options.admit?.({ number, after }) ?? Effect.void;
|
|
556
|
+
|
|
557
|
+
return yield* options.open.pipe(
|
|
558
|
+
Effect.provideService(Releasing, {
|
|
559
|
+
released: (released) =>
|
|
560
|
+
apply({ _tag: "AttemptReleased", number, released }).pipe(Effect.asVoid),
|
|
561
|
+
}),
|
|
562
|
+
);
|
|
563
|
+
}),
|
|
561
564
|
own,
|
|
562
565
|
).pipe(Effect.provideContext(context));
|
|
563
566
|
|
|
564
567
|
return Effect.uninterruptibleMask((restore) =>
|
|
565
568
|
restore(
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
),
|
|
569
|
+
Effect.retry(openOnce, {
|
|
570
|
+
schedule: reopen,
|
|
571
|
+
while: (error) => !options.definite?.(error),
|
|
572
|
+
}),
|
|
571
573
|
).pipe(
|
|
572
574
|
Effect.exit,
|
|
573
|
-
Effect.flatMap((exit) =>
|
|
575
|
+
Effect.flatMap((exit) => {
|
|
576
|
+
if (Exit.isSuccess(exit))
|
|
577
|
+
return apply({ _tag: "Opened", live: { number, value: exit.value } });
|
|
578
|
+
const cause = exit.cause;
|
|
579
|
+
|
|
580
|
+
return apply(
|
|
581
|
+
Cause.hasInterruptsOnly(cause)
|
|
582
|
+
? { _tag: "Abandoned", number }
|
|
583
|
+
: { _tag: "Failed", number, detail: describe(cause), cause: Cause.squash(cause) },
|
|
584
|
+
);
|
|
585
|
+
}),
|
|
574
586
|
),
|
|
575
587
|
);
|
|
576
588
|
};
|
|
577
589
|
|
|
578
|
-
const use = leasing(state, apply, waitTimeout, now, clock);
|
|
579
|
-
|
|
580
590
|
const end = yield* Effect.cached(PubSub.publish(events, Exit.void));
|
|
581
591
|
|
|
582
|
-
const stop = (input:
|
|
583
|
-
apply(input)
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
Deferred.isDone(releases.deferred).pipe(
|
|
589
|
-
Effect.flatMap((failed) => (failed ? FiberMap.join(releases) : Effect.void)),
|
|
590
|
-
),
|
|
591
|
-
),
|
|
592
|
-
),
|
|
593
|
-
Effect.ensuring(end),
|
|
594
|
-
Effect.asVoid,
|
|
595
|
-
);
|
|
592
|
+
const stop = Effect.fnUntraced(function* (input: Input<A | void>) {
|
|
593
|
+
yield* apply(input);
|
|
594
|
+
yield* FiberHandle.clear(opening);
|
|
595
|
+
yield* FiberMap.awaitEmpty(releases);
|
|
596
|
+
if (yield* Deferred.isDone(releases.deferred)) yield* FiberMap.join(releases);
|
|
597
|
+
}, Effect.ensuring(end));
|
|
596
598
|
|
|
597
599
|
const retire = yield* Effect.cached(stop({ _tag: "Retire" }));
|
|
598
600
|
|
|
@@ -601,7 +603,7 @@ const makeScoped = Effect.fn("Supervisor.make")(function* <E, R, A>(
|
|
|
601
603
|
|
|
602
604
|
return {
|
|
603
605
|
browser: Effect.map(waitFor(state, waitTimeout, clock), (live) => live.value.opened.browser),
|
|
604
|
-
use,
|
|
606
|
+
use: leasing(state, apply, waitTimeout, now, clock),
|
|
605
607
|
states: Stream.fromPubSubTake(events),
|
|
606
608
|
rotate: apply({ _tag: "Rotate" }).pipe(
|
|
607
609
|
Effect.flatMap(({ reply }) =>
|