effect-browser 0.3.0-beta.11 → 0.3.0-beta.14
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 +76 -29
- package/dist/{Browser-B4qncxe7.mjs → Browser-BBSySyjT.mjs} +3 -3
- package/dist/{Browser-B4qncxe7.mjs.map → Browser-BBSySyjT.mjs.map} +1 -1
- package/dist/Browser.d.mts +1 -1
- package/dist/Browser.mjs +1 -1
- package/dist/Cdp.mjs +2 -2
- package/dist/Chromium.mjs +2 -2
- package/dist/Expect.d.mts +1 -1
- package/dist/Expect.mjs +2 -2
- package/dist/{Frame-Di2PxyF7.d.mts → Frame-DkNdjjjr.d.mts} +3 -5
- package/dist/Frame.d.mts +1 -1
- package/dist/Frame.mjs +2 -4
- package/dist/Frame.mjs.map +1 -1
- package/dist/Moment.d.mts +1 -1
- package/dist/Moment.mjs +8 -4
- package/dist/Moment.mjs.map +1 -1
- package/dist/{Page-B7X6DQUn.d.mts → Page-DL9eylp3.d.mts} +2 -2
- package/dist/Page.d.mts +1 -1
- package/dist/{Plan-Akcac63g.d.mts → Plan-6As6vcwT.d.mts} +16 -9
- package/dist/{Plan-D1wrJVs8.mjs → Plan-CXA2BW-G.mjs} +24 -17
- package/dist/Plan-CXA2BW-G.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/Presentation.d.mts +1 -1
- package/dist/Presentation.mjs +1 -1
- package/dist/Stage.d.mts +5 -4
- package/dist/Stage.mjs +55 -22
- package/dist/Stage.mjs.map +1 -1
- package/dist/{Supervisor-TNKNOVzu.mjs → Supervisor-BE2sGHBK.mjs} +67 -41
- package/dist/Supervisor-BE2sGHBK.mjs.map +1 -0
- package/dist/{Supervisor-CYWFK3vM.d.mts → Supervisor-W60pZWHh.d.mts} +24 -8
- package/dist/Supervisor.d.mts +2 -2
- package/dist/Supervisor.mjs +1 -1
- package/dist/{context-B6xr8nVi.mjs → context-BAjRj7Xo.mjs} +3 -1
- package/dist/{context-B6xr8nVi.mjs.map → context-BAjRj7Xo.mjs.map} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +3 -3
- package/dist/{page-BAf__WKX.mjs → page-B92QnRfT.mjs} +201 -172
- package/dist/page-B92QnRfT.mjs.map +1 -0
- package/package.json +1 -1
- package/src/Frame.ts +2 -4
- package/src/Moment.ts +14 -3
- package/src/Plan.ts +33 -21
- package/src/Stage.ts +72 -25
- package/src/Supervisor.ts +69 -37
- package/src/internal/input/settle.ts +8 -3
- package/src/internal/page/context.ts +8 -0
- package/src/internal/page/navigation.ts +21 -15
- package/src/internal/pictures/capture.ts +172 -188
- package/src/internal/pictures/pictures.ts +50 -73
- package/src/internal/pictures/quiescence.ts +94 -0
- package/src/internal/reading/ready.ts +9 -3
- package/src/internal/supervisor/lifecycle.ts +27 -12
- package/dist/Plan-D1wrJVs8.mjs.map +0 -1
- package/dist/Supervisor-TNKNOVzu.mjs.map +0 -1
- package/dist/page-BAf__WKX.mjs.map +0 -1
package/dist/Presentation.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as Target, u as Page } from "./Page-
|
|
1
|
+
import { b as Target, u as Page } from "./Page-DL9eylp3.mjs";
|
|
2
2
|
import { Service, t as Motion_d_exports } from "./Motion.mjs";
|
|
3
3
|
import { Duration, Effect } from "effect";
|
|
4
4
|
declare namespace Presentation_d_exports {
|
package/dist/Presentation.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { lognormal } from "./Motion.mjs";
|
|
3
|
-
import { a as performed, i as pause, r as viewOf, t as internalsOf } from "./page-
|
|
3
|
+
import { a as performed, i as pause, r as viewOf, t as internalsOf } from "./page-B92QnRfT.mjs";
|
|
4
4
|
import { Clock, Duration, Effect, Fiber, MutableRef, Option, Semaphore } from "effect";
|
|
5
5
|
//#region src/Presentation.ts
|
|
6
6
|
/**
|
package/dist/Stage.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as BrowserError } from "./BrowserError-Coy7EW1N.mjs";
|
|
2
|
-
import { n as CaptureStats, o as ScreencastOptions, r as Frame } from "./Frame-
|
|
3
|
-
import { u as Page } from "./Page-
|
|
2
|
+
import { n as CaptureStats, o as ScreencastOptions, r as Frame } from "./Frame-DkNdjjjr.mjs";
|
|
3
|
+
import { u as Page } from "./Page-DL9eylp3.mjs";
|
|
4
4
|
import { Duration, Effect, Option, Schema, Scope, Stream } from "effect";
|
|
5
5
|
declare namespace Stage_d_exports {
|
|
6
6
|
export { Cue, Options, Presented, Shown, Stage, make };
|
|
@@ -25,10 +25,11 @@ export interface Cue {
|
|
|
25
25
|
readonly at?: number | undefined;
|
|
26
26
|
}) => Effect.Effect<Presented, BrowserError>;
|
|
27
27
|
}
|
|
28
|
-
/** What the output establishes
|
|
28
|
+
/** What the output establishes at nondecreasing host times; frames retain their capture times. */
|
|
29
29
|
export type Shown = {
|
|
30
30
|
readonly _tag: "Painted";
|
|
31
31
|
readonly frame: Frame;
|
|
32
|
+
readonly at: number;
|
|
32
33
|
} | {
|
|
33
34
|
readonly _tag: "Still";
|
|
34
35
|
readonly frame: Frame;
|
|
@@ -51,7 +52,7 @@ export interface Stage {
|
|
|
51
52
|
readonly shown: (options: {
|
|
52
53
|
readonly hold: Duration.Input;
|
|
53
54
|
}) => Stream.Stream<Shown>;
|
|
54
|
-
/**
|
|
55
|
+
/** Capture-timed current-page paint; use `shown` for output time. Readers retain 16 frames. */
|
|
55
56
|
readonly frames: Stream.Stream<Frame>;
|
|
56
57
|
readonly current: Effect.Effect<Option.Option<Page>>;
|
|
57
58
|
readonly stats: (options?: {
|
package/dist/Stage.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { Closed, InvalidRequest, Timeout, consequence } from "./BrowserError.mjs";
|
|
3
|
-
import { i as failWith, s as undispatched } from "./context-
|
|
4
|
-
import { t as internalsOf } from "./page-
|
|
3
|
+
import { i as failWith, s as undispatched } from "./context-BAjRj7Xo.mjs";
|
|
4
|
+
import { t as internalsOf } from "./page-B92QnRfT.mjs";
|
|
5
5
|
import { Cause, Clock, Deferred, Duration, Effect, Exit, Fiber, Option, PubSub, Result, Schedule, Schema, Scope, Stream } from "effect";
|
|
6
6
|
//#region src/Stage.ts
|
|
7
7
|
/**
|
|
@@ -21,7 +21,7 @@ var Presented = class extends Schema.Class("effect-browser/Presented")({
|
|
|
21
21
|
at: Schema.Finite,
|
|
22
22
|
latency: Schema.Finite
|
|
23
23
|
}) {};
|
|
24
|
-
const
|
|
24
|
+
const unavailable = () => undispatched("cue", new InvalidRequest({ detail: "the cue's capture is no longer available" }));
|
|
25
25
|
/** The sole writer of a cue's capture lifecycle; presentation ownership belongs to the stage. */
|
|
26
26
|
const update = (feed, input) => {
|
|
27
27
|
if (feed.state._tag === "Stopped") return;
|
|
@@ -48,9 +48,9 @@ const frameOf = (feed) => {
|
|
|
48
48
|
const internals = internalsOf(feed.page);
|
|
49
49
|
return frame !== void 0 && (internals === void 0 || internals.current(frame)) ? frame : void 0;
|
|
50
50
|
};
|
|
51
|
-
const ended = (feed) => feed.fiber === void 0 ? Effect.never : Fiber.await(feed.fiber).pipe(Effect.flatMap((exit) => Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause) ? Effect.failCause(exit.cause) : Effect.fail(
|
|
51
|
+
const ended = (feed) => feed.fiber === void 0 ? Effect.never : Fiber.await(feed.fiber).pipe(Effect.flatMap((exit) => Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause) ? Effect.failCause(exit.cause) : Effect.fail(unavailable())));
|
|
52
52
|
const firstOf = (feed, wait) => Effect.suspend(() => {
|
|
53
|
-
if (feed.state._tag === "Stopped") return Effect.fail(
|
|
53
|
+
if (feed.state._tag === "Stopped") return Effect.fail(unavailable());
|
|
54
54
|
if (feed.page.playwright.isClosed()) return failWith("cue", new Closed({ cause: "page" }));
|
|
55
55
|
if (frameOf(feed) !== void 0) return Effect.void;
|
|
56
56
|
return Effect.raceFirst(Stream.fromPubSub(feed.changed).pipe(Stream.filter(() => frameOf(feed) !== void 0), Stream.runHead, Effect.asVoid), ended(feed)).pipe(Effect.timeoutOrElse({
|
|
@@ -86,6 +86,8 @@ const evidence = (output, current, now, hold) => Stream.unwrap(Effect.sync(() =>
|
|
|
86
86
|
if (Option.isNone(parsed) || !Duration.isFinite(parsed.value) || !Duration.isPositive(parsed.value)) throw new RangeError("shown hold must be a finite positive duration");
|
|
87
87
|
const millis = Duration.toMillis(parsed.value);
|
|
88
88
|
let checkedAt = now();
|
|
89
|
+
let shownAt = Number.NEGATIVE_INFINITY;
|
|
90
|
+
let seen = current();
|
|
89
91
|
let gone;
|
|
90
92
|
const probe = Effect.gen(function* () {
|
|
91
93
|
yield* until(Math.max(checkedAt, current()?.changedAt ?? checkedAt) + millis, now);
|
|
@@ -98,24 +100,54 @@ const evidence = (output, current, now, hold) => Stream.unwrap(Effect.sync(() =>
|
|
|
98
100
|
if (current()?.feed !== feed || current()?.frame !== frame) return Option.none();
|
|
99
101
|
const at = now();
|
|
100
102
|
checkedAt = at;
|
|
101
|
-
if (Result.isFailure(confirmed) && consequence(confirmed.failure).lost !== "nothing") {
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
if (Result.isFailure(confirmed) && consequence(confirmed.failure).lost !== "nothing") return Option.some({
|
|
104
|
+
feed,
|
|
105
|
+
frame,
|
|
106
|
+
value: {
|
|
104
107
|
_tag: "Gone",
|
|
105
108
|
page: feed.page.id,
|
|
106
109
|
at
|
|
107
|
-
}
|
|
108
|
-
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
109
112
|
return Option.some({
|
|
110
|
-
|
|
113
|
+
feed,
|
|
111
114
|
frame,
|
|
112
|
-
|
|
115
|
+
value: {
|
|
116
|
+
_tag: Result.isSuccess(confirmed) && confirmed.success ? "Still" : "Stalled",
|
|
117
|
+
frame,
|
|
118
|
+
at
|
|
119
|
+
}
|
|
113
120
|
});
|
|
114
121
|
});
|
|
115
|
-
return Stream.merge(Stream.fromPubSub(output).pipe(Stream.
|
|
116
|
-
|
|
117
|
-
frame
|
|
118
|
-
|
|
122
|
+
return Stream.merge(Stream.fromPubSub(output).pipe(Stream.map(({ feed, frame, at }) => ({
|
|
123
|
+
feed,
|
|
124
|
+
frame,
|
|
125
|
+
value: {
|
|
126
|
+
_tag: "Painted",
|
|
127
|
+
frame,
|
|
128
|
+
at
|
|
129
|
+
}
|
|
130
|
+
}))), Stream.fromEffectRepeat(probe).pipe(Stream.filter(Option.isSome), Stream.map(({ value }) => value))).pipe(Stream.filter(({ feed, frame, value }) => {
|
|
131
|
+
if (value._tag === "Painted" && gone !== feed) {
|
|
132
|
+
seen = {
|
|
133
|
+
feed,
|
|
134
|
+
frame
|
|
135
|
+
};
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
seen ??= {
|
|
139
|
+
feed,
|
|
140
|
+
frame
|
|
141
|
+
};
|
|
142
|
+
return value._tag !== "Painted" && seen?.feed === feed && seen.frame === frame;
|
|
143
|
+
}), Stream.map(({ feed, value }) => {
|
|
144
|
+
if (value._tag === "Gone") gone = feed;
|
|
145
|
+
shownAt = Math.max(shownAt, value.at);
|
|
146
|
+
return value.at === shownAt ? value : {
|
|
147
|
+
...value,
|
|
148
|
+
at: shownAt
|
|
149
|
+
};
|
|
150
|
+
}));
|
|
119
151
|
}));
|
|
120
152
|
const openFeed = (page, owner, clock, settings, current, publish) => Effect.gen(function* () {
|
|
121
153
|
const scope = yield* Scope.fork(owner);
|
|
@@ -174,15 +206,16 @@ const make = Effect.fn("Stage.make")(function* (options = {}) {
|
|
|
174
206
|
let showing;
|
|
175
207
|
let painted = Number.NEGATIVE_INFINITY;
|
|
176
208
|
yield* Effect.addFinalizer(() => PubSub.shutdown(output));
|
|
177
|
-
const publish = (feed, frame) => Effect.suspend(() => {
|
|
178
|
-
painted = Math.max(painted,
|
|
209
|
+
const publish = (feed, frame, at = frame.hostTime) => Effect.suspend(() => {
|
|
210
|
+
painted = Math.max(painted, at);
|
|
179
211
|
if (showing !== void 0) {
|
|
180
212
|
showing.changedAt = now();
|
|
181
213
|
showing.frame = frame;
|
|
182
214
|
}
|
|
183
215
|
return PubSub.publish(output, {
|
|
184
216
|
feed,
|
|
185
|
-
frame
|
|
217
|
+
frame,
|
|
218
|
+
at: painted
|
|
186
219
|
});
|
|
187
220
|
});
|
|
188
221
|
const commit = (feed, options = {}) => Effect.suspend(() => {
|
|
@@ -195,7 +228,7 @@ const make = Effect.fn("Stage.make")(function* (options = {}) {
|
|
|
195
228
|
yield* Effect.raceFirst(until(asked, now), ended(feed));
|
|
196
229
|
const switchTo = Effect.suspend(() => {
|
|
197
230
|
const frame = frameOf(feed);
|
|
198
|
-
if (frame === void 0) return Effect.fail(
|
|
231
|
+
if (frame === void 0) return Effect.fail(unavailable());
|
|
199
232
|
const at = Math.max(now(), asked, frame.hostTime, painted);
|
|
200
233
|
showing = {
|
|
201
234
|
feed,
|
|
@@ -207,11 +240,11 @@ const make = Effect.fn("Stage.make")(function* (options = {}) {
|
|
|
207
240
|
}),
|
|
208
241
|
changedAt: at
|
|
209
242
|
};
|
|
210
|
-
return publish(feed, frame).pipe(Effect.
|
|
243
|
+
return publish(feed, frame, at).pipe(Effect.as(showing.presented));
|
|
211
244
|
}).pipe(Effect.uninterruptible);
|
|
212
245
|
const readyToSwitch = firstOf(feed, firstFrame).pipe(Effect.andThen(switchTo), Effect.provideService(Clock.Clock, clock));
|
|
213
246
|
const before = old === void 0 ? void 0 : internalsOf(old.feed.page);
|
|
214
|
-
return yield* before === void 0 ? readyToSwitch : before.afterInput(readyToSwitch);
|
|
247
|
+
return yield* (before === void 0 ? readyToSwitch : before.afterInput(readyToSwitch)).pipe(Effect.ensuring(Effect.suspend(() => showing?.feed === feed && old !== void 0 ? stop(old.feed) : Effect.void)));
|
|
215
248
|
}).pipe(Effect.provideService(Clock.Clock, clock)));
|
|
216
249
|
});
|
|
217
250
|
const cue = (page) => Effect.map(openFeed(page, owner, clock, settings, () => showing?.feed, publish), (feed) => ({
|
package/dist/Stage.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stage.mjs","names":[],"sources":["../src/Stage.ts"],"sourcesContent":["/**\n * An output's capture ownership. Cue a page while preparing an event; commit when that event\n * happens. A prepared cue belongs to its caller's scope, and a committed one to the stage.\n * Commits serialize, drain old-page input and refuse stale first frames. `shown` distinguishes\n * confirmed silence, uncertain silence and loss without treating capture silence as evidence.\n * @since 0.3.0\n */\nimport {\n Cause,\n Clock,\n Deferred,\n Duration,\n Effect,\n Exit,\n Fiber,\n Option,\n PubSub,\n Result,\n Schedule,\n Schema,\n Scope,\n Stream,\n} from \"effect\";\n\nimport { type BrowserError, Closed, consequence, InvalidRequest, Timeout } from \"./BrowserError.ts\";\nimport type { CaptureStats, Frame, ScreencastOptions } from \"./Frame.ts\";\nimport { failWith, undispatched } from \"./internal/page/context.ts\";\nimport { internalsOf } from \"./internal/page/page.ts\";\nimport type { Page } from \"./Page.ts\";\n\nexport interface Options extends ScreencastOptions {\n /** Bound for each first-frame wait. Defaults to 5 seconds. */\n readonly firstFrame?: Duration.Input | undefined;\n}\n\n/** A switch that took effect, on the clock shared by browser events and frames. */\nexport class Presented extends Schema.Class<Presented>(\"effect-browser/Presented\")({\n page: Schema.String,\n at: Schema.Finite,\n latency: Schema.Finite,\n}) {}\n\nexport interface Cue {\n readonly page: Page;\n /** A frame of the current document, or the capture's failure or first-frame timeout. */\n readonly ready: Effect.Effect<void, BrowserError>;\n /** Commit on the event's host time, or now. Interruption before switching leaves the cue usable. */\n readonly commit: (options?: {\n readonly at?: number | undefined;\n }) => Effect.Effect<Presented, BrowserError>;\n}\n\n/** What the output establishes about its last image; loss ends this page's evidence. */\nexport type Shown =\n | { readonly _tag: \"Painted\"; readonly frame: Frame }\n | { readonly _tag: \"Still\"; readonly frame: Frame; readonly at: number }\n | { readonly _tag: \"Stalled\"; readonly frame: Frame; readonly at: number }\n | { readonly _tag: \"Gone\"; readonly page: string; readonly at: number };\n\nexport interface Stage {\n readonly present: (\n page: Page,\n options?: { readonly at?: number | undefined },\n ) => Effect.Effect<Presented, BrowserError>;\n readonly cue: (page: Page) => Effect.Effect<Cue, BrowserError, Scope.Scope>;\n /** Paint and, after every finite positive hold without paint, current evidence or loss. */\n readonly shown: (options: { readonly hold: Duration.Input }) => Stream.Stream<Shown>;\n /** Current-page paint; a reader more than 16 frames behind loses the oldest. */\n readonly frames: Stream.Stream<Frame>;\n readonly current: Effect.Effect<Option.Option<Page>>;\n readonly stats: (options?: {\n readonly window?: Duration.Input | undefined;\n }) => Effect.Effect<Option.Option<CaptureStats>, BrowserError>;\n}\n\ntype FeedState =\n | { readonly _tag: \"Warming\" }\n | { readonly _tag: \"Ready\"; readonly latest: Frame | undefined }\n | { readonly _tag: \"Stopped\" };\ninterface Feed {\n readonly page: Page;\n readonly scope: Scope.Closeable;\n readonly changed: PubSub.PubSub<Frame>;\n state: FeedState;\n fiber: Fiber.Fiber<void, BrowserError> | undefined;\n}\ninterface Paint {\n readonly feed: Feed;\n readonly frame: Frame;\n}\n\nconst closed = () => undispatched(\"cue\", new Closed({ cause: \"released\" }));\n\n/** The sole writer of a cue's capture lifecycle; presentation ownership belongs to the stage. */\nconst update = (\n feed: Feed,\n input: { readonly _tag: \"Paint\"; readonly frame: Frame } | { readonly _tag: \"Reset\" | \"Stop\" },\n) => {\n if (feed.state._tag === \"Stopped\") return;\n if (input._tag === \"Stop\") feed.state = { _tag: \"Stopped\" };\n else if (input._tag === \"Paint\") feed.state = { _tag: \"Ready\", latest: input.frame };\n else if (feed.state._tag === \"Ready\") feed.state = { _tag: \"Ready\", latest: undefined };\n};\n\nconst restartable = (error: BrowserError) => {\n const { lost, repeat } = consequence(error);\n\n return lost === \"nothing\" && repeat !== \"pointless\";\n};\n\nconst stop = (feed: Feed) =>\n Effect.suspend(() => {\n update(feed, { _tag: \"Stop\" });\n\n return Scope.close(feed.scope, Exit.void);\n });\n\nconst frameOf = (feed: Feed) => {\n const frame = feed.state._tag === \"Ready\" ? feed.state.latest : undefined;\n const internals = internalsOf(feed.page);\n\n return frame !== undefined && (internals === undefined || internals.current(frame))\n ? frame\n : undefined;\n};\n\nconst ended = (feed: Feed): Effect.Effect<never, BrowserError> =>\n feed.fiber === undefined\n ? Effect.never\n : Fiber.await(feed.fiber).pipe(\n Effect.flatMap((exit) =>\n Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)\n ? Effect.failCause(exit.cause)\n : Effect.fail(closed()),\n ),\n );\n\nconst firstOf = (feed: Feed, wait: Duration.Duration) =>\n Effect.suspend(() => {\n if (feed.state._tag === \"Stopped\") return Effect.fail(closed());\n if (feed.page.playwright.isClosed()) return failWith(\"cue\", new Closed({ cause: \"page\" }));\n if (frameOf(feed) !== undefined) return Effect.void;\n\n return Effect.raceFirst(\n Stream.fromPubSub(feed.changed).pipe(\n Stream.filter(() => frameOf(feed) !== undefined),\n Stream.runHead,\n Effect.asVoid,\n ),\n ended(feed),\n ).pipe(\n Effect.timeoutOrElse({\n duration: wait,\n orElse: () => failWith(\"cue\", new Timeout({ millis: Duration.toMillis(wait) })),\n }),\n );\n });\n\n// A timer can wake a fraction early; a commit never precedes its event.\nconst until = (at: number, now: () => number): Effect.Effect<void> =>\n Effect.suspend(() =>\n at > now()\n ? Effect.sleep(Duration.millis(Math.ceil(at - now()))).pipe(Effect.andThen(until(at, now)))\n : Effect.void,\n );\n\n/** Reserve the next turn before waking it; a fresh caller cannot overtake a queued commit. */\nconst ordered = () => {\n const waiting: Array<Deferred.Deferred<void>> = [];\n\n return <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n Effect.acquireUseRelease(\n Effect.sync(() => {\n const turn = Deferred.makeUnsafe<void>();\n\n waiting.push(turn);\n if (waiting.length === 1) Deferred.doneUnsafe(turn, Exit.void);\n\n return turn;\n }),\n (turn) => Deferred.await(turn).pipe(Effect.andThen(effect)),\n (turn) =>\n Effect.sync(() => {\n const at = waiting.indexOf(turn);\n\n waiting.splice(at, 1);\n if (at === 0 && waiting[0] !== undefined) Deferred.doneUnsafe(waiting[0], Exit.void);\n }),\n );\n};\n\n/** A terminal capture can know the connection is gone before the page receives its close event. */\nconst confirm = (feed: Feed, frame: Frame) => {\n const exit = feed.fiber?.pollUnsafe();\n\n const failure =\n exit !== undefined && Exit.isFailure(exit) ? Cause.findErrorOption(exit.cause) : Option.none();\n\n if (Option.isSome(failure) && consequence(failure.value).lost !== \"nothing\")\n return Effect.fail(failure.value);\n\n return feed.page.playwright.isClosed()\n ? failWith(\"shown\", new Closed({ cause: \"page\" }))\n : (internalsOf(feed.page)?.confirm(frame) ?? Effect.succeed(false));\n};\n\n/** Each subscriber confirms silence; a new frame or commit invalidates a probe. */\nconst evidence = (\n output: PubSub.PubSub<Paint>,\n current: () =>\n | { readonly feed: Feed; readonly frame: Frame; readonly changedAt: number }\n | undefined,\n now: () => number,\n hold: Duration.Input,\n): Stream.Stream<Shown> =>\n Stream.unwrap(\n Effect.sync(() => {\n const parsed = Duration.fromInput(hold);\n\n if (\n Option.isNone(parsed) ||\n !Duration.isFinite(parsed.value) ||\n !Duration.isPositive(parsed.value)\n )\n throw new RangeError(\"shown hold must be a finite positive duration\");\n const millis = Duration.toMillis(parsed.value);\n let checkedAt = now();\n let gone: Feed | undefined;\n\n const probe = Effect.gen(function* () {\n yield* until(Math.max(checkedAt, current()?.changedAt ?? checkedAt) + millis, now);\n checkedAt = now();\n const before = current();\n const frame = before?.frame;\n\n if (\n before === undefined ||\n frame === undefined ||\n gone === before.feed ||\n checkedAt - before.changedAt < millis\n )\n return Option.none<Shown>();\n const { feed } = before;\n\n const confirmed = yield* Effect.result(confirm(feed, frame));\n\n if (current()?.feed !== feed || current()?.frame !== frame) return Option.none<Shown>();\n const at = now();\n\n checkedAt = at;\n if (Result.isFailure(confirmed) && consequence(confirmed.failure).lost !== \"nothing\") {\n gone = feed;\n\n return Option.some<Shown>({ _tag: \"Gone\", page: feed.page.id, at });\n }\n\n return Option.some<Shown>({\n _tag: Result.isSuccess(confirmed) && confirmed.success ? \"Still\" : \"Stalled\",\n frame,\n at,\n });\n });\n\n return Stream.merge(\n Stream.fromPubSub(output).pipe(\n Stream.filter(({ feed }) => gone !== feed),\n Stream.map(({ frame }): Shown => ({ _tag: \"Painted\", frame })),\n ),\n Stream.fromEffectRepeat(probe).pipe(\n Stream.filter(Option.isSome),\n Stream.map(({ value }) => value),\n ),\n );\n }),\n );\n\nconst openFeed = (\n page: Page,\n owner: Scope.Scope,\n clock: Clock.Clock,\n settings: ScreencastOptions,\n current: () => Feed | undefined,\n publish: (feed: Feed, frame: Frame) => Effect.Effect<unknown>,\n) =>\n Effect.gen(function* () {\n const scope = yield* Scope.fork(owner);\n const started = yield* Deferred.make<void>();\n\n const feed: Feed = {\n page,\n scope,\n changed: yield* PubSub.sliding<Frame>({ capacity: 1, replay: 1 }),\n state: { _tag: \"Warming\" },\n fiber: undefined,\n };\n\n yield* Scope.addFinalizer(\n scope,\n Effect.sync(() => {\n update(feed, { _tag: \"Stop\" });\n }),\n );\n yield* Effect.addFinalizer(() => (current() === feed ? Effect.void : stop(feed)));\n\n const accept = (frame: Frame) =>\n Effect.suspend(() => {\n update(feed, { _tag: \"Paint\", frame });\n\n return (current() === feed ? publish(feed, frame) : Effect.void).pipe(\n Effect.andThen(PubSub.publish(feed.changed, frame)),\n );\n });\n\n const capture = Effect.scoped(\n Effect.gen(function* () {\n update(feed, { _tag: \"Reset\" });\n\n const internals = internalsOf(page);\n\n const { stream, current: frame } = yield* (\n internals?.capture(settings) ??\n Effect.succeed({ stream: page.screencast(settings), current: undefined })\n );\n\n if (frame !== undefined && internals?.current(frame)) yield* accept(frame);\n yield* Deferred.succeed(started, undefined);\n yield* Stream.runForEach(stream, accept);\n }),\n ).pipe(\n Effect.ensuring(Effect.sync(() => update(feed, { _tag: \"Reset\" }))),\n Effect.retry({\n while: (error) => feed.state._tag === \"Ready\" && restartable(error),\n schedule: Schedule.spaced(\"500 millis\"),\n }),\n );\n\n feed.fiber = yield* capture.pipe(\n Effect.provideService(Clock.Clock, clock),\n Effect.forkIn(scope),\n );\n yield* Effect.raceFirst(Deferred.await(started), ended(feed)).pipe(\n Effect.onError(() => stop(feed)),\n );\n\n return feed;\n });\n\nexport const make = Effect.fn(\"Stage.make\")(function* (\n options: Options = {},\n): Effect.fn.Return<Stage, BrowserError, Scope.Scope> {\n const clock = yield* Clock.Clock;\n const owner = yield* Effect.scope;\n const now = () => Number(clock.monotonicTimeNanosUnsafe()) / 1e6;\n const waited = Duration.fromInput(options.firstFrame ?? Duration.seconds(5));\n\n if (\n Option.isNone(waited) ||\n !Duration.isFinite(waited.value) ||\n !Duration.isPositive(waited.value)\n )\n return yield* failWith(\n \"stage\",\n new InvalidRequest({ detail: \"firstFrame must be a finite positive duration\" }),\n );\n const firstFrame = waited.value;\n const settings = { quality: options.quality, size: options.size };\n const output = yield* PubSub.sliding<Paint>(16);\n const switching = ordered();\n\n let showing:\n | { readonly feed: Feed; readonly presented: Presented; frame: Frame; changedAt: number }\n | undefined;\n\n let painted = Number.NEGATIVE_INFINITY;\n\n yield* Effect.addFinalizer(() => PubSub.shutdown(output));\n\n const publish = (feed: Feed, frame: Frame) =>\n Effect.suspend(() => {\n painted = Math.max(painted, frame.hostTime);\n if (showing !== undefined) {\n showing.changedAt = now();\n showing.frame = frame;\n }\n\n return PubSub.publish(output, { feed, frame });\n });\n\n const commit = (feed: Feed, options: { readonly at?: number | undefined } = {}) =>\n Effect.suspend(() => {\n const asked = options.at ?? now();\n\n return switching(\n Effect.gen(function* () {\n if (!Number.isFinite(asked))\n return yield* failWith(\n \"cue\",\n new InvalidRequest({ detail: \"at must be finite host milliseconds\" }),\n );\n yield* firstOf(feed, firstFrame);\n const old = showing;\n\n if (old?.feed === feed) return old.presented;\n\n yield* Effect.raceFirst(until(asked, now), ended(feed));\n\n const switchTo = Effect.suspend(() => {\n const frame = frameOf(feed);\n\n if (frame === undefined) return Effect.fail(closed());\n const at = Math.max(now(), asked, frame.hostTime, painted);\n\n showing = {\n feed,\n frame,\n presented: new Presented({ page: feed.page.id, at, latency: at - asked }),\n changedAt: at,\n };\n\n return publish(feed, frame).pipe(\n Effect.andThen(old === undefined ? Effect.void : stop(old.feed)),\n Effect.as(showing.presented),\n );\n }).pipe(Effect.uninterruptible);\n\n const readyToSwitch = firstOf(feed, firstFrame).pipe(\n Effect.andThen(switchTo),\n Effect.provideService(Clock.Clock, clock),\n );\n\n const before = old === undefined ? undefined : internalsOf(old.feed.page);\n\n return yield* before === undefined ? readyToSwitch : before.afterInput(readyToSwitch);\n }).pipe(Effect.provideService(Clock.Clock, clock)),\n );\n });\n\n const cue: Stage[\"cue\"] = (page) =>\n Effect.map(\n openFeed(page, owner, clock, settings, () => showing?.feed, publish),\n (feed) => ({\n page,\n ready: firstOf(feed, firstFrame).pipe(Effect.provideService(Clock.Clock, clock)),\n commit: (options) => commit(feed, options),\n }),\n );\n\n return {\n cue,\n present: (page, options) =>\n Effect.suspend(() => {\n const asked = options?.at ?? now();\n\n return showing?.feed.page === page\n ? Effect.as(firstOf(showing.feed, firstFrame), showing.presented)\n : Effect.scoped(Effect.flatMap(cue(page), (prepared) => prepared.commit({ at: asked })));\n }),\n shown: ({ hold }) =>\n evidence(output, () => showing, now, hold).pipe(Stream.provideService(Clock.Clock, clock)),\n frames: Stream.fromPubSub(output).pipe(Stream.map(({ frame }) => frame)),\n current: Effect.sync(() => Option.fromNullishOr(showing?.feed.page)),\n stats: (options = {}) =>\n showing === undefined\n ? Effect.succeedNone\n : Effect.asSome(showing.feed.page.captureStats({ window: options.window ?? \"1 second\" })),\n } satisfies Stage;\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Stage.mjs","names":[],"sources":["../src/Stage.ts"],"sourcesContent":["/**\n * An output's capture ownership. Cue a page while preparing an event; commit when that event\n * happens. A prepared cue belongs to its caller's scope, and a committed one to the stage.\n * Commits serialize, drain old-page input and refuse stale first frames. `shown` distinguishes\n * confirmed silence, uncertain silence and loss without treating capture silence as evidence.\n * @since 0.3.0\n */\nimport {\n Cause,\n Clock,\n Deferred,\n Duration,\n Effect,\n Exit,\n Fiber,\n Option,\n PubSub,\n Result,\n Schedule,\n Schema,\n Scope,\n Stream,\n} from \"effect\";\n\nimport { type BrowserError, Closed, consequence, InvalidRequest, Timeout } from \"./BrowserError.ts\";\nimport type { CaptureStats, Frame, ScreencastOptions } from \"./Frame.ts\";\nimport { failWith, undispatched } from \"./internal/page/context.ts\";\nimport { internalsOf } from \"./internal/page/page.ts\";\nimport type { Page } from \"./Page.ts\";\n\nexport interface Options extends ScreencastOptions {\n /** Bound for each first-frame wait. Defaults to 5 seconds. */\n readonly firstFrame?: Duration.Input | undefined;\n}\n\n/** A switch that took effect, on the clock shared by browser events and frames. */\nexport class Presented extends Schema.Class<Presented>(\"effect-browser/Presented\")({\n page: Schema.String,\n at: Schema.Finite,\n latency: Schema.Finite,\n}) {}\n\nexport interface Cue {\n readonly page: Page;\n /** A frame of the current document, or the capture's failure or first-frame timeout. */\n readonly ready: Effect.Effect<void, BrowserError>;\n /** Commit on the event's host time, or now. Interruption before switching leaves the cue usable. */\n readonly commit: (options?: {\n readonly at?: number | undefined;\n }) => Effect.Effect<Presented, BrowserError>;\n}\n\n/** What the output establishes at nondecreasing host times; frames retain their capture times. */\nexport type Shown =\n | { readonly _tag: \"Painted\"; readonly frame: Frame; readonly at: number }\n | { readonly _tag: \"Still\"; readonly frame: Frame; readonly at: number }\n | { readonly _tag: \"Stalled\"; readonly frame: Frame; readonly at: number }\n | { readonly _tag: \"Gone\"; readonly page: string; readonly at: number };\n\nexport interface Stage {\n readonly present: (\n page: Page,\n options?: { readonly at?: number | undefined },\n ) => Effect.Effect<Presented, BrowserError>;\n readonly cue: (page: Page) => Effect.Effect<Cue, BrowserError, Scope.Scope>;\n /** Paint and, after every finite positive hold without paint, current evidence or loss. */\n readonly shown: (options: { readonly hold: Duration.Input }) => Stream.Stream<Shown>;\n /** Capture-timed current-page paint; use `shown` for output time. Readers retain 16 frames. */\n readonly frames: Stream.Stream<Frame>;\n readonly current: Effect.Effect<Option.Option<Page>>;\n readonly stats: (options?: {\n readonly window?: Duration.Input | undefined;\n }) => Effect.Effect<Option.Option<CaptureStats>, BrowserError>;\n}\n\ntype FeedState =\n | { readonly _tag: \"Warming\" }\n | { readonly _tag: \"Ready\"; readonly latest: Frame | undefined }\n | { readonly _tag: \"Stopped\" };\ninterface Feed {\n readonly page: Page;\n readonly scope: Scope.Closeable;\n readonly changed: PubSub.PubSub<Frame>;\n state: FeedState;\n fiber: Fiber.Fiber<void, BrowserError> | undefined;\n}\ninterface Paint {\n readonly feed: Feed;\n readonly frame: Frame;\n readonly at: number;\n}\n\ninterface Evidence {\n readonly feed: Feed;\n readonly frame: Frame;\n readonly value: Shown;\n}\n\nconst unavailable = () =>\n undispatched(\"cue\", new InvalidRequest({ detail: \"the cue's capture is no longer available\" }));\n\n/** The sole writer of a cue's capture lifecycle; presentation ownership belongs to the stage. */\nconst update = (\n feed: Feed,\n input: { readonly _tag: \"Paint\"; readonly frame: Frame } | { readonly _tag: \"Reset\" | \"Stop\" },\n) => {\n if (feed.state._tag === \"Stopped\") return;\n if (input._tag === \"Stop\") feed.state = { _tag: \"Stopped\" };\n else if (input._tag === \"Paint\") feed.state = { _tag: \"Ready\", latest: input.frame };\n else if (feed.state._tag === \"Ready\") feed.state = { _tag: \"Ready\", latest: undefined };\n};\n\nconst restartable = (error: BrowserError) => {\n const { lost, repeat } = consequence(error);\n\n return lost === \"nothing\" && repeat !== \"pointless\";\n};\n\nconst stop = (feed: Feed) =>\n Effect.suspend(() => {\n update(feed, { _tag: \"Stop\" });\n\n return Scope.close(feed.scope, Exit.void);\n });\n\nconst frameOf = (feed: Feed) => {\n const frame = feed.state._tag === \"Ready\" ? feed.state.latest : undefined;\n const internals = internalsOf(feed.page);\n\n return frame !== undefined && (internals === undefined || internals.current(frame))\n ? frame\n : undefined;\n};\n\nconst ended = (feed: Feed): Effect.Effect<never, BrowserError> =>\n feed.fiber === undefined\n ? Effect.never\n : Fiber.await(feed.fiber).pipe(\n Effect.flatMap((exit) =>\n Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)\n ? Effect.failCause(exit.cause)\n : Effect.fail(unavailable()),\n ),\n );\n\nconst firstOf = (feed: Feed, wait: Duration.Duration) =>\n Effect.suspend(() => {\n if (feed.state._tag === \"Stopped\") return Effect.fail(unavailable());\n if (feed.page.playwright.isClosed()) return failWith(\"cue\", new Closed({ cause: \"page\" }));\n if (frameOf(feed) !== undefined) return Effect.void;\n\n return Effect.raceFirst(\n Stream.fromPubSub(feed.changed).pipe(\n Stream.filter(() => frameOf(feed) !== undefined),\n Stream.runHead,\n Effect.asVoid,\n ),\n ended(feed),\n ).pipe(\n Effect.timeoutOrElse({\n duration: wait,\n orElse: () => failWith(\"cue\", new Timeout({ millis: Duration.toMillis(wait) })),\n }),\n );\n });\n\n// A timer can wake a fraction early; a commit never precedes its event.\nconst until = (at: number, now: () => number): Effect.Effect<void> =>\n Effect.suspend(() =>\n at > now()\n ? Effect.sleep(Duration.millis(Math.ceil(at - now()))).pipe(Effect.andThen(until(at, now)))\n : Effect.void,\n );\n\n/** Reserve the next turn before waking it; a fresh caller cannot overtake a queued commit. */\nconst ordered = () => {\n const waiting: Array<Deferred.Deferred<void>> = [];\n\n return <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n Effect.acquireUseRelease(\n Effect.sync(() => {\n const turn = Deferred.makeUnsafe<void>();\n\n waiting.push(turn);\n if (waiting.length === 1) Deferred.doneUnsafe(turn, Exit.void);\n\n return turn;\n }),\n (turn) => Deferred.await(turn).pipe(Effect.andThen(effect)),\n (turn) =>\n Effect.sync(() => {\n const at = waiting.indexOf(turn);\n\n waiting.splice(at, 1);\n if (at === 0 && waiting[0] !== undefined) Deferred.doneUnsafe(waiting[0], Exit.void);\n }),\n );\n};\n\n/** A terminal capture can know the connection is gone before the page receives its close event. */\nconst confirm = (feed: Feed, frame: Frame) => {\n const exit = feed.fiber?.pollUnsafe();\n\n const failure =\n exit !== undefined && Exit.isFailure(exit) ? Cause.findErrorOption(exit.cause) : Option.none();\n\n if (Option.isSome(failure) && consequence(failure.value).lost !== \"nothing\")\n return Effect.fail(failure.value);\n\n return feed.page.playwright.isClosed()\n ? failWith(\"shown\", new Closed({ cause: \"page\" }))\n : (internalsOf(feed.page)?.confirm(frame) ?? Effect.succeed(false));\n};\n\n/** Each subscriber confirms silence; a new frame or commit invalidates a probe. */\nconst evidence = (\n output: PubSub.PubSub<Paint>,\n current: () =>\n | { readonly feed: Feed; readonly frame: Frame; readonly changedAt: number }\n | undefined,\n now: () => number,\n hold: Duration.Input,\n): Stream.Stream<Shown> =>\n Stream.unwrap(\n Effect.sync(() => {\n const parsed = Duration.fromInput(hold);\n\n if (\n Option.isNone(parsed) ||\n !Duration.isFinite(parsed.value) ||\n !Duration.isPositive(parsed.value)\n )\n throw new RangeError(\"shown hold must be a finite positive duration\");\n const millis = Duration.toMillis(parsed.value);\n let checkedAt = now();\n let shownAt = Number.NEGATIVE_INFINITY;\n let seen: Pick<Paint, \"feed\" | \"frame\"> | undefined = current();\n let gone: Feed | undefined;\n\n const probe = Effect.gen(function* () {\n yield* until(Math.max(checkedAt, current()?.changedAt ?? checkedAt) + millis, now);\n checkedAt = now();\n const before = current();\n const frame = before?.frame;\n\n if (\n before === undefined ||\n frame === undefined ||\n gone === before.feed ||\n checkedAt - before.changedAt < millis\n )\n return Option.none<Evidence>();\n const { feed } = before;\n\n const confirmed = yield* Effect.result(confirm(feed, frame));\n\n if (current()?.feed !== feed || current()?.frame !== frame) return Option.none<Evidence>();\n const at = now();\n\n checkedAt = at;\n if (Result.isFailure(confirmed) && consequence(confirmed.failure).lost !== \"nothing\") {\n return Option.some<Evidence>({\n feed,\n frame,\n value: { _tag: \"Gone\", page: feed.page.id, at },\n });\n }\n\n return Option.some<Evidence>({\n feed,\n frame,\n value: {\n _tag: Result.isSuccess(confirmed) && confirmed.success ? \"Still\" : \"Stalled\",\n frame,\n at,\n },\n });\n });\n\n return Stream.merge(\n Stream.fromPubSub(output).pipe(\n Stream.map(({ feed, frame, at }): Evidence => ({\n feed,\n frame,\n value: { _tag: \"Painted\", frame, at },\n })),\n ),\n Stream.fromEffectRepeat(probe).pipe(\n Stream.filter(Option.isSome),\n Stream.map(({ value }) => value),\n ),\n ).pipe(\n // A probe can overtake queued paint while its reader is paused. It establishes nothing\n // about that reader's output until the matching frame has reached it.\n Stream.filter(({ feed, frame, value }) => {\n if (value._tag === \"Painted\" && gone !== feed) {\n seen = { feed, frame };\n\n return true;\n }\n // The subscription may have begun after its first publication.\n seen ??= { feed, frame };\n\n return value._tag !== \"Painted\" && seen?.feed === feed && seen.frame === frame;\n }),\n Stream.map(({ feed, value }) => {\n if (value._tag === \"Gone\") gone = feed;\n // A frame captured before a silence probe can arrive after the probe's evidence.\n shownAt = Math.max(shownAt, value.at);\n\n return value.at === shownAt ? value : { ...value, at: shownAt };\n }),\n );\n }),\n );\n\nconst openFeed = (\n page: Page,\n owner: Scope.Scope,\n clock: Clock.Clock,\n settings: ScreencastOptions,\n current: () => Feed | undefined,\n publish: (feed: Feed, frame: Frame) => Effect.Effect<unknown>,\n) =>\n Effect.gen(function* () {\n const scope = yield* Scope.fork(owner);\n const started = yield* Deferred.make<void>();\n\n const feed: Feed = {\n page,\n scope,\n changed: yield* PubSub.sliding<Frame>({ capacity: 1, replay: 1 }),\n state: { _tag: \"Warming\" },\n fiber: undefined,\n };\n\n yield* Scope.addFinalizer(\n scope,\n Effect.sync(() => {\n update(feed, { _tag: \"Stop\" });\n }),\n );\n yield* Effect.addFinalizer(() => (current() === feed ? Effect.void : stop(feed)));\n\n const accept = (frame: Frame) =>\n Effect.suspend(() => {\n update(feed, { _tag: \"Paint\", frame });\n\n return (current() === feed ? publish(feed, frame) : Effect.void).pipe(\n Effect.andThen(PubSub.publish(feed.changed, frame)),\n );\n });\n\n const capture = Effect.scoped(\n Effect.gen(function* () {\n update(feed, { _tag: \"Reset\" });\n\n const internals = internalsOf(page);\n\n const { stream, current: frame } = yield* (\n internals?.capture(settings) ??\n Effect.succeed({ stream: page.screencast(settings), current: undefined })\n );\n\n if (frame !== undefined && internals?.current(frame)) yield* accept(frame);\n yield* Deferred.succeed(started, undefined);\n yield* Stream.runForEach(stream, accept);\n }),\n ).pipe(\n Effect.ensuring(Effect.sync(() => update(feed, { _tag: \"Reset\" }))),\n Effect.retry({\n while: (error) => feed.state._tag === \"Ready\" && restartable(error),\n schedule: Schedule.spaced(\"500 millis\"),\n }),\n );\n\n feed.fiber = yield* capture.pipe(\n Effect.provideService(Clock.Clock, clock),\n Effect.forkIn(scope),\n );\n yield* Effect.raceFirst(Deferred.await(started), ended(feed)).pipe(\n Effect.onError(() => stop(feed)),\n );\n\n return feed;\n });\n\nexport const make = Effect.fn(\"Stage.make\")(function* (\n options: Options = {},\n): Effect.fn.Return<Stage, BrowserError, Scope.Scope> {\n const clock = yield* Clock.Clock;\n const owner = yield* Effect.scope;\n const now = () => Number(clock.monotonicTimeNanosUnsafe()) / 1e6;\n const waited = Duration.fromInput(options.firstFrame ?? Duration.seconds(5));\n\n if (\n Option.isNone(waited) ||\n !Duration.isFinite(waited.value) ||\n !Duration.isPositive(waited.value)\n )\n return yield* failWith(\n \"stage\",\n new InvalidRequest({ detail: \"firstFrame must be a finite positive duration\" }),\n );\n const firstFrame = waited.value;\n const settings = { quality: options.quality, size: options.size };\n const output = yield* PubSub.sliding<Paint>(16);\n const switching = ordered();\n\n let showing:\n | { readonly feed: Feed; readonly presented: Presented; frame: Frame; changedAt: number }\n | undefined;\n\n let painted = Number.NEGATIVE_INFINITY;\n\n yield* Effect.addFinalizer(() => PubSub.shutdown(output));\n\n const publish = (feed: Feed, frame: Frame, at = frame.hostTime) =>\n Effect.suspend(() => {\n painted = Math.max(painted, at);\n if (showing !== undefined) {\n showing.changedAt = now();\n showing.frame = frame;\n }\n\n return PubSub.publish(output, { feed, frame, at: painted });\n });\n\n const commit = (feed: Feed, options: { readonly at?: number | undefined } = {}) =>\n Effect.suspend(() => {\n const asked = options.at ?? now();\n\n return switching(\n Effect.gen(function* () {\n if (!Number.isFinite(asked))\n return yield* failWith(\n \"cue\",\n new InvalidRequest({ detail: \"at must be finite host milliseconds\" }),\n );\n yield* firstOf(feed, firstFrame);\n const old = showing;\n\n if (old?.feed === feed) return old.presented;\n\n yield* Effect.raceFirst(until(asked, now), ended(feed));\n\n const switchTo = Effect.suspend(() => {\n const frame = frameOf(feed);\n\n if (frame === undefined) return Effect.fail(unavailable());\n const at = Math.max(now(), asked, frame.hostTime, painted);\n\n showing = {\n feed,\n frame,\n presented: new Presented({ page: feed.page.id, at, latency: at - asked }),\n changedAt: at,\n };\n\n return publish(feed, frame, at).pipe(Effect.as(showing.presented));\n }).pipe(Effect.uninterruptible);\n\n const readyToSwitch = firstOf(feed, firstFrame).pipe(\n Effect.andThen(switchTo),\n Effect.provideService(Clock.Clock, clock),\n );\n\n const before = old === undefined ? undefined : internalsOf(old.feed.page);\n\n return yield* (\n before === undefined ? readyToSwitch : before.afterInput(readyToSwitch)\n ).pipe(\n // The cut owns old-page input through publication. Capture cleanup has its own bound\n // and must finish even if the caller leaves, without turning a completed cut into Timeout.\n Effect.ensuring(\n Effect.suspend(() =>\n showing?.feed === feed && old !== undefined ? stop(old.feed) : Effect.void,\n ),\n ),\n );\n }).pipe(Effect.provideService(Clock.Clock, clock)),\n );\n });\n\n const cue: Stage[\"cue\"] = (page) =>\n Effect.map(\n openFeed(page, owner, clock, settings, () => showing?.feed, publish),\n (feed) => ({\n page,\n ready: firstOf(feed, firstFrame).pipe(Effect.provideService(Clock.Clock, clock)),\n commit: (options) => commit(feed, options),\n }),\n );\n\n return {\n cue,\n present: (page, options) =>\n Effect.suspend(() => {\n const asked = options?.at ?? now();\n\n return showing?.feed.page === page\n ? Effect.as(firstOf(showing.feed, firstFrame), showing.presented)\n : Effect.scoped(Effect.flatMap(cue(page), (prepared) => prepared.commit({ at: asked })));\n }),\n shown: ({ hold }) =>\n evidence(output, () => showing, now, hold).pipe(Stream.provideService(Clock.Clock, clock)),\n frames: Stream.fromPubSub(output).pipe(Stream.map(({ frame }) => frame)),\n current: Effect.sync(() => Option.fromNullishOr(showing?.feed.page)),\n stats: (options = {}) =>\n showing === undefined\n ? Effect.succeedNone\n : Effect.asSome(showing.feed.page.captureStats({ window: options.window ?? \"1 second\" })),\n } satisfies Stage;\n});\n"],"mappings":"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"}
|
|
@@ -27,8 +27,11 @@ var Draining = class extends Schema.TaggedClass()("Draining", {
|
|
|
27
27
|
]),
|
|
28
28
|
revokeAt: Schema.Finite
|
|
29
29
|
}) {};
|
|
30
|
-
/** Lost
|
|
31
|
-
var Lost = class extends Schema.TaggedClass()("Lost", {
|
|
30
|
+
/** Lost through disconnection or a consumer's declaration that it is unusable. */
|
|
31
|
+
var Lost = class extends Schema.TaggedClass()("Lost", {
|
|
32
|
+
cause: Schema.Union([DisconnectCause, Schema.Literal("declared")]),
|
|
33
|
+
detail: Schema.optional(Schema.String)
|
|
34
|
+
}) {};
|
|
32
35
|
/**
|
|
33
36
|
* It never opened: its last try failed with `cause`, which the provider deems definite, or after
|
|
34
37
|
* which the reopen schedule gave up.
|
|
@@ -59,7 +62,8 @@ const start = () => ({
|
|
|
59
62
|
_tag: "Opening",
|
|
60
63
|
number: 1,
|
|
61
64
|
serving: void 0,
|
|
62
|
-
draining: /* @__PURE__ */ new Map()
|
|
65
|
+
draining: /* @__PURE__ */ new Map(),
|
|
66
|
+
latest: void 0
|
|
63
67
|
},
|
|
64
68
|
events: [[1, new Opening()]],
|
|
65
69
|
commands: [{
|
|
@@ -145,13 +149,14 @@ const holds = (change, input) => {
|
|
|
145
149
|
const next = (change, number, reopen) => {
|
|
146
150
|
const { exclusive } = change;
|
|
147
151
|
const current = servingOf(change.state);
|
|
148
|
-
const after =
|
|
152
|
+
const after = change.state.latest;
|
|
149
153
|
if (current !== void 0 && (reopen || exclusive)) end(change, current, reopen ? "lost" : "rotated");
|
|
150
154
|
change.state = {
|
|
151
155
|
_tag: "Opening",
|
|
152
156
|
number,
|
|
153
157
|
serving: reopen || exclusive ? void 0 : current,
|
|
154
|
-
draining: change.state.draining
|
|
158
|
+
draining: change.state.draining,
|
|
159
|
+
latest: after
|
|
155
160
|
};
|
|
156
161
|
change.events.push([number, reopen ? new Reopening() : new Opening()]);
|
|
157
162
|
change.commands.push({
|
|
@@ -164,11 +169,15 @@ const lost = (change, input) => {
|
|
|
164
169
|
const { state } = change;
|
|
165
170
|
const drain = state.draining.get(input.number);
|
|
166
171
|
const current = servingOf(state);
|
|
172
|
+
if (input.cause === "declared" && current?.number !== input.number) return;
|
|
167
173
|
if (drain?._tag === "Draining") {
|
|
168
174
|
change.events.push([input.number, new Lost({ cause: input.cause })]);
|
|
169
175
|
revoke(change, input.number, "lost");
|
|
170
176
|
} else if (state._tag !== "Retired" && current?.number === input.number) {
|
|
171
|
-
change.events.push([input.number, new Lost({
|
|
177
|
+
change.events.push([input.number, new Lost({
|
|
178
|
+
cause: input.cause,
|
|
179
|
+
detail: input.detail
|
|
180
|
+
})]);
|
|
172
181
|
if (state._tag !== "Opening") next(change, (state._tag === "Open" ? current.number : state.number) + 1, true);
|
|
173
182
|
else {
|
|
174
183
|
change.state = {
|
|
@@ -214,7 +223,8 @@ const transition = (state, input, exclusive, now = 0, handover = 3e4) => {
|
|
|
214
223
|
change.state = {
|
|
215
224
|
_tag: "Open",
|
|
216
225
|
serving: live,
|
|
217
|
-
draining: change.state.draining
|
|
226
|
+
draining: change.state.draining,
|
|
227
|
+
latest: input.live
|
|
218
228
|
};
|
|
219
229
|
}
|
|
220
230
|
break;
|
|
@@ -263,7 +273,8 @@ const transition = (state, input, exclusive, now = 0, handover = 3e4) => {
|
|
|
263
273
|
if (state.serving !== void 0) end(change, state.serving, "retired", input._tag === "Keep");
|
|
264
274
|
change.state = {
|
|
265
275
|
_tag: "Retired",
|
|
266
|
-
draining: change.state.draining
|
|
276
|
+
draining: change.state.draining,
|
|
277
|
+
latest: void 0
|
|
267
278
|
};
|
|
268
279
|
}
|
|
269
280
|
return change;
|
|
@@ -360,6 +371,11 @@ var Superseded = class extends Schema.TaggedError()("Superseded", {
|
|
|
360
371
|
]),
|
|
361
372
|
revokeAt: Schema.Finite
|
|
362
373
|
}) {};
|
|
374
|
+
const superseded = (generation, { reason, revokeAt }) => new Superseded({
|
|
375
|
+
generation,
|
|
376
|
+
reason,
|
|
377
|
+
revokeAt
|
|
378
|
+
});
|
|
363
379
|
const retired = () => new Unavailable({
|
|
364
380
|
reason: "retired",
|
|
365
381
|
detail: "the supervisor was retired"
|
|
@@ -420,6 +436,12 @@ const leasing = (state, apply, waitTimeout, now, clock) => {
|
|
|
420
436
|
generation: live.number,
|
|
421
437
|
browser: live.value.opened.browser,
|
|
422
438
|
value: live.value.opened.value,
|
|
439
|
+
lose: (detail) => apply({
|
|
440
|
+
_tag: "Lost",
|
|
441
|
+
number: live.number,
|
|
442
|
+
cause: "declared",
|
|
443
|
+
detail
|
|
444
|
+
}).pipe(Effect.asVoid),
|
|
423
445
|
ending: Deferred.await(live.value.ending),
|
|
424
446
|
retain: Effect.uninterruptible(Effect.gen(function* () {
|
|
425
447
|
const scope = yield* Scope.make();
|
|
@@ -451,7 +473,8 @@ const leasing = (state, apply, waitTimeout, now, clock) => {
|
|
|
451
473
|
number: live.number,
|
|
452
474
|
hold
|
|
453
475
|
}).pipe(Effect.asVoid));
|
|
454
|
-
|
|
476
|
+
const revoked = Deferred.await(live.value.revoked).pipe(Effect.flatMap(Effect.fail));
|
|
477
|
+
return yield* Effect.raceFirst(work(leaseOf(live)).pipe(Effect.tap(() => Deferred.isDone(live.value.revoked).pipe(Effect.flatMap((done) => done ? revoked : Effect.void)))), revoked);
|
|
455
478
|
}));
|
|
456
479
|
return use;
|
|
457
480
|
};
|
|
@@ -462,7 +485,8 @@ const acquireGeneration = (open, own) => Effect.uninterruptibleMask((restore) =>
|
|
|
462
485
|
},
|
|
463
486
|
scope,
|
|
464
487
|
ending: Deferred.makeUnsafe(),
|
|
465
|
-
revoked: Deferred.makeUnsafe()
|
|
488
|
+
revoked: Deferred.makeUnsafe(),
|
|
489
|
+
released: Deferred.makeUnsafe()
|
|
466
490
|
})))));
|
|
467
491
|
const changedUntil = (state, predicate) => SubscriptionRef.changes(state).pipe(Stream.filter(predicate), Stream.runHead, Effect.asVoid);
|
|
468
492
|
const watchGeneration = (live, rotateBefore, apply) => {
|
|
@@ -482,12 +506,7 @@ const watchGeneration = (live, rotateBefore, apply) => {
|
|
|
482
506
|
});
|
|
483
507
|
};
|
|
484
508
|
const releaseGeneration = (command, state, apply) => Effect.gen(function* () {
|
|
485
|
-
const { live
|
|
486
|
-
yield* Deferred.succeed(live.value.revoked, new Superseded({
|
|
487
|
-
generation: live.number,
|
|
488
|
-
reason: ending.reason,
|
|
489
|
-
revokeAt: ending.revokeAt
|
|
490
|
-
}));
|
|
509
|
+
const { live } = command;
|
|
491
510
|
const holds = lookup(state.value, live.number)?.holds ?? [];
|
|
492
511
|
const cleaned = yield* Effect.forEach(holds, (hold) => Effect.exit(hold._tag === "Retained" ? Scope.close(hold.scope, Exit.void) : Effect.void), { concurrency: 8 });
|
|
493
512
|
yield* changedUntil(state, (current) => (lookup(current, live.number)?.holds.size ?? 0) === 0);
|
|
@@ -498,8 +517,25 @@ const releaseGeneration = (command, state, apply) => Effect.gen(function* () {
|
|
|
498
517
|
number: live.number,
|
|
499
518
|
released
|
|
500
519
|
});
|
|
520
|
+
if (released !== void 0) yield* Deferred.succeed(live.value.released, released);
|
|
501
521
|
yield* Exit.asVoidAll([...cleaned, closed]);
|
|
502
522
|
});
|
|
523
|
+
const openedInput = (number, exit) => Exit.isSuccess(exit) ? {
|
|
524
|
+
_tag: "Opened",
|
|
525
|
+
live: {
|
|
526
|
+
number,
|
|
527
|
+
value: exit.value
|
|
528
|
+
}
|
|
529
|
+
} : Cause.hasInterruptsOnly(exit.cause) ? {
|
|
530
|
+
_tag: "Abandoned",
|
|
531
|
+
number
|
|
532
|
+
} : {
|
|
533
|
+
_tag: "Failed",
|
|
534
|
+
number,
|
|
535
|
+
detail: describe(exit.cause),
|
|
536
|
+
cause: Cause.squash(exit.cause)
|
|
537
|
+
};
|
|
538
|
+
const admissionAfter = (previous, state) => previous === void 0 || state._tag !== "Retired" && state.serving?.number === previous.number ? void 0 : Deferred.await(previous.value.released);
|
|
503
539
|
const makeScoped = Effect.fn("Supervisor.make")(function* (options) {
|
|
504
540
|
const clock = yield* Clock.Clock;
|
|
505
541
|
const now = () => Number(clock.monotonicTimeNanosUnsafe()) / 1e6;
|
|
@@ -541,11 +577,7 @@ const makeScoped = Effect.fn("Supervisor.make")(function* (options) {
|
|
|
541
577
|
yield* Effect.forkIn(Effect.provideContext(watchGeneration(command.live, rotateBefore, apply), context), command.live.value.scope);
|
|
542
578
|
break;
|
|
543
579
|
case "Drain": {
|
|
544
|
-
const ending =
|
|
545
|
-
generation: command.live.number,
|
|
546
|
-
reason: command.ending.reason,
|
|
547
|
-
revokeAt: command.ending.revokeAt
|
|
548
|
-
});
|
|
580
|
+
const ending = superseded(command.live.number, command.ending);
|
|
549
581
|
yield* Deferred.succeed(command.live.value.ending, ending);
|
|
550
582
|
yield* Effect.forkIn(until(ending.revokeAt, now).pipe(Effect.andThen(Effect.suspend(() => apply({
|
|
551
583
|
_tag: "Revoke",
|
|
@@ -554,28 +586,22 @@ const makeScoped = Effect.fn("Supervisor.make")(function* (options) {
|
|
|
554
586
|
break;
|
|
555
587
|
}
|
|
556
588
|
case "Release":
|
|
557
|
-
case "Leave":
|
|
589
|
+
case "Leave":
|
|
590
|
+
yield* Deferred.succeed(command.live.value.revoked, superseded(command.live.number, command.ending));
|
|
591
|
+
runRelease(command.live.number, releaseGeneration(command, state, apply));
|
|
558
592
|
}
|
|
559
593
|
});
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
while: (error) => !definite(error)
|
|
564
|
-
})))).pipe(Effect.exit, Effect.flatMap((exit) => Exit.isSuccess(exit) ? apply({
|
|
565
|
-
_tag: "Opened",
|
|
566
|
-
live: {
|
|
594
|
+
const openGeneration = (number, previous) => {
|
|
595
|
+
const after = previous === void 0 ? void 0 : Deferred.await(previous.value.released);
|
|
596
|
+
const openOnce = acquireGeneration(Effect.suspend(() => options.admit?.({
|
|
567
597
|
number,
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
number,
|
|
576
|
-
detail: describe(exit.cause),
|
|
577
|
-
cause: Cause.squash(exit.cause)
|
|
578
|
-
}))));
|
|
598
|
+
after: admissionAfter(previous, state.value)
|
|
599
|
+
}) ?? Effect.void).pipe(Effect.andThen(options.open)), own).pipe(Effect.provideContext(context));
|
|
600
|
+
return Effect.uninterruptibleMask((restore) => restore((exclusive && after !== void 0 ? after : Effect.void).pipe(Effect.andThen(Effect.retry(openOnce, {
|
|
601
|
+
schedule: reopen,
|
|
602
|
+
while: (error) => !definite(error)
|
|
603
|
+
})))).pipe(Effect.exit, Effect.flatMap((exit) => apply(openedInput(number, exit)))));
|
|
604
|
+
};
|
|
579
605
|
const use = leasing(state, apply, waitTimeout, now, clock);
|
|
580
606
|
const end = yield* Effect.cached(PubSub.publish(events, Exit.void));
|
|
581
607
|
const stop = (input) => apply(input).pipe(Effect.andThen(FiberHandle.clear(opening)), Effect.andThen(FiberMap.awaitEmpty(releases)), Effect.andThen(Effect.suspend(() => Deferred.isDone(releases.deferred).pipe(Effect.flatMap((failed) => failed ? FiberMap.join(releases) : Effect.void)))), Effect.ensuring(end), Effect.asVoid);
|
|
@@ -596,4 +622,4 @@ function make(options) {
|
|
|
596
622
|
//#endregion
|
|
597
623
|
export { Unconfirmed as _, make as a, Draining as c, Lost as d, Open as f, Settled as g, Reopening as h, Unavailable as i, GenerationState as l, Released as m, Superseded as n, Closed as o, Opening as p, Supervisor_exports as r, Down as s, Generation as t, Kept as u };
|
|
598
624
|
|
|
599
|
-
//# sourceMappingURL=Supervisor-
|
|
625
|
+
//# sourceMappingURL=Supervisor-BE2sGHBK.mjs.map
|