effect-browserbase 0.1.0-beta.104 → 0.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +61 -305
  2. package/dist/Account.d.mts +4 -6
  3. package/dist/Account.mjs +4 -9
  4. package/dist/Account.mjs.map +1 -1
  5. package/dist/{Acquisition-WBafHwXH.mjs → Acquisition-D8o4jtCg.mjs} +22 -68
  6. package/dist/Acquisition-D8o4jtCg.mjs.map +1 -0
  7. package/dist/Agents.d.mts +4 -6
  8. package/dist/Agents.mjs +3 -19
  9. package/dist/Agents.mjs.map +1 -1
  10. package/dist/Allocation.d.mts +9 -11
  11. package/dist/Allocation.mjs +2 -4
  12. package/dist/Allocation.mjs.map +1 -1
  13. package/dist/Browser.d.mts +23 -141
  14. package/dist/Browser.mjs +208 -164
  15. package/dist/Browser.mjs.map +1 -1
  16. package/dist/BrowserBinding.d.mts +7 -34
  17. package/dist/BrowserBinding.mjs +30 -1
  18. package/dist/BrowserBinding.mjs.map +1 -0
  19. package/dist/Certificates.d.mts +3 -5
  20. package/dist/Certificates.mjs +2 -7
  21. package/dist/Certificates.mjs.map +1 -1
  22. package/dist/{Cleanup-G2YpTCuU.d.mts → Cleanup-DMTDBir8.d.mts} +5 -6
  23. package/dist/Cleanup.d.mts +3 -2
  24. package/dist/Cleanup.mjs +1 -6
  25. package/dist/Cleanup.mjs.map +1 -1
  26. package/dist/{Client-D1_xYM9o.mjs → Client-BQteUePT.mjs} +2 -4
  27. package/dist/{Client-D1_xYM9o.mjs.map → Client-BQteUePT.mjs.map} +1 -1
  28. package/dist/Client.d.mts +51 -2
  29. package/dist/Client.mjs +1 -1
  30. package/dist/Connection-DcvJZEdc.mjs +65 -0
  31. package/dist/Connection-DcvJZEdc.mjs.map +1 -0
  32. package/dist/ContextCoordination.d.mts +5 -6
  33. package/dist/ContextCoordination.mjs +1 -8
  34. package/dist/ContextCoordination.mjs.map +1 -1
  35. package/dist/Contexts.d.mts +4 -6
  36. package/dist/Contexts.mjs +2 -8
  37. package/dist/Contexts.mjs.map +1 -1
  38. package/dist/Downloads.d.mts +6 -8
  39. package/dist/Downloads.mjs +3 -8
  40. package/dist/Downloads.mjs.map +1 -1
  41. package/dist/Errors.d.mts +3 -26
  42. package/dist/Errors.mjs +1 -137
  43. package/dist/Errors.mjs.map +1 -1
  44. package/dist/{Extensions-ClZbYb1H.mjs → Extensions-nMNMPGCc.mjs} +3 -9
  45. package/dist/{Extensions-ClZbYb1H.mjs.map → Extensions-nMNMPGCc.mjs.map} +1 -1
  46. package/dist/Extensions.d.mts +4 -6
  47. package/dist/Extensions.mjs +1 -1
  48. package/dist/Functions.d.mts +4 -6
  49. package/dist/Functions.mjs +3 -17
  50. package/dist/Functions.mjs.map +1 -1
  51. package/dist/{Launch-BHcAEq05.mjs → Launch-m0fxzJ49.mjs} +5 -5
  52. package/dist/Launch-m0fxzJ49.mjs.map +1 -0
  53. package/dist/Launch.d.mts +130 -2
  54. package/dist/Launch.mjs +4 -11
  55. package/dist/Launch.mjs.map +1 -1
  56. package/dist/PageFetch.d.mts +3 -5
  57. package/dist/PageFetch.mjs +2 -8
  58. package/dist/PageFetch.mjs.map +1 -1
  59. package/dist/Projects.d.mts +3 -5
  60. package/dist/Projects.mjs +2 -8
  61. package/dist/Projects.mjs.map +1 -1
  62. package/dist/Recordings.d.mts +6 -8
  63. package/dist/Recordings.mjs +3 -5
  64. package/dist/Recordings.mjs.map +1 -1
  65. package/dist/{References-URDAtapt.d.mts → References-DOH3Wu44.d.mts} +5 -6
  66. package/dist/References.d.mts +3 -2
  67. package/dist/References.mjs +10 -18
  68. package/dist/References.mjs.map +1 -1
  69. package/dist/Replays.d.mts +6 -8
  70. package/dist/Replays.mjs +3 -5
  71. package/dist/Replays.mjs.map +1 -1
  72. package/dist/Search.d.mts +3 -5
  73. package/dist/Search.mjs +2 -9
  74. package/dist/Search.mjs.map +1 -1
  75. package/dist/{SessionData-DRKcKCqw.d.mts → SessionData-5xYZDshJ.d.mts} +5 -6
  76. package/dist/SessionData.d.mts +3 -2
  77. package/dist/SessionData.mjs +1 -8
  78. package/dist/SessionData.mjs.map +1 -1
  79. package/dist/{Sessions-CtbseWEh.mjs → Sessions-DoQGg9nB.mjs} +34 -18
  80. package/dist/Sessions-DoQGg9nB.mjs.map +1 -0
  81. package/dist/{Sessions-B8E4kMxo.d.mts → Sessions-tFJYaXFq.d.mts} +8 -8
  82. package/dist/Sessions.d.mts +4 -3
  83. package/dist/Sessions.mjs +1 -1
  84. package/dist/{Transfers-Czv5IZzJ.d.mts → Transfers-GVqzrDWl.d.mts} +22 -9
  85. package/dist/Transfers.d.mts +3 -2
  86. package/dist/Transfers.mjs +12 -28
  87. package/dist/Transfers.mjs.map +1 -1
  88. package/dist/Uploads.d.mts +6 -8
  89. package/dist/Uploads.mjs +3 -5
  90. package/dist/Uploads.mjs.map +1 -1
  91. package/dist/Webhooks.d.mts +3 -5
  92. package/dist/Webhooks.mjs +2 -11
  93. package/dist/Webhooks.mjs.map +1 -1
  94. package/dist/{WriterFacts-osvYAsIx.d.mts → WriterFacts-5CPBGG1P.d.mts} +4 -3
  95. package/dist/index.d.mts +27 -31
  96. package/dist/index.mjs +26 -31
  97. package/package.json +3 -28
  98. package/dist/Acquisition-WBafHwXH.mjs.map +0 -1
  99. package/dist/Association-CEps4hYP.mjs +0 -10
  100. package/dist/Association-CEps4hYP.mjs.map +0 -1
  101. package/dist/Bootstrap-BXuzZojd.mjs +0 -194
  102. package/dist/Bootstrap-BXuzZojd.mjs.map +0 -1
  103. package/dist/Bootstrap-m7lHgj0D.d.mts +0 -177
  104. package/dist/Bootstrap.d.mts +0 -2
  105. package/dist/Bootstrap.mjs +0 -2
  106. package/dist/BrowserBinding-DlJTGYZL.mjs +0 -2508
  107. package/dist/BrowserBinding-DlJTGYZL.mjs.map +0 -1
  108. package/dist/BrowserData-LpcAuAub.d.mts +0 -369
  109. package/dist/BrowserData.d.mts +0 -2
  110. package/dist/BrowserData.mjs +0 -442
  111. package/dist/BrowserData.mjs.map +0 -1
  112. package/dist/Capture-Bc0z9IAi.mjs +0 -571
  113. package/dist/Capture-Bc0z9IAi.mjs.map +0 -1
  114. package/dist/Capture-BvWI-l1A.d.mts +0 -168
  115. package/dist/Capture.d.mts +0 -2
  116. package/dist/Capture.mjs +0 -2
  117. package/dist/Client-Ca0zSg1n.d.mts +0 -53
  118. package/dist/Images-DgscEfSf.mjs +0 -127
  119. package/dist/Images-DgscEfSf.mjs.map +0 -1
  120. package/dist/Launch-BHcAEq05.mjs.map +0 -1
  121. package/dist/Launch-C8CfaYxb.d.mts +0 -132
  122. package/dist/LocalBrowser-BLancJ3R.d.mts +0 -79
  123. package/dist/LocalBrowser-DsQLWNL1.mjs +0 -439
  124. package/dist/LocalBrowser-DsQLWNL1.mjs.map +0 -1
  125. package/dist/LocalBrowser.d.mts +0 -2
  126. package/dist/LocalBrowser.mjs +0 -2
  127. package/dist/PageControl.d.mts +0 -16
  128. package/dist/PageControl.mjs +0 -36
  129. package/dist/PageControl.mjs.map +0 -1
  130. package/dist/PageControlAssociation-DabEg5AR.mjs +0 -10
  131. package/dist/PageControlAssociation-DabEg5AR.mjs.map +0 -1
  132. package/dist/Session-D4nT9hOO.mjs +0 -1379
  133. package/dist/Session-D4nT9hOO.mjs.map +0 -1
  134. package/dist/Sessions-CtbseWEh.mjs.map +0 -1
  135. package/dist/rolldown-runtime-D7D4PA-g.mjs +0 -13
