effect-cf 0.6.0 → 0.8.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 +121 -29
- package/dist/Hyperdrive-By6DrSS_.d.mts +140 -0
- package/dist/Hyperdrive-LuQaVhXU.mjs +86 -0
- package/dist/HyperdrivePg.d.mts +14 -0
- package/dist/HyperdrivePg.mjs +13 -0
- package/dist/index.d.mts +1029 -1162
- package/dist/index.mjs +497 -515
- package/package.json +13 -1
- package/src/Binding.ts +17 -10
- package/src/D1.ts +3 -59
- package/src/DurableObject.ts +117 -23
- package/src/DurableObjectDefinition.ts +251 -72
- package/src/DurableObjectNamespace.ts +239 -287
- package/src/Hyperdrive.ts +82 -0
- package/src/HyperdrivePg.ts +27 -0
- package/src/Images.ts +332 -0
- package/src/Kv.ts +152 -240
- package/src/Queue.ts +18 -4
- package/src/QueueBinding.ts +45 -97
- package/src/QueueDefinition.ts +87 -41
- package/src/R2.ts +230 -0
- package/src/ServiceBinding.ts +260 -116
- package/src/Worker.ts +114 -20
- package/src/WorkerDefinition.ts +187 -63
- package/src/Workflow.ts +23 -4
- package/src/WorkflowBinding.ts +100 -109
- package/src/WorkflowDefinition.ts +116 -51
- package/src/index.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# effect-cf
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#22](https://github.com/danieljvdm/effect-cf/pull/22) [`24c27ee`](https://github.com/danieljvdm/effect-cf/commit/24c27ee2884aacc72ab51ab5a208b795e1fa9738) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Add Effect-native R2 bucket, Hyperdrive, and Cloudflare Images binding tags, including optional hosted Images operations, ArrayBuffer image inputs, and an optional Hyperdrive Postgres SQL layer integration.
|
|
8
|
+
|
|
9
|
+
- [#22](https://github.com/danieljvdm/effect-cf/pull/22) [`24c27ee`](https://github.com/danieljvdm/effect-cf/commit/24c27ee2884aacc72ab51ab5a208b795e1fa9738) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Remove non-canonical static operation helpers from existing KV and D1 resource bindings. Use yielded binding services for resource operations; D1 keeps `sqlLayer()`.
|
|
10
|
+
|
|
11
|
+
## 0.7.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- [#20](https://github.com/danieljvdm/effect-cf/pull/20) [`f57a59c`](https://github.com/danieljvdm/effect-cf/commit/f57a59c14d317438348b36ac0341dc921fe72be2) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Tighten binding APIs around Cloudflare RPC and the single-tag KV model. `rpc` now exposes the raw Cloudflare RPC result, while `call` and `scopedCall` resolve and decode definition-backed success values. Durable Object static direct helpers now keep the namespace layer requirement in their effect environment, and the old concrete `Kv.make` / `Kv.Service` constructors have been removed in favor of `Kv.Tag(...).layer({ binding })`.
|
|
16
|
+
|
|
17
|
+
- [#19](https://github.com/danieljvdm/effect-cf/pull/19) [`37b4883`](https://github.com/danieljvdm/effect-cf/commit/37b4883de9790df151ddbb16f2fd432b2d4348b5) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Replace separate binding classes with a single exported tag class API for Queues, Workflows, KV namespaces, Worker service bindings, and Durable Object namespaces. These tags now expose `layer({ binding })` directly, consumers use `const service = yield* Service`, and the old definition `.Binding(...)` / `.binding(...)` / `.Namespace(...)` / `.namespace(...)` helpers have been removed.
|
|
18
|
+
|
|
3
19
|
## 0.6.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -32,20 +32,22 @@ Runtime creation belongs at Cloudflare entrypoints, not inside binding helpers.
|
|
|
32
32
|
- `DurableObjectWebSocket` - WebSocket upgrade helpers for Durable Objects
|
|
33
33
|
- `Kv` - typed KV namespace helper
|
|
34
34
|
- `D1` - typed D1 database binding helper with an `@effect/sql-d1` backed SQL layer
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
35
|
+
- `R2` - typed R2 bucket binding helper with Effect-wrapped object and multipart operations
|
|
36
|
+
- `Hyperdrive` - typed Hyperdrive binding helper for connection strings and optional Postgres SQL integration
|
|
37
|
+
- `Images` - typed Cloudflare Images binding helper with transformation APIs and optional hosted image operations
|
|
38
|
+
- `Queue` - typed Queue producer/consumer tags plus client and error types
|
|
39
|
+
- `Workflow` - typed Workflow entrypoints, steps, starter clients, and instance types
|
|
37
40
|
- `Rpc` - Cloudflare RPC type helpers and scoped disposal utilities
|
|
38
41
|
- `WorkerConfig` - Effect `Config` helpers backed by Cloudflare `env`
|
|
39
42
|
|
|
40
43
|
## Worker Example
|
|
41
44
|
|
|
42
45
|
```ts
|
|
43
|
-
import {
|
|
46
|
+
import { Effect, Layer } from "effect";
|
|
47
|
+
import { HttpServerResponse } from "effect/unstable/http";
|
|
44
48
|
import { Worker } from "effect-cf";
|
|
45
49
|
|
|
46
|
-
export default Worker.make(
|
|
47
|
-
fetch: () => HttpServerResponse.text("ok"),
|
|
48
|
-
});
|
|
50
|
+
export default Worker.make(Layer.empty, Effect.succeed(HttpServerResponse.text("ok")));
|
|
49
51
|
```
|
|
50
52
|
|
|
51
53
|
## Durable Object Example
|
|
@@ -70,27 +72,30 @@ export const CounterDurableObject = Counter.make(Layer.empty, {
|
|
|
70
72
|
}),
|
|
71
73
|
},
|
|
72
74
|
});
|
|
75
|
+
|
|
76
|
+
export const CounterLayer = Counter.layer({ binding: "COUNTER" });
|
|
77
|
+
|
|
78
|
+
export const readCounter = Effect.gen(function* () {
|
|
79
|
+
const counters = yield* Counter;
|
|
80
|
+
return yield* counters.byName("home").get();
|
|
81
|
+
});
|
|
73
82
|
```
|
|
74
83
|
|
|
75
|
-
Define Wrangler bindings and migrations in the consuming application.
|
|
84
|
+
Define Wrangler bindings and migrations in the consuming application. Durable Object namespace bindings are provided with `YourObject.layer({ binding })`, and consumers use `const namespace = yield* YourObject`.
|
|
76
85
|
|
|
77
86
|
## Queue Example
|
|
78
87
|
|
|
79
|
-
Queues
|
|
88
|
+
Queues define the message contract once. The same class is the producer service tag and the consumer Worker definition: use `YourQueue.layer({ binding })` to provide a Cloudflare producer binding from `WorkerEnvironment`, and use `.make(...)` for the consumer Worker entrypoint.
|
|
80
89
|
|
|
81
90
|
```ts
|
|
82
91
|
import { Effect, Layer, Schema as S } from "effect";
|
|
83
92
|
import { Queue } from "effect-cf";
|
|
84
93
|
|
|
85
|
-
class
|
|
94
|
+
class AvatarQueue extends Queue.Tag<AvatarQueue>()("AvatarQueue", {
|
|
86
95
|
message: S.Struct({ userId: S.String, imageKey: S.String }),
|
|
87
96
|
}) {}
|
|
88
97
|
|
|
89
|
-
export const
|
|
90
|
-
binding: "AVATAR_QUEUE",
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
export const AvatarQueueConsumer = AvatarJobs.make(Layer.empty, {
|
|
98
|
+
export const AvatarQueueConsumer = AvatarQueue.make(Layer.empty, {
|
|
94
99
|
queue: (batch) =>
|
|
95
100
|
Effect.gen(function* () {
|
|
96
101
|
for (const message of batch.messages) {
|
|
@@ -100,6 +105,8 @@ export const AvatarQueueConsumer = AvatarJobs.make(Layer.empty, {
|
|
|
100
105
|
}),
|
|
101
106
|
});
|
|
102
107
|
|
|
108
|
+
export const AvatarQueueLayer = AvatarQueue.layer({ binding: "AVATAR_QUEUE" });
|
|
109
|
+
|
|
103
110
|
export const enqueueAvatar = (userId: string, imageKey: string) =>
|
|
104
111
|
Effect.gen(function* () {
|
|
105
112
|
const queue = yield* AvatarQueue;
|
|
@@ -107,10 +114,98 @@ export const enqueueAvatar = (userId: string, imageKey: string) =>
|
|
|
107
114
|
});
|
|
108
115
|
```
|
|
109
116
|
|
|
110
|
-
Producers should usually use `const queue = yield* AvatarQueue` and then call `queue.send(...)`, `queue.sendBatch(...)`, or `queue.metrics()`. The static `AvatarQueue.send(...)` helpers remain available for
|
|
117
|
+
Producers should usually use `const queue = yield* AvatarQueue` and then call `queue.send(...)`, `queue.sendBatch(...)`, or `queue.metrics()`. The static `AvatarQueue.send(...)` helpers remain available for concise one-off calls.
|
|
111
118
|
|
|
112
119
|
Queue handlers run inline failures through Cloudflare's normal retry path. If background work scheduled with `WorkerContext.waitUntil(...)` should also make the batch retry, use `WorkerContext.waitUntilPropagating(...)` or `waitUntil(..., { mode: "propagate" })`; the default `waitUntil` mode observes and logs failures without rejecting the native `waitUntil` promise.
|
|
113
120
|
|
|
121
|
+
## R2 Example
|
|
122
|
+
|
|
123
|
+
R2 bucket tags expose Cloudflare object operations as Effects and map nullable reads to `Option`.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { Effect, Layer, Option } from "effect";
|
|
127
|
+
import { R2 } from "effect-cf";
|
|
128
|
+
|
|
129
|
+
class ArtifactBucket extends R2.Tag<ArtifactBucket>()("ArtifactBucket") {}
|
|
130
|
+
|
|
131
|
+
export const ArtifactBucketLayer = ArtifactBucket.layer({ binding: "ARTIFACT_BUCKET" });
|
|
132
|
+
|
|
133
|
+
export const writeArtifact = (key: string, body: string) =>
|
|
134
|
+
Effect.gen(function* () {
|
|
135
|
+
const bucket = yield* ArtifactBucket;
|
|
136
|
+
yield* bucket.put(key, body, {
|
|
137
|
+
httpMetadata: { contentType: "application/json" },
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export const readArtifact = (key: string) =>
|
|
142
|
+
Effect.gen(function* () {
|
|
143
|
+
const bucket = yield* ArtifactBucket;
|
|
144
|
+
const object = yield* bucket.get(key);
|
|
145
|
+
return Option.isSome(object) ? yield* Effect.promise(() => object.value.text()) : undefined;
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Use `createMultipartUpload(...)` or `resumeMultipartUpload(...)` for large objects; returned upload handles wrap `uploadPart`, `complete`, and `abort` in Effect.
|
|
150
|
+
|
|
151
|
+
## Hyperdrive Example
|
|
152
|
+
|
|
153
|
+
Hyperdrive tags expose the binding `connectionString` directly on the yielded service.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { Effect } from "effect";
|
|
157
|
+
import { Hyperdrive } from "effect-cf";
|
|
158
|
+
|
|
159
|
+
class AppDatabase extends Hyperdrive.Tag<AppDatabase>()("AppDatabase") {}
|
|
160
|
+
|
|
161
|
+
export const AppDatabaseLayer = AppDatabase.layer({ binding: "HYPERDRIVE" });
|
|
162
|
+
|
|
163
|
+
export const databaseUrl = Effect.gen(function* () {
|
|
164
|
+
const hyperdrive = yield* AppDatabase;
|
|
165
|
+
return hyperdrive.connectionString;
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
If your Worker uses Postgres via `@effect/sql-pg`, install that driver and use the optional subpath integration:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { Hyperdrive } from "effect-cf";
|
|
173
|
+
import * as HyperdrivePg from "effect-cf/hyperdrive-pg";
|
|
174
|
+
|
|
175
|
+
class AppDatabase extends Hyperdrive.Tag<AppDatabase>()("AppDatabase") {}
|
|
176
|
+
|
|
177
|
+
export const SqlLive = HyperdrivePg.layer(AppDatabase, { binding: "HYPERDRIVE" });
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The Postgres integration builds an Effect `PgClient` from Hyperdrive's generated connection string with `PgClient.makeClient`. It intentionally does not expose app-side pool options; Hyperdrive manages the underlying database pool.
|
|
181
|
+
|
|
182
|
+
## Images Example
|
|
183
|
+
|
|
184
|
+
Images tags expose `info`, `input`, optional hosted image operations, and composable transform/draw steps.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { Effect } from "effect";
|
|
188
|
+
import { Images } from "effect-cf";
|
|
189
|
+
|
|
190
|
+
class AvatarImages extends Images.Tag<AvatarImages>()("AvatarImages") {}
|
|
191
|
+
|
|
192
|
+
export const AvatarImagesLayer = AvatarImages.layer({ binding: "IMAGES" });
|
|
193
|
+
|
|
194
|
+
export const resizeAvatar = (image: Images.ImageInputValue) =>
|
|
195
|
+
Effect.gen(function* () {
|
|
196
|
+
const images = yield* AvatarImages;
|
|
197
|
+
const result = yield* images.process(
|
|
198
|
+
Images.transform(Images.empty, { width: 256, height: 256 }),
|
|
199
|
+
{
|
|
200
|
+
stream: image,
|
|
201
|
+
outputOptions: { format: "image/webp" },
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
return yield* result.response;
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
114
209
|
## Workflow Example
|
|
115
210
|
|
|
116
211
|
Workflow definitions type the payload and result. Runtime handlers can access `Workflow.WorkflowEvent`, use durable `Workflow.step(...)`, and use normal binding services inside steps.
|
|
@@ -136,12 +231,10 @@ export const ExportWorkflowEntrypoint = ExportWorkflow.make(Layer.empty, {
|
|
|
136
231
|
}),
|
|
137
232
|
});
|
|
138
233
|
|
|
139
|
-
export const
|
|
140
|
-
binding: "EXPORT_WORKFLOW",
|
|
141
|
-
});
|
|
234
|
+
export const ExportWorkflowLayer = ExportWorkflow.layer({ binding: "EXPORT_WORKFLOW" });
|
|
142
235
|
```
|
|
143
236
|
|
|
144
|
-
|
|
237
|
+
Provide `ExportWorkflow.layer({ binding: "EXPORT_WORKFLOW" })`, then use `const workflow = yield* ExportWorkflow` or the static `ExportWorkflow.create(...)`, `createBatch(...)`, and `get(...)` helpers to start and inspect instances.
|
|
145
238
|
|
|
146
239
|
In definition-backed workflows, the `payload` argument is the typed decoded payload and is the source of truth. `Workflow.WorkflowEvent.payload` is also re-provided decoded for convenience; `Workflow.WorkflowEvent.raw.payload` remains the native Cloudflare event payload.
|
|
147
240
|
|
|
@@ -193,24 +286,23 @@ export const RoomLive = DurableObject.make(layer, {
|
|
|
193
286
|
|
|
194
287
|
Schema-backed attachments can rehydrate hibernated sockets:
|
|
195
288
|
|
|
196
|
-
```
|
|
197
|
-
const restored =
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
onInvalid: "ignore-and-close",
|
|
202
|
-
});
|
|
289
|
+
```text
|
|
290
|
+
const restored = yield* Attachments.rehydrate({
|
|
291
|
+
tag: "room:general",
|
|
292
|
+
onInvalid: "ignore-and-close",
|
|
293
|
+
});
|
|
203
294
|
|
|
204
295
|
for (const { socket, attachment } of restored) {
|
|
205
|
-
yield
|
|
296
|
+
yield* socket.send(`restored:${attachment.id}`).pipe(Effect.ignore);
|
|
206
297
|
}
|
|
207
298
|
```
|
|
208
299
|
|
|
209
300
|
Worker-to-Durable-Object forwarding should stay native so WebSocket upgrade responses are preserved:
|
|
210
301
|
|
|
211
|
-
```
|
|
302
|
+
```text
|
|
212
303
|
if (Worker.isWebSocketUpgrade(request)) {
|
|
213
|
-
|
|
304
|
+
const rooms = yield* ChatRoom;
|
|
305
|
+
return yield* rooms.byName(roomId).fetch(request);
|
|
214
306
|
}
|
|
215
307
|
```
|
|
216
308
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Config, ConfigProvider, Context, Effect, Layer } from "effect";
|
|
2
|
+
import * as _$effect_Types0 from "effect/Types";
|
|
3
|
+
import * as _$effect_Cause0 from "effect/Cause";
|
|
4
|
+
import * as _$effect_Redacted0 from "effect/Redacted";
|
|
5
|
+
import * as _$effect_Layer0 from "effect/Layer";
|
|
6
|
+
|
|
7
|
+
//#region src/Environment.d.ts
|
|
8
|
+
/** Cloudflare worker environment object (`env`). */
|
|
9
|
+
type WorkerEnv = Cloudflare.Env;
|
|
10
|
+
declare const WorkerEnvironment_base: Context.ServiceClass<WorkerEnvironment, "effect-cf/WorkerEnvironment", Cloudflare.Env>;
|
|
11
|
+
/**
|
|
12
|
+
* Context service for reading worker bindings from the current `env` object.
|
|
13
|
+
*/
|
|
14
|
+
declare class WorkerEnvironment extends WorkerEnvironment_base {}
|
|
15
|
+
type ScalarConfigValue = string | number | boolean;
|
|
16
|
+
type ScalarConfigKey = Extract<{ readonly [Key in keyof Cloudflare.Env]-?: NonNullable<Cloudflare.Env[Key]> extends ScalarConfigValue ? Key : never }[keyof Cloudflare.Env], string>;
|
|
17
|
+
/**
|
|
18
|
+
* Effect `Config` helpers for scalar Cloudflare vars and secrets declared on
|
|
19
|
+
* `Cloudflare.Env` / generated `worker-configuration.d.ts`.
|
|
20
|
+
*
|
|
21
|
+
* Users still author their app config explicitly with Effect `Config`:
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { Config, Effect } from "effect";
|
|
25
|
+
* import { WorkerConfig } from "effect-cf";
|
|
26
|
+
*
|
|
27
|
+
* const AppConfig = Config.all({
|
|
28
|
+
* databaseUrl: WorkerConfig.redacted("DATABASE_URL"),
|
|
29
|
+
* port: WorkerConfig.integer("PORT").pipe(Config.withDefault(8787)),
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* const program = Effect.gen(function* () {
|
|
33
|
+
* const config = yield* AppConfig;
|
|
34
|
+
* // ...
|
|
35
|
+
* }).pipe(Effect.provide(WorkerConfig.layer));
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Keys are constrained to scalar `Cloudflare.Env` properties (`string`,
|
|
39
|
+
* `number`, or `boolean`, including optional scalar properties). Binding
|
|
40
|
+
* objects such as `KVNamespace`, `DurableObjectNamespace`, and service
|
|
41
|
+
* bindings are intentionally excluded; keep using the package binding helpers
|
|
42
|
+
* for those live resources.
|
|
43
|
+
*/
|
|
44
|
+
declare namespace WorkerConfig {
|
|
45
|
+
/** Scalar env value types supported by the typed config key helpers. */
|
|
46
|
+
type Scalar = ScalarConfigValue;
|
|
47
|
+
/** Scalar keys available on the current consumer's `Cloudflare.Env`. */
|
|
48
|
+
type Key = ScalarConfigKey;
|
|
49
|
+
/** Read a scalar Cloudflare var or secret as a string. */
|
|
50
|
+
const string: <const Name extends Key>(name: Name) => Config.Config<string>;
|
|
51
|
+
/** Read a scalar Cloudflare secret as a redacted string. */
|
|
52
|
+
const redacted: <const Name extends Key>(name: Name) => Config.Config<_$effect_Redacted0.Redacted<string>>;
|
|
53
|
+
/** Read a scalar Cloudflare var or secret as a number. */
|
|
54
|
+
const number: <const Name extends Key>(name: Name) => Config.Config<number>;
|
|
55
|
+
/** Read a scalar Cloudflare var or secret as an integer. */
|
|
56
|
+
const integer: <const Name extends Key>(name: Name) => Config.Config<number>;
|
|
57
|
+
/** Read a scalar Cloudflare var or secret as a boolean. */
|
|
58
|
+
const boolean: <const Name extends Key>(name: Name) => Config.Config<boolean>;
|
|
59
|
+
/** Build a `ConfigProvider` from a Cloudflare worker `env` object. */
|
|
60
|
+
const providerFromEnv: (env: WorkerEnv) => ConfigProvider.ConfigProvider;
|
|
61
|
+
/**
|
|
62
|
+
* Build a `ConfigProvider` from the current `WorkerEnvironment` with a custom
|
|
63
|
+
* conversion function.
|
|
64
|
+
*/
|
|
65
|
+
const providerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => Effect.Effect<ConfigProvider.ConfigProvider, never, WorkerEnvironment>;
|
|
66
|
+
/** Build a `ConfigProvider` from the current `WorkerEnvironment`. */
|
|
67
|
+
const provider: Effect.Effect<ConfigProvider.ConfigProvider, never, WorkerEnvironment>;
|
|
68
|
+
/**
|
|
69
|
+
* Replace the active Effect `ConfigProvider` with one backed by the current
|
|
70
|
+
* Cloudflare worker `env` object.
|
|
71
|
+
*/
|
|
72
|
+
const providerLayer: _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
|
|
73
|
+
/** Alias for `providerLayer` for concise use in worker layers. */
|
|
74
|
+
const layer: _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
|
|
75
|
+
/** Build a `ConfigProvider` layer with a custom `env` conversion function. */
|
|
76
|
+
const layerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
|
|
77
|
+
}
|
|
78
|
+
declare namespace Binding_d_exports {
|
|
79
|
+
export { BindingNotFoundError, BindingService, BindingValidationError, Service, TypeId, layer$1 as layer };
|
|
80
|
+
}
|
|
81
|
+
/** Internal type id marker used by binding helper services. */
|
|
82
|
+
declare const TypeId: "effect-cf/Binding";
|
|
83
|
+
declare const BindingNotFoundError_base: new <A extends Record<string, any> = {}>(args: _$effect_Types0.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => _$effect_Cause0.YieldableError & {
|
|
84
|
+
readonly _tag: "BindingNotFoundError";
|
|
85
|
+
} & Readonly<A>;
|
|
86
|
+
/** Error raised when a configured binding does not exist on `env`. */
|
|
87
|
+
declare class BindingNotFoundError extends BindingNotFoundError_base<{
|
|
88
|
+
readonly binding: string;
|
|
89
|
+
}> {}
|
|
90
|
+
declare const BindingValidationError_base: new <A extends Record<string, any> = {}>(args: _$effect_Types0.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => _$effect_Cause0.YieldableError & {
|
|
91
|
+
readonly _tag: "BindingValidationError";
|
|
92
|
+
} & Readonly<A>;
|
|
93
|
+
/** Error raised when a binding exists but does not match the expected shape. */
|
|
94
|
+
declare class BindingValidationError extends BindingValidationError_base<{
|
|
95
|
+
readonly binding: string;
|
|
96
|
+
}> {}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a Context tag + layer for reading and validating a Cloudflare binding.
|
|
99
|
+
*/
|
|
100
|
+
interface BindingService<Self, Id extends string, Service> extends Context.ServiceClass<Self, Id, Service> {
|
|
101
|
+
readonly [TypeId]: typeof TypeId;
|
|
102
|
+
readonly binding: string;
|
|
103
|
+
readonly layer: Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
104
|
+
}
|
|
105
|
+
declare const layer$1: <Self, Resource, Service = Resource>(tag: Context.Service<Self, Service>, binding: string, isResource: (value: unknown) => value is Resource, wrap?: (resource: Resource) => Service) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
106
|
+
declare const Service: <Self>() => <Id extends string, Resource, Service = Resource>(id: Id, binding: string, isResource: (value: unknown) => value is Resource, wrap?: (resource: Resource) => Service) => BindingService<Self, Id, Service>;
|
|
107
|
+
declare namespace Hyperdrive_d_exports {
|
|
108
|
+
export { HyperdriveClient, HyperdriveDefinition, HyperdriveService, LayerOptions, Tag, TagClass, isHyperdrive, layer, make, makeClient };
|
|
109
|
+
}
|
|
110
|
+
/** Typed Hyperdrive binding definition. */
|
|
111
|
+
interface HyperdriveDefinition {
|
|
112
|
+
/** Binding name as configured in `wrangler.jsonc`. */
|
|
113
|
+
readonly binding: string;
|
|
114
|
+
}
|
|
115
|
+
interface HyperdriveClient {
|
|
116
|
+
readonly connectionString: string;
|
|
117
|
+
readonly unsafeRaw: Effect.Effect<Hyperdrive>;
|
|
118
|
+
readonly definition: HyperdriveDefinition;
|
|
119
|
+
}
|
|
120
|
+
declare const HyperdriveServiceTypeId: unique symbol;
|
|
121
|
+
/** Nominal service marker for Hyperdrive services created with {@link make}. */
|
|
122
|
+
interface HyperdriveService<Id extends string> {
|
|
123
|
+
readonly [HyperdriveServiceTypeId]: {
|
|
124
|
+
readonly id: Id;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
type LayerOptions = {
|
|
128
|
+
readonly binding: string;
|
|
129
|
+
};
|
|
130
|
+
interface TagClass<Self, Id extends string> extends Context.ServiceClass<Self, Id, HyperdriveClient> {
|
|
131
|
+
readonly id: Id;
|
|
132
|
+
readonly layer: (options: LayerOptions) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
133
|
+
}
|
|
134
|
+
declare const isHyperdrive: (value: unknown) => value is Hyperdrive;
|
|
135
|
+
declare const makeClient: (definition: HyperdriveDefinition) => (hyperdrive: Hyperdrive) => HyperdriveClient;
|
|
136
|
+
declare const layer: <Self>(tag: Context.Service<Self, HyperdriveClient>, definition: HyperdriveDefinition) => Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
|
|
137
|
+
declare const make: <Id extends string>(id: Id) => TagClass<HyperdriveService<Id>, Id>;
|
|
138
|
+
declare const Tag: <Self>() => <Id extends string>(id: Id) => TagClass<Self, Id>;
|
|
139
|
+
//#endregion
|
|
140
|
+
export { BindingService as a, WorkerConfig as c, BindingNotFoundError as i, WorkerEnv as l, LayerOptions as n, BindingValidationError as o, TagClass as r, Binding_d_exports as s, Hyperdrive_d_exports as t, WorkerEnvironment as u };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
|
+
import { Config, ConfigProvider, Context, Data, Effect, Layer } from "effect";
|
|
3
|
+
//#region src/Environment.ts
|
|
4
|
+
/**
|
|
5
|
+
* Context service for reading worker bindings from the current `env` object.
|
|
6
|
+
*/
|
|
7
|
+
var WorkerEnvironment = class extends Context.Service()("effect-cf/WorkerEnvironment") {};
|
|
8
|
+
let WorkerConfig;
|
|
9
|
+
(function(_WorkerConfig) {
|
|
10
|
+
_WorkerConfig.string = (name) => Config.string(name);
|
|
11
|
+
_WorkerConfig.redacted = (name) => Config.redacted(name);
|
|
12
|
+
_WorkerConfig.number = (name) => Config.number(name);
|
|
13
|
+
_WorkerConfig.integer = (name) => Config.int(name);
|
|
14
|
+
_WorkerConfig.boolean = (name) => Config.boolean(name);
|
|
15
|
+
const providerFromEnv = _WorkerConfig.providerFromEnv = (env) => ConfigProvider.fromUnknown(env);
|
|
16
|
+
const providerWith = _WorkerConfig.providerWith = (makeProvider) => Effect.map(WorkerEnvironment.asEffect(), makeProvider);
|
|
17
|
+
const provider = _WorkerConfig.provider = providerWith(providerFromEnv);
|
|
18
|
+
_WorkerConfig.layer = _WorkerConfig.providerLayer = ConfigProvider.layer(provider);
|
|
19
|
+
_WorkerConfig.layerWith = (makeProvider) => ConfigProvider.layer(providerWith(makeProvider));
|
|
20
|
+
})(WorkerConfig || (WorkerConfig = {}));
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/Binding.ts
|
|
23
|
+
var Binding_exports = /* @__PURE__ */ __exportAll({
|
|
24
|
+
BindingNotFoundError: () => BindingNotFoundError,
|
|
25
|
+
BindingValidationError: () => BindingValidationError,
|
|
26
|
+
Service: () => Service,
|
|
27
|
+
TypeId: () => TypeId,
|
|
28
|
+
layer: () => layer$1
|
|
29
|
+
});
|
|
30
|
+
/** Internal type id marker used by binding helper services. */
|
|
31
|
+
const TypeId = "effect-cf/Binding";
|
|
32
|
+
/** Error raised when a configured binding does not exist on `env`. */
|
|
33
|
+
var BindingNotFoundError = class extends Data.TaggedError("BindingNotFoundError") {};
|
|
34
|
+
/** Error raised when a binding exists but does not match the expected shape. */
|
|
35
|
+
var BindingValidationError = class extends Data.TaggedError("BindingValidationError") {};
|
|
36
|
+
const isPropertyTarget = (value) => (typeof value === "object" || typeof value === "function") && value !== null;
|
|
37
|
+
const getBinding = (env, binding, isResource) => Effect.gen(function* () {
|
|
38
|
+
if (!isPropertyTarget(env)) return yield* Effect.fail(new BindingValidationError({ binding }));
|
|
39
|
+
const resource = Reflect.get(env, binding);
|
|
40
|
+
if (resource === void 0) return yield* Effect.fail(new BindingNotFoundError({ binding }));
|
|
41
|
+
if (!isResource(resource)) return yield* Effect.fail(new BindingValidationError({ binding }));
|
|
42
|
+
return resource;
|
|
43
|
+
});
|
|
44
|
+
const layer$1 = (tag, binding, isResource, wrap) => Layer.effect(tag, Effect.gen(function* () {
|
|
45
|
+
const resource = yield* getBinding(yield* WorkerEnvironment, binding, isResource);
|
|
46
|
+
return wrap === void 0 ? resource : wrap(resource);
|
|
47
|
+
}));
|
|
48
|
+
const Service = () => (id, binding, isResource, wrap) => {
|
|
49
|
+
const tag = Context.Service()(id);
|
|
50
|
+
const serviceLayer = layer$1(tag, binding, isResource, wrap);
|
|
51
|
+
return Object.assign(tag, {
|
|
52
|
+
[TypeId]: TypeId,
|
|
53
|
+
binding,
|
|
54
|
+
layer: serviceLayer
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/Hyperdrive.ts
|
|
59
|
+
var Hyperdrive_exports = /* @__PURE__ */ __exportAll({
|
|
60
|
+
Tag: () => Tag,
|
|
61
|
+
isHyperdrive: () => isHyperdrive,
|
|
62
|
+
layer: () => layer,
|
|
63
|
+
make: () => make,
|
|
64
|
+
makeClient: () => makeClient
|
|
65
|
+
});
|
|
66
|
+
const isHyperdrive = (value) => {
|
|
67
|
+
if (typeof value !== "object" || value === null) return false;
|
|
68
|
+
return typeof value.connectionString === "string";
|
|
69
|
+
};
|
|
70
|
+
const makeClient = (definition) => (hyperdrive) => ({
|
|
71
|
+
definition,
|
|
72
|
+
connectionString: hyperdrive.connectionString,
|
|
73
|
+
unsafeRaw: Effect.succeed(hyperdrive)
|
|
74
|
+
});
|
|
75
|
+
const layer = (tag, definition) => layer$1(tag, definition.binding, isHyperdrive, makeClient(definition));
|
|
76
|
+
const make = (id) => Tag()(id);
|
|
77
|
+
const Tag = () => (id) => {
|
|
78
|
+
const tag = Context.Service()(id);
|
|
79
|
+
const makeLayer = (definition) => layer(tag, definition);
|
|
80
|
+
return Object.assign(tag, {
|
|
81
|
+
id,
|
|
82
|
+
layer: makeLayer
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
//#endregion
|
|
86
|
+
export { WorkerConfig as a, layer$1 as i, Binding_exports as n, WorkerEnvironment as o, Service as r, Hyperdrive_exports as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { i as BindingNotFoundError, n as LayerOptions, o as BindingValidationError, r as TagClass, u as WorkerEnvironment } from "./Hyperdrive-By6DrSS_.mjs";
|
|
2
|
+
import { Layer } from "effect";
|
|
3
|
+
import { PgClient } from "@effect/sql-pg";
|
|
4
|
+
import * as _$effect_unstable_sql_SqlClient0 from "effect/unstable/sql/SqlClient";
|
|
5
|
+
import * as _$effect_unstable_sql_SqlError0 from "effect/unstable/sql/SqlError";
|
|
6
|
+
import * as _$effect_Scope0 from "effect/Scope";
|
|
7
|
+
|
|
8
|
+
//#region src/HyperdrivePg.d.ts
|
|
9
|
+
type PgLayerOptions = Omit<PgClient.PgClientConfig & {
|
|
10
|
+
readonly acquireForStream?: boolean;
|
|
11
|
+
}, "url" | "host" | "port" | "path" | "ssl" | "database" | "username" | "password" | "stream">;
|
|
12
|
+
declare const layer: <Self, Id extends string>(tag: TagClass<Self, Id>, binding: LayerOptions, options?: PgLayerOptions) => Layer.Layer<PgClient.PgClient | _$effect_unstable_sql_SqlClient0.SqlClient, BindingNotFoundError | BindingValidationError | _$effect_unstable_sql_SqlError0.SqlError, WorkerEnvironment | Exclude<Exclude<Self, _$effect_Scope0.Scope>, Self>>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { PgLayerOptions, layer };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import "./Hyperdrive-LuQaVhXU.mjs";
|
|
2
|
+
import { Effect, Layer, Redacted } from "effect";
|
|
3
|
+
import { PgClient } from "@effect/sql-pg";
|
|
4
|
+
//#region src/HyperdrivePg.ts
|
|
5
|
+
const layer = (tag, binding, options) => Layer.unwrap(Effect.gen(function* () {
|
|
6
|
+
const hyperdrive = yield* tag;
|
|
7
|
+
return PgClient.layerFrom(PgClient.makeClient({
|
|
8
|
+
...options,
|
|
9
|
+
url: Redacted.make(hyperdrive.connectionString)
|
|
10
|
+
}));
|
|
11
|
+
})).pipe(Layer.provide(tag.layer(binding)));
|
|
12
|
+
//#endregion
|
|
13
|
+
export { layer };
|