@effect-agent/platform-cloudflare 0.1.0-beta.8 → 0.1.0-beta.80

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 (100) hide show
  1. package/dist/Alarm.d.mts +183 -0
  2. package/dist/Alarm.mjs +454 -0
  3. package/dist/Alarm.mjs.map +1 -0
  4. package/dist/BrowserRestCapture.d.mts +35 -0
  5. package/dist/BrowserRestCapture.mjs +238 -0
  6. package/dist/BrowserRestCapture.mjs.map +1 -0
  7. package/dist/BrowserRestCrawl.d.mts +17 -0
  8. package/dist/BrowserRestCrawl.mjs +244 -0
  9. package/dist/BrowserRestCrawl.mjs.map +1 -0
  10. package/dist/CloudflareAiGateway.d.mts +64 -0
  11. package/dist/CloudflareAiGateway.mjs +70 -0
  12. package/dist/CloudflareAiGateway.mjs.map +1 -0
  13. package/dist/CloudflareBindings.d.mts +112 -0
  14. package/dist/CloudflareBindings.mjs +101 -0
  15. package/dist/CloudflareBindings.mjs.map +1 -0
  16. package/dist/CloudflareBrowser-D4YqaTAl.mjs +495 -0
  17. package/dist/CloudflareBrowser-D4YqaTAl.mjs.map +1 -0
  18. package/dist/CloudflareBrowser-DJq53niJ.d.mts +80 -0
  19. package/dist/CloudflareBrowser.d.mts +2 -0
  20. package/dist/CloudflareBrowser.mjs +2 -0
  21. package/dist/CloudflareCodeMode.d.mts +44 -0
  22. package/dist/CloudflareCodeMode.mjs +616 -0
  23. package/dist/CloudflareCodeMode.mjs.map +1 -0
  24. package/dist/CloudflareConfig-f3CqTel1.d.mts +101 -0
  25. package/dist/CloudflareConfig.d.mts +2 -0
  26. package/dist/CloudflareConfig.mjs +122 -0
  27. package/dist/CloudflareConfig.mjs.map +1 -0
  28. package/dist/CloudflareMemory.d.mts +142 -0
  29. package/dist/CloudflareMemory.mjs +195 -0
  30. package/dist/CloudflareMemory.mjs.map +1 -0
  31. package/dist/CloudflareScheduling.d.mts +52 -0
  32. package/dist/CloudflareScheduling.mjs +389 -0
  33. package/dist/CloudflareScheduling.mjs.map +1 -0
  34. package/dist/CloudflareSubscriptions.d.mts +87 -0
  35. package/dist/CloudflareSubscriptions.mjs +524 -0
  36. package/dist/CloudflareSubscriptions.mjs.map +1 -0
  37. package/dist/CloudflareThreadClient.d.mts +1286 -0
  38. package/dist/CloudflareThreadClient.mjs +386 -0
  39. package/dist/CloudflareThreadClient.mjs.map +1 -0
  40. package/dist/InteractiveBrowser.d.mts +144 -0
  41. package/dist/InteractiveBrowser.mjs +1090 -0
  42. package/dist/InteractiveBrowser.mjs.map +1 -0
  43. package/dist/ProtectedBrowser.d.mts +65 -0
  44. package/dist/ProtectedBrowser.mjs +859 -0
  45. package/dist/ProtectedBrowser.mjs.map +1 -0
  46. package/dist/ThreadObject-3uDpKfB3.d.mts +816 -0
  47. package/dist/ThreadObject-DGkXv41C.mjs +836 -0
  48. package/dist/ThreadObject-DGkXv41C.mjs.map +1 -0
  49. package/dist/ThreadObject.d.mts +2 -0
  50. package/dist/ThreadObject.mjs +3 -0
  51. package/dist/WakeScheduler.d.mts +23 -0
  52. package/dist/WakeScheduler.mjs +60 -0
  53. package/dist/WakeScheduler.mjs.map +1 -0
  54. package/dist/boundary-BguazVkh.mjs +42 -0
  55. package/dist/boundary-BguazVkh.mjs.map +1 -0
  56. package/dist/browser-session-lifecycle-CUbVfEgP.mjs +84 -0
  57. package/dist/browser-session-lifecycle-CUbVfEgP.mjs.map +1 -0
  58. package/dist/browser-session-lifecycle-DJpqLXy_.d.mts +21 -0
  59. package/dist/index.d.mts +13 -1547
  60. package/dist/index.mjs +13 -1636
  61. package/dist/prepared-admission-DSi55W-7.mjs +73 -0
  62. package/dist/prepared-admission-DSi55W-7.mjs.map +1 -0
  63. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  64. package/package.json +1 -53
  65. package/src/Alarm.ts +953 -0
  66. package/src/BrowserRestCapture.ts +477 -0
  67. package/src/BrowserRestCrawl.ts +540 -0
  68. package/src/CloudflareAiGateway.ts +170 -0
  69. package/src/CloudflareBindings.ts +180 -0
  70. package/src/CloudflareBrowser.ts +15 -0
  71. package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +372 -204
  72. package/src/{config.ts → CloudflareConfig.ts} +9 -8
  73. package/src/CloudflareMemory.ts +406 -0
  74. package/src/CloudflareScheduling.ts +733 -0
  75. package/src/CloudflareSubscriptions.ts +1045 -0
  76. package/src/CloudflareThreadClient.ts +807 -0
  77. package/src/InteractiveBrowser.ts +2357 -0
  78. package/src/ProtectedBrowser.ts +2 -0
  79. package/src/ThreadObject.ts +1179 -0
  80. package/src/{wake-scheduler.ts → WakeScheduler.ts} +23 -23
  81. package/src/index.ts +12 -23
  82. package/src/internal/boundary.ts +55 -0
  83. package/src/internal/browser-quick-action.ts +857 -0
  84. package/src/internal/browser-session-lifecycle.ts +160 -0
  85. package/src/internal/layers.ts +751 -0
  86. package/src/internal/message-delivery.ts +148 -0
  87. package/src/internal/prepared-admission.ts +116 -0
  88. package/src/internal/progress-wait.ts +121 -0
  89. package/src/internal/transport.ts +42 -0
  90. package/src/protected-browser/binding.ts +187 -0
  91. package/src/protected-browser/inspect-frame.ts +125 -0
  92. package/src/protected-browser/native.ts +429 -0
  93. package/src/protected-browser/policy.ts +719 -0
  94. package/dist/index.mjs.map +0 -1
  95. package/src/alarm.ts +0 -334
  96. package/src/bindings.ts +0 -145
  97. package/src/client.ts +0 -646
  98. package/src/conversation-object.ts +0 -768
  99. package/src/layers.ts +0 -376
  100. package/src/transport.ts +0 -38
