effect-cf 0.0.3 → 0.1.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/LICENSE +21 -0
- package/README.md +36 -46
- package/dist/index.d.mts +353 -28
- package/dist/index.mjs +333 -6
- package/package.json +1 -1
- package/src/DurableObject.ts +26 -2
- package/src/DurableObjectAlarm.ts +832 -0
- package/src/Worker.ts +11 -45
- package/src/WorkerDefinition.ts +4 -9
- package/src/index.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
|
-
import { Cause, Config, ConfigProvider, Context, Data, Effect, Exit, Layer, ManagedRuntime, Option, Queue, Schema } from "effect";
|
|
2
|
+
import { Cause, Clock, Config, ConfigProvider, Context, Data, DateTime, Duration, Effect, Exit, Layer, ManagedRuntime, Option, Queue, Schema } from "effect";
|
|
3
3
|
import { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint } from "cloudflare:workers";
|
|
4
4
|
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
5
5
|
import * as RpcMessage from "effect/unstable/rpc/RpcMessage";
|
|
@@ -484,9 +484,7 @@ const make$3 = (layer, options) => {
|
|
|
484
484
|
const fetchHandler = options.fetch;
|
|
485
485
|
if (fetchHandler === void 0) return Promise.resolve(new Response("Not Found", { status: 404 }));
|
|
486
486
|
const requestServices = Layer.mergeAll(Layer.succeed(NativeRequest, request), Layer.succeed(HttpServerRequest.HttpServerRequest, HttpServerRequest.fromWeb(request)));
|
|
487
|
-
|
|
488
|
-
const requestEffect = options.requestLayer === void 0 ? fetchEffect : fetchEffect.pipe(Effect.provide(options.requestLayer));
|
|
489
|
-
return this[RunSymbol$1](requestEffect.pipe(Effect.provide(requestServices)));
|
|
487
|
+
return this[RunSymbol$1](renderFetchSuccess(fetchHandler).pipe(Effect.provide(requestServices)));
|
|
490
488
|
}
|
|
491
489
|
}
|
|
492
490
|
defineEntrypointRpcMethods("Worker", EffectWorker.prototype, options.rpc, reservedMethodNames$2, (self, effect) => self[RunSymbol$1](effect));
|
|
@@ -912,7 +910,14 @@ const make$1 = (layer, options = {}) => {
|
|
|
912
910
|
return this[RunSymbol](Effect.provideService(fetchHandler, NativeRequest, request));
|
|
913
911
|
}
|
|
914
912
|
alarm(alarmInfo) {
|
|
915
|
-
|
|
913
|
+
const logicalAlarms = options.alarms?.pipe(Effect.asVoid);
|
|
914
|
+
const rawAlarm = options.alarm?.(alarmInfo);
|
|
915
|
+
if (logicalAlarms !== void 0 && rawAlarm !== void 0) return this[RunSymbol](Effect.gen(function* () {
|
|
916
|
+
yield* logicalAlarms;
|
|
917
|
+
yield* rawAlarm;
|
|
918
|
+
}));
|
|
919
|
+
if (logicalAlarms !== void 0) return this[RunSymbol](logicalAlarms);
|
|
920
|
+
if (rawAlarm !== void 0) return this[RunSymbol](rawAlarm);
|
|
916
921
|
}
|
|
917
922
|
webSocketMessage(socket, message) {
|
|
918
923
|
if (options.webSocketMessage !== void 0) return this[RunSymbol](options.webSocketMessage(socket, message));
|
|
@@ -931,6 +936,328 @@ const Tag = Tag$1;
|
|
|
931
936
|
const method = method$1;
|
|
932
937
|
const implement = implement$1;
|
|
933
938
|
//#endregion
|
|
939
|
+
//#region src/DurableObjectAlarm.ts
|
|
940
|
+
var DurableObjectAlarm_exports = /* @__PURE__ */ __exportAll({
|
|
941
|
+
DurableObjectAlarm: () => DurableObjectAlarm,
|
|
942
|
+
DurableObjectAlarmEvent: () => DurableObjectAlarmEvent,
|
|
943
|
+
InvalidAlarmPayloadError: () => InvalidAlarmPayloadError,
|
|
944
|
+
InvalidAlarmRefError: () => InvalidAlarmRefError,
|
|
945
|
+
InvalidProcessDueAlarmsOptionsError: () => InvalidProcessDueAlarmsOptionsError,
|
|
946
|
+
InvalidRepeatEveryError: () => InvalidRepeatEveryError,
|
|
947
|
+
StoredAlarmDecodeError: () => StoredAlarmDecodeError,
|
|
948
|
+
define: () => define,
|
|
949
|
+
process: () => process,
|
|
950
|
+
processDue: () => processDue
|
|
951
|
+
});
|
|
952
|
+
const INIT_TABLE_SQL = `
|
|
953
|
+
CREATE TABLE IF NOT EXISTS effect_cf_scheduled_alarms (
|
|
954
|
+
storage_id TEXT PRIMARY KEY,
|
|
955
|
+
alarm_id TEXT NOT NULL,
|
|
956
|
+
tag TEXT NOT NULL,
|
|
957
|
+
run_at INTEGER NOT NULL,
|
|
958
|
+
repeat_every_ms INTEGER,
|
|
959
|
+
payload TEXT NOT NULL
|
|
960
|
+
);
|
|
961
|
+
CREATE INDEX IF NOT EXISTS idx_effect_cf_scheduled_alarms_run_at_storage_id
|
|
962
|
+
ON effect_cf_scheduled_alarms (run_at, storage_id);
|
|
963
|
+
`;
|
|
964
|
+
const DEFAULT_PROCESS_DUE_ALARMS_LIMIT = 100;
|
|
965
|
+
const DEFAULT_PROCESS_DUE_ALARMS_FAILURE_RESCHEDULE_AFTER = "30 seconds";
|
|
966
|
+
const getScheduledEventId = (input) => `effect-cf-alarm:${encodeURIComponent(input.tag)}:${encodeURIComponent(input.id)}`;
|
|
967
|
+
var InvalidAlarmRefError = class extends Data.TaggedError("InvalidAlarmRefError") {};
|
|
968
|
+
var InvalidAlarmPayloadError = class extends Data.TaggedError("InvalidAlarmPayloadError") {};
|
|
969
|
+
var InvalidRepeatEveryError = class extends Data.TaggedError("InvalidRepeatEveryError") {};
|
|
970
|
+
var InvalidProcessDueAlarmsOptionsError = class extends Data.TaggedError("InvalidProcessDueAlarmsOptionsError") {};
|
|
971
|
+
var StoredAlarmDecodeError = class extends Data.TaggedError("StoredAlarmDecodeError") {};
|
|
972
|
+
/**
|
|
973
|
+
* Events handled by `processDueAlarms`.
|
|
974
|
+
*
|
|
975
|
+
* `AlarmDue` represents one durable alarm whose scheduled time is now due. The
|
|
976
|
+
* `tag` is the consumer-owned routing key, while `id` is the stable alarm id
|
|
977
|
+
* within that tag.
|
|
978
|
+
*
|
|
979
|
+
* `scheduledAt` is the logical due-after timestamp from durable storage. It is
|
|
980
|
+
* not expected to match the current Durable Object alarm invocation time,
|
|
981
|
+
* especially during retries. Retries re-scan durable rows with `run_at <= now`
|
|
982
|
+
* and identify logical events by `{ tag, id }`.
|
|
983
|
+
*
|
|
984
|
+
* @example Handling events by tag
|
|
985
|
+
* ```ts
|
|
986
|
+
* yield* durableObjectAlarm.processDueAlarms((event) =>
|
|
987
|
+
* event.tag === "heartbeat"
|
|
988
|
+
* ? heartbeatManager.handleAlarmEvent(event).pipe(Effect.asVoid)
|
|
989
|
+
* : Effect.void
|
|
990
|
+
* );
|
|
991
|
+
* ```
|
|
992
|
+
*/
|
|
993
|
+
const DurableObjectAlarmEvent = Schema.TaggedUnion({ AlarmDue: {
|
|
994
|
+
id: Schema.NonEmptyString,
|
|
995
|
+
payload: Schema.Json,
|
|
996
|
+
scheduledAt: Schema.DateTimeUtc,
|
|
997
|
+
tag: Schema.NonEmptyString
|
|
998
|
+
} });
|
|
999
|
+
const AlarmRefSchema = Schema.Struct({
|
|
1000
|
+
id: Schema.NonEmptyString,
|
|
1001
|
+
tag: Schema.NonEmptyString
|
|
1002
|
+
});
|
|
1003
|
+
const decodeAlarmRef = (input) => Schema.decodeUnknownEffect(AlarmRefSchema)(input).pipe(Effect.mapError((cause) => new InvalidAlarmRefError({ cause })));
|
|
1004
|
+
const StoredPayloadString = Schema.fromJsonString(Schema.Json);
|
|
1005
|
+
const decodeStoredPayload = (row) => Schema.decodeUnknownEffect(StoredPayloadString)(row.payload).pipe(Effect.mapError((cause) => new StoredAlarmDecodeError({
|
|
1006
|
+
cause,
|
|
1007
|
+
storageId: row.storage_id
|
|
1008
|
+
})));
|
|
1009
|
+
const encodeStoredPayload = (payload) => Schema.encodeEffect(StoredPayloadString)(payload).pipe(Effect.mapError((cause) => new InvalidAlarmPayloadError({ cause })));
|
|
1010
|
+
const ensureTable = (state) => state.storage.sql.exec(INIT_TABLE_SQL).pipe(Effect.asVoid);
|
|
1011
|
+
const toRepeatEveryMillis = (input) => {
|
|
1012
|
+
if (input === void 0) return Effect.succeed(null);
|
|
1013
|
+
return Effect.try({
|
|
1014
|
+
try: () => {
|
|
1015
|
+
const millis = Duration.toMillis(Duration.fromInputUnsafe(input));
|
|
1016
|
+
if (!Number.isFinite(millis) || millis <= 0) throw new Error("Alarm repeatEvery must be a positive finite duration");
|
|
1017
|
+
return Math.ceil(millis);
|
|
1018
|
+
},
|
|
1019
|
+
catch: (cause) => new InvalidRepeatEveryError({ cause })
|
|
1020
|
+
});
|
|
1021
|
+
};
|
|
1022
|
+
const toAlarmDue = (row) => Effect.gen(function* () {
|
|
1023
|
+
if (!Number.isFinite(row.run_at)) return yield* Effect.fail(new StoredAlarmDecodeError({
|
|
1024
|
+
cause: /* @__PURE__ */ new Error("Stored alarm run_at must be a finite number"),
|
|
1025
|
+
storageId: row.storage_id
|
|
1026
|
+
}));
|
|
1027
|
+
return DurableObjectAlarmEvent.make({
|
|
1028
|
+
_tag: "AlarmDue",
|
|
1029
|
+
id: row.alarm_id,
|
|
1030
|
+
payload: yield* decodeStoredPayload(row),
|
|
1031
|
+
scheduledAt: DateTime.toUtc(DateTime.makeUnsafe(row.run_at)),
|
|
1032
|
+
tag: row.tag
|
|
1033
|
+
});
|
|
1034
|
+
});
|
|
1035
|
+
const getProcessLimit = (options) => {
|
|
1036
|
+
const limit = options?.limit ?? DEFAULT_PROCESS_DUE_ALARMS_LIMIT;
|
|
1037
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) return Effect.fail(new InvalidProcessDueAlarmsOptionsError({ cause: /* @__PURE__ */ new Error("processDueAlarms limit must be a positive safe integer") }));
|
|
1038
|
+
return Effect.succeed(limit);
|
|
1039
|
+
};
|
|
1040
|
+
const toFailureRescheduleMillis = (input) => Effect.try({
|
|
1041
|
+
try: () => {
|
|
1042
|
+
const millis = Duration.toMillis(Duration.fromInputUnsafe(input));
|
|
1043
|
+
if (!Number.isFinite(millis) || millis <= 0) throw new Error("Alarm failure rescheduleAfter must be a positive finite duration");
|
|
1044
|
+
return Math.ceil(millis);
|
|
1045
|
+
},
|
|
1046
|
+
catch: (cause) => new InvalidProcessDueAlarmsOptionsError({ cause })
|
|
1047
|
+
});
|
|
1048
|
+
const getFailureRetryDelay = (options) => toFailureRescheduleMillis(options?.retryFailedAfter ?? DEFAULT_PROCESS_DUE_ALARMS_FAILURE_RESCHEDULE_AFTER);
|
|
1049
|
+
const getFailureActionMode = (action) => typeof action === "string" ? action : action.mode;
|
|
1050
|
+
const getFailureActionRetryDelay = (action) => typeof action === "string" || action.mode !== "retry" ? void 0 : action.retryFailedAfter;
|
|
1051
|
+
const processDue = (handle, options = {}) => Effect.gen(function* () {
|
|
1052
|
+
return yield* (yield* DurableObjectAlarm).processDueAlarms(handle, options);
|
|
1053
|
+
});
|
|
1054
|
+
const process = processDue;
|
|
1055
|
+
const isAlarmDefinitionConfig = (definition) => typeof definition === "object" && definition !== null && "payload" in definition;
|
|
1056
|
+
const getAlarmDefinitionSchema = (definition) => isAlarmDefinitionConfig(definition) ? definition.payload : definition;
|
|
1057
|
+
const getAlarmDefinitionFailureAction = (definition) => {
|
|
1058
|
+
if (definition === void 0 || !isAlarmDefinitionConfig(definition)) return;
|
|
1059
|
+
if (definition.failure === void 0) return;
|
|
1060
|
+
return definition.failure === "retry" ? {
|
|
1061
|
+
mode: "retry",
|
|
1062
|
+
retryFailedAfter: definition.retry?.initialDelay
|
|
1063
|
+
} : { mode: definition.failure };
|
|
1064
|
+
};
|
|
1065
|
+
const define = (definitions) => ({ handlers: (handlers, options) => processDue((event) => Effect.gen(function* () {
|
|
1066
|
+
const definition = definitions[event.tag];
|
|
1067
|
+
if (definition === void 0) return;
|
|
1068
|
+
const schema = getAlarmDefinitionSchema(definition);
|
|
1069
|
+
const payload = yield* Schema.decodeUnknownEffect(schema)(event.payload).pipe(Effect.mapError((cause) => new StoredAlarmDecodeError({
|
|
1070
|
+
cause,
|
|
1071
|
+
storageId: getScheduledEventId(event)
|
|
1072
|
+
})));
|
|
1073
|
+
const handler = handlers[event.tag];
|
|
1074
|
+
yield* handler({
|
|
1075
|
+
...event,
|
|
1076
|
+
payload
|
|
1077
|
+
});
|
|
1078
|
+
}), {
|
|
1079
|
+
...options,
|
|
1080
|
+
onFailure: (failure) => Effect.gen(function* () {
|
|
1081
|
+
const action = getAlarmDefinitionFailureAction(definitions[failure.tag]);
|
|
1082
|
+
const optionAction = options?.onFailure === void 0 ? void 0 : yield* options.onFailure(failure);
|
|
1083
|
+
return action ?? optionAction;
|
|
1084
|
+
})
|
|
1085
|
+
}) });
|
|
1086
|
+
/**
|
|
1087
|
+
* SQLite-backed Durable Object alarm scheduler.
|
|
1088
|
+
*
|
|
1089
|
+
* `DurableObjectAlarm` stores alarms in the current Durable Object's SQLite storage
|
|
1090
|
+
* and keeps the platform alarm set to the earliest pending scheduled alarm. The
|
|
1091
|
+
* platform alarm timestamp is only a wake-up hint; logical event identity comes
|
|
1092
|
+
* from persisted `{ tag, id }` rows.
|
|
1093
|
+
*
|
|
1094
|
+
* Retry safety depends on acknowledgement ordering. `processDueAlarms` runs the
|
|
1095
|
+
* caller's handler first and only then conditionally deletes or advances the
|
|
1096
|
+
* same selected row. By default, handler failures are isolated: the failed row
|
|
1097
|
+
* is retried after a delay, later due rows continue, and the platform alarm is
|
|
1098
|
+
* reconciled once at the end. Use `mode: "ordered"` for strict workflows where
|
|
1099
|
+
* one failed logical alarm should block later due alarms.
|
|
1100
|
+
*
|
|
1101
|
+
* Provide `DurableObjectAlarm.layer` anywhere `DurableObjectState` is available.
|
|
1102
|
+
*
|
|
1103
|
+
* @example Providing the service
|
|
1104
|
+
* ```ts
|
|
1105
|
+
* const program = Effect.gen(function* () {
|
|
1106
|
+
* const durableObjectAlarm = yield* DurableObjectAlarm;
|
|
1107
|
+
* const now = yield* DateTime.now;
|
|
1108
|
+
*
|
|
1109
|
+
* yield* durableObjectAlarm.scheduleAlarm({
|
|
1110
|
+
* tag: "heartbeat",
|
|
1111
|
+
* id: "heartbeat-check",
|
|
1112
|
+
* runAt: DateTime.add(now, { seconds: 5 }),
|
|
1113
|
+
* repeatEvery: Duration.seconds(5),
|
|
1114
|
+
* payload: null,
|
|
1115
|
+
* });
|
|
1116
|
+
* }).pipe(Effect.provide(DurableObjectAlarm.layer));
|
|
1117
|
+
* ```
|
|
1118
|
+
*
|
|
1119
|
+
* @example Processing due alarms
|
|
1120
|
+
* ```ts
|
|
1121
|
+
* const handledEvents = yield* durableObjectAlarm.processDueAlarms((event) =>
|
|
1122
|
+
* Effect.gen(function* () {
|
|
1123
|
+
* if (event.tag === "heartbeat") {
|
|
1124
|
+
* yield* heartbeatManager.handleAlarmEvent(event);
|
|
1125
|
+
* }
|
|
1126
|
+
* })
|
|
1127
|
+
* );
|
|
1128
|
+
* ```
|
|
1129
|
+
*/
|
|
1130
|
+
var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("effect-cf/DurableObjectAlarm") {
|
|
1131
|
+
static layer = Layer.effect(DurableObjectAlarm, Effect.gen(function* () {
|
|
1132
|
+
const state = yield* DurableObjectState;
|
|
1133
|
+
const reconcileAlarm = Effect.fn("DurableObjectAlarm.reconcileAlarm")(function* () {
|
|
1134
|
+
const next = (yield* (yield* state.storage.sql.exec(`SELECT run_at FROM effect_cf_scheduled_alarms ORDER BY run_at ASC, storage_id ASC LIMIT 1`)).toArray())[0];
|
|
1135
|
+
if (next === void 0) {
|
|
1136
|
+
yield* state.storage.deleteAlarm();
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
yield* state.storage.setAlarm(next.run_at);
|
|
1140
|
+
});
|
|
1141
|
+
const rescheduleFailedAlarm = Effect.fn("DurableObjectAlarm.rescheduleFailedAlarm")(function* (row, retryDelayMillis) {
|
|
1142
|
+
const retryAt = (yield* Clock.currentTimeMillis) + retryDelayMillis;
|
|
1143
|
+
if (row.repeat_every_ms === null) {
|
|
1144
|
+
yield* (yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms
|
|
1145
|
+
SET run_at = ?
|
|
1146
|
+
WHERE storage_id = ?
|
|
1147
|
+
AND run_at = ?
|
|
1148
|
+
AND repeat_every_ms IS NULL
|
|
1149
|
+
AND payload = ?`, retryAt, row.storage_id, row.run_at, row.payload)).rowsWritten;
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
yield* (yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms
|
|
1153
|
+
SET run_at = ?
|
|
1154
|
+
WHERE storage_id = ?
|
|
1155
|
+
AND run_at = ?
|
|
1156
|
+
AND repeat_every_ms = ?
|
|
1157
|
+
AND payload = ?`, retryAt, row.storage_id, row.run_at, row.repeat_every_ms, row.payload)).rowsWritten;
|
|
1158
|
+
});
|
|
1159
|
+
const acknowledgeAlarm = Effect.fn("DurableObjectAlarm.acknowledgeAlarm")(function* (row) {
|
|
1160
|
+
if (row.repeat_every_ms === null) {
|
|
1161
|
+
yield* (yield* state.storage.sql.exec(`DELETE FROM effect_cf_scheduled_alarms
|
|
1162
|
+
WHERE storage_id = ?
|
|
1163
|
+
AND run_at = ?
|
|
1164
|
+
AND repeat_every_ms IS NULL
|
|
1165
|
+
AND payload = ?`, row.storage_id, row.run_at, row.payload)).rowsWritten;
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const acknowledgedAt = yield* Clock.currentTimeMillis;
|
|
1169
|
+
yield* (yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms
|
|
1170
|
+
SET run_at = ?
|
|
1171
|
+
WHERE storage_id = ?
|
|
1172
|
+
AND run_at = ?
|
|
1173
|
+
AND repeat_every_ms = ?
|
|
1174
|
+
AND payload = ?`, acknowledgedAt + row.repeat_every_ms, row.storage_id, row.run_at, row.repeat_every_ms, row.payload)).rowsWritten;
|
|
1175
|
+
});
|
|
1176
|
+
const cancelAlarm = Effect.fn("DurableObjectAlarm.cancelAlarm")(function* (input) {
|
|
1177
|
+
const ref = yield* decodeAlarmRef(input);
|
|
1178
|
+
yield* state.storage.transaction(() => Effect.gen(function* () {
|
|
1179
|
+
yield* ensureTable(state);
|
|
1180
|
+
yield* state.storage.sql.exec(`DELETE FROM effect_cf_scheduled_alarms WHERE storage_id = ?`, getScheduledEventId(ref)).pipe(Effect.asVoid);
|
|
1181
|
+
yield* reconcileAlarm();
|
|
1182
|
+
}));
|
|
1183
|
+
});
|
|
1184
|
+
const scheduleAlarm = Effect.fn("DurableObjectAlarm.scheduleAlarm")(function* (input) {
|
|
1185
|
+
const ref = yield* decodeAlarmRef(input);
|
|
1186
|
+
const repeatEveryMillis = yield* toRepeatEveryMillis(input.repeatEvery);
|
|
1187
|
+
const payload = yield* encodeStoredPayload(input.payload);
|
|
1188
|
+
const runAt = DateTime.toEpochMillis(input.runAt);
|
|
1189
|
+
yield* state.storage.transaction(() => Effect.gen(function* () {
|
|
1190
|
+
yield* ensureTable(state);
|
|
1191
|
+
yield* state.storage.sql.exec(`INSERT OR REPLACE INTO effect_cf_scheduled_alarms
|
|
1192
|
+
(storage_id, alarm_id, tag, run_at, repeat_every_ms, payload)
|
|
1193
|
+
VALUES (?, ?, ?, ?, ?, ?)`, getScheduledEventId(ref), ref.id, ref.tag, runAt, repeatEveryMillis, payload).pipe(Effect.asVoid);
|
|
1194
|
+
yield* reconcileAlarm();
|
|
1195
|
+
}));
|
|
1196
|
+
});
|
|
1197
|
+
const processDueAlarms = Effect.fn("DurableObjectAlarm.processDueAlarms")(function* (handle, options) {
|
|
1198
|
+
yield* ensureTable(state);
|
|
1199
|
+
const mode = options?.mode ?? "isolated";
|
|
1200
|
+
const limit = yield* getProcessLimit(options);
|
|
1201
|
+
const now = yield* Clock.currentTimeMillis;
|
|
1202
|
+
const dueRows = yield* (yield* state.storage.sql.exec(`SELECT storage_id, alarm_id, tag, run_at, repeat_every_ms, payload
|
|
1203
|
+
FROM effect_cf_scheduled_alarms
|
|
1204
|
+
WHERE run_at <= ?
|
|
1205
|
+
ORDER BY run_at ASC, storage_id ASC
|
|
1206
|
+
LIMIT ?`, now, limit)).toArray();
|
|
1207
|
+
const handled = [];
|
|
1208
|
+
const failed = [];
|
|
1209
|
+
const handleFailure = function* (row, event, cause) {
|
|
1210
|
+
const failure = {
|
|
1211
|
+
cause,
|
|
1212
|
+
event,
|
|
1213
|
+
id: row.alarm_id,
|
|
1214
|
+
storageId: row.storage_id,
|
|
1215
|
+
tag: row.tag
|
|
1216
|
+
};
|
|
1217
|
+
failed.push(failure);
|
|
1218
|
+
const failureAction = options?.onFailure === void 0 ? void 0 : yield* options.onFailure(failure);
|
|
1219
|
+
const actionMode = failureAction === void 0 ? mode : getFailureActionMode(failureAction);
|
|
1220
|
+
if (actionMode === "retry" || actionMode === "isolated") {
|
|
1221
|
+
const actionRetryDelay = failureAction === void 0 ? void 0 : getFailureActionRetryDelay(failureAction);
|
|
1222
|
+
yield* rescheduleFailedAlarm(row, actionRetryDelay === void 0 ? yield* getFailureRetryDelay(options) : yield* toFailureRescheduleMillis(actionRetryDelay));
|
|
1223
|
+
return "continue";
|
|
1224
|
+
}
|
|
1225
|
+
if (actionMode === "skip-and-advance-repeat") {
|
|
1226
|
+
yield* acknowledgeAlarm(row);
|
|
1227
|
+
return "continue";
|
|
1228
|
+
}
|
|
1229
|
+
yield* reconcileAlarm();
|
|
1230
|
+
return "stop";
|
|
1231
|
+
};
|
|
1232
|
+
for (const row of dueRows) {
|
|
1233
|
+
const eventExit = yield* Effect.exit(toAlarmDue(row));
|
|
1234
|
+
if (Exit.isFailure(eventExit)) {
|
|
1235
|
+
if ((yield* handleFailure(row, void 0, eventExit.cause)) === "stop") return yield* Effect.failCause(eventExit.cause);
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
const event = eventExit.value;
|
|
1239
|
+
const handleExit = yield* Effect.exit(handle(event));
|
|
1240
|
+
if (Exit.isFailure(handleExit)) {
|
|
1241
|
+
if ((yield* handleFailure(row, event, handleExit.cause)) === "stop") return yield* Effect.failCause(handleExit.cause);
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
yield* acknowledgeAlarm(row);
|
|
1245
|
+
handled.push(event);
|
|
1246
|
+
}
|
|
1247
|
+
yield* reconcileAlarm();
|
|
1248
|
+
return {
|
|
1249
|
+
failed,
|
|
1250
|
+
handled
|
|
1251
|
+
};
|
|
1252
|
+
});
|
|
1253
|
+
return DurableObjectAlarm.of({
|
|
1254
|
+
cancelAlarm,
|
|
1255
|
+
processDueAlarms,
|
|
1256
|
+
scheduleAlarm
|
|
1257
|
+
});
|
|
1258
|
+
}).pipe(Effect.withSpan("DurableObjectAlarm.layer")));
|
|
1259
|
+
};
|
|
1260
|
+
//#endregion
|
|
934
1261
|
//#region src/DurableObjectRpcWebSocket.ts
|
|
935
1262
|
var DurableObjectRpcWebSocket_exports = /* @__PURE__ */ __exportAll({
|
|
936
1263
|
DurableObjectRpcWebSocket: () => DurableObjectRpcWebSocket,
|
|
@@ -1209,4 +1536,4 @@ const Service = () => (id, definition) => {
|
|
|
1209
1536
|
});
|
|
1210
1537
|
};
|
|
1211
1538
|
//#endregion
|
|
1212
|
-
export { Binding_exports as Binding, DurableObject_exports as DurableObject, DurableObjectDefinition_exports as DurableObjectDefinition, DurableObjectNamespace_exports as DurableObjectNamespace, DurableObjectRpcWebSocket_exports as DurableObjectRpcWebSocket, DurableObjectState_exports as DurableObjectState, DurableObjectStorage_exports as DurableObjectStorage, DurableObjectWebSocket_exports as DurableObjectWebSocket, Kv_exports as Kv, Rpc_exports as Rpc, RpcDefinition_exports as RpcDefinition, ServiceBinding_exports as ServiceBinding, Worker_exports as Worker, WorkerConfig, WorkerDefinition_exports as WorkerDefinition, WorkerEnvironment };
|
|
1539
|
+
export { Binding_exports as Binding, DurableObject_exports as DurableObject, DurableObjectAlarm_exports as DurableObjectAlarm, DurableObjectDefinition_exports as DurableObjectDefinition, DurableObjectNamespace_exports as DurableObjectNamespace, DurableObjectRpcWebSocket_exports as DurableObjectRpcWebSocket, DurableObjectState_exports as DurableObjectState, DurableObjectStorage_exports as DurableObjectStorage, DurableObjectWebSocket_exports as DurableObjectWebSocket, Kv_exports as Kv, Rpc_exports as Rpc, RpcDefinition_exports as RpcDefinition, ServiceBinding_exports as ServiceBinding, Worker_exports as Worker, WorkerConfig, WorkerDefinition_exports as WorkerDefinition, WorkerEnvironment };
|
package/package.json
CHANGED
package/src/DurableObject.ts
CHANGED
|
@@ -72,6 +72,14 @@ export interface DurableObjectOptions<ROut, Rpc extends DurableObjectRpc<ROut>>
|
|
|
72
72
|
readonly rpc?: Rpc;
|
|
73
73
|
/** Optional fetch handler for HTTP/WebSocket requests. */
|
|
74
74
|
readonly fetch?: Effect.Effect<Response, unknown, FetchContext<ROut>>;
|
|
75
|
+
/**
|
|
76
|
+
* Optional logical alarm processing effect.
|
|
77
|
+
*
|
|
78
|
+
* This runs before `alarm` and should be built with helpers such as
|
|
79
|
+
* `DurableObjectAlarm.processDue(...)` so the reusable scheduler stays inside
|
|
80
|
+
* the Durable Object's single managed runtime boundary.
|
|
81
|
+
*/
|
|
82
|
+
readonly alarms?: Effect.Effect<unknown, unknown, HandlerContext<ROut>>;
|
|
75
83
|
readonly alarm?: (
|
|
76
84
|
alarmInfo?: globalThis.AlarmInvocationInfo,
|
|
77
85
|
) => Effect.Effect<void, unknown, HandlerContext<ROut>>;
|
|
@@ -141,8 +149,24 @@ export const make = <
|
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> | void {
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
const logicalAlarms = options.alarms?.pipe(Effect.asVoid);
|
|
153
|
+
const rawAlarm = options.alarm?.(alarmInfo);
|
|
154
|
+
|
|
155
|
+
if (logicalAlarms !== undefined && rawAlarm !== undefined) {
|
|
156
|
+
return this[RunSymbol](
|
|
157
|
+
Effect.gen(function* () {
|
|
158
|
+
yield* logicalAlarms;
|
|
159
|
+
yield* rawAlarm;
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (logicalAlarms !== undefined) {
|
|
165
|
+
return this[RunSymbol](logicalAlarms);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (rawAlarm !== undefined) {
|
|
169
|
+
return this[RunSymbol](rawAlarm);
|
|
146
170
|
}
|
|
147
171
|
}
|
|
148
172
|
|