effect-cf 0.34.0 → 0.35.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/ComputerWorkspace.d.mts +11 -3
- package/dist/ComputerWorkspace.mjs +31 -9
- package/dist/{DurableObjectState-De4ZojhY.mjs → DurableObjectState-DcqElJ4e.mjs} +81 -50
- package/dist/{DurableObjectState-CrEunfQ-.d.mts → DurableObjectState-DpYdNZgT.d.mts} +35 -29
- package/dist/Vitest.d.mts +2 -2
- package/dist/Vitest.mjs +7 -10
- package/dist/{WorkflowBinding-BumVQTuW.d.mts → WorkflowBinding-Cn3AuUyP.d.mts} +1 -1
- package/dist/index.d.mts +12 -4
- package/dist/index.mjs +102 -77
- package/package.json +1 -1
- package/src/AnalyticsEngine.ts +8 -1
- package/src/CloudflareOtlp.ts +6 -3
- package/src/ComputerWorkspace.ts +53 -26
- package/src/DurableObject.ts +8 -9
- package/src/DurableObjectNamespace.ts +33 -5
- package/src/DurableObjectRpcWebSocket.ts +2 -27
- package/src/DurableObjectState.ts +18 -20
- package/src/DurableObjectStorage.ts +53 -35
- package/src/DurableObjectWebSocket.ts +23 -41
- package/src/RpcDefinition.ts +5 -15
- package/src/ServiceBinding.ts +37 -5
- package/src/Vitest.ts +18 -15
- package/src/Worker.ts +107 -54
- package/src/WorkersAi.ts +21 -14
- package/src/Workflow.ts +45 -49
- package/src/internal/NativeCallback.ts +55 -0
- package/src/internal/WorkerContext.ts +5 -3
package/dist/index.mjs
CHANGED
|
@@ -2,9 +2,9 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
|
2
2
|
import { a as WorkerEnvironment, i as WorkerConfig, n as violationsMessage, r as Environment_exports, t as causeMessage } from "./ErrorMessage-Byx2OgNd.mjs";
|
|
3
3
|
import { n as Service$2, r as layer$19, t as Binding_exports } from "./Binding-C82jRhdr.mjs";
|
|
4
4
|
import { n as Artifacts_exports } from "./Artifacts-CBxWGeZB.mjs";
|
|
5
|
-
import { a as DurableObjectWebSocket_exports, c as DurableObjectStorage_exports, i as fromExecutionContext, n as DurableObjectState_exports, o as DurableWebSocketAttachmentError, r as fromDurableObjectState, s as fromWebSocket, t as DurableObjectState } from "./DurableObjectState-
|
|
6
|
-
import { Cause, Clock, Config, ConfigProvider, Context, Data, DateTime, Duration, Effect, Exit, Function as Function$1, Layer, ManagedRuntime, Option, Predicate, Queue, References, Result, Schema, Tracer } from "effect";
|
|
7
|
-
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
5
|
+
import { a as DurableObjectWebSocket_exports, c as DurableObjectStorage_exports, i as fromExecutionContext, n as DurableObjectState_exports, o as DurableWebSocketAttachmentError, r as fromDurableObjectState, s as fromWebSocket, t as DurableObjectState } from "./DurableObjectState-DcqElJ4e.mjs";
|
|
6
|
+
import { Cause, Clock, Config, ConfigProvider, Context, Data, DateTime, Duration, Effect, Exit, FiberSet, Function as Function$1, Layer, ManagedRuntime, Option, Predicate, Queue, References, Result, Schema, Tracer } from "effect";
|
|
7
|
+
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpEffect, HttpMiddleware, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
8
8
|
import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
|
|
9
9
|
import { D1Client } from "@effect/sql-d1";
|
|
10
10
|
import { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint, WorkflowEntrypoint } from "cloudflare:workers";
|
|
@@ -50,7 +50,7 @@ const embeddingResponse = (value) => ({
|
|
|
50
50
|
data: value.data ?? [],
|
|
51
51
|
[embeddingDimensionsKey]: value[embeddingDimensionsKey] ?? []
|
|
52
52
|
});
|
|
53
|
-
const decodeEmbeddingResponse = Schema.
|
|
53
|
+
const decodeEmbeddingResponse = Schema.decodeUnknownEffect(Schema.Struct({
|
|
54
54
|
data: Schema.optional(Schema.Array(Schema.Array(Schema.Number))),
|
|
55
55
|
[embeddingDimensionsKey]: Schema.optional(Schema.Array(Schema.Number))
|
|
56
56
|
}));
|
|
@@ -62,11 +62,14 @@ const makeClient$15 = (definition) => (ai) => {
|
|
|
62
62
|
definition,
|
|
63
63
|
aiGatewayLogId: tryWorkersAiSync(definition.binding, "aiGatewayLogId", () => ai.aiGatewayLogId),
|
|
64
64
|
run,
|
|
65
|
-
runEmbedding: Effect.fn("WorkersAi.runEmbedding")((model, input, options)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
runEmbedding: Effect.fn("WorkersAi.runEmbedding")(function* (model, input, options) {
|
|
66
|
+
const response = yield* run(model, input, options);
|
|
67
|
+
const decoded = yield* decodeEmbeddingResponse(response).pipe(Effect.mapError((cause) => new WorkersAiOperationError({
|
|
68
|
+
binding: definition.binding,
|
|
69
|
+
operation: "runEmbedding",
|
|
70
|
+
cause
|
|
69
71
|
})));
|
|
72
|
+
return embeddingResponse(decoded);
|
|
70
73
|
}),
|
|
71
74
|
models: Effect.fn("WorkersAi.models")((params) => tryWorkersAiPromise(definition.binding, "models", () => ai.models(params))),
|
|
72
75
|
gateway: (gatewayId) => tryWorkersAiSync(definition.binding, "gateway", () => ai.gateway(gatewayId)),
|
|
@@ -413,7 +416,11 @@ const queryRequest = (definition, sql, options) => {
|
|
|
413
416
|
};
|
|
414
417
|
const responseText = (definition, response, operation) => response.text.pipe(Effect.catch((cause) => Effect.fail(analyticsEngineQueryError(definition, operation, "Failed to read Analytics Engine SQL API response body", { cause }))));
|
|
415
418
|
const executeQueryRequest = (definition, httpClient, sql, options) => Effect.gen(function* () {
|
|
416
|
-
const
|
|
419
|
+
const request = yield* Effect.try({
|
|
420
|
+
try: () => queryRequest(definition, sql, options),
|
|
421
|
+
catch: (cause) => analyticsEngineQueryError(definition, "query", "Invalid Analytics Engine SQL API URL", { cause })
|
|
422
|
+
});
|
|
423
|
+
const response = yield* httpClient.execute(request).pipe(Effect.mapError((cause) => analyticsEngineQueryError(definition, "query", "Analytics Engine SQL API request failed", { cause })));
|
|
417
424
|
if (response.status < 200 || response.status >= 300) {
|
|
418
425
|
const body = yield* responseText(definition, response, "queryErrorBody");
|
|
419
426
|
return yield* Effect.fail(analyticsEngineQueryError(definition, "query", `Analytics Engine SQL API returned HTTP ${response.status}`, {
|
|
@@ -626,9 +633,10 @@ const layer$14 = Layer.sync(CacheStorage, () => makeClient$11(caches));
|
|
|
626
633
|
//#endregion
|
|
627
634
|
//#region src/CloudflareOtlp.ts
|
|
628
635
|
/**
|
|
629
|
-
* Worker fetch/RPC and Durable Object alarm/RPC handlers schedule a
|
|
630
|
-
* flusher. It settles within two seconds;
|
|
631
|
-
* flush explicitly.
|
|
636
|
+
* Worker fetch/queue/RPC and Durable Object alarm/RPC handlers schedule a
|
|
637
|
+
* best-effort flusher. It settles within two seconds; other lifecycles must
|
|
638
|
+
* flush explicitly. Scheduled flush failures cannot fail user events. Exporter
|
|
639
|
+
* layer finalizers run separately and follow their configured shutdown timeout.
|
|
632
640
|
*/
|
|
633
641
|
var CloudflareOtlp_exports = /* @__PURE__ */ __exportAll({
|
|
634
642
|
layer: () => layer$13,
|
|
@@ -713,6 +721,8 @@ const layerProtobuf = (options = {}) => layer$13({
|
|
|
713
721
|
*
|
|
714
722
|
* Provide this layer at runtime scope for long-lived metrics aggregation, or at
|
|
715
723
|
* handler scope when traces/logs should flush as the Cloudflare event finishes.
|
|
724
|
+
* A handler-scoped exporter can delay scope closure while its own finalizer
|
|
725
|
+
* flushes; the two-second background flush limit does not bound layer shutdown.
|
|
716
726
|
*/
|
|
717
727
|
const layerWorker = (options = {}) => makeLayer(options, {
|
|
718
728
|
"cloudflare.resource_type": "worker",
|
|
@@ -1474,7 +1484,13 @@ const makeClient$8 = (definition) => {
|
|
|
1474
1484
|
return (service) => {
|
|
1475
1485
|
const fetch = Effect.fn("ServiceBinding.fetch")(function* (input, init) {
|
|
1476
1486
|
return yield* Effect.tryPromise({
|
|
1477
|
-
try: () =>
|
|
1487
|
+
try: (signal) => {
|
|
1488
|
+
const callerSignal = init?.signal !== void 0 ? init.signal : input instanceof Request ? input.signal : void 0;
|
|
1489
|
+
return service.fetch(input, {
|
|
1490
|
+
...init,
|
|
1491
|
+
signal: callerSignal === null || callerSignal === void 0 ? signal : AbortSignal.any([callerSignal, signal])
|
|
1492
|
+
});
|
|
1493
|
+
},
|
|
1478
1494
|
catch: (cause) => new ServiceBindingFetchError({
|
|
1479
1495
|
binding: definition.binding,
|
|
1480
1496
|
cause
|
|
@@ -1515,7 +1531,11 @@ const makeClient$8 = (definition) => {
|
|
|
1515
1531
|
const scopedCall = Effect.fnUntraced(function* (method, ...args) {
|
|
1516
1532
|
const methodName = String(method);
|
|
1517
1533
|
const result = yield* rpc(method, ...args);
|
|
1518
|
-
const value = yield* scoped(result)
|
|
1534
|
+
const value = yield* scoped(result).pipe(Effect.mapError((cause) => new ServiceBindingRpcError({
|
|
1535
|
+
binding: definition.binding,
|
|
1536
|
+
method: methodName,
|
|
1537
|
+
cause
|
|
1538
|
+
})));
|
|
1519
1539
|
return yield* decodeSuccess$2(methodName, value);
|
|
1520
1540
|
}, (effect, ...args) => withRpcClientSpan(effect, definition.binding, String(args[0])));
|
|
1521
1541
|
const directMethods = makeDirectMethods$1(definition.definition, call);
|
|
@@ -1674,39 +1694,42 @@ var WorkerContext = class extends Context.Service()("effect-cf/WorkerContext") {
|
|
|
1674
1694
|
var NativeRequest = class extends Context.Service()("effect-cf/NativeRequest") {};
|
|
1675
1695
|
const isWebSocketUpgrade = (request) => request.headers.get("Upgrade")?.toLowerCase() === "websocket";
|
|
1676
1696
|
const reservedMethodNames$2 = /* @__PURE__ */ new Set([
|
|
1677
|
-
|
|
1678
|
-
"dup",
|
|
1679
|
-
"fetch",
|
|
1680
|
-
"connect",
|
|
1697
|
+
...reservedMethodNames$4,
|
|
1681
1698
|
"queue",
|
|
1682
1699
|
"scheduled",
|
|
1683
1700
|
"tail",
|
|
1684
1701
|
"tailStream",
|
|
1685
1702
|
"test",
|
|
1686
|
-
"trace"
|
|
1687
|
-
"alarm",
|
|
1688
|
-
"webSocketMessage",
|
|
1689
|
-
"webSocketClose",
|
|
1690
|
-
"webSocketError"
|
|
1703
|
+
"trace"
|
|
1691
1704
|
]);
|
|
1692
1705
|
/** Override to instrument the whole event, then call super[RunSymbol]. */
|
|
1693
1706
|
const RunSymbol$1 = Symbol.for("effect-cf/Worker/run");
|
|
1694
1707
|
const FetchSymbol = Symbol.for("effect-cf/Worker/fetch");
|
|
1695
|
-
const renderFetchSuccess = (effect) => Effect.suspend(() => {
|
|
1708
|
+
const renderFetchSuccess = (effect, eventLayer) => Effect.suspend(() => {
|
|
1696
1709
|
let nativeResponse;
|
|
1697
1710
|
let webResponse;
|
|
1698
|
-
const
|
|
1711
|
+
const httpApp = Effect.flatMap(effect, (response) => {
|
|
1699
1712
|
if (response instanceof Response) {
|
|
1700
1713
|
nativeResponse = response;
|
|
1701
1714
|
return Effect.succeed(HttpServerResponse.empty());
|
|
1702
1715
|
}
|
|
1703
1716
|
return Effect.succeed(response);
|
|
1704
|
-
})
|
|
1717
|
+
});
|
|
1718
|
+
const handleResponse = (request, response) => nativeResponse !== void 0 ? Effect.void : Effect.map(Effect.context(), (context) => {
|
|
1705
1719
|
webResponse = HttpServerResponse.toWeb(HttpEffect.scopeTransferToStream(response), {
|
|
1706
1720
|
withoutBody: request.method === "HEAD",
|
|
1707
1721
|
context
|
|
1708
1722
|
});
|
|
1723
|
+
});
|
|
1724
|
+
const telemetryMiddleware = HttpMiddleware.make((self) => Effect.gen(function* () {
|
|
1725
|
+
yield* Effect.addFinalizer(() => scheduleTelemetryFlush$1);
|
|
1726
|
+
return yield* self;
|
|
1709
1727
|
}));
|
|
1728
|
+
const handled = eventLayer === void 0 ? HttpEffect.toHandled(httpApp, handleResponse, telemetryMiddleware) : Effect.flatMap(References.TracerEnabled, (tracerEnabled) => HttpEffect.toHandled(httpApp, handleResponse, HttpMiddleware.make((self) => Effect.provideService(Effect.gen(function* () {
|
|
1729
|
+
const context = yield* Layer.build(Layer.fresh(eventLayer));
|
|
1730
|
+
yield* Effect.addFinalizer(() => Effect.provideContext(scheduleTelemetryFlush$1, context));
|
|
1731
|
+
return yield* HttpMiddleware.tracer(self).pipe(Effect.onExit(() => Effect.yieldNow), Effect.provideContext(context));
|
|
1732
|
+
}), References.TracerEnabled, tracerEnabled))).pipe(Effect.withTracerEnabled(false)));
|
|
1710
1733
|
const captured = () => nativeResponse ?? webResponse ?? new Response(null, { status: 500 });
|
|
1711
1734
|
return Effect.matchCause(handled, {
|
|
1712
1735
|
onFailure: captured,
|
|
@@ -1715,13 +1738,13 @@ const renderFetchSuccess = (effect) => Effect.suspend(() => {
|
|
|
1715
1738
|
});
|
|
1716
1739
|
const isWorkerOptions = (options) => !Effect.isEffect(options);
|
|
1717
1740
|
/**
|
|
1718
|
-
* Drains buffered OTLP telemetry
|
|
1719
|
-
* response
|
|
1741
|
+
* Drains buffered OTLP telemetry when the fetch Effect scope closes. Effect
|
|
1742
|
+
* response streams transfer that scope to the body and schedule the flush when
|
|
1743
|
+
* the body finishes or is canceled.
|
|
1720
1744
|
*
|
|
1721
|
-
* The flush is handed to `ctx.waitUntil`
|
|
1722
|
-
*
|
|
1723
|
-
*
|
|
1724
|
-
* best-effort and silently settles within two seconds.
|
|
1745
|
+
* The flush is handed to `ctx.waitUntil` instead of being awaited by the
|
|
1746
|
+
* handler. It is a no-op when the context does not contain an
|
|
1747
|
+
* `OtlpExporter.Flusher`, and silently settles within two seconds.
|
|
1725
1748
|
*/
|
|
1726
1749
|
const scheduleTelemetryFlush$1 = scheduleTelemetryFlush$2((flush) => Effect.flatMap(WorkerContext, (workerContext) => workerContext.waitUntil(flush)));
|
|
1727
1750
|
function make$9(layer, optionsOrFetch) {
|
|
@@ -1733,7 +1756,7 @@ function make$9(layer, optionsOrFetch) {
|
|
|
1733
1756
|
this.runtime = makeEntrypointRuntime(layer, env, Layer.mergeAll(Layer.succeed(ExecutionContext, ctx), Layer.succeed(WorkerContext, fromExecutionContext(ctx))));
|
|
1734
1757
|
}
|
|
1735
1758
|
[RunSymbol$1](effect, runOptions = {}) {
|
|
1736
|
-
const eventLayer = options.eventLayer;
|
|
1759
|
+
const eventLayer = runOptions.event === "fetch" ? void 0 : options.eventLayer;
|
|
1737
1760
|
const parent = runOptions.rpc?.parent;
|
|
1738
1761
|
const parentSpan = parent === void 0 ? void 0 : Tracer.externalSpan(parent);
|
|
1739
1762
|
if (eventLayer === void 0) return runEventPromise(this.runtime, effect, void 0, parentSpan);
|
|
@@ -1747,13 +1770,13 @@ function make$9(layer, optionsOrFetch) {
|
|
|
1747
1770
|
if (fetchHandler === void 0) return Promise.resolve(new Response("Not Found", { status: 404 }));
|
|
1748
1771
|
const ctx = requestContext ?? this.ctx;
|
|
1749
1772
|
const requestServices = Layer.mergeAll(Layer.succeed(NativeRequest, request), Layer.succeed(HttpServerRequest.HttpServerRequest, HttpServerRequest.fromWeb(request)), Layer.succeed(ExecutionContext, ctx), Layer.succeed(WorkerContext, fromExecutionContext(ctx)));
|
|
1750
|
-
return this[RunSymbol$1](renderFetchSuccess(fetchHandler).pipe(Effect.
|
|
1773
|
+
return this[RunSymbol$1](renderFetchSuccess(fetchHandler, options.eventLayer).pipe(Effect.provide(requestServices)), { event: "fetch" });
|
|
1751
1774
|
}
|
|
1752
1775
|
queue(batch) {
|
|
1753
1776
|
const queueHandler = options.queue;
|
|
1754
1777
|
if (queueHandler === void 0) return Promise.resolve();
|
|
1755
1778
|
const messages = batch.messages.map((message) => fromMessage(message, message.body));
|
|
1756
|
-
return this[RunSymbol$1](queueHandler(fromMessageBatch(batch, messages)), { event: "queue" });
|
|
1779
|
+
return this[RunSymbol$1](queueHandler(fromMessageBatch(batch, messages)).pipe(Effect.onExit(() => scheduleTelemetryFlush$1)), { event: "queue" });
|
|
1757
1780
|
}
|
|
1758
1781
|
}
|
|
1759
1782
|
defineEntrypointRpcMethods("Worker", EffectWorker.prototype, options.rpc, reservedMethodNames$2, (self, effect, rpc) => self[RunSymbol$1](effect, {
|
|
@@ -1812,7 +1835,13 @@ const makeClient$7 = (definition) => {
|
|
|
1812
1835
|
const getByName = (name, options) => Effect.sync(() => namespace.getByName(name, options));
|
|
1813
1836
|
const jurisdiction = (value) => Effect.sync(() => namespace.jurisdiction(value));
|
|
1814
1837
|
const fetch = (stub, input, init) => Effect.tryPromise({
|
|
1815
|
-
try: () =>
|
|
1838
|
+
try: (signal) => {
|
|
1839
|
+
const callerSignal = init?.signal !== void 0 ? init.signal : input instanceof Request ? input.signal : void 0;
|
|
1840
|
+
return stub.fetch(input, {
|
|
1841
|
+
...init,
|
|
1842
|
+
signal: callerSignal === null || callerSignal === void 0 ? signal : AbortSignal.any([callerSignal, signal])
|
|
1843
|
+
});
|
|
1844
|
+
},
|
|
1816
1845
|
catch: (cause) => new DurableObjectFetchError({
|
|
1817
1846
|
binding: definition.binding,
|
|
1818
1847
|
cause
|
|
@@ -1852,7 +1881,11 @@ const makeClient$7 = (definition) => {
|
|
|
1852
1881
|
const scopedCall = Effect.fnUntraced(function* (stub, method, ...args) {
|
|
1853
1882
|
const methodName = String(method);
|
|
1854
1883
|
const result = yield* rpc(stub, method, ...args);
|
|
1855
|
-
const value = yield* scoped(result)
|
|
1884
|
+
const value = yield* scoped(result).pipe(Effect.mapError((cause) => new DurableObjectRpcError({
|
|
1885
|
+
binding: definition.binding,
|
|
1886
|
+
method: methodName,
|
|
1887
|
+
cause
|
|
1888
|
+
})));
|
|
1856
1889
|
return yield* decodeSuccess$1(methodName, value);
|
|
1857
1890
|
}, (effect, ...args) => withRpcClientSpan(effect, definition.binding, String(args[1])));
|
|
1858
1891
|
const directMethods = makeDirectMethods(definition.definition, {
|
|
@@ -2023,18 +2056,16 @@ var DurableObject_exports = /* @__PURE__ */ __exportAll({
|
|
|
2023
2056
|
make: () => make$7,
|
|
2024
2057
|
method: () => method
|
|
2025
2058
|
});
|
|
2026
|
-
const reservedMethodNames =
|
|
2027
|
-
"constructor",
|
|
2028
|
-
"dup",
|
|
2029
|
-
"fetch",
|
|
2030
|
-
"alarm",
|
|
2031
|
-
"webSocketMessage",
|
|
2032
|
-
"webSocketClose",
|
|
2033
|
-
"webSocketError"
|
|
2034
|
-
]);
|
|
2059
|
+
const reservedMethodNames = reservedMethodNames$4;
|
|
2035
2060
|
/** Override to instrument the whole event, then call super[RunSymbol]. */
|
|
2036
2061
|
const RunSymbol = Symbol.for("effect-cf/DurableObject/run");
|
|
2037
2062
|
const scheduleTelemetryFlush = scheduleTelemetryFlush$2((flush) => Effect.flatMap(DurableObjectState, (state) => state.waitUntil(flush)));
|
|
2063
|
+
/**
|
|
2064
|
+
* Builds a Durable Object whose base `layer` lives for the in-memory instance.
|
|
2065
|
+
* Cloudflare does not expose an eviction or shutdown hook, so finalizers in
|
|
2066
|
+
* that layer are not guaranteed to run. Put resources that require timely
|
|
2067
|
+
* release in `eventLayer`, whose scope closes after each handled event.
|
|
2068
|
+
*/
|
|
2038
2069
|
const make$7 = (layer, options = {}) => {
|
|
2039
2070
|
class EffectDurableObject extends DurableObject {
|
|
2040
2071
|
runtime;
|
|
@@ -2411,7 +2442,6 @@ const layer$8 = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
2411
2442
|
const disconnects = yield* Queue.make();
|
|
2412
2443
|
const connectionsBySocket = /* @__PURE__ */ new Map();
|
|
2413
2444
|
const connectionsById = /* @__PURE__ */ new Map();
|
|
2414
|
-
const clientIds = /* @__PURE__ */ new Set();
|
|
2415
2445
|
const resetSockets = /* @__PURE__ */ new WeakSet();
|
|
2416
2446
|
const declarationsById = /* @__PURE__ */ new Map();
|
|
2417
2447
|
const declarationsByTag = /* @__PURE__ */ new Map();
|
|
@@ -2427,14 +2457,6 @@ const layer$8 = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
2427
2457
|
declarationsByTag.set(declaration.rpcTag, declaration);
|
|
2428
2458
|
}
|
|
2429
2459
|
const reserveClientId = (socket) => {
|
|
2430
|
-
const attachment = readAttachment(socket.raw, attachmentKey);
|
|
2431
|
-
if (attachment._tag === "Invalid") throw new Error("Invalid Durable Object RPC websocket attachment metadata");
|
|
2432
|
-
if (attachment._tag === "Valid") {
|
|
2433
|
-
const metadata = attachment.metadata;
|
|
2434
|
-
nextClientId = Math.max(nextClientId, metadata.clientId + 1);
|
|
2435
|
-
writeAttachment(socket.raw, attachmentKey, metadata);
|
|
2436
|
-
return metadata;
|
|
2437
|
-
}
|
|
2438
2460
|
const id = nextClientId++;
|
|
2439
2461
|
const created = {
|
|
2440
2462
|
version: legacyAttachmentVersion,
|
|
@@ -2487,7 +2509,6 @@ const layer$8 = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
2487
2509
|
for (const subscription of restoredSubscriptions) if (!addSubscription(connection, subscription)) throw new Error("Duplicate restored resumable RPC stream subscription identity");
|
|
2488
2510
|
connectionsBySocket.set(socket.raw, connection);
|
|
2489
2511
|
connectionsById.set(connection.id, connection);
|
|
2490
|
-
clientIds.add(connection.id);
|
|
2491
2512
|
nextClientId = Math.max(nextClientId, connection.id + 1);
|
|
2492
2513
|
return connection;
|
|
2493
2514
|
};
|
|
@@ -2524,7 +2545,6 @@ const layer$8 = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
2524
2545
|
if (connection === void 0) return;
|
|
2525
2546
|
connectionsBySocket.delete(socket.raw);
|
|
2526
2547
|
connectionsById.delete(connection.id);
|
|
2527
|
-
clientIds.delete(connection.id);
|
|
2528
2548
|
activeNonResumableRequestCount -= connection.nonResumableRequestIds.size;
|
|
2529
2549
|
connection.requestIds.clear();
|
|
2530
2550
|
connection.nonResumableRequestIds.clear();
|
|
@@ -2743,7 +2763,7 @@ const layer$8 = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
2743
2763
|
end: (clientId) => Effect.sync(() => {
|
|
2744
2764
|
connectionsById.get(clientId)?.socket.raw.close();
|
|
2745
2765
|
}),
|
|
2746
|
-
clientIds: Effect.sync(() =>
|
|
2766
|
+
clientIds: Effect.sync(() => new Set(connectionsById.keys())),
|
|
2747
2767
|
initialMessage: Effect.succeedNone,
|
|
2748
2768
|
supportsAck: true,
|
|
2749
2769
|
supportsTransferables: false,
|
|
@@ -2877,13 +2897,9 @@ const writeAttachment = (socket, key, metadata) => {
|
|
|
2877
2897
|
const current = socket.deserializeAttachment();
|
|
2878
2898
|
if (current !== null && current !== void 0 && !Predicate.isObject(current)) throw new Error("DurableObjectRpcWebSocket requires object-shaped websocket attachments so adapter metadata can preserve application fields");
|
|
2879
2899
|
const attachment = Predicate.isObject(current) ? current : {};
|
|
2880
|
-
const currentMetadata = Predicate.hasProperty(attachment, key) && Predicate.isObject(attachment[key]) ? attachment[key] : {};
|
|
2881
2900
|
socket.serializeAttachment({
|
|
2882
2901
|
...attachment,
|
|
2883
|
-
[key]:
|
|
2884
|
-
...currentMetadata,
|
|
2885
|
-
...metadata
|
|
2886
|
-
}
|
|
2902
|
+
[key]: metadata
|
|
2887
2903
|
});
|
|
2888
2904
|
};
|
|
2889
2905
|
const makeHeartbeatController = (state, serialization, policy) => Effect.gen(function* () {
|
|
@@ -3835,20 +3851,29 @@ const exposeCloudflareNonRetryableError = (effect) => Effect.catchCause(effect,
|
|
|
3835
3851
|
const error = Cause.findErrorOption(cause);
|
|
3836
3852
|
return Option.isSome(error) && error.value instanceof WorkflowStepNonRetryableError ? Effect.die(toCloudflareNonRetryableError(error.value)) : Effect.failCause(cause);
|
|
3837
3853
|
});
|
|
3838
|
-
const fromWorkflowStep = (step
|
|
3854
|
+
const fromWorkflowStep = (step) => ({
|
|
3839
3855
|
raw: step,
|
|
3840
|
-
do: (name, effect, config) => Effect.
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
const
|
|
3844
|
-
return
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3856
|
+
do: (name, effect, config) => Effect.gen(function* () {
|
|
3857
|
+
const context = yield* Effect.context();
|
|
3858
|
+
return yield* Effect.scoped(Effect.gen(function* () {
|
|
3859
|
+
const runPromise = yield* FiberSet.makeRuntimePromise();
|
|
3860
|
+
return yield* Effect.tryPromise({
|
|
3861
|
+
try: () => {
|
|
3862
|
+
const run = (stepContext) => {
|
|
3863
|
+
const stepEffect = Effect.scoped(Effect.provideService(effect, WorkflowStepContext$1, stepContext));
|
|
3864
|
+
return runPromise(exposeCloudflareNonRetryableError(Effect.provideContext(stepEffect, context)));
|
|
3865
|
+
};
|
|
3866
|
+
const rawStep = step;
|
|
3867
|
+
return config === void 0 ? rawStep.do(name, run) : rawStep.do(name, config, run);
|
|
3868
|
+
},
|
|
3869
|
+
catch: (cause) => new WorkflowStepError({
|
|
3870
|
+
step: name,
|
|
3871
|
+
operation: "do",
|
|
3872
|
+
cause
|
|
3873
|
+
})
|
|
3874
|
+
});
|
|
3875
|
+
}));
|
|
3876
|
+
}),
|
|
3852
3877
|
sleep: (name, duration) => Effect.tryPromise({
|
|
3853
3878
|
try: () => step.sleep(name, duration),
|
|
3854
3879
|
catch: (cause) => new WorkflowStepError({
|
|
@@ -3884,7 +3909,7 @@ const make = (layer, options) => {
|
|
|
3884
3909
|
this.runtime = makeEntrypointRuntime(layer, env, Layer.mergeAll(Layer.succeed(ExecutionContext, ctx), Layer.succeed(WorkerContext, fromExecutionContext(ctx))));
|
|
3885
3910
|
}
|
|
3886
3911
|
run(event, step) {
|
|
3887
|
-
const workflowServices = Layer.mergeAll(Layer.succeed(WorkflowEvent$1, fromWorkflowEvent(event)), Layer.succeed(WorkflowStep$1, fromWorkflowStep(step
|
|
3912
|
+
const workflowServices = Layer.mergeAll(Layer.succeed(WorkflowEvent$1, fromWorkflowEvent(event)), Layer.succeed(WorkflowStep$1, fromWorkflowStep(step)));
|
|
3888
3913
|
return runEventPromise(this.runtime, options.run(event.payload), workflowServices);
|
|
3889
3914
|
}
|
|
3890
3915
|
}
|
package/package.json
CHANGED
package/src/AnalyticsEngine.ts
CHANGED
|
@@ -572,7 +572,14 @@ const executeQueryRequest = (
|
|
|
572
572
|
options?: AnalyticsEngineQueryOptions,
|
|
573
573
|
) =>
|
|
574
574
|
Effect.gen(function* () {
|
|
575
|
-
const
|
|
575
|
+
const request = yield* Effect.try({
|
|
576
|
+
try: () => queryRequest(definition, sql, options),
|
|
577
|
+
catch: (cause) =>
|
|
578
|
+
analyticsEngineQueryError(definition, "query", "Invalid Analytics Engine SQL API URL", {
|
|
579
|
+
cause,
|
|
580
|
+
}),
|
|
581
|
+
});
|
|
582
|
+
const response = yield* httpClient.execute(request).pipe(
|
|
576
583
|
Effect.mapError((cause) =>
|
|
577
584
|
analyticsEngineQueryError(definition, "query", "Analytics Engine SQL API request failed", {
|
|
578
585
|
cause,
|
package/src/CloudflareOtlp.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Worker fetch/RPC and Durable Object alarm/RPC handlers schedule a
|
|
3
|
-
* flusher. It settles within two seconds;
|
|
4
|
-
* flush explicitly.
|
|
2
|
+
* Worker fetch/queue/RPC and Durable Object alarm/RPC handlers schedule a
|
|
3
|
+
* best-effort flusher. It settles within two seconds; other lifecycles must
|
|
4
|
+
* flush explicitly. Scheduled flush failures cannot fail user events. Exporter
|
|
5
|
+
* layer finalizers run separately and follow their configured shutdown timeout.
|
|
5
6
|
*/
|
|
6
7
|
import { ConfigProvider, Effect, Layer, Option } from "effect";
|
|
7
8
|
import type * as Tracer from "effect/Tracer";
|
|
@@ -207,6 +208,8 @@ export const layerProtobuf = (
|
|
|
207
208
|
*
|
|
208
209
|
* Provide this layer at runtime scope for long-lived metrics aggregation, or at
|
|
209
210
|
* handler scope when traces/logs should flush as the Cloudflare event finishes.
|
|
211
|
+
* A handler-scoped exporter can delay scope closure while its own finalizer
|
|
212
|
+
* flushes; the two-second background flush limit does not bound layer shutdown.
|
|
210
213
|
*/
|
|
211
214
|
export const layerWorker = (options: WorkerLayerOptions = {}): Layer.Layer<OtlpExporter.Flusher> =>
|
|
212
215
|
makeLayer(options, {
|
package/src/ComputerWorkspace.ts
CHANGED
|
@@ -100,6 +100,17 @@ export class WorkspaceAssetsError extends Schema.TaggedError<WorkspaceAssetsErro
|
|
|
100
100
|
},
|
|
101
101
|
) {}
|
|
102
102
|
|
|
103
|
+
/** Schema-serializable error while acquiring the Computer workspace client. */
|
|
104
|
+
export class WorkspaceAcquireError extends Schema.TaggedError<WorkspaceAcquireError>()(
|
|
105
|
+
"WorkspaceAcquireError",
|
|
106
|
+
{
|
|
107
|
+
operation: Schema.String,
|
|
108
|
+
message: Schema.String,
|
|
109
|
+
code: Schema.optionalKey(Schema.String),
|
|
110
|
+
cause: Schema.optionalKey(Schema.Defect({ includeStack: true })),
|
|
111
|
+
},
|
|
112
|
+
) {}
|
|
113
|
+
|
|
103
114
|
export type WorkspaceReadFileOptions = Omit<CloudflareReadFileOptions, "encoding">;
|
|
104
115
|
export type ReadTextFileOptions = WorkspaceReadFileOptions;
|
|
105
116
|
export type ListDirectoryOptions = CloudflareReaddirOptions;
|
|
@@ -469,6 +480,21 @@ const execError = (operation: string, cause: unknown) => {
|
|
|
469
480
|
return WorkspaceExecError.make(properties);
|
|
470
481
|
};
|
|
471
482
|
|
|
483
|
+
const acquireError = (operation: string, cause: unknown) => {
|
|
484
|
+
const properties: WorkspaceOperationErrorProperties = {
|
|
485
|
+
operation,
|
|
486
|
+
message: errorMessage(cause),
|
|
487
|
+
cause,
|
|
488
|
+
};
|
|
489
|
+
const code = errorCode(cause);
|
|
490
|
+
|
|
491
|
+
if (code !== undefined) {
|
|
492
|
+
properties.code = code;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return WorkspaceAcquireError.make(properties);
|
|
496
|
+
};
|
|
497
|
+
|
|
472
498
|
const assetsError = (operation: string, path: string, cause: unknown) => {
|
|
473
499
|
const properties: WorkspaceFsErrorProperties = {
|
|
474
500
|
operation,
|
|
@@ -887,9 +913,6 @@ const wrapRun = <Encoding extends WorkspaceExecEncoding>(
|
|
|
887
913
|
return { id: handle.id, backend: handle.backend, events, result, kill };
|
|
888
914
|
};
|
|
889
915
|
|
|
890
|
-
const releaseRun = (handle: CloudflareWorkspaceRuntimeExecHandle<"utf8" | undefined>) =>
|
|
891
|
-
Effect.sync(() => handle[Symbol.dispose]());
|
|
892
|
-
|
|
893
916
|
const makeRuntime = (client: CloudflareWorkspaceClient): ComputerWorkspaceRuntime => {
|
|
894
917
|
const execImplementation = Effect.fn("ComputerWorkspace.runtime.exec", {
|
|
895
918
|
attributes: { family: "runtime", operation: "exec" },
|
|
@@ -905,7 +928,7 @@ const makeRuntime = (client: CloudflareWorkspaceClient): ComputerWorkspaceRuntim
|
|
|
905
928
|
: undefined;
|
|
906
929
|
|
|
907
930
|
yield* Effect.annotateCurrentSpan({ backend: options?.backend, runId: options?.id });
|
|
908
|
-
const handle = yield* Effect.
|
|
931
|
+
const handle = yield* Effect.acquireDisposable(
|
|
909
932
|
Effect.tryPromise({
|
|
910
933
|
try: () => {
|
|
911
934
|
if (Predicate.isString(source)) {
|
|
@@ -926,7 +949,6 @@ const makeRuntime = (client: CloudflareWorkspaceClient): ComputerWorkspaceRuntim
|
|
|
926
949
|
},
|
|
927
950
|
catch: (cause) => execError("exec", cause),
|
|
928
951
|
}),
|
|
929
|
-
releaseRun,
|
|
930
952
|
);
|
|
931
953
|
|
|
932
954
|
// SAFETY: runtimeExecOptions carries Encoding to Cloudflare's corresponding handle overload.
|
|
@@ -945,12 +967,11 @@ const makeRuntime = (client: CloudflareWorkspaceClient): ComputerWorkspaceRuntim
|
|
|
945
967
|
options?: WorkspaceGetExecOptions<Encoding>,
|
|
946
968
|
) {
|
|
947
969
|
yield* Effect.annotateCurrentSpan({ backend: options?.backend, runId: id });
|
|
948
|
-
const handle = yield* Effect.
|
|
970
|
+
const handle = yield* Effect.acquireDisposable(
|
|
949
971
|
Effect.tryPromise({
|
|
950
972
|
try: () => client.runtime.getExec(id, runtimeGetOptions(options)),
|
|
951
973
|
catch: (cause) => execError("getExec", cause),
|
|
952
974
|
}),
|
|
953
|
-
releaseRun,
|
|
954
975
|
);
|
|
955
976
|
|
|
956
977
|
// SAFETY: runtimeGetOptions carries Encoding to Cloudflare's corresponding handle overload.
|
|
@@ -1169,25 +1190,31 @@ export class ComputerWorkspace extends Context.Service<
|
|
|
1169
1190
|
* Acquires the single workspace registered by `withComputerWorkspace` and
|
|
1170
1191
|
* releases its RPC client when the layer scope closes.
|
|
1171
1192
|
*/
|
|
1172
|
-
static readonly layer: Layer.Layer<ComputerWorkspace,
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1193
|
+
static readonly layer: Layer.Layer<ComputerWorkspace, WorkspaceAcquireError, DurableObjectState> =
|
|
1194
|
+
Layer.effect(
|
|
1195
|
+
ComputerWorkspace,
|
|
1196
|
+
Effect.gen(function* () {
|
|
1197
|
+
const state = yield* DurableObjectState;
|
|
1198
|
+
const host = HostRegistry.lookup(state.raw);
|
|
1199
|
+
|
|
1200
|
+
if (host === undefined) {
|
|
1201
|
+
return yield* Effect.die(
|
|
1202
|
+
"ComputerWorkspace.layer requires a Durable Object class built with withComputerWorkspace",
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const computer = yield* Effect.tryPromise({
|
|
1207
|
+
try: () => import("@cloudflare/computer"),
|
|
1208
|
+
catch: (cause) => acquireError("import", cause),
|
|
1209
|
+
});
|
|
1210
|
+
const client = yield* Effect.acquireDisposable(
|
|
1211
|
+
Effect.tryPromise({
|
|
1212
|
+
try: () => computer.getWorkspace(host),
|
|
1213
|
+
catch: (cause) => acquireError("getWorkspace", cause),
|
|
1214
|
+
}),
|
|
1181
1215
|
);
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
const computer = yield* Effect.promise(() => import("@cloudflare/computer"));
|
|
1185
|
-
const client = yield* Effect.acquireRelease(
|
|
1186
|
-
Effect.promise(() => computer.getWorkspace(host)),
|
|
1187
|
-
(workspace) => Effect.sync(() => workspace[Symbol.dispose]()),
|
|
1188
|
-
);
|
|
1189
1216
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1217
|
+
return ComputerWorkspace.of(fromWorkspaceClient(client));
|
|
1218
|
+
}),
|
|
1219
|
+
);
|
|
1193
1220
|
}
|
package/src/DurableObject.ts
CHANGED
|
@@ -5,20 +5,13 @@ import { NativeRequest } from "./Worker";
|
|
|
5
5
|
import { WorkerEnvironment, type WorkerEnv } from "./Environment";
|
|
6
6
|
import { DurableObjectState, fromDurableObjectState } from "./DurableObjectState";
|
|
7
7
|
import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
|
|
8
|
+
import * as RpcDefinition from "./RpcDefinition";
|
|
8
9
|
import * as Entrypoint from "./internal/Entrypoint";
|
|
9
10
|
import * as Runtime from "./internal/Runtime";
|
|
10
11
|
import * as Telemetry from "./internal/Telemetry";
|
|
11
12
|
import type { ReceiverOptions, RpcInvocationInfo } from "./RpcTracing";
|
|
12
13
|
|
|
13
|
-
const reservedMethodNames
|
|
14
|
-
"constructor",
|
|
15
|
-
"dup",
|
|
16
|
-
"fetch",
|
|
17
|
-
"alarm",
|
|
18
|
-
"webSocketMessage",
|
|
19
|
-
"webSocketClose",
|
|
20
|
-
"webSocketError",
|
|
21
|
-
]);
|
|
14
|
+
const reservedMethodNames: ReadonlySet<string> = RpcDefinition.reservedMethodNames;
|
|
22
15
|
|
|
23
16
|
type RuntimeContext<ROut> = DurableObjectState | WorkerEnvironment | ROut;
|
|
24
17
|
|
|
@@ -167,6 +160,12 @@ export type DurableObjectClass<Rpc extends DurableObjectRpc<ROut>, ROut> = new (
|
|
|
167
160
|
): Promise<A>;
|
|
168
161
|
};
|
|
169
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Builds a Durable Object whose base `layer` lives for the in-memory instance.
|
|
165
|
+
* Cloudflare does not expose an eviction or shutdown hook, so finalizers in
|
|
166
|
+
* that layer are not guaranteed to run. Put resources that require timely
|
|
167
|
+
* release in `eventLayer`, whose scope closes after each handled event.
|
|
168
|
+
*/
|
|
170
169
|
export const make = <
|
|
171
170
|
ROut,
|
|
172
171
|
LayerError,
|
|
@@ -250,7 +250,7 @@ export type DurableObjectNamespaceEffectClient<
|
|
|
250
250
|
stub: DurableObjectStubClient<Api>,
|
|
251
251
|
method: Method,
|
|
252
252
|
...args: StubMethodArgs<Api, Method>
|
|
253
|
-
) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>,
|
|
253
|
+
) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>, DurableObjectRpcError, Scope.Scope>;
|
|
254
254
|
/**
|
|
255
255
|
* Exposes the underlying native Durable Object namespace binding.
|
|
256
256
|
*
|
|
@@ -300,7 +300,11 @@ export type DurableObjectNamespaceStaticClient<
|
|
|
300
300
|
stub: DurableObjectStubClient<Api>,
|
|
301
301
|
method: Method,
|
|
302
302
|
...args: StubMethodArgs<Api, Method>
|
|
303
|
-
) => Effect.Effect<
|
|
303
|
+
) => Effect.Effect<
|
|
304
|
+
Awaited<StubMethodSuccess<Api, Method>>,
|
|
305
|
+
DurableObjectRpcError,
|
|
306
|
+
Scope.Scope | R
|
|
307
|
+
>;
|
|
304
308
|
readonly rawUnsafe: () => Effect.Effect<DurableObjectNamespaceClient<Api>, never, R>;
|
|
305
309
|
};
|
|
306
310
|
|
|
@@ -351,7 +355,22 @@ export const makeClient = <
|
|
|
351
355
|
|
|
352
356
|
const fetch = (stub: StubClient, input: RequestInfo | URL, init?: RequestInit) =>
|
|
353
357
|
Effect.tryPromise({
|
|
354
|
-
try: () =>
|
|
358
|
+
try: (signal) => {
|
|
359
|
+
const callerSignal =
|
|
360
|
+
init?.signal !== undefined
|
|
361
|
+
? init.signal
|
|
362
|
+
: input instanceof Request
|
|
363
|
+
? input.signal
|
|
364
|
+
: undefined;
|
|
365
|
+
|
|
366
|
+
return stub.fetch(input, {
|
|
367
|
+
...init,
|
|
368
|
+
signal:
|
|
369
|
+
callerSignal === null || callerSignal === undefined
|
|
370
|
+
? signal
|
|
371
|
+
: AbortSignal.any([callerSignal, signal]),
|
|
372
|
+
});
|
|
373
|
+
},
|
|
355
374
|
catch: (cause) => new DurableObjectFetchError({ binding: definition.binding, cause }),
|
|
356
375
|
});
|
|
357
376
|
|
|
@@ -456,10 +475,19 @@ export const makeClient = <
|
|
|
456
475
|
stub: StubClient,
|
|
457
476
|
method: Method,
|
|
458
477
|
...args: StubMethodArgs<Api, Method>
|
|
459
|
-
): Effect.fn.Return<StubMethodSuccess<Api, Method>,
|
|
478
|
+
): Effect.fn.Return<StubMethodSuccess<Api, Method>, DurableObjectRpcError, Scope.Scope> {
|
|
460
479
|
const methodName = String(method);
|
|
461
480
|
const result = yield* rpc(stub, method, ...args);
|
|
462
|
-
const value = yield* CloudflareRpc.scoped(result)
|
|
481
|
+
const value = yield* CloudflareRpc.scoped(result).pipe(
|
|
482
|
+
Effect.mapError(
|
|
483
|
+
(cause) =>
|
|
484
|
+
new DurableObjectRpcError({
|
|
485
|
+
binding: definition.binding,
|
|
486
|
+
method: methodName,
|
|
487
|
+
cause,
|
|
488
|
+
}),
|
|
489
|
+
),
|
|
490
|
+
);
|
|
463
491
|
|
|
464
492
|
return yield* decodeSuccess<Method>(methodName, value);
|
|
465
493
|
},
|