@@ -1,5 +1,6 @@
1
- import { DEFAULT_OWNERSHIP_LEASE_DURATION, DeploymentId } from "@effect-agent/session";
2
- import { DEFAULT_MAX_STORED_VALUE_BYTES } from "@effect-agent/storage-cloudflare";
1
+ import { DEFAULT_MAX_STORED_VALUE_BYTES } from "@effect-agent/storage-cloudflare/DoStorageConfig";
2
+ import { DeploymentId } from "@effect-agent/thread/Records";
3
+ import { DEFAULT_OWNERSHIP_LEASE_DURATION } from "@effect-agent/thread/SubmissionLedger";
3
4
  import { Context, Duration, Schema } from "effect";
4
5
 
5
6
  /**
@@ -25,7 +26,7 @@ export class CloudflarePlatformConfigError extends Schema.TaggedError<Cloudflare
25
26
  * (deployment spec §8, DEPLOY-007: "Admission has explicit bounded quota and overload
26
27
  * behavior... a typed rejection"). This is the DC analogue of `NodeDurableHost`'s
27
28
  * `AdmissionClosed` host gate: the port surface stays untouched, the refusal happens in the
28
- * Conversation Object's submit entry point before `DurableAgentRuntime.submit` runs, and
29
+ * Thread Object's submit entry point before `DurableAgentRuntime.submit` runs, and
29
30
  * nothing was admitted or written.
30
31
  */
