effect-cf 0.20.1 → 0.23.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +27 -3
  3. package/dist/{Hyperdrive-RTjgirMV.d.mts → Hyperdrive-COyg7R26.d.mts} +40 -13
  4. package/dist/HyperdrivePg.d.mts +13 -5
  5. package/dist/HyperdrivePg.mjs +12 -7
  6. package/dist/index.d.mts +1113 -922
  7. package/dist/index.mjs +980 -604
  8. package/package.json +10 -5
  9. package/src/AiGateway.ts +82 -47
  10. package/src/AnalyticsEngine.ts +74 -58
  11. package/src/Binding.ts +66 -13
  12. package/src/BrowserRendering.ts +30 -7
  13. package/src/Cache.ts +32 -8
  14. package/src/CloudflareOtlp.ts +32 -9
  15. package/src/ContainerNamespace.ts +251 -80
  16. package/src/D1.ts +13 -3
  17. package/src/DurableObject.ts +27 -164
  18. package/src/DurableObjectAlarm.ts +30 -9
  19. package/src/DurableObjectDefinition.ts +58 -54
  20. package/src/DurableObjectNamespace.ts +99 -96
  21. package/src/DurableObjectRpcWebSocket.ts +14 -6
  22. package/src/DurableObjectSqlite.ts +6 -3
  23. package/src/DurableObjectState.ts +63 -39
  24. package/src/DurableObjectStorage.ts +7 -1
  25. package/src/DurableObjectWebSocket.ts +51 -41
  26. package/src/Email.ts +40 -13
  27. package/src/Environment.ts +26 -6
  28. package/src/Hyperdrive.ts +2 -2
  29. package/src/HyperdrivePg.ts +25 -13
  30. package/src/Images.ts +77 -82
  31. package/src/Kv.ts +37 -27
  32. package/src/Queue.ts +6 -1
  33. package/src/QueueBinding.ts +18 -21
  34. package/src/QueueDefinition.ts +15 -14
  35. package/src/R2.ts +107 -29
  36. package/src/Rpc.ts +3 -1
  37. package/src/RpcDefinition.ts +177 -133
  38. package/src/ServiceBinding.ts +112 -108
  39. package/src/Vectorize.ts +59 -14
  40. package/src/Worker.ts +172 -192
  41. package/src/WorkerDefinition.ts +5 -10
  42. package/src/WorkersAi.ts +27 -17
  43. package/src/Workflow.ts +54 -49
  44. package/src/WorkflowBinding.ts +19 -11
  45. package/src/WorkflowDefinition.ts +24 -27
  46. package/src/index.ts +4 -3
  47. package/src/internal/Entrypoint.ts +3 -1
  48. package/src/internal/ErrorMessage.ts +24 -0
  49. package/src/internal/RpcInvocation.ts +11 -8
  50. package/src/internal/Runtime.ts +52 -0
  51. package/src/internal/WorkerContext.ts +39 -17
  52. package/src/Entry.ts +0 -259
  53. package/src/internal/Clock.ts +0 -37
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.20.1",
3
+ "version": "0.23.0",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,6 +14,7 @@
14
14
  "src"
15
15
  ],
16
16
  "type": "module",
17
+ "sideEffects": [],
17
18
  "types": "./dist/index.d.mts",
