effect-browser 0.3.0-beta.13 → 0.3.0-beta.15
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 +59 -19
- package/dist/{Browser-QiT6Cdn_.mjs → Browser-BBSySyjT.mjs} +2 -2
- package/dist/{Browser-QiT6Cdn_.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 +1 -1
- package/dist/Chromium.mjs +1 -1
- package/dist/Expect.d.mts +1 -1
- package/dist/Expect.mjs +1 -1
- 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 +7 -3
- 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-BS0jpENY.d.mts → Plan-6As6vcwT.d.mts} +3 -3
- package/dist/{Plan-Bl2NM1D5.mjs → Plan-CXA2BW-G.mjs} +4 -6
- 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 +53 -20
- package/dist/Stage.mjs.map +1 -1
- package/dist/{Supervisor-CYWFK3vM.d.mts → Supervisor-5tZ47hR5.d.mts} +40 -10
- package/dist/{Supervisor-TNKNOVzu.mjs → Supervisor-DaGlQVjv.mjs} +120 -68
- package/dist/Supervisor-DaGlQVjv.mjs.map +1 -0
- package/dist/Supervisor.d.mts +2 -2
- package/dist/Supervisor.mjs +2 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +3 -3
- package/dist/{page-ZxMhw16l.mjs → page-B92QnRfT.mjs} +196 -166
- 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 +7 -6
- package/src/Stage.ts +61 -21
- package/src/Supervisor.ts +205 -172
- 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 +40 -15
- package/dist/Plan-Bl2NM1D5.mjs.map +0 -1
- package/dist/Supervisor-TNKNOVzu.mjs.map +0 -1
- package/dist/page-ZxMhw16l.mjs.map +0 -1
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(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":";;;;;;;;;;;;;;;;;;AAoCA,IAAa,YAAb,cAA+B,OAAO,MAAiB,0BAA0B,CAAC,CAAC;CACjF,MAAM,OAAO;CACb,IAAI,OAAO;CACX,SAAS,OAAO;AAClB,CAAC,CAAC,CAAC,CAAC;AAmDJ,MAAM,eAAe,aAAa,OAAO,IAAI,OAAO,EAAE,OAAO,WAAW,CAAC,CAAC;;AAG1E,MAAM,UACJ,MACA,UACG;CACH,IAAI,KAAK,MAAM,SAAS,WAAW;CACnC,IAAI,MAAM,SAAS,QAAQ,KAAK,QAAQ,EAAE,MAAM,UAAU;MACrD,IAAI,MAAM,SAAS,SAAS,KAAK,QAAQ;EAAE,MAAM;EAAS,QAAQ,MAAM;CAAM;MAC9E,IAAI,KAAK,MAAM,SAAS,SAAS,KAAK,QAAQ;EAAE,MAAM;EAAS,QAAQ,KAAA;CAAU;AACxF;AAEA,MAAM,eAAe,UAAwB;CAC3C,MAAM,EAAE,MAAM,WAAW,YAAY,KAAK;CAE1C,OAAO,SAAS,aAAa,WAAW;AAC1C;AAEA,MAAM,QAAQ,SACZ,OAAO,cAAc;CACnB,OAAO,MAAM,EAAE,MAAM,OAAO,CAAC;CAE7B,OAAO,MAAM,MAAM,KAAK,OAAO,KAAK,IAAI;AAC1C,CAAC;AAEH,MAAM,WAAW,SAAe;CAC9B,MAAM,QAAQ,KAAK,MAAM,SAAS,UAAU,KAAK,MAAM,SAAS,KAAA;CAChE,MAAM,YAAY,YAAY,KAAK,IAAI;CAEvC,OAAO,UAAU,KAAA,MAAc,cAAc,KAAA,KAAa,UAAU,QAAQ,KAAK,KAC7E,QACA,KAAA;AACN;AAEA,MAAM,SAAS,SACb,KAAK,UAAU,KAAA,IACX,OAAO,QACP,MAAM,MAAM,KAAK,KAAK,CAAC,CAAC,KACtB,OAAO,SAAS,SACd,KAAK,UAAU,IAAI,KAAK,CAAC,MAAM,kBAAkB,KAAK,KAAK,IACvD,OAAO,UAAU,KAAK,KAAK,IAC3B,OAAO,KAAK,OAAO,CAAC,CAC1B,CACF;AAEN,MAAM,WAAW,MAAY,SAC3B,OAAO,cAAc;CACnB,IAAI,KAAK,MAAM,SAAS,WAAW,OAAO,OAAO,KAAK,OAAO,CAAC;CAC9D,IAAI,KAAK,KAAK,WAAW,SAAS,GAAG,OAAO,SAAS,OAAO,IAAI,OAAO,EAAE,OAAO,OAAO,CAAC,CAAC;CACzF,IAAI,QAAQ,IAAI,MAAM,KAAA,GAAW,OAAO,OAAO;CAE/C,OAAO,OAAO,UACZ,OAAO,WAAW,KAAK,OAAO,CAAC,CAAC,KAC9B,OAAO,aAAa,QAAQ,IAAI,MAAM,KAAA,CAAS,GAC/C,OAAO,SACP,OAAO,MACT,GACA,MAAM,IAAI,CACZ,CAAC,CAAC,KACA,OAAO,cAAc;EACnB,UAAU;EACV,cAAc,SAAS,OAAO,IAAI,QAAQ,EAAE,QAAQ,SAAS,SAAS,IAAI,EAAE,CAAC,CAAC;CAChF,CAAC,CACH;AACF,CAAC;AAGH,MAAM,SAAS,IAAY,QACzB,OAAO,cACL,KAAK,IAAI,IACL,OAAO,MAAM,SAAS,OAAO,KAAK,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,MAAM,IAAI,GAAG,CAAC,CAAC,IACxF,OAAO,IACb;;AAGF,MAAM,gBAAgB;CACpB,MAAM,UAA0C,CAAC;CAEjD,QAAiB,WACf,OAAO,kBACL,OAAO,WAAW;EAChB,MAAM,OAAO,SAAS,WAAiB;EAEvC,QAAQ,KAAK,IAAI;EACjB,IAAI,QAAQ,WAAW,GAAG,SAAS,WAAW,MAAM,KAAK,IAAI;EAE7D,OAAO;CACT,CAAC,IACA,SAAS,SAAS,MAAM,IAAI,CAAC,CAAC,KAAK,OAAO,QAAQ,MAAM,CAAC,IACzD,SACC,OAAO,WAAW;EAChB,MAAM,KAAK,QAAQ,QAAQ,IAAI;EAE/B,QAAQ,OAAO,IAAI,CAAC;EACpB,IAAI,OAAO,KAAK,QAAQ,OAAO,KAAA,GAAW,SAAS,WAAW,QAAQ,IAAI,KAAK,IAAI;CACrF,CAAC,CACL;AACJ;;AAGA,MAAM,WAAW,MAAY,UAAiB;CAC5C,MAAM,OAAO,KAAK,OAAO,WAAW;CAEpC,MAAM,UACJ,SAAS,KAAA,KAAa,KAAK,UAAU,IAAI,IAAI,MAAM,gBAAgB,KAAK,KAAK,IAAI,OAAO,KAAK;CAE/F,IAAI,OAAO,OAAO,OAAO,KAAK,YAAY,QAAQ,KAAK,CAAC,CAAC,SAAS,WAChE,OAAO,OAAO,KAAK,QAAQ,KAAK;CAElC,OAAO,KAAK,KAAK,WAAW,SAAS,IACjC,SAAS,SAAS,IAAI,OAAO,EAAE,OAAO,OAAO,CAAC,CAAC,IAC9C,YAAY,KAAK,IAAI,CAAC,EAAE,QAAQ,KAAK,KAAK,OAAO,QAAQ,KAAK;AACrE;;AAGA,MAAM,YACJ,QACA,SAGA,KACA,SAEA,OAAO,OACL,OAAO,WAAW;CAChB,MAAM,SAAS,SAAS,UAAU,IAAI;CAEtC,IACE,OAAO,OAAO,MAAM,KACpB,CAAC,SAAS,SAAS,OAAO,KAAK,KAC/B,CAAC,SAAS,WAAW,OAAO,KAAK,GAEjC,MAAM,IAAI,WAAW,+CAA+C;CACtE,MAAM,SAAS,SAAS,SAAS,OAAO,KAAK;CAC7C,IAAI,YAAY,IAAI;CACpB,IAAI;CAEJ,MAAM,QAAQ,OAAO,IAAI,aAAa;EACpC,OAAO,MAAM,KAAK,IAAI,WAAW,QAAQ,CAAC,EAAE,aAAa,SAAS,IAAI,QAAQ,GAAG;EACjF,YAAY,IAAI;EAChB,MAAM,SAAS,QAAQ;EACvB,MAAM,QAAQ,QAAQ;EAEtB,IACE,WAAW,KAAA,KACX,UAAU,KAAA,KACV,SAAS,OAAO,QAChB,YAAY,OAAO,YAAY,QAE/B,OAAO,OAAO,KAAY;EAC5B,MAAM,EAAE,SAAS;EAEjB,MAAM,YAAY,OAAO,OAAO,OAAO,QAAQ,MAAM,KAAK,CAAC;EAE3D,IAAI,QAAQ,CAAC,EAAE,SAAS,QAAQ,QAAQ,CAAC,EAAE,UAAU,OAAO,OAAO,OAAO,KAAY;EACtF,MAAM,KAAK,IAAI;EAEf,YAAY;EACZ,IAAI,OAAO,UAAU,SAAS,KAAK,YAAY,UAAU,OAAO,CAAC,CAAC,SAAS,WAAW;GACpF,OAAO;GAEP,OAAO,OAAO,KAAY;IAAE,MAAM;IAAQ,MAAM,KAAK,KAAK;IAAI;GAAG,CAAC;EACpE;EAEA,OAAO,OAAO,KAAY;GACxB,MAAM,OAAO,UAAU,SAAS,KAAK,UAAU,UAAU,UAAU;GACnE;GACA;EACF,CAAC;CACH,CAAC;CAED,OAAO,OAAO,MACZ,OAAO,WAAW,MAAM,CAAC,CAAC,KACxB,OAAO,QAAQ,EAAE,WAAW,SAAS,IAAI,GACzC,OAAO,KAAK,EAAE,aAAoB;EAAE,MAAM;EAAW;CAAM,EAAE,CAC/D,GACA,OAAO,iBAAiB,KAAK,CAAC,CAAC,KAC7B,OAAO,OAAO,OAAO,MAAM,GAC3B,OAAO,KAAK,EAAE,YAAY,KAAK,CACjC,CACF;AACF,CAAC,CACH;AAEF,MAAM,YACJ,MACA,OACA,OACA,UACA,SACA,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO,MAAM,KAAK,KAAK;CACrC,MAAM,UAAU,OAAO,SAAS,KAAW;CAE3C,MAAM,OAAa;EACjB;EACA;EACA,SAAS,OAAO,OAAO,QAAe;GAAE,UAAU;GAAG,QAAQ;EAAE,CAAC;EAChE,OAAO,EAAE,MAAM,UAAU;EACzB,OAAO,KAAA;CACT;CAEA,OAAO,MAAM,aACX,OACA,OAAO,WAAW;EAChB,OAAO,MAAM,EAAE,MAAM,OAAO,CAAC;CAC/B,CAAC,CACH;CACA,OAAO,OAAO,mBAAoB,QAAQ,MAAM,OAAO,OAAO,OAAO,KAAK,IAAI,CAAE;CAEhF,MAAM,UAAU,UACd,OAAO,cAAc;EACnB,OAAO,MAAM;GAAE,MAAM;GAAS;EAAM,CAAC;EAErC,QAAQ,QAAQ,MAAM,OAAO,QAAQ,MAAM,KAAK,IAAI,OAAO,KAAA,CAAM,KAC/D,OAAO,QAAQ,OAAO,QAAQ,KAAK,SAAS,KAAK,CAAC,CACpD;CACF,CAAC;CAyBH,KAAK,QAAQ,OAvBG,OAAO,OACrB,OAAO,IAAI,aAAa;EACtB,OAAO,MAAM,EAAE,MAAM,QAAQ,CAAC;EAE9B,MAAM,YAAY,YAAY,IAAI;EAElC,MAAM,EAAE,QAAQ,SAAS,UAAU,OACjC,WAAW,QAAQ,QAAQ,KACzB,OAAO,QAAQ;GAAE,QAAQ,KAAK,WAAW,QAAQ;GAAG,SAAS,KAAA;EAAU,CAAC;EAG5E,IAAI,UAAU,KAAA,KAAa,WAAW,QAAQ,KAAK,GAAG,OAAO,OAAO,KAAK;EACzE,OAAO,SAAS,QAAQ,SAAS,KAAA,CAAS;EAC1C,OAAO,OAAO,WAAW,QAAQ,MAAM;CACzC,CAAC,CACH,CAAC,CAAC,KACA,OAAO,SAAS,OAAO,WAAW,OAAO,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,GAClE,OAAO,MAAM;EACX,QAAQ,UAAU,KAAK,MAAM,SAAS,WAAW,YAAY,KAAK;EAClE,UAAU,SAAS,OAAO,YAAY;CACxC,CAAC,CAGuB,CAAC,CAAC,KAC1B,OAAO,eAAe,MAAM,OAAO,KAAK,GACxC,OAAO,OAAO,KAAK,CACrB;CACA,OAAO,OAAO,UAAU,SAAS,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,KAC5D,OAAO,cAAc,KAAK,IAAI,CAAC,CACjC;CAEA,OAAO;AACT,CAAC;AAEH,MAAa,OAAO,OAAO,GAAG,YAAY,CAAC,CAAC,WAC1C,UAAmB,CAAC,GACgC;CACpD,MAAM,QAAQ,OAAO,MAAM;CAC3B,MAAM,QAAQ,OAAO,OAAO;CAC5B,MAAM,YAAY,OAAO,MAAM,yBAAyB,CAAC,IAAI;CAC7D,MAAM,SAAS,SAAS,UAAU,QAAQ,cAAc,SAAS,QAAQ,CAAC,CAAC;CAE3E,IACE,OAAO,OAAO,MAAM,KACpB,CAAC,SAAS,SAAS,OAAO,KAAK,KAC/B,CAAC,SAAS,WAAW,OAAO,KAAK,GAEjC,OAAO,OAAO,SACZ,SACA,IAAI,eAAe,EAAE,QAAQ,gDAAgD,CAAC,CAChF;CACF,MAAM,aAAa,OAAO;CAC1B,MAAM,WAAW;EAAE,SAAS,QAAQ;EAAS,MAAM,QAAQ;CAAK;CAChE,MAAM,SAAS,OAAO,OAAO,QAAe,EAAE;CAC9C,MAAM,YAAY,QAAQ;CAE1B,IAAI;CAIJ,IAAI,UAAU,OAAO;CAErB,OAAO,OAAO,mBAAmB,OAAO,SAAS,MAAM,CAAC;CAExD,MAAM,WAAW,MAAY,UAC3B,OAAO,cAAc;EACnB,UAAU,KAAK,IAAI,SAAS,MAAM,QAAQ;EAC1C,IAAI,YAAY,KAAA,GAAW;GACzB,QAAQ,YAAY,IAAI;GACxB,QAAQ,QAAQ;EAClB;EAEA,OAAO,OAAO,QAAQ,QAAQ;GAAE;GAAM;EAAM,CAAC;CAC/C,CAAC;CAEH,MAAM,UAAU,MAAY,UAAgD,CAAC,MAC3E,OAAO,cAAc;EACnB,MAAM,QAAQ,QAAQ,MAAM,IAAI;EAEhC,OAAO,UACL,OAAO,IAAI,aAAa;GACtB,IAAI,CAAC,OAAO,SAAS,KAAK,GACxB,OAAO,OAAO,SACZ,OACA,IAAI,eAAe,EAAE,QAAQ,sCAAsC,CAAC,CACtE;GACF,OAAO,QAAQ,MAAM,UAAU;GAC/B,MAAM,MAAM;GAEZ,IAAI,KAAK,SAAS,MAAM,OAAO,IAAI;GAEnC,OAAO,OAAO,UAAU,MAAM,OAAO,GAAG,GAAG,MAAM,IAAI,CAAC;GAEtD,MAAM,WAAW,OAAO,cAAc;IACpC,MAAM,QAAQ,QAAQ,IAAI;IAE1B,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO,KAAK,OAAO,CAAC;IACpD,MAAM,KAAK,KAAK,IAAI,IAAI,GAAG,OAAO,MAAM,UAAU,OAAO;IAEzD,UAAU;KACR;KACA;KACA,WAAW,IAAI,UAAU;MAAE,MAAM,KAAK,KAAK;MAAI;MAAI,SAAS,KAAK;KAAM,CAAC;KACxE,WAAW;IACb;IAEA,OAAO,QAAQ,MAAM,KAAK,CAAC,CAAC,KAAK,OAAO,GAAG,QAAQ,SAAS,CAAC;GAC/D,CAAC,CAAC,CAAC,KAAK,OAAO,eAAe;GAE9B,MAAM,gBAAgB,QAAQ,MAAM,UAAU,CAAC,CAAC,KAC9C,OAAO,QAAQ,QAAQ,GACvB,OAAO,eAAe,MAAM,OAAO,KAAK,CAC1C;GAEA,MAAM,SAAS,QAAQ,KAAA,IAAY,KAAA,IAAY,YAAY,IAAI,KAAK,IAAI;GAExE,OAAO,QACL,WAAW,KAAA,IAAY,gBAAgB,OAAO,WAAW,aAAa,EAAA,CACtE,KAGA,OAAO,SACL,OAAO,cACL,SAAS,SAAS,QAAQ,QAAQ,KAAA,IAAY,KAAK,IAAI,IAAI,IAAI,OAAO,IACxE,CACF,CACF;EACF,CAAC,CAAC,CAAC,KAAK,OAAO,eAAe,MAAM,OAAO,KAAK,CAAC,CACnD;CACF,CAAC;CAEH,MAAM,OAAqB,SACzB,OAAO,IACL,SAAS,MAAM,OAAO,OAAO,gBAAgB,SAAS,MAAM,OAAO,IAClE,UAAU;EACT;EACA,OAAO,QAAQ,MAAM,UAAU,CAAC,CAAC,KAAK,OAAO,eAAe,MAAM,OAAO,KAAK,CAAC;EAC/E,SAAS,YAAY,OAAO,MAAM,OAAO;CAC3C,EACF;CAEF,OAAO;EACL;EACA,UAAU,MAAM,YACd,OAAO,cAAc;GACnB,MAAM,QAAQ,SAAS,MAAM,IAAI;GAEjC,OAAO,SAAS,KAAK,SAAS,OAC1B,OAAO,GAAG,QAAQ,QAAQ,MAAM,UAAU,GAAG,QAAQ,SAAS,IAC9D,OAAO,OAAO,OAAO,QAAQ,IAAI,IAAI,IAAI,aAAa,SAAS,OAAO,EAAE,IAAI,MAAM,CAAC,CAAC,CAAC;EAC3F,CAAC;EACH,QAAQ,EAAE,WACR,SAAS,cAAc,SAAS,KAAK,IAAI,CAAC,CAAC,KAAK,OAAO,eAAe,MAAM,OAAO,KAAK,CAAC;EAC3F,QAAQ,OAAO,WAAW,MAAM,CAAC,CAAC,KAAK,OAAO,KAAK,EAAE,YAAY,KAAK,CAAC;EACvE,SAAS,OAAO,WAAW,OAAO,cAAc,SAAS,KAAK,IAAI,CAAC;EACnE,QAAQ,UAAU,CAAC,MACjB,YAAY,KAAA,IACR,OAAO,cACP,OAAO,OAAO,QAAQ,KAAK,KAAK,aAAa,EAAE,QAAQ,QAAQ,UAAU,WAAW,CAAC,CAAC;CAC9F;AACF,CAAC"}
|
|
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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"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { c as DisconnectCause } from "./BrowserEvent-BRu2juEz.mjs";
|
|
2
1
|
import { Service, t as Browser_d_exports } from "./Browser.mjs";
|
|
3
|
-
import { Cause, Duration, Effect, Schedule, Schema, Scope, Stream } from "effect";
|
|
2
|
+
import { Cause, Context, Duration, Effect, Schedule, Schema, Scope, Stream } from "effect";
|
|
4
3
|
declare namespace lifecycle_d_exports {
|
|
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 };
|
|
4
|
+
export { Closed, Command, Down, Drain, Draining, GenerationState, Held, Input, Kept, Live, Lost, Open, Opening, Released, Reopening, Resolution, Settled, State, Step, Unconfirmed, combine, lookup, resolve, start, transition };
|
|
6
5
|
}
|
|
7
6
|
declare const Settled_base: Schema.Class<Settled, Schema.TaggedStruct<"Settled", {}>, {}>;
|
|
8
7
|
/** The provider confirmed that the generation's browser ended. */
|
|
@@ -30,9 +29,10 @@ declare const Draining_base: Schema.Class<Draining, Schema.TaggedStruct<"Drainin
|
|
|
30
29
|
/** No longer serving; holds end at this host-monotonic deadline or when their owners let go. */
|
|
31
30
|
declare class Draining extends Draining_base {}
|
|
32
31
|
declare const Lost_base: Schema.Class<Lost, Schema.TaggedStruct<"Lost", {
|
|
33
|
-
readonly cause: Schema.Literals<readonly ["connection", "session", "released"]>;
|
|
32
|
+
readonly cause: Schema.Union<readonly [Schema.Literals<readonly ["connection", "session", "released"]>, Schema.Literal<"declared">]>;
|
|
33
|
+
readonly detail: Schema.optional<Schema.String>;
|
|
34
34
|
}>, {}>;
|
|
35
|
-
/** Lost
|
|
35
|
+
/** Lost through disconnection or a consumer's declaration that it is unusable. */
|
|
36
36
|
declare class Lost extends Lost_base {}
|
|
37
37
|
declare const Down_base: Schema.Class<Down, Schema.TaggedStruct<"Down", {
|
|
38
38
|
readonly detail: Schema.String;
|
|
@@ -88,10 +88,15 @@ type State<A, H = unknown> = ({
|
|
|
88
88
|
readonly _tag: "Retired";
|
|
89
89
|
}) & {
|
|
90
90
|
readonly draining: ReadonlyMap<number, Drain<A, H>>;
|
|
91
|
+
/** Retain the first unconfirmed end, otherwise the latest settled end, without its resources. */
|
|
92
|
+
readonly released?: Released | undefined;
|
|
91
93
|
};
|
|
92
94
|
type Input<A, H = unknown> = {
|
|
93
95
|
readonly _tag: "Opened";
|
|
94
96
|
readonly live: Live<A>;
|
|
97
|
+
} | {
|
|
98
|
+
readonly _tag: "AttemptReleased";
|
|
99
|
+
readonly released: Released;
|
|
95
100
|
} | {
|
|
96
101
|
readonly _tag: "Failed";
|
|
97
102
|
readonly number: number;
|
|
@@ -103,7 +108,8 @@ type Input<A, H = unknown> = {
|
|
|
103
108
|
} | {
|
|
104
109
|
readonly _tag: "Lost";
|
|
105
110
|
readonly number: number;
|
|
106
|
-
readonly cause:
|
|
111
|
+
readonly cause: Lost["cause"];
|
|
112
|
+
readonly detail?: string | undefined;
|
|
107
113
|
} | {
|
|
108
114
|
readonly _tag: "Rotate";
|
|
109
115
|
readonly due?: number | undefined;
|
|
@@ -130,7 +136,6 @@ type Input<A, H = unknown> = {
|
|
|
130
136
|
type Command<A> = {
|
|
131
137
|
readonly _tag: "Open";
|
|
132
138
|
readonly number: number;
|
|
133
|
-
readonly after: number | undefined;
|
|
134
139
|
} | {
|
|
135
140
|
readonly _tag: "Watch";
|
|
136
141
|
readonly live: Live<A>;
|
|
@@ -163,6 +168,8 @@ declare const start: <A, H = unknown>() => Step<A, H>;
|
|
|
163
168
|
declare const lookup: <A, H>(state: State<A, H>, number: number) => Held<A, H> | undefined;
|
|
164
169
|
/** Pure admission and lifecycle changes. A rejected hold preserves state identity. */
|
|
165
170
|
declare const transition: <A, H = unknown>(state: State<A, H>, input: Input<A, H>, exclusive: boolean, now?: number, handover?: number) => Step<A, H>;
|
|
171
|
+
/** Later confirmations cannot establish that an earlier unconfirmed session ended. */
|
|
172
|
+
declare const combine: (before: Released | undefined, next: Released | undefined) => Settled | Unconfirmed | undefined;
|
|
166
173
|
type Resolution<A> = {
|
|
167
174
|
readonly _tag: "Serve";
|
|
168
175
|
readonly live: Live<A>;
|
|
@@ -175,7 +182,7 @@ type Resolution<A> = {
|
|
|
175
182
|
/** An unheld browser resolution; use obtains its hold atomically through Hold instead. */
|
|
176
183
|
declare const resolve: <A, H>(state: State<A, H>, target: number | undefined) => Resolution<A> | undefined;
|
|
177
184
|
declare namespace Supervisor_d_exports {
|
|
178
|
-
export { Closed, Down, Draining, Generation, GenerationState, Kept, Lease, Lost, Open, Opened, Opening, Options, Released, Reopening, Settled, Superseded, Supervisor, Unavailable, Unconfirmed, UseOptions, make };
|
|
185
|
+
export { Admission, Closed, Down, Draining, Generation, GenerationState, Kept, Lease, Lost, Open, Opened, Opening, Options, Released, Releasing, Reopening, Settled, Superseded, Supervisor, Unavailable, Unconfirmed, UseOptions, make };
|
|
179
186
|
}
|
|
180
187
|
declare const Generation_base: Schema.Class<Generation, Schema.Struct<{
|
|
181
188
|
readonly number: Schema.Int;
|
|
@@ -208,9 +215,30 @@ interface Opened<A = void> {
|
|
|
208
215
|
*/
|
|
209
216
|
readonly release?: Effect.Effect<Released> | undefined;
|
|
210
217
|
}
|
|
218
|
+
/** Facts available before an acquisition attempt; awaiting `after` never changes release order. */
|
|
219
|
+
interface Admission {
|
|
220
|
+
readonly number: number;
|
|
221
|
+
/**
|
|
222
|
+
* Every earlier session that stopped serving, including failed attempts and older drains.
|
|
223
|
+
* Waits for all pending releases; an Unconfirmed end remains visible to later attempts.
|
|
224
|
+
* Undefined before any release. A still-serving nonexclusive predecessor is excluded.
|
|
225
|
+
*/
|
|
226
|
+
readonly after: Effect.Effect<Released> | undefined;
|
|
227
|
+
}
|
|
228
|
+
declare const Releasing_base: Context.ServiceClass<Releasing, "effect-browser/Supervisor/Releasing", {
|
|
229
|
+
readonly released: (outcome: Released) => Effect.Effect<void>;
|
|
230
|
+
}>;
|
|
231
|
+
/**
|
|
232
|
+
* Optional service supplied during `open`. Providers report failed-attempt cleanup here before
|
|
233
|
+
* their scope closes; successful generations instead return `Opened.release`. Reporting does
|
|
234
|
+
* not release resources or replace the original acquisition failure.
|
|
235
|
+
*/
|
|
236
|
+
declare class Releasing extends Releasing_base {}
|
|
211
237
|
interface Options<E, R, A = void> {
|
|
212
238
|
/** Opens one generation in the scope it is given. */
|
|
213
239
|
readonly open: Effect.Effect<Opened<A>, E, R>;
|
|
240
|
+
/** Runs before every open attempt, including the first; failures follow `reopen` and `definite`. */
|
|
241
|
+
readonly admit?: ((opening: Admission) => Effect.Effect<void, E, R>) | undefined;
|
|
214
242
|
/** When to try a failed open again. Defaults to exponential from 1 second, for 5 minutes. */
|
|
215
243
|
readonly reopen?: Schedule.Schedule<unknown, E> | undefined;
|
|
216
244
|
/**
|
|
@@ -247,6 +275,8 @@ interface Lease<A> {
|
|
|
247
275
|
readonly generation: number;
|
|
248
276
|
readonly browser: Service;
|
|
249
277
|
readonly value: A;
|
|
278
|
+
/** Declare this serving generation unusable and revoke it now; stale declarations do nothing. */
|
|
279
|
+
readonly lose: (detail: string) => Effect.Effect<void>;
|
|
250
280
|
/** First notice that serving ended; loss or retirement may revoke before its deadline. */
|
|
251
281
|
readonly ending: Effect.Effect<Superseded>;
|
|
252
282
|
/** An independent scope; its resources close before provider release. Fails once revoked. */
|
|
@@ -276,5 +306,5 @@ type MakeOptions<E, R, A> = Omit<Options<E, R, A>, "open"> & {
|
|
|
276
306
|
declare function make<E, R, A = void>(options: Options<E, R, A>): Effect.Effect<Supervisor<A>, never, Scope.Scope | Exclude<R, Scope.Scope>>;
|
|
277
307
|
declare function make<E, R>(options: MakeOptions<E, R, never>): Effect.Effect<Supervisor<void>, never, Scope.Scope | Exclude<R, Scope.Scope>>;
|
|
278
308
|
//#endregion
|
|
279
|
-
export {
|
|
280
|
-
//# sourceMappingURL=Supervisor-
|
|
309
|
+
export { Settled as C, Reopening as S, Kept as _, Options as a, Opening as b, Supervisor as c, UseOptions as d, make as f, GenerationState as g, Draining as h, Opened as i, Supervisor_d_exports as l, Down as m, Generation as n, Releasing as o, Closed as p, Lease as r, Superseded as s, Admission as t, Unavailable as u, Lost as v, Unconfirmed as w, Released as x, Open as y };
|
|
310
|
+
//# sourceMappingURL=Supervisor-5tZ47hR5.d.mts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { DisconnectCause } from "./BrowserEvent.mjs";
|
|
3
|
-
import { Cause, Clock, DateTime, Deferred, Duration, Effect, Exit, FiberHandle, FiberMap, Option, Predicate, PubSub, Schedule, Schema, Scope, Semaphore, Stream, SubscriptionRef } from "effect";
|
|
3
|
+
import { Cause, Clock, Context, DateTime, Deferred, Duration, Effect, Exit, FiberHandle, FiberMap, Option, Predicate, PubSub, Schedule, Schema, Scope, Semaphore, Stream, SubscriptionRef } from "effect";
|
|
4
4
|
//#region src/internal/supervisor/lifecycle.ts
|
|
5
5
|
/**
|
|
6
6
|
* A supervisor's life as one state and one transition function, and the generation states it
|
|
@@ -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.
|
|
@@ -64,8 +67,7 @@ const start = () => ({
|
|
|
64
67
|
events: [[1, new Opening()]],
|
|
65
68
|
commands: [{
|
|
66
69
|
_tag: "Open",
|
|
67
|
-
number: 1
|
|
68
|
-
after: void 0
|
|
70
|
+
number: 1
|
|
69
71
|
}]
|
|
70
72
|
});
|
|
71
73
|
const servingOf = (state) => state._tag === "Retired" ? void 0 : state.serving;
|
|
@@ -145,30 +147,33 @@ const holds = (change, input) => {
|
|
|
145
147
|
const next = (change, number, reopen) => {
|
|
146
148
|
const { exclusive } = change;
|
|
147
149
|
const current = servingOf(change.state);
|
|
148
|
-
const after = exclusive ? current?.number ?? change.state.draining.keys().next().value : void 0;
|
|
149
150
|
if (current !== void 0 && (reopen || exclusive)) end(change, current, reopen ? "lost" : "rotated");
|
|
150
151
|
change.state = {
|
|
151
152
|
_tag: "Opening",
|
|
152
153
|
number,
|
|
153
154
|
serving: reopen || exclusive ? void 0 : current,
|
|
154
|
-
draining: change.state.draining
|
|
155
|
+
draining: change.state.draining,
|
|
156
|
+
released: change.state.released
|
|
155
157
|
};
|
|
156
158
|
change.events.push([number, reopen ? new Reopening() : new Opening()]);
|
|
157
159
|
change.commands.push({
|
|
158
160
|
_tag: "Open",
|
|
159
|
-
number
|
|
160
|
-
after
|
|
161
|
+
number
|
|
161
162
|
});
|
|
162
163
|
};
|
|
163
164
|
const lost = (change, input) => {
|
|
164
165
|
const { state } = change;
|
|
165
166
|
const drain = state.draining.get(input.number);
|
|
166
167
|
const current = servingOf(state);
|
|
168
|
+
if (input.cause === "declared" && current?.number !== input.number) return;
|
|
167
169
|
if (drain?._tag === "Draining") {
|
|
168
170
|
change.events.push([input.number, new Lost({ cause: input.cause })]);
|
|
169
171
|
revoke(change, input.number, "lost");
|
|
170
172
|
} else if (state._tag !== "Retired" && current?.number === input.number) {
|
|
171
|
-
change.events.push([input.number, new Lost({
|
|
173
|
+
change.events.push([input.number, new Lost({
|
|
174
|
+
cause: input.cause,
|
|
175
|
+
detail: input.detail
|
|
176
|
+
})]);
|
|
172
177
|
if (state._tag !== "Opening") next(change, (state._tag === "Open" ? current.number : state.number) + 1, true);
|
|
173
178
|
else {
|
|
174
179
|
change.state = {
|
|
@@ -214,7 +219,8 @@ const transition = (state, input, exclusive, now = 0, handover = 3e4) => {
|
|
|
214
219
|
change.state = {
|
|
215
220
|
_tag: "Open",
|
|
216
221
|
serving: live,
|
|
217
|
-
draining: change.state.draining
|
|
222
|
+
draining: change.state.draining,
|
|
223
|
+
released: change.state.released
|
|
218
224
|
};
|
|
219
225
|
}
|
|
220
226
|
break;
|
|
@@ -236,6 +242,12 @@ const transition = (state, input, exclusive, now = 0, handover = 3e4) => {
|
|
|
236
242
|
case "Abandoned":
|
|
237
243
|
change.events.push([input.number, new Closed({})]);
|
|
238
244
|
break;
|
|
245
|
+
case "AttemptReleased":
|
|
246
|
+
change.state = {
|
|
247
|
+
...state,
|
|
248
|
+
released: combine(state.released, input.released)
|
|
249
|
+
};
|
|
250
|
+
break;
|
|
239
251
|
case "Released": {
|
|
240
252
|
const drain = state.draining.get(input.number);
|
|
241
253
|
if (drain === void 0 || drain._tag === "Draining" || drain.live.holds.size > 0) break;
|
|
@@ -243,7 +255,8 @@ const transition = (state, input, exclusive, now = 0, handover = 3e4) => {
|
|
|
243
255
|
draining.delete(input.number);
|
|
244
256
|
change.state = {
|
|
245
257
|
...state,
|
|
246
|
-
draining
|
|
258
|
+
draining,
|
|
259
|
+
released: combine(state.released, input.released)
|
|
247
260
|
};
|
|
248
261
|
change.events.push([input.number, input.released === void 0 ? new Kept() : new Closed({ released: input.released })]);
|
|
249
262
|
break;
|
|
@@ -263,11 +276,14 @@ const transition = (state, input, exclusive, now = 0, handover = 3e4) => {
|
|
|
263
276
|
if (state.serving !== void 0) end(change, state.serving, "retired", input._tag === "Keep");
|
|
264
277
|
change.state = {
|
|
265
278
|
_tag: "Retired",
|
|
266
|
-
draining: change.state.draining
|
|
279
|
+
draining: change.state.draining,
|
|
280
|
+
released: change.state.released
|
|
267
281
|
};
|
|
268
282
|
}
|
|
269
283
|
return change;
|
|
270
284
|
};
|
|
285
|
+
/** Later confirmations cannot establish that an earlier unconfirmed session ended. */
|
|
286
|
+
const combine = (before, next) => before?._tag === "Unconfirmed" ? before : next ?? before;
|
|
271
287
|
/** An unheld browser resolution; use obtains its hold atomically through Hold instead. */
|
|
272
288
|
const resolve = (state, target) => {
|
|
273
289
|
if (state._tag === "Retired") return {
|
|
@@ -319,6 +335,7 @@ var Supervisor_exports = /* @__PURE__ */ __exportAll({
|
|
|
319
335
|
Open: () => Open,
|
|
320
336
|
Opening: () => Opening,
|
|
321
337
|
Released: () => Released,
|
|
338
|
+
Releasing: () => Releasing,
|
|
322
339
|
Reopening: () => Reopening,
|
|
323
340
|
Settled: () => Settled,
|
|
324
341
|
Superseded: () => Superseded,
|
|
@@ -350,6 +367,12 @@ var Unavailable = class extends Schema.TaggedError()("Unavailable", {
|
|
|
350
367
|
return `no browser: ${this.detail}`;
|
|
351
368
|
}
|
|
352
369
|
};
|
|
370
|
+
/**
|
|
371
|
+
* Optional service supplied during `open`. Providers report failed-attempt cleanup here before
|
|
372
|
+
* their scope closes; successful generations instead return `Opened.release`. Reporting does
|
|
373
|
+
* not release resources or replace the original acquisition failure.
|
|
374
|
+
*/
|
|
375
|
+
var Releasing = class extends Context.Service()("effect-browser/Supervisor/Releasing") {};
|
|
353
376
|
/** Work whose generation was revoked; input receipts still determine whether input reached a site. */
|
|
354
377
|
var Superseded = class extends Schema.TaggedError()("Superseded", {
|
|
355
378
|
generation: Schema.Int,
|
|
@@ -360,6 +383,11 @@ var Superseded = class extends Schema.TaggedError()("Superseded", {
|
|
|
360
383
|
]),
|
|
361
384
|
revokeAt: Schema.Finite
|
|
362
385
|
}) {};
|
|
386
|
+
const superseded = (generation, { reason, revokeAt }) => new Superseded({
|
|
387
|
+
generation,
|
|
388
|
+
reason,
|
|
389
|
+
revokeAt
|
|
390
|
+
});
|
|
363
391
|
const retired = () => new Unavailable({
|
|
364
392
|
reason: "retired",
|
|
365
393
|
detail: "the supervisor was retired"
|
|
@@ -368,11 +396,6 @@ const describe = (cause) => {
|
|
|
368
396
|
const error = Cause.squash(cause);
|
|
369
397
|
return Predicate.isError(error) ? error.message : String(error);
|
|
370
398
|
};
|
|
371
|
-
const unavailable = (resolution) => new Unavailable({
|
|
372
|
-
reason: resolution.reason,
|
|
373
|
-
detail: resolution.detail,
|
|
374
|
-
cause: resolution.cause
|
|
375
|
-
});
|
|
376
399
|
const timedOut = () => new Unavailable({
|
|
377
400
|
reason: "opening",
|
|
378
401
|
detail: "no generation opened within the wait"
|
|
@@ -383,7 +406,7 @@ const bounded = (input, name) => Effect.suspend(() => {
|
|
|
383
406
|
});
|
|
384
407
|
/** Resolve immediately before starting a bounded wait, including when its duration is zero. */
|
|
385
408
|
const waitFor = (state, wait, clock, target) => {
|
|
386
|
-
const result = (resolution) => resolution._tag === "Serve" ? Effect.succeed(resolution.live) : Effect.fail(
|
|
409
|
+
const result = (resolution) => resolution._tag === "Serve" ? Effect.succeed(resolution.live) : Effect.fail(new Unavailable(resolution));
|
|
387
410
|
return Effect.suspend(() => {
|
|
388
411
|
const current = resolve(state.value, target);
|
|
389
412
|
return current === void 0 ? SubscriptionRef.changes(state).pipe(Stream.map((state) => resolve(state, target)), Stream.filter(Predicate.isNotUndefined), Stream.runHead, Effect.flatMap(Option.match({
|
|
@@ -404,7 +427,7 @@ const untilRotation = (expiresAt, before) => Clock.currentTimeMillis.pipe(Effect
|
|
|
404
427
|
const until = (at, now) => Effect.suspend(() => now() >= at ? Effect.void : Effect.sleep(Duration.millis(Math.ceil(at - now()))).pipe(Effect.andThen(until(at, now))));
|
|
405
428
|
/** Borrowed work and transferred scopes hold a generation until cleanup lets it go. */
|
|
406
429
|
const leasing = (state, apply, waitTimeout, now, clock) => {
|
|
407
|
-
const borrow =
|
|
430
|
+
const borrow = Effect.fnUntraced(function* (hold, wait) {
|
|
408
431
|
const until = now() + Duration.toMillis(wait);
|
|
409
432
|
for (;;) {
|
|
410
433
|
const step = yield* apply({
|
|
@@ -420,6 +443,12 @@ const leasing = (state, apply, waitTimeout, now, clock) => {
|
|
|
420
443
|
generation: live.number,
|
|
421
444
|
browser: live.value.opened.browser,
|
|
422
445
|
value: live.value.opened.value,
|
|
446
|
+
lose: (detail) => apply({
|
|
447
|
+
_tag: "Lost",
|
|
448
|
+
number: live.number,
|
|
449
|
+
cause: "declared",
|
|
450
|
+
detail
|
|
451
|
+
}).pipe(Effect.asVoid),
|
|
423
452
|
ending: Deferred.await(live.value.ending),
|
|
424
453
|
retain: Effect.uninterruptible(Effect.gen(function* () {
|
|
425
454
|
const scope = yield* Scope.make();
|
|
@@ -451,7 +480,8 @@ const leasing = (state, apply, waitTimeout, now, clock) => {
|
|
|
451
480
|
number: live.number,
|
|
452
481
|
hold
|
|
453
482
|
}).pipe(Effect.asVoid));
|
|
454
|
-
|
|
483
|
+
const revoked = Deferred.await(live.value.revoked).pipe(Effect.flatMap(Effect.fail));
|
|
484
|
+
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
485
|
}));
|
|
456
486
|
return use;
|
|
457
487
|
};
|
|
@@ -462,9 +492,9 @@ const acquireGeneration = (open, own) => Effect.uninterruptibleMask((restore) =>
|
|
|
462
492
|
},
|
|
463
493
|
scope,
|
|
464
494
|
ending: Deferred.makeUnsafe(),
|
|
465
|
-
revoked: Deferred.makeUnsafe()
|
|
495
|
+
revoked: Deferred.makeUnsafe(),
|
|
496
|
+
released: Deferred.makeUnsafe()
|
|
466
497
|
})))));
|
|
467
|
-
const changedUntil = (state, predicate) => SubscriptionRef.changes(state).pipe(Stream.filter(predicate), Stream.runHead, Effect.asVoid);
|
|
468
498
|
const watchGeneration = (live, rotateBefore, apply) => {
|
|
469
499
|
const { browser } = live.value.opened;
|
|
470
500
|
const loss = browser.disconnected.pipe(Effect.flatMap((cause) => apply({
|
|
@@ -481,16 +511,10 @@ const watchGeneration = (live, rotateBefore, apply) => {
|
|
|
481
511
|
discard: true
|
|
482
512
|
});
|
|
483
513
|
};
|
|
484
|
-
const releaseGeneration = (command, state, apply)
|
|
485
|
-
const { live
|
|
486
|
-
yield*
|
|
487
|
-
|
|
488
|
-
reason: ending.reason,
|
|
489
|
-
revokeAt: ending.revokeAt
|
|
490
|
-
}));
|
|
491
|
-
const holds = lookup(state.value, live.number)?.holds ?? [];
|
|
492
|
-
const cleaned = yield* Effect.forEach(holds, (hold) => Effect.exit(hold._tag === "Retained" ? Scope.close(hold.scope, Exit.void) : Effect.void), { concurrency: 8 });
|
|
493
|
-
yield* changedUntil(state, (current) => (lookup(current, live.number)?.holds.size ?? 0) === 0);
|
|
514
|
+
const releaseGeneration = Effect.fnUntraced(function* (command, state, apply) {
|
|
515
|
+
const { live } = command;
|
|
516
|
+
const cleaned = yield* Effect.forEach(lookup(state.value, live.number)?.holds ?? [], (hold) => Effect.exit(hold._tag === "Retained" ? Scope.close(hold.scope, Exit.void) : Effect.void), { concurrency: 8 });
|
|
517
|
+
yield* SubscriptionRef.changes(state).pipe(Stream.filter((current) => (lookup(current, live.number)?.holds.size ?? 0) === 0), Stream.runHead);
|
|
494
518
|
const released = command._tag === "Leave" ? void 0 : yield* (live.value.opened.release ?? Effect.succeed(new Settled())).pipe(Effect.catchCause((cause) => Effect.succeed(new Unconfirmed({ detail: `the release failed: ${describe(cause)}` }))));
|
|
495
519
|
const closed = yield* Effect.exit(Scope.close(live.value.scope, Exit.void));
|
|
496
520
|
yield* apply({
|
|
@@ -498,15 +522,24 @@ const releaseGeneration = (command, state, apply) => Effect.gen(function* () {
|
|
|
498
522
|
number: live.number,
|
|
499
523
|
released
|
|
500
524
|
});
|
|
525
|
+
if (released !== void 0) yield* Deferred.succeed(live.value.released, released);
|
|
501
526
|
yield* Exit.asVoidAll([...cleaned, closed]);
|
|
502
527
|
});
|
|
528
|
+
const admissionAfter = (state) => {
|
|
529
|
+
const released = state.released;
|
|
530
|
+
const pending = [...state.draining.values()].filter((drain) => drain._tag !== "Leaving").map(({ live }) => live.value.released);
|
|
531
|
+
return pending.length === 0 && released === void 0 ? void 0 : Effect.gen(function* () {
|
|
532
|
+
let outcome = released;
|
|
533
|
+
for (const release of pending) outcome = combine(outcome, yield* Deferred.await(release));
|
|
534
|
+
return outcome ?? new Settled();
|
|
535
|
+
});
|
|
536
|
+
};
|
|
503
537
|
const makeScoped = Effect.fn("Supervisor.make")(function* (options) {
|
|
504
538
|
const clock = yield* Clock.Clock;
|
|
505
539
|
const now = () => Number(clock.monotonicTimeNanosUnsafe()) / 1e6;
|
|
506
540
|
const context = yield* Effect.context();
|
|
507
541
|
const own = yield* Scope.fork(yield* Effect.scope);
|
|
508
542
|
const exclusive = options.exclusive ?? false;
|
|
509
|
-
const definite = options.definite ?? (() => false);
|
|
510
543
|
const reopen = options.reopen ?? Schedule.exponential("1 second").pipe(Schedule.upTo({ duration: Duration.minutes(5) }));
|
|
511
544
|
const rotateBefore = options.rotateBefore === void 0 ? void 0 : Duration.fromInputUnsafe(options.rotateBefore);
|
|
512
545
|
const waitTimeout = yield* bounded(options.waitTimeout ?? Duration.minutes(2), "waitTimeout");
|
|
@@ -519,14 +552,14 @@ const makeScoped = Effect.fn("Supervisor.make")(function* (options) {
|
|
|
519
552
|
const releases = yield* FiberMap.make();
|
|
520
553
|
const runOpen = yield* FiberHandle.runtime(opening)();
|
|
521
554
|
const runRelease = yield* FiberMap.runtime(releases)();
|
|
522
|
-
const apply =
|
|
555
|
+
const apply = Effect.fnUntraced(function* (input) {
|
|
523
556
|
const current = state.value;
|
|
524
557
|
const step = transition(current, input, exclusive, now(), handover);
|
|
525
558
|
if (step.state !== current) yield* SubscriptionRef.set(state, step.state);
|
|
526
559
|
yield* perform(step);
|
|
527
560
|
return step;
|
|
528
|
-
})
|
|
529
|
-
const perform =
|
|
561
|
+
}, (effect) => applying.withPermit(effect), Effect.uninterruptible, Effect.provideService(Clock.Clock, clock));
|
|
562
|
+
const perform = Effect.fnUntraced(function* (step) {
|
|
530
563
|
for (const [number, generation] of step.events) PubSub.publishUnsafe(events, [new Generation({
|
|
531
564
|
number,
|
|
532
565
|
at: clock.currentTimeMillisUnsafe(),
|
|
@@ -535,17 +568,13 @@ const makeScoped = Effect.fn("Supervisor.make")(function* (options) {
|
|
|
535
568
|
})]);
|
|
536
569
|
for (const command of step.commands) switch (command._tag) {
|
|
537
570
|
case "Open":
|
|
538
|
-
runOpen(openGeneration(command.number
|
|
571
|
+
runOpen(openGeneration(command.number));
|
|
539
572
|
break;
|
|
540
573
|
case "Watch":
|
|
541
574
|
yield* Effect.forkIn(Effect.provideContext(watchGeneration(command.live, rotateBefore, apply), context), command.live.value.scope);
|
|
542
575
|
break;
|
|
543
576
|
case "Drain": {
|
|
544
|
-
const ending =
|
|
545
|
-
generation: command.live.number,
|
|
546
|
-
reason: command.ending.reason,
|
|
547
|
-
revokeAt: command.ending.revokeAt
|
|
548
|
-
});
|
|
577
|
+
const ending = superseded(command.live.number, command.ending);
|
|
549
578
|
yield* Deferred.succeed(command.live.value.ending, ending);
|
|
550
579
|
yield* Effect.forkIn(until(ending.revokeAt, now).pipe(Effect.andThen(Effect.suspend(() => apply({
|
|
551
580
|
_tag: "Revoke",
|
|
@@ -554,37 +583,60 @@ const makeScoped = Effect.fn("Supervisor.make")(function* (options) {
|
|
|
554
583
|
break;
|
|
555
584
|
}
|
|
556
585
|
case "Release":
|
|
557
|
-
case "Leave":
|
|
586
|
+
case "Leave":
|
|
587
|
+
yield* Deferred.succeed(command.live.value.revoked, superseded(command.live.number, command.ending));
|
|
588
|
+
runRelease(command.live.number, releaseGeneration(command, state, apply));
|
|
558
589
|
}
|
|
559
590
|
});
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
591
|
+
const openGeneration = (number) => {
|
|
592
|
+
const openOnce = acquireGeneration(Effect.gen(function* () {
|
|
593
|
+
const after = admissionAfter(state.value);
|
|
594
|
+
if (exclusive && after !== void 0) yield* after;
|
|
595
|
+
yield* options.admit?.({
|
|
596
|
+
number,
|
|
597
|
+
after
|
|
598
|
+
}) ?? Effect.void;
|
|
599
|
+
return yield* options.open.pipe(Effect.provideService(Releasing, { released: (released) => apply({
|
|
600
|
+
_tag: "AttemptReleased",
|
|
601
|
+
released
|
|
602
|
+
}).pipe(Effect.asVoid) }));
|
|
603
|
+
}), own).pipe(Effect.provideContext(context));
|
|
604
|
+
return Effect.uninterruptibleMask((restore) => restore(Effect.retry(openOnce, {
|
|
605
|
+
schedule: reopen,
|
|
606
|
+
while: (error) => !options.definite?.(error)
|
|
607
|
+
})).pipe(Effect.exit, Effect.flatMap((exit) => {
|
|
608
|
+
if (Exit.isSuccess(exit)) return apply({
|
|
609
|
+
_tag: "Opened",
|
|
610
|
+
live: {
|
|
611
|
+
number,
|
|
612
|
+
value: exit.value
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
const cause = exit.cause;
|
|
616
|
+
return apply(Cause.hasInterruptsOnly(cause) ? {
|
|
617
|
+
_tag: "Abandoned",
|
|
618
|
+
number
|
|
619
|
+
} : {
|
|
620
|
+
_tag: "Failed",
|
|
621
|
+
number,
|
|
622
|
+
detail: describe(cause),
|
|
623
|
+
cause: Cause.squash(cause)
|
|
624
|
+
});
|
|
625
|
+
})));
|
|
626
|
+
};
|
|
580
627
|
const end = yield* Effect.cached(PubSub.publish(events, Exit.void));
|
|
581
|
-
const stop =
|
|
628
|
+
const stop = Effect.fnUntraced(function* (input) {
|
|
629
|
+
yield* apply(input);
|
|
630
|
+
yield* FiberHandle.clear(opening);
|
|
631
|
+
yield* FiberMap.awaitEmpty(releases);
|
|
632
|
+
if (yield* Deferred.isDone(releases.deferred)) yield* FiberMap.join(releases);
|
|
633
|
+
}, Effect.ensuring(end));
|
|
582
634
|
const retire = yield* Effect.cached(stop({ _tag: "Retire" }));
|
|
583
635
|
yield* Effect.addFinalizer(() => options.keep === true ? stop({ _tag: "Keep" }) : retire);
|
|
584
636
|
yield* perform(started);
|
|
585
637
|
return {
|
|
586
638
|
browser: Effect.map(waitFor(state, waitTimeout, clock), (live) => live.value.opened.browser),
|
|
587
|
-
use,
|
|
639
|
+
use: leasing(state, apply, waitTimeout, now, clock),
|
|
588
640
|
states: Stream.fromPubSubTake(events),
|
|
589
641
|
rotate: apply({ _tag: "Rotate" }).pipe(Effect.flatMap(({ reply }) => reply === void 0 ? Effect.fail(retired()) : Effect.map(waitFor(state, waitTimeout, clock, reply), (live) => live.value.opened.browser))),
|
|
590
642
|
retire
|
|
@@ -594,6 +646,6 @@ function make(options) {
|
|
|
594
646
|
return makeScoped(options);
|
|
595
647
|
}
|
|
596
648
|
//#endregion
|
|
597
|
-
export {
|
|
649
|
+
export { Settled as _, Unavailable as a, Down as c, Kept as d, Lost as f, Reopening as g, Released as h, Supervisor_exports as i, Draining as l, Opening as m, Releasing as n, make as o, Open as p, Superseded as r, Closed as s, Generation as t, GenerationState as u, Unconfirmed as v };
|
|
598
650
|
|
|
599
|
-
//# sourceMappingURL=Supervisor-
|
|
651
|
+
//# sourceMappingURL=Supervisor-DaGlQVjv.mjs.map
|