effect-cf 0.23.1 → 0.25.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 +20 -0
- package/README.md +157 -7
- package/dist/{Hyperdrive-COyg7R26.d.mts → Binding-DI7u3fEY.d.mts} +5 -39
- package/dist/DurableObjectState-B5RCFnRy.mjs +384 -0
- package/dist/Hyperdrive-BwYp8Rsz.d.mts +36 -0
- package/dist/HyperdrivePg.d.mts +2 -1
- package/dist/Vitest.d.mts +192 -0
- package/dist/Vitest.mjs +184 -0
- package/dist/WorkflowBinding--eMVaixT.d.mts +1081 -0
- package/dist/index.d.mts +84 -1177
- package/dist/index.mjs +18 -435
- package/package.json +15 -6
- package/src/AnalyticsEngine.ts +7 -53
- package/src/DurableObjectAlarm.ts +0 -2
- package/src/Email.ts +5 -34
- package/src/Environment.ts +1 -4
- package/src/Vitest.ts +500 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# effect-cf
|
|
2
2
|
|
|
3
|
+
## 0.25.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#85](https://github.com/danieljvdm/effect-cf/pull/85) [`2c46f9d`](https://github.com/danieljvdm/effect-cf/commit/2c46f9d64774edb7994e3879313077e2a203777a) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Remove compatibility APIs. Use `WorkerConfig.providerLayer`,
|
|
8
|
+
`AnalyticsEngine.writeDataPoint` or `writeDataPoints`, and
|
|
9
|
+
`DurableObjectAlarm.processDue`; Email sends now require structured builder messages.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#87](https://github.com/danieljvdm/effect-cf/pull/87) [`6d6fdfa`](https://github.com/danieljvdm/effect-cf/commit/6d6fdfaaf4812bb46c49b49aad3fd2ca50af76f6) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Allow compatible Effect releases in package development dependency ranges.
|
|
14
|
+
|
|
15
|
+
## 0.24.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- [#82](https://github.com/danieljvdm/effect-cf/pull/82) [`877f289`](https://github.com/danieljvdm/effect-cf/commit/877f289a19ff15bdca1e7f9320aee3be1cffb3de) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Add an `effect-cf/vitest` entrypoint with Effect-native Workers environment, fetch, queue, execution-context, and scoped Workflow testing helpers for Cloudflare's Vitest Workers pool.
|
|
20
|
+
|
|
21
|
+
- [#84](https://github.com/danieljvdm/effect-cf/pull/84) [`9bbd554`](https://github.com/danieljvdm/effect-cf/commit/9bbd5543ca463447d0eb6a08d6441c7b72086b40) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Add Effect-native scheduled and Pages runners, Durable Object test controls, D1 migrations, binding reset helpers, Workflow introspectors, and a Secrets Store admin client to `effect-cf/vitest`. The optional `@cloudflare/vitest-pool-workers` peer dependency now requires `^0.21.3`.
|
|
22
|
+
|
|
3
23
|
## 0.23.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -4,18 +4,18 @@ Effect-native Cloudflare primitives for Workers, Durable Objects, Containers, bi
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
`effect-cf`
|
|
7
|
+
`effect-cf` targets Effect `^4.0.0-beta.105`.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
bun add effect-cf "effect@^4.0.0-beta.
|
|
10
|
+
bun add effect-cf "effect@^4.0.0-beta.105"
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
pnpm add effect-cf "effect@^4.0.0-beta.
|
|
14
|
+
pnpm add effect-cf "effect@^4.0.0-beta.105"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install effect-cf "effect@^4.0.0-beta.
|
|
18
|
+
npm install effect-cf "effect@^4.0.0-beta.105"
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Goal
|
|
@@ -43,6 +43,157 @@ Runtime creation belongs at Cloudflare entrypoints, not inside binding helpers.
|
|
|
43
43
|
- `Workflow` - typed Workflow entrypoints, steps, starter clients, and instance types
|
|
44
44
|
- `Rpc` - Cloudflare RPC type helpers and scoped disposal utilities
|
|
45
45
|
- `WorkerConfig` - Effect `Config` helpers backed by Cloudflare `env`
|
|
46
|
+
- `effect-cf/vitest` - Effect-native runners and scoped test helpers for Cloudflare's Vitest Workers pool
|
|
47
|
+
|
|
48
|
+
## Vitest Workers Pool
|
|
49
|
+
|
|
50
|
+
Install and configure a `^0.21.3` release of
|
|
51
|
+
[`@cloudflare/vitest-pool-workers`](https://github.com/cloudflare/workers-sdk/tree/main/packages/vitest-pool-workers#readme),
|
|
52
|
+
then import the test-only helpers from `effect-cf/vitest`. The `fetch` runner
|
|
53
|
+
constructs the Worker with the current test environment and waits for all
|
|
54
|
+
`waitUntil` work before completing the Effect.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { assert, it } from "@effect/vitest";
|
|
58
|
+
import { Config, Effect } from "effect";
|
|
59
|
+
import * as PoolWorkers from "effect-cf/vitest";
|
|
60
|
+
|
|
61
|
+
import WorkerEntrypoint from "../src/index";
|
|
62
|
+
|
|
63
|
+
it.effect("serves a request", () =>
|
|
64
|
+
Effect.gen(function* () {
|
|
65
|
+
const response = yield* PoolWorkers.fetch(
|
|
66
|
+
WorkerEntrypoint,
|
|
67
|
+
new Request("https://worker.test/health"),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
assert.strictEqual(response.status, 200);
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
it.effect("reads Wrangler vars", () =>
|
|
75
|
+
Config.string("APP_NAME").pipe(
|
|
76
|
+
Effect.tap((appName) => assert.strictEqual(appName, "test-app")),
|
|
77
|
+
Effect.provide(PoolWorkers.layer),
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Queue consumers can be invoked with typed message bodies while retaining the
|
|
83
|
+
pool's acknowledgement and retry result:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
it.effect("acknowledges a job", () =>
|
|
87
|
+
Effect.gen(function* () {
|
|
88
|
+
const { result } = yield* PoolWorkers.queue(MyQueueConsumer, "jobs", [
|
|
89
|
+
{
|
|
90
|
+
id: "job-1",
|
|
91
|
+
timestamp: new Date(),
|
|
92
|
+
attempts: 1,
|
|
93
|
+
body: { accountId: "account-1" },
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
assert.deepStrictEqual(result.explicitAcks, ["job-1"]);
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Scheduled handlers and Pages Functions use the same lifecycle behavior: their
|
|
103
|
+
Effects complete only after the event's `waitUntil` work has settled. Pages
|
|
104
|
+
tests must configure the `ASSETS` binding required by Pool Workers.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
it.effect("runs the cron handler", () =>
|
|
108
|
+
PoolWorkers.scheduled(worker.scheduled, {
|
|
109
|
+
cron: "30 * * * *",
|
|
110
|
+
scheduledTime: new Date("2030-01-01T00:00:00Z"),
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
it.effect("runs a Pages Function", () =>
|
|
115
|
+
Effect.gen(function* () {
|
|
116
|
+
const response = yield* PoolWorkers.pages(onRequest, {
|
|
117
|
+
request: new Request("https://pages.test/users/dan"),
|
|
118
|
+
params: { user: "dan" },
|
|
119
|
+
data: { authenticated: true },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
assert.strictEqual(response.status, 200);
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`runInDurableObject` carries the test's Effect context into the Durable Object
|
|
128
|
+
I/O context and provides the same wrapped `DurableObjectState` service used by
|
|
129
|
+
production handlers. Typed failures remain in the Effect error channel.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
it.effect("seeds a Durable Object", () =>
|
|
133
|
+
Effect.gen(function* () {
|
|
134
|
+
const stub = env.COUNTERS.getByName("home");
|
|
135
|
+
|
|
136
|
+
yield* PoolWorkers.runInDurableObject(stub, () =>
|
|
137
|
+
Effect.gen(function* () {
|
|
138
|
+
const state = yield* DurableObjectState;
|
|
139
|
+
yield* state.storage.put("count", 41);
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
assert.strictEqual(yield* PoolWorkers.runDurableObjectAlarm(stub), true);
|
|
144
|
+
|
|
145
|
+
// Recreate in-memory state while preserving Durable Object storage.
|
|
146
|
+
yield* PoolWorkers.evictDurableObject(stub);
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The subpath also exposes Effects for `listDurableObjectIds`, `reset`,
|
|
152
|
+
`abortAllDurableObjects`, `evictAllDurableObjects`, and `applyD1Migrations`.
|
|
153
|
+
`adminSecretsStore` returns an Effect-native admin client whose `create`,
|
|
154
|
+
`update`, `duplicate`, `delete`, `list`, and `get` operations are Effects.
|
|
155
|
+
|
|
156
|
+
`withExecutionContext` supports lower-level tests that need a native
|
|
157
|
+
`ExecutionContext`. Workflow introspectors and all of their modifiers and query
|
|
158
|
+
operations are Effect-native. They are disposed with the current Effect scope,
|
|
159
|
+
and modifier callbacks carry the caller's Effect context and typed failures.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
it.effect("controls a Workflow instance", () =>
|
|
163
|
+
Effect.scoped(
|
|
164
|
+
Effect.gen(function* () {
|
|
165
|
+
const instanceId = crypto.randomUUID();
|
|
166
|
+
const instance = yield* PoolWorkers.introspectWorkflowInstance(
|
|
167
|
+
env.REPORT_WORKFLOW,
|
|
168
|
+
instanceId,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
yield* instance.modify((modifier) =>
|
|
172
|
+
Effect.gen(function* () {
|
|
173
|
+
yield* modifier.disableSleeps();
|
|
174
|
+
yield* modifier.mockStepResult({ name: "render-report" }, "reports/test.json");
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
yield* Effect.promise(() =>
|
|
178
|
+
env.REPORT_WORKFLOW.create({
|
|
179
|
+
id: instanceId,
|
|
180
|
+
params: { reportId: "test", requestedBy: "dan@example.com" },
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
yield* instance.waitForStatus("complete");
|
|
185
|
+
assert.deepStrictEqual(yield* instance.getOutput, {
|
|
186
|
+
objectKey: "reports/test.json",
|
|
187
|
+
notified: true,
|
|
188
|
+
});
|
|
189
|
+
}),
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Use `introspectWorkflow` when the instance ID is not known before creation. Its
|
|
195
|
+
`modifyAll` and `get` operations are Effects, and each instance returned by
|
|
196
|
+
`get` uses the same Effect-native interface shown above.
|
|
46
197
|
|
|
47
198
|
## Worker Example
|
|
48
199
|
|
|
@@ -340,7 +491,7 @@ Onboard the sending domain under **Compute > Email Service > Email Sending**, th
|
|
|
340
491
|
}
|
|
341
492
|
```
|
|
342
493
|
|
|
343
|
-
Messages that violate a documented limit fail with `EmailValidationError` carrying every violation, without calling the binding. Use `layer({ binding, send: { validate: false } })` to skip validation and let Cloudflare reject the message instead.
|
|
494
|
+
Messages that violate a documented limit fail with `EmailValidationError` carrying every violation, without calling the binding. Use `layer({ binding, send: { validate: false } })` to skip validation and let Cloudflare reject the message instead.
|
|
344
495
|
|
|
345
496
|
Cloudflare's total message size limit is exposed as `Email.sendLimits.maxMessageBytes` but is not enforced, because the encoded MIME size is only known once Cloudflare composes the message. Oversized messages fail at the binding with `E_CONTENT_TOO_LARGE`.
|
|
346
497
|
|
|
@@ -360,7 +511,6 @@ export const RequestAnalyticsLayer = RequestAnalytics.layer({
|
|
|
360
511
|
binding: "REQUEST_ANALYTICS",
|
|
361
512
|
write: {
|
|
362
513
|
onInvalid: "error",
|
|
363
|
-
batchSize: 100,
|
|
364
514
|
},
|
|
365
515
|
});
|
|
366
516
|
|
|
@@ -375,7 +525,7 @@ export const recordPageView = (request: Request) =>
|
|
|
375
525
|
doubles: [1],
|
|
376
526
|
});
|
|
377
527
|
|
|
378
|
-
yield* analytics.
|
|
528
|
+
yield* analytics.writeDataPoints(
|
|
379
529
|
[
|
|
380
530
|
{
|
|
381
531
|
indexes: [url.hostname],
|
|
@@ -29,7 +29,7 @@ type ScalarConfigKey = Extract<{ readonly [Key in keyof Cloudflare.Env]-?: NonNu
|
|
|
29
29
|
* const program = Effect.gen(function* () {
|
|
30
30
|
* const config = yield* AppConfig;
|
|
31
31
|
* // ...
|
|
32
|
-
* }).pipe(Effect.provide(WorkerConfig.
|
|
32
|
+
* }).pipe(Effect.provide(WorkerConfig.providerLayer));
|
|
33
33
|
* ```
|
|
34
34
|
*
|
|
35
35
|
* Keys are constrained to scalar `Cloudflare.Env` properties (`string`,
|
|
@@ -82,13 +82,11 @@ declare namespace WorkerConfig {
|
|
|
82
82
|
* Cloudflare worker `env` object.
|
|
83
83
|
*/
|
|
84
84
|
const providerLayer: Layer.Layer<never, never, WorkerEnvironment>;
|
|
85
|
-
/** Alias for `providerLayer` for concise use in worker layers. */
|
|
86
|
-
const layer: Layer.Layer<never, never, WorkerEnvironment>;
|
|
87
85
|
/** Build a `ConfigProvider` layer with a custom `env` conversion function. */
|
|
88
86
|
const layerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => Layer.Layer<never, never, WorkerEnvironment>;
|
|
89
87
|
}
|
|
90
88
|
declare namespace Binding_d_exports {
|
|
91
|
-
export { BindingNotFoundError, BindingService, BindingValidationError, Service, TypeId, ValidationOptions, layer
|
|
89
|
+
export { BindingNotFoundError, BindingService, BindingValidationError, Service, TypeId, ValidationOptions, layer };
|
|
92
90
|
}
|
|
93
91
|
/** Internal type id marker used by binding helper services. */
|
|
94
92
|
declare const TypeId: "~effect-cf/Binding";
|
|
@@ -127,43 +125,11 @@ interface BindingService<Self, Id extends string, Service> extends Context.Servi
|
|
|
127
125
|
readonly binding: string;
|
|
128
126
|
readonly layer: Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
129
127
|
}
|
|
130
|
-
declare function layer
|
|
131
|
-
declare function layer
|
|
128
|
+
declare function layer<Self, Resource>(tag: Context.Service<Self, Resource>, binding: string, isResource: (value: unknown) => value is Resource, wrap?: undefined, options?: ValidationOptions): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
129
|
+
declare function layer<Self, Resource, Service>(tag: Context.Service<Self, Service>, binding: string, isResource: (value: unknown) => value is Resource, wrap: (resource: Resource) => Service, options?: ValidationOptions): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
132
130
|
declare const Service: <Self>() => {
|
|
133
131
|
<Id extends string, Resource>(id: Id, binding: string, isResource: (value: unknown) => value is Resource, wrap?: undefined, options?: ValidationOptions): BindingService<Self, Id, Resource>;
|
|
134
132
|
<Id extends string, Resource, Service>(id: Id, binding: string, isResource: (value: unknown) => value is Resource, wrap: (resource: Resource) => Service, options?: ValidationOptions): BindingService<Self, Id, Service>;
|
|
135
133
|
};
|
|
136
|
-
declare namespace Hyperdrive_d_exports {
|
|
137
|
-
export { HyperdriveClient, HyperdriveDefinition, HyperdriveService, LayerOptions, Tag, TagClass, isHyperdrive, layer, make, makeClient };
|
|
138
|
-
}
|
|
139
|
-
/** Typed Hyperdrive binding definition. */
|
|
140
|
-
interface HyperdriveDefinition {
|
|
141
|
-
/** Binding name as configured in `wrangler.jsonc`. */
|
|
142
|
-
readonly binding: string;
|
|
143
|
-
}
|
|
144
|
-
interface HyperdriveClient {
|
|
145
|
-
readonly connectionString: string;
|
|
146
|
-
readonly rawUnsafe: Effect.Effect<Hyperdrive>;
|
|
147
|
-
readonly definition: HyperdriveDefinition;
|
|
148
|
-
}
|
|
149
|
-
declare const HyperdriveServiceTypeId: unique symbol;
|
|
150
|
-
/** Nominal service marker for Hyperdrive services created with {@link make}. */
|
|
151
|
-
interface HyperdriveService<Id extends string> {
|
|
152
|
-
readonly [HyperdriveServiceTypeId]: {
|
|
153
|
-
readonly id: Id;
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
type LayerOptions = {
|
|
157
|
-
readonly binding: string;
|
|
158
|
-
};
|
|
159
|
-
interface TagClass<Self, Id extends string> extends Context.ServiceClass<Self, Id, HyperdriveClient> {
|
|
160
|
-
readonly id: Id;
|
|
161
|
-
readonly layer: (options: LayerOptions) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
162
|
-
}
|
|
163
|
-
declare const isHyperdrive: (value: unknown) => value is Hyperdrive;
|
|
164
|
-
declare const makeClient: (definition: HyperdriveDefinition) => (hyperdrive: Hyperdrive) => HyperdriveClient;
|
|
165
|
-
declare const layer: <Self>(tag: Context.Service<Self, HyperdriveClient>, definition: HyperdriveDefinition) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
166
|
-
declare const make: <Id extends string>(id: Id) => TagClass<HyperdriveService<Id>, Id>;
|
|
167
|
-
declare const Tag: <Self>() => <Id extends string>(id: Id) => TagClass<Self, Id>;
|
|
168
134
|
//#endregion
|
|
169
|
-
export {
|
|
135
|
+
export { Environment_d_exports as a, WorkerEnvironment as c, Binding_d_exports as i, BindingService as n, WorkerConfig as o, BindingValidationError as r, WorkerEnv as s, BindingNotFoundError as t };
|