@effect-agent/platform-cloudflare 0.1.0-beta.83 → 0.1.0-beta.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/Alarm.mjs +1 -1
  2. package/dist/Alarm.mjs.map +1 -1
  3. package/dist/BrowserRestCrawl.mjs +1 -1
  4. package/dist/BrowserRestCrawl.mjs.map +1 -1
  5. package/dist/CloudflareBindings.d.mts +6 -3
  6. package/dist/CloudflareBindings.mjs +14 -2
  7. package/dist/CloudflareBindings.mjs.map +1 -1
  8. package/dist/CloudflareMemory.d.mts +1 -1
  9. package/dist/CloudflareMemory.mjs +16 -9
  10. package/dist/CloudflareMemory.mjs.map +1 -1
  11. package/dist/CloudflareScheduling.mjs +15 -8
  12. package/dist/CloudflareScheduling.mjs.map +1 -1
  13. package/dist/CloudflareSubscriptions.mjs +12 -6
  14. package/dist/CloudflareSubscriptions.mjs.map +1 -1
  15. package/dist/CloudflareThreadClient.d.mts +256 -46
  16. package/dist/CloudflareThreadClient.mjs +11 -15
  17. package/dist/CloudflareThreadClient.mjs.map +1 -1
  18. package/dist/InteractiveBrowser.mjs +15 -15
  19. package/dist/InteractiveBrowser.mjs.map +1 -1
  20. package/dist/ProtectedBrowser.mjs +12 -12
  21. package/dist/ProtectedBrowser.mjs.map +1 -1
  22. package/dist/{ThreadObject-rVo6_oel.d.mts → ThreadObject-DZMkwIBh.d.mts} +29 -33
  23. package/dist/{ThreadObject-BpwkEc5E.mjs → ThreadObject-gyx8Qq03.mjs} +8 -9
  24. package/dist/ThreadObject-gyx8Qq03.mjs.map +1 -0
  25. package/dist/ThreadObject.d.mts +1 -1
  26. package/dist/ThreadObject.mjs +1 -1
  27. package/dist/WakeScheduler.mjs +7 -10
  28. package/dist/WakeScheduler.mjs.map +1 -1
  29. package/dist/{browser-session-lifecycle-CUbVfEgP.mjs → browser-session-lifecycle-CmbQABHC.mjs} +5 -5
  30. package/dist/browser-session-lifecycle-CmbQABHC.mjs.map +1 -0
  31. package/dist/index.d.mts +1 -1
  32. package/dist/index.mjs +1 -1
  33. package/package.json +1 -1
  34. package/src/Alarm.ts +6 -6
  35. package/src/BrowserRestCrawl.ts +1 -1
  36. package/src/CloudflareBindings.ts +20 -1
  37. package/src/CloudflareMemory.ts +25 -11
  38. package/src/CloudflareScheduling.ts +23 -9
  39. package/src/CloudflareSubscriptions.ts +18 -18
  40. package/src/CloudflareThreadClient.ts +14 -12
  41. package/src/InteractiveBrowser.ts +19 -22
  42. package/src/WakeScheduler.ts +12 -6
  43. package/src/internal/browser-session-lifecycle.ts +8 -8
  44. package/src/internal/message-delivery.ts +5 -6
  45. package/src/internal/transport.ts +8 -6
  46. package/src/protected-browser/binding.ts +1 -1
  47. package/src/protected-browser/policy.ts +29 -31
  48. package/dist/ThreadObject-BpwkEc5E.mjs.map +0 -1
  49. package/dist/browser-session-lifecycle-CUbVfEgP.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-session-lifecycle-CmbQABHC.mjs","names":[],"sources":["../src/internal/browser-session-lifecycle.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redacted, Schema, Stream } from \"effect\";\nimport { FetchHttpClient, HttpClient, HttpClientRequest } from \"effect/unstable/http\";\n\nexport class BrowserRunCleanupError extends Schema.TaggedError<BrowserRunCleanupError>()(\n \"BrowserRunCleanupError\",\n {\n reason: Schema.Literals([\n \"configuration\",\n \"authorization\",\n \"rate-limited\",\n \"provider\",\n \"malformed\",\n \"timeout\",\n \"pending\",\n ]),\n status: Schema.optionalKey(Schema.Int),\n },\n) {}\n\nexport interface BrowserRunLifecycleOptions {\n readonly accountId: string;\n readonly apiToken: Redacted.Redacted<string>;\n}\n\nconst Identity = Schema.String.check(Schema.isUUID());\nconst Account = Schema.String.check(Schema.isPattern(/^[a-f0-9]{32}$/));\nconst Closed = Schema.Struct({ status: Schema.Literals([\"closing\", \"closed\"]) });\nconst Metadata = Schema.Struct({ sessionId: Identity, endTime: Schema.optionalKey(Schema.Finite) });\n// This exact provider response is accepted only on a fixed-origin, authenticated,\n// exact-session request. An arbitrary 404 or a listing omission is never absence.\nconst Absent = Schema.Struct({ error: Schema.Literal(\"Session not found\") });\n\nexport class BrowserRunSessionLifecycle extends Context.Service<\n BrowserRunSessionLifecycle,\n {\n readonly close: (\n sessionId: Redacted.Redacted<string>,\n ) => Effect.Effect<void, BrowserRunCleanupError>;\n }\n>()(\"@effect-agent/platform-cloudflare/BrowserRunSessionLifecycle\") {\n static layer(options: BrowserRunLifecycleOptions) {\n return Layer.effect(\n this,\n Effect.gen(function* () {\n if (\n !Schema.is(Account)(options.accountId) ||\n Redacted.value(options.apiToken).length === 0\n ) {\n return yield* new BrowserRunCleanupError({ reason: \"configuration\" });\n }\n const client = yield* HttpClient.HttpClient;\n\n const request = Effect.fn(\"BrowserRunSessionLifecycle.request\")(function* (\n method: \"GET\" | \"DELETE\",\n sessionId: string,\n ) {\n const path = method === \"DELETE\" ? \"browser\" : \"session\";\n\n const response = yield* client\n .execute(\n HttpClientRequest.make(method)(\n `https://api.cloudflare.com/client/v4/accounts/${options.accountId}/browser-rendering/devtools/${path}/${sessionId}`,\n ).pipe(HttpClientRequest.bearerToken(options.apiToken)),\n )\n .pipe(\n // workerd supports manual/follow, not error. Reject every redirect below.\n Effect.provideService(FetchHttpClient.RequestInit, { redirect: \"manual\" }),\n Effect.mapError(() => new BrowserRunCleanupError({ reason: \"provider\" })),\n );\n\n if (response.status === 401 || response.status === 403)\n return yield* new BrowserRunCleanupError({\n reason: \"authorization\",\n status: response.status,\n });\n if (response.status === 429)\n return yield* new BrowserRunCleanupError({\n reason: \"rate-limited\",\n status: response.status,\n });\n if (response.status !== 200 && response.status !== 404)\n return yield* new BrowserRunCleanupError({\n reason: \"provider\",\n status: response.status,\n });\n if (response.headers[\"content-type\"]?.split(\";\", 1)[0]?.trim() !== \"application/json\")\n return yield* new BrowserRunCleanupError({ reason: \"malformed\" });\n\n const bytes = yield* Stream.runFoldEffect(\n response.stream,\n () => new Uint8Array(),\n (body, chunk) => {\n if (body.byteLength + chunk.byteLength > 16_384)\n return Effect.fail(new BrowserRunCleanupError({ reason: \"malformed\" }));\n const combined = new Uint8Array(body.byteLength + chunk.byteLength);\n\n combined.set(body);\n combined.set(chunk, body.byteLength);\n\n return Effect.succeed(combined);\n },\n ).pipe(Effect.mapError(() => new BrowserRunCleanupError({ reason: \"malformed\" })));\n\n const body = yield* Effect.try({\n try: () => new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: false }).decode(bytes),\n catch: () => new BrowserRunCleanupError({ reason: \"malformed\" }),\n });\n\n if (response.status === 404) {\n const absent = Schema.decodeOption(Schema.fromJsonString(Absent))(body, {\n onExcessProperty: \"error\",\n });\n\n if (Option.isSome(absent)) return true;\n\n return yield* new BrowserRunCleanupError({ reason: \"malformed\" });\n }\n if (method === \"DELETE\") {\n const result = yield* Schema.decodeEffect(Schema.fromJsonString(Closed))(body).pipe(\n Effect.mapError(() => new BrowserRunCleanupError({ reason: \"malformed\" })),\n );\n\n return result.status === \"closed\";\n }\n\n const result = yield* Schema.decodeEffect(Schema.fromJsonString(Metadata))(body).pipe(\n Effect.mapError(() => new BrowserRunCleanupError({ reason: \"malformed\" })),\n );\n\n if (result.sessionId !== sessionId)\n return yield* new BrowserRunCleanupError({ reason: \"malformed\" });\n\n return result.endTime !== undefined && result.endTime > 0;\n });\n\n const close = Effect.fn(\"BrowserRunSessionLifecycle.close\")(\n function* (sessionId: Redacted.Redacted<string>) {\n const id = yield* Schema.decodeEffect(Identity)(Redacted.value(sessionId)).pipe(\n Effect.mapError(() => new BrowserRunCleanupError({ reason: \"configuration\" })),\n );\n\n if (yield* request(\"DELETE\", id)) return;\n for (let read = 0; read < 2; read++) {\n if (yield* request(\"GET\", id)) return;\n }\n\n return yield* new BrowserRunCleanupError({ reason: \"pending\" });\n },\n Effect.timeoutOrElse({\n duration: \"10 seconds\",\n orElse: () => Effect.fail(new BrowserRunCleanupError({ reason: \"timeout\" })),\n }),\n Effect.withTracerEnabled(false),\n );\n\n return { close };\n }),\n );\n }\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -8,6 +8,6 @@ import { t as CloudflareMemory_d_exports } from "./CloudflareMemory.mjs";
