effect-cf 0.53.0 → 0.55.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 +16 -0
- package/README.md +6 -0
- package/dist/DurableObject.d.mts +1 -0
- package/dist/DurableObject.mjs +33 -11
- package/dist/DurableObjectAlarm.d.mts +76 -9
- package/dist/DurableObjectAlarm.mjs +265 -36
- package/dist/RateLimit.d.mts +50 -0
- package/dist/RateLimit.mjs +46 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/internal/Runtime.mjs +5 -3
- package/package.json +1 -1
- package/src/DurableObject.ts +93 -30
- package/src/DurableObjectAlarm.ts +647 -60
- package/src/RateLimit.ts +110 -0
- package/src/index.ts +1 -0
- package/src/internal/Entrypoint.ts +8 -4
- package/src/internal/Runtime.ts +12 -7
|
@@ -12,26 +12,34 @@ import * as Exit from "effect/Exit";
|
|
|
12
12
|
import * as Duration from "effect/Duration";
|
|
13
13
|
import * as Clock from "effect/Clock";
|
|
14
14
|
import * as DateTime from "effect/DateTime";
|
|
15
|
+
import * as Deferred from "effect/Deferred";
|
|
16
|
+
import * as Semaphore from "effect/Semaphore";
|
|
15
17
|
//#region src/DurableObjectAlarm.ts
|
|
16
18
|
var DurableObjectAlarm_exports = /* @__PURE__ */ __exportAll({
|
|
17
19
|
AlarmParked: () => AlarmParked,
|
|
18
20
|
AlarmReporter: () => AlarmReporter,
|
|
19
21
|
DEFAULT_MIN_REPEAT_INTERVAL_MS: () => DEFAULT_MIN_REPEAT_INTERVAL_MS,
|
|
22
|
+
DispatchAfterEvent: () => DispatchAfterEvent,
|
|
20
23
|
DurableObjectAlarm: () => DurableObjectAlarm,
|
|
21
24
|
DurableObjectAlarmEvent: () => DurableObjectAlarmEvent,
|
|
25
|
+
HasDueAlarms: () => HasDueAlarms,
|
|
22
26
|
InvalidAlarmPayloadError: () => InvalidAlarmPayloadError,
|
|
23
27
|
InvalidAlarmRefError: () => InvalidAlarmRefError,
|
|
28
|
+
InvalidAlarmRegistrationError: () => InvalidAlarmRegistrationError,
|
|
24
29
|
InvalidProcessDueAlarmsOptionsError: () => InvalidProcessDueAlarmsOptionsError,
|
|
25
30
|
InvalidRepeatEveryError: () => InvalidRepeatEveryError,
|
|
26
31
|
InvalidScheduleConfigurationError: () => InvalidScheduleConfigurationError,
|
|
27
32
|
MIN_REPEAT_INTERVAL_MS: () => MIN_REPEAT_INTERVAL_MS,
|
|
28
33
|
MIN_RETRY_DELAY_MS: () => MIN_RETRY_DELAY_MS,
|
|
29
34
|
PARKED_RETRY_DELAY_MS: () => PARKED_RETRY_DELAY_MS,
|
|
35
|
+
RunAlarm: () => RunAlarm,
|
|
30
36
|
ScheduleConfiguration: () => ScheduleConfiguration,
|
|
31
37
|
StoredAlarmDecodeError: () => StoredAlarmDecodeError,
|
|
32
38
|
Tag: () => Tag,
|
|
33
39
|
UNCHANGED_ATTEMPT_BUDGET: () => 8,
|
|
34
40
|
define: () => define,
|
|
41
|
+
dispatchRawAlarm: () => dispatchRawAlarm,
|
|
42
|
+
mergeAll: () => mergeAll,
|
|
35
43
|
processDue: () => processDue
|
|
36
44
|
});
|
|
37
45
|
const INIT_TABLE_SQL = `
|
|
@@ -39,6 +47,7 @@ CREATE TABLE IF NOT EXISTS effect_cf_scheduled_alarms (
|
|
|
39
47
|
storage_id TEXT PRIMARY KEY,
|
|
40
48
|
alarm_id TEXT NOT NULL,
|
|
41
49
|
tag TEXT NOT NULL,
|
|
50
|
+
lifecycle TEXT NOT NULL DEFAULT 'automatic',
|
|
42
51
|
run_at INTEGER NOT NULL,
|
|
43
52
|
wake_at INTEGER NOT NULL DEFAULT 0,
|
|
44
53
|
repeat_every_ms INTEGER,
|
|
@@ -54,10 +63,12 @@ CREATE TABLE IF NOT EXISTS effect_cf_alarm_attempts (
|
|
|
54
63
|
);
|
|
55
64
|
`;
|
|
56
65
|
const WAKE_INDEX = "idx_effect_cf_scheduled_alarms_wake_at_storage_id";
|
|
66
|
+
const LIFECYCLE_INDEX = "idx_effect_cf_scheduled_alarms_lifecycle_wake_at_storage_id";
|
|
57
67
|
const DEFAULT_PROCESS_DUE_ALARMS_LIMIT = 100;
|
|
58
68
|
const MIN_RETRY_DELAY_MS = 1e3;
|
|
59
69
|
/** Minimum and default parked recovery interval. */
|
|
60
70
|
const PARKED_RETRY_DELAY_MS = 36e5;
|
|
71
|
+
const DEFAULT_IN_FLIGHT_RECOVERY_MS = 1e3;
|
|
61
72
|
/** Absolute product repeat floor. */
|
|
62
73
|
const MIN_REPEAT_INTERVAL_MS = 1e3;
|
|
63
74
|
/** Default product schedule floor. Never use repeats to poll state. */
|
|
@@ -65,7 +76,10 @@ const DEFAULT_MIN_REPEAT_INTERVAL_MS = 6e4;
|
|
|
65
76
|
/** Optional policy overrides. Provide with Layer.succeed in the Durable Object's application layer. */
|
|
66
77
|
const ScheduleConfiguration = Context.Reference("effect-cf/DurableObjectAlarm/ScheduleConfiguration", { defaultValue: () => ({}) });
|
|
67
78
|
const safeIntegerAtLeast = (minimum) => S.Int.check(S.isGreaterThanOrEqualTo(minimum), S.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
|
|
79
|
+
const AlarmLifecycleSchema = S.Literals(["automatic", "manual"]);
|
|
68
80
|
const ScheduleConfigurationSchema = S.Struct({
|
|
81
|
+
dispatchAfterEvent: S.Boolean,
|
|
82
|
+
inFlightRecovery: safeIntegerAtLeast(1),
|
|
69
83
|
minimumRetryDelay: safeIntegerAtLeast(MIN_RETRY_DELAY_MS),
|
|
70
84
|
unchangedAttemptBudget: safeIntegerAtLeast(1),
|
|
71
85
|
parkedRetryDelay: safeIntegerAtLeast(PARKED_RETRY_DELAY_MS),
|
|
@@ -89,6 +103,9 @@ const AlarmParked = S.TaggedStruct("AlarmParked", {
|
|
|
89
103
|
/** Install with Layer.succeed / Effect.provideService. Reporting cannot undo a committed guard. */
|
|
90
104
|
const AlarmReporter = Context.Reference("effect-cf/DurableObjectAlarm/AlarmReporter", { defaultValue: () => (event) => Effect.logWarning(event) });
|
|
91
105
|
const CurrentAlarmPass = Context.Reference("effect-cf/DurableObjectAlarm/CurrentAlarmPass", { defaultValue: () => void 0 });
|
|
106
|
+
/** One dispatcher's view of a registration pass: its tags, shared manual permits, raw tracking. */
|
|
107
|
+
const CurrentDispatch = Context.Reference("effect-cf/DurableObjectAlarm/CurrentDispatch", { defaultValue: () => void 0 });
|
|
108
|
+
const ManualTags = Symbol("effect-cf/DurableObjectAlarm/ManualTags");
|
|
92
109
|
var InvalidAlarmRefError = class extends Data.TaggedError("InvalidAlarmRefError") {
|
|
93
110
|
get message() {
|
|
94
111
|
return `Invalid Durable Object alarm ref: ${causeMessage(this.cause)}`;
|
|
@@ -114,6 +131,11 @@ var InvalidScheduleConfigurationError = class extends Data.TaggedError("InvalidS
|
|
|
114
131
|
return `Invalid Durable Object alarm configuration: ${causeMessage(this.cause)}`;
|
|
115
132
|
}
|
|
116
133
|
};
|
|
134
|
+
var InvalidAlarmRegistrationError = class extends Data.TaggedError("InvalidAlarmRegistrationError") {
|
|
135
|
+
get message() {
|
|
136
|
+
return `Invalid Durable Object alarm registration: ${causeMessage(this.cause)}`;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
117
139
|
const getScheduleConfiguration = Effect.fnUntraced(function* (defaults) {
|
|
118
140
|
const input = {
|
|
119
141
|
...defaults,
|
|
@@ -121,6 +143,8 @@ const getScheduleConfiguration = Effect.fnUntraced(function* (defaults) {
|
|
|
121
143
|
};
|
|
122
144
|
return yield* Effect.try({
|
|
123
145
|
try: () => S.decodeUnknownSync(ScheduleConfigurationSchema)({
|
|
146
|
+
dispatchAfterEvent: input.dispatchAfterEvent ?? false,
|
|
147
|
+
inFlightRecovery: Math.ceil(Duration.toMillis(input.inFlightRecovery ?? DEFAULT_IN_FLIGHT_RECOVERY_MS)),
|
|
124
148
|
minimumRetryDelay: Math.ceil(Duration.toMillis(input.minimumRetryDelay ?? 1e3)),
|
|
125
149
|
unchangedAttemptBudget: input.unchangedAttemptBudget ?? 8,
|
|
126
150
|
parkedRetryDelay: Math.ceil(Duration.toMillis(input.parkedRetryDelay ?? 36e5)),
|
|
@@ -146,7 +170,14 @@ const AlarmRefSchema = S.Struct({
|
|
|
146
170
|
tag: S.NonEmptyString
|
|
147
171
|
});
|
|
148
172
|
const decodeAlarmRef = (input) => S.decodeUnknownEffect(AlarmRefSchema)(input).pipe(Effect.mapError((cause) => new InvalidAlarmRefError({ cause })));
|
|
173
|
+
/** @internal */
|
|
174
|
+
const RunAlarm = Symbol("effect-cf/DurableObjectAlarm/RunAlarm");
|
|
175
|
+
/** @internal */
|
|
176
|
+
const DispatchAfterEvent = Symbol("effect-cf/DurableObjectAlarm/DispatchAfterEvent");
|
|
177
|
+
/** @internal */
|
|
178
|
+
const HasDueAlarms = Symbol("effect-cf/DurableObjectAlarm/HasDueAlarms");
|
|
149
179
|
const StoredPayloadString = S.fromJsonString(S.Json);
|
|
180
|
+
const StoredTags = S.fromJsonString(S.Array(S.String));
|
|
150
181
|
const decodeStoredPayload = (row) => S.decodeUnknownEffect(StoredPayloadString)(row.payload).pipe(Effect.mapError((cause) => new StoredAlarmDecodeError({
|
|
151
182
|
cause,
|
|
152
183
|
storageId: row.storage_id
|
|
@@ -154,15 +185,19 @@ const decodeStoredPayload = (row) => S.decodeUnknownEffect(StoredPayloadString)(
|
|
|
154
185
|
const encodeStoredPayload = (payload) => S.encodeEffect(StoredPayloadString)(payload).pipe(Effect.mapError((cause) => new InvalidAlarmPayloadError({ cause })));
|
|
155
186
|
const ensureTable = Effect.fnUntraced(function* (state) {
|
|
156
187
|
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 = ?",
|
|
188
|
+
if ((yield* (yield* state.storage.sql.exec("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?", LIFECYCLE_INDEX)).toArray()).length > 0) return;
|
|
158
189
|
const columns = yield* state.storage.sql.exec("SELECT name FROM pragma_table_info('effect_cf_scheduled_alarms')");
|
|
159
|
-
|
|
190
|
+
const names = new Set((yield* columns.toArray()).map((column) => column.name));
|
|
191
|
+
if (!names.has("lifecycle")) yield* state.storage.sql.exec("ALTER TABLE effect_cf_scheduled_alarms ADD COLUMN lifecycle TEXT NOT NULL DEFAULT 'automatic'");
|
|
192
|
+
if (!names.has("wake_at")) yield* state.storage.sql.exec("ALTER TABLE effect_cf_scheduled_alarms ADD COLUMN wake_at INTEGER NOT NULL DEFAULT 0");
|
|
160
193
|
yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms SET
|
|
161
194
|
wake_at = MAX(run_at, COALESCE((SELECT retry_at FROM effect_cf_alarm_attempts s
|
|
162
195
|
WHERE s.storage_id = effect_cf_scheduled_alarms.storage_id), run_at))`);
|
|
163
196
|
yield* state.storage.sql.exec("DROP INDEX IF EXISTS idx_effect_cf_scheduled_alarms_run_at_storage_id");
|
|
164
197
|
yield* state.storage.sql.exec(`CREATE INDEX IF NOT EXISTS ${WAKE_INDEX}
|
|
165
198
|
ON effect_cf_scheduled_alarms (wake_at, storage_id)`);
|
|
199
|
+
yield* state.storage.sql.exec(`CREATE INDEX IF NOT EXISTS ${LIFECYCLE_INDEX}
|
|
200
|
+
ON effect_cf_scheduled_alarms (lifecycle, wake_at, storage_id)`);
|
|
166
201
|
});
|
|
167
202
|
const toRepeatEveryMillis = (input, minimum) => {
|
|
168
203
|
if (input === void 0) return Effect.succeed(null);
|
|
@@ -208,6 +243,71 @@ const processDue = (handle, options = {}) => Effect.gen(function* () {
|
|
|
208
243
|
return yield* (yield* DurableObjectAlarm).processDueAlarms(handle, options);
|
|
209
244
|
});
|
|
210
245
|
const TypedAlarmSchedulerTypeId = Symbol.for("effect-cf/DurableObjectAlarm/TypedScheduler");
|
|
246
|
+
const RegistrationParts = Symbol("effect-cf/DurableObjectAlarm/RegistrationParts");
|
|
247
|
+
const emptyResult = {
|
|
248
|
+
failed: [],
|
|
249
|
+
handled: [],
|
|
250
|
+
parked: []
|
|
251
|
+
};
|
|
252
|
+
const missingAlarm = (tag) => new InvalidAlarmRegistrationError({ cause: /* @__PURE__ */ new Error(`No manual handler registered for alarm tag "${tag}"`) });
|
|
253
|
+
const validateRegistration = (parts) => Effect.try({
|
|
254
|
+
try: () => {
|
|
255
|
+
if (parts.invalid !== void 0) throw new Error(parts.invalid);
|
|
256
|
+
const tags = parts.dispatchers.flatMap((dispatcher) => dispatcher.tags);
|
|
257
|
+
S.decodeUnknownSync(S.Array(S.NonEmptyString))([...parts.services, ...tags]);
|
|
258
|
+
if (new Set(parts.services).size !== parts.services.length || new Set(tags).size !== tags.length) throw new Error("Alarm service keys and definition tags must be unique within a Durable Object");
|
|
259
|
+
},
|
|
260
|
+
catch: (cause) => new InvalidAlarmRegistrationError({ cause })
|
|
261
|
+
});
|
|
262
|
+
const runRegistrations = (parts, rawAlarm) => Effect.gen(function* () {
|
|
263
|
+
yield* validateRegistration(parts);
|
|
264
|
+
const alarms = yield* DurableObjectAlarm;
|
|
265
|
+
const permits = yield* Semaphore.make(4);
|
|
266
|
+
const parentRaw = (yield* CurrentDispatch)?.raw;
|
|
267
|
+
const recoveryScope = {
|
|
268
|
+
tags: parts.dispatchers.flatMap((dispatcher) => dispatcher.tags),
|
|
269
|
+
exclude: true,
|
|
270
|
+
permits,
|
|
271
|
+
raw: parentRaw
|
|
272
|
+
};
|
|
273
|
+
const recovery = rawAlarm === void 0 ? makeDefinition({}).handlers({}).pipe(Effect.provideService(CurrentDispatch, recoveryScope)) : Effect.suspend(() => {
|
|
274
|
+
const raw = { processed: false };
|
|
275
|
+
return rawAlarm.pipe(Effect.onExit(() => raw.processed ? Effect.void : makeDefinition({}).handlers({}).pipe(Effect.asVoid)), Effect.provideService(CurrentDispatch, {
|
|
276
|
+
...recoveryScope,
|
|
277
|
+
raw
|
|
278
|
+
}), Effect.as(emptyResult));
|
|
279
|
+
});
|
|
280
|
+
return yield* alarms.deferWakes(Effect.gen(function* () {
|
|
281
|
+
const run = (dispatchers) => Effect.forEach(dispatchers, (dispatcher) => dispatcher.run.pipe(Effect.catchTag("InvalidProcessDueAlarmsOptionsError", (error) => Effect.fail(error).pipe(Effect.onExit(() => makeDefinition({}).handlers({}).pipe(Effect.asVoid)))), Effect.provideService(CurrentDispatch, {
|
|
282
|
+
tags: dispatcher.tags,
|
|
283
|
+
exclude: false,
|
|
284
|
+
permits,
|
|
285
|
+
raw: parentRaw
|
|
286
|
+
}), Effect.exit), { concurrency: "unbounded" });
|
|
287
|
+
const automatic = parts.dispatchers.filter((dispatcher) => rawAlarm !== void 0 && dispatcher.automatic);
|
|
288
|
+
const independent = parts.dispatchers.filter((dispatcher) => rawAlarm === void 0 || !dispatcher.automatic);
|
|
289
|
+
const exits = (yield* Effect.all([run(independent), run(automatic).pipe(Effect.flatMap((exits) => Effect.map(Effect.exit(recovery), (exit) => [...exits, exit])))], { concurrency: "unbounded" })).flat();
|
|
290
|
+
yield* Exit.asVoidAll(exits);
|
|
291
|
+
return {
|
|
292
|
+
failed: exits.flatMap((exit) => Exit.isSuccess(exit) ? exit.value.failed : []),
|
|
293
|
+
handled: exits.flatMap((exit) => Exit.isSuccess(exit) ? exit.value.handled : []),
|
|
294
|
+
parked: exits.flatMap((exit) => Exit.isSuccess(exit) ? exit.value.parked : [])
|
|
295
|
+
};
|
|
296
|
+
}));
|
|
297
|
+
});
|
|
298
|
+
const makeRegistration = (layer, parts) => ({
|
|
299
|
+
[RegistrationParts]: parts,
|
|
300
|
+
layer: layer.pipe(Layer.provide(Layer.effectDiscard(validateRegistration(parts)))),
|
|
301
|
+
run: runRegistrations(parts)
|
|
302
|
+
});
|
|
303
|
+
function mergeAll(registration, ...others) {
|
|
304
|
+
const parts = [registration, ...others].map((item) => item[RegistrationParts]);
|
|
305
|
+
return makeRegistration(Layer.mergeAll(registration.layer, ...others.map((item) => item.layer)), {
|
|
306
|
+
services: parts.flatMap((part) => part?.services ?? []),
|
|
307
|
+
dispatchers: parts.flatMap((part) => part?.dispatchers ?? []),
|
|
308
|
+
invalid: parts.some((part) => part === void 0) ? "mergeAll requires registrations returned by Tag.handlers" : parts.find((part) => part?.invalid !== void 0)?.invalid
|
|
309
|
+
});
|
|
310
|
+
}
|
|
211
311
|
const isAlarmDefinitionConfig = (definition) => Predicate.isObject(definition) && "payload" in definition;
|
|
212
312
|
const getAlarmDefinitionSchema = (definition) => isAlarmDefinitionConfig(definition) ? definition.payload : definition;
|
|
213
313
|
const getAlarmDefinitionFailureAction = (definition) => {
|
|
@@ -227,7 +327,8 @@ const makeDefinition = (definitions) => {
|
|
|
227
327
|
const payload = yield* S.encodeEffect(getAlarmDefinitionSchema(definition))(input.payload).pipe(Effect.flatMap(S.decodeUnknownEffect(S.Json)), Effect.mapError((cause) => new InvalidAlarmPayloadError({ cause })));
|
|
228
328
|
yield* mutations[method]({
|
|
229
329
|
...input,
|
|
230
|
-
payload
|
|
330
|
+
payload,
|
|
331
|
+
lifecycle: isAlarmDefinitionConfig(definition) ? definition.lifecycle : void 0
|
|
231
332
|
});
|
|
232
333
|
});
|
|
233
334
|
return {
|
|
@@ -243,6 +344,7 @@ const makeDefinition = (definitions) => {
|
|
|
243
344
|
make: (alarms) => ({
|
|
244
345
|
[TypedAlarmSchedulerTypeId]: TypedAlarmSchedulerTypeId,
|
|
245
346
|
...bind(alarms),
|
|
347
|
+
deferWakes: alarms.deferWakes,
|
|
246
348
|
getAlarmStatus: (input) => alarms.getAlarmStatus(input),
|
|
247
349
|
transaction: (closure) => alarms.transaction((tx) => closure(bind(tx)))
|
|
248
350
|
}),
|
|
@@ -264,6 +366,7 @@ const makeDefinition = (definitions) => {
|
|
|
264
366
|
});
|
|
265
367
|
}), {
|
|
266
368
|
...options,
|
|
369
|
+
[ManualTags]: new Set(Object.entries(definitions).filter(([, definition]) => isAlarmDefinitionConfig(definition) && definition.lifecycle === "manual").map(([tag]) => tag)),
|
|
267
370
|
onFailure: (failure) => Effect.gen(function* () {
|
|
268
371
|
const action = getAlarmDefinitionFailureAction(definitionFor(failure.tag));
|
|
269
372
|
const optionAction = options?.onFailure === void 0 ? void 0 : yield* options.onFailure(failure);
|
|
@@ -274,32 +377,70 @@ const makeDefinition = (definitions) => {
|
|
|
274
377
|
};
|
|
275
378
|
/** Handler-only definitions for applications using the raw scheduler. Prefer Tag for typed scheduling. */
|
|
276
379
|
const define = (definitions) => ({ handlers: makeDefinition(definitions).handlers });
|
|
380
|
+
const hasSchedulerTables = Effect.fnUntraced(function* (state) {
|
|
381
|
+
return yield* Effect.gen(function* () {
|
|
382
|
+
if (state.raw.storage.sql === void 0) return false;
|
|
383
|
+
return (yield* (yield* state.storage.sql.exec("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1", "effect_cf_scheduled_alarms")).toArray()).length > 0;
|
|
384
|
+
}).pipe(Effect.catchIf((error) => error.cause instanceof Error && (error.cause.message.startsWith("SQL is not enabled for this Durable Object class") || error.cause.message.startsWith("This Durable Object is not backed by SQLite storage")), () => Effect.succeed(false)));
|
|
385
|
+
});
|
|
386
|
+
/** @internal Preserve custom dispatch while recovering retained alarms after deployments. */
|
|
387
|
+
const dispatchRawAlarm = (rawAlarm, registration) => Effect.gen(function* () {
|
|
388
|
+
const parts = registration?.[RegistrationParts];
|
|
389
|
+
if (parts !== void 0) return yield* runRegistrations(parts, rawAlarm).pipe(Effect.asVoid);
|
|
390
|
+
const custom = registration === void 0 ? rawAlarm : registration.run.pipe(Effect.andThen(rawAlarm));
|
|
391
|
+
const state = yield* DurableObjectState;
|
|
392
|
+
if (!(yield* hasSchedulerTables(state))) return yield* custom;
|
|
393
|
+
return yield* runRegistrations({
|
|
394
|
+
services: [],
|
|
395
|
+
dispatchers: []
|
|
396
|
+
}, custom).pipe(Effect.asVoid);
|
|
397
|
+
});
|
|
277
398
|
/** The typed scheduler becomes available when its handlers are registered on a Durable Object. */
|
|
278
399
|
const Tag = () => (id, definitions) => {
|
|
279
400
|
const tag = Context.Service()(id);
|
|
280
401
|
const definition = makeDefinition(definitions);
|
|
281
|
-
return Object.assign(tag, { handlers: (handlers, options) => ({
|
|
282
|
-
|
|
283
|
-
|
|
402
|
+
return Object.assign(tag, { handlers: (handlers, options) => makeRegistration(Layer.effect(tag, Effect.map(DurableObjectAlarm, definition.make)), {
|
|
403
|
+
services: [id],
|
|
404
|
+
dispatchers: [{
|
|
405
|
+
tags: Object.keys(definitions),
|
|
406
|
+
automatic: Object.values(definitions).some((entry) => !isAlarmDefinitionConfig(entry) || entry.lifecycle !== "manual"),
|
|
407
|
+
run: definition.handlers(handlers, options)
|
|
408
|
+
}]
|
|
284
409
|
}) });
|
|
285
410
|
};
|
|
286
411
|
var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("effect-cf/DurableObjectAlarm") {
|
|
287
412
|
static layer = Layer.effect(DurableObjectAlarm, Effect.gen(function* () {
|
|
288
413
|
const state = yield* DurableObjectState;
|
|
289
414
|
const configurationDefaults = { ...yield* ScheduleConfiguration };
|
|
415
|
+
let wakeDeferrals = 0;
|
|
416
|
+
let wakesReady;
|
|
417
|
+
let pendingAlarm;
|
|
418
|
+
let dispatchActive = false;
|
|
419
|
+
let dispatchNeedsReconcile = false;
|
|
420
|
+
let deferredRecoveryDelay = PARKED_RETRY_DELAY_MS;
|
|
421
|
+
let deferredRecoveryAt = 0;
|
|
290
422
|
const readRow = Effect.fnUntraced(function* (storageId) {
|
|
291
423
|
return (yield* (yield* state.storage.sql.exec(`${alarmRowsSql} WHERE a.storage_id = ?`, storageId)).toArray())[0];
|
|
292
424
|
});
|
|
425
|
+
const writeNativeAlarm = (runAt) => Effect.tryPromise({
|
|
426
|
+
try: () => runAt === null ? state.raw.storage.deleteAlarm() : state.raw.storage.setAlarm(runAt),
|
|
427
|
+
catch: (cause) => new StorageOperationError({
|
|
428
|
+
operation: runAt === null ? "deleteAlarm" : "setAlarm",
|
|
429
|
+
cause
|
|
430
|
+
})
|
|
431
|
+
});
|
|
293
432
|
const reconcileAlarm = Effect.fn("DurableObjectAlarm.reconcileAlarm")(function* () {
|
|
433
|
+
if (wakeDeferrals > 0 || dispatchActive) {
|
|
434
|
+
if (dispatchActive) dispatchNeedsReconcile = true;
|
|
435
|
+
const current = yield* state.storage.getAlarm();
|
|
436
|
+
const now = yield* Clock.currentTimeMillis;
|
|
437
|
+
if (deferredRecoveryAt <= now) deferredRecoveryAt = now + deferredRecoveryDelay;
|
|
438
|
+
if (current === null || current <= now || current > deferredRecoveryAt) yield* writeNativeAlarm(deferredRecoveryAt);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
294
441
|
const next = (yield* (yield* state.storage.sql.exec(`SELECT wake_at AS run_at FROM effect_cf_scheduled_alarms
|
|
295
|
-
|
|
296
|
-
yield*
|
|
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
|
-
});
|
|
442
|
+
ORDER BY wake_at, storage_id LIMIT 1`)).toArray())[0];
|
|
443
|
+
yield* writeNativeAlarm(next?.run_at ?? null);
|
|
303
444
|
});
|
|
304
445
|
const writeAttempts = Effect.fnUntraced(function* (storageId, attempts, parked, retryAt, progress) {
|
|
305
446
|
yield* state.storage.sql.exec(`INSERT OR REPLACE INTO effect_cf_alarm_attempts
|
|
@@ -318,17 +459,15 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
318
459
|
const scheduleAlarm = Effect.fn("DurableObjectAlarm.scheduleAlarm")(function* (input, earlier, reports) {
|
|
319
460
|
const configuration = yield* getScheduleConfiguration(configurationDefaults);
|
|
320
461
|
const ref = yield* decodeAlarmRef(input);
|
|
462
|
+
const lifecycle = yield* S.decodeUnknownEffect(AlarmLifecycleSchema)(input.lifecycle ?? "automatic").pipe(Effect.mapError((cause) => new InvalidAlarmRefError({ cause })));
|
|
463
|
+
if (lifecycle === "manual" && (input.repeatEvery !== void 0 || input.progress !== void 0)) return yield* Effect.fail(new InvalidAlarmRefError({ cause: /* @__PURE__ */ new Error("Manual alarms own their repeats and progress budgets") }));
|
|
321
464
|
const repeatEveryMillis = yield* toRepeatEveryMillis(input.repeatEvery, configuration.minimumRepeatInterval);
|
|
322
465
|
const payload = yield* encodeStoredPayload(input.payload);
|
|
323
466
|
const storageId = getScheduledEventId(ref);
|
|
324
467
|
const existing = yield* readRow(storageId);
|
|
325
468
|
const pass = yield* CurrentAlarmPass;
|
|
326
|
-
const source = pass?.row;
|
|
469
|
+
const source = lifecycle === "automatic" && pass?.row.lifecycle === "automatic" ? pass.row : void 0;
|
|
327
470
|
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
471
|
if (input.progress !== void 0) yield* S.decodeUnknownEffect(S.Natural)(input.progress).pipe(Effect.mapError((cause) => new InvalidAlarmRefError({ cause })));
|
|
333
472
|
if (source === void 0 && input.progress !== void 0 && input.progress <= progress) return;
|
|
334
473
|
const progressed = input.progress !== void 0 && input.progress > progress;
|
|
@@ -341,11 +480,11 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
341
480
|
const keepEarlier = earlier && existing !== void 0 && existing.run_at <= runAt;
|
|
342
481
|
const scheduledAt = keepEarlier ? existing.run_at : runAt;
|
|
343
482
|
const scheduledPayload = keepEarlier ? existing.payload : payload;
|
|
344
|
-
const scheduledRepeat = keepEarlier ? existing.repeat_every_ms : repeatEveryMillis;
|
|
345
|
-
const nextProgress = Math.max(progress, input.progress ?? -1);
|
|
483
|
+
const scheduledRepeat = lifecycle === "manual" ? null : keepEarlier ? existing.repeat_every_ms : repeatEveryMillis;
|
|
484
|
+
const nextProgress = lifecycle === "manual" ? -1 : Math.max(progress, input.progress ?? -1);
|
|
346
485
|
yield* state.storage.sql.exec(`INSERT OR REPLACE INTO effect_cf_scheduled_alarms
|
|
347
|
-
(storage_id, alarm_id, tag, run_at, repeat_every_ms, payload)
|
|
348
|
-
VALUES (?, ?, ?, ?, ?, ?)`, storageId, ref.id, ref.tag, scheduledAt, scheduledRepeat, scheduledPayload);
|
|
486
|
+
(storage_id, alarm_id, tag, lifecycle, run_at, repeat_every_ms, payload)
|
|
487
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, storageId, ref.id, ref.tag, lifecycle, scheduledAt, scheduledRepeat, scheduledPayload);
|
|
349
488
|
yield* writeAttempts(storageId, attempts, parked, retryAt, nextProgress);
|
|
350
489
|
if (parked === 1 && retryAt !== null && (existing?.parked ?? source?.parked ?? 0) === 0) reports.push(storageId);
|
|
351
490
|
});
|
|
@@ -365,10 +504,15 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
365
504
|
}
|
|
366
505
|
});
|
|
367
506
|
const transaction = Effect.fnUntraced(function* (closure) {
|
|
507
|
+
const pass = yield* CurrentAlarmPass;
|
|
508
|
+
if (pass !== void 0 && !pass.active) return yield* Effect.fail(new StorageOperationError({
|
|
509
|
+
operation: "alarm.transaction",
|
|
510
|
+
cause: /* @__PURE__ */ new Error("Alarm handlers cannot mutate detached work after their pass ends")
|
|
511
|
+
}));
|
|
368
512
|
const reports = [];
|
|
369
513
|
const result = yield* state.storage.transaction(() => Effect.withFiber((owner) => {
|
|
370
514
|
let active = true;
|
|
371
|
-
const requireActive = (effect) => Effect.withFiber((fiber) => active && fiber === owner ? effect : Effect.fail(new StorageOperationError({
|
|
515
|
+
const requireActive = (effect) => Effect.withFiber((fiber) => active && fiber === owner && (pass === void 0 || pass.active) ? effect : Effect.fail(new StorageOperationError({
|
|
372
516
|
operation: "alarm.transaction",
|
|
373
517
|
cause: /* @__PURE__ */ new Error("Alarm mutations require their active transaction callback")
|
|
374
518
|
})));
|
|
@@ -388,6 +532,60 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
388
532
|
yield* reportParked(reports);
|
|
389
533
|
return result;
|
|
390
534
|
});
|
|
535
|
+
const deferWakes = (body) => Effect.acquireUseRelease(Effect.gen(function* () {
|
|
536
|
+
const configuration = yield* getScheduleConfiguration(configurationDefaults);
|
|
537
|
+
const now = yield* Clock.currentTimeMillis;
|
|
538
|
+
if (wakeDeferrals === 0) {
|
|
539
|
+
deferredRecoveryDelay = configuration.inFlightRecovery;
|
|
540
|
+
deferredRecoveryAt = now + deferredRecoveryDelay;
|
|
541
|
+
wakesReady ??= Deferred.makeUnsafe();
|
|
542
|
+
}
|
|
543
|
+
wakeDeferrals++;
|
|
544
|
+
}), () => transaction(() => Effect.void).pipe(Effect.andThen(body)), () => Effect.gen(function* () {
|
|
545
|
+
wakeDeferrals--;
|
|
546
|
+
if (wakeDeferrals === 0) yield* (dispatchActive ? Effect.void : transaction(() => Effect.void)).pipe(Effect.ensuring(Effect.sync(() => {
|
|
547
|
+
if (wakeDeferrals === 0 && wakesReady !== void 0) {
|
|
548
|
+
Deferred.doneUnsafe(wakesReady, Effect.void);
|
|
549
|
+
wakesReady = void 0;
|
|
550
|
+
}
|
|
551
|
+
})));
|
|
552
|
+
}));
|
|
553
|
+
const awaitWakes = Effect.suspend(() => wakesReady === void 0 ? Effect.void : Deferred.await(wakesReady).pipe(Effect.andThen(awaitWakes)));
|
|
554
|
+
const runAlarm = (event, native) => Effect.flatMap(hasSchedulerTables(state), (managed) => !managed ? native ? event(Effect.void) : Effect.void : Effect.uninterruptibleMask((restore) => Effect.suspend(() => {
|
|
555
|
+
const pending = pendingAlarm;
|
|
556
|
+
if (pending !== void 0) return restore((native ? transaction(() => Effect.void) : Effect.void).pipe(Effect.andThen(Deferred.await(pending))));
|
|
557
|
+
const completed = Deferred.makeUnsafe();
|
|
558
|
+
let dispatchStarted = false;
|
|
559
|
+
pendingAlarm = completed;
|
|
560
|
+
dispatchNeedsReconcile = native;
|
|
561
|
+
return restore(Effect.gen(function* () {
|
|
562
|
+
if (native && wakesReady !== void 0) yield* transaction(() => Effect.void);
|
|
563
|
+
yield* awaitWakes;
|
|
564
|
+
dispatchActive = true;
|
|
565
|
+
yield* event(Effect.gen(function* () {
|
|
566
|
+
yield* getScheduleConfiguration(configurationDefaults);
|
|
567
|
+
dispatchStarted = true;
|
|
568
|
+
}));
|
|
569
|
+
})).pipe(Effect.onExit((exit) => Effect.gen(function* () {
|
|
570
|
+
const reconcile = dispatchNeedsReconcile || dispatchStarted || Exit.isFailure(exit);
|
|
571
|
+
dispatchActive = Exit.isFailure(exit) && !dispatchStarted;
|
|
572
|
+
if (dispatchActive) {
|
|
573
|
+
const recoveryDelay = yield* getScheduleConfiguration(configurationDefaults).pipe(Effect.match({
|
|
574
|
+
onFailure: () => PARKED_RETRY_DELAY_MS,
|
|
575
|
+
onSuccess: (configuration) => configuration.parkedRetryDelay
|
|
576
|
+
}));
|
|
577
|
+
if (wakeDeferrals === 0) {
|
|
578
|
+
deferredRecoveryDelay = recoveryDelay;
|
|
579
|
+
deferredRecoveryAt = 0;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const reconciled = yield* (reconcile ? transaction(() => Effect.void) : Effect.void).pipe(Effect.exit);
|
|
583
|
+
dispatchActive = false;
|
|
584
|
+
pendingAlarm = void 0;
|
|
585
|
+
yield* Deferred.done(completed, Exit.asVoidAll([exit, reconciled]));
|
|
586
|
+
yield* reconciled;
|
|
587
|
+
})));
|
|
588
|
+
})));
|
|
391
589
|
const rescheduleFailedAlarm = Effect.fnUntraced(function* (row, initialDelay, configuration, reports) {
|
|
392
590
|
const current = yield* readRow(row.storage_id);
|
|
393
591
|
if (current === void 0 || current.revision !== row.revision) return;
|
|
@@ -411,8 +609,22 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
411
609
|
const limit = yield* getProcessLimit(options);
|
|
412
610
|
const initialDelay = yield* getFailureRetryDelay(options, configuration);
|
|
413
611
|
const now = yield* Clock.currentTimeMillis;
|
|
414
|
-
const
|
|
415
|
-
|
|
612
|
+
const dispatch = yield* CurrentDispatch;
|
|
613
|
+
if (dispatch?.raw !== void 0) dispatch.raw.processed = true;
|
|
614
|
+
const tagFilter = dispatch === void 0 ? "" : ` AND a.tag ${dispatch.exclude ? "NOT IN" : "IN"} (SELECT value FROM json_each(?))`;
|
|
615
|
+
const tagBindings = dispatch === void 0 ? [] : [S.encodeSync(StoredTags)([...dispatch.tags])];
|
|
616
|
+
const select = Effect.fnUntraced(function* (lifecycle, batchLimit) {
|
|
617
|
+
return yield* (yield* state.storage.sql.exec(`${alarmRowsSql} WHERE a.lifecycle = ? AND a.wake_at <= ?${tagFilter}
|
|
618
|
+
ORDER BY a.wake_at ASC, a.storage_id ASC LIMIT ?`, lifecycle, now, ...tagBindings, batchLimit)).toArray();
|
|
619
|
+
});
|
|
620
|
+
const dueRows = yield* select("automatic", limit);
|
|
621
|
+
const manualRows = yield* select("manual", DEFAULT_PROCESS_DUE_ALARMS_LIMIT);
|
|
622
|
+
if (manualRows.length > 0) yield* transaction(() => Effect.gen(function* () {
|
|
623
|
+
for (const row of manualRows) {
|
|
624
|
+
const recoveryAt = now + (options?.[ManualTags]?.has(row.tag) ? configuration.inFlightRecovery : configuration.parkedRetryDelay);
|
|
625
|
+
yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms SET run_at = ?, wake_at = ? WHERE ${sameRevisionSql}`, recoveryAt, recoveryAt, row.storage_id, row.storage_id, row.revision);
|
|
626
|
+
}
|
|
627
|
+
}));
|
|
416
628
|
const handled = [];
|
|
417
629
|
const failed = [];
|
|
418
630
|
const parked = [];
|
|
@@ -437,9 +649,9 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
437
649
|
if (Exit.isFailure(actionExit)) return yield* Effect.failCause(actionExit.cause);
|
|
438
650
|
if (Exit.isFailure(delayExit)) return yield* Effect.failCause(delayExit.cause);
|
|
439
651
|
});
|
|
440
|
-
|
|
652
|
+
const handleRow = Effect.fnUntraced(function* (row) {
|
|
441
653
|
const current = yield* readRow(row.storage_id);
|
|
442
|
-
if (current === void 0 || current.revision !== row.revision)
|
|
654
|
+
if (current === void 0 || current.revision !== row.revision) return;
|
|
443
655
|
const inPass = (effect) => {
|
|
444
656
|
const pass = {
|
|
445
657
|
row,
|
|
@@ -450,21 +662,30 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
450
662
|
pass.active = false;
|
|
451
663
|
})));
|
|
452
664
|
};
|
|
665
|
+
const manual = row.lifecycle === "manual";
|
|
666
|
+
if (manual && !options?.[ManualTags]?.has(row.tag)) return yield* Effect.fail(missingAlarm(row.tag));
|
|
453
667
|
const eventExit = yield* Effect.exit(toAlarmDue(row));
|
|
454
668
|
if (Exit.isFailure(eventExit)) {
|
|
455
|
-
|
|
456
|
-
|
|
669
|
+
if (manual) return yield* Effect.failCause(eventExit.cause);
|
|
670
|
+
return yield* inPass(handleFailure(row, void 0, eventExit.cause));
|
|
457
671
|
}
|
|
458
672
|
const event = eventExit.value;
|
|
459
673
|
const handleExit = yield* Effect.exit(inPass(Effect.suspend(() => handle(event))));
|
|
460
674
|
if (Exit.isFailure(handleExit)) {
|
|
461
|
-
yield*
|
|
462
|
-
|
|
675
|
+
if (manual) return yield* Effect.failCause(handleExit.cause);
|
|
676
|
+
return yield* inPass(handleFailure(row, event, handleExit.cause));
|
|
463
677
|
}
|
|
464
|
-
yield* transaction(() => acknowledgeAlarm(row, configuration));
|
|
678
|
+
if (!manual) yield* transaction(() => acknowledgeAlarm(row, configuration));
|
|
465
679
|
handled.push(event);
|
|
466
|
-
}
|
|
467
|
-
yield*
|
|
680
|
+
});
|
|
681
|
+
const permits = dispatch?.permits ?? (yield* Semaphore.make(4));
|
|
682
|
+
const automatic = Effect.forEach(dueRows, handleRow, { discard: true }).pipe(Effect.exit);
|
|
683
|
+
const exits = yield* manualRows.length === 0 ? automatic.pipe(Effect.map((exit) => [exit])) : Effect.all([automatic, Effect.forEach(manualRows, (row) => permits.withPermits(1)(handleRow(row)).pipe(Effect.exit), { concurrency: 4 }).pipe(Effect.flatMap(Exit.asVoidAll), Effect.exit)], { concurrency: 2 });
|
|
684
|
+
yield* transaction(() => Effect.gen(function* () {
|
|
685
|
+
const recoveryAt = (yield* Clock.currentTimeMillis) + configuration.parkedRetryDelay;
|
|
686
|
+
for (const row of manualRows) yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms SET run_at = ?, wake_at = ? WHERE ${sameRevisionSql}`, recoveryAt, recoveryAt, row.storage_id, row.storage_id, row.revision);
|
|
687
|
+
}));
|
|
688
|
+
yield* Exit.asVoidAll(exits);
|
|
468
689
|
return {
|
|
469
690
|
failed,
|
|
470
691
|
handled,
|
|
@@ -472,6 +693,14 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
472
693
|
};
|
|
473
694
|
});
|
|
474
695
|
return DurableObjectAlarm.of({
|
|
696
|
+
[RunAlarm]: runAlarm,
|
|
697
|
+
[DispatchAfterEvent]: Effect.map(ScheduleConfiguration, (configuration) => (configuration.dispatchAfterEvent ?? configurationDefaults.dispatchAfterEvent) === true),
|
|
698
|
+
[HasDueAlarms]: Effect.gen(function* () {
|
|
699
|
+
yield* ensureTable(state);
|
|
700
|
+
const now = yield* Clock.currentTimeMillis;
|
|
701
|
+
return (yield* (yield* state.storage.sql.exec("SELECT 1 FROM effect_cf_scheduled_alarms WHERE wake_at <= ? LIMIT 1", now)).toArray()).length > 0;
|
|
702
|
+
}),
|
|
703
|
+
deferWakes,
|
|
475
704
|
cancelAlarm: (input) => transaction((alarms) => alarms.cancelAlarm(input)),
|
|
476
705
|
getAlarmStatus: Effect.fnUntraced(function* (input) {
|
|
477
706
|
const ref = yield* decodeAlarmRef(input);
|
|
@@ -492,4 +721,4 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
|
|
|
492
721
|
}));
|
|
493
722
|
};
|
|
494
723
|
//#endregion
|
|
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 };
|
|
724
|
+
export { AlarmParked, AlarmReporter, DEFAULT_MIN_REPEAT_INTERVAL_MS, DispatchAfterEvent, DurableObjectAlarm, DurableObjectAlarmEvent, DurableObjectAlarm_exports, HasDueAlarms, InvalidAlarmPayloadError, InvalidAlarmRefError, InvalidAlarmRegistrationError, InvalidProcessDueAlarmsOptionsError, InvalidRepeatEveryError, InvalidScheduleConfigurationError, MIN_REPEAT_INTERVAL_MS, MIN_RETRY_DELAY_MS, PARKED_RETRY_DELAY_MS, RunAlarm, ScheduleConfiguration, StoredAlarmDecodeError, Tag, define, dispatchRawAlarm, mergeAll, processDue };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { WorkerEnvironment } from "./Environment.mjs";
|
|
2
|
+
import { BindingNotFoundError, BindingValidationError } from "./Binding.mjs";
|
|
3
|
+
import * as Context$1 from "effect/Context";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { Layer } from "effect";
|
|
6
|
+
declare namespace RateLimit_d_exports {
|
|
7
|
+
export { BindingNotFoundError, BindingValidationError, LayerOptions, RateLimitBinding, RateLimitClient, RateLimitDefinition, RateLimitOperationError, RateLimitOptions, RateLimitOutcome, RateLimitService, Tag, TagClass, isRateLimit, layer, make, makeClient };
|
|
8
|
+
}
|
|
9
|
+
type RateLimitBinding = globalThis.RateLimit;
|
|
10
|
+
type RateLimitOptions = globalThis.RateLimitOptions;
|
|
11
|
+
type RateLimitOutcome = globalThis.RateLimitOutcome;
|
|
12
|
+
declare const RateLimitOperationError_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 & {
|
|
13
|
+
readonly _tag: "RateLimitOperationError";
|
|
14
|
+
} & Readonly<A>;
|
|
15
|
+
declare class RateLimitOperationError extends RateLimitOperationError_base<{
|
|
16
|
+
readonly binding: string;
|
|
17
|
+
readonly operation: "limit";
|
|
18
|
+
readonly cause: unknown;
|
|
19
|
+
}> {
|
|
20
|
+
get message(): string;
|
|
21
|
+
}
|
|
22
|
+
interface RateLimitDefinition {
|
|
23
|
+
readonly binding: string;
|
|
24
|
+
}
|
|
25
|
+
interface RateLimitClient {
|
|
26
|
+
/** Denial is a successful result with success: false. Calls are never retried. */
|
|
27
|
+
readonly limit: (options: RateLimitOptions) => Effect.Effect<RateLimitOutcome, RateLimitOperationError>;
|
|
28
|
+
readonly rawUnsafe: Effect.Effect<RateLimitBinding>;
|
|
29
|
+
readonly definition: RateLimitDefinition;
|
|
30
|
+
}
|
|
31
|
+
declare const RateLimitServiceTypeId: unique symbol;
|
|
32
|
+
interface RateLimitService<Id extends string> {
|
|
33
|
+
readonly [RateLimitServiceTypeId]: {
|
|
34
|
+
readonly id: Id;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
type LayerOptions = {
|
|
38
|
+
readonly binding: string;
|
|
39
|
+
};
|
|
40
|
+
interface TagClass<Self, Id extends string> extends Context$1.ServiceClass<Self, Id, RateLimitClient> {
|
|
41
|
+
readonly id: Id;
|
|
42
|
+
readonly layer: (options: LayerOptions) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
43
|
+
}
|
|
44
|
+
declare const isRateLimit: <Candidate>(value: Candidate) => value is Candidate & RateLimitBinding;
|
|
45
|
+
declare const makeClient: (definition: RateLimitDefinition) => (rateLimit: RateLimitBinding) => RateLimitClient;
|
|
46
|
+
declare const layer: <Self>(tag: Context$1.Service<Self, RateLimitClient>, definition: RateLimitDefinition) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
47
|
+
declare const make: <Id extends string>(id: Id) => TagClass<RateLimitService<Id>, Id>;
|
|
48
|
+
declare const Tag: <Self>() => <Id extends string>(id: Id) => TagClass<Self, Id>;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { type BindingNotFoundError, type BindingValidationError, LayerOptions, RateLimitBinding, RateLimitClient, RateLimitDefinition, RateLimitOperationError, RateLimitOptions, RateLimitOutcome, RateLimitService, RateLimit_d_exports, Tag, TagClass, isRateLimit, layer, make, makeClient };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { layer as layer$1 } from "./Binding.mjs";
|
|
3
|
+
import * as Context from "effect/Context";
|
|
4
|
+
import * as Data from "effect/Data";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Predicate from "effect/Predicate";
|
|
7
|
+
//#region src/RateLimit.ts
|
|
8
|
+
var RateLimit_exports = /* @__PURE__ */ __exportAll({
|
|
9
|
+
RateLimitOperationError: () => RateLimitOperationError,
|
|
10
|
+
Tag: () => Tag,
|
|
11
|
+
isRateLimit: () => isRateLimit,
|
|
12
|
+
layer: () => layer,
|
|
13
|
+
make: () => make,
|
|
14
|
+
makeClient: () => makeClient
|
|
15
|
+
});
|
|
16
|
+
const expectedRateLimit = "Rate Limiting binding with limit()";
|
|
17
|
+
var RateLimitOperationError = class extends Data.TaggedError("RateLimitOperationError") {
|
|
18
|
+
get message() {
|
|
19
|
+
return `Cloudflare rate limit binding "${this.binding}" operation "limit" failed`;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const isRateLimit = (value) => Predicate.hasProperty(value, "limit") && Predicate.isFunction(value.limit);
|
|
23
|
+
const makeClient = (definition) => (rateLimit) => ({
|
|
24
|
+
definition,
|
|
25
|
+
limit: (options) => Effect.tryPromise({
|
|
26
|
+
try: () => rateLimit.limit(options),
|
|
27
|
+
catch: (cause) => new RateLimitOperationError({
|
|
28
|
+
binding: definition.binding,
|
|
29
|
+
operation: "limit",
|
|
30
|
+
cause
|
|
31
|
+
})
|
|
32
|
+
}),
|
|
33
|
+
rawUnsafe: Effect.succeed(rateLimit)
|
|
34
|
+
});
|
|
35
|
+
const layer = (tag, definition) => layer$1(tag, definition.binding, isRateLimit, makeClient(definition), { expected: expectedRateLimit });
|
|
36
|
+
const make = (id) => Tag()(id);
|
|
37
|
+
const Tag = () => (id) => {
|
|
38
|
+
const tag = Context.Service()(id);
|
|
39
|
+
const makeLayer = (definition) => layer(tag, definition);
|
|
40
|
+
return Object.assign(tag, {
|
|
41
|
+
id,
|
|
42
|
+
layer: makeLayer
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
export { RateLimitOperationError, RateLimit_exports, Tag, isRateLimit, layer, make, makeClient };
|
package/dist/index.d.mts
CHANGED
|
@@ -36,10 +36,11 @@ import { DurableObjectSqlite_d_exports } from "./DurableObjectSqlite.mjs";
|
|
|
36
36
|
import { Email_d_exports } from "./Email.mjs";
|
|
37
37
|
import { Images_d_exports } from "./Images.mjs";
|
|
38
38
|
import { Kv_d_exports } from "./Kv.mjs";
|
|
39
|
+
import { RateLimit_d_exports } from "./RateLimit.mjs";
|
|
39
40
|
import { R2_d_exports } from "./R2.mjs";
|
|
40
41
|
import { RpcTargets_d_exports } from "./RpcTargets.mjs";
|
|
41
42
|
import { Vectorize_d_exports } from "./Vectorize.mjs";
|
|
42
43
|
import { WebTransport_d_exports } from "./WebTransport.mjs";
|
|
43
44
|
import { WorkflowDefinition_d_exports } from "./WorkflowDefinition.mjs";
|
|
44
45
|
import { Workflow_d_exports } from "./Workflow.mjs";
|
|
45
|
-
export { AiGateway_d_exports as AiGateway, AnalyticsEngine_d_exports as AnalyticsEngine, Artifacts_d_exports as Artifacts, Binding_d_exports as Binding, BrowserRendering_d_exports as BrowserRendering, Cache_d_exports as Cache, CloudflareOtlp_d_exports as CloudflareOtlp, CloudflareTracer_d_exports as CloudflareTracer, ContainerFiles_d_exports as ContainerFiles, ContainerNamespace_d_exports as ContainerNamespace, D1_d_exports as D1, DurableObject_d_exports as DurableObject, DurableObjectAlarm_d_exports as DurableObjectAlarm, DurableObjectContainer_d_exports as DurableObjectContainer, DurableObjectDefinition_d_exports as DurableObjectDefinition, DurableObjectNamespace_d_exports as DurableObjectNamespace, DurableObjectRpcWebSocket_d_exports as DurableObjectRpcWebSocket, DurableObjectSqlite_d_exports as DurableObjectSqlite, DurableObjectState_d_exports as DurableObjectState, DurableObjectStorage_d_exports as DurableObjectStorage, DurableObjectWebSocket_d_exports as DurableObjectWebSocket, Email_d_exports as Email, Environment_d_exports as Environment, Hyperdrive_d_exports as Hyperdrive, Images_d_exports as Images, Kv_d_exports as Kv, Queue_d_exports as Queue, QueueBinding_d_exports as QueueBinding, QueueDefinition_d_exports as QueueDefinition, R2_d_exports as R2, Rpc_d_exports as Rpc, RpcDefinition_d_exports as RpcDefinition, RpcTargets_d_exports as RpcTargets, RpcTracing_d_exports as RpcTracing, RpcWebSocketClient_d_exports as RpcWebSocketClient, ServiceBinding_d_exports as ServiceBinding, Vectorize_d_exports as Vectorize, WebTransport_d_exports as WebTransport, Worker_d_exports as Worker, WorkerConfig, WorkerDefinition_d_exports as WorkerDefinition, type WorkerEnv, WorkerEnvironment, WorkersAi_d_exports as WorkersAi, Workflow_d_exports as Workflow, WorkflowBinding_d_exports as WorkflowBinding, WorkflowDefinition_d_exports as WorkflowDefinition };
|
|
46
|
+
export { AiGateway_d_exports as AiGateway, AnalyticsEngine_d_exports as AnalyticsEngine, Artifacts_d_exports as Artifacts, Binding_d_exports as Binding, BrowserRendering_d_exports as BrowserRendering, Cache_d_exports as Cache, CloudflareOtlp_d_exports as CloudflareOtlp, CloudflareTracer_d_exports as CloudflareTracer, ContainerFiles_d_exports as ContainerFiles, ContainerNamespace_d_exports as ContainerNamespace, D1_d_exports as D1, DurableObject_d_exports as DurableObject, DurableObjectAlarm_d_exports as DurableObjectAlarm, DurableObjectContainer_d_exports as DurableObjectContainer, DurableObjectDefinition_d_exports as DurableObjectDefinition, DurableObjectNamespace_d_exports as DurableObjectNamespace, DurableObjectRpcWebSocket_d_exports as DurableObjectRpcWebSocket, DurableObjectSqlite_d_exports as DurableObjectSqlite, DurableObjectState_d_exports as DurableObjectState, DurableObjectStorage_d_exports as DurableObjectStorage, DurableObjectWebSocket_d_exports as DurableObjectWebSocket, Email_d_exports as Email, Environment_d_exports as Environment, Hyperdrive_d_exports as Hyperdrive, Images_d_exports as Images, Kv_d_exports as Kv, Queue_d_exports as Queue, QueueBinding_d_exports as QueueBinding, QueueDefinition_d_exports as QueueDefinition, R2_d_exports as R2, RateLimit_d_exports as RateLimit, Rpc_d_exports as Rpc, RpcDefinition_d_exports as RpcDefinition, RpcTargets_d_exports as RpcTargets, RpcTracing_d_exports as RpcTracing, RpcWebSocketClient_d_exports as RpcWebSocketClient, ServiceBinding_d_exports as ServiceBinding, Vectorize_d_exports as Vectorize, WebTransport_d_exports as WebTransport, Worker_d_exports as Worker, WorkerConfig, WorkerDefinition_d_exports as WorkerDefinition, type WorkerEnv, WorkerEnvironment, WorkersAi_d_exports as WorkersAi, Workflow_d_exports as Workflow, WorkflowBinding_d_exports as WorkflowBinding, WorkflowDefinition_d_exports as WorkflowDefinition };
|