18
19
  "exports": {
19
20
  ".": {
@@ -52,12 +53,16 @@
52
53
  "vitest": "catalog:"
53
54
  },
54
55
  "peerDependencies": {
55
- "@effect/sql-d1": "^4.0.0-beta.105",
56
- "@effect/sql-pg": "^4.0.0-beta.105",
57
- "@effect/sql-sqlite-do": "^4.0.0-beta.105",
58
- "effect": "^4.0.0-beta.105"
56
+ "@cloudflare/workers-types": "^4.20260611.1",
57
+ "@effect/sql-d1": "4.0.0-beta.105",
58
+ "@effect/sql-pg": "4.0.0-beta.105",
59
+ "@effect/sql-sqlite-do": "4.0.0-beta.105",
60
+ "effect": "4.0.0-beta.105"
59
61
  },
60
62
  "peerDependenciesMeta": {
63
+ "@cloudflare/workers-types": {
64
+ "optional": true
65
+ },
61
66
  "@effect/sql-pg": {
62
67
  "optional": true
63
68
  }
package/src/AiGateway.ts CHANGED
@@ -4,6 +4,7 @@ import type { AiGateway as CloudflareAiGateway } from "@cloudflare/workers-types
4
4
  import * as Binding from "./Binding";
5
5
  import type { WorkerEnvironment } from "./Environment";
6
6
  import { isWorkersAiBinding, type WorkersAiBinding } from "./WorkersAi";
7
+ import * as ErrorMessage from "./internal/ErrorMessage";
7
8
 
8
9
  const expectedAiGatewayBinding = "Workers AI binding with gateway()";
9
10
 
@@ -12,7 +13,11 @@ export class AiGatewayOperationError extends Data.TaggedError("AiGatewayOperatio
12
13
  readonly binding: string;
13
14
  readonly operation: string;
14
15
  readonly cause: unknown;
15
- }> {}
16
+ }> {
17
+ override get message(): string {
18
+ return `AI Gateway ${this.operation} failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
19
+ }
20
+ }
16
21
 
17
22
  /** Typed AI Gateway client definition. */
18
23
  export interface AiGatewayDefinition {
@@ -53,7 +58,7 @@ export interface AiGatewayClient {
53
58
  data: AiGatewayPatchLog,
54
59
  ) => Effect.Effect<void, AiGatewayOperationError>;
55
60
  readonly getLog: (logId: string) => Effect.Effect<AiGatewayLog, AiGatewayOperationError>;
56
- readonly unsafeRaw: Effect.Effect<AiGatewayBinding, AiGatewayOperationError>;
61
+ readonly rawUnsafe: Effect.Effect<AiGatewayBinding, AiGatewayOperationError>;
57
62
  readonly definition: AiGatewayDefinition;
58
63
  }
59
64
 
@@ -93,13 +98,19 @@ const aiGatewayError = (binding: string, operation: string, cause: unknown) =>
93
98
  const tryAiGatewayPromise = <A>(
94
99
  binding: string,
95
100
  operation: string,
96
- evaluate: () => Promise<A>,
101
+ evaluate: (signal: AbortSignal) => Promise<A>,
97
102
  ): Effect.Effect<A, AiGatewayOperationError> =>
98
103
  Effect.tryPromise({
99
104
  try: evaluate,
100
105
  catch: (cause) => aiGatewayError(binding, operation, cause),
101
106
  });
102
107
 
108
+ const mergeAbortSignals = (
109
+ userSignal: AbortSignal | null | undefined,
110
+ signal: AbortSignal,
111
+ ): AbortSignal =>
112
+ userSignal === null || userSignal === undefined ? signal : AbortSignal.any([userSignal, signal]);
113
+
103
114
  const tryAiGatewaySync = <A>(
104
115
  binding: string,
105
116
  operation: string,
@@ -126,32 +137,42 @@ export const isAiGatewayBinding = (value: unknown): value is WorkersAiBinding =>
126
137
  export const makeClient = (definition: AiGatewayDefinition, gateway: AiGatewayBinding) =>
127
138
  ({
128
139
  definition,
129
- run: (data, options) =>
130
- tryAiGatewayPromise(definition.binding, "run", () =>
131
- gateway.run(
132
- (Array.isArray(data) ? [...data] : data) as
133
- | AiGatewayUniversalRequest
134
- | Array<AiGatewayUniversalRequest>,
135
- options,
140
+ run: Effect.fn("AiGateway.run")(
141
+ (
142
+ data: AiGatewayUniversalRequest | ReadonlyArray<AiGatewayUniversalRequest>,
143
+ options?: AiGatewayRunOptions,
144
+ ) =>
145
+ tryAiGatewayPromise(definition.binding, "run", () =>
146
+ gateway.run(
147
+ (Array.isArray(data) ? [...data] : data) as
148
+ | AiGatewayUniversalRequest
149
+ | Array<AiGatewayUniversalRequest>,
150
+ options,
151
+ ),
136
152
  ),
137
- ),
138
- getUrl: (provider) =>
153
+ ),
154
+ getUrl: Effect.fn("AiGateway.getUrl")((provider?: AiGatewayProvider) =>
139
155
  tryAiGatewayPromise(definition.binding, "getUrl", () => gateway.getUrl(provider)),
140
- fetch: (options) =>
141
- Effect.gen(function* () {
142
- const baseUrl = yield* tryAiGatewayPromise(definition.binding, "getUrl", () =>
143
- gateway.getUrl(options.provider),
144
- );
145
-
146
- return yield* tryAiGatewayPromise(definition.binding, "fetch", () =>
147
- fetch(providerUrl(baseUrl, options.path).href, options.init),
148
- );
149
- }),
150
- patchLog: (logId, data) =>
156
+ ),
157
+ fetch: Effect.fn("AiGateway.fetch")(function* (options: AiGatewayFetchOptions) {
158
+ const baseUrl = yield* tryAiGatewayPromise(definition.binding, "getUrl", () =>
159
+ gateway.getUrl(options.provider),
160
+ );
161
+
162
+ return yield* tryAiGatewayPromise(definition.binding, "fetch", (signal) =>
163
+ fetch(providerUrl(baseUrl, options.path).href, {
164
+ ...options.init,
165
+ signal: mergeAbortSignals(options.init?.signal, signal),
166
+ }),
167
+ );
168
+ }),
169
+ patchLog: Effect.fn("AiGateway.patchLog")((logId: string, data: AiGatewayPatchLog) =>
151
170
  tryAiGatewayPromise(definition.binding, "patchLog", () => gateway.patchLog(logId, data)),
152
- getLog: (logId) =>
171
+ ),
172
+ getLog: Effect.fn("AiGateway.getLog")((logId: string) =>
153
173
  tryAiGatewayPromise(definition.binding, "getLog", () => gateway.getLog(logId)),
154
- unsafeRaw: Effect.succeed(gateway),
174
+ ),
175
+ rawUnsafe: Effect.succeed(gateway),
155
176
  }) satisfies AiGatewayClient;
156
177
 
157
178
  export const makeClientFromAi = (
@@ -174,7 +195,7 @@ export const makeClientFromAi = (
174
195
  patchLog: (logId, data) =>
175
196
  withGateway((raw) => makeClient(definition, raw).patchLog(logId, data)),
176
197
  getLog: (logId) => withGateway((raw) => makeClient(definition, raw).getLog(logId)),
177
- unsafeRaw: gateway,
198
+ rawUnsafe: gateway,
178
199
  };
179
200
  };
180
201
 
@@ -214,45 +235,59 @@ export const makeHttpClient = (
214
235
 
215
236
  return {
216
237
  definition: clientDefinition,
217
- run: (data, options) =>
218
- tryAiGatewayPromise(binding, "run", () =>
219
- request(gatewayUrl(definition), {
220
- method: "POST",
221
- body: JSON.stringify(data),
222
- headers: new Headers({
223
- "content-type": "application/json",
224
- ...Object.fromEntries(new Headers(extraHeaders(options)).entries()),
238
+ run: Effect.fn("AiGateway.run")(
239
+ (
240
+ data: AiGatewayUniversalRequest | ReadonlyArray<AiGatewayUniversalRequest>,
241
+ options?: AiGatewayRunOptions,
242
+ ) =>
243
+ tryAiGatewayPromise(binding, "run", (signal) =>
244
+ request(gatewayUrl(definition), {
245
+ method: "POST",
246
+ body: JSON.stringify(data),
247
+ headers: new Headers({
248
+ "content-type": "application/json",
249
+ ...Object.fromEntries(new Headers(extraHeaders(options)).entries()),
250
+ }),
251
+ signal: mergeAbortSignals(options?.signal, signal),
225
252
  }),
226
- signal: options?.signal,
227
- }),
228
- ),
253
+ ),
254
+ ),
229
255
  getUrl,
230
- fetch: (options) =>
256
+ fetch: Effect.fn("AiGateway.fetch")((options: AiGatewayFetchOptions) =>
231
257
  getUrl(options.provider).pipe(
232
258
  Effect.flatMap((baseUrl) =>
233
- tryAiGatewayPromise(binding, "fetch", () =>
234
- request(providerUrl(baseUrl, options.path).href, options.init),
259
+ tryAiGatewayPromise(binding, "fetch", (signal) =>
260
+ request(providerUrl(baseUrl, options.path).href, {
261
+ ...options.init,
262
+ signal: mergeAbortSignals(options.init?.signal, signal),
263
+ }),
235
264
  ),
236
265
  ),
237
266
  ),
238
- patchLog: (logId, data) =>
239
- tryAiGatewayPromise(binding, "patchLog", () =>
267
+ ),
268
+ patchLog: Effect.fn("AiGateway.patchLog")((logId: string, data: AiGatewayPatchLog) =>
269
+ tryAiGatewayPromise(binding, "patchLog", (signal) =>
240
270
  request(providerUrl(gatewayUrl(definition), `logs/${logId}`).href, {
241
271
  method: "PATCH",
242
272
  body: JSON.stringify(data),
243
273
  headers: { "content-type": "application/json" },
274
+ signal,
244
275
  }).then(() => undefined),
245
276
  ),
246
- getLog: (logId) =>
247
- tryAiGatewayPromise(binding, "getLog", async () => {
248
- const response = await request(providerUrl(gatewayUrl(definition), `logs/${logId}`).href);
277
+ ),
278
+ getLog: Effect.fn("AiGateway.getLog")((logId: string) =>
279
+ tryAiGatewayPromise(binding, "getLog", async (signal) => {
280
+ const response = await request(providerUrl(gatewayUrl(definition), `logs/${logId}`).href, {
281
+ signal,
282
+ });
249
283
 
250
284
  return (await response.json()) as AiGatewayLog;
251
285
  }),
252
- unsafeRaw: Effect.fail(
286
+ ),
287
+ rawUnsafe: Effect.fail(
253
288
  aiGatewayError(
254
289
  binding,
255
- "unsafeRaw",
290
+ "rawUnsafe",
256
291
  new TypeError("HTTP AI Gateway clients have no raw binding"),
257
292
  ),
258
293
  ),
@@ -23,6 +23,7 @@ import {
23
23
 
24
24
  import * as Binding from "./Binding";
25
25
  import type { WorkerEnvironment } from "./Environment";
26
+ import * as ErrorMessage from "./internal/ErrorMessage";
26
27
 
27
28
  const expectedAnalyticsEngineDataset = "Analytics Engine dataset binding with writeDataPoint()";
28
29
  const defaultQueryApiBaseUrl = "https://api.cloudflare.com/client/v4";
@@ -70,7 +71,11 @@ export class AnalyticsEngineOperationError extends Data.TaggedError(
70
71
  readonly binding: string;
71
72
  readonly operation: string;
72
73
  readonly cause: unknown;
73
- }> {}
74
+ }> {
75
+ override get message(): string {
76
+ return `Analytics Engine ${this.operation} failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
77
+ }
78
+ }
74
79
 
75
80
  export interface AnalyticsEngineWriteViolation {
76
81
  readonly path: string;
@@ -87,7 +92,11 @@ export class AnalyticsEngineWriteValidationError extends Data.TaggedError(
87
92
  readonly operation: string;
88
93
  readonly violations: ReadonlyArray<AnalyticsEngineWriteViolation>;
89
94
  readonly cause?: unknown;
90
- }> {}
95
+ }> {
96
+ override get message(): string {
97
+ return `Analytics Engine ${this.operation} for binding "${this.binding}" failed validation: ${ErrorMessage.violationsMessage(this.violations)}`;
98
+ }
99
+ }
91
100
 
92
101
  /** Error raised when an Analytics Engine SQL API query fails. */
93
102
  export class AnalyticsEngineQueryError extends Data.TaggedError("AnalyticsEngineQueryError")<{
@@ -170,7 +179,7 @@ export interface AnalyticsEngineClient {
170
179
  dataPoints: ReadonlyArray<AnalyticsEngineDataPoint>,
171
180
  options?: AnalyticsEngineWriteBatchOptions,
172
181
  ) => Effect.Effect<void, AnalyticsEngineWriteError>;
173
- readonly unsafeRaw: Effect.Effect<AnalyticsEngineBinding>;
182
+ readonly rawUnsafe: Effect.Effect<AnalyticsEngineBinding>;
174
183
  readonly definition: AnalyticsEngineDefinition;
175
184
  }
176
185
 
@@ -257,11 +266,11 @@ export interface QueryTagClass<Self, Id extends string> extends Context.ServiceC
257
266
  readonly layer: (
258
267
  definition: AnalyticsEngineQueryDefinition,
259
268
  ) => Layer.Layer<Self, never, HttpClient.HttpClient>;
260
- readonly fetchLayer: (definition: AnalyticsEngineQueryDefinition) => Layer.Layer<Self>;
269
+ readonly layerFetch: (definition: AnalyticsEngineQueryDefinition) => Layer.Layer<Self>;
261
270
  readonly layerConfig: (
262
271
  config?: Config.Config<AnalyticsEngineQueryDefinition>,
263
272
  ) => Layer.Layer<Self, Config.ConfigError, HttpClient.HttpClient>;
264
- readonly fetchLayerConfig: (
273
+ readonly layerFetchConfig: (
265
274
  config?: Config.Config<AnalyticsEngineQueryDefinition>,
266
275
  ) => Layer.Layer<Self, Config.ConfigError>;
267
276
  }
@@ -633,26 +642,30 @@ const makeQueryClientWith = (
633
642
  definition: AnalyticsEngineQueryDefinition,
634
643
  httpClient: HttpClient.HttpClient,
635
644
  ): AnalyticsEngineQueryClient => {
636
- const raw = (sql: string, options?: AnalyticsEngineQueryOptions) =>
637
- executeQueryRequest(definition, httpClient, sql, options);
638
- const query = (sql: string, options?: AnalyticsEngineQueryOptions) =>
639
- Effect.gen(function* () {
640
- const response = yield* raw(sql, options);
641
- const json = yield* response.json.pipe(
642
- Effect.catch((cause) =>
643
- Effect.fail(
644
- analyticsEngineQueryError(
645
- definition,
646
- "json",
647
- "Failed to read Analytics Engine SQL API JSON response body",
648
- { cause },
649
- ),
645
+ const raw = Effect.fn("AnalyticsEngine.raw")(
646
+ (sql: string, options?: AnalyticsEngineQueryOptions) =>
647
+ executeQueryRequest(definition, httpClient, sql, options),
648
+ );
649
+ const query = Effect.fn("AnalyticsEngine.query")(function* (
650
+ sql: string,
651
+ options?: AnalyticsEngineQueryOptions,
652
+ ) {
653
+ const response = yield* raw(sql, options);
654
+ const json = yield* response.json.pipe(
655
+ Effect.catch((cause) =>
656
+ Effect.fail(
657
+ analyticsEngineQueryError(
658
+ definition,
659
+ "json",
660
+ "Failed to read Analytics Engine SQL API JSON response body",
661
+ { cause },
650
662
  ),
651
663
  ),
652
- );
664
+ ),
665
+ );
653
666
 
654
- return yield* decodeQueryResponse(json);
655
- });
667
+ return yield* decodeQueryResponse(json);
668
+ });
656
669
  const queryResult = <Row>(
657
670
  row: S.Codec<Row, unknown>,
658
671
  sql: string,
@@ -672,7 +685,7 @@ const makeQueryClientWith = (
672
685
  data,
673
686
  rows: result.rows,
674
687
  } satisfies AnalyticsEngineQueryResult<Row>;
675
- });
688
+ }).pipe(Effect.withSpan("AnalyticsEngine.queryResult"));
676
689
 
677
690
  return {
678
691
  definition,
@@ -680,17 +693,23 @@ const makeQueryClientWith = (
680
693
  query,
681
694
  queryResult,
682
695
  queryRows: (row, sql, options) =>
683
- queryResult(row, sql, options).pipe(Effect.map((result) => result.data)),
696
+ queryResult(row, sql, options).pipe(
697
+ Effect.map((result) => result.data),
698
+ Effect.withSpan("AnalyticsEngine.queryRows"),
699
+ ),
684
700
  queryOne: (row, sql, options) =>
685
701
  queryResult(row, sql, options).pipe(
686
702
  Effect.map((result) =>
687
703
  result.data[0] === undefined ? Option.none() : Option.some(result.data[0]),
688
704
  ),
705
+ Effect.withSpan("AnalyticsEngine.queryOne"),
689
706
  ),
690
- queryText: (sql, options) =>
691
- raw(sql, options).pipe(
692
- Effect.flatMap((response) => responseText(definition, response, "text")),
693
- ),
707
+ queryText: Effect.fn("AnalyticsEngine.queryText")(
708
+ (sql: string, options?: AnalyticsEngineQueryOptions) =>
709
+ raw(sql, options).pipe(
710
+ Effect.flatMap((response) => responseText(definition, response, "text")),
711
+ ),
712
+ ),
694
713
  };
695
714
  };
696
715
 
@@ -708,7 +727,7 @@ export const isAnalyticsEngineDataset = (value: unknown): value is AnalyticsEngi
708
727
  export const makeClient =
709
728
  (definition: AnalyticsEngineDefinition, defaults?: AnalyticsEngineWritePolicy) =>
710
729
  (dataset: AnalyticsEngineBinding): AnalyticsEngineClient => {
711
- const writeDataPoint = (
730
+ const writeDataPoint = Effect.fn("AnalyticsEngine.writeDataPoint")((
712
731
  dataPoint?: AnalyticsEngineDataPoint,
713
732
  options?: AnalyticsEngineWriteOptions,
714
733
  ) => {
@@ -724,31 +743,28 @@ export const makeClient =
724
743
  ),
725
744
  }),
726
745
  );
727
- };
746
+ });
728
747
 