8
8
  import { t as CloudflareScheduling_d_exports } from "./CloudflareScheduling.mjs";
9
9
  import { t as CloudflareSubscriptions_d_exports } from "./CloudflareSubscriptions.mjs";
10
10
  import { t as CloudflareThreadClient_d_exports } from "./CloudflareThreadClient.mjs";
11
- import { f as ThreadObject_d_exports } from "./ThreadObject-rVo6_oel.mjs";
11
+ import { f as ThreadObject_d_exports } from "./ThreadObject-DZMkwIBh.mjs";
12
12
  import { t as WakeScheduler_d_exports } from "./WakeScheduler.mjs";
13
13
  export { Alarm_d_exports as Alarm, CloudflareAiGateway_d_exports as CloudflareAiGateway, CloudflareBindings_d_exports as CloudflareBindings, CloudflareBrowser_d_exports as CloudflareBrowser, CloudflareCodeMode_d_exports as CloudflareCodeMode, CloudflareConfig_d_exports as CloudflareConfig, CloudflareMemory_d_exports as CloudflareMemory, CloudflareScheduling_d_exports as CloudflareScheduling, CloudflareSubscriptions_d_exports as CloudflareSubscriptions, CloudflareThreadClient_d_exports as CloudflareThreadClient, ThreadObject_d_exports as ThreadObject, WakeScheduler_d_exports as WakeScheduler };
package/dist/index.mjs CHANGED
@@ -8,6 +8,6 @@ import { t as CloudflareThreadClient_exports } from "./CloudflareThreadClient.mj
8
8
  import { t as CloudflareScheduling_exports } from "./CloudflareScheduling.mjs";
9
9
  import { t as CloudflareSubscriptions_exports } from "./CloudflareSubscriptions.mjs";
10
10
  import { t as WakeScheduler_exports } from "./WakeScheduler.mjs";
11
- import { l as ThreadObject_exports } from "./ThreadObject-BpwkEc5E.mjs";
11
+ import { l as ThreadObject_exports } from "./ThreadObject-gyx8Qq03.mjs";
12
12
  import { t as CloudflareAiGateway_exports } from "./CloudflareAiGateway.mjs";
