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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,6 +21,11 @@
21
21
  "import": "./dist/index.mjs",
22
22
  "default": "./dist/index.mjs"
23
23
  },
24
+ "./hyperdrive-pg": {
25
+ "types": "./dist/HyperdrivePg.d.mts",
26
+ "import": "./dist/HyperdrivePg.mjs",
27
+ "default": "./dist/HyperdrivePg.mjs"
28
+ },
24
29
  "./package.json": "./package.json"
25
30
  },
26
31
  "publishConfig": {
@@ -37,6 +42,7 @@
37
42
  "@cloudflare/vitest-pool-workers": "^0.15.0",
38
43
  "@cloudflare/workers-types": "^4.20260421.1",
39
44
  "@effect/sql-d1": "catalog:",
45
+ "@effect/sql-pg": "4.0.0-beta.65",
40
46
  "@effect/vitest": "catalog:",
41
47
  "effect": "catalog:",
42
48
  "typescript": "^6.0.2",
@@ -45,6 +51,12 @@
45
51
  },
46
52
  "peerDependencies": {
47
53
  "@effect/sql-d1": "^4.0.0-beta.65",
54
+ "@effect/sql-pg": "^4.0.0-beta.65",
48
55
  "effect": "^4.0.0-beta.65"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "@effect/sql-pg": {
59
+ "optional": true
60
+ }
49
61
  }
50
62
  }
package/src/Binding.ts CHANGED
@@ -59,6 +59,21 @@ export interface BindingService<Self, Id extends string, Service> extends Contex
59
59
  >;
60
60
  }
61
61
 
62
+ export const layer = <Self, Resource, Service = Resource>(
63
+ tag: Context.Service<Self, Service>,
64
+ binding: string,
65
+ isResource: (value: unknown) => value is Resource,
66
+ wrap?: (resource: Resource) => Service,
67
+ ): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment> =>
68
+ Layer.effect(
69
+ tag,
70
+ Effect.gen(function* () {
71
+ const env = yield* WorkerEnvironment;
72
+ const resource = yield* getBinding(env, binding, isResource);
73
+ return wrap === undefined ? (resource as unknown as Service) : wrap(resource);
74
+ }),
75
+ );
76
+
62
77
  export const Service =
63
78
  <Self>() =>
64
79
  <Id extends string, Resource, Service = Resource>(
@@ -68,19 +83,11 @@ export const Service =
68
83
  wrap?: (resource: Resource) => Service,
69
84
  ): BindingService<Self, Id, Service> => {
70
85
  const tag = Context.Service<Self, Service>()(id);
71
-
72
- const layer = Layer.effect(
73
- tag,
74
- Effect.gen(function* () {
75
- const env = yield* WorkerEnvironment;
76
- const resource = yield* getBinding(env, binding, isResource);
77
- return wrap === undefined ? (resource as unknown as Service) : wrap(resource);
78
- }),
79
- );
86
+ const serviceLayer = layer(tag, binding, isResource, wrap);
80
87
 
81
88
  return Object.assign(tag, {
82
89
  [TypeId]: TypeId,
83
90
  binding,
84
- layer,
91
+ layer: serviceLayer,
85
92
  }) as BindingService<Self, Id, Service>;
86
93
  };
package/src/D1.ts CHANGED
@@ -1,17 +1,10 @@
1
1
  import { D1Client } from "@effect/sql-d1";
2
- import { Data, Effect, Layer } from "effect";
2
+ import { Effect, Layer } from "effect";
3
3
 
4
4
  import * as Binding from "./Binding";
5
5
 
6
6
  const TypeId = "effect-cf/D1" as const;
7
7
 
8
- /** Error raised when a D1 operation fails. */
9
- export class D1OperationError extends Data.TaggedError("D1OperationError")<{
10
- readonly binding: string;
11
- readonly operation: string;
12
- readonly cause: unknown;
13
- }> {}
14
-
15
8
  /** Typed D1 binding definition. */
16
9
  export interface D1Definition {
17
10
  /** Binding name as configured in `wrangler.jsonc`. */
@@ -44,29 +37,6 @@ const isD1Database = (value: unknown): value is D1Database => {
44
37
  );
45
38
  };
46
39
 
47
- const d1Error = (binding: string, operation: string, cause: unknown) =>
48
- new D1OperationError({ binding, operation, cause });
49
-
50
- const tryD1Promise = <A>(
51
- binding: string,
52
- operation: string,
53
- evaluate: () => Promise<A>,
54
- ): Effect.Effect<A, D1OperationError> =>
55
- Effect.tryPromise({
56
- try: evaluate,
57
- catch: (cause) => d1Error(binding, operation, cause),
58
- });
59
-
60
- const tryD1Sync = <A>(
61
- binding: string,
62
- operation: string,
63
- evaluate: () => A,
64
- ): Effect.Effect<A, D1OperationError> =>
65
- Effect.try({
66
- try: evaluate,
67
- catch: (cause) => d1Error(binding, operation, cause),
68
- });
69
-
70
40
  /**
71
41
  * Creates a typed D1 service tag plus Effect helpers.
72
42
  */
