effect-cf 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -0
- package/dist/ComputerWorkspace.mjs +1 -1
- package/dist/{DurableObjectState-DCh4PZMN.mjs → DurableObjectState-DLL0YgBd.mjs} +1 -1
- package/dist/Vitest.mjs +1 -1
- package/dist/index.d.mts +19 -1
- package/dist/index.mjs +25 -14
- package/package.json +1 -1
- package/src/DurableObjectAlarm.ts +80 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# effect-cf
|
|
2
2
|
|
|
3
|
+
## 0.37.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#135](https://github.com/danieljvdm/effect-cf/pull/135) [`9dea62e`](https://github.com/danieljvdm/effect-cf/commit/9dea62e74818b7fc9da919e8bdbfcc49991aef36) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Add `DurableObjectAlarm.transaction` to commit application storage and logical alarm schedule, replacement, or cancellation together in one native SQLite Durable Object transaction. Application queries can use the existing storage wrapper or a `SqlClient` from `@effect/sql-sqlite-do` backed by the same object. The callback receives transaction-only alarm mutations and preserves the caller's success value, typed errors, and service requirements.
|
|
8
|
+
|
|
9
|
+
Failures before commit roll back application rows, logical alarms, and native alarm reconciliation together. Interruption or a lost reply after commit does not undo committed state. Keep external effects outside the transaction and durably pre-arm a later wake before fallible external work; Cloudflare's native alarm retries remain bounded.
|
|
10
|
+
|
|
3
11
|
## 0.36.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -47,6 +47,8 @@ Declare `SETTINGS` in `wrangler.jsonc` and pass `SettingsLive` to `Worker.make`.
|
|
|
47
47
|
|
|
48
48
|
The [counter example](https://github.com/danieljvdm/effect-cf/tree/main/examples/counter) shows `DurableObject.Tag`, storage, a typed RPC call, and the complete Wrangler configuration.
|
|
49
49
|
|
|
50
|
+
For atomic application writes and alarm changes, see the [alarm transaction example](tests/fixtures/alarm-transaction-consumer.ts) and [API contract](src/DurableObjectAlarm.ts).
|
|
51
|
+
|
|
50
52
|
## Native RPC tracing
|
|
51
53
|
|
|
52
54
|
`call()`, `scopedCall()`, and definition methods create one CLIENT span named `binding/method`, covering argument encoding, the native RPC wait, and success decoding. Raw `rpc()` retains Cloudflare's pipelined result without creating a span. Wrap its complete lifetime with `RpcTracing.withRpcClientSpan` when tracing raw calls.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as DurableObjectState } from "./DurableObjectState-
|
|
1
|
+
import { t as DurableObjectState } from "./DurableObjectState-DLL0YgBd.mjs";
|
|
2
2
|
import { fromClient } from "./ComputerArtifacts.mjs";
|
|
3
3
|
import { t as lookup } from "./ComputerWorkspaceHostRegistry-CY5Fv4Ng.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Predicate, Schema, Stream } from "effect";
|
|
@@ -288,4 +288,4 @@ const runExitPreservingTypedFailures = async (run, effect) => {
|
|
|
288
288
|
return exit;
|
|
289
289
|
};
|
|
290
290
|
//#endregion
|
|
291
|
-
export { DurableWebSocketAttachmentError as a, DurableObjectWebSocket_exports as i, DurableObjectState_exports as n, fromWebSocket as o, fromDurableObjectState as r, DurableObjectStorage_exports as s, DurableObjectState as t };
|
|
291
|
+
export { DurableWebSocketAttachmentError as a, StorageOperationError as c, DurableObjectWebSocket_exports as i, DurableObjectState_exports as n, fromWebSocket as o, fromDurableObjectState as r, DurableObjectStorage_exports as s, DurableObjectState as t };
|
package/dist/Vitest.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as WorkerEnvironment, i as WorkerConfig } from "./ErrorMessage-Byx2OgNd.mjs";
|
|
2
2
|
import { r as runNativeCallback } from "./WorkerContext-Yobnfw-K.mjs";
|
|
3
|
-
import { r as fromDurableObjectState, t as DurableObjectState } from "./DurableObjectState-
|
|
3
|
+
import { r as fromDurableObjectState, t as DurableObjectState } from "./DurableObjectState-DLL0YgBd.mjs";
|
|
4
4
|
import { ConfigProvider, Effect, Exit, Layer } from "effect";
|
|
5
5
|
import { env } from "cloudflare:workers";
|
|
6
6
|
import { abortAllDurableObjects as abortAllDurableObjects$1, adminSecretsStore as adminSecretsStore$1, applyD1Migrations as applyD1Migrations$1, createExecutionContext, createMessageBatch, createPagesEventContext, createScheduledController, evictAllDurableObjects as evictAllDurableObjects$1, evictDurableObject as evictDurableObject$1, getQueueResult, introspectWorkflow as introspectWorkflow$1, introspectWorkflowInstance as introspectWorkflowInstance$1, listDurableObjectIds as listDurableObjectIds$1, reset as reset$1, runDurableObjectAlarm as runDurableObjectAlarm$1, runInDurableObject as runInDurableObject$1, waitOnExecutionContext } from "cloudflare:test";
|
package/dist/index.d.mts
CHANGED
|
@@ -853,7 +853,7 @@ type DurableObjectClass<Rpc extends DurableObjectRpc<ROut>, ROut> = new (state:
|
|
|
853
853
|
*/
|
|
854
854
|
declare const make$6: <ROut, LayerError, REvent = never, EventLayerError = never, const Rpc extends DurableObjectRpc<ROut | REvent> = Record<never, never>>(layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>, options?: DurableObjectOptions<ROut, REvent, EventLayerError, Rpc>) => DurableObjectClass<Rpc, ROut | REvent>;
|
|
855
855
|
declare namespace DurableObjectAlarm_d_exports {
|
|
856
|
-
export { AlarmDefinitionConfig, AlarmDefinitionEntry, AlarmDefinitionPayload, AlarmDefinitions, AlarmFailurePolicy, AlarmPayload, AlarmPayloadSchema, AlarmRef, AlarmRetryPolicy, AlarmScheduler, DefinedAlarmEvent, DefinedAlarmHandlers, DurableObjectAlarm, DurableObjectAlarmError, DurableObjectAlarmEvent, InvalidAlarmPayloadError, InvalidAlarmRefError, InvalidProcessDueAlarmsOptionsError, InvalidRepeatEveryError, ProcessDueAlarmsFailure, ProcessDueAlarmsFailureAction, ProcessDueAlarmsHandler, ProcessDueAlarmsMode, ProcessDueAlarmsOptions, ProcessDueAlarmsResult, ScheduleAlarmInput, StoredAlarmDecodeError, define, processDue };
|
|
856
|
+
export { AlarmDefinitionConfig, AlarmDefinitionEntry, AlarmDefinitionPayload, AlarmDefinitions, AlarmFailurePolicy, AlarmPayload, AlarmPayloadSchema, AlarmRef, AlarmRetryPolicy, AlarmScheduler, AlarmTransaction, DefinedAlarmEvent, DefinedAlarmHandlers, DurableObjectAlarm, DurableObjectAlarmError, DurableObjectAlarmEvent, InvalidAlarmPayloadError, InvalidAlarmRefError, InvalidProcessDueAlarmsOptionsError, InvalidRepeatEveryError, ProcessDueAlarmsFailure, ProcessDueAlarmsFailureAction, ProcessDueAlarmsHandler, ProcessDueAlarmsMode, ProcessDueAlarmsOptions, ProcessDueAlarmsResult, ScheduleAlarmInput, StoredAlarmDecodeError, define, processDue };
|
|
857
857
|
}
|
|
858
858
|
type AlarmPayload = Schema.Json;
|
|
859
859
|
declare const InvalidAlarmRefError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
@@ -946,8 +946,26 @@ interface ProcessDueAlarmsOptions<OnFailureR = never, OnFailureE = never> {
|
|
|
946
946
|
readonly retryFailedAfter?: Duration.Input;
|
|
947
947
|
}
|
|
948
948
|
type ProcessDueAlarmsHandler<R = never, E = never> = (event: DurableObjectAlarmEvent) => Effect.Effect<void, E, R>;
|
|
949
|
+
/**
|
|
950
|
+
* Alarm mutations owned by one transaction callback. Run them in the callback's
|
|
951
|
+
* fiber; forked work and use after the callback ends fail with StorageOperationError.
|
|
952
|
+
*/
|
|
953
|
+
type AlarmTransaction = Pick<AlarmScheduler, "scheduleAlarm" | "cancelAlarm">;
|
|
949
954
|
/** Own `storage.setAlarm()` exclusively: a Durable Object has one platform alarm timestamp. */
|
|
950
955
|
type AlarmScheduler = {
|
|
956
|
+
/**
|
|
957
|
+
* Commits local application storage and logical alarms in one native SQLite
|
|
958
|
+
* Durable Object transaction, reconciling the native alarm before commit.
|
|
959
|
+
* Use the supplied mutations, not standalone alarm methods or nested transactions.
|
|
960
|
+
* SqlClient queries must use this same Durable Object's storage.
|
|
961
|
+
*
|
|
962
|
+
* Failure, defects and interruption before commit roll back. A lost reply or
|
|
963
|
+
* interruption after commit does not undo committed state. Keep RPC and other
|
|
964
|
+
* external effects outside; atomically pre-arm a later wake before fallible work.
|
|
965
|
+
* Cloudflare's native retries are bounded; composition does not remove the
|
|
966
|
+
* pre-arm requirement or promise infinite retry liveness.
|
|
967
|
+
*/
|
|
968
|
+
readonly transaction: <A, E, R>(closure: (alarms: AlarmTransaction) => Effect.Effect<A, E, R>) => Effect.Effect<A, E | StorageOperationError, R>;
|
|
951
969
|
readonly cancelAlarm: (input: AlarmRef) => Effect.Effect<void, InvalidAlarmRefError | StorageOperationError>;
|
|
952
970
|
/** Acknowledge after handling. Conditional writes preserve handler replacements; alarms are at-least-once. */
|
|
953
971
|
readonly processDueAlarms: <R = never, E = never, OnFailureR = never, OnFailureE = never>(handle: ProcessDueAlarmsHandler<R, E>, options?: ProcessDueAlarmsOptions<OnFailureR, OnFailureE>) => Effect.Effect<ProcessDueAlarmsResult, E | OnFailureE | DurableObjectAlarmError, R | OnFailureR>;
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { a as WorkerEnvironment, i as WorkerConfig, n as violationsMessage, r as
|
|
|
3
3
|
import { n as Service$1, r as layer$17, t as Binding_exports } from "./Binding-C82jRhdr.mjs";
|
|
4
4
|
import { n as Artifacts_exports } from "./Artifacts-CBxWGeZB.mjs";
|
|
5
5
|
import { t as fromExecutionContext } from "./WorkerContext-Yobnfw-K.mjs";
|
|
6
|
-
import { a as DurableWebSocketAttachmentError, i as DurableObjectWebSocket_exports, n as DurableObjectState_exports, o as fromWebSocket, r as fromDurableObjectState, s as DurableObjectStorage_exports, t as DurableObjectState } from "./DurableObjectState-
|
|
6
|
+
import { a as DurableWebSocketAttachmentError, c as StorageOperationError, i as DurableObjectWebSocket_exports, n as DurableObjectState_exports, o as fromWebSocket, r as fromDurableObjectState, s as DurableObjectStorage_exports, t as DurableObjectState } from "./DurableObjectState-DLL0YgBd.mjs";
|
|
7
7
|
import { A as QueueDefinition_exports, C as decodeSuccess, D as method$1, E as make$15, O as reservedMethodNames$2, S as decodeArgs, T as encodeSuccess, _ as RpcTracing_exports, a as WorkerDefinition_exports, b as RpcDefinition_exports, c as resolve, d as makeEntrypointRuntime, f as runEventPromise, g as recordDecodedArgs, h as invokeRpcMethod, i as Worker_exports, j as QueueBinding_exports, k as Queue_exports, l as scoped, m as defineEntrypointRpcMethods, n as NativeRequest, o as ServiceBinding_exports, p as assumeEntrypointClass, r as WorkerContext, s as Rpc_exports, t as ExecutionContext, u as scheduleTelemetryFlush$1, v as withRpcClientSpan, w as encodeArgs, x as assertNoReservedMethods, y as withRpcTraceContext } from "./Worker-XsGJsCJX.mjs";
|
|
8
8
|
import { Cause, Clock, Config, ConfigProvider, Context, Data, DateTime, Duration, Effect, Exit, FiberSet, Function as Function$1, Layer, Option, Predicate, Queue, Result, Schema, Tracer } from "effect";
|
|
9
9
|
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
@@ -1430,25 +1430,35 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
1430
1430
|
});
|
|
1431
1431
|
const cancelAlarm = Effect.fn("DurableObjectAlarm.cancelAlarm")(function* (input) {
|
|
1432
1432
|
const ref = yield* decodeAlarmRef(input);
|
|
1433
|
-
yield* state.storage.
|
|
1434
|
-
yield* ensureTable(state);
|
|
1435
|
-
yield* state.storage.sql.exec(`DELETE FROM effect_cf_scheduled_alarms WHERE storage_id = ?`, getScheduledEventId(ref)).pipe(Effect.asVoid);
|
|
1436
|
-
yield* reconcileAlarm();
|
|
1437
|
-
}));
|
|
1433
|
+
yield* state.storage.sql.exec(`DELETE FROM effect_cf_scheduled_alarms WHERE storage_id = ?`, getScheduledEventId(ref));
|
|
1438
1434
|
});
|
|
1439
1435
|
const scheduleAlarm = Effect.fn("DurableObjectAlarm.scheduleAlarm")(function* (input) {
|
|
1440
1436
|
const ref = yield* decodeAlarmRef(input);
|
|
1441
1437
|
const repeatEveryMillis = yield* toRepeatEveryMillis(input.repeatEvery);
|
|
1442
1438
|
const payload = yield* encodeStoredPayload(input.payload);
|
|
1443
1439
|
const runAt = DateTime.toEpochMillis(input.runAt);
|
|
1444
|
-
yield* state.storage.
|
|
1445
|
-
yield* ensureTable(state);
|
|
1446
|
-
yield* state.storage.sql.exec(`INSERT OR REPLACE INTO effect_cf_scheduled_alarms
|
|
1440
|
+
yield* state.storage.sql.exec(`INSERT OR REPLACE INTO effect_cf_scheduled_alarms
|
|
1447
1441
|
(storage_id, alarm_id, tag, run_at, repeat_every_ms, payload)
|
|
1448
|
-
VALUES (?, ?, ?, ?, ?, ?)`, getScheduledEventId(ref), ref.id, ref.tag, runAt, repeatEveryMillis, payload)
|
|
1449
|
-
yield* reconcileAlarm();
|
|
1450
|
-
}));
|
|
1442
|
+
VALUES (?, ?, ?, ?, ?, ?)`, getScheduledEventId(ref), ref.id, ref.tag, runAt, repeatEveryMillis, payload);
|
|
1451
1443
|
});
|
|
1444
|
+
const transaction = (closure) => state.storage.transaction(() => Effect.withFiber((owner) => {
|
|
1445
|
+
let active = true;
|
|
1446
|
+
const requireActive = (effect) => Effect.withFiber((fiber) => active && fiber === owner ? effect : Effect.fail(new StorageOperationError({
|
|
1447
|
+
operation: "alarm.transaction",
|
|
1448
|
+
cause: /* @__PURE__ */ new Error("Alarm mutations require their active transaction callback")
|
|
1449
|
+
})));
|
|
1450
|
+
return Effect.gen(function* () {
|
|
1451
|
+
yield* ensureTable(state);
|
|
1452
|
+
const result = yield* Effect.suspend(() => closure({
|
|
1453
|
+
cancelAlarm: (input) => requireActive(cancelAlarm(input)),
|
|
1454
|
+
scheduleAlarm: (input) => requireActive(scheduleAlarm(input))
|
|
1455
|
+
})).pipe(Effect.ensuring(Effect.sync(() => {
|
|
1456
|
+
active = false;
|
|
1457
|
+
})));
|
|
1458
|
+
yield* reconcileAlarm();
|
|
1459
|
+
return result;
|
|
1460
|
+
});
|
|
1461
|
+
}));
|
|
1452
1462
|
const processDueAlarms = Effect.fn("DurableObjectAlarm.processDueAlarms")(function* (handle, options) {
|
|
1453
1463
|
yield* ensureTable(state);
|
|
1454
1464
|
const mode = options?.mode ?? "isolated";
|
|
@@ -1507,9 +1517,10 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
1507
1517
|
};
|
|
1508
1518
|
});
|
|
1509
1519
|
return DurableObjectAlarm.of({
|
|
1510
|
-
cancelAlarm,
|
|
1520
|
+
cancelAlarm: (input) => transaction((alarms) => alarms.cancelAlarm(input)),
|
|
1511
1521
|
processDueAlarms,
|
|
1512
|
-
scheduleAlarm
|
|
1522
|
+
scheduleAlarm: (input) => transaction((alarms) => alarms.scheduleAlarm(input)),
|
|
1523
|
+
transaction
|
|
1513
1524
|
});
|
|
1514
1525
|
}).pipe(Effect.withSpan("DurableObjectAlarm.layer")));
|
|
1515
1526
|
};
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "effect";
|
|
13
13
|
|
|
14
14
|
import { DurableObjectState } from "./DurableObjectState";
|
|
15
|
-
import type
|
|
15
|
+
import { type SqlStorageValue, StorageOperationError } from "./DurableObjectStorage";
|
|
16
16
|
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
17
17
|
|
|
18
18
|
const INIT_TABLE_SQL = `
|
|
@@ -177,8 +177,30 @@ export type ProcessDueAlarmsHandler<R = never, E = never> = (
|
|
|
177
177
|
event: DurableObjectAlarmEvent,
|
|
178
178
|
) => Effect.Effect<void, E, R>;
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Alarm mutations owned by one transaction callback. Run them in the callback's
|
|
182
|
+
* fiber; forked work and use after the callback ends fail with StorageOperationError.
|
|
183
|
+
*/
|
|
184
|
+
export type AlarmTransaction = Pick<AlarmScheduler, "scheduleAlarm" | "cancelAlarm">;
|
|
185
|
+
|
|
180
186
|
/** Own `storage.setAlarm()` exclusively: a Durable Object has one platform alarm timestamp. */
|
|
181
187
|
export type AlarmScheduler = {
|
|
188
|
+
/**
|
|
189
|
+
* Commits local application storage and logical alarms in one native SQLite
|
|
190
|
+
* Durable Object transaction, reconciling the native alarm before commit.
|
|
191
|
+
* Use the supplied mutations, not standalone alarm methods or nested transactions.
|
|
192
|
+
* SqlClient queries must use this same Durable Object's storage.
|
|
193
|
+
*
|
|
194
|
+
* Failure, defects and interruption before commit roll back. A lost reply or
|
|
195
|
+
* interruption after commit does not undo committed state. Keep RPC and other
|
|
196
|
+
* external effects outside; atomically pre-arm a later wake before fallible work.
|
|
197
|
+
* Cloudflare's native retries are bounded; composition does not remove the
|
|
198
|
+
* pre-arm requirement or promise infinite retry liveness.
|
|
199
|
+
*/
|
|
200
|
+
readonly transaction: <A, E, R>(
|
|
201
|
+
closure: (alarms: AlarmTransaction) => Effect.Effect<A, E, R>,
|
|
202
|
+
) => Effect.Effect<A, E | StorageOperationError, R>;
|
|
203
|
+
|
|
182
204
|
readonly cancelAlarm: (
|
|
183
205
|
input: AlarmRef,
|
|
184
206
|
) => Effect.Effect<void, InvalidAlarmRefError | StorageOperationError>;
|
|
@@ -523,17 +545,9 @@ export class DurableObjectAlarm extends Context.Service<DurableObjectAlarm, Alar
|
|
|
523
545
|
const cancelAlarm = Effect.fn("DurableObjectAlarm.cancelAlarm")(function* (input: AlarmRef) {
|
|
524
546
|
const ref = yield* decodeAlarmRef(input);
|
|
525
547
|
|
|
526
|
-
yield* state.storage.
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
yield* state.storage.sql
|
|
530
|
-
.exec(
|
|
531
|
-
`DELETE FROM effect_cf_scheduled_alarms WHERE storage_id = ?`,
|
|
532
|
-
getScheduledEventId(ref),
|
|
533
|
-
)
|
|
534
|
-
.pipe(Effect.asVoid);
|
|
535
|
-
yield* reconcileAlarm();
|
|
536
|
-
}),
|
|
548
|
+
yield* state.storage.sql.exec(
|
|
549
|
+
`DELETE FROM effect_cf_scheduled_alarms WHERE storage_id = ?`,
|
|
550
|
+
getScheduledEventId(ref),
|
|
537
551
|
);
|
|
538
552
|
});
|
|
539
553
|
|
|
@@ -545,27 +559,62 @@ export class DurableObjectAlarm extends Context.Service<DurableObjectAlarm, Alar
|
|
|
545
559
|
const payload = yield* encodeStoredPayload(input.payload);
|
|
546
560
|
const runAt = DateTime.toEpochMillis(input.runAt);
|
|
547
561
|
|
|
548
|
-
yield* state.storage.
|
|
549
|
-
|
|
550
|
-
yield* ensureTable(state);
|
|
551
|
-
yield* state.storage.sql
|
|
552
|
-
.exec(
|
|
553
|
-
`INSERT OR REPLACE INTO effect_cf_scheduled_alarms
|
|
562
|
+
yield* state.storage.sql.exec(
|
|
563
|
+
`INSERT OR REPLACE INTO effect_cf_scheduled_alarms
|
|
554
564
|
(storage_id, alarm_id, tag, run_at, repeat_every_ms, payload)
|
|
555
565
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
)
|
|
563
|
-
.pipe(Effect.asVoid);
|
|
564
|
-
yield* reconcileAlarm();
|
|
565
|
-
}),
|
|
566
|
+
getScheduledEventId(ref),
|
|
567
|
+
ref.id,
|
|
568
|
+
ref.tag,
|
|
569
|
+
runAt,
|
|
570
|
+
repeatEveryMillis,
|
|
571
|
+
payload,
|
|
566
572
|
);
|
|
567
573
|
});
|
|
568
574
|
|
|
575
|
+
const transaction: AlarmScheduler["transaction"] = (closure) =>
|
|
576
|
+
state.storage.transaction(() =>
|
|
577
|
+
Effect.withFiber((owner) => {
|
|
578
|
+
let active = true;
|
|
579
|
+
|
|
580
|
+
// A callback-owned handle cannot escape via a returned Effect or a
|
|
581
|
+
// detached fiber and write after reconciliation or rollback.
|
|
582
|
+
const requireActive = <A, E>(effect: Effect.Effect<A, E>) =>
|
|
583
|
+
Effect.withFiber<A, E | StorageOperationError>((fiber) =>
|
|
584
|
+
active && fiber === owner
|
|
585
|
+
? effect
|
|
586
|
+
: Effect.fail(
|
|
587
|
+
new StorageOperationError({
|
|
588
|
+
operation: "alarm.transaction",
|
|
589
|
+
cause: new Error(
|
|
590
|
+
"Alarm mutations require their active transaction callback",
|
|
591
|
+
),
|
|
592
|
+
}),
|
|
593
|
+
),
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
return Effect.gen(function* () {
|
|
597
|
+
yield* ensureTable(state);
|
|
598
|
+
const result = yield* Effect.suspend(() =>
|
|
599
|
+
closure({
|
|
600
|
+
cancelAlarm: (input) => requireActive(cancelAlarm(input)),
|
|
601
|
+
scheduleAlarm: (input) => requireActive(scheduleAlarm(input)),
|
|
602
|
+
}),
|
|
603
|
+
).pipe(
|
|
604
|
+
Effect.ensuring(
|
|
605
|
+
Effect.sync(() => {
|
|
606
|
+
active = false;
|
|
607
|
+
}),
|
|
608
|
+
),
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
yield* reconcileAlarm();
|
|
612
|
+
|
|
613
|
+
return result;
|
|
614
|
+
});
|
|
615
|
+
}),
|
|
616
|
+
);
|
|
617
|
+
|
|
569
618
|
const processDueAlarms = Effect.fn("DurableObjectAlarm.processDueAlarms")(function* <
|
|
570
619
|
R,
|
|
571
620
|
E,
|
|
@@ -669,9 +718,10 @@ export class DurableObjectAlarm extends Context.Service<DurableObjectAlarm, Alar
|
|
|
669
718
|
});
|
|
670
719
|
|
|
671
720
|
return DurableObjectAlarm.of({
|
|
672
|
-
cancelAlarm,
|
|
721
|
+
cancelAlarm: (input) => transaction((alarms) => alarms.cancelAlarm(input)),
|
|
673
722
|
processDueAlarms,
|
|
674
|
-
scheduleAlarm,
|
|
723
|
+
scheduleAlarm: (input) => transaction((alarms) => alarms.scheduleAlarm(input)),
|
|
724
|
+
transaction,
|
|
675
725
|
});
|
|
676
726
|
}).pipe(Effect.withSpan("DurableObjectAlarm.layer")),
|
|
677
727
|
);
|