31
32
  export class AdmissionLimitExceeded extends Schema.TaggedError<AdmissionLimitExceeded>()(
@@ -52,13 +53,13 @@ export const CLOUDFLARE_DATABASE_CAP_BYTES = 10_000_000_000;
52
53
  export const DEFAULT_MAX_DATABASE_BYTES = 9_000_000_000;
53
54
 
54
55
  /**
55
- * Explicit bounded admission quotas checked by the Conversation Object BEFORE admission
56
+ * Explicit bounded admission quotas checked by the Thread Object BEFORE admission
56
57
  * (exit gate "resource limits are checked before admission").
57
58
  */
58
59
  export class CloudflareAdmissionLimitsValue extends Schema.Class<CloudflareAdmissionLimitsValue>(
59
60
  "@effect-agent/platform-cloudflare/CloudflareAdmissionLimitsValue",
60
61
  )({
61
- /** Maximum nonterminal Submissions per Conversation lane before new admissions refuse. */
62
+ /** Maximum nonterminal Submissions per Thread lane before new admissions refuse. */
62
63
  maxQueueDepthPerLane: Schema.Int.check(
63
64
  Schema.isGreaterThan(0),
64
65
  Schema.isLessThanOrEqualTo(100_000),
@@ -74,7 +75,7 @@ export class CloudflareAdmissionLimitsValue extends Schema.Class<CloudflareAdmis
74
75
 
75
76
  /**
76
77
  * Validated Cloudflare durable runtime configuration. The producer identity of one
77
- * Conversation Object is `{producerPrefix}:{conversationId}` — stable across incarnations of
78
+ * Thread Object is `{producerPrefix}:{threadId}` — stable across incarnations of
78
79
  * the same deployment, distinct across deployments — and producer-epoch fencing (not the
79
80
  * producer name) remains the correctness authority (DUR-006).
80
81
  */
@@ -82,7 +83,7 @@ export class CloudflareDurableRuntimeConfigValue extends Schema.Class<Cloudflare
82
83
  "@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfigValue",
83
84
  )({
84
85
  deploymentId: DeploymentId,
85
- /** Head of the minted producer identity `{producerPrefix}:{conversationId}`. */
86
+ /** Head of the minted producer identity `{producerPrefix}:{threadId}`. */
86
87
  producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
87
88
  /** Submission ownership lease duration (D5); fences work across Object incarnations. */
88
89
  ownershipLeaseDuration: PositiveMillis,
@@ -119,7 +120,7 @@ export class CloudflareDurableRuntimeConfig extends Context.Service<
119
120
  CloudflareDurableRuntimeConfigValue
120
121
  >()("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfig") {}
121
122
 
122
- /** Documented production defaults applied by `CloudflareDurableRuntime.layer`. */
123
+ /** Documented production defaults applied by `ThreadObject.layer`. */
123
124
  export const CLOUDFLARE_RUNTIME_DEFAULTS = {
124
125
  ownershipLeaseDuration: Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
125
126
  alarmBackoffBase: 100,
@@ -0,0 +1,406 @@
1
+ import * as Memory from "@effect-agent/core/Memory";
2
+ import * as MemoryNamespace from "@effect-agent/core/MemoryNamespace";
3
+ import { MemoryNamespaceAddress } from "@effect-agent/core/MemoryNamespace";
4
+ import { type MemoryLookup, MemoryRecallLimits } from "@effect-agent/core/MemoryReference";
5
+ import { MemoryAccess } from "@effect-agent/core/MemoryRevalidation";
6
+ import {
7
+ type MemoryReader,
8
+ type MemoryWrite,
9
+ MemoryKey,
10
+ MemoryDocument,
11
+ MemoryMutationFailpoint,
12
+ MemoryStorageError,
13
+ MemoryWriter,
14
+ } from "@effect-agent/core/MemoryStore";
15
+ import {
16
+ type MemoryIndexSearch,
17
+ type SemanticMemoryProfile,
18
+ } from "@effect-agent/core/SemanticMemoryIndex";
19
+ import { type SemanticCandidateLimits } from "@effect-agent/core/SemanticMemoryRevalidation";
20
+ import {
21
+ type DoMemoryStorageLimits,
22
+ defaultDoMemoryStorageLimits,
23
+ doMemoryStoreLayerWithFailpoints,
24
+ } from "@effect-agent/storage-cloudflare/DoMemoryStore";
25
+ import {
26
+ type MemoryOwnerAuthorizer,
27
+ decodeMemoryWire,
28
+ defaultMemoryRpcLimits,
29
+ encodeMemoryWire,
30
+ handleMemoryOwnerRequest,
31
+ MemoryOwnerIdentity,
32
+ MemoryOwnerRequest,
33
+ MemoryOwnerResponse,
34
+ MemoryRpcError,
35
+ MemoryRpcLimits,
36
+ type MemoryOwnerFailure,
37
+ } from "@effect-agent/storage-cloudflare/MemoryProtocol";
38
+ import { Principal } from "@effect-agent/thread/SubmissionLedger";
39
+ import { Clock, Context, Effect, Layer, Schema } from "effect";
40
+ import {
41
+ DurableObject as EffectCfDurableObject,
42
+ DurableObjectState,
43
+ type WorkerEnvironment,
44
+ } from "effect-cf";
45
+
46
+ export interface MemoryObjectRpc extends Rpc.DurableObjectBranded {
47
+ memory(encoded: string): Promise<string>;
48
+ }
49
+
50
+ export class MemoryObjectNamespace extends Context.Service<
51
+ MemoryObjectNamespace,
52
+ {
53
+ readonly namespace: DurableObjectNamespace<MemoryObjectRpc>;
54
+ }
55
+ >()("@effect-agent/platform-cloudflare/MemoryObjectNamespace") {}
56
+
57
+ /** Namespace version and identity are already canonicalized by MemoryNamespace. */
58
+ export const memoryObjectName = (namespace: MemoryNamespace.Any): string => namespace.address;
59
+
60
+ /**
61
+ * Effect-native, host-bound memory client. Recall revalidates the entire admitted lookup
62
+ * in one RPC and renders it locally. No retries or per-source splitting occur here.
63
+ * Interrupted callers stop waiting; the owner has its own deadline. A timed-out write
64
+ * may have committed: reconcile by sending the identical operation ID and command.
65
+ */
66
+ const makeMemoryClient = Effect.fn("CloudflareMemoryClient.make")(function* <
67
+ Namespace extends MemoryNamespace.Any,
68
+ >(
69
+ access: MemoryAccess<Namespace>,
70
+ principal: Principal,
71
+ rpcLimits: MemoryRpcLimits = defaultMemoryRpcLimits,
72
+ ) {
73
+ const validated = yield* Schema.decodeUnknownEffect(MemoryRpcLimits)(rpcLimits).pipe(
74
+ Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
75
+ );
76
+
77
+ const bound = yield* Schema.decodeUnknownEffect(MemoryAccess.Wire)(access).pipe(
78
+ Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
79
+ );
80
+
81
+ principal = yield* Schema.decodeUnknownEffect(Principal)(principal).pipe(
82
+ Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
83
+ );
84
+ const { namespace } = yield* MemoryObjectNamespace;
85
+
86
+ const call = Effect.fn("CloudflareMemoryClient.call")(function* (request: MemoryOwnerRequest) {
87
+ const decoded = yield* Schema.decodeUnknownEffect(MemoryOwnerRequest)(request).pipe(
88
+ Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
89
+ );
90
+
91
+ const encoded = yield* encodeMemoryWire(MemoryOwnerRequest, decoded, validated.maxRequestBytes);
92
+
93
+ const raw = yield* Effect.tryPromise({
94
+ try: () =>
95
+ namespace.get(namespace.idFromName(memoryObjectName(bound.namespace))).memory(encoded),
96
+ catch: () => MemoryRpcError.make({ reason: "unavailable" }),
97
+ });
98
+
99
+ const response = yield* decodeMemoryWire(MemoryOwnerResponse, raw, validated.maxResponseBytes);
100
+
101
+ if (response._tag === "Failed") return yield* response.failure;
102
+ if (
103
+ !MemoryNamespace.equals(response.access.namespace, bound.namespace) ||
104
+ response.access.scope !== bound.scope
105
+ )
106
+ return yield* MemoryRpcError.make({ reason: "protocol" });
107
+
108
+ return response;
109
+ });
110
+
111
+ const withinDeadline = <A, E, R>(effect: Effect.Effect<A, E, R>, timeoutMillis: number) =>
112
+ effect.pipe(
113
+ Effect.timeoutOrElse({
114
+ duration: timeoutMillis,
115
+ orElse: () => Effect.fail(MemoryRpcError.make({ reason: "timeout" })),
116
+ }),
117
+ );
118
+
119
+ const revalidate = Effect.fn("CloudflareMemoryClient.revalidate")(function* (
120
+ lookup: MemoryLookup,
121
+ limits: MemoryRecallLimits,
122
+ ) {
123
+ limits = yield* Schema.decodeUnknownEffect(MemoryRecallLimits)(limits).pipe(
124
+ Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
125
+ );
126
+ const timeoutMillis = Math.min(validated.timeoutMillis, limits.timeoutMillis);
127
+
128
+ return yield* Effect.gen(function* () {
129
+ const response = yield* call({
130
+ _tag: "Revalidate",
131
+ version: 1,
132
+ access: bound,
133
+ principal,
134
+ lookup,
135
+ limits,
136
+ deadlineMillis: (yield* Clock.currentTimeMillis) + timeoutMillis,
137
+ });
138
+
139
+ if (response._tag !== "Lookup") return yield* MemoryRpcError.make({ reason: "protocol" });
140
+
141
+ return response.lookup;
142
+ }).pipe((effect) => withinDeadline(effect, timeoutMillis));
143
+ });
144
+
145
+ const change = Effect.fn("CloudflareMemoryClient.change")(function* (
146
+ write: MemoryWrite<Namespace>,
147
+ ) {
148
+ if (!MemoryNamespace.equals(write.key.namespace, bound.namespace))
149
+ return yield* MemoryRpcError.make({ reason: "denied" });
150
+
151
+ return yield* Effect.gen(function* () {
152
+ const response = yield* call({
153
+ _tag: "Change",
154
+ version: 1,
155
+ access: bound,
156
+ principal,
157
+ write,
158
+ deadlineMillis: (yield* Clock.currentTimeMillis) + validated.timeoutMillis,
159
+ });
160
+
161
+ if (response._tag !== "Changed" || response.document.key.id !== write.key.id)
162
+ return yield* MemoryRpcError.make({ reason: "protocol" });
163
+
164
+ return yield* MemoryDocument.restore(access.namespace, response.document);
165
+ }).pipe((effect) => withinDeadline(effect, validated.timeoutMillis));
166
+ });
167
+
168
+ /**
169
+ * Read one exact current document in one owner RPC. Null means absent; withdrawals return
170
+ * tombstones. Denial, unavailable storage and deadlines fail typed, never become absence.
171
+ * Reads begun after an acknowledged write observe it or a later revision. The owner checks
172
+ * exact-key authority and active document scopes; source-dependent provenance policy remains
173
+ * application-owned. No extraction, job draining, embedding, discovery or rendering occurs.
174
+ */
175
+ const get = Effect.fn("CloudflareMemoryClient.get")(function* (key: MemoryKey<Namespace>) {
176
+ const decodedKey = yield* Schema.decodeUnknownEffect(MemoryKey.Wire)(key).pipe(
177
+ Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
178
+ );
179
+
180
+ if (!MemoryNamespace.equals(decodedKey.namespace, bound.namespace))
181
+ return yield* MemoryRpcError.make({ reason: "denied" });
182
+
183
+ return yield* Effect.gen(function* () {
184
+ const response = yield* call({
185
+ _tag: "Get",
186
+ version: 1,
187
+ access: bound,
188
+ principal,
189
+ key: decodedKey,
190
+ deadlineMillis: (yield* Clock.currentTimeMillis) + validated.timeoutMillis,
191
+ });
192
+
193
+ if (
194
+ response._tag !== "Document" ||
195
+ !MemoryNamespace.equals(response.key.namespace, bound.namespace) ||
196
+ response.key.id !== decodedKey.id
197
+ )
198
+ return yield* MemoryRpcError.make({ reason: "protocol" });
199
+ if (response.document === null) return null;
200
+ if (
201
+ response.document.key.id !== decodedKey.id ||
202
+ response.document.source.id !== decodedKey.id ||
203
+ (response.document._tag === "ActiveMemoryDocument" &&
204
+ !response.document.scopes.includes(bound.scope))
205
+ )
206
+ return yield* MemoryRpcError.make({ reason: "protocol" });
207
+
208
+ yield* encodeMemoryWire(MemoryDocument.Wire, response.document, validated.maxSourceBytes);
209
+
210
+ return yield* MemoryDocument.restore(access.namespace, response.document);
211
+ }).pipe((effect) => withinDeadline(effect, validated.timeoutMillis));
212
+ });
213
+
214
+ const revalidateSemantic = Effect.fn("CloudflareMemoryClient.revalidateSemantic")(function* (
215
+ found: MemoryIndexSearch<Namespace>,
216
+ profile: SemanticMemoryProfile,
217
+ limits: SemanticCandidateLimits,
218
+ ) {
219
+ return yield* Effect.gen(function* () {
220
+ const response = yield* call({
221
+ _tag: "RevalidateSemantic",
222
+ version: 1,
223
+ access: bound,
224
+ principal,
225
+ found,
226
+ profile,
227
+ limits,
228
+ deadlineMillis: (yield* Clock.currentTimeMillis) + validated.timeoutMillis,
229
+ });
230
+
231
+ if (response._tag !== "Semantic") return yield* MemoryRpcError.make({ reason: "protocol" });
232
+
233
+ return response.result;
234
+ }).pipe((effect) => withinDeadline(effect, validated.timeoutMillis));
235
+ });
236
+
237
+ /**
238
+ * Revalidate in one owner RPC, then render whole passages within the caller's budget.
239
+ * The bound source is essential: unavailable/stale results and matches that cannot fit
240
+ * fail instead of silently producing empty context. No-match remains successful.
241
+ * The single outcome has sourceId "memory". No embedding or candidate search is performed.
242
+ * Use revalidate with Memory.recall for multiple readers sharing one output budget.
243
+ */
244
+ const recall = Effect.fn("CloudflareMemoryClient.recall")(function* (
245
+ lookup: MemoryLookup,
246
+ limits: MemoryRecallLimits,
247
+ estimateTokens?: (text: string) => number,
248
+ ) {
249
+ return yield* Memory.recall(
250
+ [{ id: "memory", essential: true, read: revalidate(lookup, limits) }],
251
+ limits,
252
+ estimateTokens,
253
+ );
254
+ });
255
+
256
+ return { get, recall, revalidate, revalidateSemantic, change };
257
+ });
258
+
259
+ export const CloudflareMemoryClient = {
260
+ /** Bind access and principal using the MemoryObjectNamespace supplied by the application. */
261
+ make: makeMemoryClient,
262
+ /** Use a resolved Worker or Durable Object binding without manual service provisioning. */
263
+ fromBinding: Effect.fn("CloudflareMemoryClient.fromBinding")(function* <
264
+ Namespace extends MemoryNamespace.Any,
265
+ >(
266
+ binding: DurableObjectNamespace<MemoryObjectRpc>,
267
+ options: {
268
+ readonly access: MemoryAccess<Namespace>;
269
+ readonly principal: Principal;
270
+ readonly rpcLimits?: MemoryRpcLimits;
271
+ },
272
+ ) {
273
+ return yield* makeMemoryClient(options.access, options.principal, options.rpcLimits).pipe(
274
+ Effect.provideService(MemoryObjectNamespace, { namespace: binding }),
275
+ );
276
+ }),
277
+ };
278
+
279
+ /**
280
+ * Optional activity-processor destination. Keeps domain write errors intact; transport,
281
+ * authorization and deadline failures become the existing MemoryStorageError contract.
282
+ * Receipts remain authoritative, including after caller interruption or lost replies.
283
+ */
284
+ export const cloudflareMemoryWriterLayer = (
285
+ access: MemoryAccess,
286
+ principal: Principal,
287
+ limits: MemoryRpcLimits = defaultMemoryRpcLimits,
288
+ ) =>
289
+ Layer.effect(
290
+ MemoryWriter,
291
+ Effect.gen(function* () {
292
+ const client = yield* CloudflareMemoryClient.make(access, principal, limits);
293
+
294
+ return MemoryWriter.fromAdapter({
295
+ change: (write) =>
296
+ client.change(write).pipe(
297
+ Effect.catchTag("MemoryRpcError", (error) =>
298
+ Effect.fail(
299
+ MemoryStorageError.make({
300
+ operation: `memory RPC ${error.reason}`,
301
+ reason:
302
+ error.reason === "unavailable" || error.reason === "timeout"
303
+ ? "unavailable"
304
+ : "invalid-input",
305
+ }),
306
+ ),
307
+ ),
308
+ Effect.catchTag(["MemoryRecallError", "MemoryIndexError", "SemanticMemoryError"], () =>
309
+ Effect.fail(
310
+ MemoryStorageError.make({ operation: "memory RPC response", reason: "corrupt" }),
311
+ ),
312
+ ),
313
+ ),
314
+ });
315
+ }),
316
+ );
317
+
318
+ type OwnerServices = MemoryReader | MemoryWriter | MemoryOwnerAuthorizer | MemoryOwnerIdentity;
319
+
320
+ export interface MemoryObjectInstance extends InstanceType<
321
+ EffectCfDurableObject.DurableObjectClass<Record<never, never>, OwnerServices>
322
+ > {
323
+ memory(encoded: string): Promise<string>;
324
+ }
325
+
326
+ export interface MemoryObjectClass {
327
+ new (ctx: globalThis.DurableObjectState, env: Cloudflare.Env): MemoryObjectInstance;
328
+ }
329
+
330
+ /**
331
+ * Dedicated SQLite owner, independent of Thread lifetimes. The host binds authorization
332
+ * after restoring its namespace definition from MemoryOwnerIdentity. Do not retain
333
+ * cleanup-scoped resources in the host Layer; it lives for the DO incarnation.
334
+ */
335
+ const makeMemoryObject = <E>(
336
+ host: Layer.Layer<
337
+ MemoryOwnerAuthorizer,
338
+ E,
339
+ MemoryOwnerIdentity | DurableObjectState.DurableObjectState | WorkerEnvironment
340
+ >,
341
+ options: {
342
+ readonly storageLimits?: DoMemoryStorageLimits;
343
+ readonly rpcLimits?: MemoryRpcLimits;
344
+ readonly failpoints?: Layer.Layer<
345
+ MemoryMutationFailpoint,
346
+ never,
347
+ DurableObjectState.DurableObjectState
348
+ >;
349
+ } = {},
350
+ ): MemoryObjectClass => {
351
+ const identity = Layer.effect(
352
+ MemoryOwnerIdentity,
353
+ Effect.gen(function* () {
354
+ const state = yield* DurableObjectState.DurableObjectState;
355
+
356
+ const address = yield* Schema.decodeUnknownEffect(MemoryNamespaceAddress)(
357
+ state.raw.id.name,
358
+ ).pipe(Effect.mapError(() => MemoryRpcError.make({ reason: "denied" })));
359
+
360
+ return { namespace: MemoryNamespace.Any.make({ address }) };
361
+ }),
362
+ );
363
+
364
+ const store = Layer.unwrap(
365
+ Effect.map(DurableObjectState.DurableObjectState, (state) =>
366
+ doMemoryStoreLayerWithFailpoints(
367
+ state.raw.storage,
368
+ options.storageLimits ?? defaultDoMemoryStorageLimits,
369
+ ),
370
+ ),
371
+ ).pipe(Layer.provide(options.failpoints ?? MemoryMutationFailpoint.layer));
372
+
373
+ const application = Layer.merge(store, host).pipe(Layer.provideMerge(identity));
374
+
375
+ const runtime: Layer.Layer<
376
+ OwnerServices,
377
+ E | MemoryOwnerFailure,
378
+ DurableObjectState.DurableObjectState | WorkerEnvironment
379
+ > = Layer.effectContext(
380
+ Effect.gen(function* () {
381
+ const state = yield* DurableObjectState.DurableObjectState;
382
+ const scope = yield* Effect.scope;
383
+
384
+ yield* Schema.decodeUnknownEffect(MemoryRpcLimits)(
385
+ options.rpcLimits ?? defaultMemoryRpcLimits,
386
+ ).pipe(Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })));
387
+
388
+ return yield* state.blockConcurrencyWhile(Layer.buildWithScope(application, scope));
389
+ }),
390
+ );
391
+
392
+ const rpc = { memory: (encoded: string) => handleMemoryOwnerRequest(encoded, options.rpcLimits) };
393
+
394
+ return EffectCfDurableObject.make<
395
+ OwnerServices,
396
+ E | MemoryOwnerFailure,
397
+ never,
398
+ never,
399
+ typeof rpc
400
+ >(runtime, { rpc });
401
+ };
402
+
403
+ export const MemoryObject = {
404
+ /** Build the SQLite Durable Object class with the application's owner authorization Layer. */
405
+ make: makeMemoryObject,
406
+ };