effect-cf 0.52.0 → 0.54.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +7 -1
  3. package/dist/AnalyticsEngine.d.mts +2 -2
  4. package/dist/AnalyticsEngine.mjs +3 -3
  5. package/dist/CloudflareOtlp.d.mts +3 -3
  6. package/dist/CloudflareOtlp.mjs +7 -7
  7. package/dist/D1.d.mts +1 -1
  8. package/dist/DurableObject.d.mts +1 -0
  9. package/dist/DurableObject.mjs +33 -11
  10. package/dist/DurableObjectAlarm.d.mts +74 -9
  11. package/dist/DurableObjectAlarm.mjs +245 -35
  12. package/dist/DurableObjectRpcWebSocket.d.mts +3 -3
  13. package/dist/DurableObjectRpcWebSocket.mjs +3 -3
  14. package/dist/DurableObjectSqlite.d.mts +1 -1
  15. package/dist/DurableObjectSqlite.mjs +2 -2
  16. package/dist/HyperdrivePg.d.mts +1 -1
  17. package/dist/Mcp.d.mts +3 -3
  18. package/dist/Mcp.mjs +3 -3
  19. package/dist/RateLimit.d.mts +50 -0
  20. package/dist/RateLimit.mjs +46 -0
  21. package/dist/RpcWebSocketClient.d.mts +6 -6
  22. package/dist/RpcWebSocketClient.mjs +3 -3
  23. package/dist/Worker.d.mts +2 -2
  24. package/dist/Worker.mjs +4 -4
  25. package/dist/index.d.mts +2 -1
  26. package/dist/index.mjs +2 -1
  27. package/dist/internal/Runtime.mjs +5 -3
  28. package/dist/internal/Telemetry.mjs +1 -1
  29. package/package.json +5 -5
  30. package/src/AnalyticsEngine.ts +4 -4
  31. package/src/CloudflareOtlp.ts +8 -8
  32. package/src/D1.ts +1 -1
  33. package/src/DurableObject.ts +93 -30
  34. package/src/DurableObjectAlarm.ts +601 -59
  35. package/src/DurableObjectRpcWebSocket.ts +3 -3
  36. package/src/DurableObjectSqlite.ts +2 -2
  37. package/src/HyperdrivePg.ts +1 -1
  38. package/src/Mcp.ts +4 -4
  39. package/src/RateLimit.ts +110 -0
  40. package/src/RpcWebSocketClient.ts +6 -6
  41. package/src/Worker.ts +4 -4
  42. package/src/index.ts +1 -0
  43. package/src/internal/Entrypoint.ts +8 -4
  44. package/src/internal/Runtime.ts +12 -7
  45. package/src/internal/Telemetry.ts +1 -1
@@ -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,6 +63,7 @@ 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. */
@@ -65,7 +75,9 @@ const DEFAULT_MIN_REPEAT_INTERVAL_MS = 6e4;
65
75
  /** Optional policy overrides. Provide with Layer.succeed in the Durable Object's application layer. */
66
76
  const ScheduleConfiguration = Context.Reference("effect-cf/DurableObjectAlarm/ScheduleConfiguration", { defaultValue: () => ({}) });