729
- const writeDataPoints = (
748
+ const writeDataPoints = Effect.fn("AnalyticsEngine.writeDataPoints")(function* (
730
749
  dataPoints: ReadonlyArray<AnalyticsEngineDataPoint>,
731
750
  options?: AnalyticsEngineWriteBatchOptions,
732
- ) => {
751
+ ) {
733
752
  const policy = resolveWritePolicy(defaults, options);
753
+ const validPoints = yield* validateDataPoints(
754
+ definition.binding,
755
+ "writeDataPoints",
756
+ dataPoints,
757
+ policy,
758
+ );
734
759
 
735
- return Effect.gen(function* () {
736
- const validPoints = yield* validateDataPoints(
737
- definition.binding,
738
- "writeDataPoints",
739
- dataPoints,
740
- policy,
741
- );
742
-
743
- yield* writeChunks(
744
- definition.binding,
745
- "writeDataPoints",
746
- dataset,
747
- validPoints,
748
- policy.batchSize,
749
- );
750
- });
751
- };
760
+ yield* writeChunks(
761
+ definition.binding,
762
+ "writeDataPoints",
763
+ dataset,
764
+ validPoints,
765
+ policy.batchSize,
766
+ );
767
+ });
752
768
 
753
769
  return {
754
770
  definition,
@@ -756,7 +772,7 @@ export const makeClient =
756
772
  write: writeDataPoint,
757
773
  writeDataPoints,
758
774
  writeBatch: writeDataPoints,
759
- unsafeRaw: Effect.succeed(dataset),
775
+ rawUnsafe: Effect.succeed(dataset),
760
776
  };
