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
package/src/RateLimit.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Data from "effect/Data";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Predicate from "effect/Predicate";
|
|
5
|
+
import type { Layer } from "effect";
|
|
6
|
+
|
|
7
|
+
import * as Binding from "./Binding";
|
|
8
|
+
import type { WorkerEnvironment } from "./Environment";
|
|
9
|
+
|
|
10
|
+
export type { BindingNotFoundError, BindingValidationError } from "./Binding";
|
|
11
|
+
|
|
12
|
+
const expectedRateLimit = "Rate Limiting binding with limit()";
|
|
13
|
+
|
|
14
|
+
export type RateLimitBinding = globalThis.RateLimit;
|
|
15
|
+
export type RateLimitOptions = globalThis.RateLimitOptions;
|
|
16
|
+
export type RateLimitOutcome = globalThis.RateLimitOutcome;
|
|
17
|
+
|
|
18
|
+
export class RateLimitOperationError extends Data.TaggedError("RateLimitOperationError")<{
|
|
19
|
+
readonly binding: string;
|
|
20
|
+
readonly operation: "limit";
|
|
21
|
+
readonly cause: unknown;
|
|
22
|
+
}> {
|
|
23
|
+
override get message(): string {
|
|
24
|
+
return `Cloudflare rate limit binding "${this.binding}" operation "limit" failed`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RateLimitDefinition {
|
|
29
|
+
readonly binding: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RateLimitClient {
|
|
33
|
+
/** Denial is a successful result with success: false. Calls are never retried. */
|
|
34
|
+
readonly limit: (
|
|
35
|
+
options: RateLimitOptions,
|
|
36
|
+
) => Effect.Effect<RateLimitOutcome, RateLimitOperationError>;
|
|
37
|
+
readonly rawUnsafe: Effect.Effect<RateLimitBinding>;
|
|
38
|
+
readonly definition: RateLimitDefinition;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare const RateLimitServiceTypeId: unique symbol;
|
|
42
|
+
|
|
43
|
+
export interface RateLimitService<Id extends string> {
|
|
44
|
+
readonly [RateLimitServiceTypeId]: {
|
|
45
|
+
readonly id: Id;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type LayerOptions = {
|
|
50
|
+
readonly binding: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export interface TagClass<Self, Id extends string> extends Context.ServiceClass<
|
|
54
|
+
Self,
|
|
55
|
+
Id,
|
|
56
|
+
RateLimitClient
|
|
57
|
+
> {
|
|
58
|
+
readonly id: Id;
|
|
59
|
+
readonly layer: (
|
|
60
|
+
options: LayerOptions,
|
|
61
|
+
) => Layer.Layer<
|
|
62
|
+
Self,
|
|
63
|
+
Binding.BindingNotFoundError | Binding.BindingValidationError,
|
|
64
|
+
WorkerEnvironment
|
|
65
|
+
>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const isRateLimit = <Candidate>(value: Candidate): value is Candidate & RateLimitBinding =>
|
|
69
|
+
Predicate.hasProperty(value, "limit") && Predicate.isFunction(value.limit);
|
|
70
|
+
|
|
71
|
+
export const makeClient =
|
|
72
|
+
(definition: RateLimitDefinition) =>
|
|
73
|
+
(rateLimit: RateLimitBinding): RateLimitClient => ({
|
|
74
|
+
definition,
|
|
75
|
+
limit: (options) =>
|
|
76
|
+
Effect.tryPromise({
|
|
77
|
+
try: () => rateLimit.limit(options),
|
|
78
|
+
catch: (cause) =>
|
|
79
|
+
new RateLimitOperationError({
|
|
80
|
+
binding: definition.binding,
|
|
81
|
+
operation: "limit",
|
|
82
|
+
cause,
|
|
83
|
+
}),
|
|
84
|
+
}),
|
|
85
|
+
rawUnsafe: Effect.succeed(rateLimit),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const layer = <Self>(
|
|
89
|
+
tag: Context.Service<Self, RateLimitClient>,
|
|
90
|
+
definition: RateLimitDefinition,
|
|
91
|
+
) =>
|
|
92
|
+
Binding.layer(tag, definition.binding, isRateLimit, makeClient(definition), {
|
|
93
|
+
expected: expectedRateLimit,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const make = <Id extends string>(id: Id) => Tag<RateLimitService<Id>>()<Id>(id);
|
|
97
|
+
|
|
98
|
+
export const Tag =
|
|
99
|
+
<Self>() =>
|
|
100
|
+
<Id extends string>(id: Id) => {
|
|
101
|
+
const tag = Context.Service<Self, RateLimitClient>()(id);
|
|
102
|
+
|
|
103
|
+
const makeLayer = (definition: LayerOptions) => layer(tag, definition);
|
|
104
|
+
|
|
105
|
+
// SAFETY: these are exactly the members required by TagClass, attached to the matching service tag.
|
|
106
|
+
return Object.assign(tag, {
|
|
107
|
+
id,
|
|
108
|
+
layer: makeLayer,
|
|
109
|
+
}) as TagClass<Self, Id>;
|
|
110
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * as Kv from "./Kv";
|
|
|
27
27
|
export * as Queue from "./Queue";
|
|
28
28
|
export * as QueueBinding from "./QueueBinding";
|
|
29
29
|
export * as QueueDefinition from "./QueueDefinition";
|
|
30
|
+
export * as RateLimit from "./RateLimit";
|
|
30
31
|
export * as R2 from "./R2";
|
|
31
32
|
export * as Rpc from "./Rpc";
|
|
32
33
|
export * as RpcDefinition from "./RpcDefinition";
|
|
@@ -44,12 +44,16 @@ const invokeEntrypointRpcMethod = <Self>(
|
|
|
44
44
|
);
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
export const provideEntrypointServices = <ROut, LayerError, RIn>(
|
|
47
|
+
export const provideEntrypointServices = <ROut, LayerError, RIn, ServicesError = never>(
|
|
48
48
|
layer: Layer.Layer<ROut, LayerError, RIn>,
|
|
49
|
-
services: Layer.Layer<RIn,
|
|
50
|
-
): Layer.Layer<ROut | RIn, LayerError, never> =>
|
|
49
|
+
services: Layer.Layer<RIn, ServicesError, never>,
|
|
50
|
+
): Layer.Layer<ROut | RIn, LayerError | ServicesError, never> =>
|
|
51
51
|
// SAFETY: provideMerge supplies RIn while retaining it in the output context for entrypoint consumers.
|
|
52
|
-
layer.pipe(Layer.provideMerge(services)) as Layer.Layer<
|
|
52
|
+
layer.pipe(Layer.provideMerge(services)) as Layer.Layer<
|
|
53
|
+
ROut | RIn,
|
|
54
|
+
LayerError | ServicesError,
|
|
55
|
+
never
|
|
56
|
+
>;
|
|
53
57
|
|
|
54
58
|
export const defineEntrypointRpcMethods = <Self, Prototype extends object = object>(
|
|
55
59
|
target: string,
|
package/src/internal/Runtime.ts
CHANGED
|
@@ -56,11 +56,14 @@ const runPromise = <A, E, R, LayerError>(
|
|
|
56
56
|
* layer so the resulting runtime satisfies both the user services and the
|
|
57
57
|
* platform services.
|
|
58
58
|
*/
|
|
59
|
-
export const makeEntrypointRuntime = <ROut, LayerError, Services>(
|
|
59
|
+
export const makeEntrypointRuntime = <ROut, LayerError, Services, ServicesError = never>(
|
|
60
60
|
layer: Layer.Layer<ROut, LayerError, Services | WorkerEnvironment>,
|
|
61
61
|
env: WorkerEnv,
|
|
62
|
-
services: Layer.Layer<Services>,
|
|
63
|
-
): ManagedRuntime.ManagedRuntime<
|
|
62
|
+
services: Layer.Layer<Services, ServicesError>,
|
|
63
|
+
): ManagedRuntime.ManagedRuntime<
|
|
64
|
+
ROut | Services | WorkerEnvironment,
|
|
65
|
+
LayerError | ServicesError
|
|
66
|
+
> => {
|
|
64
67
|
const entrypointServices = Layer.mergeAll(
|
|
65
68
|
services,
|
|
66
69
|
ConfigProvider.layer(WorkerConfig.providerFromEnv(env)),
|
|
@@ -70,6 +73,10 @@ export const makeEntrypointRuntime = <ROut, LayerError, Services>(
|
|
|
70
73
|
return ManagedRuntime.make(provideEntrypointServices(layer, entrypointServices));
|
|
71
74
|
};
|
|
72
75
|
|
|
76
|
+
/** Close event resources before an outer lifecycle (such as alarm reconciliation) completes. */
|
|
77
|
+
export const scopeEvent = <A, E, R>(effect: Effect.Effect<A, E, R | Scope.Scope>) =>
|
|
78
|
+
Effect.scoped(RpcTargets.withScope(effect));
|
|
79
|
+
|
|
73
80
|
export function runEventPromise<A, E, R, LayerError>(
|
|
74
81
|
runtime: ManagedRuntime.ManagedRuntime<R, LayerError>,
|
|
75
82
|
effect: Effect.Effect<A, E, NoInfer<R> | Scope.Scope>,
|
|
@@ -105,7 +112,7 @@ export function runEventPromise<A, E, R, REvent, EventLayerError, LayerError>(
|
|
|
105
112
|
const [runtime, effect, eventLayer, parent, onFailure] = args;
|
|
106
113
|
|
|
107
114
|
if (eventLayer === undefined) {
|
|
108
|
-
const event =
|
|
115
|
+
const event = scopeEvent(effect);
|
|
109
116
|
|
|
110
117
|
return runPromise(
|
|
111
118
|
runtime,
|
|
@@ -114,9 +121,7 @@ export function runEventPromise<A, E, R, REvent, EventLayerError, LayerError>(
|
|
|
114
121
|
);
|
|
115
122
|
}
|
|
116
123
|
|
|
117
|
-
const event = Effect.
|
|
118
|
-
RpcTargets.withScope(effect.pipe(Effect.provide(eventLayer, { local: true }))),
|
|
119
|
-
);
|
|
124
|
+
const event = scopeEvent(effect.pipe(Effect.provide(eventLayer, { local: true })));
|
|
120
125
|
|
|
121
126
|
return runPromise(
|
|
122
127
|
runtime,
|