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

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 +184 -0
  2. package/dist/Alarm.mjs +451 -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-BpwkEc5E.mjs +836 -0
  47. package/dist/ThreadObject-BpwkEc5E.mjs.map +1 -0
  48. package/dist/ThreadObject-NbFOWK1I.d.mts +816 -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 +967 -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
@@ -0,0 +1,733 @@
1
+ import { AgentId } from "@effect-agent/core/Identifiers";
2
+ import {
3
+ DoScheduleAlarmControl,
4
+ DoScheduleTransaction,
5
+ scheduleStoreLayer,
6
+ } from "@effect-agent/storage-cloudflare/DoScheduleStore";
7
+ import { type DurableSubmitAgent } from "@effect-agent/thread/DurableAgentRuntime";
8
+ import { DefinitionDigests, PersistedJson } from "@effect-agent/thread/Records";
9
+ import {
10
+ ScheduleAuthorizationError,
11
+ type ScheduleAuthorizer,
12
+ ScheduleCapacityError,
13
+ ScheduleConflict,
14
+ ScheduleDestination,
15
+ ScheduleFailpointError,
16
+ ScheduleId,
17
+ type SchedulingLimits,
18
+ ScheduleNotFound,
19
+ ScheduleOwner,
20
+ type ScheduleScope,
21
+ ScheduleScope as ScheduleScopeSchema,
22
+ ScheduleSnapshot,
23
+ ScheduleSnapshotPage as ScheduleSnapshotPageSchema,
24
+ ScheduleStorageError,
25
+ ScheduleTimingRequest,
26
+ ScheduleValidationError,
27
+ defaultSchedulingLimits,
28
+ } from "@effect-agent/thread/Schedule";
29
+ import { scheduleOwnerKey } from "@effect-agent/thread/ScheduleTransition";
30
+ import {
31
+ Scheduling,
32
+ ScheduleDriver,
33
+ type ScheduleManagementFailure,
34
+ ScheduleWakeNoop,
35
+ } from "@effect-agent/thread/Scheduling";
36
+ import { AdmissionFence } from "@effect-agent/thread/SubmissionLedger";
37
+ import { BrowserCrypto } from "@effect/platform-browser";
38
+ import { SqliteClient } from "@effect/sql-sqlite-do";
39
+ import { Clock, Context, DateTime, Effect, Layer, Schema } from "effect";
40
+ import {
41
+ DurableObject as EffectCfDurableObject,
42
+ DurableObjectAlarm,
43
+ DurableObjectState as EffectCfDurableObjectState,
44
+ type WorkerEnvironment,
45
+ } from "effect-cf";
46
+
47
+ import type { ThreadObjectNamespace } from "./CloudflareBindings.ts";
48
+ import { CloudflareThreadClient } from "./CloudflareThreadClient.ts";
49
+ import {
50
+ cloudflarePreparedInputAdmissionLayer,
51
+ cloudflareScheduledInputAdmissionLayer,
52
+ } from "./internal/prepared-admission.ts";
53
+
54
+ const SCHEDULE_ALARM_TAG = "effect-agent/ScheduleOwnerWake";
55
+ const SCHEDULE_ALARM_ID = "driver";
56
+
57
+ const ScheduleAlarmPayload = Schema.Struct({
58
+ schemaVersion: Schema.Literal(1),
59
+ generation: Schema.Int.check(Schema.isGreaterThan(0)),
60
+ });
61
+
62
+ export class ScheduleAlarmProtocolError extends Schema.TaggedError<ScheduleAlarmProtocolError>()(
63
+ "ScheduleAlarmProtocolError",
64
+ { message: Schema.String },
65
+ ) {}
66
+
67
+ const boundedProtocolMessage = (message: string): string =>
68
+ message.length <= 4_096 ? message : `${message.slice(0, 4_093)}...`;
69
+
70
+ export class ScheduleOwnerProtocolError extends Schema.TaggedError<ScheduleOwnerProtocolError>()(
71
+ "ScheduleOwnerProtocolError",
72
+ { message: Schema.String.check(Schema.isMaxLength(4_096)) },
73
+ ) {}
74
+
75
+ const ScheduleMutationRequestFields = {
76
+ schemaVersion: Schema.Literal(1),
77
+ agentId: AgentId,
78
+ input: PersistedJson,
79
+ scope: ScheduleScopeSchema,
80
+ scheduleId: ScheduleId,
81
+ timing: ScheduleTimingRequest,
82
+ destination: ScheduleDestination,
83
+ deliveryPrincipal: ScheduleScopeSchema.fields.principal,
84
+ definitions: DefinitionDigests,
85
+ admissionGroup: Schema.optionalKey(Schema.NonEmptyString.check(Schema.isMaxLength(256))),
86
+ admissionFence: Schema.optionalKey(AdmissionFence),
87
+ };
88
+
89
+ const ScheduleCreateRequest = Schema.TaggedStruct("Create", ScheduleMutationRequestFields);
90
+
91
+ const ScheduleUpdateRequest = Schema.TaggedStruct("Update", {
92
+ ...ScheduleMutationRequestFields,
93
+ expectedRevision: Schema.Int.check(Schema.isGreaterThan(0)),
94
+ });
95
+
96
+ const ScheduleGetRequest = Schema.TaggedStruct("Get", {
97
+ schemaVersion: Schema.Literal(1),
98
+ scope: ScheduleScopeSchema,
99
+ scheduleId: ScheduleId,
100
+ });
101
+
102
+ const ScheduleListRequest = Schema.TaggedStruct("List", {
103
+ schemaVersion: Schema.Literal(1),
104
+ scope: ScheduleScopeSchema,
105
+ after: Schema.optionalKey(ScheduleId),
106
+ limit: Schema.optionalKey(
107
+ Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)),
108
+ ),
109
+ });
110
+
111
+ const ScheduleControlRequest = Schema.TaggedStruct("Control", {
112
+ schemaVersion: Schema.Literal(1),
113
+ operation: Schema.Literals(["pause", "resume", "cancel"]),
114
+ scope: ScheduleScopeSchema,
115
+ scheduleId: ScheduleId,
116
+ expectedRevision: Schema.Int.check(Schema.isGreaterThan(0)),
117
+ });
118
+
119
+ const ScheduleRecoverRequest = Schema.TaggedStruct("Recover", {
120
+ schemaVersion: Schema.Literal(1),
121
+ scope: ScheduleScopeSchema,
122
+ scheduleId: ScheduleId,
123
+ expectedRevision: Schema.Int.check(Schema.isGreaterThan(0)),
124
+ expectedGeneration: Schema.Natural,
125
+ });
126
+
127
+ const ScheduleOwnerRequest = Schema.Union([
128
+ ScheduleRecoverRequest,
129
+ ScheduleCreateRequest,
130
+ ScheduleUpdateRequest,
131
+ ScheduleGetRequest,
132
+ ScheduleListRequest,
133
+ ScheduleControlRequest,
134
+ ]);
135
+
136
+ type ScheduleOwnerRequest = typeof ScheduleOwnerRequest.Type;
137
+
138
+ const ScheduleOwnerFailure = Schema.Union([
139
+ ScheduleValidationError,
140
+ ScheduleAuthorizationError,
141
+ ScheduleConflict,
142
+ ScheduleNotFound,
143
+ ScheduleCapacityError,
144
+ ScheduleStorageError,
145
+ ScheduleFailpointError,
146
+ ScheduleOwnerProtocolError,
147
+ ]);
148
+
149
+ type ScheduleOwnerFailure = typeof ScheduleOwnerFailure.Type;
150
+
151
+ const ScheduleOwnerResponse = Schema.Union([
152
+ Schema.TaggedStruct("Snapshot", { value: ScheduleSnapshot }),
153
+ Schema.TaggedStruct("Page", { value: ScheduleSnapshotPageSchema }),
154
+ Schema.TaggedStruct("Failed", { failure: ScheduleOwnerFailure }),
155
+ ]);
156
+
157
+ type ScheduleOwnerResponse = typeof ScheduleOwnerResponse.Type;
158
+
159
+ const decodeScheduleOwnerRequest = Schema.decodeUnknownEffect(ScheduleOwnerRequest);
160
+ const encodeScheduleOwnerRequest = Schema.encodeEffect(ScheduleOwnerRequest);
161
+ const decodeScheduleOwnerResponse = Schema.decodeUnknownEffect(ScheduleOwnerResponse);
162
+ const encodeScheduleOwnerResponse = Schema.encodeEffect(ScheduleOwnerResponse);
163
+
164
+ const scheduleProtocolFailure = (message: string): ScheduleOwnerResponse => ({
165
+ _tag: "Failed",
166
+ failure: ScheduleOwnerProtocolError.make({ message: boundedProtocolMessage(message) }),
167
+ });
168
+
169
+ export interface ScheduleOwnerObjectRpc extends Rpc.DurableObjectBranded {
170
+ schedule(encoded: unknown): Promise<unknown>;
171
+ }
172
+
173
+ export class ScheduleOwnerNamespace extends Context.Service<
174
+ ScheduleOwnerNamespace,
175
+ { readonly namespace: DurableObjectNamespace<ScheduleOwnerObjectRpc> }
176
+ >()("@effect-agent/platform-cloudflare/ScheduleOwnerNamespace") {}
177
+
178
+ const passthroughAgent = (agentId: AgentId): DurableSubmitAgent<typeof PersistedJson> => ({
179
+ definition: { id: agentId, input: PersistedJson },
180
+ });
181
+
182
+ const requestOwner = (request: ScheduleOwnerRequest): ScheduleOwner => request.scope.owner;
183
+
184
+ /** Provides the same authorized management service as NodeScheduling.layer. */
185
+ export class CloudflareSchedulingClient {
186
+ static readonly layer: Layer.Layer<Scheduling, never, ScheduleOwnerNamespace> = Layer.effect(
187
+ Scheduling,
188
+ Effect.gen(function* () {
189
+ const { namespace } = yield* ScheduleOwnerNamespace;
190
+
191
+ const call = Effect.fn("CloudflareSchedulingClient.call")(function* (
192
+ owner: ScheduleOwner,
193
+ request: ScheduleOwnerRequest,
194
+ ): Effect.fn.Return<ScheduleOwnerResponse, ScheduleManagementFailure> {
195
+ const encoded = yield* encodeScheduleOwnerRequest(request).pipe(
196
+ Effect.mapError(() =>
197
+ ScheduleStorageError.make({ operation: "Schedule Owner protocol", reason: "corrupt" }),
198
+ ),
199
+ );
200
+
201
+ 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
+ });
209
+
210
+ const response = yield* decodeScheduleOwnerResponse(raw).pipe(
211
+ Effect.mapError(() =>
212
+ ScheduleStorageError.make({ operation: "Schedule Owner protocol", reason: "corrupt" }),
213
+ ),
214
+ );
215
+
216
+ if (response._tag !== "Failed") return response;
217
+
218
+ return yield* response.failure._tag === "ScheduleOwnerProtocolError"
219
+ ? ScheduleStorageError.make({ operation: "Schedule Owner protocol", reason: "corrupt" })
220
+ : response.failure;
221
+ });
222
+
223
+ const encodeInput = Effect.fn("CloudflareSchedulingClient.encodeInput")(function* <
224
+ InputSchema extends Schema.Top,
225
+ >(
226
+ agent: DurableSubmitAgent<InputSchema>,
227
+ input: InputSchema["Type"],
228
+ ): Effect.fn.Return<PersistedJson, ScheduleValidationError, InputSchema["EncodingServices"]> {
229
+ const encoded = yield* Schema.encodeEffect(agent.definition.input)(input).pipe(
230
+ Effect.mapError(() =>
231
+ ScheduleValidationError.make({
232
+ message: "Unable to encode Agent input",
233
+ }),
234
+ ),
235
+ );
236
+
237
+ return yield* Schema.decodeUnknownEffect(PersistedJson)(encoded).pipe(
238
+ Effect.mapError(() =>
239
+ ScheduleValidationError.make({
240
+ message: "Agent input does not satisfy the canonical persistence bounds",
241
+ }),
242
+ ),
243
+ );
244
+ });
245
+
246
+ const create: Scheduling["Service"]["create"] = (agent, input, options) =>
247
+ Effect.gen(function* () {
248
+ const payload = yield* encodeInput(agent, input);
249
+
250
+ const response = yield* call(options.scope.owner, {
251
+ _tag: "Create",
252
+ schemaVersion: 1,
253
+ agentId: agent.definition.id,
254
+ input: payload,
255
+ ...options,
256
+ });
257
+
258
+ return response._tag === "Snapshot"
259
+ ? response.value
260
+ : yield* ScheduleStorageError.make({
261
+ operation: "Schedule Owner protocol",
262
+ reason: "corrupt",
263
+ });
264
+ });
265
+
266
+ const update: Scheduling["Service"]["update"] = (agent, input, options) =>
267
+ Effect.gen(function* () {
268
+ const payload = yield* encodeInput(agent, input);
269
+
270
+ const response = yield* call(options.scope.owner, {
271
+ _tag: "Update",
272
+ schemaVersion: 1,
273
+ agentId: agent.definition.id,
274
+ input: payload,
275
+ ...options,
276
+ });
277
+
278
+ return response._tag === "Snapshot"
279
+ ? response.value
280
+ : yield* ScheduleStorageError.make({
281
+ operation: "Schedule Owner protocol",
282
+ reason: "corrupt",
283
+ });
284
+ });
285
+
286
+ const get: Scheduling["Service"]["get"] = (scope, scheduleId) =>
287
+ Effect.gen(function* () {
288
+ const response = yield* call(scope.owner, {
289
+ _tag: "Get",
290
+ schemaVersion: 1,
291
+ scope,
292
+ scheduleId,
293
+ });
294
+
295
+ return response._tag === "Snapshot"
296
+ ? response.value
297
+ : yield* ScheduleStorageError.make({
298
+ operation: "Schedule Owner protocol",
299
+ reason: "corrupt",
300
+ });
301
+ });
302
+
303
+ const list: Scheduling["Service"]["list"] = (scope, options = {}) =>
304
+ Effect.gen(function* () {
305
+ const response = yield* call(scope.owner, {
306
+ _tag: "List",
307
+ schemaVersion: 1,
308
+ scope,
309
+ ...(options.after === undefined ? {} : { after: options.after }),
310
+ ...(options.limit === undefined ? {} : { limit: options.limit }),
311
+ });
312
+
313
+ return response._tag === "Page"
314
+ ? response.value
315
+ : yield* ScheduleStorageError.make({
316
+ operation: "Schedule Owner protocol",
317
+ reason: "corrupt",
318
+ });
319
+ });
320
+
321
+ const control = (
322
+ operation: "pause" | "resume" | "cancel",
323
+ scope: ScheduleScope,
324
+ scheduleId: ScheduleId,
325
+ expectedRevision: number,
326
+ ) =>
327
+ Effect.gen(function* () {
328
+ const response = yield* call(scope.owner, {
329
+ _tag: "Control",
330
+ schemaVersion: 1,
331
+ operation,
332
+ scope,
333
+ scheduleId,
334
+ expectedRevision,
335
+ });
336
+
337
+ return response._tag === "Snapshot"
338
+ ? response.value
339
+ : yield* ScheduleStorageError.make({
340
+ operation: "Schedule Owner protocol",
341
+ reason: "corrupt",
342
+ });
343
+ });
344
+
345
+ return Scheduling.of({
346
+ create,
347
+ update,
348
+ get,
349
+ list,
350
+ pause: (scope, id, revision) => control("pause", scope, id, revision),
351
+ resume: (scope, id, revision) => control("resume", scope, id, revision),
352
+ recover: (scope, scheduleId, expectedRevision, expectedGeneration) =>
353
+ Effect.gen(function* () {
354
+ const response = yield* call(scope.owner, {
355
+ _tag: "Recover",
356
+ schemaVersion: 1,
357
+ scope,
358
+ scheduleId,
359
+ expectedRevision,
360
+ expectedGeneration,
361
+ });
362
+
363
+ return response._tag === "Snapshot"
364
+ ? response.value
365
+ : yield* ScheduleStorageError.make({
366
+ operation: "Schedule Owner protocol",
367
+ reason: "corrupt",
368
+ });
369
+ }),
370
+ cancel: (scope, id, revision) => control("cancel", scope, id, revision),
371
+ });
372
+ }),
373
+ );
374
+ }
375
+
376
+ export class ScheduleOwnerIdentity extends Context.Service<
377
+ ScheduleOwnerIdentity,
378
+ { readonly owner: ScheduleOwner }
379
+ >()("@effect-agent/platform-cloudflare/ScheduleOwnerIdentity") {}
380
+
381
+ const decodeOwnerName = Effect.fn("decodeScheduleOwnerName")(function* (
382
+ name: string | null | undefined,
383
+ ): Effect.fn.Return<ScheduleOwner, ScheduleOwnerProtocolError> {
384
+ if (name === null || name === undefined) {
385
+ return yield* ScheduleOwnerProtocolError.make({
386
+ message: "Schedule Owner objects require an idFromName identity",
387
+ });
388
+ }
389
+
390
+ const tuple = yield* Schema.decodeUnknownEffect(
391
+ Schema.fromJsonString(Schema.Tuple([Schema.String, Schema.String])),
392
+ )(name).pipe(
393
+ Effect.mapError(() =>
394
+ ScheduleOwnerProtocolError.make({ message: "Schedule Owner object name is malformed" }),
395
+ ),
396
+ );
397
+
398
+ return yield* Schema.decodeUnknownEffect(ScheduleOwner)({
399
+ tenantId: tuple[0],
400
+ ownerId: tuple[1],
401
+ }).pipe(
402
+ Effect.mapError(() =>
403
+ ScheduleOwnerProtocolError.make({ message: "Schedule Owner object identity is invalid" }),
404
+ ),
405
+ );
406
+ });
407
+
408
+ const alarmStorageError = (operation: string) => (error: { readonly _tag: string }) =>
409
+ ScheduleStorageError.make({
410
+ operation,
411
+ reason: error._tag === "StorageOperationError" ? "unavailable" : "corrupt",
412
+ });
413
+
414
+ const transactionLayer: Layer.Layer<
415
+ DoScheduleTransaction,
416
+ never,
417
+ DurableObjectAlarm.DurableObjectAlarm
418
+ > = Layer.effect(
419
+ DoScheduleTransaction,
420
+ Effect.gen(function* () {
421
+ const alarms = yield* DurableObjectAlarm.DurableObjectAlarm;
422
+
423
+ return DoScheduleTransaction.of({
424
+ run: (body) =>
425
+ Effect.gen(function* () {
426
+ const nowMillis = yield* Clock.currentTimeMillis;
427
+
428
+ return yield* alarms
429
+ .transaction((transaction) =>
430
+ body((replacement) =>
431
+ replacement.deadlineAtMillis === null
432
+ ? transaction
433
+ .cancelAlarm({ id: SCHEDULE_ALARM_ID, tag: SCHEDULE_ALARM_TAG })
434
+ .pipe(Effect.mapError(alarmStorageError("cancel Schedule Owner alarm")))
435
+ : Effect.fromOption(
436
+ DateTime.make(Math.max(replacement.deadlineAtMillis, nowMillis + 1)),
437
+ ).pipe(
438
+ Effect.mapError(() =>
439
+ ScheduleStorageError.make({
440
+ operation: "validate Schedule Owner alarm deadline",
441
+ reason: "corrupt",
442
+ }),
443
+ ),
444
+ Effect.flatMap((runAt) =>
445
+ transaction
446
+ .scheduleAlarm({
447
+ id: SCHEDULE_ALARM_ID,
448
+ tag: SCHEDULE_ALARM_TAG,
449
+ runAt,
450
+ payload: {
451
+ schemaVersion: 1,
452
+ generation: replacement.generation,
453
+ },
454
+ })
455
+ .pipe(
456
+ Effect.mapError(alarmStorageError("schedule Schedule Owner alarm")),
457
+ ),
458
+ ),
459
+ ),
460
+ ),
461
+ )
462
+ .pipe(
463
+ Effect.catchTag("StorageOperationError", () =>
464
+ ScheduleStorageError.make({
465
+ operation: "commit Schedule Owner transaction",
466
+ reason: "unavailable",
467
+ }),
468
+ ),
469
+ );
470
+ }),
471
+ });
472
+ }),
473
+ );
474
+
475
+ type ScheduleRuntimeServices =
476
+ | Scheduling
477
+ | ScheduleDriver
478
+ | DoScheduleAlarmControl
479
+ | ScheduleOwnerIdentity
480
+ | DurableObjectAlarm.DurableObjectAlarm;
481
+
482
+ const ensureOwner = (
483
+ expected: ScheduleOwner,
484
+ request: ScheduleOwnerRequest,
485
+ ): Effect.Effect<void, ScheduleOwnerProtocolError> => {
486
+ const observed = requestOwner(request);
487
+
488
+ return observed.tenantId === expected.tenantId && observed.ownerId === expected.ownerId
489
+ ? Effect.void
490
+ : Effect.fail(
491
+ ScheduleOwnerProtocolError.make({
492
+ message: "The request owner does not match the addressed Schedule Owner object",
493
+ }),
494
+ );
495
+ };
496
+
497
+ const handleScheduleRequest = Effect.fn("ScheduleOwner.handleRequest")(function* (
498
+ encoded: unknown,
499
+ ): Effect.fn.Return<unknown, never, Scheduling | ScheduleOwnerIdentity> {
500
+ const decoded = yield* decodeScheduleOwnerRequest(encoded).pipe(Effect.result);
501
+
502
+ if (decoded._tag === "Failure") {
503
+ return yield* encodeScheduleOwnerResponse(
504
+ scheduleProtocolFailure("The Schedule request could not be decoded"),
505
+ ).pipe(Effect.orDie);
506
+ }
507
+ const request = decoded.success;
508
+ const { owner } = yield* ScheduleOwnerIdentity;
509
+ const scheduling = yield* Scheduling;
510
+
511
+ const response = yield* Effect.gen(function* () {
512
+ yield* ensureOwner(owner, request);
513
+ switch (request._tag) {
514
+ case "Create": {
515
+ const value = yield* scheduling.create(passthroughAgent(request.agentId), request.input, {
516
+ scope: request.scope,
517
+ scheduleId: request.scheduleId,
518
+ timing: request.timing,
519
+ destination: request.destination,
520
+ deliveryPrincipal: request.deliveryPrincipal,
521
+ definitions: request.definitions,
522
+ ...(request.admissionGroup === undefined
523
+ ? {}
524
+ : { admissionGroup: request.admissionGroup }),
525
+ ...(request.admissionFence === undefined
526
+ ? {}
527
+ : { admissionFence: request.admissionFence }),
528
+ });
529
+
530
+ return { _tag: "Snapshot" as const, value };
531
+ }
532
+ case "Update": {
533
+ const value = yield* scheduling.update(passthroughAgent(request.agentId), request.input, {
534
+ scope: request.scope,
535
+ scheduleId: request.scheduleId,
536
+ timing: request.timing,
537
+ destination: request.destination,
538
+ deliveryPrincipal: request.deliveryPrincipal,
539
+ definitions: request.definitions,
540
+ ...(request.admissionGroup === undefined
541
+ ? {}
542
+ : { admissionGroup: request.admissionGroup }),
543
+ ...(request.admissionFence === undefined
544
+ ? {}
545
+ : { admissionFence: request.admissionFence }),
546
+ expectedRevision: request.expectedRevision,
547
+ });
548
+
549
+ return { _tag: "Snapshot" as const, value };
550
+ }
551
+ case "Get":
552
+ return {
553
+ _tag: "Snapshot" as const,
554
+ value: yield* scheduling.get(request.scope, request.scheduleId),
555
+ };
556
+ case "List":
557
+ return {
558
+ _tag: "Page" as const,
559
+ value: yield* scheduling.list(request.scope, {
560
+ ...(request.after === undefined ? {} : { after: request.after }),
561
+ ...(request.limit === undefined ? {} : { limit: request.limit }),
562
+ }),
563
+ };
564
+ case "Recover":
565
+ return {
566
+ _tag: "Snapshot" as const,
567
+ value: yield* scheduling.recover(
568
+ request.scope,
569
+ request.scheduleId,
570
+ request.expectedRevision,
571
+ request.expectedGeneration,
572
+ ),
573
+ };
574
+ case "Control": {
575
+ const value =
576
+ request.operation === "pause"
577
+ ? yield* scheduling.pause(request.scope, request.scheduleId, request.expectedRevision)
578
+ : request.operation === "resume"
579
+ ? yield* scheduling.resume(
580
+ request.scope,
581
+ request.scheduleId,
582
+ request.expectedRevision,
583
+ )
584
+ : yield* scheduling.cancel(
585
+ request.scope,
586
+ request.scheduleId,
587
+ request.expectedRevision,
588
+ );
589
+
590
+ return { _tag: "Snapshot" as const, value };
591
+ }
592
+ }
593
+ }).pipe(
594
+ Effect.map((value): ScheduleOwnerResponse => value),
595
+ Effect.catch((failure) =>
596
+ Schema.is(ScheduleOwnerFailure)(failure)
597
+ ? Effect.succeed({ _tag: "Failed" as const, failure })
598
+ : Effect.succeed(
599
+ scheduleProtocolFailure("The Schedule operation failed outside its public contract"),
600
+ ),
601
+ ),
602
+ );
603
+
604
+ return yield* encodeScheduleOwnerResponse(response).pipe(Effect.orDie);
605
+ });
606
+
607
+ /** @internal The complete native alarm operation, including its event deadline. */
608
+ export const scheduleAlarmHandler = (limits: SchedulingLimits) =>
609
+ DurableObjectAlarm.processDue(
610
+ (event) =>
611
+ Effect.gen(function* () {
612
+ if (event.tag !== SCHEDULE_ALARM_TAG || event.id !== SCHEDULE_ALARM_ID) {
613
+ return yield* ScheduleAlarmProtocolError.make({
614
+ message: `Unsupported Schedule Owner alarm ${event.tag}/${event.id}`,
615
+ });
616
+ }
617
+ yield* Schema.decodeUnknownEffect(ScheduleAlarmPayload)(event.payload).pipe(
618
+ Effect.mapError(() =>
619
+ ScheduleAlarmProtocolError.make({
620
+ message: "Unsupported Schedule Owner alarm payload version",
621
+ }),
622
+ ),
623
+ );
624
+ const scheduling = yield* ScheduleDriver;
625
+ const alarmControl = yield* DoScheduleAlarmControl;
626
+ const { owner } = yield* ScheduleOwnerIdentity;
627
+ const nowMillis = yield* Clock.currentTimeMillis;
628
+
629
+ yield* alarmControl.prearm(nowMillis + limits.recoveryPollMillis);
630
+ const pass = yield* scheduling.runDue(owner);
631
+
632
+ if (pass.failed > 0) {
633
+ yield* alarmControl.prearm((yield* Clock.currentTimeMillis) + limits.recoveryPollMillis);
634
+ } else {
635
+ yield* alarmControl.reconcile;
636
+ }
637
+ }),
638
+ { mode: "ordered" },
639
+ ).pipe(
640
+ // Bound due acquisition and acknowledgement as well as admission. Prepared occurrences
641
+ // and their replacement alarm survive interruption and retain their idempotency keys.
642
+ Effect.timeout("14 minutes"),
643
+ Effect.asVoid,
644
+ );
645
+
646
+ export interface ScheduleOwnerObjectInstance extends InstanceType<
647
+ EffectCfDurableObject.DurableObjectClass<Record<never, never>, ScheduleRuntimeServices>
648
+ > {
649
+ schedule(encoded: unknown): Promise<unknown>;
650
+ alarm(alarmInfo?: AlarmInvocationInfo): Promise<void> | void;
651
+ }
652
+
653
+ export interface ScheduleOwnerObjectClass {
654
+ new (ctx: DurableObjectState, env: Cloudflare.Env): ScheduleOwnerObjectInstance;
655
+ }
656
+
657
+ /**
658
+ * The host Layer supplies authorization and routing and is cached for the object incarnation.
659
+ * Cloudflare eviction does not guarantee its finalizers run. Do not acquire resources requiring
660
+ * cleanup in this Layer; acquire them inside scoped `manage` / `prepare` operations instead.
661
+ * Native services belong to effect-cf; the database and alarm runtime remain instance-owned.
662
+ */
663
+ export const makeScheduleOwnerObjectClass = <E>(
664
+ host: Layer.Layer<
665
+ ScheduleAuthorizer | ThreadObjectNamespace,
666
+ E,
667
+ EffectCfDurableObjectState.DurableObjectState | WorkerEnvironment | ScheduleOwnerIdentity
668
+ >,
669
+ limits: SchedulingLimits = defaultSchedulingLimits,
670
+ ): ScheduleOwnerObjectClass => {
671
+ const ownerLayer = Layer.effect(
672
+ ScheduleOwnerIdentity,
673
+ Effect.gen(function* () {
674
+ const state = yield* EffectCfDurableObjectState.DurableObjectState;
675
+
676
+ return ScheduleOwnerIdentity.of({ owner: yield* decodeOwnerName(state.raw.id.name) });
677
+ }),
678
+ );
679
+
680
+ const sqlLayer = Layer.unwrap(
681
+ Effect.map(EffectCfDurableObjectState.DurableObjectState, (state) =>
682
+ SqliteClient.layer({ storage: state.raw.storage }),
683
+ ),
684
+ );
685
+
686
+ const application = Layer.merge(Scheduling.layer(limits), ScheduleDriver.layer(limits)).pipe(
687
+ Layer.provideMerge(
688
+ scheduleStoreLayer.pipe(Layer.provide(transactionLayer), Layer.provide(sqlLayer)),
689
+ ),
690
+ Layer.provide(
691
+ cloudflareScheduledInputAdmissionLayer.pipe(
692
+ Layer.provide(cloudflarePreparedInputAdmissionLayer),
693
+ Layer.provide(CloudflareThreadClient.layer),
694
+ ),
695
+ ),
696
+ Layer.provide(ScheduleWakeNoop),
697
+ Layer.provide(BrowserCrypto.layer),
698
+ Layer.provideMerge(DurableObjectAlarm.DurableObjectAlarm.layer),
699
+ Layer.provide(host),
700
+ Layer.provideMerge(ownerLayer),
701
+ );
702
+
703
+ const runtime: Layer.Layer<
704
+ ScheduleRuntimeServices,
705
+ E | ScheduleStorageError | ScheduleOwnerProtocolError | ScheduleValidationError,
706
+ EffectCfDurableObjectState.DurableObjectState | WorkerEnvironment
707
+ > = Layer.effectContext(
708
+ Effect.gen(function* () {
709
+ const state = yield* EffectCfDurableObjectState.DurableObjectState;
710
+ const scope = yield* Effect.scope;
711
+
712
+ return yield* state.blockConcurrencyWhile(Layer.buildWithScope(application, scope));
713
+ }),
714
+ );
715
+
716
+ const rpc = {
717
+ schedule: (encoded: unknown) => handleScheduleRequest(encoded),
718
+ } satisfies EffectCfDurableObject.DurableObjectRpc<ScheduleRuntimeServices>;
719
+
720
+ const Base = EffectCfDurableObject.make(runtime, {
721
+ initialize: Effect.void,
722
+ rpc,
723
+ alarms: scheduleAlarmHandler(limits),
724
+ });
725
+
726
+ class ScheduleOwnerObject extends Base {
727
+ override alarm(alarmInfo?: AlarmInvocationInfo): Promise<void> | void {
728
+ return super.alarm?.(alarmInfo);
729
+ }
730
+ }
731
+
732
+ return ScheduleOwnerObject;
733
+ };