67
77
  const safeIntegerAtLeast = (minimum) => S.Int.check(S.isGreaterThanOrEqualTo(minimum), S.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
78
+ const AlarmLifecycleSchema = S.Literals(["automatic", "manual"]);
68
79
  const ScheduleConfigurationSchema = S.Struct({
80
+ dispatchAfterEvent: S.Boolean,
69
81
  minimumRetryDelay: safeIntegerAtLeast(MIN_RETRY_DELAY_MS),
70
82
  unchangedAttemptBudget: safeIntegerAtLeast(1),
71
83
  parkedRetryDelay: safeIntegerAtLeast(PARKED_RETRY_DELAY_MS),
@@ -89,6 +101,9 @@ const AlarmParked = S.TaggedStruct("AlarmParked", {
89
101
  /** Install with Layer.succeed / Effect.provideService. Reporting cannot undo a committed guard. */
90
102
  const AlarmReporter = Context.Reference("effect-cf/DurableObjectAlarm/AlarmReporter", { defaultValue: () => (event) => Effect.logWarning(event) });
91
103
  const CurrentAlarmPass = Context.Reference("effect-cf/DurableObjectAlarm/CurrentAlarmPass", { defaultValue: () => void 0 });
104
+ /** One dispatcher's view of a registration pass: its tags, shared manual permits, raw tracking. */
105
+ const CurrentDispatch = Context.Reference("effect-cf/DurableObjectAlarm/CurrentDispatch", { defaultValue: () => void 0 });
106
+ const ManualTags = Symbol("effect-cf/DurableObjectAlarm/ManualTags");
92
107
  var InvalidAlarmRefError = class extends Data.TaggedError("InvalidAlarmRefError") {
93
108
  get message() {
94
109
  return `Invalid Durable Object alarm ref: ${causeMessage(this.cause)}`;
@@ -114,6 +129,11 @@ var InvalidScheduleConfigurationError = class extends Data.TaggedError("InvalidS
114
129
  return `Invalid Durable Object alarm configuration: ${causeMessage(this.cause)}`;
115
130
  }
116
131
  };
132
+ var InvalidAlarmRegistrationError = class extends Data.TaggedError("InvalidAlarmRegistrationError") {
133
+ get message() {
134
+ return `Invalid Durable Object alarm registration: ${causeMessage(this.cause)}`;
135
+ }
136
+ };
117
137
  const getScheduleConfiguration = Effect.fnUntraced(function* (defaults) {
118
138
  const input = {
119
139
  ...defaults,
@@ -121,6 +141,7 @@ const getScheduleConfiguration = Effect.fnUntraced(function* (defaults) {
121
141
  };
122
142
  return yield* Effect.try({
123
143
  try: () => S.decodeUnknownSync(ScheduleConfigurationSchema)({
144
+ dispatchAfterEvent: input.dispatchAfterEvent ?? false,
124
145
  minimumRetryDelay: Math.ceil(Duration.toMillis(input.minimumRetryDelay ?? 1e3)),
125
146
  unchangedAttemptBudget: input.unchangedAttemptBudget ?? 8,
126
147
  parkedRetryDelay: Math.ceil(Duration.toMillis(input.parkedRetryDelay ?? 36e5)),
@@ -146,7 +167,14 @@ const AlarmRefSchema = S.Struct({
146
167
  tag: S.NonEmptyString
147
168
  });
148
169
  const decodeAlarmRef = (input) => S.decodeUnknownEffect(AlarmRefSchema)(input).pipe(Effect.mapError((cause) => new InvalidAlarmRefError({ cause })));
170
+ /** @internal */
171
+ const RunAlarm = Symbol("effect-cf/DurableObjectAlarm/RunAlarm");
172
+ /** @internal */
173
+ const DispatchAfterEvent = Symbol("effect-cf/DurableObjectAlarm/DispatchAfterEvent");
174
+ /** @internal */
175
+ const HasDueAlarms = Symbol("effect-cf/DurableObjectAlarm/HasDueAlarms");
149
176
  const StoredPayloadString = S.fromJsonString(S.Json);
177
+ const StoredTags = S.fromJsonString(S.Array(S.String));
150
178
  const decodeStoredPayload = (row) => S.decodeUnknownEffect(StoredPayloadString)(row.payload).pipe(Effect.mapError((cause) => new StoredAlarmDecodeError({
151
179
  cause,
152
180
  storageId: row.storage_id
@@ -154,15 +182,19 @@ const decodeStoredPayload = (row) => S.decodeUnknownEffect(StoredPayloadString)(
154
182
  const encodeStoredPayload = (payload) => S.encodeEffect(StoredPayloadString)(payload).pipe(Effect.mapError((cause) => new InvalidAlarmPayloadError({ cause })));
155
183
  const ensureTable = Effect.fnUntraced(function* (state) {
156
184
  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;
185
+ if ((yield* (yield* state.storage.sql.exec("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?", LIFECYCLE_INDEX)).toArray()).length > 0) return;
158
186
  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");
187
+ const names = new Set((yield* columns.toArray()).map((column) => column.name));
188
+ if (!names.has("lifecycle")) yield* state.storage.sql.exec("ALTER TABLE effect_cf_scheduled_alarms ADD COLUMN lifecycle TEXT NOT NULL DEFAULT 'automatic'");
189
+ 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
190
  yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms SET
161
191
  wake_at = MAX(run_at, COALESCE((SELECT retry_at FROM effect_cf_alarm_attempts s
162
192
  WHERE s.storage_id = effect_cf_scheduled_alarms.storage_id), run_at))`);
163
193
  yield* state.storage.sql.exec("DROP INDEX IF EXISTS idx_effect_cf_scheduled_alarms_run_at_storage_id");
164
194
  yield* state.storage.sql.exec(`CREATE INDEX IF NOT EXISTS ${WAKE_INDEX}
165
195
  ON effect_cf_scheduled_alarms (wake_at, storage_id)`);
196
+ yield* state.storage.sql.exec(`CREATE INDEX IF NOT EXISTS ${LIFECYCLE_INDEX}
197
+ ON effect_cf_scheduled_alarms (lifecycle, wake_at, storage_id)`);
166
198
  });
167
199
  const toRepeatEveryMillis = (input, minimum) => {
168
200
  if (input === void 0) return Effect.succeed(null);
@@ -208,6 +240,71 @@ const processDue = (handle, options = {}) => Effect.gen(function* () {
208
240
  return yield* (yield* DurableObjectAlarm).processDueAlarms(handle, options);
209
241
  });
210
242
  const TypedAlarmSchedulerTypeId = Symbol.for("effect-cf/DurableObjectAlarm/TypedScheduler");
243
+ const RegistrationParts = Symbol("effect-cf/DurableObjectAlarm/RegistrationParts");
244
+ const emptyResult = {
245
+ failed: [],
246
+ handled: [],
247
+ parked: []
248
+ };
249
+ const missingAlarm = (tag) => new InvalidAlarmRegistrationError({ cause: /* @__PURE__ */ new Error(`No manual handler registered for alarm tag "${tag}"`) });
250
+ const validateRegistration = (parts) => Effect.try({
251
+ try: () => {
252
+ if (parts.invalid !== void 0) throw new Error(parts.invalid);
253
+ const tags = parts.dispatchers.flatMap((dispatcher) => dispatcher.tags);
254
+ S.decodeUnknownSync(S.Array(S.NonEmptyString))([...parts.services, ...tags]);
255
+ 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");
256
+ },
257
+ catch: (cause) => new InvalidAlarmRegistrationError({ cause })
258
+ });
259
+ const runRegistrations = (parts, rawAlarm) => Effect.gen(function* () {
260
+ yield* validateRegistration(parts);
261
+ const alarms = yield* DurableObjectAlarm;
262
+ const permits = yield* Semaphore.make(4);
263
+ const parentRaw = (yield* CurrentDispatch)?.raw;
264
+ const recoveryScope = {
265
+ tags: parts.dispatchers.flatMap((dispatcher) => dispatcher.tags),
266
+ exclude: true,
267
+ permits,
268
+ raw: parentRaw
269
+ };
270
+ const recovery = rawAlarm === void 0 ? makeDefinition({}).handlers({}).pipe(Effect.provideService(CurrentDispatch, recoveryScope)) : Effect.suspend(() => {
271
+ const raw = { processed: false };
272
+ return rawAlarm.pipe(Effect.onExit(() => raw.processed ? Effect.void : makeDefinition({}).handlers({}).pipe(Effect.asVoid)), Effect.provideService(CurrentDispatch, {
273
+ ...recoveryScope,
274
+ raw
275
+ }), Effect.as(emptyResult));
276
+ });
277
+ return yield* alarms.deferWakes(Effect.gen(function* () {
278
+ 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, {
279
+ tags: dispatcher.tags,
280
+ exclude: false,
281
+ permits,
282
+ raw: parentRaw
283
+ }), Effect.exit), { concurrency: "unbounded" });
284
+ const automatic = parts.dispatchers.filter((dispatcher) => rawAlarm !== void 0 && dispatcher.automatic);
285
+ const independent = parts.dispatchers.filter((dispatcher) => rawAlarm === void 0 || !dispatcher.automatic);
286
+ const exits = (yield* Effect.all([run(independent), run(automatic).pipe(Effect.flatMap((exits) => Effect.map(Effect.exit(recovery), (exit) => [...exits, exit])))], { concurrency: "unbounded" })).flat();
287
+ yield* Exit.asVoidAll(exits);
288
+ return {
289
+ failed: exits.flatMap((exit) => Exit.isSuccess(exit) ? exit.value.failed : []),
290
+ handled: exits.flatMap((exit) => Exit.isSuccess(exit) ? exit.value.handled : []),
291
+ parked: exits.flatMap((exit) => Exit.isSuccess(exit) ? exit.value.parked : [])
292
+ };
293
+ }));
294
+ });
295
+ const makeRegistration = (layer, parts) => ({
296
+ [RegistrationParts]: parts,
297
+ layer: layer.pipe(Layer.provide(Layer.effectDiscard(validateRegistration(parts)))),
298
+ run: runRegistrations(parts)
299
+ });
300
+ function mergeAll(registration, ...others) {
301
+ const parts = [registration, ...others].map((item) => item[RegistrationParts]);
302
+ return makeRegistration(Layer.mergeAll(registration.layer, ...others.map((item) => item.layer)), {
303
+ services: parts.flatMap((part) => part?.services ?? []),
304
+ dispatchers: parts.flatMap((part) => part?.dispatchers ?? []),
305
+ invalid: parts.some((part) => part === void 0) ? "mergeAll requires registrations returned by Tag.handlers" : parts.find((part) => part?.invalid !== void 0)?.invalid
306
+ });
307
+ }
211
308
  const isAlarmDefinitionConfig = (definition) => Predicate.isObject(definition) && "payload" in definition;
212
309
  const getAlarmDefinitionSchema = (definition) => isAlarmDefinitionConfig(definition) ? definition.payload : definition;
213
310
  const getAlarmDefinitionFailureAction = (definition) => {
@@ -227,7 +324,8 @@ const makeDefinition = (definitions) => {
227
324
  const payload = yield* S.encodeEffect(getAlarmDefinitionSchema(definition))(input.payload).pipe(Effect.flatMap(S.decodeUnknownEffect(S.Json)), Effect.mapError((cause) => new InvalidAlarmPayloadError({ cause })));
228
325
  yield* mutations[method]({
229
326
  ...input,
230
- payload
327
+ payload,
328
+ lifecycle: isAlarmDefinitionConfig(definition) ? definition.lifecycle : void 0
231
329
  });
232
330
  });
233
331
  return {
@@ -243,6 +341,7 @@ const makeDefinition = (definitions) => {
243
341
  make: (alarms) => ({
244
342
  [TypedAlarmSchedulerTypeId]: TypedAlarmSchedulerTypeId,
245
343
  ...bind(alarms),
344
+ deferWakes: alarms.deferWakes,
246
345
  getAlarmStatus: (input) => alarms.getAlarmStatus(input),
247
346
  transaction: (closure) => alarms.transaction((tx) => closure(bind(tx)))
248
347
  }),
@@ -264,6 +363,7 @@ const makeDefinition = (definitions) => {
264
363
  });
265
364
  }), {
266
365
  ...options,
366
+ [ManualTags]: new Set(Object.entries(definitions).filter(([, definition]) => isAlarmDefinitionConfig(definition) && definition.lifecycle === "manual").map(([tag]) => tag)),
267
367
  onFailure: (failure) => Effect.gen(function* () {
268
368
  const action = getAlarmDefinitionFailureAction(definitionFor(failure.tag));
269
369
  const optionAction = options?.onFailure === void 0 ? void 0 : yield* options.onFailure(failure);
@@ -274,32 +374,70 @@ const makeDefinition = (definitions) => {
274
374
  };
275
375
  /** Handler-only definitions for applications using the raw scheduler. Prefer Tag for typed scheduling. */
276
376
  const define = (definitions) => ({ handlers: makeDefinition(definitions).handlers });
377
+ const hasSchedulerTables = Effect.fnUntraced(function* (state) {
378
+ return yield* Effect.gen(function* () {
379
+ if (state.raw.storage.sql === void 0) return false;
380
+ 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;
381
+ }).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)));
382
+ });
383
+ /** @internal Preserve custom dispatch while recovering retained alarms after deployments. */
384
+ const dispatchRawAlarm = (rawAlarm, registration) => Effect.gen(function* () {
385
+ const parts = registration?.[RegistrationParts];
386
+ if (parts !== void 0) return yield* runRegistrations(parts, rawAlarm).pipe(Effect.asVoid);
387
+ const custom = registration === void 0 ? rawAlarm : registration.run.pipe(Effect.andThen(rawAlarm));
388
+ const state = yield* DurableObjectState;
389
+ if (!(yield* hasSchedulerTables(state))) return yield* custom;
390
+ return yield* runRegistrations({
391
+ services: [],
392
+ dispatchers: []
393
+ }, custom).pipe(Effect.asVoid);
394
+ });
277
395
  /** The typed scheduler becomes available when its handlers are registered on a Durable Object. */
278
396
  const Tag = () => (id, definitions) => {
279
397
  const tag = Context.Service()(id);
280
398
  const definition = makeDefinition(definitions);
281
- return Object.assign(tag, { handlers: (handlers, options) => ({
282
- layer: Layer.effect(tag, Effect.map(DurableObjectAlarm, definition.make)),
283
- run: definition.handlers(handlers, options)
399
+ return Object.assign(tag, { handlers: (handlers, options) => makeRegistration(Layer.effect(tag, Effect.map(DurableObjectAlarm, definition.make)), {
400
+ services: [id],
401
+ dispatchers: [{
402
+ tags: Object.keys(definitions),
403
+ automatic: Object.values(definitions).some((entry) => !isAlarmDefinitionConfig(entry) || entry.lifecycle !== "manual"),
404
+ run: definition.handlers(handlers, options)
405
+ }]
284
406
  }) });
285
407
  };
286
408
  var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("effect-cf/DurableObjectAlarm") {
287
409
  static layer = Layer.effect(DurableObjectAlarm, Effect.gen(function* () {
288
410
  const state = yield* DurableObjectState;
289
411
  const configurationDefaults = { ...yield* ScheduleConfiguration };
412
+ let wakeDeferrals = 0;
413
+ let wakesReady;
414
+ let pendingAlarm;
415
+ let dispatchActive = false;
416
+ let dispatchNeedsReconcile = false;
417
+ let deferredRecoveryDelay = PARKED_RETRY_DELAY_MS;
418
+ let deferredRecoveryAt = 0;
290
419
  const readRow = Effect.fnUntraced(function* (storageId) {
291
420
  return (yield* (yield* state.storage.sql.exec(`${alarmRowsSql} WHERE a.storage_id = ?`, storageId)).toArray())[0];
292
421
  });
422
+ const writeNativeAlarm = (runAt) => Effect.tryPromise({
423
+ try: () => runAt === null ? state.raw.storage.deleteAlarm() : state.raw.storage.setAlarm(runAt),
424
+ catch: (cause) => new StorageOperationError({
425
+ operation: runAt === null ? "deleteAlarm" : "setAlarm",
426
+ cause
427
+ })
428
+ });
293
429
  const reconcileAlarm = Effect.fn("DurableObjectAlarm.reconcileAlarm")(function* () {
430
+ if (wakeDeferrals > 0 || dispatchActive) {
431
+ if (dispatchActive) dispatchNeedsReconcile = true;
432
+ const current = yield* state.storage.getAlarm();
433
+ const now = yield* Clock.currentTimeMillis;
434
+ if (deferredRecoveryAt <= now) deferredRecoveryAt = now + deferredRecoveryDelay;
435
+ if (current === null || current <= now || current > deferredRecoveryAt) yield* writeNativeAlarm(deferredRecoveryAt);
436
+ return;
437
+ }
294
438
  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
- });
439
+ ORDER BY wake_at, storage_id LIMIT 1`)).toArray())[0];
440
+ yield* writeNativeAlarm(next?.run_at ?? null);
303
441
  });
304
442
  const writeAttempts = Effect.fnUntraced(function* (storageId, attempts, parked, retryAt, progress) {
305
443
  yield* state.storage.sql.exec(`INSERT OR REPLACE INTO effect_cf_alarm_attempts
@@ -318,17 +456,15 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
318
456
  const scheduleAlarm = Effect.fn("DurableObjectAlarm.scheduleAlarm")(function* (input, earlier, reports) {
319
457
  const configuration = yield* getScheduleConfiguration(configurationDefaults);
320
458
  const ref = yield* decodeAlarmRef(input);
459
+ const lifecycle = yield* S.decodeUnknownEffect(AlarmLifecycleSchema)(input.lifecycle ?? "automatic").pipe(Effect.mapError((cause) => new InvalidAlarmRefError({ cause })));
460
+ 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
461
  const repeatEveryMillis = yield* toRepeatEveryMillis(input.repeatEvery, configuration.minimumRepeatInterval);
322
462
  const payload = yield* encodeStoredPayload(input.payload);
323
463
  const storageId = getScheduledEventId(ref);
324
464
  const existing = yield* readRow(storageId);
325
465
  const pass = yield* CurrentAlarmPass;
326
- const source = pass?.row;
466
+ const source = lifecycle === "automatic" && pass?.row.lifecycle === "automatic" ? pass.row : void 0;
327
467
  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
468
  if (input.progress !== void 0) yield* S.decodeUnknownEffect(S.Natural)(input.progress).pipe(Effect.mapError((cause) => new InvalidAlarmRefError({ cause })));
333
469
  if (source === void 0 && input.progress !== void 0 && input.progress <= progress) return;
334
470
  const progressed = input.progress !== void 0 && input.progress > progress;
@@ -341,11 +477,11 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
341
477
  const keepEarlier = earlier && existing !== void 0 && existing.run_at <= runAt;
342
478
  const scheduledAt = keepEarlier ? existing.run_at : runAt;
343
479
  const scheduledPayload = keepEarlier ? existing.payload : payload;
344
- const scheduledRepeat = keepEarlier ? existing.repeat_every_ms : repeatEveryMillis;
345
- const nextProgress = Math.max(progress, input.progress ?? -1);
480
+ const scheduledRepeat = lifecycle === "manual" ? null : keepEarlier ? existing.repeat_every_ms : repeatEveryMillis;
481
+ const nextProgress = lifecycle === "manual" ? -1 : Math.max(progress, input.progress ?? -1);
346
482
  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);
483
+ (storage_id, alarm_id, tag, lifecycle, run_at, repeat_every_ms, payload)
484
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, storageId, ref.id, ref.tag, lifecycle, scheduledAt, scheduledRepeat, scheduledPayload);
349
485
  yield* writeAttempts(storageId, attempts, parked, retryAt, nextProgress);
350
486
  if (parked === 1 && retryAt !== null && (existing?.parked ?? source?.parked ?? 0) === 0) reports.push(storageId);
351
487
  });
@@ -365,10 +501,15 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
365
501
  }
366
502
  });
367
503
  const transaction = Effect.fnUntraced(function* (closure) {
504
+ const pass = yield* CurrentAlarmPass;
505
+ if (pass !== void 0 && !pass.active) return yield* Effect.fail(new StorageOperationError({
506
+ operation: "alarm.transaction",
507
+ cause: /* @__PURE__ */ new Error("Alarm handlers cannot mutate detached work after their pass ends")
508
+ }));
368
509
  const reports = [];
369
510
  const result = yield* state.storage.transaction(() => Effect.withFiber((owner) => {
370
511
  let active = true;
371
- const requireActive = (effect) => Effect.withFiber((fiber) => active && fiber === owner ? effect : Effect.fail(new StorageOperationError({
512
+ const requireActive = (effect) => Effect.withFiber((fiber) => active && fiber === owner && (pass === void 0 || pass.active) ? effect : Effect.fail(new StorageOperationError({
372
513
  operation: "alarm.transaction",
373
514
  cause: /* @__PURE__ */ new Error("Alarm mutations require their active transaction callback")
374
515
  })));
@@ -388,6 +529,50 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
388
529
  yield* reportParked(reports);
389
530
  return result;
390
531
  });
532
+ const deferWakes = (body) => Effect.acquireUseRelease(Effect.gen(function* () {
533
+ const configuration = yield* getScheduleConfiguration(configurationDefaults);
534
+ const now = yield* Clock.currentTimeMillis;
535
+ if (wakeDeferrals === 0) {
536
+ deferredRecoveryDelay = configuration.parkedRetryDelay;
537
+ deferredRecoveryAt = now + deferredRecoveryDelay;
538
+ wakesReady ??= Deferred.makeUnsafe();
539
+ }
540
+ wakeDeferrals++;
541
+ }), () => transaction(() => Effect.void).pipe(Effect.andThen(body)), () => Effect.gen(function* () {
542
+ wakeDeferrals--;
543
+ if (wakeDeferrals === 0) yield* (dispatchActive ? Effect.void : transaction(() => Effect.void)).pipe(Effect.ensuring(Effect.sync(() => {
544
+ if (wakeDeferrals === 0 && wakesReady !== void 0) {
545
+ Deferred.doneUnsafe(wakesReady, Effect.void);
546
+ wakesReady = void 0;
547
+ }
548
+ })));
549
+ }));
550
+ const awaitWakes = Effect.suspend(() => wakesReady === void 0 ? Effect.void : Deferred.await(wakesReady).pipe(Effect.andThen(awaitWakes)));
551
+ const runAlarm = (event, native) => Effect.flatMap(hasSchedulerTables(state), (managed) => !managed ? native ? event(Effect.void) : Effect.void : Effect.uninterruptibleMask((restore) => Effect.suspend(() => {
552
+ const pending = pendingAlarm;
553
+ if (pending !== void 0) return restore((native ? transaction(() => Effect.void) : Effect.void).pipe(Effect.andThen(Deferred.await(pending))));
554
+ const completed = Deferred.makeUnsafe();
555
+ let dispatchStarted = false;
556
+ pendingAlarm = completed;
557
+ dispatchNeedsReconcile = native;
558
+ return restore(Effect.gen(function* () {
559
+ if (native && wakesReady !== void 0) yield* transaction(() => Effect.void);
560
+ yield* awaitWakes;
561
+ dispatchActive = true;
562
+ yield* event(Effect.gen(function* () {
563
+ yield* getScheduleConfiguration(configurationDefaults);
564
+ dispatchStarted = true;
565
+ }));
566
+ })).pipe(Effect.onExit((exit) => Effect.gen(function* () {
567
+ const reconcile = dispatchNeedsReconcile || dispatchStarted || Exit.isFailure(exit);
568
+ dispatchActive = Exit.isFailure(exit) && !dispatchStarted;
569
+ const reconciled = yield* (reconcile ? transaction(() => Effect.void) : Effect.void).pipe(Effect.exit);
570
+ dispatchActive = false;
571
+ pendingAlarm = void 0;
572
+ yield* Deferred.done(completed, Exit.asVoidAll([exit, reconciled]));
573
+ yield* reconciled;
574
+ })));
575
+ })));
391
576
  const rescheduleFailedAlarm = Effect.fnUntraced(function* (row, initialDelay, configuration, reports) {
392
577
  const current = yield* readRow(row.storage_id);
393
578
  if (current === void 0 || current.revision !== row.revision) return;
@@ -411,8 +596,19 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
411
596
  const limit = yield* getProcessLimit(options);
412
597
  const initialDelay = yield* getFailureRetryDelay(options, configuration);
413
598
  const now = yield* Clock.currentTimeMillis;
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();
599
+ const dispatch = yield* CurrentDispatch;
600
+ if (dispatch?.raw !== void 0) dispatch.raw.processed = true;
601
+ const tagFilter = dispatch === void 0 ? "" : ` AND a.tag ${dispatch.exclude ? "NOT IN" : "IN"} (SELECT value FROM json_each(?))`;
602
+ const tagBindings = dispatch === void 0 ? [] : [S.encodeSync(StoredTags)([...dispatch.tags])];
603
+ const select = Effect.fnUntraced(function* (lifecycle, batchLimit) {
604
+ return yield* (yield* state.storage.sql.exec(`${alarmRowsSql} WHERE a.lifecycle = ? AND a.wake_at <= ?${tagFilter}
605
+ ORDER BY a.wake_at ASC, a.storage_id ASC LIMIT ?`, lifecycle, now, ...tagBindings, batchLimit)).toArray();
606
+ });
607
+ const dueRows = yield* select("automatic", limit);
608
+ const manualRows = yield* select("manual", DEFAULT_PROCESS_DUE_ALARMS_LIMIT);
609
+ if (manualRows.length > 0) yield* transaction(() => Effect.gen(function* () {
610
+ for (const row of manualRows) yield* state.storage.sql.exec(`UPDATE effect_cf_scheduled_alarms SET run_at = ?, wake_at = ? WHERE ${sameRevisionSql}`, now + configuration.parkedRetryDelay, now + configuration.parkedRetryDelay, row.storage_id, row.storage_id, row.revision);
611
+ }));
416
612
  const handled = [];
417
613
  const failed = [];
418
614
  const parked = [];
@@ -437,9 +633,9 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
437
633
  if (Exit.isFailure(actionExit)) return yield* Effect.failCause(actionExit.cause);
438
634
  if (Exit.isFailure(delayExit)) return yield* Effect.failCause(delayExit.cause);
439
635
  });
440
- for (const row of dueRows) {
636
+ const handleRow = Effect.fnUntraced(function* (row) {
441
637
  const current = yield* readRow(row.storage_id);
442
- if (current === void 0 || current.revision !== row.revision) continue;
638
+ if (current === void 0 || current.revision !== row.revision) return;
443
639
  const inPass = (effect) => {
444
640
  const pass = {
445
641
  row,
@@ -450,21 +646,27 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
450
646
  pass.active = false;
451
647
  })));
452
648
  };
649
+ const manual = row.lifecycle === "manual";
650
+ if (manual && !options?.[ManualTags]?.has(row.tag)) return yield* Effect.fail(missingAlarm(row.tag));
453
651
  const eventExit = yield* Effect.exit(toAlarmDue(row));
454
652
  if (Exit.isFailure(eventExit)) {
455
- yield* inPass(handleFailure(row, void 0, eventExit.cause));
456
- continue;
653
+ if (manual) return yield* Effect.failCause(eventExit.cause);
654
+ return yield* inPass(handleFailure(row, void 0, eventExit.cause));
457
655
  }
458
656
  const event = eventExit.value;
459
657
  const handleExit = yield* Effect.exit(inPass(Effect.suspend(() => handle(event))));
460
658
  if (Exit.isFailure(handleExit)) {
461
- yield* inPass(handleFailure(row, event, handleExit.cause));
462
- continue;
659
+ if (manual) return yield* Effect.failCause(handleExit.cause);
660
+ return yield* inPass(handleFailure(row, event, handleExit.cause));
463
661
  }
464
- yield* transaction(() => acknowledgeAlarm(row, configuration));
662
+ if (!manual) yield* transaction(() => acknowledgeAlarm(row, configuration));
465
663
  handled.push(event);
466
- }
664
+ });
665
+ const permits = dispatch?.permits ?? (yield* Semaphore.make(4));
666
+ const automatic = Effect.forEach(dueRows, handleRow, { discard: true }).pipe(Effect.exit);
667
+ 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 });
467
668
  yield* transaction(() => Effect.void);
669
+ yield* Exit.asVoidAll(exits);
468
670
  return {
469
671
  failed,
470
672
  handled,
@@ -472,6 +674,14 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
472
674
  };
473
675
  });
474
676
  return DurableObjectAlarm.of({
677
+ [RunAlarm]: runAlarm,
678
+ [DispatchAfterEvent]: Effect.map(ScheduleConfiguration, (configuration) => (configuration.dispatchAfterEvent ?? configurationDefaults.dispatchAfterEvent) === true),
679
+ [HasDueAlarms]: Effect.gen(function* () {
680
+ yield* ensureTable(state);
681
+ const now = yield* Clock.currentTimeMillis;
682
+ return (yield* (yield* state.storage.sql.exec("SELECT 1 FROM effect_cf_scheduled_alarms WHERE wake_at <= ? LIMIT 1", now)).toArray()).length > 0;
683
+ }),
684
+ deferWakes,
475
685
  cancelAlarm: (input) => transaction((alarms) => alarms.cancelAlarm(input)),
476
686
  getAlarmStatus: Effect.fnUntraced(function* (input) {
477
687
  const ref = yield* decodeAlarmRef(input);
@@ -492,4 +702,4 @@ var DurableObjectAlarm = class DurableObjectAlarm extends Context.Service()("eff
492
702
  }));
493
703
  };
494
704
  //#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 };
705
+ 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 };
@@ -5,9 +5,9 @@ import * as Effect from "effect/Effect";
5
5
  import * as Schema from "effect/Schema";
6
6
  import * as Layer from "effect/Layer";
7
7
  import * as Option from "effect/Option";
8
- import * as RpcMessage from "effect/unstable/rpc/RpcMessage";
9
- import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
10
- import * as RpcServer from "effect/unstable/rpc/RpcServer";
8
+ import * as RpcMessage from "effect/rpc/RpcMessage";
9
+ import * as RpcSerialization from "effect/rpc/RpcSerialization";
10
+ import * as RpcServer from "effect/rpc/RpcServer";
11
11
  declare namespace DurableObjectRpcWebSocket_d_exports {
12
12
  export { DurableObjectRpcWebSocket, DurableObjectRpcWebSocketService, HeartbeatPolicy, LayerOptions, NativeWebSocketMessage, ResumableStreamDeclaration, RpcWebSocketAttachment, layer, resumableStream };
13
13
  }
@@ -8,9 +8,9 @@ import * as Layer from "effect/Layer";
8
8
  import * as Predicate from "effect/Predicate";
9
9
  import * as Option from "effect/Option";
10
10
  import * as Queue from "effect/Queue";
11
- import * as RpcMessage from "effect/unstable/rpc/RpcMessage";
12
- import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
13
- import * as RpcServer from "effect/unstable/rpc/RpcServer";
11
+ import * as RpcMessage from "effect/rpc/RpcMessage";
12
+ import * as RpcSerialization from "effect/rpc/RpcSerialization";
13
+ import * as RpcServer from "effect/rpc/RpcServer";
14
14
  //#region src/DurableObjectRpcWebSocket.ts
15
15
  var DurableObjectRpcWebSocket_exports = /* @__PURE__ */ __exportAll({
16
16
  DurableObjectRpcWebSocket: () => DurableObjectRpcWebSocket,
@@ -1,7 +1,7 @@
1
1
  import { DurableObjectState } from "./DurableObjectState.mjs";
2
2
  import * as Layer from "effect/Layer";
3
3
  import { SqliteClient } from "@effect/sql-sqlite-do";
4
- import { SqlClient } from "effect/unstable/sql";
4
+ import { SqlClient } from "effect/sql";
5
5
  declare namespace DurableObjectSqlite_d_exports {
6
6
  export { SqliteLayerOptions, layer };
7
7
  }
@@ -5,8 +5,8 @@ import * as Effect from "effect/Effect";
5
5
  import * as Layer from "effect/Layer";
6
6
  import * as Scheduler from "effect/Scheduler";
7
7
  import { SqliteClient } from "@effect/sql-sqlite-do";
8
- import * as Reactivity from "effect/unstable/reactivity/Reactivity";
9
- import { SqlClient } from "effect/unstable/sql";
8
+ import * as Reactivity from "effect/reactivity/Reactivity";
9
+ import { SqlClient } from "effect/sql";
10
10
  //#region src/DurableObjectSqlite.ts
11
11
  var DurableObjectSqlite_exports = /* @__PURE__ */ __exportAll({ layer: () => layer });
12
12
  const layer = (options) => Layer.effectContext(Effect.gen(function* () {
@@ -2,7 +2,7 @@ import { WorkerEnvironment } from "./Environment.mjs";
2
2
  import { BindingNotFoundError, BindingValidationError } from "./Binding.mjs";
3
3
  import { LayerOptions, TagClass } from "./Hyperdrive.mjs";
4
4
  import * as Layer from "effect/Layer";
5
- import { SqlClient, SqlError } from "effect/unstable/sql";
5
+ import { SqlClient, SqlError } from "effect/sql";
6
6
  import { PgClient } from "@effect/sql-pg";
7
7
  //#region src/HyperdrivePg.d.ts
8
8
  type PgLayerOptions = Omit<Parameters<typeof PgClient.makeClient>[0], "url" | "host" | "port" | "path" | "ssl" | "database" | "username" | "password" | "stream">;
package/dist/Mcp.d.mts CHANGED
@@ -2,8 +2,8 @@ import { NativeRequest } from "./Worker.mjs";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Option from "effect/Option";
4
4
  import { CreateMcpHandlerOptions, ServerOptions } from "@modelcontextprotocol/server";
5
- import * as Tool from "effect/unstable/ai/Tool";
6
- import { Toolkit } from "effect/unstable/ai";
5
+ import * as Tool from "effect/ai/Tool";
6
+ import { Toolkit } from "effect/ai";
7
7
  //#region src/Mcp.d.ts
8
8
  /**
9
9
  * CORS headers applied to MCP responses.
@@ -96,7 +96,7 @@ type FromToolkitContext<Tools extends Record<string, Tool.Any>> = NativeRequest
96
96
  *
97
97
  * ```ts
98
98
  * import { Effect, Layer, Option } from "effect"
99
- * import { HttpRouter } from "effect/unstable/http"
99
+ * import { HttpRouter } from "effect/http"
100
100
  * import { Worker } from "effect-cf"
101
101
  * import * as Mcp from "effect-cf/mcp"
102
102
  *
package/dist/Mcp.mjs CHANGED
@@ -10,8 +10,8 @@ import * as Exit from "effect/Exit";
10
10
  import * as Stream from "effect/Stream";
11
11
  import { McpServer, createMcpHandler, hostHeaderValidationResponse, localhostAllowedHostnames, localhostAllowedOrigins, originValidationResponse } from "@modelcontextprotocol/server";
12
12
  import * as Sink from "effect/Sink";
13
- import * as AiError from "effect/unstable/ai/AiError";
14
- import * as Tool from "effect/unstable/ai/Tool";
13
+ import * as AiError from "effect/ai/AiError";
14
+ import * as Tool from "effect/ai/Tool";
15
15
  //#region src/Mcp.ts
16
16
  const DEFAULT_CORS_OPTIONS = {
17
17
  origin: "*",
@@ -214,7 +214,7 @@ const registerToolkitTool = (server, built, tool, run) => {
214
214
  *
215
215
  * ```ts
216
216
  * import { Effect, Layer, Option } from "effect"
217
- * import { HttpRouter } from "effect/unstable/http"
217
+ * import { HttpRouter } from "effect/http"
218
218
  * import { Worker } from "effect-cf"
219
219
  * import * as Mcp from "effect-cf/mcp"
220
220
  *
@@ -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 };