effect-browserbase 0.2.0-beta.9 → 0.3.0-beta.5
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 +250 -317
- package/dist/Browserbase-FDltiz36.mjs +542 -0
- package/dist/Browserbase-FDltiz36.mjs.map +1 -0
- package/dist/Browserbase.d.mts +105 -0
- package/dist/Browserbase.mjs +3 -0
- package/dist/BrowserbaseClient-CtAQ2rg4.d.mts +195 -0
- package/dist/BrowserbaseClient.d.mts +2 -0
- package/dist/BrowserbaseClient.mjs +190 -0
- package/dist/BrowserbaseClient.mjs.map +1 -0
- package/dist/BrowserbaseError-D1EOrLNc.d.mts +79 -0
- package/dist/BrowserbaseError.d.mts +2 -0
- package/dist/BrowserbaseError.mjs +101 -0
- package/dist/BrowserbaseError.mjs.map +1 -0
- package/dist/ContextLease.d.mts +42 -0
- package/dist/ContextLease.mjs +80 -0
- package/dist/ContextLease.mjs.map +1 -0
- package/dist/index.d.mts +5 -27
- package/dist/index.mjs +5 -26
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/testing/index.d.mts +134 -0
- package/dist/testing/index.mjs +382 -0
- package/dist/testing/index.mjs.map +1 -0
- package/package.json +51 -121
- package/src/Browserbase.ts +698 -0
- package/src/BrowserbaseClient.ts +559 -0
- package/src/BrowserbaseError.ts +108 -0
- package/src/ContextLease.ts +118 -0
- package/src/index.ts +7 -0
- package/src/internal/captureConnection.ts +214 -0
- package/src/testing/BrowserbaseContract.ts +170 -0
- package/src/testing/TestBrowserbase.ts +445 -0
- package/src/testing/index.ts +7 -0
- package/dist/Account.d.mts +0 -32
- package/dist/Account.mjs +0 -36
- package/dist/Account.mjs.map +0 -1
- package/dist/Acquisition-BOABu_qJ.mjs +0 -244
- package/dist/Acquisition-BOABu_qJ.mjs.map +0 -1
- package/dist/Agents-CPaTOH_g.d.mts +0 -156
- package/dist/Agents.d.mts +0 -3
- package/dist/Agents.mjs +0 -292
- package/dist/Agents.mjs.map +0 -1
- package/dist/Allocation.d.mts +0 -97
- package/dist/Allocation.mjs +0 -27
- package/dist/Allocation.mjs.map +0 -1
- package/dist/Authorization-Ll5f_XNM.mjs +0 -12
- package/dist/Authorization-Ll5f_XNM.mjs.map +0 -1
- package/dist/Browser-ByrWJ0ss.mjs +0 -274
- package/dist/Browser-ByrWJ0ss.mjs.map +0 -1
- package/dist/Browser.d.mts +0 -83
- package/dist/Browser.mjs +0 -3
- package/dist/BrowserBinding.d.mts +0 -13
- package/dist/BrowserBinding.mjs +0 -31
- package/dist/BrowserBinding.mjs.map +0 -1
- package/dist/Certificates-D4NQmyeA.d.mts +0 -34
- package/dist/Certificates.d.mts +0 -3
- package/dist/Certificates.mjs +0 -85
- package/dist/Certificates.mjs.map +0 -1
- package/dist/Cleanup-DMTDBir8.d.mts +0 -23
- package/dist/Cleanup.d.mts +0 -3
- package/dist/Cleanup.mjs +0 -43
- package/dist/Cleanup.mjs.map +0 -1
- package/dist/Client-BEC4Q8ZS.mjs +0 -564
- package/dist/Client-BEC4Q8ZS.mjs.map +0 -1
- package/dist/Client-B_f6bBLT.d.mts +0 -52
- package/dist/Client.d.mts +0 -3
- package/dist/Client.mjs +0 -2
- package/dist/Connection-sxyxtwlk.mjs +0 -56
- package/dist/Connection-sxyxtwlk.mjs.map +0 -1
- package/dist/ContextCoordination.d.mts +0 -29
- package/dist/ContextCoordination.mjs +0 -94
- package/dist/ContextCoordination.mjs.map +0 -1
- package/dist/ContextWriter-CiEma24z.mjs +0 -204
- package/dist/ContextWriter-CiEma24z.mjs.map +0 -1
- package/dist/Contexts-DqdL7cyX.d.mts +0 -31
- package/dist/Contexts.d.mts +0 -3
- package/dist/Contexts.mjs +0 -97
- package/dist/Contexts.mjs.map +0 -1
- package/dist/Deadline-CrViaogr.mjs +0 -13
- package/dist/Deadline-CrViaogr.mjs.map +0 -1
- package/dist/Downloads-CX52GOFJ.d.mts +0 -43
- package/dist/Downloads.d.mts +0 -3
- package/dist/Downloads.mjs +0 -137
- package/dist/Downloads.mjs.map +0 -1
- package/dist/Errors.d.mts +0 -100
- package/dist/Errors.mjs +0 -194
- package/dist/Errors.mjs.map +0 -1
- package/dist/Extensions-BTUn9zhK.mjs +0 -231
- package/dist/Extensions-BTUn9zhK.mjs.map +0 -1
- package/dist/Extensions-DBXuVM1f.d.mts +0 -43
- package/dist/Extensions.d.mts +0 -3
- package/dist/Extensions.mjs +0 -2
- package/dist/Functions-CsKMMUuZ.d.mts +0 -226
- package/dist/Functions.d.mts +0 -3
- package/dist/Functions.mjs +0 -318
- package/dist/Functions.mjs.map +0 -1
- package/dist/Issued-DejqoMYL.mjs +0 -15
- package/dist/Issued-DejqoMYL.mjs.map +0 -1
- package/dist/Launch-cAdE2rrZ.mjs +0 -115
- package/dist/Launch-cAdE2rrZ.mjs.map +0 -1
- package/dist/Launch.d.mts +0 -127
- package/dist/Launch.mjs +0 -97
- package/dist/Launch.mjs.map +0 -1
- package/dist/PageFetch-CjAM_0DS.d.mts +0 -40
- package/dist/PageFetch.d.mts +0 -3
- package/dist/PageFetch.mjs +0 -59
- package/dist/PageFetch.mjs.map +0 -1
- package/dist/Projects-tm5bTDH0.d.mts +0 -38
- package/dist/Projects.d.mts +0 -3
- package/dist/Projects.mjs +0 -65
- package/dist/Projects.mjs.map +0 -1
- package/dist/Recordings-B9k5aODB.d.mts +0 -27
- package/dist/Recordings.d.mts +0 -3
- package/dist/Recordings.mjs +0 -144
- package/dist/Recordings.mjs.map +0 -1
- package/dist/References-DOH3Wu44.d.mts +0 -34
- package/dist/References.d.mts +0 -3
- package/dist/References.mjs +0 -33
- package/dist/References.mjs.map +0 -1
- package/dist/Replays-mV93WGtx.d.mts +0 -27
- package/dist/Replays.d.mts +0 -3
- package/dist/Replays.mjs +0 -107
- package/dist/Replays.mjs.map +0 -1
- package/dist/Resource-J7Kkk5mu.mjs +0 -54
- package/dist/Resource-J7Kkk5mu.mjs.map +0 -1
- package/dist/Search-B64e7g-h.d.mts +0 -37
- package/dist/Search.d.mts +0 -3
- package/dist/Search.mjs +0 -46
- package/dist/Search.mjs.map +0 -1
- package/dist/SessionData-5xYZDshJ.d.mts +0 -38
- package/dist/SessionData.d.mts +0 -3
- package/dist/SessionData.mjs +0 -48
- package/dist/SessionData.mjs.map +0 -1
- package/dist/Sessions-D2H5m6rF.mjs +0 -260
- package/dist/Sessions-D2H5m6rF.mjs.map +0 -1
- package/dist/Sessions-oJEUmZth.d.mts +0 -69
- package/dist/Sessions.d.mts +0 -4
- package/dist/Sessions.mjs +0 -3
- package/dist/Testing.d.mts +0 -119
- package/dist/Testing.mjs +0 -206
- package/dist/Testing.mjs.map +0 -1
- package/dist/TransferPolicy-DQ06i7SZ.mjs +0 -25
- package/dist/TransferPolicy-DQ06i7SZ.mjs.map +0 -1
- package/dist/Transfers-GVqzrDWl.d.mts +0 -89
- package/dist/Transfers.d.mts +0 -3
- package/dist/Transfers.mjs +0 -85
- package/dist/Transfers.mjs.map +0 -1
- package/dist/Uploads-C2_bACgv.d.mts +0 -28
- package/dist/Uploads.d.mts +0 -3
- package/dist/Uploads.mjs +0 -105
- package/dist/Uploads.mjs.map +0 -1
- package/dist/Webhooks-BoeRAxzG.d.mts +0 -59
- package/dist/Webhooks.d.mts +0 -3
- package/dist/Webhooks.mjs +0 -134
- package/dist/Webhooks.mjs.map +0 -1
- package/dist/WriterFacts-BBJRDZYY.d.mts +0 -54
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { t as __exportAll } from "../rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { BrowserbaseClient, layer as layer$1 } from "../BrowserbaseClient.mjs";
|
|
3
|
+
import { Clock, Context, DateTime, Effect, Layer, Option, Predicate, Redacted, Schedule, Schema } from "effect";
|
|
4
|
+
import { HttpClient, HttpClientError, HttpClientResponse } from "effect/http";
|
|
5
|
+
import { TestClock } from "effect/testing";
|
|
6
|
+
//#region src/testing/BrowserbaseContract.ts
|
|
7
|
+
/**
|
|
8
|
+
* What this package relies on the Browserbase API to do, as named checks over
|
|
9
|
+
* `BrowserbaseClient`, each an Effect with no test runner around it. A package test runs them
|
|
10
|
+
* against `TestBrowserbase`, which keeps the fake to them; run against Browserbase itself, they
|
|
11
|
+
* show whether the fake is still faithful, at the cost of a few one-minute sessions. Checks that
|
|
12
|
+
* move the test clock run only against the fake.
|
|
13
|
+
*
|
|
14
|
+
* @category testing
|
|
15
|
+
* @since 0.3.0
|
|
16
|
+
*/
|
|
17
|
+
var BrowserbaseContract_exports = /* @__PURE__ */ __exportAll({
|
|
18
|
+
Broken: () => Broken,
|
|
19
|
+
checks: () => checks
|
|
20
|
+
});
|
|
21
|
+
/** A check found the API behaving otherwise. */
|
|
22
|
+
var Broken = class extends Schema.TaggedError()("Broken", {
|
|
23
|
+
check: Schema.String,
|
|
24
|
+
detail: Schema.String
|
|
25
|
+
}) {
|
|
26
|
+
get message() {
|
|
27
|
+
return `${this.check}: ${this.detail}`;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const check = (name, run, clock = false) => ({
|
|
31
|
+
name,
|
|
32
|
+
clock,
|
|
33
|
+
run: run((holds, detail) => holds ? Effect.void : Effect.fail(new Broken({
|
|
34
|
+
check: name,
|
|
35
|
+
detail
|
|
36
|
+
}))).pipe(Effect.withSpan("BrowserbaseContract.check", { attributes: { check: name } }))
|
|
37
|
+
});
|
|
38
|
+
const running = (session) => session.status === "PENDING" || session.status === "RUNNING";
|
|
39
|
+
/** What the call failed with, or `nothing` when it succeeded. */
|
|
40
|
+
const failure = (call) => Effect.match(call, {
|
|
41
|
+
onFailure: (error) => error.reason._tag,
|
|
42
|
+
onSuccess: () => "nothing"
|
|
43
|
+
});
|
|
44
|
+
/** Read the session until it has ended, for at most 10 seconds. */
|
|
45
|
+
const ended = (client, id) => client.getSession(id).pipe(Effect.repeat({
|
|
46
|
+
until: (session) => !running(session),
|
|
47
|
+
schedule: Schedule.spaced("1 second").pipe(Schedule.upTo({ times: 10 }))
|
|
48
|
+
}));
|
|
49
|
+
const checks = [
|
|
50
|
+
check("a new session runs, and ends once released", (expect) => Effect.gen(function* () {
|
|
51
|
+
const client = yield* BrowserbaseClient;
|
|
52
|
+
const session = yield* client.createSession({ timeout: 60 });
|
|
53
|
+
yield* expect(running(session), `a new session is ${session.status}`);
|
|
54
|
+
yield* client.releaseSession(session.id);
|
|
55
|
+
const after = yield* ended(client, session.id);
|
|
56
|
+
yield* expect(after.status === "COMPLETED", `a released session is ${after.status}`);
|
|
57
|
+
yield* client.releaseSession(session.id);
|
|
58
|
+
const again = yield* client.getSession(session.id);
|
|
59
|
+
yield* expect(again.status === "COMPLETED", `released twice, it is ${again.status}`);
|
|
60
|
+
})),
|
|
61
|
+
check("a well-formed id no session has is not found", (expect) => Effect.gen(function* () {
|
|
62
|
+
const client = yield* BrowserbaseClient;
|
|
63
|
+
const read = yield* failure(client.getSession("00000000-0000-4000-8000-000000000000"));
|
|
64
|
+
yield* expect(read === "NotFound", `reading it failed with ${read}`);
|
|
65
|
+
})),
|
|
66
|
+
check("a session id of another shape is refused as invalid", (expect) => Effect.gen(function* () {
|
|
67
|
+
const client = yield* BrowserbaseClient;
|
|
68
|
+
const read = yield* failure(client.getSession("not-a-session-of-this-project"));
|
|
69
|
+
yield* expect(read === "InvalidRequest", `reading it failed with ${read}`);
|
|
70
|
+
})),
|
|
71
|
+
check("sessions are found by status and user metadata", (expect) => Effect.gen(function* () {
|
|
72
|
+
const client = yield* BrowserbaseClient;
|
|
73
|
+
const label = `contract-${yield* Clock.currentTimeMillis}`;
|
|
74
|
+
const session = yield* client.createSession({
|
|
75
|
+
timeout: 60,
|
|
76
|
+
userMetadata: { label }
|
|
77
|
+
});
|
|
78
|
+
const query = (value) => `user_metadata['label']:'${value}'`;
|
|
79
|
+
const running = (value) => Effect.map(Effect.forEach(["PENDING", "RUNNING"], (status) => client.listSessions({
|
|
80
|
+
status,
|
|
81
|
+
query: query(value)
|
|
82
|
+
})), (found) => new Set(found.flat().map(({ id }) => id)));
|
|
83
|
+
const found = yield* running(label);
|
|
84
|
+
const other = yield* running(`${label}-x`);
|
|
85
|
+
yield* client.releaseSession(session.id);
|
|
86
|
+
yield* ended(client, session.id);
|
|
87
|
+
const gone = yield* running(label);
|
|
88
|
+
const any = yield* client.listSessions({ query: query(label) });
|
|
89
|
+
yield* expect(found.size === 1 && found.has(session.id), `${found.size} unended sessions had its label`);
|
|
90
|
+
yield* expect(other.size === 0, `${other.size} sessions had another label`);
|
|
91
|
+
yield* expect(gone.size === 0, `${gone.size} still ran once it had ended`);
|
|
92
|
+
yield* expect(any.some(({ id, status }) => id === session.id && status === "COMPLETED"), `with no status, a search found ${any.length} sessions, not it ended`);
|
|
93
|
+
})),
|
|
94
|
+
check("a context id of another shape is refused as invalid", (expect) => Effect.gen(function* () {
|
|
95
|
+
const client = yield* BrowserbaseClient;
|
|
96
|
+
const read = yield* failure(client.getContext("not-a-context-of-this-project"));
|
|
97
|
+
yield* expect(read === "InvalidRequest", `reading it failed with ${read}`);
|
|
98
|
+
})),
|
|
99
|
+
check("a stored context reads back, and is gone once deleted", (expect) => Effect.gen(function* () {
|
|
100
|
+
const client = yield* BrowserbaseClient;
|
|
101
|
+
const created = yield* client.createContext();
|
|
102
|
+
const read = yield* client.getContext(created.id);
|
|
103
|
+
yield* client.deleteContext(created.id);
|
|
104
|
+
const gone = yield* failure(client.getContext(created.id));
|
|
105
|
+
yield* expect(read.id === created.id, `it read back as ${read.id}`);
|
|
106
|
+
yield* expect(gone === "NotFound", `once deleted, reading it failed with ${gone}`);
|
|
107
|
+
})),
|
|
108
|
+
check("a session ends at its timeout", (expect) => Effect.gen(function* () {
|
|
109
|
+
const client = yield* BrowserbaseClient;
|
|
110
|
+
const session = yield* client.createSession({ timeout: 60 });
|
|
111
|
+
yield* TestClock.adjust("61 seconds");
|
|
112
|
+
const after = yield* client.getSession(session.id);
|
|
113
|
+
yield* expect(after.status === "TIMED_OUT", `after its timeout it is ${after.status}`);
|
|
114
|
+
}), true)
|
|
115
|
+
];
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/testing/TestBrowserbase.ts
|
|
118
|
+
/**
|
|
119
|
+
* An in-memory Browserbase API for tests. It is the `HttpClient` the real `BrowserbaseClient` runs
|
|
120
|
+
* on, so the client and the provider above it run as they would against Browserbase, and nothing
|
|
121
|
+
* leaves the process. Sessions run until released, or until their timeout on the Effect `Clock`,
|
|
122
|
+
* so `TestClock` drives their end. A `Script` decides how creates, releases and session reads
|
|
123
|
+
* answer, in turn, so a test can lose a create, leave a release pending or fail status reads.
|
|
124
|
+
*
|
|
125
|
+
* It keeps sessions and stored contexts and nothing else: any other endpoint fails the test with a
|
|
126
|
+
* defect. It doesn't model regions, quotas or billing, and a context is only a record, with no
|
|
127
|
+
* browser state. Every session hands out the script's `connectUrl`, such as the DevTools address
|
|
128
|
+
* of a local Chromium. Its ids are UUIDs, as Browserbase's are, and as Browserbase does, it refuses
|
|
129
|
+
* a session or context id of any other shape as invalid, where it answers an unknown one as not
|
|
130
|
+
* found.
|
|
131
|
+
*
|
|
132
|
+
* @category testing
|
|
133
|
+
* @since 0.3.0
|
|
134
|
+
*/
|
|
135
|
+
var TestBrowserbase_exports = /* @__PURE__ */ __exportAll({
|
|
136
|
+
Create: () => Create,
|
|
137
|
+
Read: () => Read,
|
|
138
|
+
Release: () => Release,
|
|
139
|
+
Script: () => Script,
|
|
140
|
+
TestBrowserbase: () => TestBrowserbase,
|
|
141
|
+
apiKey: () => apiKey,
|
|
142
|
+
layer: () => layer,
|
|
143
|
+
make: () => make
|
|
144
|
+
});
|
|
145
|
+
const Refusal = Schema.Int.check(Schema.isBetween({
|
|
146
|
+
minimum: 400,
|
|
147
|
+
maximum: 599
|
|
148
|
+
}));
|
|
149
|
+
/** How a session create answers. */
|
|
150
|
+
const Create = Schema.Union([
|
|
151
|
+
Schema.TaggedStruct("Accept", {}),
|
|
152
|
+
Schema.TaggedStruct("Reject", {
|
|
153
|
+
status: Refusal,
|
|
154
|
+
retryAfterSeconds: Schema.optional(Schema.Int)
|
|
155
|
+
}),
|
|
156
|
+
Schema.TaggedStruct("Malformed", {}),
|
|
157
|
+
Schema.TaggedStruct("Lost", { listedAfter: Schema.optional(Schema.Finite) })
|
|
158
|
+
]);
|
|
159
|
+
/** How a release answers. */
|
|
160
|
+
const Release = Schema.Union([
|
|
161
|
+
Schema.TaggedStruct("Ends", {}),
|
|
162
|
+
Schema.TaggedStruct("Pending", { millis: Schema.optional(Schema.Finite) }),
|
|
163
|
+
Schema.TaggedStruct("Refused", { status: Refusal }),
|
|
164
|
+
Schema.TaggedStruct("Lost", {})
|
|
165
|
+
]);
|
|
166
|
+
/** How a session read answers. */
|
|
167
|
+
const Read = Schema.Union([
|
|
168
|
+
Schema.TaggedStruct("Answer", {}),
|
|
169
|
+
Schema.TaggedStruct("Refused", { status: Refusal }),
|
|
170
|
+
Schema.TaggedStruct("Lost", {})
|
|
171
|
+
]);
|
|
172
|
+
const Script = Schema.Struct({
|
|
173
|
+
/** How creates answer, in turn; once these run out, each is accepted. */
|
|
174
|
+
creates: Schema.optional(Schema.Array(Create)),
|
|
175
|
+
/** How releases answer, in turn; once these run out, each ends its session. */
|
|
176
|
+
releases: Schema.optional(Schema.Array(Release)),
|
|
177
|
+
/** How session reads answer, in turn; once these run out, each answers. */
|
|
178
|
+
reads: Schema.optional(Schema.Array(Read)),
|
|
179
|
+
/** The DevTools address each session hands out. Without it, sessions have none. */
|
|
180
|
+
connectUrl: Schema.optional(Schema.String)
|
|
181
|
+
});
|
|
182
|
+
/** The fake's handle, beside the `BrowserbaseClient` that `layer` runs on it. */
|
|
183
|
+
var TestBrowserbase = class extends Context.Service()("effect-browserbase/testing/TestBrowserbase") {};
|
|
184
|
+
/** The API key the fake takes; any other is unauthorized. */
|
|
185
|
+
const apiKey = "test-browserbase-key";
|
|
186
|
+
const CreateBody = Schema.fromJsonString(Schema.Struct({
|
|
187
|
+
timeout: Schema.optional(Schema.Finite),
|
|
188
|
+
keepAlive: Schema.optional(Schema.Boolean),
|
|
189
|
+
userMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
190
|
+
browserSettings: Schema.optional(Schema.Struct({ context: Schema.optional(Schema.Struct({
|
|
191
|
+
id: Schema.String,
|
|
192
|
+
persist: Schema.optional(Schema.Boolean)
|
|
193
|
+
})) }))
|
|
194
|
+
}));
|
|
195
|
+
const ReleaseBody = Schema.fromJsonString(Schema.Struct({ status: Schema.Literal("REQUEST_RELEASE") }));
|
|
196
|
+
const iso = (millis) => DateTime.formatIso(DateTime.makeUnsafe(millis));
|
|
197
|
+
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
198
|
+
/** The next id, a UUID as Browserbase's are, counted so that every run hands out the same. */
|
|
199
|
+
const nextId = (fake) => `00000000-0000-4000-8000-${(++fake.made.count).toString(16).padStart(12, "0")}`;
|
|
200
|
+
/** How the session ended by now, or undefined while it runs. */
|
|
201
|
+
const endOf = (fake, row) => {
|
|
202
|
+
const end = row.ends !== void 0 && row.ends.at <= row.expiresAt ? row.ends : {
|
|
203
|
+
status: "TIMED_OUT",
|
|
204
|
+
at: row.expiresAt
|
|
205
|
+
};
|
|
206
|
+
return end.at <= fake.now() ? end : void 0;
|
|
207
|
+
};
|
|
208
|
+
/** The session as Browserbase answers it; only creates and reads hand out its address. */
|
|
209
|
+
const describe = (fake, row, connect) => {
|
|
210
|
+
const end = endOf(fake, row);
|
|
211
|
+
return {
|
|
212
|
+
id: row.id,
|
|
213
|
+
createdAt: iso(row.createdAt),
|
|
214
|
+
updatedAt: iso(end?.at ?? row.createdAt),
|
|
215
|
+
startedAt: iso(row.createdAt),
|
|
216
|
+
expiresAt: iso(row.expiresAt),
|
|
217
|
+
projectId: "test-project",
|
|
218
|
+
status: end?.status ?? "RUNNING",
|
|
219
|
+
proxyBytes: 0,
|
|
220
|
+
keepAlive: row.keepAlive,
|
|
221
|
+
region: "us-west-2",
|
|
222
|
+
...end === void 0 ? {} : { endedAt: iso(end.at) },
|
|
223
|
+
...row.context === void 0 ? {} : { contextId: row.context.id },
|
|
224
|
+
...row.userMetadata === void 0 ? {} : { userMetadata: row.userMetadata },
|
|
225
|
+
...connect && fake.connectUrl !== void 0 ? { connectUrl: fake.connectUrl } : {},
|
|
226
|
+
...connect ? {
|
|
227
|
+
seleniumRemoteUrl: `https://connect.browserbase.test/webdriver/${row.id}`,
|
|
228
|
+
signingKey: `signing-key-${row.id}`
|
|
229
|
+
} : {}
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
const bodyText = (request) => request.body._tag === "Uint8Array" ? request.body.text ?? new TextDecoder().decode(request.body.body) : "";
|
|
233
|
+
/** A metadata query in Browserbase's one form, `user_metadata['a']['b']:'value'`, as a test. */
|
|
234
|
+
const metadataQuery = (query) => {
|
|
235
|
+
const parsed = /^user_metadata((?:\['[^']*'\])+):'([^']*)'$/.exec(query);
|
|
236
|
+
if (parsed === null) return void 0;
|
|
237
|
+
const path = [...(parsed[1] ?? "").matchAll(/\['([^']*)'\]/g)].map((part) => part[1] ?? "");
|
|
238
|
+
return (metadata) => path.reduce((value, key) => Predicate.isObject(value) ? Reflect.get(value, key) : void 0, metadata) === parsed[2];
|
|
239
|
+
};
|
|
240
|
+
const unsupported = (detail) => Effect.die(/* @__PURE__ */ new Error(`TestBrowserbase ${detail}`));
|
|
241
|
+
/** Answer one request as Browserbase would, after the script's say. */
|
|
242
|
+
const serve = (fake, request, url) => {
|
|
243
|
+
const answer = (status, body, headers = {}) => Effect.succeed(HttpClientResponse.fromWeb(request, new Response(body === void 0 ? null : JSON.stringify(body), {
|
|
244
|
+
status,
|
|
245
|
+
headers: {
|
|
246
|
+
"content-type": "application/json",
|
|
247
|
+
...headers
|
|
248
|
+
}
|
|
249
|
+
})));
|
|
250
|
+
const lost = Effect.fail(new HttpClientError.HttpClientError({ reason: new HttpClientError.TransportError({
|
|
251
|
+
request,
|
|
252
|
+
description: "TestBrowserbase lost this answer, as its script said"
|
|
253
|
+
}) }));
|
|
254
|
+
const refused = (status) => answer(status, { message: "refused, as the script said" });
|
|
255
|
+
const [, version, kind, id, rest] = url.pathname.split("/");
|
|
256
|
+
const row = id === void 0 ? void 0 : fake.sessions.get(id);
|
|
257
|
+
fake.requests.push(`${request.method} ${url.pathname}${url.search}`);
|
|
258
|
+
if (request.headers["x-bb-api-key"] !== "test-browserbase-key") return answer(401, { message: "Invalid API key" });
|
|
259
|
+
if (version !== "v1") return unsupported(`serves only /v1, not ${url.pathname}`);
|
|
260
|
+
if ((kind === "sessions" || kind === "contexts") && id !== void 0 && !uuid.test(id)) return answer(400, { message: `Invalid ${kind === "sessions" ? "Session" : "Context"} ID` });
|
|
261
|
+
switch (`${request.method} ${kind}${id === void 0 ? "" : " id"}${rest === void 0 ? "" : ` ${rest}`}`) {
|
|
262
|
+
case "POST sessions": {
|
|
263
|
+
const reply = fake.pending.creates.shift() ?? { _tag: "Accept" };
|
|
264
|
+
if (reply._tag === "Reject") return answer(reply.status, { message: "refused, as the script said" }, reply.retryAfterSeconds === void 0 ? {} : { "retry-after": `${reply.retryAfterSeconds}` });
|
|
265
|
+
const body = Option.getOrUndefined(Schema.decodeOption(CreateBody)(bodyText(request)));
|
|
266
|
+
const loaded = body?.browserSettings?.context;
|
|
267
|
+
const createdAt = fake.now();
|
|
268
|
+
const created = {
|
|
269
|
+
id: nextId(fake),
|
|
270
|
+
createdAt,
|
|
271
|
+
listedAt: createdAt + (reply._tag === "Lost" ? reply.listedAfter ?? 0 : 0),
|
|
272
|
+
expiresAt: createdAt + (body?.timeout ?? 300) * 1e3,
|
|
273
|
+
keepAlive: body?.keepAlive ?? false,
|
|
274
|
+
userMetadata: body?.userMetadata,
|
|
275
|
+
context: loaded === void 0 ? void 0 : {
|
|
276
|
+
id: loaded.id,
|
|
277
|
+
persist: loaded.persist ?? false
|
|
278
|
+
},
|
|
279
|
+
ends: void 0,
|
|
280
|
+
releases: 0
|
|
281
|
+
};
|
|
282
|
+
fake.sessions.set(created.id, created);
|
|
283
|
+
if (reply._tag === "Lost") return lost;
|
|
284
|
+
return answer(201, reply._tag === "Malformed" ? { id: created.id } : describe(fake, created, true));
|
|
285
|
+
}
|
|
286
|
+
case "GET sessions": {
|
|
287
|
+
const status = url.searchParams.get("status");
|
|
288
|
+
const query = url.searchParams.get("q");
|
|
289
|
+
const matches = query === null ? () => true : metadataQuery(query);
|
|
290
|
+
if (matches === void 0) return unsupported(`cannot read the query ${query}`);
|
|
291
|
+
return answer(200, [...fake.sessions.values()].filter((kept) => kept.listedAt <= fake.now() && (status === null || (endOf(fake, kept)?.status ?? "RUNNING") === status) && matches(kept.userMetadata)).map((kept) => describe(fake, kept, false)));
|
|
292
|
+
}
|
|
293
|
+
case "GET sessions id": {
|
|
294
|
+
const reply = fake.pending.reads.shift() ?? { _tag: "Answer" };
|
|
295
|
+
if (reply._tag === "Refused") return refused(reply.status);
|
|
296
|
+
if (reply._tag === "Lost") return lost;
|
|
297
|
+
return row === void 0 ? answer(404, { message: "Session not found" }) : answer(200, describe(fake, row, true));
|
|
298
|
+
}
|
|
299
|
+
case "POST sessions id": {
|
|
300
|
+
if (row === void 0) return answer(404, { message: "Session not found" });
|
|
301
|
+
if (Option.isNone(Schema.decodeOption(ReleaseBody)(bodyText(request)))) return answer(400, { message: "status must be REQUEST_RELEASE" });
|
|
302
|
+
row.releases += 1;
|
|
303
|
+
const reply = fake.pending.releases.shift() ?? { _tag: "Ends" };
|
|
304
|
+
if (reply._tag === "Refused") return refused(reply.status);
|
|
305
|
+
if (endOf(fake, row) === void 0 && (reply._tag !== "Pending" || reply.millis !== void 0)) row.ends = {
|
|
306
|
+
status: "COMPLETED",
|
|
307
|
+
at: fake.now() + (reply._tag === "Pending" ? reply.millis ?? 0 : 0)
|
|
308
|
+
};
|
|
309
|
+
return reply._tag === "Lost" ? lost : answer(200, describe(fake, row, false));
|
|
310
|
+
}
|
|
311
|
+
case "POST contexts": {
|
|
312
|
+
const created = nextId(fake);
|
|
313
|
+
fake.contexts.set(created, fake.now());
|
|
314
|
+
return answer(201, {
|
|
315
|
+
id: created,
|
|
316
|
+
publicKey: "test-public-key",
|
|
317
|
+
cipherAlgorithm: "AES-256-CBC",
|
|
318
|
+
initializationVectorSize: 16
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
case "GET contexts id": {
|
|
322
|
+
const createdAt = id === void 0 ? void 0 : fake.contexts.get(id);
|
|
323
|
+
return createdAt === void 0 ? answer(404, { message: "Context not found" }) : answer(200, {
|
|
324
|
+
id,
|
|
325
|
+
createdAt: iso(createdAt),
|
|
326
|
+
updatedAt: iso(createdAt),
|
|
327
|
+
projectId: "test-project"
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
case "DELETE contexts id": return id !== void 0 && fake.contexts.delete(id) ? answer(204) : answer(404, { message: "Context not found" });
|
|
331
|
+
default: return unsupported(`does not implement ${request.method} ${url.pathname}`);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
/** The fake as an `HttpClient`, with its handle. */
|
|
335
|
+
const make = Effect.fnUntraced(function* (script = {}) {
|
|
336
|
+
const clock = yield* Clock.Clock;
|
|
337
|
+
const decoded = yield* Effect.orDie(Schema.decodeEffect(Script)(script));
|
|
338
|
+
const fake = {
|
|
339
|
+
now: () => clock.currentTimeMillisUnsafe(),
|
|
340
|
+
connectUrl: decoded.connectUrl,
|
|
341
|
+
pending: {
|
|
342
|
+
creates: [...decoded.creates ?? []],
|
|
343
|
+
releases: [...decoded.releases ?? []],
|
|
344
|
+
reads: [...decoded.reads ?? []]
|
|
345
|
+
},
|
|
346
|
+
requests: [],
|
|
347
|
+
sessions: /* @__PURE__ */ new Map(),
|
|
348
|
+
contexts: /* @__PURE__ */ new Map(),
|
|
349
|
+
made: { count: 0 }
|
|
350
|
+
};
|
|
351
|
+
const handle = {
|
|
352
|
+
requests: Effect.sync(() => [...fake.requests]),
|
|
353
|
+
sessions: Effect.sync(() => [...fake.sessions.values()].map((row) => ({
|
|
354
|
+
id: row.id,
|
|
355
|
+
status: endOf(fake, row)?.status ?? "RUNNING",
|
|
356
|
+
userMetadata: row.userMetadata,
|
|
357
|
+
releases: row.releases,
|
|
358
|
+
keepAlive: row.keepAlive,
|
|
359
|
+
context: row.context
|
|
360
|
+
}))),
|
|
361
|
+
end: (id) => Effect.sync(() => {
|
|
362
|
+
const row = fake.sessions.get(id);
|
|
363
|
+
if (row !== void 0 && endOf(fake, row) === void 0) row.ends = {
|
|
364
|
+
status: "ERROR",
|
|
365
|
+
at: fake.now()
|
|
366
|
+
};
|
|
367
|
+
})
|
|
368
|
+
};
|
|
369
|
+
return {
|
|
370
|
+
http: HttpClient.make((request, url) => serve(fake, request, url)),
|
|
371
|
+
handle
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
/** The real `BrowserbaseClient` over the fake, and the fake's handle. */
|
|
375
|
+
const layer = (script = {}) => Layer.unwrap(Effect.map(make(script), ({ http, handle }) => Layer.merge(layer$1({
|
|
376
|
+
apiKey: Redacted.make(apiKey),
|
|
377
|
+
baseUrl: "https://api.browserbase.test"
|
|
378
|
+
}).pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, http))), Layer.succeed(TestBrowserbase, handle))));
|
|
379
|
+
//#endregion
|
|
380
|
+
export { BrowserbaseContract_exports as BrowserbaseContract, TestBrowserbase_exports as TestBrowserbase };
|
|
381
|
+
|
|
382
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["clientLayer"],"sources":["../../src/testing/BrowserbaseContract.ts","../../src/testing/TestBrowserbase.ts"],"sourcesContent":["/**\n * What this package relies on the Browserbase API to do, as named checks over\n * `BrowserbaseClient`, each an Effect with no test runner around it. A package test runs them\n * against `TestBrowserbase`, which keeps the fake to them; run against Browserbase itself, they\n * show whether the fake is still faithful, at the cost of a few one-minute sessions. Checks that\n * move the test clock run only against the fake.\n *\n * @category testing\n * @since 0.3.0\n */\nimport { Clock, Effect, Schedule, Schema } from \"effect\";\nimport { TestClock } from \"effect/testing\";\n\nimport { BrowserbaseClient, type Session } from \"../BrowserbaseClient.ts\";\nimport type { BrowserbaseError } from \"../BrowserbaseError.ts\";\n\n/** A check found the API behaving otherwise. */\nexport class Broken extends Schema.TaggedError<Broken>()(\"Broken\", {\n check: Schema.String,\n detail: Schema.String,\n}) {\n override get message() {\n return `${this.check}: ${this.detail}`;\n }\n}\n\nexport interface Check {\n readonly name: string;\n /** It moves the test clock, so only the fake can run it. */\n readonly clock: boolean;\n readonly run: Effect.Effect<void, Broken | BrowserbaseError, BrowserbaseClient>;\n}\n\nconst check = (\n name: string,\n run: (\n expect: (holds: boolean, detail: string) => Effect.Effect<void, Broken>,\n ) => Effect.Effect<void, Broken | BrowserbaseError, BrowserbaseClient>,\n clock = false,\n): Check => ({\n name,\n clock,\n run: run((holds, detail) =>\n holds ? Effect.void : Effect.fail(new Broken({ check: name, detail })),\n ).pipe(Effect.withSpan(\"BrowserbaseContract.check\", { attributes: { check: name } })),\n});\n\nconst running = (session: Session) => session.status === \"PENDING\" || session.status === \"RUNNING\";\n\n/** What the call failed with, or `nothing` when it succeeded. */\nconst failure = <A, R>(call: Effect.Effect<A, BrowserbaseError, R>) =>\n Effect.match(call, { onFailure: (error) => error.reason._tag, onSuccess: () => \"nothing\" });\n\n/** Read the session until it has ended, for at most 10 seconds. */\nconst ended = (client: BrowserbaseClient[\"Service\"], id: string) =>\n client.getSession(id).pipe(\n Effect.repeat({\n until: (session) => !running(session),\n schedule: Schedule.spaced(\"1 second\").pipe(Schedule.upTo({ times: 10 })),\n }),\n );\n\nexport const checks: ReadonlyArray<Check> = [\n check(\"a new session runs, and ends once released\", (expect) =>\n Effect.gen(function* () {\n const client = yield* BrowserbaseClient;\n const session = yield* client.createSession({ timeout: 60 });\n\n yield* expect(running(session), `a new session is ${session.status}`);\n yield* client.releaseSession(session.id);\n const after = yield* ended(client, session.id);\n\n yield* expect(after.status === \"COMPLETED\", `a released session is ${after.status}`);\n // Releasing a session that has ended succeeds and changes nothing.\n yield* client.releaseSession(session.id);\n const again = yield* client.getSession(session.id);\n\n yield* expect(again.status === \"COMPLETED\", `released twice, it is ${again.status}`);\n }),\n ),\n check(\"a well-formed id no session has is not found\", (expect) =>\n Effect.gen(function* () {\n const client = yield* BrowserbaseClient;\n const read = yield* failure(client.getSession(\"00000000-0000-4000-8000-000000000000\"));\n\n yield* expect(read === \"NotFound\", `reading it failed with ${read}`);\n }),\n ),\n check(\"a session id of another shape is refused as invalid\", (expect) =>\n Effect.gen(function* () {\n const client = yield* BrowserbaseClient;\n const read = yield* failure(client.getSession(\"not-a-session-of-this-project\"));\n\n yield* expect(read === \"InvalidRequest\", `reading it failed with ${read}`);\n }),\n ),\n check(\"sessions are found by status and user metadata\", (expect) =>\n Effect.gen(function* () {\n const client = yield* BrowserbaseClient;\n const label = `contract-${yield* Clock.currentTimeMillis}`;\n const session = yield* client.createSession({ timeout: 60, userMetadata: { label } });\n const query = (value: string) => `user_metadata['label']:'${value}'`;\n\n // A new session is PENDING or RUNNING, and may change between the two queries, so the\n // package looks under both statuses; a check of one alone would fail a compliant API.\n const running = (value: string) =>\n Effect.map(\n Effect.forEach([\"PENDING\", \"RUNNING\"], (status) =>\n client.listSessions({ status, query: query(value) }),\n ),\n (found) => new Set(found.flat().map(({ id }) => id)),\n );\n\n const found = yield* running(label);\n const other = yield* running(`${label}-x`);\n\n yield* client.releaseSession(session.id);\n yield* ended(client, session.id);\n const gone = yield* running(label);\n // With no status, a search finds sessions in every state, as a lost create's search needs.\n const any = yield* client.listSessions({ query: query(label) });\n\n yield* expect(\n found.size === 1 && found.has(session.id),\n `${found.size} unended sessions had its label`,\n );\n yield* expect(other.size === 0, `${other.size} sessions had another label`);\n yield* expect(gone.size === 0, `${gone.size} still ran once it had ended`);\n yield* expect(\n any.some(({ id, status }) => id === session.id && status === \"COMPLETED\"),\n `with no status, a search found ${any.length} sessions, not it ended`,\n );\n }),\n ),\n check(\"a context id of another shape is refused as invalid\", (expect) =>\n Effect.gen(function* () {\n const client = yield* BrowserbaseClient;\n const read = yield* failure(client.getContext(\"not-a-context-of-this-project\"));\n\n yield* expect(read === \"InvalidRequest\", `reading it failed with ${read}`);\n }),\n ),\n check(\"a stored context reads back, and is gone once deleted\", (expect) =>\n Effect.gen(function* () {\n const client = yield* BrowserbaseClient;\n const created = yield* client.createContext();\n const read = yield* client.getContext(created.id);\n\n yield* client.deleteContext(created.id);\n const gone = yield* failure(client.getContext(created.id));\n\n yield* expect(read.id === created.id, `it read back as ${read.id}`);\n yield* expect(gone === \"NotFound\", `once deleted, reading it failed with ${gone}`);\n }),\n ),\n check(\n \"a session ends at its timeout\",\n (expect) =>\n Effect.gen(function* () {\n const client = yield* BrowserbaseClient;\n const session = yield* client.createSession({ timeout: 60 });\n\n yield* TestClock.adjust(\"61 seconds\");\n const after = yield* client.getSession(session.id);\n\n yield* expect(after.status === \"TIMED_OUT\", `after its timeout it is ${after.status}`);\n }),\n true,\n ),\n];\n","/**\n * An in-memory Browserbase API for tests. It is the `HttpClient` the real `BrowserbaseClient` runs\n * on, so the client and the provider above it run as they would against Browserbase, and nothing\n * leaves the process. Sessions run until released, or until their timeout on the Effect `Clock`,\n * so `TestClock` drives their end. A `Script` decides how creates, releases and session reads\n * answer, in turn, so a test can lose a create, leave a release pending or fail status reads.\n *\n * It keeps sessions and stored contexts and nothing else: any other endpoint fails the test with a\n * defect. It doesn't model regions, quotas or billing, and a context is only a record, with no\n * browser state. Every session hands out the script's `connectUrl`, such as the DevTools address\n * of a local Chromium. Its ids are UUIDs, as Browserbase's are, and as Browserbase does, it refuses\n * a session or context id of any other shape as invalid, where it answers an unknown one as not\n * found.\n *\n * @category testing\n * @since 0.3.0\n */\nimport {\n Clock,\n Context,\n DateTime,\n Effect,\n Layer,\n Option,\n Predicate,\n Redacted,\n Schema,\n} from \"effect\";\nimport {\n HttpClient,\n HttpClientError,\n type HttpClientRequest,\n HttpClientResponse,\n} from \"effect/http\";\n\nimport { type BrowserbaseClient, layer as clientLayer } from \"../BrowserbaseClient.ts\";\n\nconst Refusal = Schema.Int.check(Schema.isBetween({ minimum: 400, maximum: 599 }));\n\n/** How a session create answers. */\nexport const Create = Schema.Union([\n /** The session is made, and runs. */\n Schema.TaggedStruct(\"Accept\", {}),\n /** Refused with `status`, and nothing is made. */\n Schema.TaggedStruct(\"Reject\", {\n status: Refusal,\n retryAfterSeconds: Schema.optional(Schema.Int),\n }),\n /** The session is made, but the answer holds only its id. */\n Schema.TaggedStruct(\"Malformed\", {}),\n /**\n * The session is made, but no answer arrives, and Browserbase lists it only `listedAfter`\n * milliseconds later, as a search can trail what was made.\n */\n Schema.TaggedStruct(\"Lost\", { listedAfter: Schema.optional(Schema.Finite) }),\n]);\n\n/** How a release answers. */\nexport const Release = Schema.Union([\n /** The session ends at once. */\n Schema.TaggedStruct(\"Ends\", {}),\n /** Accepted, but the session runs on for `millis`, or without them until its timeout. */\n Schema.TaggedStruct(\"Pending\", { millis: Schema.optional(Schema.Finite) }),\n /** Refused with `status`; the session runs on. */\n Schema.TaggedStruct(\"Refused\", { status: Refusal }),\n /** The session ends, but no answer arrives. */\n Schema.TaggedStruct(\"Lost\", {}),\n]);\n\n/** How a session read answers. */\nexport const Read = Schema.Union([\n Schema.TaggedStruct(\"Answer\", {}),\n Schema.TaggedStruct(\"Refused\", { status: Refusal }),\n Schema.TaggedStruct(\"Lost\", {}),\n]);\n\nexport const Script = Schema.Struct({\n /** How creates answer, in turn; once these run out, each is accepted. */\n creates: Schema.optional(Schema.Array(Create)),\n /** How releases answer, in turn; once these run out, each ends its session. */\n releases: Schema.optional(Schema.Array(Release)),\n /** How session reads answer, in turn; once these run out, each answers. */\n reads: Schema.optional(Schema.Array(Read)),\n /** The DevTools address each session hands out. Without it, sessions have none. */\n connectUrl: Schema.optional(Schema.String),\n});\n\nexport type Script = typeof Script.Type;\n\n/** A session as the fake keeps it, with how many releases it was asked. */\nexport interface Kept {\n readonly id: string;\n readonly status: string;\n readonly userMetadata: Readonly<Record<string, unknown>> | undefined;\n readonly releases: number;\n /** Whether it was created to outlive its connections, until released or timed out. */\n readonly keepAlive: boolean;\n /** The stored context it loaded, and whether it saves to it as it ends. */\n readonly context: { readonly id: string; readonly persist: boolean } | undefined;\n}\n\nexport interface Handle {\n /** Every request, oldest first, as `METHOD /path?query`. */\n readonly requests: Effect.Effect<ReadonlyArray<string>>;\n /** Each session as Browserbase would describe it now, oldest first. */\n readonly sessions: Effect.Effect<ReadonlyArray<Kept>>;\n /** End a session as Browserbase does on its own, as when its browser fails. */\n readonly end: (id: string) => Effect.Effect<void>;\n}\n\n/** The fake's handle, beside the `BrowserbaseClient` that `layer` runs on it. */\nexport class TestBrowserbase extends Context.Service<TestBrowserbase, Handle>()(\n \"effect-browserbase/testing/TestBrowserbase\",\n) {}\n\n/** The API key the fake takes; any other is unauthorized. */\nexport const apiKey = \"test-browserbase-key\";\n\nconst CreateBody = Schema.fromJsonString(\n Schema.Struct({\n timeout: Schema.optional(Schema.Finite),\n keepAlive: Schema.optional(Schema.Boolean),\n userMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),\n browserSettings: Schema.optional(\n Schema.Struct({\n context: Schema.optional(\n Schema.Struct({ id: Schema.String, persist: Schema.optional(Schema.Boolean) }),\n ),\n }),\n ),\n }),\n);\n\nconst ReleaseBody = Schema.fromJsonString(\n Schema.Struct({ status: Schema.Literal(\"REQUEST_RELEASE\") }),\n);\n\ninterface Row {\n readonly id: string;\n readonly createdAt: number;\n /** When a list first shows it. */\n readonly listedAt: number;\n readonly expiresAt: number;\n readonly keepAlive: boolean;\n readonly userMetadata: Readonly<Record<string, unknown>> | undefined;\n readonly context: { readonly id: string; readonly persist: boolean } | undefined;\n /** How and when it ends, unless its timeout comes first. */\n ends: { readonly status: string; readonly at: number } | undefined;\n releases: number;\n}\n\n/** Everything one fake keeps. */\ninterface Fake {\n readonly now: () => number;\n readonly connectUrl: string | undefined;\n readonly pending: {\n readonly creates: Array<typeof Create.Type>;\n readonly releases: Array<typeof Release.Type>;\n readonly reads: Array<typeof Read.Type>;\n };\n readonly requests: Array<string>;\n readonly sessions: Map<string, Row>;\n readonly contexts: Map<string, number>;\n /** How many sessions and contexts it has made, together, for their ids. */\n readonly made: { count: number };\n}\n\nconst iso = (millis: number) => DateTime.formatIso(DateTime.makeUnsafe(millis));\n\nconst uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** The next id, a UUID as Browserbase's are, counted so that every run hands out the same. */\nconst nextId = (fake: Fake) =>\n `00000000-0000-4000-8000-${(++fake.made.count).toString(16).padStart(12, \"0\")}`;\n\n/** How the session ended by now, or undefined while it runs. */\nconst endOf = (fake: Fake, row: Row) => {\n const end =\n row.ends !== undefined && row.ends.at <= row.expiresAt\n ? row.ends\n : { status: \"TIMED_OUT\", at: row.expiresAt };\n\n return end.at <= fake.now() ? end : undefined;\n};\n\n/** The session as Browserbase answers it; only creates and reads hand out its address. */\nconst describe = (fake: Fake, row: Row, connect: boolean) => {\n const end = endOf(fake, row);\n\n return {\n id: row.id,\n createdAt: iso(row.createdAt),\n updatedAt: iso(end?.at ?? row.createdAt),\n startedAt: iso(row.createdAt),\n expiresAt: iso(row.expiresAt),\n projectId: \"test-project\",\n status: end?.status ?? \"RUNNING\",\n proxyBytes: 0,\n keepAlive: row.keepAlive,\n region: \"us-west-2\",\n ...(end === undefined ? {} : { endedAt: iso(end.at) }),\n ...(row.context === undefined ? {} : { contextId: row.context.id }),\n ...(row.userMetadata === undefined ? {} : { userMetadata: row.userMetadata }),\n ...(connect && fake.connectUrl !== undefined ? { connectUrl: fake.connectUrl } : {}),\n ...(connect\n ? {\n seleniumRemoteUrl: `https://connect.browserbase.test/webdriver/${row.id}`,\n signingKey: `signing-key-${row.id}`,\n }\n : {}),\n };\n};\n\nconst bodyText = (request: HttpClientRequest.HttpClientRequest) =>\n request.body._tag === \"Uint8Array\"\n ? (request.body.text ?? new TextDecoder().decode(request.body.body))\n : \"\";\n\n/** A metadata query in Browserbase's one form, `user_metadata['a']['b']:'value'`, as a test. */\nconst metadataQuery = (query: string) => {\n const parsed = /^user_metadata((?:\\['[^']*'\\])+):'([^']*)'$/.exec(query);\n\n if (parsed === null) return undefined;\n const path = [...(parsed[1] ?? \"\").matchAll(/\\['([^']*)'\\]/g)].map((part) => part[1] ?? \"\");\n\n return (metadata: unknown) =>\n path.reduce<unknown>(\n (value, key) => (Predicate.isObject(value) ? Reflect.get(value, key) : undefined),\n metadata,\n ) === parsed[2];\n};\n\nconst unsupported = (detail: string) => Effect.die(new Error(`TestBrowserbase ${detail}`));\n\n/** Answer one request as Browserbase would, after the script's say. */\nconst serve = (fake: Fake, request: HttpClientRequest.HttpClientRequest, url: URL) => {\n const answer = (status: number, body?: unknown, headers: Record<string, string> = {}) =>\n Effect.succeed(\n HttpClientResponse.fromWeb(\n request,\n new Response(body === undefined ? null : JSON.stringify(body), {\n status,\n headers: { \"content-type\": \"application/json\", ...headers },\n }),\n ),\n );\n\n const lost = Effect.fail(\n new HttpClientError.HttpClientError({\n reason: new HttpClientError.TransportError({\n request,\n description: \"TestBrowserbase lost this answer, as its script said\",\n }),\n }),\n );\n\n const refused = (status: number) => answer(status, { message: \"refused, as the script said\" });\n const [, version, kind, id, rest] = url.pathname.split(\"/\");\n const row = id === undefined ? undefined : fake.sessions.get(id);\n\n fake.requests.push(`${request.method} ${url.pathname}${url.search}`);\n if (request.headers[\"x-bb-api-key\"] !== apiKey)\n return answer(401, { message: \"Invalid API key\" });\n if (version !== \"v1\") return unsupported(`serves only /v1, not ${url.pathname}`);\n // Browserbase refuses a session or context id that is not a UUID before it looks for it.\n if ((kind === \"sessions\" || kind === \"contexts\") && id !== undefined && !uuid.test(id))\n return answer(400, { message: `Invalid ${kind === \"sessions\" ? \"Session\" : \"Context\"} ID` });\n\n switch (\n `${request.method} ${kind}${id === undefined ? \"\" : \" id\"}${rest === undefined ? \"\" : ` ${rest}`}`\n ) {\n case \"POST sessions\": {\n const reply = fake.pending.creates.shift() ?? { _tag: \"Accept\" };\n\n if (reply._tag === \"Reject\")\n return answer(\n reply.status,\n { message: \"refused, as the script said\" },\n reply.retryAfterSeconds === undefined\n ? {}\n : { \"retry-after\": `${reply.retryAfterSeconds}` },\n );\n const body = Option.getOrUndefined(Schema.decodeOption(CreateBody)(bodyText(request)));\n const loaded = body?.browserSettings?.context;\n const createdAt = fake.now();\n\n const created: Row = {\n id: nextId(fake),\n createdAt,\n listedAt: createdAt + (reply._tag === \"Lost\" ? (reply.listedAfter ?? 0) : 0),\n expiresAt: createdAt + (body?.timeout ?? 300) * 1000,\n keepAlive: body?.keepAlive ?? false,\n userMetadata: body?.userMetadata,\n context:\n loaded === undefined ? undefined : { id: loaded.id, persist: loaded.persist ?? false },\n ends: undefined,\n releases: 0,\n };\n\n fake.sessions.set(created.id, created);\n if (reply._tag === \"Lost\") return lost;\n\n return answer(\n 201,\n reply._tag === \"Malformed\" ? { id: created.id } : describe(fake, created, true),\n );\n }\n case \"GET sessions\": {\n const status = url.searchParams.get(\"status\");\n const query = url.searchParams.get(\"q\");\n const matches = query === null ? () => true : metadataQuery(query);\n\n if (matches === undefined) return unsupported(`cannot read the query ${query}`);\n\n return answer(\n 200,\n [...fake.sessions.values()]\n .filter(\n (kept) =>\n kept.listedAt <= fake.now() &&\n (status === null || (endOf(fake, kept)?.status ?? \"RUNNING\") === status) &&\n matches(kept.userMetadata),\n )\n .map((kept) => describe(fake, kept, false)),\n );\n }\n case \"GET sessions id\": {\n const reply = fake.pending.reads.shift() ?? { _tag: \"Answer\" };\n\n if (reply._tag === \"Refused\") return refused(reply.status);\n if (reply._tag === \"Lost\") return lost;\n\n return row === undefined\n ? answer(404, { message: \"Session not found\" })\n : answer(200, describe(fake, row, true));\n }\n case \"POST sessions id\": {\n if (row === undefined) return answer(404, { message: \"Session not found\" });\n if (Option.isNone(Schema.decodeOption(ReleaseBody)(bodyText(request))))\n return answer(400, { message: \"status must be REQUEST_RELEASE\" });\n row.releases += 1;\n const reply = fake.pending.releases.shift() ?? { _tag: \"Ends\" };\n\n if (reply._tag === \"Refused\") return refused(reply.status);\n // Releasing a session that has ended changes nothing, and succeeds.\n if (\n endOf(fake, row) === undefined &&\n (reply._tag !== \"Pending\" || reply.millis !== undefined)\n )\n row.ends = {\n status: \"COMPLETED\",\n at: fake.now() + (reply._tag === \"Pending\" ? (reply.millis ?? 0) : 0),\n };\n\n return reply._tag === \"Lost\" ? lost : answer(200, describe(fake, row, false));\n }\n case \"POST contexts\": {\n const created = nextId(fake);\n\n fake.contexts.set(created, fake.now());\n\n return answer(201, {\n id: created,\n publicKey: \"test-public-key\",\n cipherAlgorithm: \"AES-256-CBC\",\n initializationVectorSize: 16,\n });\n }\n case \"GET contexts id\": {\n const createdAt = id === undefined ? undefined : fake.contexts.get(id);\n\n return createdAt === undefined\n ? answer(404, { message: \"Context not found\" })\n : answer(200, {\n id,\n createdAt: iso(createdAt),\n updatedAt: iso(createdAt),\n projectId: \"test-project\",\n });\n }\n case \"DELETE contexts id\":\n return id !== undefined && fake.contexts.delete(id)\n ? answer(204)\n : answer(404, { message: \"Context not found\" });\n default:\n return unsupported(`does not implement ${request.method} ${url.pathname}`);\n }\n};\n\n/** The fake as an `HttpClient`, with its handle. */\nexport const make = Effect.fnUntraced(function* (script: Script = {}) {\n const clock = yield* Clock.Clock;\n const decoded = yield* Effect.orDie(Schema.decodeEffect(Script)(script));\n\n const fake: Fake = {\n now: () => clock.currentTimeMillisUnsafe(),\n connectUrl: decoded.connectUrl,\n pending: {\n creates: [...(decoded.creates ?? [])],\n releases: [...(decoded.releases ?? [])],\n reads: [...(decoded.reads ?? [])],\n },\n requests: [],\n sessions: new Map(),\n contexts: new Map(),\n made: { count: 0 },\n };\n\n const handle: Handle = {\n requests: Effect.sync(() => [...fake.requests]),\n sessions: Effect.sync(() =>\n [...fake.sessions.values()].map((row) => ({\n id: row.id,\n status: endOf(fake, row)?.status ?? \"RUNNING\",\n userMetadata: row.userMetadata,\n releases: row.releases,\n keepAlive: row.keepAlive,\n context: row.context,\n })),\n ),\n end: (id) =>\n Effect.sync(() => {\n const row = fake.sessions.get(id);\n\n if (row !== undefined && endOf(fake, row) === undefined)\n row.ends = { status: \"ERROR\", at: fake.now() };\n }),\n };\n\n return { http: HttpClient.make((request, url) => serve(fake, request, url)), handle };\n});\n\n/** The real `BrowserbaseClient` over the fake, and the fake's handle. */\nexport const layer = (script: Script = {}): Layer.Layer<BrowserbaseClient | TestBrowserbase> =>\n Layer.unwrap(\n Effect.map(make(script), ({ http, handle }) =>\n Layer.merge(\n clientLayer({\n apiKey: Redacted.make(apiKey),\n baseUrl: \"https://api.browserbase.test\",\n }).pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, http))),\n Layer.succeed(TestBrowserbase, handle),\n ),\n ),\n );\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,146 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-browserbase",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0-beta.5",
|
|
4
|
+
"description": "Browserbase for effect-browser: hosted browser sessions, contexts, web search and page fetch.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/mannyc2/effect-agent-browserbase.git",
|
|
9
9
|
"directory": "packages/browserbase"
|
|
10
10
|
},
|
|
11
|
-
"type": "module",
|
|
12
|
-
"sideEffects": [],
|
|
13
|
-
"engines": {
|
|
14
|
-
"node": "^22.18.0 || >=24.11.0"
|
|
15
|
-
},
|
|
16
|
-
"peerDependencies": {
|
|
17
|
-
"effect": "4.0.0-rc.117",
|
|
18
|
-
"effect-browser": "0.2.0-beta.9"
|
|
19
|
-
},
|
|
20
|
-
"homepage": "https://github.com/mannyc2/effect-agent-browserbase#readme",
|
|
21
|
-
"bugs": {
|
|
22
|
-
"url": "https://github.com/mannyc2/effect-agent-browserbase/issues"
|
|
23
|
-
},
|
|
24
11
|
"files": [
|
|
25
|
-
"dist"
|
|
12
|
+
"dist",
|
|
13
|
+
"src"
|
|
26
14
|
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": [],
|
|
27
17
|
"exports": {
|
|
28
18
|
".": {
|
|
19
|
+
"@effect-browser/source": "./src/index.ts",
|
|
29
20
|
"types": "./dist/index.d.mts",
|
|
30
21
|
"default": "./dist/index.mjs"
|
|
31
22
|
},
|
|
32
|
-
"./
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
"./agents": {
|
|
37
|
-
"types": "./dist/Agents.d.mts",
|
|
38
|
-
"default": "./dist/Agents.mjs"
|
|
39
|
-
},
|
|
40
|
-
"./allocation": {
|
|
41
|
-
"types": "./dist/Allocation.d.mts",
|
|
42
|
-
"default": "./dist/Allocation.mjs"
|
|
43
|
-
},
|
|
44
|
-
"./browser": {
|
|
45
|
-
"types": "./dist/Browser.d.mts",
|
|
46
|
-
"default": "./dist/Browser.mjs"
|
|
47
|
-
},
|
|
48
|
-
"./browser-binding": {
|
|
49
|
-
"types": "./dist/BrowserBinding.d.mts",
|
|
50
|
-
"default": "./dist/BrowserBinding.mjs"
|
|
51
|
-
},
|
|
52
|
-
"./certificates": {
|
|
53
|
-
"types": "./dist/Certificates.d.mts",
|
|
54
|
-
"default": "./dist/Certificates.mjs"
|
|
55
|
-
},
|
|
56
|
-
"./cleanup": {
|
|
57
|
-
"types": "./dist/Cleanup.d.mts",
|
|
58
|
-
"default": "./dist/Cleanup.mjs"
|
|
59
|
-
},
|
|
60
|
-
"./client": {
|
|
61
|
-
"types": "./dist/Client.d.mts",
|
|
62
|
-
"default": "./dist/Client.mjs"
|
|
63
|
-
},
|
|
64
|
-
"./context-coordination": {
|
|
65
|
-
"types": "./dist/ContextCoordination.d.mts",
|
|
66
|
-
"default": "./dist/ContextCoordination.mjs"
|
|
67
|
-
},
|
|
68
|
-
"./contexts": {
|
|
69
|
-
"types": "./dist/Contexts.d.mts",
|
|
70
|
-
"default": "./dist/Contexts.mjs"
|
|
71
|
-
},
|
|
72
|
-
"./downloads": {
|
|
73
|
-
"types": "./dist/Downloads.d.mts",
|
|
74
|
-
"default": "./dist/Downloads.mjs"
|
|
23
|
+
"./Browserbase": {
|
|
24
|
+
"@effect-browser/source": "./src/Browserbase.ts",
|
|
25
|
+
"types": "./dist/Browserbase.d.mts",
|
|
26
|
+
"default": "./dist/Browserbase.mjs"
|
|
75
27
|
},
|
|
76
|
-
"./
|
|
77
|
-
"
|
|
78
|
-
"
|
|
28
|
+
"./BrowserbaseClient": {
|
|
29
|
+
"@effect-browser/source": "./src/BrowserbaseClient.ts",
|
|
30
|
+
"types": "./dist/BrowserbaseClient.d.mts",
|
|
31
|
+
"default": "./dist/BrowserbaseClient.mjs"
|
|
79
32
|
},
|
|
80
|
-
"./
|
|
81
|
-
"
|
|
82
|
-
"
|
|
33
|
+
"./BrowserbaseError": {
|
|
34
|
+
"@effect-browser/source": "./src/BrowserbaseError.ts",
|
|
35
|
+
"types": "./dist/BrowserbaseError.d.mts",
|
|
36
|
+
"default": "./dist/BrowserbaseError.mjs"
|
|
83
37
|
},
|
|
84
|
-
"./
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
"./launch": {
|
|
89
|
-
"types": "./dist/Launch.d.mts",
|
|
90
|
-
"default": "./dist/Launch.mjs"
|
|
91
|
-
},
|
|
92
|
-
"./page-fetch": {
|
|
93
|
-
"types": "./dist/PageFetch.d.mts",
|
|
94
|
-
"default": "./dist/PageFetch.mjs"
|
|
95
|
-
},
|
|
96
|
-
"./projects": {
|
|
97
|
-
"types": "./dist/Projects.d.mts",
|
|
98
|
-
"default": "./dist/Projects.mjs"
|
|
99
|
-
},
|
|
100
|
-
"./recordings": {
|
|
101
|
-
"types": "./dist/Recordings.d.mts",
|
|
102
|
-
"default": "./dist/Recordings.mjs"
|
|
103
|
-
},
|
|
104
|
-
"./references": {
|
|
105
|
-
"types": "./dist/References.d.mts",
|
|
106
|
-
"default": "./dist/References.mjs"
|
|
107
|
-
},
|
|
108
|
-
"./replays": {
|
|
109
|
-
"types": "./dist/Replays.d.mts",
|
|
110
|
-
"default": "./dist/Replays.mjs"
|
|
111
|
-
},
|
|
112
|
-
"./search": {
|
|
113
|
-
"types": "./dist/Search.d.mts",
|
|
114
|
-
"default": "./dist/Search.mjs"
|
|
115
|
-
},
|
|
116
|
-
"./session-data": {
|
|
117
|
-
"types": "./dist/SessionData.d.mts",
|
|
118
|
-
"default": "./dist/SessionData.mjs"
|
|
119
|
-
},
|
|
120
|
-
"./sessions": {
|
|
121
|
-
"types": "./dist/Sessions.d.mts",
|
|
122
|
-
"default": "./dist/Sessions.mjs"
|
|
38
|
+
"./ContextLease": {
|
|
39
|
+
"@effect-browser/source": "./src/ContextLease.ts",
|
|
40
|
+
"types": "./dist/ContextLease.d.mts",
|
|
41
|
+
"default": "./dist/ContextLease.mjs"
|
|
123
42
|
},
|
|
124
43
|
"./testing": {
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
"./transfers": {
|
|
129
|
-
"types": "./dist/Transfers.d.mts",
|
|
130
|
-
"default": "./dist/Transfers.mjs"
|
|
131
|
-
},
|
|
132
|
-
"./uploads": {
|
|
133
|
-
"types": "./dist/Uploads.d.mts",
|
|
134
|
-
"default": "./dist/Uploads.mjs"
|
|
135
|
-
},
|
|
136
|
-
"./webhooks": {
|
|
137
|
-
"types": "./dist/Webhooks.d.mts",
|
|
138
|
-
"default": "./dist/Webhooks.mjs"
|
|
44
|
+
"@effect-browser/source": "./src/testing/index.ts",
|
|
45
|
+
"types": "./dist/testing/index.d.mts",
|
|
46
|
+
"default": "./dist/testing/index.mjs"
|
|
139
47
|
}
|
|
140
48
|
},
|
|
141
49
|
"publishConfig": {
|
|
142
|
-
"access": "public"
|
|
143
|
-
|
|
144
|
-
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "vp pack",
|
|
54
|
+
"check": "tsc --noEmit -p tsconfig.json",
|
|
55
|
+
"test": "vp test --run"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@effect/vitest": "4.0.0",
|
|
59
|
+
"@types/node": "26.1.2",
|
|
60
|
+
"effect": "4.0.0",
|
|
61
|
+
"effect-browser": "0.3.0-beta.5",
|
|
62
|
+
"playwright-core": "1.63.0",
|
|
63
|
+
"typescript": "7.0.2",
|
|
64
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
|
|
65
|
+
"vite-plus": "1.1.0",
|
|
66
|
+
"vitest": "5.0.3"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"effect": "~4.0.0",
|
|
70
|
+
"effect-browser": "^0.3.0-beta.4",
|
|
71
|
+
"playwright-core": "^1.63.0"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": "^22.18.0 || >=24.11.0"
|
|
145
75
|
}
|
|
146
76
|
}
|