effect-cf 0.27.0 → 0.28.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 +10 -0
- package/dist/{Artifacts-DcoXcRP5.d.mts → Artifacts-DnHQA-Ax.d.mts} +42 -25
- package/dist/{Artifacts-BhWrdyqX.mjs → Artifacts-rq3hUKQE.mjs} +46 -30
- package/dist/{Binding-COCtoJXa.d.mts → Binding-xJJJPmiD.d.mts} +6 -5
- package/dist/ComputerArtifacts.d.mts +1 -1
- package/dist/ComputerArtifacts.mjs +9 -9
- package/dist/ComputerWorkspace.d.mts +5 -5
- package/dist/ComputerWorkspace.mjs +107 -75
- package/dist/ComputerWorkspaceHost.d.mts +1 -1
- package/dist/ComputerWorkspaceHost.mjs +18 -16
- package/dist/{DurableObjectState-biHrHwrh.d.mts → DurableObjectState-B3uFY1eM.d.mts} +85 -62
- package/dist/{DurableObjectState-Dux8KfjG.mjs → DurableObjectState-BjkK2kwH.mjs} +3 -3
- package/dist/{ErrorMessage-_lg53p3W.mjs → ErrorMessage-BM0K-Nik.mjs} +1 -4
- package/dist/{Hyperdrive-6p59yyVk.d.mts → Hyperdrive-z-9fFz15.d.mts} +2 -2
- package/dist/HyperdrivePg.d.mts +2 -2
- package/dist/Vitest.d.mts +6 -5
- package/dist/Vitest.mjs +2 -2
- package/dist/{WorkflowBinding-zlRPCXVf.d.mts → WorkflowBinding-wZ-yGHow.d.mts} +5 -10
- package/dist/index.d.mts +82 -60
- package/dist/index.mjs +268 -170
- package/package.json +1 -1
- package/src/AiGateway.ts +62 -7
- package/src/AnalyticsEngine.ts +17 -13
- package/src/Artifacts.ts +111 -47
- package/src/Binding.ts +34 -23
- package/src/BrowserRendering.ts +72 -33
- package/src/CloudflareOtlp.ts +8 -5
- package/src/ComputerArtifacts.ts +17 -9
- package/src/ComputerWorkspace.ts +202 -98
- package/src/ComputerWorkspaceHost.ts +55 -27
- package/src/ContainerNamespace.ts +8 -9
- package/src/D1.ts +8 -14
- package/src/DurableObject.ts +27 -11
- package/src/DurableObjectAlarm.ts +17 -5
- package/src/DurableObjectDefinition.ts +89 -72
- package/src/DurableObjectNamespace.ts +26 -12
- package/src/DurableObjectRpcWebSocket.ts +9 -10
- package/src/DurableObjectState.ts +1 -0
- package/src/DurableObjectStorage.ts +5 -2
- package/src/DurableObjectWebSocket.ts +4 -1
- package/src/Email.ts +26 -25
- package/src/Hyperdrive.ts +4 -10
- package/src/Images.ts +12 -14
- package/src/Kv.ts +15 -17
- package/src/Queue.ts +2 -1
- package/src/QueueBinding.ts +7 -12
- package/src/QueueDefinition.ts +7 -2
- package/src/R2.ts +33 -24
- package/src/Rpc.ts +15 -21
- package/src/RpcDefinition.ts +33 -38
- package/src/ServiceBinding.ts +26 -8
- package/src/Vectorize.ts +45 -18
- package/src/Vitest.ts +9 -3
- package/src/WebTransport.ts +11 -5
- package/src/Worker.ts +91 -21
- package/src/WorkerDefinition.ts +143 -63
- package/src/WorkersAi.ts +50 -28
- package/src/Workflow.ts +66 -13
- package/src/WorkflowBinding.ts +18 -25
- package/src/WorkflowDefinition.ts +4 -2
- package/src/internal/Entrypoint.ts +29 -13
- package/src/internal/ErrorMessage.ts +7 -6
- package/src/internal/RpcInvocation.ts +34 -11
- package/src/internal/Runtime.ts +33 -22
- package/src/internal/WorkerContext.ts +3 -3
package/package.json
CHANGED
package/src/AiGateway.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Data, Effect, type Layer } from "effect";
|
|
1
|
+
import { Context, Data, Effect, type Layer, Schema as S } from "effect";
|
|
2
2
|
import type { AiGateway as CloudflareAiGateway } from "@cloudflare/workers-types";
|
|
3
3
|
|
|
4
4
|
import * as Binding from "./Binding";
|
|
@@ -37,6 +37,46 @@ export type AiGatewayProvider = NonNullable<Parameters<AiGatewayBinding["getUrl"
|
|
|
37
37
|
export type AiGatewayUniversalRequest = Exclude<AiGatewayRunData, ReadonlyArray<unknown>>;
|
|
38
38
|
export type AiGatewayHeaders = NonNullable<AiGatewayUniversalRequest["headers"]>;
|
|
39
39
|
export type AiGatewayRunOptions = Parameters<CloudflareAiGateway["run"]>[1];
|
|
40
|
+
type AiGatewayBindingCandidate = Parameters<typeof isWorkersAiBinding>[0];
|
|
41
|
+
|
|
42
|
+
const AiGatewayMetadata = S.Record(S.String, S.Union([S.String, S.Number, S.Boolean, S.Null]));
|
|
43
|
+
const AiGatewayHttpMetadata = S.Union([AiGatewayMetadata, S.fromJsonString(AiGatewayMetadata)]);
|
|
44
|
+
const AiGatewayLogResponse = S.Struct({
|
|
45
|
+
success: S.Literal(true),
|
|
46
|
+
result: S.Struct({
|
|
47
|
+
id: S.String,
|
|
48
|
+
provider: S.String,
|
|
49
|
+
model: S.String,
|
|
50
|
+
model_type: S.optional(S.String),
|
|
51
|
+
path: S.String,
|
|
52
|
+
duration: S.Number,
|
|
53
|
+
request_type: S.optional(S.String),
|
|
54
|
+
request_content_type: S.optional(S.String),
|
|
55
|
+
status_code: S.Number,
|
|
56
|
+
response_content_type: S.optional(S.String),
|
|
57
|
+
success: S.Boolean,
|
|
58
|
+
cached: S.Boolean,
|
|
59
|
+
tokens_in: S.optional(S.Number),
|
|
60
|
+
tokens_out: S.optional(S.Number),
|
|
61
|
+
metadata: S.optional(AiGatewayHttpMetadata),
|
|
62
|
+
step: S.optional(S.Number),
|
|
63
|
+
cost: S.optional(S.Number),
|
|
64
|
+
custom_cost: S.optional(S.Boolean),
|
|
65
|
+
request_size: S.Number,
|
|
66
|
+
request_head: S.optional(S.String),
|
|
67
|
+
request_head_complete: S.Boolean,
|
|
68
|
+
response_size: S.Number,
|
|
69
|
+
response_head: S.optional(S.String),
|
|
70
|
+
response_head_complete: S.Boolean,
|
|
71
|
+
created_at: S.DateFromString,
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
const AiGatewayPatchLogResponse = S.Struct({
|
|
75
|
+
success: S.Literal(true),
|
|
76
|
+
result: S.Unknown,
|
|
77
|
+
});
|
|
78
|
+
const decodeAiGatewayLogResponse = S.decodeUnknownPromise(AiGatewayLogResponse);
|
|
79
|
+
const decodeAiGatewayPatchLogResponse = S.decodeUnknownPromise(AiGatewayPatchLogResponse);
|
|
40
80
|
|
|
41
81
|
export interface AiGatewayFetchOptions {
|
|
42
82
|
readonly provider?: AiGatewayProvider;
|
|
@@ -131,7 +171,13 @@ const providerUrl = (baseUrl: string, path = "") => {
|
|
|
131
171
|
const gatewayUrl = (definition: { readonly accountId: string; readonly gatewayId: string }) =>
|
|
132
172
|
`https://gateway.ai.cloudflare.com/v1/${definition.accountId}/${definition.gatewayId}/`;
|
|
133
173
|
|
|
134
|
-
|
|
174
|
+
const ensureSuccessfulResponse = (response: Response): void => {
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
throw new Error(`AI Gateway HTTP request failed with status ${response.status}`);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const isAiGatewayBinding = (value: AiGatewayBindingCandidate): value is WorkersAiBinding =>
|
|
135
181
|
isWorkersAiBinding(value);
|
|
136
182
|
|
|
137
183
|
export const makeClient = (definition: AiGatewayDefinition, gateway: AiGatewayBinding) =>
|
|
@@ -144,6 +190,7 @@ export const makeClient = (definition: AiGatewayDefinition, gateway: AiGatewayBi
|
|
|
144
190
|
) =>
|
|
145
191
|
tryAiGatewayPromise(definition.binding, "run", () =>
|
|
146
192
|
gateway.run(
|
|
193
|
+
// SAFETY: spreading the readonly batch produces the mutable array accepted by the binding.
|
|
147
194
|
(Array.isArray(data) ? [...data] : data) as
|
|
148
195
|
| AiGatewayUniversalRequest
|
|
149
196
|
| Array<AiGatewayUniversalRequest>,
|
|
@@ -225,6 +272,7 @@ export const makeHttpClient = (
|
|
|
225
272
|
Array.isArray(options.extraHeaders) ||
|
|
226
273
|
options.extraHeaders instanceof Headers
|
|
227
274
|
) {
|
|
275
|
+
// SAFETY: the preceding branches retain only HeadersInit-compatible header containers.
|
|
228
276
|
return options?.extraHeaders as HeadersInit | undefined;
|
|
229
277
|
}
|
|
230
278
|
|
|
@@ -266,14 +314,17 @@ export const makeHttpClient = (
|
|
|
266
314
|
),
|
|
267
315
|
),
|
|
268
316
|
patchLog: Effect.fn("AiGateway.patchLog")((logId: string, data: AiGatewayPatchLog) =>
|
|
269
|
-
tryAiGatewayPromise(binding, "patchLog", (signal) =>
|
|
270
|
-
request(providerUrl(gatewayUrl(definition), `logs/${logId}`).href, {
|
|
317
|
+
tryAiGatewayPromise(binding, "patchLog", async (signal) => {
|
|
318
|
+
const response = await request(providerUrl(gatewayUrl(definition), `logs/${logId}`).href, {
|
|
271
319
|
method: "PATCH",
|
|
272
320
|
body: JSON.stringify(data),
|
|
273
321
|
headers: { "content-type": "application/json" },
|
|
274
322
|
signal,
|
|
275
|
-
})
|
|
276
|
-
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
ensureSuccessfulResponse(response);
|
|
326
|
+
await decodeAiGatewayPatchLogResponse(await response.json());
|
|
327
|
+
}),
|
|
277
328
|
),
|
|
278
329
|
getLog: Effect.fn("AiGateway.getLog")((logId: string) =>
|
|
279
330
|
tryAiGatewayPromise(binding, "getLog", async (signal) => {
|
|
@@ -281,7 +332,10 @@ export const makeHttpClient = (
|
|
|
281
332
|
signal,
|
|
282
333
|
});
|
|
283
334
|
|
|
284
|
-
|
|
335
|
+
ensureSuccessfulResponse(response);
|
|
336
|
+
const payload = await decodeAiGatewayLogResponse(await response.json());
|
|
337
|
+
|
|
338
|
+
return payload.result;
|
|
285
339
|
}),
|
|
286
340
|
),
|
|
287
341
|
rawUnsafe: Effect.fail(
|
|
@@ -315,6 +369,7 @@ export const Tag =
|
|
|
315
369
|
|
|
316
370
|
const makeLayer = (definition: LayerOptions) => layer(tag, definition);
|
|
317
371
|
|
|
372
|
+
// SAFETY: these are exactly the members required by TagClass, attached to the matching service tag.
|
|
318
373
|
return Object.assign(tag, {
|
|
319
374
|
id,
|
|
320
375
|
layer: makeLayer,
|
package/src/AnalyticsEngine.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Effect,
|
|
11
11
|
Layer,
|
|
12
12
|
Option,
|
|
13
|
+
Predicate,
|
|
13
14
|
Result,
|
|
14
15
|
Schema as S,
|
|
15
16
|
} from "effect";
|
|
@@ -321,7 +322,7 @@ const byteLength = (value: AnalyticsEngineFieldValue) => {
|
|
|
321
322
|
return 0;
|
|
322
323
|
}
|
|
323
324
|
|
|
324
|
-
return
|
|
325
|
+
return Predicate.isString(value) ? textEncoder.encode(value).byteLength : value.byteLength;
|
|
325
326
|
};
|
|
326
327
|
|
|
327
328
|
const lengthViolation = (
|
|
@@ -420,11 +421,15 @@ const schemaViolation = (
|
|
|
420
421
|
|
|
421
422
|
const toAnalyticsEngineDataPoint = (
|
|
422
423
|
dataPoint: S.Schema.Type<typeof AnalyticsEngineDataPointSchema>,
|
|
423
|
-
): AnalyticsEngineDataPoint =>
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
424
|
+
): AnalyticsEngineDataPoint => {
|
|
425
|
+
const result: AnalyticsEngineDataPoint = {};
|
|
426
|
+
|
|
427
|
+
if (dataPoint.indexes !== undefined) result.indexes = [...dataPoint.indexes];
|
|
428
|
+
if (dataPoint.doubles !== undefined) result.doubles = [...dataPoint.doubles];
|
|
429
|
+
if (dataPoint.blobs !== undefined) result.blobs = [...dataPoint.blobs];
|
|
430
|
+
|
|
431
|
+
return result;
|
|
432
|
+
};
|
|
428
433
|
|
|
429
434
|
const validateDataPoint = (
|
|
430
435
|
binding: string,
|
|
@@ -678,13 +683,10 @@ const makeQueryClientWith = (
|
|
|
678
683
|
export const makeQueryClient = (definition: AnalyticsEngineQueryDefinition) =>
|
|
679
684
|
Effect.map(HttpClient.HttpClient, (httpClient) => makeQueryClientWith(definition, httpClient));
|
|
680
685
|
|
|
681
|
-
export const isAnalyticsEngineDataset = (
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
return typeof Reflect.get(value, "writeDataPoint") === "function";
|
|
687
|
-
};
|
|
686
|
+
export const isAnalyticsEngineDataset = <Candidate>(
|
|
687
|
+
value: Candidate,
|
|
688
|
+
): value is Candidate & AnalyticsEngineBinding =>
|
|
689
|
+
Predicate.hasProperty(value, "writeDataPoint") && Predicate.isFunction(value.writeDataPoint);
|
|
688
690
|
|
|
689
691
|
export const makeClient =
|
|
690
692
|
(definition: AnalyticsEngineDefinition, defaults?: AnalyticsEngineWritePolicy) =>
|
|
@@ -793,6 +795,7 @@ export const Tag =
|
|
|
793
795
|
|
|
794
796
|
const makeLayer = (definition: LayerOptions) => layer(tag, definition);
|
|
795
797
|
|
|
798
|
+
// SAFETY: these are exactly the members required by TagClass, attached to the matching service tag.
|
|
796
799
|
return Object.assign(tag, {
|
|
797
800
|
id,
|
|
798
801
|
layer: makeLayer,
|
|
@@ -811,6 +814,7 @@ export const QueryTag =
|
|
|
811
814
|
const makeLayerFetchConfig = (config?: Config.Config<AnalyticsEngineQueryDefinition>) =>
|
|
812
815
|
layerFetchConfig(tag, config);
|
|
813
816
|
|
|
817
|
+
// SAFETY: these are exactly the members required by QueryTagClass, attached to the matching service tag.
|
|
814
818
|
return Object.assign(tag, {
|
|
815
819
|
id,
|
|
816
820
|
layer: makeLayer,
|
package/src/Artifacts.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
ArtifactsTokenInfo as CloudflareArtifactsTokenInfo,
|
|
7
7
|
ArtifactsTokenListResult as CloudflareArtifactsTokenListResult,
|
|
8
8
|
} from "@cloudflare/workers-types";
|
|
9
|
-
import { Context, Data, Effect, type Layer } from "effect";
|
|
9
|
+
import { Context, Data, Effect, type Layer, Predicate, Schema } from "effect";
|
|
10
10
|
|
|
11
11
|
import * as Binding from "./Binding";
|
|
12
12
|
import type { WorkerEnvironment } from "./Environment";
|
|
@@ -31,6 +31,7 @@ export type ArtifactsOperation =
|
|
|
31
31
|
| "getToken"
|
|
32
32
|
| "revokeToken"
|
|
33
33
|
| "fork"
|
|
34
|
+
| "info"
|
|
34
35
|
| "log"
|
|
35
36
|
| "readCommit"
|
|
36
37
|
| "readTree"
|
|
@@ -136,34 +137,37 @@ export type ArtifactsTokenInfo = CloudflareArtifactsTokenInfo;
|
|
|
136
137
|
/** Repository token list result. */
|
|
137
138
|
export type ArtifactsTokenListResult = CloudflareArtifactsTokenListResult;
|
|
138
139
|
|
|
139
|
-
/**
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* publish its fields in the binding reference or Workers type declarations.
|
|
144
|
-
*/
|
|
145
|
-
export interface ArtifactsLogResult {
|
|
146
|
-
readonly [field: string]: unknown;
|
|
140
|
+
/** Identity recorded for a Git commit author or committer. */
|
|
141
|
+
export interface ArtifactsCommitIdentity {
|
|
142
|
+
readonly name: string;
|
|
143
|
+
readonly email: string;
|
|
147
144
|
}
|
|
148
145
|
|
|
149
|
-
/**
|
|
150
|
-
* Parsed Git commit returned by `readCommit()`.
|
|
151
|
-
*
|
|
152
|
-
* Cloudflare currently documents this generated type by name but does not
|
|
153
|
-
* publish its fields in the binding reference or Workers type declarations.
|
|
154
|
-
*/
|
|
146
|
+
/** Parsed Git commit returned by `readCommit()` and `log()`. */
|
|
155
147
|
export interface ArtifactsCommit {
|
|
156
|
-
readonly
|
|
148
|
+
readonly hash: string;
|
|
149
|
+
readonly treeHash: string;
|
|
150
|
+
readonly message: string;
|
|
151
|
+
readonly author: ArtifactsCommitIdentity;
|
|
152
|
+
readonly committer: ArtifactsCommitIdentity;
|
|
153
|
+
readonly parents: ReadonlyArray<string>;
|
|
154
|
+
/** Unix timestamp in seconds. */
|
|
155
|
+
readonly authoredAt: number;
|
|
156
|
+
/** Unix timestamp in seconds. */
|
|
157
|
+
readonly committedAt: number;
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
/** Bare commit array returned by `log()`. */
|
|
161
|
+
export type ArtifactsLogResult = ReadonlyArray<ArtifactsCommit>;
|
|
162
|
+
|
|
159
163
|
/**
|
|
160
164
|
* Parsed Git tree returned by `readTree()`.
|
|
161
165
|
*
|
|
162
166
|
* Cloudflare currently documents this generated type by name but does not
|
|
163
167
|
* publish its fields in the binding reference or Workers type declarations.
|
|
164
168
|
*/
|
|
165
|
-
export interface ArtifactsTree {
|
|
166
|
-
readonly [field: string]:
|
|
169
|
+
export interface ArtifactsTree<Field = unknown> {
|
|
170
|
+
readonly [field: string]: Field;
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
/** Options for `Artifacts.create()`. */
|
|
@@ -228,7 +232,15 @@ export interface ArtifactsLogOptions {
|
|
|
228
232
|
}
|
|
229
233
|
|
|
230
234
|
/** Runtime repository handle, including methods absent from current shipped Workers types. */
|
|
231
|
-
export interface ArtifactsRepoBinding extends ArtifactsRepoInfo {
|
|
235
|
+
export interface ArtifactsRepoBinding extends Partial<ArtifactsRepoInfo> {
|
|
236
|
+
/**
|
|
237
|
+
* Read repository metadata.
|
|
238
|
+
*
|
|
239
|
+
* Remote bindings expose method-only RPC handles and provide metadata only
|
|
240
|
+
* through this method. It is optional solely for compatibility with older
|
|
241
|
+
* local handles that expose metadata as data properties.
|
|
242
|
+
*/
|
|
243
|
+
readonly info?: () => Promise<ArtifactsRepoInfo>;
|
|
232
244
|
/**
|
|
233
245
|
* Create a Git token for the repository.
|
|
234
246
|
*
|
|
@@ -262,8 +274,15 @@ export interface ArtifactsBinding {
|
|
|
262
274
|
readonly delete: (name: RepoName) => Promise<boolean>;
|
|
263
275
|
}
|
|
264
276
|
|
|
265
|
-
/**
|
|
277
|
+
/**
|
|
278
|
+
* Effect wrapper around a repository handle returned by `Artifacts.get()`.
|
|
279
|
+
*
|
|
280
|
+
* The inherited metadata is the snapshot read while resolving the client.
|
|
281
|
+
* Use `info()` to refresh it from the repository handle.
|
|
282
|
+
*/
|
|
266
283
|
export interface ArtifactsRepoClient extends ArtifactsRepoInfo {
|
|
284
|
+
/** Read and validate the repository's current metadata. */
|
|
285
|
+
readonly info: () => Effect.Effect<ArtifactsRepoInfo, ArtifactsOperationError>;
|
|
267
286
|
/**
|
|
268
287
|
* Create a Git token for the repository.
|
|
269
288
|
*
|
|
@@ -319,6 +338,11 @@ export type LayerOptions = {
|
|
|
319
338
|
readonly binding: string;
|
|
320
339
|
};
|
|
321
340
|
|
|
341
|
+
interface MutableArtifactsErrorDetails {
|
|
342
|
+
code?: ArtifactsOperationError["code"];
|
|
343
|
+
numericCode?: number;
|
|
344
|
+
}
|
|
345
|
+
|
|
322
346
|
export interface TagClass<Self, Id extends string> extends Context.ServiceClass<
|
|
323
347
|
Self,
|
|
324
348
|
Id,
|
|
@@ -337,17 +361,18 @@ export interface TagClass<Self, Id extends string> extends Context.ServiceClass<
|
|
|
337
361
|
const artifactsErrorDetails = (
|
|
338
362
|
cause: unknown,
|
|
339
363
|
): Pick<ArtifactsOperationError, "code" | "numericCode"> => {
|
|
340
|
-
if (
|
|
364
|
+
if (!Predicate.isObject(cause)) {
|
|
341
365
|
return {};
|
|
342
366
|
}
|
|
343
367
|
|
|
344
|
-
const code =
|
|
345
|
-
const numericCode =
|
|
368
|
+
const code = Predicate.hasProperty(cause, "code") ? cause.code : undefined;
|
|
369
|
+
const numericCode = Predicate.hasProperty(cause, "numericCode") ? cause.numericCode : undefined;
|
|
370
|
+
const details: MutableArtifactsErrorDetails = {};
|
|
346
371
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
372
|
+
if (Predicate.isString(code)) details.code = code;
|
|
373
|
+
if (Predicate.isNumber(numericCode)) details.numericCode = numericCode;
|
|
374
|
+
|
|
375
|
+
return details;
|
|
351
376
|
};
|
|
352
377
|
|
|
353
378
|
const artifactsError = (binding: string, operation: ArtifactsOperation, cause: unknown) =>
|
|
@@ -372,13 +397,53 @@ const spanOptions = (binding: string, operation: ArtifactsOperation) => ({
|
|
|
372
397
|
attributes: { binding, operation },
|
|
373
398
|
});
|
|
374
399
|
|
|
375
|
-
const hasFunction =
|
|
376
|
-
|
|
400
|
+
const hasFunction = <Candidate, Key extends string>(
|
|
401
|
+
value: Candidate,
|
|
402
|
+
key: Key,
|
|
403
|
+
): value is Candidate & Record<Key, (...args: ReadonlyArray<any>) => any> =>
|
|
404
|
+
Predicate.hasProperty(value, key) && Predicate.isFunction(value[key]);
|
|
405
|
+
|
|
406
|
+
const ArtifactsRepoInfoSchema = Schema.Struct({
|
|
407
|
+
id: Schema.String,
|
|
408
|
+
name: Schema.String,
|
|
409
|
+
description: Schema.NullOr(Schema.String),
|
|
410
|
+
defaultBranch: Schema.String,
|
|
411
|
+
createdAt: Schema.String,
|
|
412
|
+
updatedAt: Schema.String,
|
|
413
|
+
lastPushAt: Schema.NullOr(Schema.String),
|
|
414
|
+
source: Schema.NullOr(Schema.String),
|
|
415
|
+
readOnly: Schema.Boolean,
|
|
416
|
+
remote: Schema.String,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const decodeRepoInfo = (
|
|
420
|
+
binding: string,
|
|
421
|
+
value: ArtifactsRepoInfo | ArtifactsRepoBinding,
|
|
422
|
+
): Effect.Effect<ArtifactsRepoInfo, ArtifactsOperationError> =>
|
|
423
|
+
Schema.decodeUnknownEffect(ArtifactsRepoInfoSchema)(value).pipe(
|
|
424
|
+
Effect.mapError((cause) => artifactsError(binding, "info", cause)),
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const readRepoInfo = (
|
|
428
|
+
binding: string,
|
|
429
|
+
repo: ArtifactsRepoBinding,
|
|
430
|
+
): Effect.Effect<ArtifactsRepoInfo, ArtifactsOperationError> => {
|
|
431
|
+
if (hasFunction(repo, "info")) {
|
|
432
|
+
return tryArtifactsPromise(binding, "info", () => repo.info()).pipe(
|
|
433
|
+
Effect.flatMap((info) => decodeRepoInfo(binding, info)),
|
|
434
|
+
Effect.withSpan("Artifacts.info", spanOptions(binding, "info")),
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return decodeRepoInfo(binding, repo).pipe(
|
|
439
|
+
Effect.withSpan("Artifacts.info", spanOptions(binding, "info")),
|
|
440
|
+
);
|
|
441
|
+
};
|
|
377
442
|
|
|
378
443
|
/** Tests whether a value exposes the complete documented Artifacts repo handle API. */
|
|
379
|
-
export const isArtifactsRepoBinding = (
|
|
380
|
-
|
|
381
|
-
|
|
444
|
+
export const isArtifactsRepoBinding = <Candidate>(
|
|
445
|
+
value: Candidate,
|
|
446
|
+
): value is Candidate & ArtifactsRepoBinding =>
|
|
382
447
|
hasFunction(value, "createToken") &&
|
|
383
448
|
hasFunction(value, "listTokens") &&
|
|
384
449
|
hasFunction(value, "revokeToken") &&
|
|
@@ -388,26 +453,22 @@ export const isArtifactsRepoBinding = (value: unknown): value is ArtifactsRepoBi
|
|
|
388
453
|
hasFunction(value, "readTree");
|
|
389
454
|
|
|
390
455
|
/** Tests whether a value exposes the complete documented Artifacts namespace API. */
|
|
391
|
-
export const isArtifactsBinding = (
|
|
392
|
-
|
|
393
|
-
|
|
456
|
+
export const isArtifactsBinding = <Candidate>(
|
|
457
|
+
value: Candidate,
|
|
458
|
+
): value is Candidate & ArtifactsBinding =>
|
|
394
459
|
hasFunction(value, "create") &&
|
|
395
460
|
hasFunction(value, "get") &&
|
|
396
461
|
hasFunction(value, "import") &&
|
|
397
462
|
hasFunction(value, "list") &&
|
|
398
463
|
hasFunction(value, "delete");
|
|
399
464
|
|
|
400
|
-
const wrapRepo = (
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
lastPushAt: repo.lastPushAt,
|
|
408
|
-
source: repo.source,
|
|
409
|
-
readOnly: repo.readOnly,
|
|
410
|
-
remote: repo.remote,
|
|
465
|
+
const wrapRepo = (
|
|
466
|
+
binding: string,
|
|
467
|
+
repo: ArtifactsRepoBinding,
|
|
468
|
+
info: ArtifactsRepoInfo,
|
|
469
|
+
): ArtifactsRepoClient => ({
|
|
470
|
+
...info,
|
|
471
|
+
info: () => readRepoInfo(binding, repo),
|
|
411
472
|
createToken: Effect.fn(
|
|
412
473
|
"Artifacts.createToken",
|
|
413
474
|
spanOptions(binding, "createToken"),
|
|
@@ -463,7 +524,9 @@ export const makeClient =
|
|
|
463
524
|
tryArtifactsPromise(definition.binding, "get", () => artifacts.get(name)).pipe(
|
|
464
525
|
Effect.flatMap((repo) =>
|
|
465
526
|
isArtifactsRepoBinding(repo)
|
|
466
|
-
?
|
|
527
|
+
? readRepoInfo(definition.binding, repo).pipe(
|
|
528
|
+
Effect.map((info) => wrapRepo(definition.binding, repo, info)),
|
|
529
|
+
)
|
|
467
530
|
: Effect.fail(
|
|
468
531
|
artifactsError(
|
|
469
532
|
definition.binding,
|
|
@@ -512,6 +575,7 @@ export const Tag =
|
|
|
512
575
|
|
|
513
576
|
const makeLayer = (definition: LayerOptions) => layer(tag, definition);
|
|
514
577
|
|
|
578
|
+
// SAFETY: these are exactly the members required by TagClass, attached to the matching service tag.
|
|
515
579
|
return Object.assign(tag, {
|
|
516
580
|
id,
|
|
517
581
|
layer: makeLayer,
|
package/src/Binding.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Data, Effect, Layer } from "effect";
|
|
1
|
+
import { Context, Data, Effect, Layer, Predicate } from "effect";
|
|
2
2
|
|
|
3
3
|
import { WorkerEnvironment, type WorkerEnv } from "./Environment";
|
|
4
4
|
|
|
@@ -28,22 +28,25 @@ export interface ValidationOptions {
|
|
|
28
28
|
|
|
29
29
|
const defaultExpected = "Cloudflare binding resource";
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
type BindingCandidate = Parameters<typeof Predicate.isUnknown>[0];
|
|
32
|
+
type PropertyTarget = { readonly constructor?: Function };
|
|
33
33
|
|
|
34
|
-
const
|
|
34
|
+
const isPropertyTarget = (value: BindingCandidate): value is PropertyTarget =>
|
|
35
|
+
Predicate.isObjectOrArray(value) || Predicate.isFunction(value);
|
|
36
|
+
|
|
37
|
+
const getObjectName = (value: PropertyTarget): string => {
|
|
35
38
|
const tag = (() => {
|
|
36
39
|
try {
|
|
37
40
|
return Object.prototype.toString.call(value).slice("[object ".length, -1);
|
|
38
41
|
} catch {
|
|
39
|
-
return
|
|
42
|
+
return Predicate.isFunction(value) ? "function" : "object";
|
|
40
43
|
}
|
|
41
44
|
})();
|
|
42
45
|
const constructorName = (() => {
|
|
43
46
|
try {
|
|
44
47
|
return "constructor" in value &&
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
Predicate.isFunction(value.constructor) &&
|
|
49
|
+
Predicate.isString(value.constructor.name)
|
|
47
50
|
? value.constructor.name
|
|
48
51
|
: undefined;
|
|
49
52
|
} catch {
|
|
@@ -62,7 +65,7 @@ const getObjectName = (value: object | Function): string => {
|
|
|
62
65
|
return tag;
|
|
63
66
|
};
|
|
64
67
|
|
|
65
|
-
const propertyNames = (value:
|
|
68
|
+
const propertyNames = (value: PropertyTarget): ReadonlyArray<string> => {
|
|
66
69
|
const names = new Set<string>();
|
|
67
70
|
|
|
68
71
|
for (const target of [value, Object.getPrototypeOf(value)] as const) {
|
|
@@ -82,21 +85,27 @@ const propertyNames = (value: object | Function): ReadonlyArray<string> => {
|
|
|
82
85
|
return [...names].filter((name) => name !== "constructor").sort();
|
|
83
86
|
};
|
|
84
87
|
|
|
85
|
-
const isMethod = (value:
|
|
88
|
+
const isMethod = (value: PropertyTarget, name: string): boolean => {
|
|
86
89
|
try {
|
|
87
|
-
return
|
|
90
|
+
return Predicate.hasProperty(value, name) && Predicate.isFunction(value[name]);
|
|
88
91
|
} catch {
|
|
89
92
|
return false;
|
|
90
93
|
}
|
|
91
94
|
};
|
|
92
95
|
|
|
93
|
-
const describeActual = (value:
|
|
96
|
+
const describeActual = (value: BindingCandidate): string => {
|
|
94
97
|
if (value === null) {
|
|
95
98
|
return "null";
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
if (
|
|
99
|
-
return
|
|
101
|
+
if (!isPropertyTarget(value)) {
|
|
102
|
+
if (Predicate.isString(value)) return "string";
|
|
103
|
+
if (Predicate.isNumber(value)) return "number";
|
|
104
|
+
if (Predicate.isBoolean(value)) return "boolean";
|
|
105
|
+
if (Predicate.isBigInt(value)) return "bigint";
|
|
106
|
+
if (Predicate.isSymbol(value)) return "symbol";
|
|
107
|
+
|
|
108
|
+
return "undefined";
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
const names = propertyNames(value);
|
|
@@ -117,7 +126,7 @@ const describeActual = (value: unknown): string => {
|
|
|
117
126
|
const getBinding = <Resource>(
|
|
118
127
|
env: WorkerEnv,
|
|
119
128
|
binding: string,
|
|
120
|
-
isResource: (value:
|
|
129
|
+
isResource: (value: BindingCandidate) => value is Resource,
|
|
121
130
|
options?: ValidationOptions,
|
|
122
131
|
): Effect.Effect<Resource, BindingNotFoundError | BindingValidationError> =>
|
|
123
132
|
Effect.gen(function* () {
|
|
@@ -134,7 +143,7 @@ const getBinding = <Resource>(
|
|
|
134
143
|
);
|
|
135
144
|
}
|
|
136
145
|
|
|
137
|
-
const resource =
|
|
146
|
+
const resource = Predicate.hasProperty(env, binding) ? env[binding] : undefined;
|
|
138
147
|
|
|
139
148
|
if (resource === undefined) {
|
|
140
149
|
return yield* Effect.fail(
|
|
@@ -191,7 +200,7 @@ export interface BindingService<Self, Id extends string, Service> extends Contex
|
|
|
191
200
|
const makeBindingLayer = <Self, Resource, Service>(
|
|
192
201
|
tag: Context.Service<Self, Service>,
|
|
193
202
|
binding: string,
|
|
194
|
-
isResource: (value:
|
|
203
|
+
isResource: (value: BindingCandidate) => value is Resource,
|
|
195
204
|
wrap: ((resource: Resource) => Service) | undefined,
|
|
196
205
|
options: ValidationOptions | undefined,
|
|
197
206
|
): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment> =>
|
|
@@ -201,28 +210,29 @@ const makeBindingLayer = <Self, Resource, Service>(
|
|
|
201
210
|
const env = yield* WorkerEnvironment;
|
|
202
211
|
const resource = yield* getBinding(env, binding, isResource, options);
|
|
203
212
|
|
|
204
|
-
|
|
213
|
+
// SAFETY: the overload without wrap fixes Service to Resource; the other branch invokes wrap.
|
|
214
|
+
return wrap === undefined ? (resource as Resource & Service) : wrap(resource);
|
|
205
215
|
}),
|
|
206
216
|
);
|
|
207
217
|
|
|
208
218
|
export function layer<Self, Resource>(
|
|
209
219
|
tag: Context.Service<Self, Resource>,
|
|
210
220
|
binding: string,
|
|
211
|
-
isResource: (value:
|
|
221
|
+
isResource: (value: BindingCandidate) => value is Resource,
|
|
212
222
|
wrap?: undefined,
|
|
213
223
|
options?: ValidationOptions,
|
|
214
224
|
): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
215
225
|
export function layer<Self, Resource, Service>(
|
|
216
226
|
tag: Context.Service<Self, Service>,
|
|
217
227
|
binding: string,
|
|
218
|
-
isResource: (value:
|
|
228
|
+
isResource: (value: BindingCandidate) => value is Resource,
|
|
219
229
|
wrap: (resource: Resource) => Service,
|
|
220
230
|
options?: ValidationOptions,
|
|
221
231
|
): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
222
232
|
export function layer<Self, Resource, Service = Resource>(
|
|
223
233
|
tag: Context.Service<Self, Service>,
|
|
224
234
|
binding: string,
|
|
225
|
-
isResource: (value:
|
|
235
|
+
isResource: (value: BindingCandidate) => value is Resource,
|
|
226
236
|
wrap?: (resource: Resource) => Service,
|
|
227
237
|
options?: ValidationOptions,
|
|
228
238
|
): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment> {
|
|
@@ -233,27 +243,28 @@ export const Service = <Self>() => {
|
|
|
233
243
|
function makeService<Id extends string, Resource>(
|
|
234
244
|
id: Id,
|
|
235
245
|
binding: string,
|
|
236
|
-
isResource: (value:
|
|
246
|
+
isResource: (value: BindingCandidate) => value is Resource,
|
|
237
247
|
wrap?: undefined,
|
|
238
248
|
options?: ValidationOptions,
|
|
239
249
|
): BindingService<Self, Id, Resource>;
|
|
240
250
|
function makeService<Id extends string, Resource, Service>(
|
|
241
251
|
id: Id,
|
|
242
252
|
binding: string,
|
|
243
|
-
isResource: (value:
|
|
253
|
+
isResource: (value: BindingCandidate) => value is Resource,
|
|
244
254
|
wrap: (resource: Resource) => Service,
|
|
245
255
|
options?: ValidationOptions,
|
|
246
256
|
): BindingService<Self, Id, Service>;
|
|
247
257
|
function makeService<Id extends string, Resource, Service = Resource>(
|
|
248
258
|
id: Id,
|
|
249
259
|
binding: string,
|
|
250
|
-
isResource: (value:
|
|
260
|
+
isResource: (value: BindingCandidate) => value is Resource,
|
|
251
261
|
wrap?: (resource: Resource) => Service,
|
|
252
262
|
options?: ValidationOptions,
|
|
253
263
|
): BindingService<Self, Id, Service> {
|
|
254
264
|
const tag = Context.Service<Self, Service>()(`effect-cf/Binding/${id}` as const);
|
|
255
265
|
const serviceLayer = makeBindingLayer(tag, binding, isResource, wrap, options);
|
|
256
266
|
|
|
267
|
+
// SAFETY: the assigned metadata and layer exactly implement BindingService for this tag.
|
|
257
268
|
return Object.assign(tag, {
|
|
258
269
|
[TypeId]: TypeId,
|
|
259
270
|
id,
|