761
777
  };
762
778
 
@@ -774,7 +790,7 @@ export const queryLayer = <Self>(
774
790
  definition: AnalyticsEngineQueryDefinition,
775
791
  ) => Layer.effect(tag, makeQueryClient(definition));
776
792
 
777
- export const queryFetchLayer = <Self>(
793
+ export const layerFetch = <Self>(
778
794
  tag: Context.Service<Self, AnalyticsEngineQueryClient>,
779
795
  definition: AnalyticsEngineQueryDefinition,
780
796
  ) => queryLayer(tag, definition).pipe(Layer.provide(FetchHttpClient.layer));
@@ -792,7 +808,7 @@ export const queryLayerConfig = <Self>(
792
808
  }),
793
809
  );
794
810
 
795
- export const queryFetchLayerConfig = <Self>(
811
+ export const layerFetchConfig = <Self>(
796
812
  tag: Context.Service<Self, AnalyticsEngineQueryClient>,
797
813
  config: Config.Config<AnalyticsEngineQueryDefinition> = queryConfig(),
798
814
  ) => queryLayerConfig(tag, config).pipe(Layer.provide(FetchHttpClient.layer));
@@ -834,18 +850,18 @@ export const QueryTag =
834
850
  <Id extends string>(id: Id) => {
835
851
  const tag = Context.Service<Self, AnalyticsEngineQueryClient>()(id);
836
852
  const makeLayer = (definition: AnalyticsEngineQueryDefinition) => queryLayer(tag, definition);
837
- const makeFetchLayer = (definition: AnalyticsEngineQueryDefinition) =>
838
- queryFetchLayer(tag, definition);
853
+ const makeLayerFetch = (definition: AnalyticsEngineQueryDefinition) =>
854
+ layerFetch(tag, definition);
839
855
  const makeLayerConfig = (config?: Config.Config<AnalyticsEngineQueryDefinition>) =>
840
856
  queryLayerConfig(tag, config);
841
- const makeFetchLayerConfig = (config?: Config.Config<AnalyticsEngineQueryDefinition>) =>
842
- queryFetchLayerConfig(tag, config);
857
+ const makeLayerFetchConfig = (config?: Config.Config<AnalyticsEngineQueryDefinition>) =>
858
+ layerFetchConfig(tag, config);
843
859
 
844
860
  return Object.assign(tag, {
845
861
  id,
846
862
  layer: makeLayer,
847
- fetchLayer: makeFetchLayer,
863
+ layerFetch: makeLayerFetch,
848
864
  layerConfig: makeLayerConfig,
849
- fetchLayerConfig: makeFetchLayerConfig,
865
+ layerFetchConfig: makeLayerFetchConfig,
850
866
  }) as QueryTagClass<Self, Id>;
851
867
  };
package/src/Binding.ts CHANGED
@@ -3,7 +3,10 @@ import { Context, Data, Effect, Layer } from "effect";
3
3
  import { WorkerEnvironment, type WorkerEnv } from "./Environment";
4
4
 
5
5
  /** Internal type id marker used by binding helper services. */
6
- export const TypeId = "effect-cf/Binding" as const;
6
+ export const TypeId = "~effect-cf/Binding" as const;
7
+
8
+ /** Internal type id marker used by binding helper services. */
9
+ export type TypeId = typeof TypeId;
7
10
 
8
11
  /** Error raised when a configured binding does not exist on `env`. */
9
12
  export class BindingNotFoundError extends Data.TaggedError("BindingNotFoundError")<{
@@ -161,13 +164,17 @@ const getBinding = <Resource>(
161
164
 
162
165
  /**
163
166
  * Creates a Context tag + layer for reading and validating a Cloudflare binding.
167
+ *
168
+ * The Context key is namespaced as `effect-cf/Binding/<id>`; `id` stays the
169
+ * bare user-supplied identifier.
164
170
  */
165
171
  export interface BindingService<Self, Id extends string, Service> extends Context.ServiceClass<
166
172
  Self,
167
- Id,
173
+ `effect-cf/Binding/${Id}`,
168
174
  Service
169
175
  > {
170
- readonly [TypeId]: typeof TypeId;
176
+ readonly [TypeId]: TypeId;
177
+ readonly id: Id;
171
178
  readonly binding: string;
172
179
  readonly layer: Layer.Layer<
173
180
  Self,
@@ -176,12 +183,17 @@ export interface BindingService<Self, Id extends string, Service> extends Contex
176
183
  >;
177
184
  }
178
185
 
179
- export const layer = <Self, Resource, Service = Resource>(
186
+ /**
187
+ * The overloads on {@link layer} and {@link Service} guarantee that
188
+ * `Service = Resource` whenever `wrap` is absent, making the fallback cast
189
+ * safe.
190
+ */
191
+ const makeBindingLayer = <Self, Resource, Service>(
180
192
  tag: Context.Service<Self, Service>,
181
193
  binding: string,
182
194
  isResource: (value: unknown) => value is Resource,
183
- wrap?: (resource: Resource) => Service,
184
- options?: ValidationOptions,
195
+ wrap: ((resource: Resource) => Service) | undefined,
196
+ options: ValidationOptions | undefined,
185
197
  ): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment> =>
186
198
  Layer.effect(
187
199
  tag,
@@ -193,21 +205,62 @@ export const layer = <Self, Resource, Service = Resource>(
193
205
  }),
194
206
  );
195
207
 
196
- export const Service =
197
- <Self>() =>
198
- <Id extends string, Resource, Service = Resource>(
208
+ export function layer<Self, Resource>(
209
+ tag: Context.Service<Self, Resource>,
210
+ binding: string,
211
+ isResource: (value: unknown) => value is Resource,
212
+ wrap?: undefined,
213
+ options?: ValidationOptions,
214
+ ): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
215
+ export function layer<Self, Resource, Service>(
216
+ tag: Context.Service<Self, Service>,
217
+ binding: string,
218
+ isResource: (value: unknown) => value is Resource,
219
+ wrap: (resource: Resource) => Service,
220
+ options?: ValidationOptions,
221
+ ): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment>;
222
+ export function layer<Self, Resource, Service = Resource>(
223
+ tag: Context.Service<Self, Service>,
224
+ binding: string,
225
+ isResource: (value: unknown) => value is Resource,
226
+ wrap?: (resource: Resource) => Service,
227
+ options?: ValidationOptions,
228
+ ): Layer.Layer<Self, BindingNotFoundError | BindingValidationError, WorkerEnvironment> {
229
+ return makeBindingLayer(tag, binding, isResource, wrap, options);
230
+ }
231
+
232
+ export const Service = <Self>() => {
233
+ function makeService<Id extends string, Resource>(
234
+ id: Id,
235
+ binding: string,
236
+ isResource: (value: unknown) => value is Resource,
237
+ wrap?: undefined,
238
+ options?: ValidationOptions,
239
+ ): BindingService<Self, Id, Resource>;
240
+ function makeService<Id extends string, Resource, Service>(
241
+ id: Id,
242
+ binding: string,
243
+ isResource: (value: unknown) => value is Resource,
244
+ wrap: (resource: Resource) => Service,
245
+ options?: ValidationOptions,
246
+ ): BindingService<Self, Id, Service>;
247
+ function makeService<Id extends string, Resource, Service = Resource>(
199
248
  id: Id,
200
249
  binding: string,
201
250
  isResource: (value: unknown) => value is Resource,
202
251
  wrap?: (resource: Resource) => Service,
203
252
  options?: ValidationOptions,
204
- ): BindingService<Self, Id, Service> => {
205
- const tag = Context.Service<Self, Service>()(id);
206
- const serviceLayer = layer(tag, binding, isResource, wrap, options);
253
+ ): BindingService<Self, Id, Service> {
254
+ const tag = Context.Service<Self, Service>()(`effect-cf/Binding/${id}` as const);
255
+ const serviceLayer = makeBindingLayer(tag, binding, isResource, wrap, options);
207
256
 
208
257
  return Object.assign(tag, {
209
258
  [TypeId]: TypeId,
259
+ id,
210
260
  binding,
211
261
  layer: serviceLayer,
212
262
  }) as BindingService<Self, Id, Service>;
213
- };
263
+ }
264
+
265
+ return makeService;
266
+ };