@@ -1,1379 +0,0 @@
1
- import { Identifier } from "./References.mjs";
2
- import { BrowserError, InitializationError, SessionError } from "./Errors.mjs";
3
- import { t as BrowserbaseClient } from "./Client-D1_xYM9o.mjs";
4
- import { ActionResult, Checkpoint, CheckpointOptions, ClickRequest, FillRequest, HoverRequest, InputReceipt, KeyStroke, NavigateRequest, NavigationResult, Observation, ObservationOptions, ObservedElement, PointerMoveRequest, PressRequest, ReadTextRequest, ScreenshotRequest, ScreenshotResult, ScrollRequest, StartNavigationRequest, Target, TextResult, TypeRequest, Viewport, WheelRequest } from "./BrowserData.mjs";
5
- import { isTerminalSessionStatus } from "./SessionData.mjs";
6
- import { n as nowMillis, r as until, t as deadlineAfter } from "./Deadline-CrViaogr.mjs";
7
- import { o as issueLiveView, t as BrowserbaseSessions } from "./Sessions-CtbseWEh.mjs";
8
- import { a as reported, i as noLocalConnection, n as reportCleanup, o as connectionAddress, r as makeCleanup, t as acquireRemote } from "./Acquisition-WBafHwXH.mjs";
9
- import { h as snapshot, m as invoke, s as Plan } from "./Bootstrap-BXuzZojd.mjs";
10
- import { o as publicError } from "./Images-DgscEfSf.mjs";
11
- import { o as bindingImplementation, t as BrowserbaseBrowserBinding } from "./BrowserBinding-DlJTGYZL.mjs";
12
- import { t as associate } from "./Association-CEps4hYP.mjs";
13
- import { t as associatePageControl } from "./PageControlAssociation-DabEg5AR.mjs";
14
- import { Cause, Clock, Deferred, Effect, Exit, FiberSet, Option, Redacted, Schema, Scope, Semaphore } from "effect";
15
- //#region src/internal/browser/BindingRunner.ts
16
- const makeBindingRunner = (capacity, handle, onFault) => {
17
- if (!Number.isSafeInteger(capacity) || capacity < 1) throw new RangeError("Invalid binding callback capacity");
18
- return Effect.gen(function* () {
19
- const parent = yield* Scope.Scope;
20
- const runtimeScope = yield* Scope.fork(parent, "sequential");
21
- const run = yield* FiberSet.makeRuntimePromise().pipe(Scope.provide(runtimeScope));
22
- let accepting = true;
23
- let faulted = false;
24
- let inFlight = 0;
25
- const close = () => {
26
- accepting = false;
27
- };
28
- const fault = () => {
29
- if (!accepting || faulted) return;
30
- faulted = true;
31
- onFault();
32
- };
33
- yield* Scope.addFinalizer(runtimeScope, Effect.sync(close));
34
- const submit = (input, failureMode = "fail-session") => {
35
- if (!accepting || faulted) return {
36
- _tag: "Rejected",
37
- reason: "closed"
38
- };
39
- if (inFlight >= capacity) {
40
- if (failureMode === "fail-session") fault();
41
- return {
42
- _tag: "Rejected",
43
- reason: "capacity"
44
- };
45
- }
46
- inFlight++;
47
- const result = run(Effect.exit(Effect.suspend(() => handle(input)))).then((exit) => {
48
- if (failureMode === "fail-session" && Exit.isFailure(exit)) fault();
49
- return exit;
50
- }, (error) => {
51
- if (failureMode === "fail-session") fault();
52
- throw error;
53
- }).finally(() => {
54
- inFlight--;
55
- });
56
- result.catch(() => {});
57
- return {
58
- _tag: "Accepted",
59
- result
60
- };
61
- };
62
- return {
63
- close,
64
- submit
65
- };
66
- });
67
- };
68
- //#endregion
69
- //#region src/internal/browser/Bootstrap.ts
70
- const guarded = (content, origins) => origins === void 0 ? `(() => {\n${content}\n})();` : `(() => {\nconst origins = ${JSON.stringify([...origins])};\nif (!origins.includes(globalThis.location && globalThis.location.origin)) return;\n${content}\n})();`;
71
- /**
72
- * Dependent steps become one script in declared order: a failing step stops the ones after it
73
- * rather than letting a later step observe half-initialized state. Origin-excluded steps are
74
- * skipped inside the document that reports the origin, never by guessing from a navigation URL.
75
- */
76
- const compileBootstrap = (plan) => {
77
- if (plan.scripts.length === 0 && plan.permissions.length === 0) return void 0;
78
- const bundle = plan.scripts.length === 0 ? void 0 : plan.scripts.map((script) => guarded(script.content, script.origins)).join("\n");
79
- const readiness = plan.scripts.flatMap((script) => script.readiness === void 0 ? [] : [{
80
- step: script.id,
81
- expression: script.readiness.expression,
82
- timeoutMillis: script.readiness.timeoutMillis,
83
- ...script.origins === void 0 ? {} : { origins: script.origins }
84
- }]);
85
- const existingDocuments = plan.scripts.some((script) => script.readiness?.existingDocuments === "RequireFreshNavigation") ? "RequireFreshNavigation" : "AcceptAlreadyRunning";
86
- return {
87
- ...bundle === void 0 ? {} : { bundle },
88
- permissions: plan.permissions.map((grant) => ({
89
- origin: grant.origin,
90
- permissions: [...grant.permissions]
91
- })),
92
- readiness,
93
- existingDocuments
94
- };
95
- };
96
- /** Duplicate step identity would make registration order and readiness reports ambiguous. */
97
- const duplicateStep = (plan) => new Set(plan.scripts.map((script) => script.id)).size !== plan.scripts.length || new Set(plan.permissions.map((grant) => grant.origin)).size !== plan.permissions.length || new Set((plan.bindings ?? []).map((registration) => registration.name)).size !== (plan.bindings?.length ?? 0);
98
- //#endregion
99
- //#region src/internal/browser/Bindings.ts
100
- const invalidPlan = () => BrowserError.make({
101
- operation: "configure",
102
- reason: "configuration",
103
- outcome: "undispatched"
104
- });
105
- const AdmissionPlan = Schema.Struct({
106
- ...Plan.fields,
107
- bindings: Schema.optionalKey(Schema.Array(Schema.Unknown).check(Schema.isMaxLength(16)))
108
- });
109
- /** Validate before allocation and retain an immutable snapshot, including authenticated callbacks. */
110
- const preparePlan = Effect.fnUntraced(function* (plan) {
111
- const source = yield* Effect.try({
112
- try: () => ({ ...plan }),
113
- catch: invalidPlan
114
- });
115
- const data = yield* Schema.decodeEffect(AdmissionPlan, { onExcessProperty: "error" })(source).pipe(Effect.mapError(invalidPlan));
116
- if (duplicateStep({
117
- scripts: data.scripts,
118
- permissions: data.permissions
119
- })) return yield* invalidPlan();
120
- const candidates = source.bindings === void 0 ? [] : source.bindings;
121
- const bindings = yield* Effect.forEach([...candidates], (registration) => snapshot(registration));
122
- if (new Set(bindings.map((registration) => registration.name)).size !== bindings.length) return yield* invalidPlan();
123
- return Object.freeze({
124
- scripts: Object.freeze(data.scripts.map((script) => Object.freeze({
125
- ...script,
126
- ...script.origins === void 0 ? {} : { origins: Object.freeze([...script.origins]) },
127
- ...script.readiness === void 0 ? {} : { readiness: Object.freeze({ ...script.readiness }) }
128
- }))),
129
- permissions: Object.freeze(data.permissions.map((grant) => Object.freeze({
130
- ...grant,
131
- permissions: Object.freeze([...grant.permissions])
132
- }))),
133
- ...bindings.length === 0 ? {} : { bindings: Object.freeze(bindings) }
134
- });
135
- });
136
- const rejected = () => {
137
- const error = /* @__PURE__ */ new Error("Browser binding call rejected");
138
- error.name = "BrowserbaseBindingError";
139
- error.stack = "";
140
- const result = Promise.reject(error);
141
- result.catch(() => {});
142
- return result;
143
- };
144
- const increment = (value) => Math.min(Number.MAX_SAFE_INTEGER, value + 1);
145
- /** The caller must pass preparePlan's snapshot; dependencies are captured at acquisition. */
146
- const makeBindings = Effect.fnUntraced(function* (plan) {
147
- const consumer = yield* Effect.context();
148
- const ownerScope = yield* Scope.fork(yield* Scope.Scope, "sequential");
149
- const failure = yield* Deferred.make();
150
- const connections = /* @__PURE__ */ new Set();
151
- const failures = [];
152
- let droppedFailures = 0;
153
- let faulted = false;
154
- let ownerClosed = false;
155
- const entries = (plan.bindings ?? []).map((registration) => ({
156
- registration,
157
- state: {
158
- name: registration.name,
159
- inFlight: 0,
160
- pendingNative: 0,
161
- retired: 0,
162
- accepted: 0,
163
- succeeded: 0,
164
- rejected: 0
165
- }
166
- }));
167
- yield* Scope.addFinalizer(ownerScope, Effect.suspend(() => {
168
- ownerClosed = true;
169
- const owned = [...connections];
170
- for (const connection of owned) connection.close();
171
- return Effect.forEach(owned, (connection) => connection.dispose, { discard: true });
172
- }));
173
- const connect = Effect.fnUntraced(function* (onFault, isActive, isCurrent = isActive) {
174
- const connectionScope = yield* Scope.fork(yield* Scope.Scope, "sequential");
175
- let closed = ownerClosed || faulted;
176
- const fences = [];
177
- const current = () => !closed && !ownerClosed && !faulted && isCurrent();
178
- const active = () => current() && isActive();
179
- const close = () => {
180
- closed = true;
181
- for (const fence of fences) fence();
182
- };
183
- const record = (registration, cause) => {
184
- if (failures.length === 32) {
185
- failures.shift();
186
- droppedFailures = increment(droppedFailures);
187
- }
188
- failures.push(Object.freeze({
189
- name: registration.name,
190
- mode: registration.failureMode,
191
- cause
192
- }));
193
- if (registration.failureMode === "fail-session" && current()) {
194
- faulted = true;
195
- Deferred.doneUnsafe(failure, Effect.failCause(cause));
196
- for (const connection of connections) connection.close();
197
- onFault();
198
- }
199
- };
200
- const bindings = yield* Effect.forEach(entries, ({ registration, state }) => Effect.gen(function* () {
201
- const error = (reason, operation = "callback") => InitializationError.make({
202
- operation,
203
- step: registration.name,
204
- reason
205
- });
206
- const release = (work) => {
207
- if (!work.finished || work.pending !== 0 || work.released) return;
208
- work.released = true;
209
- state.inFlight--;
210
- state.retired--;
211
- };
212
- const track = (work, promise) => {
213
- work.pending++;
214
- state.pendingNative++;
215
- const settled = () => {
216
- work.pending--;
217
- state.pendingNative--;
218
- release(work);
219
- };
220
- promise.then(settled, settled);
221
- return promise;
222
- };
223
- const finish = (work) => {
224
- if (work.finished) return;
225
- work.finished = true;
226
- state.retired++;
227
- const disposing = Promise.resolve().then(() => work.call.dispose()).catch(() => {
228
- record(registration, Cause.fail(error("native", "dispose")));
229
- });
230
- track(work, disposing);
231
- };
232
- const native = (work, action) => Effect.tryPromise({
233
- try: (signal) => track(work, action(signal)),
234
- catch: (cause) => Schema.is(InitializationError)(cause) ? error(cause.reason) : error("native")
235
- });
236
- const execute = Effect.fnUntraced(function* (work) {
237
- const check = Effect.suspend(() => active() ? native(work, work.call.check) : Effect.fail(error("closed")));
238
- if (!active()) return yield* error("closed");
239
- const text = yield* native(work, work.call.read);
240
- const result = yield* invoke(registration, text, check);
241
- yield* check;
242
- return result;
243
- });
244
- const runner = yield* makeBindingRunner(registration.maxConcurrent, (work) => Effect.scoped(execute(work)).pipe(Effect.timeoutOrElse({
245
- duration: registration.timeoutMillis,
246
- orElse: () => Effect.fail(error("timeout"))
247
- }), Effect.provideContext(consumer)), () => {}).pipe(Scope.provide(connectionScope));
248
- fences.push(runner.close);
249
- const invoke$1 = (call) => {
250
- if (!active()) {
251
- state.rejected = increment(state.rejected);
252
- return rejected();
253
- }
254
- if (state.inFlight >= registration.maxConcurrent) {
255
- state.rejected = increment(state.rejected);
256
- record(registration, Cause.fail(error("busy")));
257
- return rejected();
258
- }
259
- state.inFlight++;
260
- state.accepted = increment(state.accepted);
261
- const work = {
262
- call,
263
- pending: 0,
264
- finished: false,
265
- released: false
266
- };
267
- const admission = runner.submit(work, "reject-call");
268
- if (admission._tag === "Rejected") {
269
- state.rejected = increment(state.rejected);
270
- finish(work);
271
- return rejected();
272
- }
273
- const result = admission.result.then((exit) => {
274
- if (Exit.isFailure(exit)) {
275
- state.rejected = increment(state.rejected);
276
- record(registration, exit.cause);
277
- return rejected();
278
- }
279
- if (!active()) {
280
- state.rejected = increment(state.rejected);
281
- return rejected();
282
- }
283
- state.succeeded = increment(state.succeeded);
284
- return exit.value;
285
- }, () => {
286
- state.rejected = increment(state.rejected);
287
- if (active()) record(registration, Cause.fail(error("native")));
288
- return rejected();
289
- }).finally(() => finish(work));
290
- result.catch(() => {});
291
- return result;
292
- };
293
- return Object.freeze({
294
- name: registration.name,
295
- origins: registration.origins,
296
- maxConcurrent: registration.maxConcurrent,
297
- maxInputBytes: registration.maxInputBytes,
298
- timeoutMillis: registration.timeoutMillis,
299
- invoke: invoke$1
300
- });
301
- }));
302
- const connection = {
303
- bindings: Object.freeze(bindings),
304
- close,
305
- reportFailure: (error) => {
306
- if (!current()) return;
307
- record({
308
- name: error.step,
309
- failureMode: "fail-session"
310
- }, Cause.fail(error));
311
- },
312
- dispose: Effect.sync(close).pipe(Effect.andThen(Scope.close(connectionScope, Exit.void)))
313
- };
314
- connections.add(connection);
315
- yield* Scope.addFinalizer(connectionScope, Effect.sync(() => {
316
- close();
317
- connections.delete(connection);
318
- }));
319
- return connection;
320
- }, Effect.uninterruptible);
321
- return {
322
- failure: Deferred.await(failure),
323
- diagnostics: Effect.sync(() => Object.freeze({
324
- faulted,
325
- bindings: Object.freeze(entries.map(({ state }) => Object.freeze({ ...state }))),
326
- failures: Object.freeze([...failures]),
327
- droppedFailures
328
- })),
329
- connect
330
- };
331
- });
332
- //#endregion
333
- //#region src/internal/browser/PublicSession.ts
334
- const checked = (schema, value, operation) => Schema.decodeUnknownEffect(schema)(value, { onExcessProperty: "error" }).pipe(Effect.mapError(() => BrowserError.make({
335
- operation,
336
- reason: "configuration",
337
- outcome: "undispatched"
338
- })));
339
- const decoded = (schema, operation) => (value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => BrowserError.make({
340
- operation,
341
- reason: "malformed"
342
- })));
343
- const action = decoded(ActionResult, "action-result");
344
- const pointerMoved = decoded(InputReceipt, "pointer-move");
345
- const hovered = decoded(InputReceipt, "hover");
346
- const wheeled = decoded(InputReceipt, "wheel");
347
- const pressed = decoded(InputReceipt, "press");
348
- const typed = decoded(InputReceipt, "type");
349
- const makeTarget = (bound) => ({
350
- navigate: (request) => checked(NavigateRequest, request, "navigate").pipe(Effect.flatMap((value) => bound.navigate(value.url)), Effect.flatMap((url) => decoded(NavigationResult, "navigate")({ url }))),
351
- startNavigation: (request) => checked(StartNavigationRequest, request, "navigate").pipe(Effect.flatMap((value) => bound.startNavigation(value.url, value.timeoutMillis)), Effect.map((operation) => ({
352
- target: operation.target,
353
- completed: operation.completed.pipe(Effect.flatMap((url) => decoded(NavigationResult, "navigate")({ url }))),
354
- stop: operation.stop
355
- }))),
356
- readText: (request) => checked(ReadTextRequest, request, "read-text").pipe(Effect.flatMap((value) => bound.readText(value.selector)), Effect.flatMap((text) => decoded(TextResult, "read-text")({ text }))),
357
- click: (request) => checked(ClickRequest, request, "click").pipe(Effect.flatMap((value) => bound.click(value.selector)), Effect.flatMap((url) => action({ url }))),
358
- fill: (request) => checked(FillRequest, request, "fill").pipe(Effect.flatMap((value) => bound.fill(value.selector, value.value)), Effect.flatMap((url) => action({ url }))),
359
- scroll: (request) => checked(ScrollRequest, request, "scroll").pipe(Effect.flatMap((value) => bound.scroll(value.deltaX, value.deltaY)), Effect.flatMap((url) => action({ url }))),
360
- pointerMove: (request) => checked(PointerMoveRequest, request, "pointer-move").pipe(Effect.flatMap((value) => bound.pointerMove(value.to)), Effect.flatMap((input) => pointerMoved({
361
- ...input,
362
- kind: "pointer-move"
363
- }))),
364
- hover: (request) => checked(HoverRequest, request, "hover").pipe(Effect.flatMap((value) => bound.hover(value.selector)), Effect.flatMap((input) => hovered({
365
- ...input,
366
- kind: "hover"
367
- }))),
368
- wheel: (request) => checked(WheelRequest, request, "wheel").pipe(Effect.flatMap((value) => bound.wheel(value.deltaX, value.deltaY, value.at).pipe(Effect.flatMap((input) => wheeled({
369
- ...input,
370
- kind: "wheel",
371
- delta: {
372
- x: value.deltaX,
373
- y: value.deltaY
374
- }
375
- }))))),
376
- press: (request) => checked(PressRequest, request, "press").pipe(Effect.flatMap((value) => bound.press(value.key, value.modifiers ?? [], value.into)), Effect.flatMap((input) => pressed({
377
- ...input,
378
- kind: "press"
379
- }))),
380
- type: (request) => checked(TypeRequest, request, "type").pipe(Effect.flatMap((value) => bound.type(value.text, value.into)), Effect.flatMap((input) => typed({
381
- ...input,
382
- kind: "type"
383
- }))),
384
- screenshot: (request) => checked(ScreenshotRequest, request, "screenshot").pipe(Effect.flatMap((value) => bound.screenshot(value.fullPage)), Effect.flatMap((bytes) => decoded(ScreenshotResult, "screenshot")({
385
- mediaType: "image/png",
386
- bytes: new Uint8Array(bytes)
387
- })))
388
- });
389
- /** A browser-operation failure keeps its meaning when it is reported as an initialization one. */
390
- const initialization = (reason) => reason === "busy" ? "busy" : reason === "closed" || reason === "disconnected" ? "closed" : reason === "timeout" ? "timeout" : reason === "stale" ? "stale" : reason === "unsupported" ? "unsupported" : reason === "configuration" ? "configuration" : "native";
391
- const makeSession = (controls, bindings) => {
392
- const currentTarget = controls.currentTarget.pipe(Effect.map(() => makeTarget(controls.bind())));
393
- const Wait = Schema.Struct({
394
- selector: ClickRequest.fields.selector,
395
- state: Schema.Literals([
396
- "visible",
397
- "hidden",
398
- "attached",
399
- "detached"
400
- ])
401
- });
402
- const navigate = (url) => action({ url });
403
- const session = {
404
- failure: bindings.failure,
405
- bindingDiagnostics: bindings.diagnostics,
406
- bind: () => makeTarget(controls.bind()),
407
- currentTarget,
408
- target: controls.currentTarget,
409
- observe: (options = {}) => checked(ObservationOptions, options, "observe").pipe(Effect.flatMap((value) => controls.observe({
410
- ...value,
411
- scope: value.scope ?? "document"
412
- }))),
413
- checkpoint: (options = {}) => checked(CheckpointOptions, options, "checkpoint").pipe(Effect.flatMap((value) => controls.checkpoint({
414
- ...value,
415
- picture: value.picture ?? false
416
- })), Effect.flatMap(({ picture, ...sampled }) => decoded(Checkpoint, "checkpoint")({
417
- ...sampled,
418
- ...picture === void 0 ? {} : { picture: {
419
- mediaType: "image/png",
420
- bytes: new Uint8Array(picture)
421
- } }
422
- }))),
423
- controlFacts: (reference) => checked(ObservedElement, reference, "control-facts").pipe(Effect.flatMap(controls.controlFacts)),
424
- revalidateElement: (reference) => checked(ObservedElement, reference, "revalidate").pipe(Effect.flatMap((value) => controls.revalidate(value).pipe(Effect.as(value)))),
425
- clickElement: (reference, admission) => checked(ObservedElement, reference, "click").pipe(Effect.flatMap((value) => controls.bind().click(value, admission?.admit)), Effect.flatMap(navigate)),
426
- fillElement: (reference, value, admission) => checked(ObservedElement, reference, "fill").pipe(Effect.flatMap((element) => checked(FillRequest.fields.value, value, "fill").pipe(Effect.flatMap((text) => controls.bind().fill(element, text, admission?.admit)))), Effect.flatMap(navigate)),
427
- hoverElement: (reference, admission) => checked(ObservedElement, reference, "hover").pipe(Effect.flatMap((value) => controls.bind().hover(value, admission?.admit)), Effect.flatMap((input) => hovered({
428
- ...input,
429
- kind: "hover"
430
- }))),
431
- pressElement: (reference, stroke, admission) => checked(ObservedElement, reference, "press").pipe(Effect.flatMap((element) => checked(KeyStroke, stroke, "press").pipe(Effect.flatMap((value) => controls.bind().press(value.key, value.modifiers ?? [], element, admission?.admit)))), Effect.flatMap((input) => pressed({
432
- ...input,
433
- kind: "press"
434
- }))),
435
- typeElement: (reference, text, admission) => checked(ObservedElement, reference, "type").pipe(Effect.flatMap((element) => checked(TypeRequest.fields.text, text, "type").pipe(Effect.flatMap((value) => controls.bind().type(value, element, admission?.admit)))), Effect.flatMap((input) => typed({
436
- ...input,
437
- kind: "type"
438
- }))),
439
- pages: controls.pages,
440
- frames: controls.frames,
441
- selectPage: (id) => checked(Identifier, id, "select-page").pipe(Effect.flatMap(controls.selectPage), Effect.andThen(currentTarget)),
442
- selectFrame: (id) => checked(Identifier, id, "select-frame").pipe(Effect.flatMap(controls.selectFrame), Effect.andThen(currentTarget)),
443
- createPage: controls.createPage(),
444
- closePage: (id) => checked(Identifier, id, "close-page").pipe(Effect.flatMap(controls.closePage)),
445
- resizeViewport: (viewport) => checked(Viewport, viewport, "resize").pipe(Effect.flatMap(controls.resize)),
446
- waitFor: (request) => checked(Wait, request, "wait").pipe(Effect.flatMap((value) => controls.waitFor(value.selector, value.state))),
447
- clickAndWait: (request) => checked(ClickRequest, request, "click-and-wait").pipe(Effect.flatMap((value) => controls.clickAndWait(value.selector)), Effect.flatMap((url) => action({ url }))),
448
- ready: controls.readiness.pipe(Effect.mapError((error) => InitializationError.make({
449
- operation: "ready",
450
- step: "session",
451
- reason: initialization(error.reason)
452
- })), Effect.flatMap((state) => state._tag === "NotReady" ? Effect.fail(InitializationError.make({
453
- operation: "ready",
454
- step: state.step,
455
- reason: state.reason === "failed" ? "output" : state.reason
456
- })) : Effect.succeed({ _tag: state._tag })))
457
- };
458
- associate(session, controls.capture);
459
- associatePageControl(session, controls.pageControl);
460
- return session;
461
- };
462
- //#endregion
463
- //#region src/internal/session/Attachment.ts
464
- const failure = (reason) => SessionError.make({
465
- operation: "session-attach",
466
- reason,
467
- outcome: "undispatched"
468
- });
469
- /**
470
- * Borrowing an already running session. This scope owns its local connection and nothing
471
- * else: no allocation attempt is made, no release is requested, and no Context-writer
472
- * authority is claimed. Whoever allocated the session keeps all three.
473
- */
474
- const attachRemote = Effect.fnUntraced(function* (options, local = noLocalConnection, limits) {
475
- const client = yield* BrowserbaseClient;
476
- const sessions = yield* BrowserbaseSessions;
477
- const parent = yield* Scope.Scope;
478
- const pendingWaitMillis = options.pendingWaitMillis ?? 0;
479
- if (!Number.isSafeInteger(pendingWaitMillis) || pendingWaitMillis < 0 || pendingWaitMillis > 6e5) return yield* failure("configuration");
480
- const observed = yield* sessions.retrieve(options.reference);
481
- if (isTerminalSessionStatus(observed.status)) return yield* failure("expired");
482
- if (observed.status !== "RUNNING") {
483
- if (pendingWaitMillis === 0) return yield* failure("active");
484
- yield* sessions.waitUntilRunning(observed.reference, { timeoutMillis: pendingWaitMillis });
485
- }
486
- const reference = observed.reference;
487
- return yield* Effect.uninterruptible(Effect.gen(function* () {
488
- const resourceScope = yield* Scope.make();
489
- let closing = false;
490
- let cleanup;
491
- const terminate = yield* Effect.cached(Effect.uninterruptible(Effect.gen(function* () {
492
- closing = true;
493
- yield* (yield* makeCleanup(reference, "borrowed", sessions, local, (result) => {
494
- cleanup = result;
495
- }, limits)).close;
496
- if (cleanup !== void 0) yield* reportCleanup(cleanup);
497
- if (cleanup !== void 0 && options.onCleanup !== void 0) yield* reported(options.onCleanup(cleanup));
498
- })));
499
- const closeScope = yield* Effect.cached(Scope.close(resourceScope, Exit.void));
500
- yield* Scope.addFinalizer(resourceScope, terminate);
501
- yield* Scope.addFinalizer(parent, closeScope);
502
- return {
503
- reference,
504
- status: observed,
505
- connection: (timeoutMillis) => Effect.suspend(() => closing ? Effect.fail(SessionError.make({
506
- operation: "session-connect",
507
- reason: "expired",
508
- outcome: "undispatched"
509
- })) : connectionAddress(client, sessions, reference, timeoutMillis)),
510
- release: closeScope.pipe(Effect.andThen(Effect.suspend(() => cleanup === void 0 ? Effect.die(/* @__PURE__ */ new Error("Borrowed session closed without cleanup evidence")) : Effect.succeed(cleanup)))),
511
- cleanupResult: Effect.sync(() => Option.fromNullishOr(cleanup))
512
- };
513
- }));
514
- });
515
- //#endregion
516
- //#region src/internal/browser/Owner.ts
517
- /** The browser domain's bounded step: one deadline, one declared BrowserError timeout. */
518
- const within = (effect, deadline, operation, onTimeout) => until(effect, deadline, () => {
519
- onTimeout?.();
520
- return BrowserError.make({
521
- operation,
522
- reason: "timeout"
523
- });
524
- });
525
- const makeOwner = Effect.fnUntraced(function* (limits) {
526
- const semaphore = yield* Semaphore.make(1);
527
- const clock = yield* Clock.Clock;
528
- const lifetimeDeadline = yield* deadlineAfter(limits.maxElapsedMillis);
529
- const hooks = /* @__PURE__ */ new Set();
530
- const state = {
531
- phase: "acquiring",
532
- generation: 0,
533
- revision: 0,
534
- selection: 0,
535
- actions: 0
536
- };
537
- let active;
538
- const reservations = /* @__PURE__ */ new Map();
539
- const invalidate = (reason) => {
540
- state.revision++;
541
- for (const hook of hooks) hook(reason);
542
- };
543
- const fence = (phase, reason) => {
544
- if (state.phase === "closed" || state.phase === "closing" && phase !== "closed" && phase !== "closing") return;
545
- state.phase = phase;
546
- state.generation++;
547
- active?.abort();
548
- for (const reservation of reservations.values()) reservation.abort();
549
- reservations.clear();
550
- invalidate(reason);
551
- };
552
- /** Taken under the permit that dispatched the work, so nothing can interleave before it. */
553
- const reserve = (key) => {
554
- const controller = new AbortController();
555
- reservations.set(key, controller);
556
- return {
557
- signal: controller.signal,
558
- settle: (outcome) => {
559
- if (reservations.get(key) !== controller) return;
560
- reservations.delete(key);
561
- if (outcome === "unknown") fence("uncertain", "uncertain");
562
- }
563
- };
564
- };
565
- const guard = (operation, body, options = {}) => semaphore.withPermitsIfAvailable(1)(Effect.gen(function* () {
566
- if (!(options.phases ?? ["open"]).includes(state.phase)) return yield* BrowserError.make({
567
- operation,
568
- reason: state.phase === "paused" ? "busy" : "closed",
569
- outcome: "undispatched"
570
- });
571
- yield* options.preflight ?? Effect.void;
572
- const now = yield* nowMillis;
573
- if (now >= lifetimeDeadline) {
574
- fence("uncertain", "uncertain");
575
- return yield* BrowserError.make({
576
- operation,
577
- reason: "timeout",
578
- outcome: "undispatched"
579
- });
580
- }
581
- if (options.charge !== false) {
582
- if (state.actions >= limits.maxActions) return yield* BrowserError.make({
583
- operation,
584
- reason: "limit",
585
- outcome: "undispatched"
586
- });
587
- state.actions++;
588
- }
589
- const controller = new AbortController();
590
- active = controller;
591
- let dispatched = false;
592
- const generation = state.generation;
593
- const allowed = options.phases ?? ["open"];
594
- const check = () => {
595
- if (controller.signal.aborted || state.generation !== generation || !allowed.includes(state.phase)) throw BrowserError.make({
596
- operation,
597
- reason: "stale",
598
- outcome: dispatched ? "unknown" : "undispatched"
599
- });
600
- };
601
- const ticket = {
602
- signal: controller.signal,
603
- deadline: Math.min(now + limits.actionTimeoutMillis, lifetimeDeadline),
604
- generation,
605
- remainingMillis: () => Math.max(1, Math.min(now + limits.actionTimeoutMillis, lifetimeDeadline) - Number(clock.monotonicTimeNanosUnsafe()) / 1e6),
606
- get dispatched() {
607
- return dispatched;
608
- },
609
- check,
610
- dispatch() {
611
- check();
612
- if (!dispatched && options.mutation) invalidate("observation");
613
- dispatched = true;
614
- }
615
- };
616
- const uncertain = () => {
617
- controller.abort();
618
- if (dispatched && state.phase !== "closing" && state.phase !== "closed") fence("uncertain", "uncertain");
619
- };
620
- return yield* within(body(ticket), ticket.deadline, operation, uncertain).pipe(Effect.tap((_) => options.verifyAfter === false ? Effect.void : Effect.try({
621
- try: check,
622
- catch: () => BrowserError.make({
623
- operation,
624
- reason: "stale",
625
- outcome: dispatched ? "unknown" : "undispatched"
626
- })
627
- })), Effect.catch((error) => {
628
- if (dispatched) uncertain();
629
- if (!Schema.is(BrowserError)(error)) return Effect.fail(error);
630
- return Effect.fail(BrowserError.make({
631
- operation,
632
- reason: error.reason,
633
- outcome: error.outcome ?? (dispatched ? "unknown" : "undispatched"),
634
- ...error.status === void 0 ? {} : { status: error.status },
635
- ...error.retryAfterMillis === void 0 ? {} : { retryAfterMillis: error.retryAfterMillis }
636
- }));
637
- }), Effect.onInterrupt(() => Effect.sync(uncertain)), Effect.ensuring(Effect.sync(() => {
638
- controller.abort();
639
- if (active === controller) active = void 0;
640
- })));
641
- })).pipe(Effect.flatMap(Effect.fromOption(() => BrowserError.make({
642
- operation,
643
- reason: "busy",
644
- outcome: "undispatched"
645
- }))));
646
- /** Lifecycle transitions are performed while holding guard's permit; close alone preempts it. */
647
- return {
648
- state,
649
- lifetimeDeadline,
650
- guard,
651
- reserve,
652
- reserved: (key) => reservations.has(key),
653
- invalidate,
654
- fence,
655
- onInvalidate(hook) {
656
- hooks.add(hook);
657
- return () => {
658
- hooks.delete(hook);
659
- };
660
- }
661
- };
662
- });
663
- /** Without a native answer, whether the step was sent is all the owner knows about it. */
664
- const unsettled = (ticket) => ({
665
- reason: "provider",
666
- outcome: ticket.dispatched ? "unknown" : "undispatched"
667
- });
668
- /**
669
- * The one place a native step becomes a public error: whatever the driver raised, the caller
670
- * sees the operation it asked for.
671
- */
672
- const native = (operation, ticket, body) => Effect.callback((resume) => {
673
- let done = false;
674
- const cleanup = () => {
675
- done = true;
676
- ticket.signal.removeEventListener("abort", abort);
677
- };
678
- const abort = () => {
679
- if (done) return;
680
- cleanup();
681
- resume(Effect.fail(BrowserError.make({
682
- operation,
683
- reason: "stale",
684
- outcome: ticket.dispatched ? "unknown" : "undispatched"
685
- })));
686
- };
687
- ticket.signal.addEventListener("abort", abort, { once: true });
688
- try {
689
- ticket.check();
690
- body().then((value) => {
691
- if (!done) {
692
- cleanup();
693
- resume(Effect.succeed(value));
694
- }
695
- }, (error) => {
696
- if (!done) {
697
- cleanup();
698
- resume(Effect.fail(publicError(error, operation, unsettled(ticket))));
699
- }
700
- });
701
- } catch (error) {
702
- cleanup();
703
- resume(Effect.fail(publicError(error, operation, unsettled(ticket))));
704
- }
705
- return Effect.sync(cleanup);
706
- });
707
- //#endregion
708
- //#region src/internal/browser/Session.ts
709
- /** Allocates the session, and therefore owns releasing it. */
710
- const providerControls = (client, sessions, reference) => ({
711
- liveView: (ttl) => issueLiveView(client, reference, ttl),
712
- verifyReconnect: sessions.retrieve(reference).pipe(Effect.mapError((error) => BrowserError.make({
713
- operation: "reconnect",
714
- reason: error.reason
715
- })), Effect.flatMap((status) => status.status === "RUNNING" ? Effect.void : Effect.fail(BrowserError.make({
716
- operation: "reconnect",
717
- reason: "expired"
718
- }))))
719
- });
720
- const ownedRemote = (options) => (local, lifetimeDeadline) => Effect.gen(function* () {
721
- const client = yield* BrowserbaseClient;
722
- const sessions = yield* BrowserbaseSessions;
723
- const acquired = yield* acquireRemote({
724
- launch: options.launch,
725
- ...options.contextWriter === void 0 ? {} : { contextWriter: options.contextWriter },
726
- allocationDeadline: lifetimeDeadline,
727
- ...options.onCleanup === void 0 ? {} : { onCleanup: options.onCleanup },
728
- ...options.onAllocationUncertain === void 0 ? {} : { onAllocationUncertain: options.onAllocationUncertain }
729
- }, local);
730
- return {
731
- ...acquired,
732
- ...providerControls(client, sessions, acquired.reference)
733
- };
734
- });
735
- /** Borrows a running session: local connection only, and never a release request. */
736
- const borrowedRemote = (options) => (local) => Effect.gen(function* () {
737
- const client = yield* BrowserbaseClient;
738
- const sessions = yield* BrowserbaseSessions;
739
- const acquired = yield* attachRemote(options, local);
740
- return {
741
- ...acquired,
742
- ...providerControls(client, sessions, acquired.reference)
743
- };
744
- });
745
- /**
746
- * The one owned browser. Remote allocation, release and terminal observation belong to the
747
- * canonical control plane; this owner supplies only the local connection's cleanup evidence.
748
- */
749
- const acquireSession = Effect.fnUntraced(function* (limits, options) {
750
- const engine = options.engine ?? bindingImplementation(yield* BrowserbaseBrowserBinding);
751
- if (engine === void 0) return yield* BrowserError.make({
752
- operation: "connect",
753
- reason: "configuration",
754
- outcome: "undispatched"
755
- });
756
- const parentScope = yield* Scope.Scope;
757
- const bindingLifetime = yield* Scope.fork(parentScope, "sequential");
758
- const ended = yield* Deferred.make();
759
- const owner = yield* makeOwner(limits);
760
- const clock = yield* Clock.Clock;
761
- let driver;
762
- let connectPending = false;
763
- let activeConnection;
764
- let handoffToken;
765
- let reconnectTarget;
766
- let activeBindings;
767
- const fenceBindings = () => {
768
- activeBindings?.close();
769
- driver?.fenceInitialization?.();
770
- };
771
- const disposeBindings = Effect.gen(function* () {
772
- const connection = activeBindings;
773
- activeBindings = void 0;
774
- const callbacks = yield* Effect.exit(connection?.dispose ?? Effect.void);
775
- const registrations = yield* Effect.tryPromise({
776
- try: () => driver?.disposeInitialization?.() ?? Promise.resolve(),
777
- catch: () => BrowserError.make({
778
- operation: "close",
779
- reason: "provider"
780
- })
781
- }).pipe(Effect.exit);
782
- if (Exit.isFailure(callbacks)) return yield* Effect.failCause(callbacks.cause);
783
- if (Exit.isFailure(registrations)) return yield* Effect.failCause(registrations.cause);
784
- });
785
- const capture = {
786
- owner,
787
- target: () => {
788
- if (driver === void 0) throw BrowserError.make({
789
- operation: "target",
790
- reason: "closed"
791
- });
792
- return Target.make({
793
- generation: owner.state.generation,
794
- ...driver.selected()
795
- });
796
- },
797
- resolve: (ticket, requested) => native("capture-start", ticket, async () => {
798
- if (driver === void 0) throw BrowserError.make({
799
- operation: "capture",
800
- reason: "closed"
801
- });
802
- const binding = await driver.capture(requested);
803
- return {
804
- key: binding.targetId,
805
- target: Target.make({
806
- generation: owner.state.generation,
807
- pageId: binding.pageId,
808
- frameId: binding.frameId
809
- }),
810
- source: binding.source
811
- };
812
- }),
813
- captureLeases: /* @__PURE__ */ new Map(),
814
- captureReservedBytes: 0
815
- };
816
- owner.onInvalidate((reason) => {
817
- driver?.invalidateObservation();
818
- if ([
819
- "disconnected",
820
- "uncertain",
821
- "closed"
822
- ].includes(reason)) fenceBindings();
823
- if ([
824
- "paused",
825
- "disconnected",
826
- "uncertain",
827
- "closed"
828
- ].includes(reason)) for (const lease of capture.captureLeases.values()) lease.invalidate(reason);
829
- });
830
- /**
831
- * The local half of cleanup. Each step is an independent fact for the canonical
832
- * coordinator, which owns the release request and the terminal status observation.
833
- */
834
- const local = {
835
- fence: Effect.sync(() => {
836
- owner.fence("closing", "closed");
837
- handoffToken = void 0;
838
- activeConnection = void 0;
839
- Deferred.doneUnsafe(ended, Effect.void);
840
- }),
841
- capture: Effect.suspend(() => Effect.forEach([...capture.captureLeases.values()], (lease) => lease.stop, { discard: true })),
842
- initialization: disposeBindings,
843
- disconnect: Effect.suspend(() => {
844
- const acquired = driver;
845
- driver = void 0;
846
- if (acquired === void 0) return Effect.succeed(connectPending ? "pending" : "not-connected");
847
- return Effect.tryPromise({
848
- try: () => acquired.disconnect(),
849
- catch: () => BrowserError.make({
850
- operation: "disconnect",
851
- reason: "provider"
852
- })
853
- }).pipe(Effect.as("closed"));
854
- }).pipe(Effect.ensuring(Effect.sync(() => {
855
- owner.state.phase = "closed";
856
- })))
857
- };
858
- const acquired = yield* options.remote(local, owner.lifetimeDeadline);
859
- const ref = acquired.reference;
860
- const release = acquired.release;
861
- const cleanupResult = acquired.cleanupResult;
862
- const closeScope = acquired.release.pipe(Effect.ensuring(Deferred.succeed(ended, void 0)), Effect.asVoid);
863
- const connectionEvents = (connectionLease) => ({
864
- invalidate: (reason) => {
865
- if (activeConnection === connectionLease) owner.invalidate(reason);
866
- },
867
- disconnected: () => {
868
- if (activeConnection !== connectionLease) return;
869
- if (owner.state.phase !== "closing" && owner.state.phase !== "closed" && owner.state.phase !== "detached") owner.fence("uncertain", "disconnected");
870
- },
871
- pause: () => {
872
- if (activeConnection !== connectionLease) return;
873
- if (owner.state.phase === "open") owner.fence("paused", "paused");
874
- else if (owner.state.phase === "acquiring") owner.fence("uncertain", "uncertain");
875
- },
876
- fault: () => {
877
- if (activeConnection !== connectionLease) return;
878
- if (owner.state.phase !== "closing" && owner.state.phase !== "closed") owner.fence("uncertain", "uncertain");
879
- }
880
- });
881
- const connectNative = (url, nativeOptions) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
882
- const connectionLease = {};
883
- activeConnection = connectionLease;
884
- connectPending = true;
885
- const events = connectionEvents(connectionLease);
886
- const bindings = options.connectBindings === void 0 ? void 0 : yield* options.connectBindings(events.fault, () => activeConnection === connectionLease && (owner.state.phase === "acquiring" || owner.state.phase === "open"), () => activeConnection === connectionLease && [
887
- "acquiring",
888
- "open",
889
- "paused"
890
- ].includes(owner.state.phase)).pipe(Scope.provide(bindingLifetime));
891
- activeBindings = bindings;
892
- const retired = () => activeConnection !== connectionLease || [
893
- "closing",
894
- "closed",
895
- "uncertain"
896
- ].includes(owner.state.phase);
897
- if (retired()) {
898
- bindings?.close();
899
- yield* bindings?.dispose ?? Effect.void;
900
- return yield* BrowserError.make({
901
- operation: "connect",
902
- reason: "closed"
903
- });
904
- }
905
- const acquired = yield* restore(engine.connect({
906
- connection: Redacted.value(url),
907
- options: {
908
- ...nativeOptions,
909
- ...bindings === void 0 ? {} : {
910
- bindings: bindings.bindings,
911
- onBindingFault: bindings.reportFailure
912
- }
913
- },
914
- events,
915
- onAbandoned: () => bindings?.close(),
916
- onSettled: () => {
917
- if (activeConnection === connectionLease) connectPending = false;
918
- }
919
- }));
920
- driver = acquired;
921
- if (activeConnection !== connectionLease || owner.state.phase === "closed" || owner.state.phase === "closing" || owner.state.phase === "uncertain") {
922
- yield* Effect.tryPromise({
923
- try: () => acquired.disconnect(),
924
- catch: () => BrowserError.make({
925
- operation: "connect",
926
- reason: "provider"
927
- })
928
- }).pipe(Effect.ignore);
929
- driver = void 0;
930
- return yield* BrowserError.make({
931
- operation: "connect",
932
- reason: "closed"
933
- });
934
- }
935
- return acquired;
936
- }));
937
- const getDriver = () => {
938
- if (driver === void 0) throw BrowserError.make({
939
- operation: "target",
940
- reason: "closed",
941
- outcome: "undispatched"
942
- });
943
- return driver;
944
- };
945
- /**
946
- * Work that depends on an initialized document waits for the current document's readiness.
947
- * Navigation, selection and page management deliberately do not: initialization must never
948
- * deadlock the navigation that produces the document it is waiting for.
949
- */
950
- const dependent = [
951
- "read-text",
952
- "checkpoint",
953
- "control-facts",
954
- "revalidate",
955
- "click",
956
- "fill",
957
- "scroll",
958
- "pointer-move",
959
- "hover",
960
- "wheel",
961
- "press",
962
- "type",
963
- "screenshot",
964
- "observe",
965
- "wait",
966
- "click-and-wait",
967
- "download-action",
968
- "select-files",
969
- "file-chooser"
970
- ];
971
- const requireReady = async (operation, ticket) => {
972
- if (!dependent.includes(operation)) return;
973
- const state = await getDriver().documentReadiness(ticket);
974
- if (state._tag === "Ready" || state._tag === "NotApplicable") return;
975
- throw BrowserError.make({
976
- operation,
977
- reason: state._tag === "RequiresNavigation" || state.reason === "stale" ? "stale" : state.reason === "timeout" ? "timeout" : "failed",
978
- outcome: "undispatched"
979
- });
980
- };
981
- /**
982
- * `dependent` is false for lifecycle observations, which report what is actually on the
983
- * reattached page. Readiness governs work that depends on initialization, not the reading
984
- * that tells a caller whether this document was initialized at all.
985
- */
986
- const observeInside = (ticket, maximumBytes = Math.min(options.maxReturnedBytes, 16384), controls = 32, dependent = true, scope = "document") => Effect.suspend(() => {
987
- const revision = owner.state.revision;
988
- return native("observe", ticket, async () => {
989
- await getDriver().pageControl?.checkSelected(ticket);
990
- if (dependent) await requireReady("observe", ticket);
991
- return getDriver().observe(scope, maximumBytes, controls, ticket);
992
- }).pipe(Effect.flatMap((raw) => Effect.gen(function* () {
993
- if (owner.state.revision !== revision) return yield* BrowserError.make({
994
- operation: "observe",
995
- reason: "stale"
996
- });
997
- const result = yield* Effect.try({
998
- try: () => Observation.make({
999
- ...raw,
1000
- target: capture.target(),
1001
- revision
1002
- }),
1003
- catch: (error) => publicError(error, "observe", { reason: "malformed" })
1004
- });
1005
- const encoded = yield* Schema.encodeEffect(Schema.fromJsonString(Observation))(result).pipe(Effect.mapError(() => BrowserError.make({
1006
- operation: "observe",
1007
- reason: "malformed"
1008
- })));
1009
- if (new TextEncoder().encode(encoded).length > options.maxReturnedBytes) return yield* BrowserError.make({
1010
- operation: "observe",
1011
- reason: "limit"
1012
- });
1013
- return result;
1014
- })));
1015
- });
1016
- /** A requested text bound may narrow the policy's bound, never widen it. */
1017
- const textBudget = (operation, requested) => {
1018
- const bytes = requested ?? Math.min(options.maxReturnedBytes, 16384);
1019
- return bytes > options.maxReturnedBytes ? Effect.fail(BrowserError.make({
1020
- operation,
1021
- reason: "configuration",
1022
- outcome: "undispatched"
1023
- })) : Effect.succeed(bytes);
1024
- };
1025
- const readSelected = owner.guard("target", () => Effect.try({
1026
- try: capture.target,
1027
- catch: () => BrowserError.make({
1028
- operation: "target",
1029
- reason: "closed",
1030
- outcome: "undispatched"
1031
- })
1032
- }), { charge: false });
1033
- /**
1034
- * While a navigation is in flight on the selected page, nothing else may change that page.
1035
- * Reads, checkpoints, holds and every other page proceed.
1036
- */
1037
- const unreserved = (operation) => Effect.suspend(() => {
1038
- let pageId;
1039
- try {
1040
- pageId = driver?.selected().pageId;
1041
- } catch {}
1042
- return pageId !== void 0 && owner.reserved(pageId) ? Effect.fail(BrowserError.make({
1043
- operation,
1044
- reason: "busy",
1045
- outcome: "undispatched"
1046
- })) : Effect.void;
1047
- });
1048
- const nativeOperation = (operation, action, options = {}) => owner.guard(operation, (ticket) => native(operation, ticket, async () => {
1049
- if ([
1050
- "resize",
1051
- "wait",
1052
- "click-and-wait",
1053
- "download-action",
1054
- "select-files",
1055
- "file-chooser",
1056
- "checkpoint",
1057
- "control-facts",
1058
- "revalidate"
1059
- ].includes(operation)) await getDriver().pageControl?.checkSelected(ticket);
1060
- await requireReady(operation, ticket);
1061
- return action(getDriver(), ticket);
1062
- }), {
1063
- ...options,
1064
- ...options.mutation === true && options.anyPage !== true ? { preflight: unreserved(operation) } : {}
1065
- });
1066
- /** A bound handle captures the page selection and connection generation, never the current DOM. */
1067
- const bind = () => {
1068
- const generation = owner.state.generation, selection = owner.state.selection;
1069
- const check = () => {
1070
- if (owner.state.generation !== generation || owner.state.selection !== selection) return Effect.fail(BrowserError.make({
1071
- operation: "handle",
1072
- reason: "stale",
1073
- outcome: "undispatched"
1074
- }));
1075
- return Effect.void;
1076
- };
1077
- const run = (operation, action, mutation = false) => owner.guard(operation, (ticket) => native(operation, ticket, async () => {
1078
- await getDriver().pageControl?.checkSelected(ticket);
1079
- await requireReady(operation, ticket);
1080
- return action(getDriver(), ticket);
1081
- }), {
1082
- mutation,
1083
- preflight: mutation ? Effect.suspend(check).pipe(Effect.andThen(unreserved(operation))) : Effect.suspend(check)
1084
- });
1085
- /**
1086
- * Native input, stamped on the host monotonic clock that stamps captured frames, around
1087
- * the native command alone: admission and readiness are over before the clock is read.
1088
- */
1089
- const input = (operation, action) => run(operation, async (driver, ticket) => {
1090
- const target = capture.target();
1091
- const startedMonotonicNanos = clock.monotonicTimeNanosUnsafe();
1092
- return {
1093
- ...await action(driver, ticket),
1094
- target,
1095
- startedMonotonicNanos,
1096
- completedMonotonicNanos: clock.monotonicTimeNanosUnsafe()
1097
- };
1098
- }, true);
1099
- /**
1100
- * Dispatches under a short permit and leaves the browser loading outside it. The reservation
1101
- * is taken under that same permit, so no other mutation can reach the page in between, and
1102
- * it is released only on a known outcome: an unsettled operation whose scope closes, or a
1103
- * navigation that fails after dispatch, fences the owner exactly as an interrupted mutation
1104
- * always has. Interrupting a waiter on `completed` stops nothing; `stop` is the one way to.
1105
- */
1106
- const startNavigation = Effect.fnUntraced(function* (url, timeoutMillis = limits.actionTimeoutMillis) {
1107
- const begun = yield* run("navigate", async (driver, ticket) => {
1108
- const target = capture.target();
1109
- const navigation = await driver.beginNavigation(url, timeoutMillis, ticket);
1110
- return {
1111
- target,
1112
- navigation,
1113
- reservation: owner.reserve(navigation.pageId)
1114
- };
1115
- }, true);
1116
- const { navigation, reservation } = begun;
1117
- const outcome = yield* Deferred.make();
1118
- const failed = (reason) => Effect.fail(BrowserError.make({
1119
- operation: "navigate",
1120
- reason,
1121
- outcome: "unknown"
1122
- }));
1123
- /**
1124
- * A navigation has exactly one outcome, and whoever decides it first settles the
1125
- * reservation. It is released before anyone waiting is told, so the operation a waiter
1126
- * runs next is admitted rather than finding its own page still reserved.
1127
- */
1128
- const decide = (result, known) => {
1129
- if (Deferred.isDoneUnsafe(outcome)) return;
1130
- reservation.settle(known ? "known" : "unknown");
1131
- Deferred.doneUnsafe(outcome, result);
1132
- };
1133
- navigation.settled.then((url) => decide(Effect.succeed(url), true), (error) => decide(Effect.fail(publicError(error, "navigate", {
1134
- reason: "provider",
1135
- outcome: "unknown"
1136
- })), false));
1137
- reservation.signal.addEventListener("abort", () => decide(failed("stale"), true), { once: true });
1138
- yield* Effect.addFinalizer(() => Effect.sync(() => decide(failed("stale"), false)));
1139
- return {
1140
- target: begun.target,
1141
- completed: Deferred.await(outcome),
1142
- /**
1143
- * The browser's acknowledgement is the known outcome. Playwright's own promise is not
1144
- * waited for: an aborted parse fires no DOMContentLoaded, so it only ever times out.
1145
- */
1146
- stop: Effect.tryPromise({
1147
- try: () => navigation.stop(),
1148
- catch: (error) => publicError(error, "navigate-stop", {
1149
- reason: "provider",
1150
- outcome: "unknown"
1151
- })
1152
- }).pipe(Effect.timeoutOrElse({
1153
- duration: 3e3,
1154
- orElse: () => Effect.fail(BrowserError.make({
1155
- operation: "navigate-stop",
1156
- reason: "timeout",
1157
- outcome: "unknown"
1158
- }))
1159
- }), Effect.tap(() => Effect.sync(() => decide(failed("interrupted"), true))))
1160
- };
1161
- });
1162
- return {
1163
- startNavigation,
1164
- navigate: (url) => Effect.scoped(startNavigation(url).pipe(Effect.flatMap((operation) => operation.completed))),
1165
- readText: (selector) => run("read-text", (driver, ticket) => driver.readText(selector, options.maxReturnedBytes, ticket)),
1166
- click: (target, policy) => run("click", (driver, ticket) => driver.click(target, ticket, policy), true),
1167
- fill: (target, value, policy) => run("fill", (driver, ticket) => driver.fill(target, value, ticket, policy), true),
1168
- scroll: (x, y) => run("scroll", (driver, ticket) => driver.scroll(x, y, ticket), true),
1169
- pointerMove: (to) => input("pointer-move", (driver, ticket) => driver.pointerMove(to, ticket)),
1170
- hover: (target, policy) => input("hover", (driver, ticket) => driver.hover(target, ticket, policy)),
1171
- wheel: (deltaX, deltaY, at) => input("wheel", (driver, ticket) => driver.wheel(deltaX, deltaY, at, ticket)),
1172
- press: (key, modifiers, into, policy) => input("press", (driver, ticket) => driver.press(key, modifiers, into, ticket, policy)),
1173
- type: (text, into, policy) => input("type", (driver, ticket) => driver.type(text, into, ticket, policy)),
1174
- screenshot: (full) => run("screenshot", (driver, ticket) => driver.screenshot(full, options.maxReturnedBytes, ticket))
1175
- };
1176
- };
1177
- /**
1178
- * The remaining execution lifetime bounds waiting for a RUNNING session and its endpoint,
1179
- * within the resource service's own maximum wait. A long business budget does not become
1180
- * an invalid provider-wait configuration.
1181
- */
1182
- const remainingMillis = () => Math.min(6e5, Math.max(1, Math.ceil(owner.lifetimeDeadline - Number(clock.monotonicTimeNanosUnsafe()) / 1e6)));
1183
- const connectionUrl = (operation) => Effect.suspend(() => acquired.connection(remainingMillis())).pipe(Effect.mapError((error) => BrowserError.make({
1184
- operation,
1185
- reason: error.reason
1186
- })));
1187
- const connected = yield* Effect.cached(owner.guard("connect", (ticket) => connectionUrl("connect").pipe(Effect.flatMap((url) => connectNative(url, options.driver)), Effect.andThen(native("connect", ticket, async () => {
1188
- getDriver().selected();
1189
- })), Effect.tap(() => Effect.sync(() => {
1190
- owner.state.phase = "open";
1191
- }))), {
1192
- phases: ["acquiring"],
1193
- charge: false,
1194
- verifyAfter: false
1195
- }).pipe(Effect.onError(() => closeScope), Effect.onInterrupt(() => closeScope)));
1196
- const execution = () => {
1197
- const port = getDriver().pageControl;
1198
- if (port === void 0) throw BrowserError.make({
1199
- operation: "page-control",
1200
- reason: "unsupported",
1201
- outcome: "undispatched"
1202
- });
1203
- return port;
1204
- };
1205
- const controls = {
1206
- pageControl: {
1207
- state: (page) => nativeOperation("page-state", (_driver, ticket) => execution().state(page, ticket), { charge: false }),
1208
- suspend: (page) => nativeOperation("page-suspend", (_driver, ticket) => execution().suspend(page, ticket), { charge: false }),
1209
- resume: (receipt) => nativeOperation("page-resume", (_driver, ticket) => execution().resume(receipt, ticket), { charge: false })
1210
- },
1211
- reference: ref,
1212
- capture,
1213
- bind,
1214
- currentTarget: readSelected,
1215
- cleanupResult,
1216
- close: release,
1217
- observe: (reading = { scope: "document" }) => textBudget("observe", reading.maxTextBytes).pipe(Effect.flatMap((bytes) => owner.guard("observe", (ticket) => observeInside(ticket, bytes, reading.maxControls ?? 32, true, reading.scope)))),
1218
- /**
1219
- * Passive: not a mutation, so the action observation and the selection are left exactly as
1220
- * they were. A held page is refused rather than woken to be read.
1221
- */
1222
- checkpoint: (reading) => textBudget("checkpoint", reading.maxTextBytes).pipe(Effect.flatMap((bytes) => nativeOperation("checkpoint", async (driver, ticket) => {
1223
- const target = capture.target();
1224
- const revision = owner.state.revision;
1225
- const startedMonotonicNanos = clock.monotonicTimeNanosUnsafe();
1226
- return {
1227
- ...await driver.checkpoint(bytes, reading.maxControls ?? 32, reading.picture ? options.maxReturnedBytes : void 0, ticket),
1228
- target,
1229
- revision,
1230
- startedMonotonicNanos,
1231
- completedMonotonicNanos: clock.monotonicTimeNanosUnsafe()
1232
- };
1233
- }))),
1234
- controlFacts: (reference) => nativeOperation("control-facts", (driver, ticket) => driver.controlFacts(reference, ticket)),
1235
- /** Not charged: it sends no input and reads one node the caller was already given. */
1236
- revalidate: (reference) => nativeOperation("revalidate", (driver, ticket) => driver.revalidate(reference, ticket), { charge: false }),
1237
- readiness: owner.guard("ready", (ticket) => native("ready", ticket, () => getDriver().documentReadiness(ticket)), { charge: false }),
1238
- pages: nativeOperation("list-pages", (driver, ticket) => driver.listPages(ticket), { charge: false }),
1239
- frames: nativeOperation("list-frames", (driver, ticket) => driver.listFrames(ticket), { charge: false }),
1240
- selectPage: (id) => nativeOperation("select-page", async (driver, ticket) => {
1241
- await driver.selectPage(id, ticket);
1242
- owner.state.selection++;
1243
- }, { charge: false }),
1244
- selectFrame: (id) => nativeOperation("select-frame", async (driver, ticket) => {
1245
- await driver.selectFrame(id, ticket);
1246
- owner.state.selection++;
1247
- }, { charge: false }),
1248
- createPage: () => nativeOperation("new-page", (driver, ticket) => driver.newPage(ticket), {
1249
- mutation: true,
1250
- charge: false,
1251
- anyPage: true
1252
- }),
1253
- closePage: (id) => nativeOperation("close-page", (driver, ticket) => driver.closePage(id, ticket), {
1254
- mutation: true,
1255
- charge: false,
1256
- anyPage: true
1257
- }),
1258
- resize: (viewport) => nativeOperation("resize", (driver, ticket) => driver.resize(viewport, ticket), {
1259
- mutation: true,
1260
- charge: false
1261
- }),
1262
- waitFor: (selector, state) => nativeOperation("wait", (driver, ticket) => driver.waitFor(selector, state, ticket)),
1263
- clickAndWait: (target) => nativeOperation("click-and-wait", (driver, ticket) => driver.clickAndWait(target, ticket), { mutation: true }),
1264
- clickForDownload: (target) => nativeOperation("download-action", (driver, ticket) => driver.clickForDownload(target, ticket), { mutation: true }),
1265
- selectFiles: (target, files) => nativeOperation("select-files", (driver, ticket) => driver.selectFiles(target, files, ticket), { mutation: true }),
1266
- clickForFileSelection: (target, files) => nativeOperation("file-chooser", (driver, ticket) => driver.clickForFileSelection(target, files, ticket), { mutation: true }),
1267
- liveView: (ttl) => owner.guard("live-view", () => acquired.liveView === void 0 ? Effect.fail(BrowserError.make({
1268
- operation: "live-view",
1269
- reason: "unsupported",
1270
- outcome: "undispatched"
1271
- })) : acquired.liveView(ttl), {
1272
- charge: false,
1273
- phases: ["open", "paused"]
1274
- }),
1275
- beginHandoff: (ttl) => owner.guard("handoff", () => Effect.gen(function* () {
1276
- if (options.driver.pageControl || acquired.liveView === void 0) return yield* BrowserError.make({
1277
- operation: "handoff",
1278
- reason: "unsupported",
1279
- outcome: "undispatched"
1280
- });
1281
- if (owner.state.phase === "open") owner.fence("paused", "paused");
1282
- handoffToken ??= globalThis.crypto.randomUUID();
1283
- const view = yield* acquired.liveView(ttl);
1284
- return {
1285
- token: Redacted.make(handoffToken),
1286
- view
1287
- };
1288
- }), {
1289
- charge: false,
1290
- phases: ["open", "paused"],
1291
- verifyAfter: false
1292
- }),
1293
- resume: (token, operatorReleasedControl) => owner.guard("resume", (ticket) => Effect.gen(function* () {
1294
- if (!operatorReleasedControl || handoffToken === void 0 || Redacted.value(token) !== handoffToken) return yield* BrowserError.make({
1295
- operation: "resume",
1296
- reason: "authorization",
1297
- outcome: "undispatched"
1298
- });
1299
- yield* native("resume", ticket, () => getDriver().dismissDialogs(ticket));
1300
- const observation = yield* observeInside(ticket, void 0, void 0, false);
1301
- owner.state.phase = "open";
1302
- handoffToken = void 0;
1303
- return observation;
1304
- }), {
1305
- charge: false,
1306
- phases: ["paused"],
1307
- verifyAfter: false
1308
- }),
1309
- detach: owner.guard("detach", (ticket) => Effect.gen(function* () {
1310
- if (!options.keepAlive) return yield* BrowserError.make({
1311
- operation: "detach",
1312
- reason: "unsupported",
1313
- outcome: "undispatched"
1314
- });
1315
- reconnectTarget = yield* native("detach", ticket, () => getDriver().selectedTargetId());
1316
- const attached = getDriver();
1317
- activeConnection = void 0;
1318
- owner.fence("detached", "disconnected");
1319
- const initialization = yield* Effect.exit(disposeBindings);
1320
- const disconnected = yield* Effect.tryPromise({
1321
- try: () => attached.disconnect(),
1322
- catch: () => BrowserError.make({
1323
- operation: "detach",
1324
- reason: "disconnected"
1325
- })
1326
- }).pipe(Effect.exit);
1327
- driver = void 0;
1328
- if (Exit.isFailure(initialization)) return yield* Effect.failCause(initialization.cause);
1329
- if (Exit.isFailure(disconnected)) return yield* Effect.failCause(disconnected.cause);
1330
- return {
1331
- reference: ref,
1332
- targetId: reconnectTarget
1333
- };
1334
- }), {
1335
- charge: false,
1336
- verifyAfter: false
1337
- }).pipe(Effect.onError(() => Effect.sync(() => owner.fence("uncertain", "uncertain")))),
1338
- reconnect: (operatorReleasedControl) => owner.guard("reconnect", (ticket) => Effect.gen(function* () {
1339
- if (!options.keepAlive || reconnectTarget === void 0 || !operatorReleasedControl || acquired.verifyReconnect === void 0) return yield* BrowserError.make({
1340
- operation: "reconnect",
1341
- reason: "unsupported",
1342
- outcome: "undispatched"
1343
- });
1344
- yield* acquired.verifyReconnect;
1345
- owner.state.phase = "acquiring";
1346
- const endpoint = yield* connectionUrl("reconnect");
1347
- yield* connectNative(endpoint, {
1348
- ...options.driver,
1349
- initialTargetId: reconnectTarget,
1350
- newPage: false,
1351
- preserveViewport: true
1352
- });
1353
- const observation = yield* observeInside(ticket, void 0, void 0, false);
1354
- owner.state.phase = "open";
1355
- return observation;
1356
- }), {
1357
- charge: false,
1358
- phases: ["detached", "acquiring"],
1359
- verifyAfter: false
1360
- }).pipe(Effect.onError(() => closeScope), Effect.onInterrupt(() => closeScope))
1361
- };
1362
- const remaining = Math.max(0, owner.lifetimeDeadline - Number(clock.monotonicTimeNanosUnsafe()) / 1e6);
1363
- yield* Effect.raceFirst(Effect.sleep(remaining).pipe(Effect.as(true)), Deferred.await(ended).pipe(Effect.as(false))).pipe(Effect.flatMap((expired) => expired ? closeScope : Effect.void), Effect.forkIn(parentScope));
1364
- return {
1365
- reference: ref,
1366
- /** The caller keeps its own lease type, including whether an allocation attempt exists. */
1367
- lease: acquired,
1368
- close: controls.close,
1369
- connect: Effect.suspend(() => owner.state.phase === "closed" || owner.state.phase === "closing" ? Effect.fail(BrowserError.make({
1370
- operation: "connect",
1371
- reason: "closed",
1372
- outcome: "undispatched"
1373
- })) : connected.pipe(Effect.as(controls)))
1374
- };
1375
- });
1376
- //#endregion
1377
- export { decoded as a, preparePlan as c, checked as i, compileBootstrap as l, borrowedRemote as n, makeSession as o, ownedRemote as r, makeBindings as s, acquireSession as t };
1378
-
1379
- //# sourceMappingURL=Session-D4nT9hOO.mjs.map