effect-cf 0.50.0 → 0.51.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 +14 -0
- package/README.md +2 -0
- package/dist/DurableObjectAlarm.d.mts +68 -15
- package/dist/DurableObjectAlarm.mjs +261 -110
- package/dist/DurableObjectContainer.d.mts +1 -1
- package/dist/DurableObjectContainer.mjs +1 -1
- package/dist/DurableObjectRpcWebSocket.d.mts +2 -2
- package/dist/DurableObjectRpcWebSocket.mjs +2 -6
- package/dist/DurableObjectStorage.d.mts +2 -9
- package/dist/DurableObjectStorage.mjs +1 -14
- package/dist/DurableObjectWebSocket.d.mts +10 -2
- package/dist/DurableObjectWebSocket.mjs +18 -2
- package/dist/RpcTracing.mjs +1 -1
- package/package.json +1 -1
- package/src/DurableObjectAlarm.ts +536 -202
- package/src/DurableObjectRpcWebSocket.ts +4 -14
- package/src/DurableObjectStorage.ts +2 -59
- package/src/DurableObjectWebSocket.ts +24 -0
|
@@ -9,19 +9,28 @@ import * as S from "effect/Schema";
|
|
|
9
9
|
import * as Layer from "effect/Layer";
|
|
10
10
|
import * as Predicate from "effect/Predicate";
|
|
11
11
|
import * as Exit from "effect/Exit";
|
|
12
|
-
import * as Clock from "effect/Clock";
|
|
13
12
|
import * as Duration from "effect/Duration";
|
|
13
|
+
import * as Clock from "effect/Clock";
|
|
14
14
|
import * as DateTime from "effect/DateTime";
|
|
15
15
|
//#region src/DurableObjectAlarm.ts
|
|
16
16
|
var DurableObjectAlarm_exports = /* @__PURE__ */ __exportAll({
|
|
17
|
+
AlarmParked: () => AlarmParked,
|
|
18
|
+
AlarmReporter: () => AlarmReporter,
|
|
19
|
+
DEFAULT_MIN_REPEAT_INTERVAL_MS: () => DEFAULT_MIN_REPEAT_INTERVAL_MS,
|
|
17
20
|
DurableObjectAlarm: () => DurableObjectAlarm,
|
|
18
21
|
DurableObjectAlarmEvent: () => DurableObjectAlarmEvent,
|
|
19
22
|
InvalidAlarmPayloadError: () => InvalidAlarmPayloadError,
|
|
20
23
|
InvalidAlarmRefError: () => InvalidAlarmRefError,
|
|
21
24
|
InvalidProcessDueAlarmsOptionsError: () => InvalidProcessDueAlarmsOptionsError,
|
|
22
25
|
InvalidRepeatEveryError: () => InvalidRepeatEveryError,
|
|
26
|
+
InvalidScheduleConfigurationError: () => InvalidScheduleConfigurationError,
|
|
27
|
+
MIN_REPEAT_INTERVAL_MS: () => MIN_REPEAT_INTERVAL_MS,
|
|
28
|
+
MIN_RETRY_DELAY_MS: () => MIN_RETRY_DELAY_MS,
|
|
29
|
+
PARKED_RETRY_DELAY_MS: () => PARKED_RETRY_DELAY_MS,
|
|
30
|
+
ScheduleConfiguration: () => ScheduleConfiguration,
|
|
23
31
|
StoredAlarmDecodeError: () => StoredAlarmDecodeError,
|
|
24
32
|
Tag: () => Tag,
|
|
33
|
+
UNCHANGED_ATTEMPT_BUDGET: () => 8,
|
|
25
34
|
define: () => define,
|
|
26
35
|
processDue: () => processDue
|
|
27
36
|
});
|
|
@@ -31,15 +40,55 @@ CREATE TABLE IF NOT EXISTS effect_cf_scheduled_alarms (
|
|
|
31
40
|
alarm_id TEXT NOT NULL,
|
|
32
41
|
tag TEXT NOT NULL,
|
|
33
42
|
run_at INTEGER NOT NULL,
|
|
43
|
+
wake_at INTEGER NOT NULL DEFAULT 0,
|
|
34
44
|
repeat_every_ms INTEGER,
|
|
35
45
|
payload TEXT NOT NULL
|
|
36
46
|
);
|
|
37
|
-
CREATE
|
|
38
|
-
|
|
47
|
+
CREATE TABLE IF NOT EXISTS effect_cf_alarm_attempts (
|
|
48
|
+
storage_id TEXT PRIMARY KEY,
|
|
49
|
+
revision TEXT NOT NULL,
|
|
50
|
+
attempts INTEGER NOT NULL,
|
|
51
|
+
parked INTEGER NOT NULL,
|
|
52
|
+
retry_at INTEGER,
|
|
53
|
+
progress INTEGER NOT NULL
|
|
54
|
+
);
|
|
39
55
|
`;
|
|
56
|
+
const WAKE_INDEX = "idx_effect_cf_scheduled_alarms_wake_at_storage_id";
|
|
40
57
|
const DEFAULT_PROCESS_DUE_ALARMS_LIMIT = 100;
|
|
41
|
-
const
|
|
58
|
+
const MIN_RETRY_DELAY_MS = 1e3;
|
|
59
|
+
/** Minimum and default parked recovery interval. */
|
|
60
|
+
const PARKED_RETRY_DELAY_MS = 36e5;
|
|
61
|
+
/** Absolute product repeat floor. */
|
|
62
|
+
const MIN_REPEAT_INTERVAL_MS = 1e3;
|
|
63
|
+
/** Default product schedule floor. Never use repeats to poll state. */
|
|
64
|
+
const DEFAULT_MIN_REPEAT_INTERVAL_MS = 6e4;
|
|
65
|
+
/** Optional policy overrides. Provide with Layer.succeed in the Durable Object's application layer. */
|
|
66
|
+
const ScheduleConfiguration = Context.Reference("effect-cf/DurableObjectAlarm/ScheduleConfiguration", { defaultValue: () => ({}) });
|
|
67
|
+
const safeIntegerAtLeast = (minimum) => S.Int.check(S.isGreaterThanOrEqualTo(minimum), S.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
|
|
68
|
+
const ScheduleConfigurationSchema = S.Struct({
|
|
69
|
+
minimumRetryDelay: safeIntegerAtLeast(MIN_RETRY_DELAY_MS),
|
|
70
|
+
unchangedAttemptBudget: safeIntegerAtLeast(1),
|
|
71
|
+
parkedRetryDelay: safeIntegerAtLeast(PARKED_RETRY_DELAY_MS),
|
|
72
|
+
minimumRepeatInterval: safeIntegerAtLeast(MIN_REPEAT_INTERVAL_MS)
|
|
73
|
+
}).check(S.makeFilter((configuration) => configuration.parkedRetryDelay >= configuration.minimumRetryDelay ? void 0 : "parkedRetryDelay must be at least minimumRetryDelay"));
|
|
74
|
+
const retryDelay = (attempts, configuration, parked, initialDelay = configuration.minimumRetryDelay) => parked ? configuration.parkedRetryDelay : Math.min(configuration.parkedRetryDelay, initialDelay * 2 ** (attempts - 1));
|
|
42
75
|
const getScheduledEventId = (input) => `effect-cf-alarm:${encodeURIComponent(input.tag)}:${encodeURIComponent(input.id)}`;
|
|
76
|
+
const alarmRowsSql = `
|
|
77
|
+
SELECT a.*, COALESCE(s.revision, '') AS revision,
|
|
78
|
+
COALESCE(s.attempts, 0) AS attempts, COALESCE(s.parked, 0) AS parked,
|
|
79
|
+
s.retry_at, COALESCE(s.progress, -1) AS progress
|
|
80
|
+
FROM effect_cf_scheduled_alarms a
|
|
81
|
+
LEFT JOIN effect_cf_alarm_attempts s USING (storage_id)`;
|
|
82
|
+
const sameRevisionSql = `storage_id = ? AND COALESCE(
|
|
83
|
+
(SELECT revision FROM effect_cf_alarm_attempts WHERE storage_id = ?), '') = ?`;
|
|
84
|
+
/** Content-free: no alarm identifiers, payload, error text or consumer state. */
|
|
85
|
+
const AlarmParked = S.TaggedStruct("AlarmParked", {
|
|
86
|
+
attempts: S.Int,
|
|
87
|
+
retryAt: S.DateTimeUtc
|
|
88
|
+
});
|
|
89
|
+
/** Install with Layer.succeed / Effect.provideService. Reporting cannot undo a committed guard. */
|
|
90
|
+
const AlarmReporter = Context.Reference("effect-cf/DurableObjectAlarm/AlarmReporter", { defaultValue: () => (event) => Effect.logWarning(event) });
|
|
91
|
+
const CurrentAlarmPass = Context.Reference("effect-cf/DurableObjectAlarm/CurrentAlarmPass", { defaultValue: () => void 0 });
|
|
43
92
|
var InvalidAlarmRefError = class extends Data.TaggedError("InvalidAlarmRefError") {
|
|
44
93
|
get message() {
|
|
45
94
|
return `Invalid Durable Object alarm ref: ${causeMessage(this.cause)}`;
|
|
@@ -60,6 +109,26 @@ var InvalidProcessDueAlarmsOptionsError = class extends Data.TaggedError("Invali
|
|
|
60
109
|
return `Invalid processDueAlarms options: ${causeMessage(this.cause)}`;
|
|
61
110
|
}
|
|
62
111
|
};
|
|
112
|
+
var InvalidScheduleConfigurationError = class extends Data.TaggedError("InvalidScheduleConfigurationError") {
|
|
113
|
+
get message() {
|
|
114
|
+
return `Invalid Durable Object alarm configuration: ${causeMessage(this.cause)}`;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const getScheduleConfiguration = Effect.fnUntraced(function* (defaults) {
|
|
118
|
+
const input = {
|
|
119
|
+
...defaults,
|
|
120
|
+
...yield* ScheduleConfiguration
|
|
121
|
+
};
|
|
122
|
+
return yield* Effect.try({
|
|
123
|
+
try: () => S.decodeUnknownSync(ScheduleConfigurationSchema)({
|
|
124
|
+
minimumRetryDelay: Math.ceil(Duration.toMillis(input.minimumRetryDelay ?? 1e3)),
|
|
125
|
+
unchangedAttemptBudget: input.unchangedAttemptBudget ?? 8,
|
|
126
|
+
parkedRetryDelay: Math.ceil(Duration.toMillis(input.parkedRetryDelay ?? 36e5)),
|
|
127
|
+
minimumRepeatInterval: Math.ceil(Duration.toMillis(input.minimumRepeatInterval ?? 6e4))
|
|
128
|
+
}),
|
|
129
|
+
catch: (cause) => new InvalidScheduleConfigurationError({ cause })
|
|
130
|
+
});
|
|
131
|
+
});
|
|
63
132
|
var StoredAlarmDecodeError = class extends Data.TaggedError("StoredAlarmDecodeError") {
|
|
64
133
|
get message() {
|
|
65
134
|
return `Failed to decode stored alarm "${this.storageId}": ${causeMessage(this.cause)}`;
|
|
@@ -83,13 +152,24 @@ const decodeStoredPayload = (row) => S.decodeUnknownEffect(StoredPayloadString)(
|
|
|
83
152
|
storageId: row.storage_id
|
|
84
153
|
})));
|
|
85
154
|
const encodeStoredPayload = (payload) => S.encodeEffect(StoredPayloadString)(payload).pipe(Effect.mapError((cause) => new InvalidAlarmPayloadError({ cause })));
|
|
86
|
-
const ensureTable = (state)
|
|
87
|
-
|
|
155
|
+
const ensureTable = Effect.fnUntraced(function* (state) {
|
|
156
|
+
yield* state.storage.sql.exec(INIT_TABLE_SQL);
|
|
157
|
+
if ((yield* (yield* state.storage.sql.exec("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?", WAKE_INDEX)).toArray()).length > 0) return;
|
|
158
|
+
const columns = yield* state.storage.sql.exec("SELECT name FROM pragma_table_info('effect_cf_scheduled_alarms')");
|
|
159
|
+
if (!new Set((yield* columns.toArray()).map((column) => column.name)).has("wake_at")) yield* state.storage.sql.exec("ALTER TABLE effect_cf_scheduled_alarms ADD COLUMN wake_at INTEGER NOT NULL DEFAULT 0");
|
|
160
|
+
yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms SET
|
|
161
|
+
wake_at = MAX(run_at, COALESCE((SELECT retry_at FROM effect_cf_alarm_attempts s
|
|
162
|
+
WHERE s.storage_id = effect_cf_scheduled_alarms.storage_id), run_at))`);
|
|
163
|
+
yield* state.storage.sql.exec("DROP INDEX IF EXISTS idx_effect_cf_scheduled_alarms_run_at_storage_id");
|
|
164
|
+
yield* state.storage.sql.exec(`CREATE INDEX IF NOT EXISTS ${WAKE_INDEX}
|
|
165
|
+
ON effect_cf_scheduled_alarms (wake_at, storage_id)`);
|
|
166
|
+
});
|
|
167
|
+
const toRepeatEveryMillis = (input, minimum) => {
|
|
88
168
|
if (input === void 0) return Effect.succeed(null);
|
|
89
169
|
return Effect.try({
|
|
90
170
|
try: () => {
|
|
91
171
|
const millis = Duration.toMillis(input);
|
|
92
|
-
if (!Number.isFinite(millis) || millis
|
|
172
|
+
if (!Number.isFinite(millis) || millis < minimum) throw new Error(`Alarm repeatEvery must be at least ${minimum} milliseconds and finite`);
|
|
93
173
|
return Math.ceil(millis);
|
|
94
174
|
},
|
|
95
175
|
catch: (cause) => new InvalidRepeatEveryError({ cause })
|
|
@@ -113,15 +193,15 @@ const getProcessLimit = (options) => {
|
|
|
113
193
|
if (!Number.isSafeInteger(limit) || limit <= 0) return Effect.fail(new InvalidProcessDueAlarmsOptionsError({ cause: /* @__PURE__ */ new Error("processDueAlarms limit must be a positive safe integer") }));
|
|
114
194
|
return Effect.succeed(limit);
|
|
115
195
|
};
|
|
116
|
-
const toFailureRescheduleMillis = (input) => Effect.try({
|
|
196
|
+
const toFailureRescheduleMillis = (input, minimum) => Effect.try({
|
|
117
197
|
try: () => {
|
|
118
198
|
const millis = Duration.toMillis(input);
|
|
119
199
|
if (!Number.isFinite(millis) || millis <= 0) throw new Error("Alarm failure rescheduleAfter must be a positive finite duration");
|
|
120
|
-
return Math.ceil(millis);
|
|
200
|
+
return Math.max(minimum, Math.ceil(millis));
|
|
121
201
|
},
|
|
122
202
|
catch: (cause) => new InvalidProcessDueAlarmsOptionsError({ cause })
|
|
123
203
|
});
|
|
124
|
-
const getFailureRetryDelay = (options) => toFailureRescheduleMillis(options?.retryFailedAfter ??
|
|
204
|
+
const getFailureRetryDelay = (options, configuration) => toFailureRescheduleMillis(options?.retryFailedAfter ?? configuration.minimumRetryDelay, configuration.minimumRetryDelay);
|
|
125
205
|
const getFailureActionMode = (action) => Predicate.isString(action) ? action : action.mode;
|
|
126
206
|
const getFailureActionRetryDelay = (action) => Predicate.isString(action) || action.mode !== "retry" ? void 0 : action.retryFailedAfter;
|
|
127
207
|
const processDue = (handle, options = {}) => Effect.gen(function* () {
|
|
@@ -140,25 +220,30 @@ const getAlarmDefinitionFailureAction = (definition) => {
|
|
|
140
220
|
};
|
|
141
221
|
const makeDefinition = (definitions) => {
|
|
142
222
|
const definitionFor = (tag) => Object.hasOwn(definitions, tag) ? definitions[tag] : void 0;
|
|
143
|
-
const bind = (mutations) =>
|
|
144
|
-
|
|
223
|
+
const bind = (mutations) => {
|
|
224
|
+
const schedule = (method) => Effect.fn("DefinedAlarms.scheduleAlarm")(function* (input) {
|
|
145
225
|
const definition = definitionFor(input.tag);
|
|
146
226
|
if (definition === void 0) return yield* Effect.fail(new InvalidAlarmRefError({ cause: /* @__PURE__ */ new Error(`Unknown alarm tag "${input.tag}"`) }));
|
|
147
227
|
const payload = yield* S.encodeEffect(getAlarmDefinitionSchema(definition))(input.payload).pipe(Effect.flatMap(S.decodeUnknownEffect(S.Json)), Effect.mapError((cause) => new InvalidAlarmPayloadError({ cause })));
|
|
148
|
-
yield* mutations
|
|
228
|
+
yield* mutations[method]({
|
|
149
229
|
...input,
|
|
150
230
|
payload
|
|
151
231
|
});
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
232
|
+
});
|
|
233
|
+
return {
|
|
234
|
+
scheduleAlarm: schedule("scheduleAlarm"),
|
|
235
|
+
scheduleAlarmEarlier: schedule("scheduleAlarmEarlier"),
|
|
236
|
+
cancelAlarm: Effect.fn("DefinedAlarms.cancelAlarm")(function* (input) {
|
|
237
|
+
if (definitionFor(input.tag) === void 0) return yield* Effect.fail(new InvalidAlarmRefError({ cause: /* @__PURE__ */ new Error(`Unknown alarm tag "${input.tag}"`) }));
|
|
238
|
+
yield* mutations.cancelAlarm(input);
|
|
239
|
+
})
|
|
240
|
+
};
|
|
241
|
+
};
|
|
158
242
|
return {
|
|
159
243
|
make: (alarms) => ({
|
|
160
244
|
[TypedAlarmSchedulerTypeId]: TypedAlarmSchedulerTypeId,
|
|
161
245
|
...bind(alarms),
|
|
246
|
+
getAlarmStatus: (input) => alarms.getAlarmStatus(input),
|
|
162
247
|
transaction: (closure) => alarms.transaction((tx) => closure(bind(tx)))
|
|
163
248
|
}),
|
|
164
249
|
handlers: (handlers, options) => processDue((event) => Effect.gen(function* () {
|
|
@@ -201,93 +286,137 @@ const Tag = () => (id, definitions) => {
|
|
|
201
286
|
var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("effect-cf/DurableObjectAlarm") {
|
|
202
287
|
static layer = Layer.effect(DurableObjectAlarm, Effect.gen(function* () {
|
|
203
288
|
const state = yield* DurableObjectState;
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
yield* state.storage.deleteAlarm();
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
yield* state.storage.setAlarm(next.run_at);
|
|
289
|
+
const configurationDefaults = { ...yield* ScheduleConfiguration };
|
|
290
|
+
const readRow = Effect.fnUntraced(function* (storageId) {
|
|
291
|
+
return (yield* (yield* state.storage.sql.exec(`${alarmRowsSql} WHERE a.storage_id = ?`, storageId)).toArray())[0];
|
|
211
292
|
});
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
yield* (yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms
|
|
224
|
-
SET run_at = ?
|
|
225
|
-
WHERE storage_id = ?
|
|
226
|
-
AND run_at = ?
|
|
227
|
-
AND repeat_every_ms = ?
|
|
228
|
-
AND payload = ?`, retryAt, row.storage_id, row.run_at, row.repeat_every_ms, row.payload)).rowsWritten;
|
|
293
|
+
const reconcileAlarm = Effect.fn("DurableObjectAlarm.reconcileAlarm")(function* () {
|
|
294
|
+
const next = (yield* (yield* state.storage.sql.exec(`SELECT wake_at AS run_at FROM effect_cf_scheduled_alarms
|
|
295
|
+
ORDER BY wake_at, storage_id LIMIT 1`)).toArray())[0];
|
|
296
|
+
yield* Effect.tryPromise({
|
|
297
|
+
try: () => next === void 0 ? state.raw.storage.deleteAlarm() : state.raw.storage.setAlarm(next.run_at),
|
|
298
|
+
catch: (cause) => new StorageOperationError({
|
|
299
|
+
operation: next === void 0 ? "deleteAlarm" : "setAlarm",
|
|
300
|
+
cause
|
|
301
|
+
})
|
|
302
|
+
});
|
|
229
303
|
});
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const acknowledgedAt = yield* Clock.currentTimeMillis;
|
|
240
|
-
yield* (yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms
|
|
241
|
-
SET run_at = ?
|
|
242
|
-
WHERE storage_id = ?
|
|
243
|
-
AND run_at = ?
|
|
244
|
-
AND repeat_every_ms = ?
|
|
245
|
-
AND payload = ?`, acknowledgedAt + row.repeat_every_ms, row.storage_id, row.run_at, row.repeat_every_ms, row.payload)).rowsWritten;
|
|
304
|
+
const writeAttempts = Effect.fnUntraced(function* (storageId, attempts, parked, retryAt, progress) {
|
|
305
|
+
yield* state.storage.sql.exec(`INSERT OR REPLACE INTO effect_cf_alarm_attempts
|
|
306
|
+
(storage_id, revision, attempts, parked, retry_at, progress)
|
|
307
|
+
VALUES (?, ?, ?, ?, ?, ?)`, storageId, crypto.randomUUID(), attempts, parked, retryAt, progress);
|
|
308
|
+
yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms
|
|
309
|
+
SET wake_at = MAX(run_at, COALESCE(?, run_at))
|
|
310
|
+
WHERE storage_id = ?`, retryAt, storageId);
|
|
246
311
|
});
|
|
247
312
|
const cancelAlarm = Effect.fn("DurableObjectAlarm.cancelAlarm")(function* (input) {
|
|
248
313
|
const ref = yield* decodeAlarmRef(input);
|
|
249
|
-
|
|
314
|
+
const storageId = getScheduledEventId(ref);
|
|
315
|
+
yield* state.storage.sql.exec(`DELETE FROM effect_cf_scheduled_alarms WHERE storage_id = ?`, storageId);
|
|
316
|
+
yield* state.storage.sql.exec(`DELETE FROM effect_cf_alarm_attempts WHERE storage_id = ?`, storageId);
|
|
250
317
|
});
|
|
251
|
-
const scheduleAlarm = Effect.fn("DurableObjectAlarm.scheduleAlarm")(function* (input) {
|
|
318
|
+
const scheduleAlarm = Effect.fn("DurableObjectAlarm.scheduleAlarm")(function* (input, earlier, reports) {
|
|
319
|
+
const configuration = yield* getScheduleConfiguration(configurationDefaults);
|
|
252
320
|
const ref = yield* decodeAlarmRef(input);
|
|
253
|
-
const repeatEveryMillis = yield* toRepeatEveryMillis(input.repeatEvery);
|
|
321
|
+
const repeatEveryMillis = yield* toRepeatEveryMillis(input.repeatEvery, configuration.minimumRepeatInterval);
|
|
254
322
|
const payload = yield* encodeStoredPayload(input.payload);
|
|
323
|
+
const storageId = getScheduledEventId(ref);
|
|
324
|
+
const existing = yield* readRow(storageId);
|
|
325
|
+
const pass = yield* CurrentAlarmPass;
|
|
326
|
+
const source = pass?.row;
|
|
327
|
+
const progress = existing?.progress ?? source?.progress ?? -1;
|
|
328
|
+
if (pass !== void 0 && !pass.active) return yield* Effect.fail(new StorageOperationError({
|
|
329
|
+
operation: "alarm.schedule",
|
|
330
|
+
cause: /* @__PURE__ */ new Error("Alarm handlers cannot schedule detached work after their pass ends")
|
|
331
|
+
}));
|
|
332
|
+
if (input.progress !== void 0) yield* S.decodeUnknownEffect(S.Natural)(input.progress).pipe(Effect.mapError((cause) => new InvalidAlarmRefError({ cause })));
|
|
333
|
+
if (source === void 0 && input.progress !== void 0 && input.progress <= progress) return;
|
|
334
|
+
const progressed = input.progress !== void 0 && input.progress > progress;
|
|
335
|
+
const sourceProgressed = source !== void 0 && storageId === source.storage_id && progress > source.progress;
|
|
336
|
+
const attempts = source === void 0 || progressed || sourceProgressed ? 0 : source.parked === 1 || existing?.parked === 1 ? Math.max(source.attempts, existing?.attempts ?? 0) : Math.min(configuration.unchangedAttemptBudget, Math.max(source.attempts + 1, existing?.attempts ?? 0));
|
|
337
|
+
const parked = attempts > 0 && (attempts >= configuration.unchangedAttemptBudget || source?.parked === 1 || existing?.parked === 1) ? 1 : 0;
|
|
338
|
+
const now = yield* Clock.currentTimeMillis;
|
|
339
|
+
const retryAt = attempts === 0 ? null : now + retryDelay(attempts, configuration, parked === 1);
|
|
255
340
|
const runAt = DateTime.toEpochMillis(input.runAt);
|
|
341
|
+
const keepEarlier = earlier && existing !== void 0 && existing.run_at <= runAt;
|
|
342
|
+
const scheduledAt = keepEarlier ? existing.run_at : runAt;
|
|
343
|
+
const scheduledPayload = keepEarlier ? existing.payload : payload;
|
|
344
|
+
const scheduledRepeat = keepEarlier ? existing.repeat_every_ms : repeatEveryMillis;
|
|
345
|
+
const nextProgress = Math.max(progress, input.progress ?? -1);
|
|
256
346
|
yield* state.storage.sql.exec(`INSERT OR REPLACE INTO effect_cf_scheduled_alarms
|
|
257
|
-
|
|
258
|
-
|
|
347
|
+
(storage_id, alarm_id, tag, run_at, repeat_every_ms, payload)
|
|
348
|
+
VALUES (?, ?, ?, ?, ?, ?)`, storageId, ref.id, ref.tag, scheduledAt, scheduledRepeat, scheduledPayload);
|
|
349
|
+
yield* writeAttempts(storageId, attempts, parked, retryAt, nextProgress);
|
|
350
|
+
if (parked === 1 && retryAt !== null && (existing?.parked ?? source?.parked ?? 0) === 0) reports.push(storageId);
|
|
259
351
|
});
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
352
|
+
const reportParked = Effect.fnUntraced(function* (storageIds) {
|
|
353
|
+
if (storageIds.length === 0) return;
|
|
354
|
+
const pass = yield* CurrentAlarmPass;
|
|
355
|
+
const report = yield* AlarmReporter;
|
|
356
|
+
for (const storageId of new Set(storageIds)) {
|
|
357
|
+
const row = yield* readRow(storageId);
|
|
358
|
+
if (row === void 0 || row.parked === 0 || row.retry_at === null) continue;
|
|
359
|
+
const event = AlarmParked.make({
|
|
360
|
+
attempts: row.attempts,
|
|
361
|
+
retryAt: DateTime.makeUnsafe(Math.max(row.run_at, row.retry_at))
|
|
362
|
+
});
|
|
363
|
+
pass?.parked.push(event);
|
|
364
|
+
yield* Effect.exit(Effect.suspend(() => report(event)));
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
const transaction = Effect.fnUntraced(function* (closure) {
|
|
368
|
+
const reports = [];
|
|
369
|
+
const result = yield* state.storage.transaction(() => Effect.withFiber((owner) => {
|
|
370
|
+
let active = true;
|
|
371
|
+
const requireActive = (effect) => Effect.withFiber((fiber) => active && fiber === owner ? effect : Effect.fail(new StorageOperationError({
|
|
372
|
+
operation: "alarm.transaction",
|
|
373
|
+
cause: /* @__PURE__ */ new Error("Alarm mutations require their active transaction callback")
|
|
273
374
|
})));
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
375
|
+
return Effect.gen(function* () {
|
|
376
|
+
yield* ensureTable(state);
|
|
377
|
+
const result = yield* Effect.suspend(() => closure({
|
|
378
|
+
cancelAlarm: (input) => requireActive(cancelAlarm(input)),
|
|
379
|
+
scheduleAlarm: (input) => requireActive(scheduleAlarm(input, false, reports)),
|
|
380
|
+
scheduleAlarmEarlier: (input) => requireActive(scheduleAlarm(input, true, reports))
|
|
381
|
+
})).pipe(Effect.ensuring(Effect.sync(() => {
|
|
382
|
+
active = false;
|
|
383
|
+
})));
|
|
384
|
+
yield* reconcileAlarm();
|
|
385
|
+
return result;
|
|
386
|
+
});
|
|
387
|
+
}));
|
|
388
|
+
yield* reportParked(reports);
|
|
389
|
+
return result;
|
|
390
|
+
});
|
|
391
|
+
const rescheduleFailedAlarm = Effect.fnUntraced(function* (row, initialDelay, configuration, reports) {
|
|
392
|
+
const current = yield* readRow(row.storage_id);
|
|
393
|
+
if (current === void 0 || current.revision !== row.revision) return;
|
|
394
|
+
const attempts = row.parked === 1 ? row.attempts : Math.min(configuration.unchangedAttemptBudget, row.attempts + 1);
|
|
395
|
+
const parked = row.parked === 1 || attempts >= configuration.unchangedAttemptBudget ? 1 : 0;
|
|
396
|
+
const retryAt = (yield* Clock.currentTimeMillis) + retryDelay(attempts, configuration, parked === 1, initialDelay);
|
|
397
|
+
yield* writeAttempts(row.storage_id, attempts, parked, retryAt, row.progress);
|
|
398
|
+
if (parked === 1 && row.parked === 0) reports.push(row.storage_id);
|
|
399
|
+
});
|
|
400
|
+
const acknowledgeAlarm = Effect.fnUntraced(function* (row, configuration) {
|
|
401
|
+
if (row.repeat_every_ms === null) {
|
|
402
|
+
if ((yield* (yield* state.storage.sql.exec(`DELETE FROM effect_cf_scheduled_alarms WHERE ${sameRevisionSql}`, row.storage_id, row.storage_id, row.revision)).rowsWritten) > 0) yield* state.storage.sql.exec(`DELETE FROM effect_cf_alarm_attempts WHERE storage_id = ?`, row.storage_id);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const now = yield* Clock.currentTimeMillis;
|
|
406
|
+
if ((yield* (yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms SET run_at = ? WHERE ${sameRevisionSql}`, now + Math.max(configuration.minimumRepeatInterval, row.repeat_every_ms), row.storage_id, row.storage_id, row.revision)).rowsWritten) > 0) yield* writeAttempts(row.storage_id, 0, 0, null, row.progress);
|
|
407
|
+
});
|
|
278
408
|
const processDueAlarms = Effect.fn("DurableObjectAlarm.processDueAlarms")(function* (handle, options) {
|
|
409
|
+
const configuration = yield* getScheduleConfiguration(configurationDefaults);
|
|
279
410
|
yield* ensureTable(state);
|
|
280
|
-
const mode = options?.mode ?? "isolated";
|
|
281
411
|
const limit = yield* getProcessLimit(options);
|
|
412
|
+
const initialDelay = yield* getFailureRetryDelay(options, configuration);
|
|
282
413
|
const now = yield* Clock.currentTimeMillis;
|
|
283
|
-
const dueRows = yield* (yield* state.storage.sql.exec(
|
|
284
|
-
|
|
285
|
-
WHERE run_at <= ?
|
|
286
|
-
ORDER BY run_at ASC, storage_id ASC
|
|
287
|
-
LIMIT ?`, now, limit)).toArray();
|
|
414
|
+
const dueRows = yield* (yield* state.storage.sql.exec(`${alarmRowsSql}
|
|
415
|
+
WHERE a.wake_at <= ? ORDER BY a.wake_at ASC, a.storage_id ASC LIMIT ?`, now, limit)).toArray();
|
|
288
416
|
const handled = [];
|
|
289
417
|
const failed = [];
|
|
290
|
-
const
|
|
418
|
+
const parked = [];
|
|
419
|
+
const handleFailure = Effect.fnUntraced(function* (row, event, cause) {
|
|
291
420
|
const failure = {
|
|
292
421
|
cause,
|
|
293
422
|
event,
|
|
@@ -296,49 +425,71 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
296
425
|
tag: row.tag
|
|
297
426
|
};
|
|
298
427
|
failed.push(failure);
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
yield* reconcileAlarm();
|
|
312
|
-
return "stop";
|
|
313
|
-
};
|
|
428
|
+
const actionExit = yield* Effect.exit(options?.onFailure === void 0 ? Effect.void : Effect.suspend(() => options.onFailure(failure)));
|
|
429
|
+
const action = Exit.isSuccess(actionExit) ? actionExit.value : void 0;
|
|
430
|
+
const actionMode = action === void 0 ? "retry" : getFailureActionMode(action);
|
|
431
|
+
const delayInput = action === void 0 ? void 0 : getFailureActionRetryDelay(action);
|
|
432
|
+
const delayExit = yield* Effect.exit(delayInput === void 0 ? Effect.succeed(initialDelay) : toFailureRescheduleMillis(delayInput, configuration.minimumRetryDelay));
|
|
433
|
+
const delay = Exit.isSuccess(delayExit) ? delayExit.value : initialDelay;
|
|
434
|
+
const reports = [];
|
|
435
|
+
yield* transaction(() => actionMode === "skip-and-advance-repeat" ? acknowledgeAlarm(row, configuration) : rescheduleFailedAlarm(row, delay, configuration, reports));
|
|
436
|
+
yield* reportParked(reports);
|
|
437
|
+
if (Exit.isFailure(actionExit)) return yield* Effect.failCause(actionExit.cause);
|
|
438
|
+
if (Exit.isFailure(delayExit)) return yield* Effect.failCause(delayExit.cause);
|
|
439
|
+
});
|
|
314
440
|
for (const row of dueRows) {
|
|
441
|
+
const current = yield* readRow(row.storage_id);
|
|
442
|
+
if (current === void 0 || current.revision !== row.revision) continue;
|
|
443
|
+
const inPass = (effect) => {
|
|
444
|
+
const pass = {
|
|
445
|
+
row,
|
|
446
|
+
parked,
|
|
447
|
+
active: true
|
|
448
|
+
};
|
|
449
|
+
return effect.pipe(Effect.provideService(CurrentAlarmPass, pass), Effect.ensuring(Effect.sync(() => {
|
|
450
|
+
pass.active = false;
|
|
451
|
+
})));
|
|
452
|
+
};
|
|
315
453
|
const eventExit = yield* Effect.exit(toAlarmDue(row));
|
|
316
454
|
if (Exit.isFailure(eventExit)) {
|
|
317
|
-
|
|
455
|
+
yield* inPass(handleFailure(row, void 0, eventExit.cause));
|
|
318
456
|
continue;
|
|
319
457
|
}
|
|
320
458
|
const event = eventExit.value;
|
|
321
|
-
const handleExit = yield* Effect.exit(handle(event));
|
|
459
|
+
const handleExit = yield* Effect.exit(inPass(Effect.suspend(() => handle(event))));
|
|
322
460
|
if (Exit.isFailure(handleExit)) {
|
|
323
|
-
|
|
461
|
+
yield* inPass(handleFailure(row, event, handleExit.cause));
|
|
324
462
|
continue;
|
|
325
463
|
}
|
|
326
|
-
yield* acknowledgeAlarm(row);
|
|
464
|
+
yield* transaction(() => acknowledgeAlarm(row, configuration));
|
|
327
465
|
handled.push(event);
|
|
328
466
|
}
|
|
329
|
-
yield*
|
|
467
|
+
yield* transaction(() => Effect.void);
|
|
330
468
|
return {
|
|
331
469
|
failed,
|
|
332
|
-
handled
|
|
470
|
+
handled,
|
|
471
|
+
parked
|
|
333
472
|
};
|
|
334
473
|
});
|
|
335
474
|
return DurableObjectAlarm.of({
|
|
336
475
|
cancelAlarm: (input) => transaction((alarms) => alarms.cancelAlarm(input)),
|
|
476
|
+
getAlarmStatus: Effect.fnUntraced(function* (input) {
|
|
477
|
+
const ref = yield* decodeAlarmRef(input);
|
|
478
|
+
yield* ensureTable(state);
|
|
479
|
+
const row = yield* readRow(getScheduledEventId(ref));
|
|
480
|
+
return row === void 0 ? void 0 : {
|
|
481
|
+
attempts: row.attempts,
|
|
482
|
+
parked: row.parked === 1,
|
|
483
|
+
runAt: DateTime.makeUnsafe(row.run_at),
|
|
484
|
+
retryAt: row.retry_at === null ? void 0 : DateTime.makeUnsafe(row.retry_at)
|
|
485
|
+
};
|
|
486
|
+
}),
|
|
337
487
|
processDueAlarms,
|
|
338
488
|
scheduleAlarm: (input) => transaction((alarms) => alarms.scheduleAlarm(input)),
|
|
489
|
+
scheduleAlarmEarlier: (input) => transaction((alarms) => alarms.scheduleAlarmEarlier(input)),
|
|
339
490
|
transaction
|
|
340
491
|
});
|
|
341
492
|
}));
|
|
342
493
|
};
|
|
343
494
|
//#endregion
|
|
344
|
-
export { DurableObjectAlarm, DurableObjectAlarmEvent, DurableObjectAlarm_exports, InvalidAlarmPayloadError, InvalidAlarmRefError, InvalidProcessDueAlarmsOptionsError, InvalidRepeatEveryError, StoredAlarmDecodeError, Tag, define, processDue };
|
|
495
|
+
export { AlarmParked, AlarmReporter, DEFAULT_MIN_REPEAT_INTERVAL_MS, DurableObjectAlarm, DurableObjectAlarmEvent, DurableObjectAlarm_exports, InvalidAlarmPayloadError, InvalidAlarmRefError, InvalidProcessDueAlarmsOptionsError, InvalidRepeatEveryError, InvalidScheduleConfigurationError, MIN_REPEAT_INTERVAL_MS, MIN_RETRY_DELAY_MS, PARKED_RETRY_DELAY_MS, ScheduleConfiguration, StoredAlarmDecodeError, Tag, define, processDue };
|
|
@@ -3,8 +3,8 @@ import * as Context$1 from "effect/Context";
|
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Layer$1 from "effect/Layer";
|
|
5
5
|
import * as Option from "effect/Option";
|
|
6
|
-
import * as Duration from "effect/Duration";
|
|
7
6
|
import * as Stream from "effect/Stream";
|
|
7
|
+
import * as Duration from "effect/Duration";
|
|
8
8
|
import { Scope } from "effect";
|
|
9
9
|
declare namespace DurableObjectContainer_d_exports {
|
|
10
10
|
export { ContainerError, ContainerExecOptions, ContainerExecOutput, ContainerExecOutputText, ContainerHttpReadinessOptions, ContainerInfo, ContainerNotConfiguredError, ContainerProcess, ContainerProcessLog, ContainerReadinessTimeoutError, ContainerSnapshot, ContainerSnapshotOptions, ContainerStartOptions, ContainerTcpPort, DurableObjectContainer, DurableObjectContainerService, fromContainer, layer };
|
|
@@ -6,8 +6,8 @@ import * as Data from "effect/Data";
|
|
|
6
6
|
import * as Effect from "effect/Effect";
|
|
7
7
|
import * as Layer from "effect/Layer";
|
|
8
8
|
import * as Option from "effect/Option";
|
|
9
|
-
import * as Duration from "effect/Duration";
|
|
10
9
|
import * as Stream from "effect/Stream";
|
|
10
|
+
import * as Duration from "effect/Duration";
|
|
11
11
|
//#region src/DurableObjectContainer.ts
|
|
12
12
|
var DurableObjectContainer_exports = /* @__PURE__ */ __exportAll({
|
|
13
13
|
ContainerError: () => ContainerError,
|
|
@@ -64,8 +64,8 @@ interface LayerOptions {
|
|
|
64
64
|
readonly attachmentKey?: string | undefined;
|
|
65
65
|
/**
|
|
66
66
|
* Heartbeat behavior. `"auto-response"` (the default) installs Cloudflare's
|
|
67
|
-
* hibernation-safe text Ping/Pong response
|
|
68
|
-
*
|
|
67
|
+
* hibernation-safe text Ping/Pong response for the lifetime of RPC sockets,
|
|
68
|
+
* including pending operations. Keepalives never dispatch application code.
|
|
69
69
|
* `"passthrough"` leaves the Durable Object's existing auto-response
|
|
70
70
|
* configuration unchanged and makes the application responsible for waking a
|
|
71
71
|
* lost pending operation.
|
|
@@ -70,7 +70,6 @@ const layer = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
70
70
|
const declarationsByTag = /* @__PURE__ */ new Map();
|
|
71
71
|
const replayRequests = [];
|
|
72
72
|
let nextClientId = 0;
|
|
73
|
-
let activeNonResumableRequestCount = 0;
|
|
74
73
|
let writeRequest;
|
|
75
74
|
for (const declaration of declarations) {
|
|
76
75
|
if (declaration.id.length === 0 || declaration.rpcTag.length === 0) return yield* Effect.die(/* @__PURE__ */ new Error("Resumable RPC stream declaration ids and tags must be non-empty"));
|
|
@@ -137,7 +136,7 @@ const layer = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
137
136
|
return connection;
|
|
138
137
|
};
|
|
139
138
|
const heartbeat = yield* makeHeartbeatController(durableObjectState, serialization, heartbeatPolicy);
|
|
140
|
-
const syncHeartbeat = () => heartbeat.setEnabled(connectionsBySocket.size > 0
|
|
139
|
+
const syncHeartbeat = () => heartbeat.setEnabled(connectionsBySocket.size > 0);
|
|
141
140
|
const persistConnection = Effect.fn("DurableObjectRpcWebSocket.persistConnection")(function* (connection) {
|
|
142
141
|
return yield* Effect.try({
|
|
143
142
|
try: () => {
|
|
@@ -171,7 +170,6 @@ const layer = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
171
170
|
if (connection === void 0) return;
|
|
172
171
|
connectionsBySocket.delete(socket.raw);
|
|
173
172
|
connectionsById.delete(connection.id);
|
|
174
|
-
activeNonResumableRequestCount -= connection.nonResumableRequestIds.size;
|
|
175
173
|
connection.requestIds.clear();
|
|
176
174
|
connection.nonResumableRequestIds.clear();
|
|
177
175
|
connection.subscriptionsByRequestId.clear();
|
|
@@ -210,7 +208,6 @@ const layer = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
210
208
|
} else {
|
|
211
209
|
connection.requestIds.add(request.id);
|
|
212
210
|
connection.nonResumableRequestIds.add(request.id);
|
|
213
|
-
activeNonResumableRequestCount++;
|
|
214
211
|
}
|
|
215
212
|
if (!(yield* persistOrReset(connection))) return false;
|
|
216
213
|
yield* syncHeartbeat();
|
|
@@ -218,7 +215,7 @@ const layer = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
218
215
|
});
|
|
219
216
|
const completeRequest = Effect.fn("DurableObjectRpcWebSocket.completeRequest")(function* (connection, requestId, preserveAckFilter = false) {
|
|
220
217
|
if (connection.requestIds.delete(requestId)) {
|
|
221
|
-
|
|
218
|
+
connection.nonResumableRequestIds.delete(requestId);
|
|
222
219
|
const subscription = removeSubscription(connection, requestId);
|
|
223
220
|
connection.currentPendingRequestIds.delete(requestId);
|
|
224
221
|
if (preserveAckFilter && subscription !== void 0) connection.suppressedAckRequestIds.add(requestId);
|
|
@@ -229,7 +226,6 @@ const layer = (options = {}) => Layer.effectContext(Effect.gen(function* () {
|
|
|
229
226
|
});
|
|
230
227
|
const completeAllRequests = Effect.fn("DurableObjectRpcWebSocket.completeAllRequests")(function* (connection) {
|
|
231
228
|
const hadRequests = connection.requestIds.size > 0;
|
|
232
|
-
if (connection.requestIds.size > 0) activeNonResumableRequestCount -= connection.nonResumableRequestIds.size;
|
|
233
229
|
connection.requestIds.clear();
|
|
234
230
|
connection.nonResumableRequestIds.clear();
|
|
235
231
|
connection.subscriptionsByRequestId.clear();
|
|
@@ -2,7 +2,6 @@ import * as Effect from "effect/Effect";
|
|
|
2
2
|
import * as S from "effect/Schema";
|
|
3
3
|
import * as Option from "effect/Option";
|
|
4
4
|
import * as Cause from "effect/Cause";
|
|
5
|
-
import * as Duration from "effect/Duration";
|
|
6
5
|
declare namespace DurableObjectStorage_d_exports {
|
|
7
6
|
export { DurableObjectStorage, DurableObjectTransaction, SchemaBackedSyncKvStorage, SqlCursor, SqlStorage, SqlStorageValue, StorageOperationError, SyncKvDefinition, SyncKvStorage, fromDurableObjectStorage };
|
|
8
7
|
}
|
|
@@ -69,23 +68,17 @@ interface DurableObjectTransaction {
|
|
|
69
68
|
delete(keys: Array<string>, options?: globalThis.DurableObjectPutOptions): StorageEffect<number>;
|
|
70
69
|
rollback(): StorageEffect<void>;
|
|
71
70
|
getAlarm(options?: globalThis.DurableObjectGetAlarmOptions): StorageEffect<number | null>;
|
|
72
|
-
setAlarm(scheduledTime: number | Date, options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
|
|
73
|
-
setAlarmAfter(delay: Duration.Input, options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
|
|
74
|
-
deleteAlarm(options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
|
|
75
71
|
}
|
|
76
72
|
interface DurableObjectStorage {
|
|
77
73
|
get<T = unknown>(key: string, options?: globalThis.DurableObjectGetOptions): StorageEffect<T | undefined>;
|
|
78
74
|
put<T>(key: string, value: T, options?: globalThis.DurableObjectPutOptions): StorageEffect<void>;
|
|
79
75
|
delete(key: string, options?: globalThis.DurableObjectPutOptions): StorageEffect<boolean>;
|
|
80
76
|
/**
|
|
81
|
-
* Deletes all stored data
|
|
82
|
-
*
|
|
77
|
+
* Deletes all stored data, including logical schedules. Use the alarm scheduler
|
|
78
|
+
* to cancel individual alarms without deleting application data.
|
|
83
79
|
*/
|
|
84
80
|
deleteAll(options?: globalThis.DurableObjectPutOptions): StorageEffect<void>;
|
|
85
81
|
getAlarm(options?: globalThis.DurableObjectGetAlarmOptions): StorageEffect<number | null>;
|
|
86
|
-
setAlarm(scheduledTime: number | Date, options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
|
|
87
|
-
setAlarmAfter(delay: Duration.Input, options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
|
|
88
|
-
deleteAlarm(options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
|
|
89
82
|
/**
|
|
90
83
|
* SQLite-backed Durable Object storage only.
|
|
91
84
|
*
|