effect-cf 0.53.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.
- package/CHANGELOG.md +10 -0
- package/README.md +6 -0
- package/dist/DurableObject.d.mts +1 -0
- package/dist/DurableObject.mjs +33 -11
- package/dist/DurableObjectAlarm.d.mts +74 -9
- package/dist/DurableObjectAlarm.mjs +245 -35
- 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 +601 -59
- 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
|
@@ -31,14 +31,16 @@ const makeEntrypointRuntime = (layer, env, services) => {
|
|
|
31
31
|
const entrypointServices = Layer.mergeAll(services, ConfigProvider.layer(WorkerConfig.providerFromEnv(env)), Layer.succeed(WorkerEnvironment, env));
|
|
32
32
|
return ManagedRuntime.make(provideEntrypointServices(layer, entrypointServices));
|
|
33
33
|
};
|
|
34
|
+
/** Close event resources before an outer lifecycle (such as alarm reconciliation) completes. */
|
|
35
|
+
const scopeEvent = (effect) => Effect.scoped(withScope(effect));
|
|
34
36
|
function runEventPromise(...args) {
|
|
35
37
|
const [runtime, effect, eventLayer, parent, onFailure] = args;
|
|
36
38
|
if (eventLayer === void 0) {
|
|
37
|
-
const event =
|
|
39
|
+
const event = scopeEvent(effect);
|
|
38
40
|
return runPromise(runtime, parent === void 0 ? event : Effect.withParentSpan(event, parent), onFailure);
|
|
39
41
|
}
|
|
40
|
-
const event =
|
|
42
|
+
const event = scopeEvent(effect.pipe(Effect.provide(eventLayer, { local: true })));
|
|
41
43
|
return runPromise(runtime, parent === void 0 ? event : Effect.withParentSpan(event, parent), onFailure);
|
|
42
44
|
}
|
|
43
45
|
//#endregion
|
|
44
|
-
export { makeEntrypointRuntime, runEventPromise };
|
|
46
|
+
export { makeEntrypointRuntime, runEventPromise, scopeEvent };
|
package/package.json
CHANGED
package/src/DurableObject.ts
CHANGED
|
@@ -10,8 +10,14 @@ import { WorkerEnvironment, type WorkerEnv } from "./Environment";
|
|
|
10
10
|
import { DurableObjectState, fromDurableObjectState } from "./DurableObjectState";
|
|
11
11
|
import {
|
|
12
12
|
DurableObjectAlarm,
|
|
13
|
+
DispatchAfterEvent,
|
|
14
|
+
HasDueAlarms,
|
|
15
|
+
RunAlarm,
|
|
16
|
+
dispatchRawAlarm,
|
|
13
17
|
type AlarmRegistration,
|
|
14
18
|
type AlarmService,
|
|
19
|
+
type InvalidAlarmRegistrationError,
|
|
20
|
+
type InvalidScheduleConfigurationError,
|
|
15
21
|
} from "./DurableObjectAlarm";
|
|
16
22
|
import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
|
|
17
23
|
import * as RpcDefinition from "./RpcDefinition";
|
|
@@ -229,6 +235,7 @@ export type DurableObjectClass<Rpc extends DurableObjectRpc<ROut>, ROut> = new (
|
|
|
229
235
|
env: WorkerEnv,
|
|
230
236
|
) => CloudflareDurableObject<WorkerEnv> &
|
|
231
237
|
DurableObjectRpcApi<Rpc, ROut> & {
|
|
238
|
+
alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> | void;
|
|
232
239
|
[RunSymbol]<A, E>(
|
|
233
240
|
effect: Effect.Effect<A, E, HandlerContext<ROut>>,
|
|
234
241
|
options?: RunOptions,
|
|
@@ -274,12 +281,15 @@ export function make<
|
|
|
274
281
|
): DurableObjectClass<Rpc, ROut | REvent | RAlarm> {
|
|
275
282
|
const registration =
|
|
276
283
|
options.alarms !== undefined && !Effect.isEffect(options.alarms) ? options.alarms : undefined;
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
284
|
+
const legacyAlarms = Effect.isEffect(options.alarms)
|
|
285
|
+
? options.alarms.pipe(Effect.asVoid)
|
|
286
|
+
: undefined;
|
|
280
287
|
|
|
281
288
|
class EffectDurableObject extends CloudflareDurableObject<WorkerEnv> {
|
|
282
|
-
readonly runtime: ManagedRuntime.ManagedRuntime<
|
|
289
|
+
readonly runtime: ManagedRuntime.ManagedRuntime<
|
|
290
|
+
RuntimeContext<ROut | RAlarm>,
|
|
291
|
+
LayerError | InvalidAlarmRegistrationError
|
|
292
|
+
>;
|
|
283
293
|
|
|
284
294
|
constructor(state: globalThis.DurableObjectState, env: WorkerEnv) {
|
|
285
295
|
super(state, env);
|
|
@@ -290,12 +300,16 @@ export function make<
|
|
|
290
300
|
// SAFETY: RAlarm is inferred only from the registration's layer; without a registration it is never.
|
|
291
301
|
const services = (registration?.layer ?? Layer.empty).pipe(
|
|
292
302
|
Layer.provideMerge(baseServices),
|
|
293
|
-
) as Layer.Layer<
|
|
303
|
+
) as Layer.Layer<
|
|
304
|
+
DurableObjectState | DurableObjectAlarm | RAlarm,
|
|
305
|
+
InvalidAlarmRegistrationError
|
|
306
|
+
>;
|
|
294
307
|
|
|
295
308
|
this.runtime = Runtime.makeEntrypointRuntime<
|
|
296
309
|
ROut,
|
|
297
310
|
LayerError,
|
|
298
|
-
DurableObjectState | DurableObjectAlarm | RAlarm
|
|
311
|
+
DurableObjectState | DurableObjectAlarm | RAlarm,
|
|
312
|
+
InvalidAlarmRegistrationError
|
|
299
313
|
>(layer, env, services);
|
|
300
314
|
|
|
301
315
|
const initialize = options.initialize;
|
|
@@ -322,27 +336,54 @@ export function make<
|
|
|
322
336
|
const eventLayer = runOptions.eventLayer === false ? undefined : options.eventLayer;
|
|
323
337
|
const parent = runOptions.rpc?.parent;
|
|
324
338
|
const parentSpan = parent === undefined ? undefined : Tracer.externalSpan(parent);
|
|
339
|
+
const afterResponse = runOptions.event === "rpc" || runOptions.event === "fetch";
|
|
340
|
+
let dispatchAfterEvent = false;
|
|
341
|
+
const handler = afterResponse
|
|
342
|
+
? effect.pipe(
|
|
343
|
+
Effect.onExit(() =>
|
|
344
|
+
Effect.flatMap(DurableObjectAlarm, (alarms) =>
|
|
345
|
+
Effect.map(alarms[DispatchAfterEvent], (enabled) => {
|
|
346
|
+
dispatchAfterEvent = enabled;
|
|
347
|
+
}),
|
|
348
|
+
),
|
|
349
|
+
),
|
|
350
|
+
)
|
|
351
|
+
: effect;
|
|
352
|
+
let response: Promise<A>;
|
|
325
353
|
|
|
326
354
|
if (eventLayer === undefined) {
|
|
327
355
|
// SAFETY: initialize cannot require event services; without an event layer, all requirements are already provided.
|
|
328
|
-
|
|
356
|
+
response = Runtime.runEventPromise(
|
|
329
357
|
this.runtime,
|
|
330
358
|
// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
|
|
331
|
-
|
|
359
|
+
handler as Effect.Effect<A, E, HandlerContext<ROut | RAlarm>>,
|
|
332
360
|
undefined,
|
|
333
361
|
parentSpan,
|
|
334
362
|
runOptions.onFailure,
|
|
335
363
|
);
|
|
364
|
+
} else {
|
|
365
|
+
response = Runtime.runEventPromise<
|
|
366
|
+
A,
|
|
367
|
+
E,
|
|
368
|
+
RuntimeContext<ROut | RAlarm>,
|
|
369
|
+
REvent,
|
|
370
|
+
EventLayerError,
|
|
371
|
+
LayerError | InvalidAlarmRegistrationError
|
|
372
|
+
>(this.runtime, handler, eventLayer, parentSpan, runOptions.onFailure);
|
|
336
373
|
}
|
|
337
374
|
|
|
338
|
-
return
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
375
|
+
if (!afterResponse) return response;
|
|
376
|
+
|
|
377
|
+
return response.finally(() => {
|
|
378
|
+
if (dispatchAfterEvent) {
|
|
379
|
+
this.ctx.waitUntil(
|
|
380
|
+
// The default scheduler yields to a later task, after the response chain unwinds.
|
|
381
|
+
Effect.runPromise(
|
|
382
|
+
Effect.yieldNow.pipe(Effect.andThen(Effect.promise(() => this.#runAlarm(false)))),
|
|
383
|
+
),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
346
387
|
}
|
|
347
388
|
|
|
348
389
|
fetch(request: Request): Promise<Response> {
|
|
@@ -357,21 +398,43 @@ export function make<
|
|
|
357
398
|
});
|
|
358
399
|
}
|
|
359
400
|
|
|
360
|
-
alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void>
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
401
|
+
alarm(alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> {
|
|
402
|
+
return this.#runAlarm(true, alarmInfo);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
#runAlarm(native: boolean, alarmInfo?: globalThis.AlarmInvocationInfo): Promise<void> {
|
|
406
|
+
const event = (beginDispatch: Effect.Effect<void, InvalidScheduleConfigurationError>) => {
|
|
407
|
+
const alarmEffect = Effect.gen(function* () {
|
|
408
|
+
const alarms = yield* DurableObjectAlarm;
|
|
409
|
+
|
|
410
|
+
if (!native && !(yield* alarms[HasDueAlarms])) return;
|
|
411
|
+
const rawAlarm = options.alarm?.(alarmInfo);
|
|
412
|
+
const customAlarm =
|
|
413
|
+
legacyAlarms === undefined
|
|
414
|
+
? (rawAlarm ?? Effect.void)
|
|
415
|
+
: legacyAlarms.pipe(Effect.andThen(rawAlarm ?? Effect.void));
|
|
416
|
+
|
|
417
|
+
yield* beginDispatch;
|
|
418
|
+
// Custom dispatchers, including Effect-form alarms, recover retained checkpoints.
|
|
419
|
+
yield* registration === undefined || rawAlarm !== undefined
|
|
420
|
+
? dispatchRawAlarm(customAlarm, registration)
|
|
421
|
+
: registration.run.pipe(Effect.asVoid);
|
|
422
|
+
}).pipe(Effect.onExit(() => scheduleTelemetryFlush));
|
|
423
|
+
|
|
424
|
+
return Runtime.scopeEvent(
|
|
425
|
+
options.eventLayer === undefined
|
|
426
|
+
? alarmEffect
|
|
427
|
+
: alarmEffect.pipe(Effect.provide(options.eventLayer, { local: true })),
|
|
428
|
+
);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
return this[RunSymbol](
|
|
432
|
+
Effect.flatMap(DurableObjectAlarm, (alarms) => alarms[RunAlarm](event, native)),
|
|
433
|
+
{
|
|
372
434
|
event: "alarm",
|
|
373
|
-
|
|
374
|
-
|
|
435
|
+
eventLayer: false,
|
|
436
|
+
},
|
|
437
|
+
);
|
|
375
438
|
}
|
|
376
439
|
|
|
377
440
|
webSocketMessage(socket: WebSocket, message: string | ArrayBuffer): Promise<void> | void {
|