@@ -76,9 +46,8 @@ export const make = <Id extends string>(id: Id, definition: D1Definition) =>
76
46
  /**
77
47
  * Builds a D1 service around a Cloudflare D1 database binding.
78
48
  *
79
- * The returned service exposes the raw `D1Database` binding, small native D1
80
- * helpers, and `sqlLayer(...)` for providing `effect/unstable/sql` via
81
- * `@effect/sql-d1`.
49
+ * The returned service exposes the raw `D1Database` binding and `sqlLayer(...)`
50
+ * for providing `effect/unstable/sql` via `@effect/sql-d1`.
82
51
  *
83
52
  * @example
84
53
  * ```ts
@@ -102,34 +71,9 @@ export const Service =
102
71
  }),
103
72
  ).pipe(Layer.provide(tag.layer));
104
73
 
105
- const prepare = Effect.fnUntraced(function* (query: string) {
106
- const db = yield* tag;
107
- return yield* tryD1Sync(definition.binding, "prepare", () => db.prepare(query));
108
- });
109
-
110
- const batch = Effect.fnUntraced(function* <T = unknown>(
111
- statements: ReadonlyArray<D1PreparedStatement>,
112
- ) {
113
- const db = yield* tag;
114
- return yield* tryD1Promise(definition.binding, "batch", () => db.batch<T>([...statements]));
115
- });
116
-
117
- const exec = Effect.fnUntraced(function* (query: string) {
118
- const db = yield* tag;
119
- return yield* tryD1Promise(definition.binding, "exec", () => db.exec(query));
120
- });
121
-
122
- const unsafeRaw = Effect.fnUntraced(function* () {
123
- return yield* tag;
124
- });
125
-
126
74
  return Object.assign(tag, {
127
75
  [TypeId]: TypeId,
128
76
  definition,
129
77
  sqlLayer,
130
- prepare,
131
- batch,
132
- exec,
133
- unsafeRaw,
134
78
  });
135
79
  };
@@ -1,11 +1,15 @@
1
1
  import { DurableObject as CloudflareDurableObject } from "cloudflare:workers";
2
- import { Effect, Layer, ManagedRuntime, type Scope } from "effect";
2
+ import { Effect, Layer, ManagedRuntime, type Context, type Scope } from "effect";
3
+ import type { Schema as S } from "effect";
3
4
 
4
5
  import { NativeRequest } from "./Worker";
5
6
  import { WorkerEnvironment, type WorkerEnv } from "./Environment";
6
7
  import { DurableObjectState, fromDurableObjectState } from "./DurableObjectState";
7
8
  import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
9
+ import type * as Binding from "./Binding";
8
10
  import * as DurableObjectDefinition from "./DurableObjectDefinition";
11
+ import type * as DurableObjectNamespace from "./DurableObjectNamespace";
12
+ import type * as Rpc from "./Rpc";
9
13
  import * as Entrypoint from "./internal/Entrypoint";
10
14
 
11
15
  const reservedMethodNames = new Set<string>([
@@ -208,40 +212,130 @@ export const make = <
208
212
  return Entrypoint.assumeEntrypointClass<DurableObjectClass<Rpc, ROut>>(EffectDurableObject);
209
213
  };
210
214
 
211
- export const Tag = DurableObjectDefinition.Tag;
215
+ export type ServiceFreeSchema = S.Codec<any, any, never, never>;
212
216
 
213
- export const method = DurableObjectDefinition.method;
217
+ export interface Method<
218
+ Args extends ReadonlyArray<ServiceFreeSchema> = ReadonlyArray<ServiceFreeSchema>,
219
+ Success extends ServiceFreeSchema = ServiceFreeSchema,
220
+ > {
221
+ readonly args: Args;
222
+ readonly success: Success;
223
+ }
224
+
225
+ export namespace Method {
226
+ export type Any = Method<ReadonlyArray<ServiceFreeSchema>, ServiceFreeSchema>;
227
+
228
+ type ArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = Args extends readonly []
229
+ ? []
230
+ : Args extends readonly [
231
+ infer Head extends ServiceFreeSchema,
232
+ ...infer Tail extends ReadonlyArray<ServiceFreeSchema>,
233
+ ]
234
+ ? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
235
+ : Array<S.Schema.Type<Args[number]>>;
214
236
 
215
- export const implement = DurableObjectDefinition.implement;
237
+ export type Args<Self extends Any> = ArgsFromSchemas<Self["args"]>;
238
+
239
+ export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
240
+ }
216
241
 
217
- export type Definition<
218
- Id extends string = string,
219
- Methods extends DurableObjectDefinition.Definition.Any["methods"] =
220
- DurableObjectDefinition.Definition.Any["methods"],
221
- > = DurableObjectDefinition.Definition<Id, Methods>;
242
+ export type Methods = Record<string, Method.Any>;
243
+
244
+ export type ReservedMethodName = DurableObjectDefinition.ReservedMethodName;
245
+
246
+ export type NoReservedMethods<MethodsShape extends Methods> =
247
+ Extract<keyof MethodsShape, ReservedMethodName> extends never ? MethodsShape : never;
248
+
249
+ export interface Definition<Id extends string = string, MethodsShape extends Methods = Methods> {
250
+ readonly id: Id;
251
+ readonly methods: MethodsShape;
252
+ }
222
253
 
223
254
  export namespace Definition {
224
- export type Any = DurableObjectDefinition.Definition.Any;
255
+ export type Any = Definition<string, Methods>;
225
256
  }
226
257
 
227
- export type ServerApi<Self extends DurableObjectDefinition.Definition.Any> =
228
- DurableObjectDefinition.ServerApi<Self>;
258
+ export type LayerOptions = DurableObjectDefinition.LayerOptions;
229
259
 
230
- export type Api<Self extends DurableObjectDefinition.Definition.Any> =
231
- DurableObjectDefinition.Api<Self>;
260
+ export type ServerApi<Self extends Definition.Any> = {
261
+ readonly [Key in keyof Self["methods"]]: (
262
+ ...args: Method.Args<Self["methods"][Key]>
263
+ ) => Promise<Method.Success<Self["methods"][Key]>>;
264
+ };
232
265
 
233
- export type Handlers<
234
- ROut,
235
- Self extends DurableObjectDefinition.Definition.Any,
236
- > = DurableObjectDefinition.Handlers<ROut, Self>;
266
+ export type Api<Self extends Definition.Any> = Rpc.Provider<ServerApi<Self>, ReservedMethodName>;
237
267
 
238
- export type Options<
268
+ export type Handlers<ROut, Self extends Definition.Any> = {
269
+ readonly [Key in keyof Self["methods"]]: (
270
+ ...args: Method.Args<Self["methods"][Key]>
271
+ ) => DurableObjectHandler<ROut, Method.Success<Self["methods"][Key]>>;
272
+ };
273
+
274
+ export interface Options<ROut, Self extends Definition.Any> extends Omit<
275
+ DurableObjectOptions<ROut, Handlers<ROut, Self>>,
276
+ "rpc"
277
+ > {
278
+ readonly rpc: Handlers<ROut, Self>;
279
+ }
280
+
281
+ export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Context.ServiceClass<
282
+ Self,
283
+ Id,
284
+ DurableObjectNamespace.DurableObjectNamespaceEffectClient<
285
+ Api<Definition<Id, MethodsShape>>,
286
+ Definition<Id, MethodsShape>
287
+ >
288
+ > &
289
+ DurableObjectNamespace.DurableObjectNamespaceStaticClient<
290
+ Self,
291
+ Api<Definition<Id, MethodsShape>>,
292
+ Definition<Id, MethodsShape>
293
+ > & {
294
+ readonly id: Id;
295
+ readonly methods: MethodsShape;
296
+ readonly make: <ROut, LayerError>(
297
+ layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
298
+ options: Options<ROut, Definition<Id, MethodsShape>>,
299
+ ) => DurableObjectClass<Handlers<ROut, Definition<Id, MethodsShape>>, ROut>;
300
+ readonly layer: (
301
+ options: LayerOptions,
302
+ ) => Layer.Layer<
303
+ Self,
304
+ Binding.BindingNotFoundError | Binding.BindingValidationError,
305
+ WorkerEnvironment
306
+ >;
307
+ };
308
+
309
+ export type TagFactory = <Self>() => <Id extends string, const MethodsShape extends Methods>(
310
+ id: Id,
311
+ methods: MethodsShape & NoReservedMethods<MethodsShape>,
312
+ ) => TagClass<Self, Id, MethodsShape>;
313
+
314
+ export const Tag = DurableObjectDefinition.Tag as unknown as TagFactory;
315
+
316
+ export const method = DurableObjectDefinition.method as {
317
+ <Success extends ServiceFreeSchema>(definition: {
318
+ readonly success: Success;
319
+ }): Method<readonly [], Success>;
320
+ <
321
+ const Args extends ReadonlyArray<ServiceFreeSchema>,
322
+ Success extends ServiceFreeSchema,
323
+ >(definition: {
324
+ readonly args: Args;
325
+ readonly success: Success;
326
+ }): Method<Args, Success>;
327
+ };
328
+
329
+ export const implement = DurableObjectDefinition.implement as unknown as <
239
330
  ROut,
240
- Self extends DurableObjectDefinition.Definition.Any,
241
- > = DurableObjectDefinition.Options<ROut, Self>;
331
+ const Self extends Definition.Any,
332
+ >(
333
+ _definition: Self,
334
+ handlers: Handlers<ROut, Self>,
335
+ ) => Handlers<ROut, Self>;
242
336
 
243
337
  export type HandlerEffect<
244
338
  ROut,
245
- Self extends DurableObjectDefinition.Definition.Any,
339
+ Self extends Definition.Any,
246
340
  Key extends keyof Self["methods"],
247
- > = DurableObjectDefinition.HandlerEffect<ROut, Self, Key>;
341
+ > = DurableObjectHandler<ROut, Method.Success<Self["methods"][Key]>>;