13
13
  export { Alarm_exports as Alarm, CloudflareAiGateway_exports as CloudflareAiGateway, CloudflareBindings_exports as CloudflareBindings, CloudflareBrowser_exports as CloudflareBrowser, CloudflareCodeMode_exports as CloudflareCodeMode, CloudflareConfig_exports as CloudflareConfig, CloudflareMemory_exports as CloudflareMemory, CloudflareScheduling_exports as CloudflareScheduling, CloudflareSubscriptions_exports as CloudflareSubscriptions, CloudflareThreadClient_exports as CloudflareThreadClient, ThreadObject_exports as ThreadObject, WakeScheduler_exports as WakeScheduler };
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@effect-agent/platform-cloudflare","version":"0.1.0-beta.83","dependencies":{"@effect-agent/core":"0.1.0-beta.83","@effect-agent/engine":"0.1.0-beta.83","@effect-agent/sandbox":"0.1.0-beta.83","@effect-agent/storage-cloudflare":"0.1.0-beta.83","@effect-agent/thread":"0.1.0-beta.83","@effect/platform-browser":"4.0.0-rc.112","@effect/sql-sqlite-do":"4.0.0-rc.112"},"devDependencies":{"@cloudflare/puppeteer":"1.1.0","@cloudflare/vitest-pool-workers":"0.21.3","@cloudflare/workers-types":"5.20260825.1","@effect-agent/capabilities":"0.1.0-beta.83","@effect-agent/testing":"0.1.0-beta.83","@effect/platform-node":"4.0.0-rc.112","@effect/sql-d1":"4.0.0-rc.112","@effect/vitest":"4.0.0-rc.112","effect":"4.0.0-rc.112","effect-cf":"0.40.0","esbuild":"0.28.1","miniflare":"5.20260811.1-alpha","typescript":"7.0.2","vite-plus":"0.3.0","vitest":"4.1.11"},"peerDependencies":{"@cloudflare/puppeteer":"^1.1.0","effect":"^4.0.0-rc.112","effect-cf":"^0.40.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./Alarm":{"types":"./dist/Alarm.d.mts","default":"./dist/Alarm.mjs"},"./BrowserRestCapture":{"types":"./dist/BrowserRestCapture.d.mts","default":"./dist/BrowserRestCapture.mjs"},"./BrowserRestCrawl":{"types":"./dist/BrowserRestCrawl.d.mts","default":"./dist/BrowserRestCrawl.mjs"},"./CloudflareBindings":{"types":"./dist/CloudflareBindings.d.mts","default":"./dist/CloudflareBindings.mjs"},"./CloudflareBrowser":{"types":"./dist/CloudflareBrowser.d.mts","default":"./dist/CloudflareBrowser.mjs"},"./CloudflareCodeMode":{"types":"./dist/CloudflareCodeMode.d.mts","default":"./dist/CloudflareCodeMode.mjs"},"./CloudflareConfig":{"types":"./dist/CloudflareConfig.d.mts","default":"./dist/CloudflareConfig.mjs"},"./CloudflareMemory":{"types":"./dist/CloudflareMemory.d.mts","default":"./dist/CloudflareMemory.mjs"},"./CloudflareScheduling":{"types":"./dist/CloudflareScheduling.d.mts","default":"./dist/CloudflareScheduling.mjs"},"./CloudflareSubscriptions":{"types":"./dist/CloudflareSubscriptions.d.mts","default":"./dist/CloudflareSubscriptions.mjs"},"./CloudflareThreadClient":{"types":"./dist/CloudflareThreadClient.d.mts","default":"./dist/CloudflareThreadClient.mjs"},"./InteractiveBrowser":{"types":"./dist/InteractiveBrowser.d.mts","default":"./dist/InteractiveBrowser.mjs"},"./ProtectedBrowser":{"types":"./dist/ProtectedBrowser.d.mts","default":"./dist/ProtectedBrowser.mjs"},"./ThreadObject":{"types":"./dist/ThreadObject.d.mts","default":"./dist/ThreadObject.mjs"},"./WakeScheduler":{"types":"./dist/WakeScheduler.d.mts","default":"./dist/WakeScheduler.mjs"},"./CloudflareAiGateway":{"types":"./dist/CloudflareAiGateway.d.mts","default":"./dist/CloudflareAiGateway.mjs"}},"description":"Cloudflare Layer assembly for Effect Agent: Durable Objects and Browser Run adapters.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-cloudflare"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json"},"peerDependenciesMeta":{"@cloudflare/puppeteer":{"optional":true}}}
1
+ {"name":"@effect-agent/platform-cloudflare","version":"0.1.0-beta.85","dependencies":{"@effect-agent/core":"0.1.0-beta.85","@effect-agent/engine":"0.1.0-beta.85","@effect-agent/sandbox":"0.1.0-beta.85","@effect-agent/storage-cloudflare":"0.1.0-beta.85","@effect-agent/thread":"0.1.0-beta.85","@effect/platform-browser":"4.0.0-rc.112","@effect/sql-sqlite-do":"4.0.0-rc.112"},"devDependencies":{"@cloudflare/puppeteer":"1.1.0","@cloudflare/vitest-pool-workers":"0.21.3","@cloudflare/workers-types":"5.20260825.1","@effect-agent/capabilities":"0.1.0-beta.85","@effect-agent/testing":"0.1.0-beta.85","@effect/platform-node":"4.0.0-rc.112","@effect/sql-d1":"4.0.0-rc.112","@effect/vitest":"4.0.0-rc.112","effect":"4.0.0-rc.112","effect-cf":"0.41.0","esbuild":"0.28.1","miniflare":"5.20260811.1-alpha","typescript":"7.0.2","vite-plus":"0.3.0","vitest":"4.1.11"},"peerDependencies":{"@cloudflare/puppeteer":"^1.1.0","effect":"^4.0.0-rc.112","effect-cf":"^0.41.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./Alarm":{"types":"./dist/Alarm.d.mts","default":"./dist/Alarm.mjs"},"./BrowserRestCapture":{"types":"./dist/BrowserRestCapture.d.mts","default":"./dist/BrowserRestCapture.mjs"},"./BrowserRestCrawl":{"types":"./dist/BrowserRestCrawl.d.mts","default":"./dist/BrowserRestCrawl.mjs"},"./CloudflareBindings":{"types":"./dist/CloudflareBindings.d.mts","default":"./dist/CloudflareBindings.mjs"},"./CloudflareBrowser":{"types":"./dist/CloudflareBrowser.d.mts","default":"./dist/CloudflareBrowser.mjs"},"./CloudflareCodeMode":{"types":"./dist/CloudflareCodeMode.d.mts","default":"./dist/CloudflareCodeMode.mjs"},"./CloudflareConfig":{"types":"./dist/CloudflareConfig.d.mts","default":"./dist/CloudflareConfig.mjs"},"./CloudflareMemory":{"types":"./dist/CloudflareMemory.d.mts","default":"./dist/CloudflareMemory.mjs"},"./CloudflareScheduling":{"types":"./dist/CloudflareScheduling.d.mts","default":"./dist/CloudflareScheduling.mjs"},"./CloudflareSubscriptions":{"types":"./dist/CloudflareSubscriptions.d.mts","default":"./dist/CloudflareSubscriptions.mjs"},"./CloudflareThreadClient":{"types":"./dist/CloudflareThreadClient.d.mts","default":"./dist/CloudflareThreadClient.mjs"},"./InteractiveBrowser":{"types":"./dist/InteractiveBrowser.d.mts","default":"./dist/InteractiveBrowser.mjs"},"./ProtectedBrowser":{"types":"./dist/ProtectedBrowser.d.mts","default":"./dist/ProtectedBrowser.mjs"},"./ThreadObject":{"types":"./dist/ThreadObject.d.mts","default":"./dist/ThreadObject.mjs"},"./WakeScheduler":{"types":"./dist/WakeScheduler.d.mts","default":"./dist/WakeScheduler.mjs"},"./CloudflareAiGateway":{"types":"./dist/CloudflareAiGateway.d.mts","default":"./dist/CloudflareAiGateway.mjs"}},"description":"Cloudflare Layer assembly for Effect Agent: Durable Objects and Browser Run adapters.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-cloudflare"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json"},"peerDependenciesMeta":{"@cloudflare/puppeteer":{"optional":true}}}
package/src/Alarm.ts CHANGED
@@ -545,12 +545,12 @@ export class ThreadMaintenance extends Context.Service<
545
545
 
546
546
  // A broken disposable index still needs a retry alarm and must not prevent startup.
547
547
  const projectionDeadline = projection.pendingDeadline.pipe(
548
- Effect.catchCause((cause) =>
549
- Cause.hasInterrupts(cause)
550
- ? Effect.failCause(cause)
551
- : Effect.logError("Thread projection deadline unavailable", cause).pipe(
552
- Effect.as(Option.some(0)),
553
- ),
548
+ Effect.catchCauseIf(
549
+ (cause) => !Cause.hasInterrupts(cause),
550
+ (cause) =>
551
+ Effect.logError("Thread projection deadline unavailable", cause).pipe(
552
+ Effect.as(Option.some(0)),
553
+ ),
554
554
  ),
555
555
  );
556
556
 
@@ -319,7 +319,7 @@ const makeCrawl =
319
319
  );
320
320
  }
