effect-cf 0.13.0 → 0.14.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.
- package/CHANGELOG.md +19 -0
- package/dist/{Hyperdrive-L6ldMlRF.d.mts → Hyperdrive-C0vfrPeG.d.mts} +1 -1
- package/dist/HyperdrivePg.d.mts +1 -1
- package/dist/index.d.mts +500 -311
- package/dist/index.mjs +434 -315
- package/package.json +8 -8
- package/src/AiGateway.ts +284 -0
- package/src/BrowserRendering.ts +286 -0
- package/src/CloudflareOtlp.ts +90 -386
- package/src/DurableObject.ts +5 -2
- package/src/Environment.ts +1 -1
- package/src/Images.ts +42 -24
- package/src/Kv.ts +6 -2
- package/src/QueueBinding.ts +19 -10
- package/src/R2.ts +47 -33
- package/src/Vectorize.ts +213 -0
- package/src/Worker.ts +2 -0
- package/src/WorkersAi.ts +239 -0
- package/src/Workflow.ts +2 -0
- package/src/WorkflowBinding.ts +15 -9
- package/src/index.ts +4 -0
- package/src/internal/Clock.ts +28 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-cf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Effect-native primitives for Cloudflare Workers and bindings.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"prepublishOnly": "vp run build"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@cloudflare/vitest-pool-workers": "
|
|
43
|
-
"@cloudflare/workers-types": "
|
|
44
|
-
"@effect/platform-node": "4.0.0-beta.
|
|
42
|
+
"@cloudflare/vitest-pool-workers": "catalog:",
|
|
43
|
+
"@cloudflare/workers-types": "catalog:",
|
|
44
|
+
"@effect/platform-node": "4.0.0-beta.77",
|
|
45
45
|
"@effect/sql-d1": "catalog:",
|
|
46
46
|
"@effect/sql-pg": "catalog:",
|
|
47
47
|
"@effect/sql-sqlite-do": "catalog:",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"vitest": "catalog:"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@effect/sql-d1": "^4.0.0-beta.
|
|
56
|
-
"@effect/sql-pg": "^4.0.0-beta.
|
|
57
|
-
"@effect/sql-sqlite-do": "^4.0.0-beta.
|
|
58
|
-
"effect": "^4.0.0-beta.
|
|
55
|
+
"@effect/sql-d1": "^4.0.0-beta.77",
|
|
56
|
+
"@effect/sql-pg": "^4.0.0-beta.77",
|
|
57
|
+
"@effect/sql-sqlite-do": "^4.0.0-beta.77",
|
|
58
|
+
"effect": "^4.0.0-beta.77"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
|
61
61
|
"@effect/sql-pg": {
|
package/src/AiGateway.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { Context, Data, Effect, type Layer } from "effect";
|
|
2
|
+
import type { AiGateway as CloudflareAiGateway } from "@cloudflare/workers-types";
|
|
3
|
+
|
|
4
|
+
import * as Binding from "./Binding";
|
|
5
|
+
import type { WorkerEnvironment } from "./Environment";
|
|
6
|
+
import { isWorkersAiBinding, type WorkersAiBinding } from "./WorkersAi";
|
|
7
|
+
|
|
8
|
+
const expectedAiGatewayBinding = "Workers AI binding with gateway()";
|
|
9
|
+
|
|
10
|
+
/** Error raised when an AI Gateway operation fails. */
|
|
11
|
+
export class AiGatewayOperationError extends Data.TaggedError("AiGatewayOperationError")<{
|
|
12
|
+
readonly binding: string;
|
|
13
|
+
readonly operation: string;
|
|
14
|
+
readonly cause: unknown;
|
|
15
|
+
}> {}
|
|
16
|
+
|
|
17
|
+
/** Typed AI Gateway client definition. */
|
|
18
|
+
export interface AiGatewayDefinition {
|
|
19
|
+
/** Binding name as configured in `wrangler.jsonc`. */
|
|
20
|
+
readonly binding: string;
|
|
21
|
+
/** Gateway id/name passed to `env.AI.gateway(id)`. */
|
|
22
|
+
readonly gatewayId: string;
|
|
23
|
+
/** Optional account id used by the HTTP-only helper. */
|
|
24
|
+
readonly accountId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type AiGatewayBinding = CloudflareAiGateway;
|
|
28
|
+
type AiGatewayRunData = Parameters<AiGatewayBinding["run"]>[0];
|
|
29
|
+
export type AiGatewayPatchLog = Parameters<AiGatewayBinding["patchLog"]>[1];
|
|
30
|
+
export type AiGatewayLog = Awaited<ReturnType<AiGatewayBinding["getLog"]>>;
|
|
31
|
+
export type AiGatewayProvider = NonNullable<Parameters<AiGatewayBinding["getUrl"]>[0]>;
|
|
32
|
+
export type AiGatewayUniversalRequest = Exclude<AiGatewayRunData, ReadonlyArray<unknown>>;
|
|
33
|
+
export type AiGatewayHeaders = NonNullable<AiGatewayUniversalRequest["headers"]>;
|
|
34
|
+
export type AiGatewayRunOptions = Parameters<CloudflareAiGateway["run"]>[1];
|
|
35
|
+
|
|
36
|
+
export interface AiGatewayFetchOptions {
|
|
37
|
+
readonly provider?: AiGatewayProvider;
|
|
38
|
+
readonly path?: string;
|
|
39
|
+
readonly init?: RequestInit;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AiGatewayClient {
|
|
43
|
+
readonly run: (
|
|
44
|
+
data: AiGatewayUniversalRequest | ReadonlyArray<AiGatewayUniversalRequest>,
|
|
45
|
+
options?: AiGatewayRunOptions,
|
|
46
|
+
) => Effect.Effect<Response, AiGatewayOperationError>;
|
|
47
|
+
readonly getUrl: (provider?: AiGatewayProvider) => Effect.Effect<string, AiGatewayOperationError>;
|
|
48
|
+
readonly fetch: (
|
|
49
|
+
options: AiGatewayFetchOptions,
|
|
50
|
+
) => Effect.Effect<Response, AiGatewayOperationError>;
|
|
51
|
+
readonly patchLog: (
|
|
52
|
+
logId: string,
|
|
53
|
+
data: AiGatewayPatchLog,
|
|
54
|
+
) => Effect.Effect<void, AiGatewayOperationError>;
|
|
55
|
+
readonly getLog: (logId: string) => Effect.Effect<AiGatewayLog, AiGatewayOperationError>;
|
|
56
|
+
readonly unsafeRaw: Effect.Effect<AiGatewayBinding, AiGatewayOperationError>;
|
|
57
|
+
readonly definition: AiGatewayDefinition;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
declare const AiGatewayServiceTypeId: unique symbol;
|
|
61
|
+
|
|
62
|
+
/** Nominal service marker for AI Gateway services created with {@link make}. */
|
|
63
|
+
export interface AiGatewayService<Id extends string> {
|
|
64
|
+
readonly [AiGatewayServiceTypeId]: {
|
|
65
|
+
readonly id: Id;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type LayerOptions = {
|
|
70
|
+
readonly binding: string;
|
|
71
|
+
readonly gatewayId: string;
|
|
72
|
+
readonly accountId?: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export interface TagClass<Self, Id extends string> extends Context.ServiceClass<
|
|
76
|
+
Self,
|
|
77
|
+
Id,
|
|
78
|
+
AiGatewayClient
|
|
79
|
+
> {
|
|
80
|
+
readonly id: Id;
|
|
81
|
+
readonly layer: (
|
|
82
|
+
options: LayerOptions,
|
|
83
|
+
) => Layer.Layer<
|
|
84
|
+
Self,
|
|
85
|
+
Binding.BindingNotFoundError | Binding.BindingValidationError | AiGatewayOperationError,
|
|
86
|
+
WorkerEnvironment
|
|
87
|
+
>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const aiGatewayError = (binding: string, operation: string, cause: unknown) =>
|
|
91
|
+
new AiGatewayOperationError({ binding, operation, cause });
|
|
92
|
+
|
|
93
|
+
const tryAiGatewayPromise = <A>(
|
|
94
|
+
binding: string,
|
|
95
|
+
operation: string,
|
|
96
|
+
evaluate: () => Promise<A>,
|
|
97
|
+
): Effect.Effect<A, AiGatewayOperationError> =>
|
|
98
|
+
Effect.tryPromise({
|
|
99
|
+
try: evaluate,
|
|
100
|
+
catch: (cause) => aiGatewayError(binding, operation, cause),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const tryAiGatewaySync = <A>(
|
|
104
|
+
binding: string,
|
|
105
|
+
operation: string,
|
|
106
|
+
evaluate: () => A,
|
|
107
|
+
): Effect.Effect<A, AiGatewayOperationError> =>
|
|
108
|
+
Effect.try({
|
|
109
|
+
try: evaluate,
|
|
110
|
+
catch: (cause) => aiGatewayError(binding, operation, cause),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const providerUrl = (baseUrl: string, path = "") => {
|
|
114
|
+
const url = new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
115
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
116
|
+
return normalizedPath === "" ? url : new URL(normalizedPath, url);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const gatewayUrl = (definition: { readonly accountId: string; readonly gatewayId: string }) =>
|
|
120
|
+
`https://gateway.ai.cloudflare.com/v1/${definition.accountId}/${definition.gatewayId}/`;
|
|
121
|
+
|
|
122
|
+
export const isAiGatewayBinding = (value: unknown): value is WorkersAiBinding =>
|
|
123
|
+
isWorkersAiBinding(value);
|
|
124
|
+
|
|
125
|
+
export const makeClient = (definition: AiGatewayDefinition, gateway: AiGatewayBinding) =>
|
|
126
|
+
({
|
|
127
|
+
definition,
|
|
128
|
+
run: (data, options) =>
|
|
129
|
+
tryAiGatewayPromise(definition.binding, "run", () =>
|
|
130
|
+
gateway.run(
|
|
131
|
+
(Array.isArray(data) ? [...data] : data) as
|
|
132
|
+
| AiGatewayUniversalRequest
|
|
133
|
+
| Array<AiGatewayUniversalRequest>,
|
|
134
|
+
options,
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
getUrl: (provider) =>
|
|
138
|
+
tryAiGatewayPromise(definition.binding, "getUrl", () => gateway.getUrl(provider)),
|
|
139
|
+
fetch: (options) =>
|
|
140
|
+
Effect.gen(function* () {
|
|
141
|
+
const baseUrl = yield* tryAiGatewayPromise(definition.binding, "getUrl", () =>
|
|
142
|
+
gateway.getUrl(options.provider),
|
|
143
|
+
);
|
|
144
|
+
return yield* tryAiGatewayPromise(definition.binding, "fetch", () =>
|
|
145
|
+
fetch(providerUrl(baseUrl, options.path).href, options.init),
|
|
146
|
+
);
|
|
147
|
+
}),
|
|
148
|
+
patchLog: (logId, data) =>
|
|
149
|
+
tryAiGatewayPromise(definition.binding, "patchLog", () => gateway.patchLog(logId, data)),
|
|
150
|
+
getLog: (logId) =>
|
|
151
|
+
tryAiGatewayPromise(definition.binding, "getLog", () => gateway.getLog(logId)),
|
|
152
|
+
unsafeRaw: Effect.succeed(gateway),
|
|
153
|
+
}) satisfies AiGatewayClient;
|
|
154
|
+
|
|
155
|
+
export const makeClientFromAi = (
|
|
156
|
+
definition: AiGatewayDefinition,
|
|
157
|
+
ai: WorkersAiBinding,
|
|
158
|
+
): AiGatewayClient => {
|
|
159
|
+
const gateway = tryAiGatewaySync(definition.binding, "gateway", () =>
|
|
160
|
+
ai.gateway(definition.gatewayId),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const withGateway = <A>(
|
|
164
|
+
operation: (gateway: AiGatewayBinding) => Effect.Effect<A, AiGatewayOperationError>,
|
|
165
|
+
) => gateway.pipe(Effect.flatMap(operation));
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
definition,
|
|
169
|
+
run: (data, options) => withGateway((raw) => makeClient(definition, raw).run(data, options)),
|
|
170
|
+
getUrl: (provider) => withGateway((raw) => makeClient(definition, raw).getUrl(provider)),
|
|
171
|
+
fetch: (options) => withGateway((raw) => makeClient(definition, raw).fetch(options)),
|
|
172
|
+
patchLog: (logId, data) =>
|
|
173
|
+
withGateway((raw) => makeClient(definition, raw).patchLog(logId, data)),
|
|
174
|
+
getLog: (logId) => withGateway((raw) => makeClient(definition, raw).getLog(logId)),
|
|
175
|
+
unsafeRaw: gateway,
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const makeHttpClient = (
|
|
180
|
+
definition: Omit<AiGatewayDefinition, "binding"> & {
|
|
181
|
+
readonly accountId: string;
|
|
182
|
+
readonly binding?: string;
|
|
183
|
+
},
|
|
184
|
+
request: typeof fetch = fetch,
|
|
185
|
+
): AiGatewayClient => {
|
|
186
|
+
const binding = definition.binding ?? "AI_GATEWAY";
|
|
187
|
+
const clientDefinition: AiGatewayDefinition = {
|
|
188
|
+
binding,
|
|
189
|
+
gatewayId: definition.gatewayId,
|
|
190
|
+
accountId: definition.accountId,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const getUrl = (provider?: AiGatewayProvider) =>
|
|
194
|
+
Effect.succeed(
|
|
195
|
+
provider === undefined
|
|
196
|
+
? gatewayUrl(definition)
|
|
197
|
+
: providerUrl(gatewayUrl(definition), provider).href.replace(/\/$/, ""),
|
|
198
|
+
);
|
|
199
|
+
const extraHeaders = (options?: AiGatewayRunOptions): HeadersInit | undefined => {
|
|
200
|
+
if (
|
|
201
|
+
options?.extraHeaders === undefined ||
|
|
202
|
+
Array.isArray(options.extraHeaders) ||
|
|
203
|
+
options.extraHeaders instanceof Headers
|
|
204
|
+
) {
|
|
205
|
+
return options?.extraHeaders as HeadersInit | undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return Object.fromEntries(
|
|
209
|
+
Object.entries(options.extraHeaders).map(([key, value]) => [key, String(value)]),
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
definition: clientDefinition,
|
|
215
|
+
run: (data, options) =>
|
|
216
|
+
tryAiGatewayPromise(binding, "run", () =>
|
|
217
|
+
request(gatewayUrl(definition), {
|
|
218
|
+
method: "POST",
|
|
219
|
+
body: JSON.stringify(data),
|
|
220
|
+
headers: new Headers({
|
|
221
|
+
"content-type": "application/json",
|
|
222
|
+
...Object.fromEntries(new Headers(extraHeaders(options)).entries()),
|
|
223
|
+
}),
|
|
224
|
+
signal: options?.signal,
|
|
225
|
+
}),
|
|
226
|
+
),
|
|
227
|
+
getUrl,
|
|
228
|
+
fetch: (options) =>
|
|
229
|
+
getUrl(options.provider).pipe(
|
|
230
|
+
Effect.flatMap((baseUrl) =>
|
|
231
|
+
tryAiGatewayPromise(binding, "fetch", () =>
|
|
232
|
+
request(providerUrl(baseUrl, options.path).href, options.init),
|
|
233
|
+
),
|
|
234
|
+
),
|
|
235
|
+
),
|
|
236
|
+
patchLog: (logId, data) =>
|
|
237
|
+
tryAiGatewayPromise(binding, "patchLog", () =>
|
|
238
|
+
request(providerUrl(gatewayUrl(definition), `logs/${logId}`).href, {
|
|
239
|
+
method: "PATCH",
|
|
240
|
+
body: JSON.stringify(data),
|
|
241
|
+
headers: { "content-type": "application/json" },
|
|
242
|
+
}).then(() => undefined),
|
|
243
|
+
),
|
|
244
|
+
getLog: (logId) =>
|
|
245
|
+
tryAiGatewayPromise(binding, "getLog", async () => {
|
|
246
|
+
const response = await request(providerUrl(gatewayUrl(definition), `logs/${logId}`).href);
|
|
247
|
+
return (await response.json()) as AiGatewayLog;
|
|
248
|
+
}),
|
|
249
|
+
unsafeRaw: Effect.fail(
|
|
250
|
+
aiGatewayError(
|
|
251
|
+
binding,
|
|
252
|
+
"unsafeRaw",
|
|
253
|
+
new TypeError("HTTP AI Gateway clients have no raw binding"),
|
|
254
|
+
),
|
|
255
|
+
),
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const layer = <Self>(
|
|
260
|
+
tag: Context.Service<Self, AiGatewayClient>,
|
|
261
|
+
definition: AiGatewayDefinition,
|
|
262
|
+
) =>
|
|
263
|
+
Binding.layer(
|
|
264
|
+
tag,
|
|
265
|
+
definition.binding,
|
|
266
|
+
isAiGatewayBinding,
|
|
267
|
+
(ai) => makeClientFromAi(definition, ai),
|
|
268
|
+
{ expected: expectedAiGatewayBinding },
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
export const make = <Id extends string>(id: Id) => Tag<AiGatewayService<Id>>()<Id>(id);
|
|
272
|
+
|
|
273
|
+
export const Tag =
|
|
274
|
+
<Self>() =>
|
|
275
|
+
<Id extends string>(id: Id) => {
|
|
276
|
+
const tag = Context.Service<Self, AiGatewayClient>()(id);
|
|
277
|
+
|
|
278
|
+
const makeLayer = (definition: LayerOptions) => layer(tag, definition);
|
|
279
|
+
|
|
280
|
+
return Object.assign(tag, {
|
|
281
|
+
id,
|
|
282
|
+
layer: makeLayer,
|
|
283
|
+
}) as TagClass<Self, Id>;
|
|
284
|
+
};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { Context, Data, Effect, type Layer } from "effect";
|
|
2
|
+
|
|
3
|
+
import * as Binding from "./Binding";
|
|
4
|
+
import type { WorkerEnvironment } from "./Environment";
|
|
5
|
+
|
|
6
|
+
const expectedBrowserRenderingBinding = "Browser Rendering binding resource";
|
|
7
|
+
|
|
8
|
+
/** Error raised when a Browser Rendering operation fails. */
|
|
9
|
+
export class BrowserRenderingOperationError extends Data.TaggedError(
|
|
10
|
+
"BrowserRenderingOperationError",
|
|
11
|
+
)<{
|
|
12
|
+
readonly binding: string;
|
|
13
|
+
readonly operation: string;
|
|
14
|
+
readonly cause: unknown;
|
|
15
|
+
}> {}
|
|
16
|
+
|
|
17
|
+
/** Typed Browser Rendering binding definition. */
|
|
18
|
+
export interface BrowserRenderingDefinition {
|
|
19
|
+
/** Binding name as configured in `wrangler.jsonc`. */
|
|
20
|
+
readonly binding: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type BrowserRenderingBinding = object;
|
|
24
|
+
export type BrowserRenderingLaunch<RawBinding, Browser, LaunchOptions = unknown> = (
|
|
25
|
+
binding: RawBinding,
|
|
26
|
+
options?: LaunchOptions,
|
|
27
|
+
) => Promise<Browser>;
|
|
28
|
+
export type BrowserRenderingConnect<RawBinding, Browser, ConnectOptions = unknown> = (
|
|
29
|
+
binding: RawBinding,
|
|
30
|
+
options?: ConnectOptions,
|
|
31
|
+
) => Promise<Browser>;
|
|
32
|
+
|
|
33
|
+
export interface BrowserRenderingBrowserLike<Page = BrowserRenderingPageLike> {
|
|
34
|
+
readonly newPage: () => Promise<Page>;
|
|
35
|
+
readonly close?: () => Promise<void>;
|
|
36
|
+
readonly disconnect?: () => void;
|
|
37
|
+
readonly version?: () => Promise<string>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface BrowserRenderingPageLike {
|
|
41
|
+
readonly goto?: (url: string, options?: unknown) => Promise<unknown>;
|
|
42
|
+
readonly setContent?: (html: string, options?: unknown) => Promise<void>;
|
|
43
|
+
readonly content?: () => Promise<string>;
|
|
44
|
+
readonly screenshot?: (options?: unknown) => Promise<Uint8Array | string>;
|
|
45
|
+
readonly pdf?: (options?: unknown) => Promise<Uint8Array>;
|
|
46
|
+
readonly evaluate?: (pageFunction: unknown, ...args: ReadonlyArray<unknown>) => Promise<unknown>;
|
|
47
|
+
readonly close?: (options?: unknown) => Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface BrowserRenderingPageClient<Page extends BrowserRenderingPageLike> {
|
|
51
|
+
readonly raw: Page;
|
|
52
|
+
readonly goto: (
|
|
53
|
+
url: string,
|
|
54
|
+
options?: Parameters<NonNullable<Page["goto"]>>[1],
|
|
55
|
+
) => Effect.Effect<
|
|
56
|
+
Awaited<ReturnType<NonNullable<Page["goto"]>>>,
|
|
57
|
+
BrowserRenderingOperationError
|
|
58
|
+
>;
|
|
59
|
+
readonly setContent: (
|
|
60
|
+
html: string,
|
|
61
|
+
options?: Parameters<NonNullable<Page["setContent"]>>[1],
|
|
62
|
+
) => Effect.Effect<void, BrowserRenderingOperationError>;
|
|
63
|
+
readonly content: Effect.Effect<string, BrowserRenderingOperationError>;
|
|
64
|
+
readonly screenshot: <A = Uint8Array | string>(
|
|
65
|
+
options?: Parameters<NonNullable<Page["screenshot"]>>[0],
|
|
66
|
+
) => Effect.Effect<A, BrowserRenderingOperationError>;
|
|
67
|
+
readonly pdf: <A = Uint8Array>(
|
|
68
|
+
options?: Parameters<NonNullable<Page["pdf"]>>[0],
|
|
69
|
+
) => Effect.Effect<A, BrowserRenderingOperationError>;
|
|
70
|
+
readonly evaluate: <A = unknown>(
|
|
71
|
+
pageFunction: Parameters<NonNullable<Page["evaluate"]>>[0],
|
|
72
|
+
...args: ReadonlyArray<unknown>
|
|
73
|
+
) => Effect.Effect<A, BrowserRenderingOperationError>;
|
|
74
|
+
readonly close: (
|
|
75
|
+
options?: Parameters<NonNullable<Page["close"]>>[0],
|
|
76
|
+
) => Effect.Effect<void, BrowserRenderingOperationError>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface BrowserRenderingBrowserClient<
|
|
80
|
+
Browser extends BrowserRenderingBrowserLike<Page>,
|
|
81
|
+
Page extends BrowserRenderingPageLike,
|
|
82
|
+
> {
|
|
83
|
+
readonly raw: Browser;
|
|
84
|
+
readonly newPage: Effect.Effect<BrowserRenderingPageClient<Page>, BrowserRenderingOperationError>;
|
|
85
|
+
readonly version: Effect.Effect<string, BrowserRenderingOperationError>;
|
|
86
|
+
readonly close: Effect.Effect<void, BrowserRenderingOperationError>;
|
|
87
|
+
readonly disconnect: Effect.Effect<void, BrowserRenderingOperationError>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface BrowserRenderingClient<
|
|
91
|
+
RawBinding extends BrowserRenderingBinding = BrowserRenderingBinding,
|
|
92
|
+
> {
|
|
93
|
+
readonly launchWith: <
|
|
94
|
+
Browser extends BrowserRenderingBrowserLike<Page>,
|
|
95
|
+
Page extends BrowserRenderingPageLike = BrowserRenderingPageLike,
|
|
96
|
+
LaunchOptions = unknown,
|
|
97
|
+
>(
|
|
98
|
+
launch: BrowserRenderingLaunch<RawBinding, Browser, LaunchOptions>,
|
|
99
|
+
options?: LaunchOptions,
|
|
100
|
+
) => Effect.Effect<BrowserRenderingBrowserClient<Browser, Page>, BrowserRenderingOperationError>;
|
|
101
|
+
readonly connectWith: <
|
|
102
|
+
Browser extends BrowserRenderingBrowserLike<Page>,
|
|
103
|
+
Page extends BrowserRenderingPageLike = BrowserRenderingPageLike,
|
|
104
|
+
ConnectOptions = unknown,
|
|
105
|
+
>(
|
|
106
|
+
connect: BrowserRenderingConnect<RawBinding, Browser, ConnectOptions>,
|
|
107
|
+
options?: ConnectOptions,
|
|
108
|
+
) => Effect.Effect<BrowserRenderingBrowserClient<Browser, Page>, BrowserRenderingOperationError>;
|
|
109
|
+
readonly unsafeRaw: Effect.Effect<RawBinding>;
|
|
110
|
+
readonly definition: BrowserRenderingDefinition;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
declare const BrowserRenderingServiceTypeId: unique symbol;
|
|
114
|
+
|
|
115
|
+
/** Nominal service marker for Browser Rendering services created with {@link make}. */
|
|
116
|
+
export interface BrowserRenderingService<Id extends string> {
|
|
117
|
+
readonly [BrowserRenderingServiceTypeId]: {
|
|
118
|
+
readonly id: Id;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export type LayerOptions = {
|
|
123
|
+
readonly binding: string;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export interface TagClass<Self, Id extends string> extends Context.ServiceClass<
|
|
127
|
+
Self,
|
|
128
|
+
Id,
|
|
129
|
+
BrowserRenderingClient
|
|
130
|
+
> {
|
|
131
|
+
readonly id: Id;
|
|
132
|
+
readonly layer: (
|
|
133
|
+
options: LayerOptions,
|
|
134
|
+
) => Layer.Layer<
|
|
135
|
+
Self,
|
|
136
|
+
Binding.BindingNotFoundError | Binding.BindingValidationError,
|
|
137
|
+
WorkerEnvironment
|
|
138
|
+
>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const browserRenderingError = (binding: string, operation: string, cause: unknown) =>
|
|
142
|
+
new BrowserRenderingOperationError({ binding, operation, cause });
|
|
143
|
+
|
|
144
|
+
const tryBrowserRenderingPromise = <A>(
|
|
145
|
+
binding: string,
|
|
146
|
+
operation: string,
|
|
147
|
+
evaluate: () => Promise<A>,
|
|
148
|
+
): Effect.Effect<A, BrowserRenderingOperationError> =>
|
|
149
|
+
Effect.tryPromise({
|
|
150
|
+
try: evaluate,
|
|
151
|
+
catch: (cause) => browserRenderingError(binding, operation, cause),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const tryBrowserRenderingSync = <A>(
|
|
155
|
+
binding: string,
|
|
156
|
+
operation: string,
|
|
157
|
+
evaluate: () => A,
|
|
158
|
+
): Effect.Effect<A, BrowserRenderingOperationError> =>
|
|
159
|
+
Effect.try({
|
|
160
|
+
try: evaluate,
|
|
161
|
+
catch: (cause) => browserRenderingError(binding, operation, cause),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const missingMethod = (binding: string, operation: string) =>
|
|
165
|
+
Effect.fail(
|
|
166
|
+
browserRenderingError(
|
|
167
|
+
binding,
|
|
168
|
+
operation,
|
|
169
|
+
new TypeError(`Browser Rendering object does not expose ${operation}()`),
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const wrapPage = <Page extends BrowserRenderingPageLike>(
|
|
174
|
+
binding: string,
|
|
175
|
+
page: Page,
|
|
176
|
+
): BrowserRenderingPageClient<Page> => ({
|
|
177
|
+
raw: page,
|
|
178
|
+
goto: (url, options) =>
|
|
179
|
+
(page.goto === undefined
|
|
180
|
+
? missingMethod(binding, "goto")
|
|
181
|
+
: tryBrowserRenderingPromise(binding, "goto", () =>
|
|
182
|
+
page.goto!(url, options),
|
|
183
|
+
)) as Effect.Effect<
|
|
184
|
+
Awaited<ReturnType<NonNullable<Page["goto"]>>>,
|
|
185
|
+
BrowserRenderingOperationError
|
|
186
|
+
>,
|
|
187
|
+
setContent: (html, options) =>
|
|
188
|
+
page.setContent === undefined
|
|
189
|
+
? missingMethod(binding, "setContent")
|
|
190
|
+
: tryBrowserRenderingPromise(binding, "setContent", () => page.setContent!(html, options)),
|
|
191
|
+
content:
|
|
192
|
+
page.content === undefined
|
|
193
|
+
? missingMethod(binding, "content")
|
|
194
|
+
: tryBrowserRenderingPromise(binding, "content", () => page.content!()),
|
|
195
|
+
screenshot: (options) =>
|
|
196
|
+
(page.screenshot === undefined
|
|
197
|
+
? missingMethod(binding, "screenshot")
|
|
198
|
+
: tryBrowserRenderingPromise(binding, "screenshot", () =>
|
|
199
|
+
page.screenshot!(options),
|
|
200
|
+
)) as Effect.Effect<never, BrowserRenderingOperationError>,
|
|
201
|
+
pdf: (options) =>
|
|
202
|
+
(page.pdf === undefined
|
|
203
|
+
? missingMethod(binding, "pdf")
|
|
204
|
+
: tryBrowserRenderingPromise(binding, "pdf", () => page.pdf!(options))) as Effect.Effect<
|
|
205
|
+
never,
|
|
206
|
+
BrowserRenderingOperationError
|
|
207
|
+
>,
|
|
208
|
+
evaluate: (pageFunction, ...args) =>
|
|
209
|
+
(page.evaluate === undefined
|
|
210
|
+
? missingMethod(binding, "evaluate")
|
|
211
|
+
: tryBrowserRenderingPromise(binding, "evaluate", () =>
|
|
212
|
+
page.evaluate!(pageFunction, ...args),
|
|
213
|
+
)) as Effect.Effect<never, BrowserRenderingOperationError>,
|
|
214
|
+
close: (options) =>
|
|
215
|
+
page.close === undefined
|
|
216
|
+
? Effect.void
|
|
217
|
+
: tryBrowserRenderingPromise(binding, "closePage", () => page.close!(options)),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const wrapBrowser = <
|
|
221
|
+
Browser extends BrowserRenderingBrowserLike<Page>,
|
|
222
|
+
Page extends BrowserRenderingPageLike,
|
|
223
|
+
>(
|
|
224
|
+
binding: string,
|
|
225
|
+
browser: Browser,
|
|
226
|
+
): BrowserRenderingBrowserClient<Browser, Page> => ({
|
|
227
|
+
raw: browser,
|
|
228
|
+
newPage: tryBrowserRenderingPromise(binding, "newPage", () => browser.newPage()).pipe(
|
|
229
|
+
Effect.map((page) => wrapPage(binding, page)),
|
|
230
|
+
),
|
|
231
|
+
version:
|
|
232
|
+
browser.version === undefined
|
|
233
|
+
? missingMethod(binding, "version")
|
|
234
|
+
: tryBrowserRenderingPromise(binding, "version", () => browser.version!()),
|
|
235
|
+
close:
|
|
236
|
+
browser.close === undefined
|
|
237
|
+
? Effect.void
|
|
238
|
+
: tryBrowserRenderingPromise(binding, "closeBrowser", () => browser.close!()),
|
|
239
|
+
disconnect:
|
|
240
|
+
browser.disconnect === undefined
|
|
241
|
+
? Effect.void
|
|
242
|
+
: tryBrowserRenderingSync(binding, "disconnect", () => browser.disconnect!()),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
export const isBrowserRenderingBinding = (value: unknown): value is BrowserRenderingBinding =>
|
|
246
|
+
(typeof value === "object" || typeof value === "function") && value !== null;
|
|
247
|
+
|
|
248
|
+
export const makeClient =
|
|
249
|
+
<RawBinding extends BrowserRenderingBinding = BrowserRenderingBinding>(
|
|
250
|
+
definition: BrowserRenderingDefinition,
|
|
251
|
+
) =>
|
|
252
|
+
(binding: RawBinding): BrowserRenderingClient<RawBinding> => ({
|
|
253
|
+
definition,
|
|
254
|
+
launchWith: (launch, options) =>
|
|
255
|
+
tryBrowserRenderingPromise(definition.binding, "launch", () => launch(binding, options)).pipe(
|
|
256
|
+
Effect.map((browser) => wrapBrowser(definition.binding, browser)),
|
|
257
|
+
),
|
|
258
|
+
connectWith: (connect, options) =>
|
|
259
|
+
tryBrowserRenderingPromise(definition.binding, "connect", () =>
|
|
260
|
+
connect(binding, options),
|
|
261
|
+
).pipe(Effect.map((browser) => wrapBrowser(definition.binding, browser))),
|
|
262
|
+
unsafeRaw: Effect.succeed(binding),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
export const layer = <Self>(
|
|
266
|
+
tag: Context.Service<Self, BrowserRenderingClient>,
|
|
267
|
+
definition: BrowserRenderingDefinition,
|
|
268
|
+
) =>
|
|
269
|
+
Binding.layer(tag, definition.binding, isBrowserRenderingBinding, makeClient(definition), {
|
|
270
|
+
expected: expectedBrowserRenderingBinding,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
export const make = <Id extends string>(id: Id) => Tag<BrowserRenderingService<Id>>()<Id>(id);
|
|
274
|
+
|
|
275
|
+
export const Tag =
|
|
276
|
+
<Self>() =>
|
|
277
|
+
<Id extends string>(id: Id) => {
|
|
278
|
+
const tag = Context.Service<Self, BrowserRenderingClient>()(id);
|
|
279
|
+
|
|
280
|
+
const makeLayer = (definition: LayerOptions) => layer(tag, definition);
|
|
281
|
+
|
|
282
|
+
return Object.assign(tag, {
|
|
283
|
+
id,
|
|
284
|
+
layer: makeLayer,
|
|
285
|
+
}) as TagClass<Self, Id>;
|
|
286
|
+
};
|