321
321
 
322
- return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(schema))(bodyText).pipe(
322
+ return yield* Schema.decodeEffect(Schema.fromJsonString(schema))(bodyText).pipe(
323
323
  Effect.mapError((cause) =>
324
324
  protocolError(
325
325
  "Browser Run returned a malformed crawl response",
@@ -1,6 +1,7 @@
1
1
  import { type ThreadId } from "@effect-agent/core/Identifiers";
2
2
  import { type ProducerId } from "@effect-agent/thread/Records";
3
3
  import { Context, Effect, Layer, Predicate, Schema } from "effect";
4
+ import { RpcTargets } from "effect-cf";
4
5
 
5
6
  /**
6
7
  * Cloudflare platform bindings as Effect services (DEPLOY-010: "Cloudflare platform bindings
@@ -58,7 +59,8 @@ export type ThreadObjectClient = Omit<ThreadObjectRpc, keyof Rpc.DurableObjectBr
58
59
  /**
59
60
  * Deterministic logical Thread placement. The native adapter uses `idFromName(threadId)`;
60
61
  * a shared application owner can bind that logical identity into its RPC adapter instead.
61
- * Lookup performs no I/O and grants no authority. Every call resolves its target afresh.
62
+ * Lookup performs no I/O and grants no authority. Calls reuse a native target within
63
+ * one invocation; incoming requests and durable retries acquire their own targets.
62
64
  */
63
65
  export class ThreadObjectNamespace extends Context.Service<
64
66
  ThreadObjectNamespace,
@@ -79,6 +81,23 @@ export class ThreadObjectNamespace extends Context.Service<
79
81
  }
80
82
  }
81
83
 
84
+ /** Invoke a placed Thread using the current invocation's native RPC channel. */
85
+ export const callThreadObject = Effect.fn("callThreadObject")(function* <A, E>(
86
+ threadId: ThreadId,
87
+ invoke: (target: ThreadObjectClient) => Promise<A>,
88
+ onError: (cause: unknown) => E,
89
+ ): Effect.fn.Return<A, E, ThreadObjectNamespace> {
90
+ const namespace = yield* ThreadObjectNamespace;
91
+
92
+ const target = yield* RpcTargets.get(namespace, threadId, () => namespace.get(threadId)).pipe(
93
+ Effect.mapError(onError),
94
+ );
95
+
96
+ return yield* Effect.tryPromise({ try: () => invoke(target), catch: onError }).pipe(
97
+ Effect.tapCause(() => RpcTargets.invalidate(target)),
98
+ );
99
+ });
100
+
82
101
  /**
83
102
  * Narrow one `env` member to a `DurableObjectNamespace`. `env` is an untyped platform value,
84
103
  * and a namespace binding is a host object no Schema can decode, so this is the documented
@@ -40,6 +40,7 @@ import { Clock, Context, Effect, Layer, Schema } from "effect";
40
40
  import {
41
41
  DurableObject as EffectCfDurableObject,
42
42
  DurableObjectState,
43
+ RpcTargets,
43
44
  type WorkerEnvironment,
44
45
  } from "effect-cf";
45
46
 
@@ -70,7 +71,7 @@ const makeMemoryClient = Effect.fn("CloudflareMemoryClient.make")(function* <
70
71
  principal: Principal,
71
72
  rpcLimits: MemoryRpcLimits = defaultMemoryRpcLimits,
72
73
  ) {
73
- const validated = yield* Schema.decodeUnknownEffect(MemoryRpcLimits)(rpcLimits).pipe(
74
+ const validated = yield* Schema.decodeEffect(MemoryRpcLimits)(rpcLimits).pipe(
74
75
  Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
75
76
  );
76
77
 
@@ -78,23 +79,36 @@ const makeMemoryClient = Effect.fn("CloudflareMemoryClient.make")(function* <
78
79
  Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
79
80
  );
80
81
 
81
- principal = yield* Schema.decodeUnknownEffect(Principal)(principal).pipe(
82
+ principal = yield* Schema.decodeEffect(Principal)(principal).pipe(
82
83
  Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
83
84
  );
84
85
  const { namespace } = yield* MemoryObjectNamespace;
85
86
 
86
87
  const call = Effect.fn("CloudflareMemoryClient.call")(function* (request: MemoryOwnerRequest) {
87
- const decoded = yield* Schema.decodeUnknownEffect(MemoryOwnerRequest)(request).pipe(
88
+ const decoded = yield* Schema.decodeEffect(MemoryOwnerRequest)(request).pipe(
88
89
  Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
89
90
  );
90
91
 
91
92
  const encoded = yield* encodeMemoryWire(MemoryOwnerRequest, decoded, validated.maxRequestBytes);
92
93
 
94
+ const unavailable = (cause: unknown) => {
95
+ const error = MemoryRpcError.make({ reason: "unavailable" });
96
+
97
+ error.cause = cause;
98
+
99
+ return error;
100
+ };
101
+
102
+ const address = memoryObjectName(bound.namespace);
103
+
104
+ const target = yield* RpcTargets.get(namespace, address, () =>
105
+ namespace.get(namespace.idFromName(address)),
106
+ ).pipe(Effect.mapError(unavailable));
107
+
93
108
  const raw = yield* Effect.tryPromise({
94
- try: () =>
95
- namespace.get(namespace.idFromName(memoryObjectName(bound.namespace))).memory(encoded),
96
- catch: () => MemoryRpcError.make({ reason: "unavailable" }),
97
- });
109
+ try: () => target.memory(encoded),
110
+ catch: unavailable,
111
+ }).pipe(Effect.tapCause(() => RpcTargets.invalidate(target)));
98
112
 
99
113
  const response = yield* decodeMemoryWire(MemoryOwnerResponse, raw, validated.maxResponseBytes);
100
114
 
@@ -120,7 +134,7 @@ const makeMemoryClient = Effect.fn("CloudflareMemoryClient.make")(function* <
120
134
  lookup: MemoryLookup,
121
135
  limits: MemoryRecallLimits,
122
136
  ) {
123
- limits = yield* Schema.decodeUnknownEffect(MemoryRecallLimits)(limits).pipe(
137
+ limits = yield* Schema.decodeEffect(MemoryRecallLimits)(limits).pipe(
124
138
  Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
125
139
  );
126
140
  const timeoutMillis = Math.min(validated.timeoutMillis, limits.timeoutMillis);
@@ -381,9 +395,9 @@ const makeMemoryObject = <E>(
381
395
  const state = yield* DurableObjectState.DurableObjectState;
382
396
  const scope = yield* Effect.scope;
383
397
 
384
- yield* Schema.decodeUnknownEffect(MemoryRpcLimits)(
385
- options.rpcLimits ?? defaultMemoryRpcLimits,
386
- ).pipe(Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })));
398
+ yield* Schema.decodeEffect(MemoryRpcLimits)(options.rpcLimits ?? defaultMemoryRpcLimits).pipe(
399
+ Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
400
+ );
387
401
 
388
402
  return yield* state.blockConcurrencyWhile(Layer.buildWithScope(application, scope));
389
403
  }),
@@ -40,6 +40,7 @@ import { Clock, Context, DateTime, Effect, Layer, Schema } from "effect";
40
40
  import {
41
41
  DurableObject as EffectCfDurableObject,
42
42
  DurableObjectAlarm,
43
+ RpcTargets,
43
44
  DurableObjectState as EffectCfDurableObjectState,
44
45
  type WorkerEnvironment,
45
46
  } from "effect-cf";
@@ -198,14 +199,27 @@ export class CloudflareSchedulingClient {
198
199
  ),
199
200
  );
200
201
 
202
+ const unavailable = (cause: unknown) => {
203
+ const error = ScheduleStorageError.make({
204
+ operation: "call Schedule Owner",
205
+ reason: "unavailable",
206
+ });
207
+
208
+ error.cause = cause;
209
+
210
+ return error;
211
+ };
212
+
213
+ const address = scheduleOwnerKey(owner);
214
+
215
+ const target = yield* RpcTargets.get(namespace, address, () =>
216
+ namespace.get(namespace.idFromName(address)),
217
+ ).pipe(Effect.mapError(unavailable));
218
+
201
219
  const raw = yield* Effect.tryPromise({
202
- try: () => namespace.get(namespace.idFromName(scheduleOwnerKey(owner))).schedule(encoded),
203
- catch: () =>
204
- ScheduleStorageError.make({
205
- operation: "call Schedule Owner",
206
- reason: "unavailable",
207
- }),
208
- });
220
+ try: () => target.schedule(encoded),
221
+ catch: unavailable,
222
+ }).pipe(Effect.tapCause(() => RpcTargets.invalidate(target)));
209
223
 
210
224
  const response = yield* decodeScheduleOwnerResponse(raw).pipe(
211
225
  Effect.mapError(() =>
@@ -387,7 +401,7 @@ const decodeOwnerName = Effect.fn("decodeScheduleOwnerName")(function* (
387
401
  });
388
402
  }
389
403
 
390
- const tuple = yield* Schema.decodeUnknownEffect(
404
+ const tuple = yield* Schema.decodeEffect(
391
405
  Schema.fromJsonString(Schema.Tuple([Schema.String, Schema.String])),
392
406
  )(name).pipe(
393
407
  Effect.mapError(() =>
@@ -395,7 +409,7 @@ const decodeOwnerName = Effect.fn("decodeScheduleOwnerName")(function* (
395
409
  ),
396
410
  );
397
411
 
398
- return yield* Schema.decodeUnknownEffect(ScheduleOwner)({
412
+ return yield* Schema.decodeEffect(ScheduleOwner)({
399
413
  tenantId: tuple[0],
400
414
  ownerId: tuple[1],
401
415
  }).pipe(
@@ -140,7 +140,7 @@ export const makeSubscriptionPartitionAlarmHandler = Effect.fn(
140
140
  message: "Ancillary alarm tag mismatch",
141
141
  });
142
142
 
143
- const payload = yield* Schema.decodeUnknownEffect(options.payload)(event.payload).pipe(
143
+ const payload = yield* Schema.decodeEffect(options.payload)(event.payload).pipe(
144
144
  Effect.mapError(() =>
145
145
  SubscriptionAlarmProtocolError.make({ message: "Invalid ancillary alarm payload" }),
146
146
  ),
@@ -149,10 +149,10 @@ export const makeSubscriptionPartitionAlarmHandler = Effect.fn(
149
149
  yield* options.handle({ ...event, payload });
150
150
  }).pipe(
151
151
  Effect.scoped,
152
- Effect.timeout(options.timeoutMillis),
153
- Effect.catchTag("TimeoutError", () =>
154
- SubscriptionAlarmExtensionError.make({ code: "timeout" }),
155
- ),
152
+ Effect.timeoutOrElse({
153
+ duration: options.timeoutMillis,
154
+ orElse: () => SubscriptionAlarmExtensionError.make({ code: "timeout" }),
155
+ }),
156
156
  Effect.provideContext(services),
157
157
  ),
158
158
  };
@@ -629,7 +629,7 @@ const decodePartitionName = Effect.fn("decodeSubscriptionPartitionName")(functio
629
629
  });
630
630
  }
631
631
 
632
- const tuple = yield* Schema.decodeUnknownEffect(
632
+ const tuple = yield* Schema.decodeEffect(
633
633
  Schema.fromJsonString(Schema.Tuple([Schema.String, Schema.String])),
634
634
  )(name).pipe(
635
635
  Effect.mapError(() =>
@@ -639,7 +639,7 @@ const decodePartitionName = Effect.fn("decodeSubscriptionPartitionName")(functio
639
639
  ),
640
640
  );
641
641
 
642
- return yield* Schema.decodeUnknownEffect(SourcePartition)({
642
+ return yield* Schema.decodeEffect(SourcePartition)({
643
643
  tenantId: tuple[0],
644
644
  address: tuple[1],
645
645
  }).pipe(
@@ -860,10 +860,10 @@ const alarmHandler = (limits: SubscriptionLimits) =>
860
860
 
861
861
  return yield* handler.handle(event).pipe(
862
862
  Effect.scoped,
863
- Effect.timeout(MAX_ANCILLARY_ALARM_MILLIS),
864
- Effect.catchTag("TimeoutError", () =>
865
- SubscriptionAlarmExtensionError.make({ code: "timeout" }),
866
- ),
863
+ Effect.timeoutOrElse({
864
+ duration: MAX_ANCILLARY_ALARM_MILLIS,
865
+ orElse: () => SubscriptionAlarmExtensionError.make({ code: "timeout" }),
866
+ }),
867
867
  );
868
868
  }
869
869
  if (event.id !== SUBSCRIPTION_ALARM_ID) {
@@ -884,13 +884,13 @@ const alarmHandler = (limits: SubscriptionLimits) =>
884
884
  yield* alarmControl.prearm((yield* Clock.currentTimeMillis) + limits.retryMillis);
885
885
 
886
886
  const pass = yield* driver.runDue.pipe(
887
- Effect.catchCause((cause) =>
888
- Cause.hasInterrupts(cause)
889
- ? Effect.failCause(cause)
890
- : Clock.currentTimeMillis.pipe(
891
- Effect.flatMap((time) => alarmControl.prearm(time + limits.retryMillis)),
892
- Effect.andThen(Effect.failCause(cause)),
893
- ),
887
+ Effect.catchCauseIf(
888
+ (cause) => !Cause.hasInterrupts(cause),
889
+ (cause) =>
890
+ Clock.currentTimeMillis.pipe(
891
+ Effect.flatMap((time) => alarmControl.prearm(time + limits.retryMillis)),
892
+ Effect.andThen(Effect.failCause(cause)),
893
+ ),
894
894
  ),
895
895
  );
896
896
 
@@ -1,6 +1,6 @@
1
1
  import { AgentInputError } from "@effect-agent/core/AgentError";
2
2
  import { AgentId, type ThreadId } from "@effect-agent/core/Identifiers";
3
- import { MessageAdmission } from "@effect-agent/core/Messaging";
3
+ import { InputMessage } from "@effect-agent/core/Messaging";
4
4
  import { DigestError } from "@effect-agent/thread/Digest";
5
5
  import {
6
6
  Receipt,
@@ -48,7 +48,11 @@ import { Context, Crypto, Duration, Effect, Layer, Schema } from "effect";
48
48
  import { RpcTracing } from "effect-cf";
49
49
 
50
50
  import { DurableAlarmError } from "./Alarm.ts";
51
- import { ThreadObjectNamespace, type ThreadObjectRpc } from "./CloudflareBindings.ts";
51
+ import {
52
+ callThreadObject,
53
+ ThreadObjectNamespace,
54
+ type ThreadObjectRpc,
55
+ } from "./CloudflareBindings.ts";
52
56
  import { AdmissionLimitExceeded } from "./CloudflareConfig.ts";
53
57
  import { cloudflareFailureSignals, safeCauseMessage } from "./internal/boundary.ts";
54
58
 
@@ -117,7 +121,7 @@ export class SubmitRequest extends Schema.Class<SubmitRequest>(
117
121
  admissionGroup: Schema.optionalKey(AdmissionGroup),
118
122
  admissionFence: Schema.optionalKey(AdmissionFence),
119
123
  workerAdmission: Schema.optionalKey(WorkerAdmission),
120
- messageAdmission: Schema.optionalKey(MessageAdmission),
124
+ messageAdmission: Schema.optionalKey(InputMessage),
121
125
  definitions: DefinitionDigests,
122
126
  inputPayload: PersistedJson,
123
127
  }) {}
@@ -445,7 +449,8 @@ export class CloudflareThreadClient extends Context.Service<
445
449
  ThreadObjectNamespace | Crypto.Crypto
446
450
  > = Layer.effect(CloudflareThreadClient)(
447
451
  Effect.gen(function* () {
448
- const { get, rpcTracing } = yield* ThreadObjectNamespace;
452
+ const namespace = yield* ThreadObjectNamespace;
453
+ const { rpcTracing } = namespace;
449
454
  const crypto = yield* Crypto.Crypto;
450
455
 
451
456
  const call = Effect.fn(
@@ -458,13 +463,10 @@ export class CloudflareThreadClient extends Context.Service<
458
463
  const traceArgs =
459
464
  rpcTracing === undefined ? [] : yield* RpcTracing.withRpcTraceContext([]);
460
465
 
461
- const raw = yield* Effect.tryPromise({
462
- try: () => {
463
- const stub = get(threadId);
464
-
465
- return stub[hostRpcMethods[operation]](encoded, ...traceArgs);
466
- },
467
- catch: (cause) =>
466
+ const raw = yield* callThreadObject(
467
+ threadId,
468
+ (stub) => stub[hostRpcMethods[operation]](encoded, ...traceArgs),
469
+ (cause) =>
468
470
  ThreadClientError.make({
469
471
  threadId,
470
472
  message: boundHostDiagnostic(
@@ -476,7 +478,7 @@ export class CloudflareThreadClient extends Context.Service<
476
478
  cause,
477
479
  ...cloudflareFailureSignals(cause),
478
480
  }),
479
- });
481
+ ).pipe(Effect.provideService(ThreadObjectNamespace, namespace));
480
482
 
481
483
  return yield* decodeHostResponse(raw).pipe(
482
484
  Effect.mapError((error): HostProtocolError =>
@@ -36,7 +36,6 @@ import {
36
36
  Duration,
37
37
  Effect,
38
38
  Layer,
39
- Option,
40
39
  Redacted,
41
40
  Ref,
42
41
  Schema,
@@ -231,7 +230,7 @@ export class BrowserRunViewport extends Schema.Class<BrowserRunViewport>("Browse
231
230
  ) {}
232
231
 
233
232
  const decodeViewport = (input: BrowserRunViewport) =>
234
- Schema.decodeUnknownEffect(BrowserRunViewport)(input, { onExcessProperty: "error" }).pipe(
233
+ Schema.decodeEffect(BrowserRunViewport)(input, { onExcessProperty: "error" }).pipe(
235
234
  Effect.mapError(() => policyError("The browser viewport is malformed")),
236
235
  // Copy only presentation fields, including for Schema class instances.
237
236
  Effect.map((viewport) => ({
@@ -1043,7 +1042,7 @@ const makeProductionPage = (page: Page): BrowserRunInteractivePage => {
1043
1042
  ),
1044
1043
  );
1045
1044
 
1046
- if (observation._tag === "Text") Schema.decodeUnknownSync(PageObservation)(observation.text);
1045
+ if (observation._tag === "Text") Schema.decodeSync(PageObservation)(observation.text);
1047
1046
 
1048
1047
  return observation;
1049
1048
  },
@@ -1177,7 +1176,7 @@ const snapshotPolicy = Effect.fn("BrowserRunInteractive.snapshotPolicy")(functio
1177
1176
  InteractiveBrowserPolicySnapshot,
1178
1177
  InteractiveBrowserPolicyDeniedError | InteractiveBrowserUnsupportedError
1179
1178
  > {
1180
- const decoded = yield* Schema.decodeUnknownEffect(InteractiveBrowserPolicy)(input).pipe(
1179
+ const decoded = yield* Schema.decodeEffect(InteractiveBrowserPolicy)(input).pipe(
1181
1180
  Effect.mapError(() => policyError("The interactive browser policy is malformed")),
1182
1181
  );
1183
1182
 
@@ -1651,15 +1650,13 @@ const makeHandle = Effect.fn("BrowserRunInteractive.makeHandle")(function* (
1651
1650
  }),
1652
1651
  )
1653
1652
  .pipe(
1654
- Effect.flatMap((result) =>
1655
- Option.isSome(result)
1656
- ? Effect.succeed(result.value)
1657
- : Effect.fail(
1658
- InteractiveBrowserBusyError.make({
1659
- implementation: browserRunInteractiveImplementation,
1660
- message: "The browser handle already has an operation in flight",
1661
- }),
1662
- ),
1653
+ Effect.flatMap(
1654
+ Effect.fromOption(() =>
1655
+ InteractiveBrowserBusyError.make({
1656
+ implementation: browserRunInteractiveImplementation,
1657
+ message: "The browser handle already has an operation in flight",
1658
+ }),
1659
+ ),
1663
1660
  ),
1664
1661
  );
1665
1662
 
@@ -1714,7 +1711,7 @@ const makeHandle = Effect.fn("BrowserRunInteractive.makeHandle")(function* (
1714
1711
  });
1715
1712
  }
1716
1713
 
1717
- return yield* Schema.decodeUnknownEffect(BrowserTextResult)({
1714
+ return yield* Schema.decodeEffect(BrowserTextResult)({
1718
1715
  text: observation.text,
1719
1716
  }).pipe(
1720
1717
  Effect.mapError((cause) =>
@@ -1748,7 +1745,7 @@ const makeHandle = Effect.fn("BrowserRunInteractive.makeHandle")(function* (
1748
1745
  }),
1749
1746
  ),
1750
1747
  screenshot: (request) =>
1751
- Schema.decodeUnknownEffect(BrowserScreenshotRequest)(request).pipe(
1748
+ Schema.decodeEffect(BrowserScreenshotRequest)(request).pipe(
1752
1749
  Effect.mapError(() => policyError("The browser screenshot request is malformed")),
1753
1750
  Effect.flatMap((decoded) =>
1754
1751
  run(
@@ -1771,7 +1768,7 @@ const makeHandle = Effect.fn("BrowserRunInteractive.makeHandle")(function* (
1771
1768
  });
1772
1769
  }
1773
1770
 
1774
- return yield* Schema.decodeUnknownEffect(PageScreenshotResult)({
1771
+ return yield* Schema.decodeEffect(PageScreenshotResult)({
1775
1772
  implementation: browserRunInteractiveImplementation,
1776
1773
  mediaType: "image/png",
1777
1774
  bytes: new Uint8Array(bytes),
@@ -1786,7 +1783,7 @@ const makeHandle = Effect.fn("BrowserRunInteractive.makeHandle")(function* (
1786
1783
  ),
1787
1784
  ),
1788
1785
  scroll: (request) =>
1789
- Schema.decodeUnknownEffect(BrowserScrollRequest)(request).pipe(
1786
+ Schema.decodeEffect(BrowserScrollRequest)(request).pipe(
1790
1787
  Effect.mapError(() => policyError("The browser scroll request is malformed")),
1791
1788
  Effect.flatMap((decoded) =>
1792
1789
  run(
@@ -2209,7 +2206,7 @@ const makeHostService = (
2209
2206
  ),
2210
2207
  ),
2211
2208
  getLiveView: (request) =>
2212
- Schema.decodeUnknownEffect(BrowserRunLiveViewRequest)(request).pipe(
2209
+ Schema.decodeEffect(BrowserRunLiveViewRequest)(request).pipe(
2213
2210
  Effect.mapError(() => policyError("The Live View request is malformed")),
2214
2211
  Effect.flatMap((decoded) =>
2215
2212
  runtime.run(
@@ -2222,7 +2219,7 @@ const makeHostService = (
2222
2219
  "Cloudflare returned a malformed Live View response",
2223
2220
  ).pipe(
2224
2221
  Effect.flatMap((observation) =>
2225
- Schema.decodeUnknownEffect(BrowserRunLiveViewResult)({
2222
+ Schema.decodeEffect(BrowserRunLiveViewResult)({
2226
2223
  devtoolsFrontendUrl: Redacted.make(observation.devtoolsFrontendUrl),
2227
2224
  }).pipe(
2228
2225
  Effect.mapError(() =>
@@ -2236,7 +2233,7 @@ const makeHostService = (
2236
2233
  ),
2237
2234
  ),
2238
2235
  handoff: (request) =>
2239
- Schema.decodeUnknownEffect(BrowserRunHandoffRequest)(request).pipe(
2236
+ Schema.decodeEffect(BrowserRunHandoffRequest)(request).pipe(
2240
2237
  Effect.mapError(() => policyError("The browser handoff request is malformed")),
2241
2238
  Effect.flatMap((decoded) =>
2242
2239
  runtime.run(
@@ -2249,7 +2246,7 @@ const makeHostService = (
2249
2246
  "Cloudflare returned a malformed browser handoff response",
2250
2247
  ).pipe(
2251
2248
  Effect.flatMap((observation) =>
2252
- Schema.decodeUnknownEffect(BrowserRunHandoffResult)({
2249
+ Schema.decodeEffect(BrowserRunHandoffResult)({
2253
2250
  handoffId: Redacted.make(observation.handoffId),
2254
2251
  }).pipe(
2255
2252
  Effect.mapError(() =>
@@ -2272,7 +2269,7 @@ const makeHostService = (
2272
2269
  "Cloudflare returned a malformed browser handoff state",
2273
2270
  ).pipe(
2274
2271
  Effect.flatMap((observation) =>
2275
- Schema.decodeUnknownEffect(BrowserRunHandoffState)({
2272
+ Schema.decodeEffect(BrowserRunHandoffState)({
2276
2273
  active: observation.active,
2277
2274
  ...(observation.handoffId === undefined
2278
2275
  ? {}
@@ -3,7 +3,11 @@ import { makeWakeSubscriptionHub, WakeScheduler } from "@effect-agent/thread/Wak
3
3
  import { Effect, Layer, PubSub, Schema, Stream } from "effect";
4
4
 
5
5
  import { DurableAlarmService } from "./Alarm.ts";
6
- import { ThreadObjectPlacement, ThreadObjectNamespace } from "./CloudflareBindings.ts";
6
+ import {
7
+ callThreadObject,
8
+ ThreadObjectPlacement,
9
+ ThreadObjectNamespace,
10
+ } from "./CloudflareBindings.ts";
7
11
  import { safeCauseMessage } from "./internal/boundary.ts";
8
12
 
9
13
  /**
@@ -41,7 +45,7 @@ export const cloudflareWakeSchedulerLayer: Layer.Layer<
41
45
  Effect.gen(function* () {
42
46
  const alarm = yield* DurableAlarmService;
43
47
  const placement = yield* ThreadObjectPlacement;
44
- const { get } = yield* ThreadObjectNamespace;
48
+ const namespace = yield* ThreadObjectNamespace;
45
49
  const hints = yield* PubSub.sliding<ThreadId>(WAKE_BUFFER_CAPACITY);
46
50
  const progress = yield* makeWakeSubscriptionHub;
47
51
 
@@ -60,15 +64,17 @@ export const cloudflareWakeSchedulerLayer: Layer.Layer<
60
64
  );
61
65
 
62
66
  const notifyRemote = (threadId: ThreadId) =>
63
- Effect.tryPromise({
64
- try: () => get(threadId).wake(),
65
- catch: (cause) =>
67
+ callThreadObject(
68
+ threadId,
69
+ (target) => target.wake(),
70
+ (cause) =>
66
71
  RemoteWakeDropped.make({
67
72
  threadId,
68
73
  message: safeCauseMessage(cause, "The remote wake failed without a diagnostic"),
69
74
  cause,
70
75
  }),
71
- }).pipe(
76
+ ).pipe(
77
+ Effect.provideService(ThreadObjectNamespace, namespace),
72
78
  Effect.catch((error) =>
73
79
  Effect.logWarning(`CloudflareWakeScheduler: remote wake of ${threadId} dropped`, error),
74
80
  ),
@@ -107,7 +107,7 @@ export class BrowserRunSessionLifecycle extends Context.Service<
107
107
  });
108
108
 
109
109
  if (response.status === 404) {
110
- const absent = Schema.decodeUnknownOption(Schema.fromJsonString(Absent))(body, {
110
+ const absent = Schema.decodeOption(Schema.fromJsonString(Absent))(body, {
111
111
  onExcessProperty: "error",
112
112
  });
113
113
 
@@ -116,16 +116,16 @@ export class BrowserRunSessionLifecycle extends Context.Service<
116
116
  return yield* new BrowserRunCleanupError({ reason: "malformed" });
117
117
  }
118
118
  if (method === "DELETE") {
119
- const result = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Closed))(
120
- body,
121
- ).pipe(Effect.mapError(() => new BrowserRunCleanupError({ reason: "malformed" })));
119
+ const result = yield* Schema.decodeEffect(Schema.fromJsonString(Closed))(body).pipe(
120
+ Effect.mapError(() => new BrowserRunCleanupError({ reason: "malformed" })),
121
+ );
122
122
 
123
123
  return result.status === "closed";
124
124
  }
125
125
 
126
- const result = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Metadata))(
127
- body,
128
- ).pipe(Effect.mapError(() => new BrowserRunCleanupError({ reason: "malformed" })));
126
+ const result = yield* Schema.decodeEffect(Schema.fromJsonString(Metadata))(body).pipe(
127
+ Effect.mapError(() => new BrowserRunCleanupError({ reason: "malformed" })),
128
+ );
129
129
 
130
130
  if (result.sessionId !== sessionId)
131
131
  return yield* new BrowserRunCleanupError({ reason: "malformed" });
@@ -135,7 +135,7 @@ export class BrowserRunSessionLifecycle extends Context.Service<
135
135
 
136
136
  const close = Effect.fn("BrowserRunSessionLifecycle.close")(
137
137
  function* (sessionId: Redacted.Redacted<string>) {
138
- const id = yield* Schema.decodeUnknownEffect(Identity)(Redacted.value(sessionId)).pipe(
138
+ const id = yield* Schema.decodeEffect(Identity)(Redacted.value(sessionId)).pipe(
139
139
  Effect.mapError(() => new BrowserRunCleanupError({ reason: "configuration" })),
140
140
  );
141
141