effect-cf 0.41.1 → 0.42.1

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 (62) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +28 -0
  3. package/dist/AiGateway.d.mts +2 -2
  4. package/dist/AnalyticsEngine.d.mts +2 -2
  5. package/dist/Artifacts.d.mts +2 -2
  6. package/dist/BrowserRendering.d.mts +2 -2
  7. package/dist/ContainerNamespace.d.mts +2 -2
  8. package/dist/D1.d.mts +9 -12
  9. package/dist/DurableObject.d.mts +9 -5
  10. package/dist/DurableObject.mjs +9 -4
  11. package/dist/DurableObjectDefinition.d.mts +15 -12
  12. package/dist/DurableObjectDefinition.mjs +5 -3
  13. package/dist/DurableObjectNamespace.d.mts +10 -8
  14. package/dist/DurableObjectSqlite.mjs +1 -1
  15. package/dist/Email.d.mts +2 -2
  16. package/dist/Hyperdrive.d.mts +2 -2
  17. package/dist/Images.d.mts +2 -2
  18. package/dist/Kv.d.mts +2 -2
  19. package/dist/Queue.d.mts +5 -4
  20. package/dist/QueueDefinition.d.mts +4 -4
  21. package/dist/R2.d.mts +2 -2
  22. package/dist/RpcDefinition.d.mts +26 -12
  23. package/dist/RpcDefinition.mjs +22 -5
  24. package/dist/Sandbox.d.mts +1 -1
  25. package/dist/ServiceBinding.d.mts +17 -18
  26. package/dist/Vectorize.d.mts +2 -2
  27. package/dist/Worker.d.mts +5 -3
  28. package/dist/Worker.mjs +4 -3
  29. package/dist/WorkerDefinition.d.mts +15 -11
  30. package/dist/WorkerDefinition.mjs +5 -3
  31. package/dist/WorkersAi.d.mts +2 -2
  32. package/dist/Workflow.d.mts +5 -4
  33. package/dist/WorkflowDefinition.d.mts +4 -4
  34. package/dist/index.d.mts +2 -2
  35. package/dist/internal/NativeCallback.mjs +5 -2
  36. package/package.json +1 -1
  37. package/src/AiGateway.ts +2 -0
  38. package/src/AnalyticsEngine.ts +2 -0
  39. package/src/Artifacts.ts +2 -0
  40. package/src/BrowserRendering.ts +2 -0
  41. package/src/ContainerNamespace.ts +2 -0
  42. package/src/D1.ts +16 -8
  43. package/src/DurableObject.ts +30 -4
  44. package/src/DurableObjectDefinition.ts +33 -17
  45. package/src/DurableObjectNamespace.ts +19 -35
  46. package/src/Email.ts +2 -0
  47. package/src/Hyperdrive.ts +2 -0
  48. package/src/Images.ts +2 -0
  49. package/src/Kv.ts +2 -0
  50. package/src/Queue.ts +4 -7
  51. package/src/QueueDefinition.ts +7 -4
  52. package/src/R2.ts +2 -0
  53. package/src/RpcDefinition.ts +64 -25
  54. package/src/Sandbox.ts +2 -0
  55. package/src/ServiceBinding.ts +39 -34
  56. package/src/Vectorize.ts +2 -0
  57. package/src/Worker.ts +12 -1
  58. package/src/WorkerDefinition.ts +28 -17
  59. package/src/WorkersAi.ts +2 -0
  60. package/src/Workflow.ts +9 -8
  61. package/src/WorkflowDefinition.ts +12 -4
  62. package/src/internal/NativeCallback.ts +13 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.41.1",
3
+ "version": "0.42.1",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/AiGateway.ts CHANGED
@@ -10,6 +10,8 @@ import type { WorkerEnvironment } from "./Environment";
10
10
  import { isWorkersAiBinding, type WorkersAiBinding } from "./WorkersAi";
11
11
  import * as ErrorMessage from "./internal/ErrorMessage";
12
12
 
13
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
14
+
13
15
  const expectedAiGatewayBinding = "Workers AI binding with gateway()";
14
16
 
15
17
  export class AiGatewayOperationError extends Data.TaggedError("AiGatewayOperationError")<{
@@ -21,6 +21,8 @@ import * as Binding from "./Binding";
21
21
  import type { WorkerEnvironment } from "./Environment";
22
22
  import * as ErrorMessage from "./internal/ErrorMessage";
23
23
 
24
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
25
+
24
26
  const expectedAnalyticsEngineDataset = "Analytics Engine dataset binding with writeDataPoint()";
25
27
  const defaultQueryApiBaseUrl = "https://api.cloudflare.com/client/v4";
26
28
  const textEncoder = new TextEncoder();
package/src/Artifacts.ts CHANGED
@@ -17,6 +17,8 @@ import * as Binding from "./Binding";
17
17
  import type { WorkerEnvironment } from "./Environment";
18
18
  import * as ErrorMessage from "./internal/ErrorMessage";
19
19
 
20
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
21
+
20
22
  const expectedArtifactsBinding =
21
23
  "Artifacts binding with create(), get(), import(), list(), and delete()";
22
24
 
@@ -8,6 +8,8 @@ import * as Binding from "./Binding";
8
8
  import type { WorkerEnvironment } from "./Environment";
9
9
  import * as ErrorMessage from "./internal/ErrorMessage";
10
10
 
11
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
12
+
11
13
  const expectedBrowserRenderingBinding = "Browser Rendering binding resource";
12
14
 
13
15
  export class BrowserRenderingOperationError extends Data.TaggedError(
@@ -10,6 +10,8 @@ import * as RpcTargets from "./RpcTargets";
10
10
  import type { WorkerEnvironment } from "./Environment";
11
11
  import * as ErrorMessage from "./internal/ErrorMessage";
12
12
 
13
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
14
+
13
15
  const expectedContainerNamespace = "Container namespace binding with getByName()";
14
16
 
15
17
  export type ContainerSignal = "SIGKILL" | "SIGINT" | "SIGTERM";
package/src/D1.ts CHANGED
@@ -8,6 +8,8 @@ import type { SqlClient } from "effect/unstable/sql";
8
8
  import * as Binding from "./Binding";
9
9
  import type { WorkerEnvironment } from "./Environment";
10
10
 
11
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
12
+
11
13
  const TypeId = "~effect-cf/D1" as const;
12
14
 
13
15
  export type TypeId = typeof TypeId;
@@ -19,6 +21,18 @@ export interface D1Definition {
19
21
 
20
22
  export type D1SqlLayerOptions = Omit<D1Client.D1ClientConfig, "db">;
21
23
 
24
+ export type ServiceClass<Self, Id extends string> = Binding.BindingService<Self, Id, D1Database> & {
25
+ [TypeId]: typeof TypeId;
26
+ definition: D1Definition;
27
+ sqlLayer: (
28
+ options?: D1SqlLayerOptions,
29
+ ) => Layer.Layer<
30
+ D1Client.D1Client | SqlClient.SqlClient,
31
+ Config.ConfigError | Binding.BindingNotFoundError | Binding.BindingValidationError,
32
+ WorkerEnvironment
33
+ >;
34
+ };
35
+
22
36
  declare const D1ServiceTypeId: unique symbol;
23
37
 
24
38
  export interface D1Service<Id extends string> {
@@ -40,18 +54,12 @@ export const make = <Id extends string>(id: Id, definition: D1Definition) =>
40
54
 
41
55
  export const Service =
42
56
  <Self>() =>
43
- <Id extends string>(id: Id, definition: D1Definition) => {
57
+ <Id extends string>(id: Id, definition: D1Definition): ServiceClass<Self, Id> => {
44
58
  const tag = Binding.Service<Self>()(id, definition.binding, isD1Database, undefined, {
45
59
  expected: expectedD1Database,
46
60
  });
47
61
 
48
- const sqlLayer = (
49
- options?: D1SqlLayerOptions,
50
- ): Layer.Layer<
51
- D1Client.D1Client | SqlClient.SqlClient,
52
- Config.ConfigError | Binding.BindingNotFoundError | Binding.BindingValidationError,
53
- WorkerEnvironment
54
- > =>
62
+ const sqlLayer: ServiceClass<Self, Id>["sqlLayer"] = (options) =>
55
63
  Layer.unwrap(
56
64
  Effect.gen(function* () {
57
65
  const db = yield* tag;
@@ -1,6 +1,7 @@
1
1
  import { DurableObject as CloudflareDurableObject } from "cloudflare:workers";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Layer from "effect/Layer";
4
+ import * as Scheduler from "effect/Scheduler";
4
5
  import * as Tracer from "effect/Tracer";
5
6
  import type { ManagedRuntime, Scope } from "effect";
6
7
 
@@ -19,6 +20,17 @@ import * as Runtime from "./internal/Runtime";
19
20
  import * as Telemetry from "./internal/Telemetry";
20
21
  import type { ReceiverOptions, RpcInvocationInfo } from "./RpcTracing";
21
22
 
23
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
24
+ export type {
25
+ DurableObjectFetchError,
26
+ DurableObjectNamespaceClient,
27
+ DurableObjectRpcError,
28
+ DurableObjectStubClient,
29
+ StubCall,
30
+ StubRpc,
31
+ StubScopedCall,
32
+ } from "./DurableObjectNamespace";
33
+
22
34
  const reservedMethodNames: ReadonlySet<string> = RpcDefinition.reservedMethodNames;
23
35
 
24
36
  export type RuntimeContext<ROut> =
@@ -114,8 +126,10 @@ interface DurableObjectOptionsBase<
114
126
  /**
115
127
  * Effect run when Cloudflare loads this Durable Object instance into memory.
116
128
  *
117
- * Use `DurableObjectState.blockConcurrencyWhile` inside this hook when
118
- * incoming events should wait for setup to finish. Cloudflare may construct
129
+ * The base layer is acquired before incoming events are admitted. The hook
130
+ * itself runs in the background; start it with
131
+ * `DurableObjectState.blockConcurrencyWhile` when incoming events should
132
+ * wait for setup to finish. Cloudflare may construct
119
133
  * the same Durable Object id again after eviction or restart; use Durable
120
134
  * Object storage if work must happen only once per id.
121
135
  */
@@ -287,7 +301,17 @@ export function make<
287
301
  const initialize = options.initialize;
288
302
 
289
303
  if (initialize !== undefined) {
290
- state.waitUntil(this[RunSymbol](initialize, { eventLayer: false }));
304
+ state.waitUntil(
305
+ state.blockConcurrencyWhile(async () => {
306
+ // Build services before admitting the first event, then let the
307
+ // hook synchronously install its own gate if it needs one. Its
308
+ // background work and failure reporting keep their waitUntil owner.
309
+ await Effect.runPromise(this.runtime.contextEffect, {
310
+ scheduler: new Scheduler.MixedScheduler("sync"),
311
+ });
312
+ state.waitUntil(this[RunSymbol](initialize, { eventLayer: false }));
313
+ }),
314
+ );
291
315
  }
292
316
  }
293
317
 
@@ -403,15 +427,17 @@ export type {
403
427
  LayerOptions,
404
428
  Method,
405
429
  Methods,
430
+ NativeSchema,
406
431
  NoReservedMethods,
407
432
  Options,
408
433
  ReservedMethodName,
434
+ RpcSchema,
409
435
  ServerApi,
410
436
  ServiceFreeSchema,
411
437
  TagClass,
412
438
  } from "./DurableObjectDefinition";
413
439
 
414
- export { implement, method, Tag } from "./DurableObjectDefinition";
440
+ export { implement, method, native, Tag } from "./DurableObjectDefinition";
415
441
 
416
442
  // Preserve the original public type export while using a domain-role name internally.
417
443
  export type { DurableObjectRpcApi as "DurableObjectRpcShape" };
@@ -11,41 +11,55 @@ import * as RpcDefinition from "./RpcDefinition";
11
11
  import { recordDecodedArgs } from "./internal/RpcInvocation";
12
12
  import type { WorkerEnvironment } from "./Environment";
13
13
 
14
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
15
+ export type {
16
+ DurableObjectFetchError,
17
+ DurableObjectNamespaceClient,
18
+ DurableObjectRpcError,
19
+ DurableObjectStubClient,
20
+ StubCall,
21
+ StubRpc,
22
+ StubScopedCall,
23
+ } from "./DurableObjectNamespace";
24
+
14
25
  type ErasedInvoke<E> = (...args: ReadonlyArray<unknown>) => Effect.Effect<unknown, E>;
15
26
 
16
27
  export type ServiceFreeSchema = S.Codec<any, any, never, never>;
28
+ export type NativeSchema<Schema extends ServiceFreeSchema = ServiceFreeSchema> =
29
+ RpcDefinition.NativeSchema<Schema>;
30
+ export type RpcSchema = RpcDefinition.RpcSchema;
17
31
 
18
32
  export interface Method<
19
- Args extends ReadonlyArray<ServiceFreeSchema> = ReadonlyArray<ServiceFreeSchema>,
20
- Success extends ServiceFreeSchema = ServiceFreeSchema,
33
+ Args extends ReadonlyArray<RpcSchema> = ReadonlyArray<RpcSchema>,
34
+ Success extends RpcSchema = RpcSchema,
21
35
  > {
22
36
  readonly args: Args;
23
37
  readonly success: Success;
24
38
  }
25
39
 
26
40
  export namespace Method {
27
- export type Any = Method<ReadonlyArray<ServiceFreeSchema>, ServiceFreeSchema>;
41
+ export type Any = Method<ReadonlyArray<RpcSchema>, RpcSchema>;
28
42
 
29
- type ArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = Args extends readonly []
43
+ type ArgsFromSchemas<Args extends ReadonlyArray<RpcSchema>> = Args extends readonly []
30
44
  ? []
31
45
  : Args extends readonly [
32
- infer Head extends ServiceFreeSchema,
33
- ...infer Tail extends ReadonlyArray<ServiceFreeSchema>,
46
+ infer Head extends RpcSchema,
47
+ ...infer Tail extends ReadonlyArray<RpcSchema>,
34
48
  ]
35
- ? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
36
- : Array<S.Schema.Type<Args[number]>>;
49
+ ? [RpcDefinition.SchemaType<Head>, ...ArgsFromSchemas<Tail>]
50
+ : Array<RpcDefinition.SchemaType<Args[number]>>;
37
51
 
38
- type EncodedArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = {
39
- [Index in keyof Args]: S.Json;
52
+ type EncodedArgsFromSchemas<Args extends ReadonlyArray<RpcSchema>> = {
53
+ [Index in keyof Args]: RpcDefinition.WireEncoded<Args[Index]>;
40
54
  };
41
55
 
42
56
  export type Args<Self extends Any> = ArgsFromSchemas<Self["args"]>;
43
57
 
44
58
  export type EncodedArgs<Self extends Any> = EncodedArgsFromSchemas<Self["args"]>;
45
59
 
46
- export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
60
+ export type Success<Self extends Any> = RpcDefinition.SchemaType<Self["success"]>;
47
61
 
48
- export type EncodedSuccess<_Self extends Any> = S.Json;
62
+ export type EncodedSuccess<Self extends Any> = RpcDefinition.WireEncoded<Self["success"]>;
49
63
  }
50
64
 
51
65
  export type Methods = Record<string, Method.Any>;
@@ -178,20 +192,22 @@ export type TagClass<
178
192
  /**
179
193
  * Defines a single RPC method schema in a Durable Object definition.
180
194
  */
181
- export function method<Success extends ServiceFreeSchema>(definition: {
195
+ export function method<Success extends RpcSchema>(definition: {
182
196
  readonly success: Success;
183
197
  }): Method<readonly [], Success>;
184
198
  export function method<
185
- const Args extends ReadonlyArray<ServiceFreeSchema>,
186
- Success extends ServiceFreeSchema,
199
+ const Args extends ReadonlyArray<RpcSchema>,
200
+ Success extends RpcSchema,
187
201
  >(definition: { readonly args: Args; readonly success: Success }): Method<Args, Success>;
188
202
  export function method(definition: {
189
- readonly args?: ReadonlyArray<ServiceFreeSchema>;
190
- readonly success: ServiceFreeSchema;
203
+ readonly args?: ReadonlyArray<RpcSchema>;
204
+ readonly success: RpcSchema;
191
205
  }): Method {
192
206
  return RpcDefinition.method(definition);
193
207
  }
194
208
 
209
+ export const native = RpcDefinition.native;
210
+
195
211
  const makeDefinition = <Id extends string, const MethodDefinitions extends Methods>(
196
212
  id: Id,
197
213
  methods: MethodDefinitions & NoReservedMethods<MethodDefinitions>,
@@ -120,12 +120,24 @@ type StubMethodCloudflareReturn<
120
120
  Method extends keyof Api,
121
121
  > = RpcInvocation.AsyncMethodCloudflareReturn<Api, Method>;
122
122
 
123
- type StubCall<R, Api extends object> = <Method extends StubMethodKey<Api>>(
123
+ export type StubCall<R, Api extends object> = <Method extends StubMethodKey<Api>>(
124
124
  stub: DurableObjectStubClient<Api>,
125
125
  method: Method,
126
126
  ...args: StubMethodArgs<Api, Method>
127
127
  ) => Effect.Effect<StubMethodSuccess<Api, Method>, DurableObjectRpcError, R>;
128
128
 
129
+ export type StubRpc<R, Api extends object> = <Method extends StubMethodKey<Api>>(
130
+ stub: DurableObjectStubClient<Api>,
131
+ method: Method,
132
+ ...args: StubMethodArgs<Api, Method>
133
+ ) => Effect.Effect<StubMethodCloudflareReturn<Api, Method>, DurableObjectRpcError, R>;
134
+
135
+ export type StubScopedCall<R, Api extends object> = <Method extends StubMethodKey<Api>>(
136
+ stub: DurableObjectStubClient<Api>,
137
+ method: Method,
138
+ ...args: StubMethodArgs<Api, Method>
139
+ ) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>, DurableObjectRpcError, Scope.Scope | R>;
140
+
129
141
  type DurableObjectDirectClient<R, Api extends object> = {
130
142
  readonly fetch: (
131
143
  input: RequestInfo | URL,
@@ -227,22 +239,14 @@ export type DurableObjectNamespaceEffectClient<
227
239
  *
228
240
  * Most application code should use {@link call} instead.
229
241
  */
230
- readonly rpc: <Method extends StubMethodKey<Api>>(
231
- stub: DurableObjectStubClient<Api>,
232
- method: Method,
233
- ...args: StubMethodArgs<Api, Method>
234
- ) => Effect.Effect<StubMethodCloudflareReturn<Api, Method>, DurableObjectRpcError>;
242
+ readonly rpc: StubRpc<never, Api>;
235
243
  /**
236
244
  * Invokes a Durable Object RPC method, resolves Cloudflare's RPC result, and
237
245
  * decodes the success value when the namespace was created from a definition.
238
246
  *
239
247
  * This is the normal choice when application code wants the final typed value.
240
248
  */
241
- readonly call: <Method extends StubMethodKey<Api>>(
242
- stub: DurableObjectStubClient<Api>,
243
- method: Method,
244
- ...args: StubMethodArgs<Api, Method>
245
- ) => Effect.Effect<StubMethodSuccess<Api, Method>, DurableObjectRpcError>;
249
+ readonly call: StubCall<never, Api>;
246
250
  /**
247
251
  * Invokes a Durable Object RPC method in the current `Scope`, resolves
248
252
  * Cloudflare's RPC result, decodes definition-backed success values, and
@@ -252,11 +256,7 @@ export type DurableObjectNamespaceEffectClient<
252
256
  * Use this for RPC methods that return Cloudflare RPC resources or other
253
257
  * disposable objects whose lifetime should be tied to an Effect scope.
254
258
  */
255
- readonly scopedCall: <Method extends StubMethodKey<Api>>(
256
- stub: DurableObjectStubClient<Api>,
257
- method: Method,
258
- ...args: StubMethodArgs<Api, Method>
259
- ) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>, DurableObjectRpcError, Scope.Scope>;
259
+ readonly scopedCall: StubScopedCall<never, Api>;
260
260
  /**
261
261
  * Exposes the underlying native Durable Object namespace binding.
262
262
  *
@@ -292,25 +292,9 @@ export type DurableObjectNamespaceStaticClient<
292
292
  input: RequestInfo | URL,
293
293
  init?: RequestInit,
294
294
  ) => Effect.Effect<globalThis.Response, DurableObjectFetchError, R>;
295
- readonly rpc: <Method extends StubMethodKey<Api>>(
296
- stub: DurableObjectStubClient<Api>,
297
- method: Method,
298
- ...args: StubMethodArgs<Api, Method>
299
- ) => Effect.Effect<StubMethodCloudflareReturn<Api, Method>, DurableObjectRpcError, R>;
300
- readonly call: <Method extends StubMethodKey<Api>>(
301
- stub: DurableObjectStubClient<Api>,
302
- method: Method,
303
- ...args: StubMethodArgs<Api, Method>
304
- ) => Effect.Effect<StubMethodSuccess<Api, Method>, DurableObjectRpcError, R>;
305
- readonly scopedCall: <Method extends StubMethodKey<Api>>(
306
- stub: DurableObjectStubClient<Api>,
307
- method: Method,
308
- ...args: StubMethodArgs<Api, Method>
309
- ) => Effect.Effect<
310
- Awaited<StubMethodSuccess<Api, Method>>,
311
- DurableObjectRpcError,
312
- Scope.Scope | R
313
- >;
295
+ readonly rpc: StubRpc<R, Api>;
296
+ readonly call: StubCall<R, Api>;
297
+ readonly scopedCall: StubScopedCall<R, Api>;
314
298
  readonly rawUnsafe: () => Effect.Effect<DurableObjectNamespaceClient<Api>, never, R>;
315
299
  };
316
300
 
package/src/Email.ts CHANGED
@@ -14,6 +14,8 @@ import * as Binding from "./Binding";
14
14
  import type { WorkerEnvironment } from "./Environment";
15
15
  import * as ErrorMessage from "./internal/ErrorMessage";
16
16
 
17
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
18
+
17
19
  const expectedSendEmailBinding = "Send Email binding with send()";
18
20
  const textEncoder = new TextEncoder();
19
21
 
package/src/Hyperdrive.ts CHANGED
@@ -6,6 +6,8 @@ import type { Layer } from "effect";
6
6
  import * as Binding from "./Binding";
7
7
  import type { WorkerEnvironment } from "./Environment";
8
8
 
9
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
10
+
9
11
  const expectedHyperdrive = "Hyperdrive binding with connectionString";
10
12
 
11
13
  export interface HyperdriveDefinition {
package/src/Images.ts CHANGED
@@ -33,6 +33,8 @@ import * as Binding from "./Binding";
33
33
  import type { WorkerEnvironment } from "./Environment";
34
34
  import * as ErrorMessage from "./internal/ErrorMessage";
35
35
 
36
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
37
+
36
38
  const TypeId = "~effect-cf/Images/Steps" as const;
37
39
 
38
40
  export type TypeId = typeof TypeId;
package/src/Kv.ts CHANGED
@@ -14,6 +14,8 @@ import * as Binding from "./Binding";
14
14
  import type { WorkerEnvironment } from "./Environment";
15
15
  import * as ErrorMessage from "./internal/ErrorMessage";
16
16
 
17
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
18
+
17
19
  const expectedKvNamespace =
18
20
  "KV namespace binding with get(), put(), delete(), getWithMetadata(), and list()";
19
21
 
package/src/Queue.ts CHANGED
@@ -7,6 +7,9 @@ import type { WorkerEnvironment } from "./Environment";
7
7
  import type * as QueueDefinition from "./QueueDefinition";
8
8
  import * as ErrorMessage from "./internal/ErrorMessage";
9
9
 
10
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
11
+ export type { QueueOperationError, QueueProducer } from "./QueueBinding";
12
+
10
13
  export interface QueueMessage<Body> {
11
14
  readonly raw: globalThis.Message<unknown>;
12
15
  readonly id: string;
@@ -111,13 +114,7 @@ export namespace Definition {
111
114
 
112
115
  export type LayerOptions = QueueDefinition.LayerOptions;
113
116
 
114
- export type TagClass<
115
- Self,
116
- Id extends string,
117
- Message extends QueueDefinition.Definition.Any["message"],
118
- > = QueueDefinition.TagClass<Self, Id, Message>;
119
-
120
- export { implement, Tag } from "./QueueDefinition";
117
+ export { implement, Tag, type TagClass } from "./QueueDefinition";
121
118
 
122
119
  export type Handler<ROut, Self extends QueueDefinition.Definition.Any> = QueueDefinition.Handler<
123
120
  ROut,
@@ -11,6 +11,9 @@ import type { ExecutionContext, WorkerContext } from "./Worker";
11
11
  import * as WorkerEntrypoint from "./Worker";
12
12
  import type * as RpcDefinition from "./RpcDefinition";
13
13
 
14
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
15
+ export type { QueueOperationError, QueueProducer } from "./QueueBinding";
16
+
14
17
  export interface Definition<
15
18
  Id extends string = string,
16
19
  Message extends RpcDefinition.ServiceFreeSchema = RpcDefinition.ServiceFreeSchema,
@@ -43,15 +46,15 @@ export type LayerOptions = {
43
46
  readonly binding: string;
44
47
  };
45
48
 
46
- export interface TagClass<
49
+ export type TagClass<
47
50
  Self,
48
51
  Id extends string,
49
52
  Message extends RpcDefinition.ServiceFreeSchema,
50
- > extends Context.ServiceClass<
53
+ > = Context.ServiceClass<
51
54
  Self,
52
55
  `effect-cf/Queue/${Id}`,
53
56
  QueueBinding.QueueBindingClient<Message>
54
- > {
57
+ > & {
55
58
  readonly id: Id;
56
59
  readonly message: Message;
57
60
  readonly make: <ROut, LayerError>(
@@ -83,7 +86,7 @@ export interface TagClass<
83
86
  never,
84
87
  Self
85
88
  >;
86
- }
89
+ };
87
90
 
88
91
  const makeDefinition = <Id extends string, Message extends RpcDefinition.ServiceFreeSchema>(
89
92
  id: Id,
package/src/R2.ts CHANGED
@@ -24,6 +24,8 @@ import * as Binding from "./Binding";
24
24
  import type { WorkerEnvironment } from "./Environment";
25
25
  import * as ErrorMessage from "./internal/ErrorMessage";
26
26
 
27
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
28
+
27
29
  const expectedR2Bucket =
28
30
  "R2 bucket binding with head(), get(), put(), createMultipartUpload(), resumeMultipartUpload(), delete(), and list()";
29
31
 
@@ -148,45 +148,86 @@ export type ReservedMethodName = (typeof reservedMethodNameValues)[number];
148
148
 
149
149
  export type ServiceFreeSchema = S.Codec<any, any, never, never>;
150
150
 
151
+ export const NativeSchemaTypeId: unique symbol = Symbol.for("effect-cf/RpcDefinition/NativeSchema");
152
+
153
+ /** A schema whose encoded value is passed directly to Workers RPC. */
154
+ export interface NativeSchema<Schema extends ServiceFreeSchema = ServiceFreeSchema> {
155
+ readonly [NativeSchemaTypeId]: typeof NativeSchemaTypeId;
156
+ readonly schema: Schema;
157
+ }
158
+
159
+ export type RpcSchema = ServiceFreeSchema | NativeSchema;
160
+
161
+ export type SchemaType<Schema extends RpcSchema> =
162
+ Schema extends NativeSchema<infer Native extends ServiceFreeSchema>
163
+ ? S.Schema.Type<Native>
164
+ : Schema extends ServiceFreeSchema
165
+ ? S.Schema.Type<Schema>
166
+ : never;
167
+
168
+ export type WireEncoded<Schema extends RpcSchema> =
169
+ Schema extends NativeSchema<infer Native extends ServiceFreeSchema>
170
+ ? S.Codec.Encoded<Native>
171
+ : S.Json;
172
+
173
+ /**
174
+ * Marks a schema to use its declared encoded form directly at the Workers RPC
175
+ * boundary instead of lowering it to canonical JSON.
176
+ */
177
+ export const native = <Schema extends ServiceFreeSchema>(schema: Schema): NativeSchema<Schema> => {
178
+ const nativeSchema: NativeSchema<Schema> = {
179
+ [NativeSchemaTypeId]: NativeSchemaTypeId,
180
+ schema,
181
+ };
182
+
183
+ return Object.freeze(nativeSchema);
184
+ };
185
+
186
+ const isNativeSchema = (schema: RpcSchema): schema is NativeSchema =>
187
+ Predicate.hasProperty(schema, NativeSchemaTypeId) &&
188
+ schema[NativeSchemaTypeId] === NativeSchemaTypeId;
189
+
151
190
  /**
152
191
  * Workers RPC structured-clones every value that crosses an isolate boundary
153
192
  * and rejects class instances. Declaration schemas such as `Schema.Result`
154
193
  * keep their container instance in their `Encoded` form, so the declared
155
- * schemas are lowered to their canonical JSON codec before touching the wire.
194
+ * schemas are lowered to their canonical JSON codec before touching the wire,
195
+ * unless explicitly wrapped with `native`.
156
196
  */
157
- const wireCodec = <Schema extends S.Constraint>(schema: Schema) => S.toCodecJson(schema);
197
+ const wireCodec = (schema: RpcSchema): ServiceFreeSchema =>
198
+ isNativeSchema(schema) ? schema.schema : S.toCodecJson(schema);
158
199
 
159
200
  export interface Method<
160
- Args extends ReadonlyArray<ServiceFreeSchema> = ReadonlyArray<ServiceFreeSchema>,
161
- Success extends ServiceFreeSchema = ServiceFreeSchema,
201
+ Args extends ReadonlyArray<RpcSchema> = ReadonlyArray<RpcSchema>,
202
+ Success extends RpcSchema = RpcSchema,
162
203
  > {
163
204
  readonly args: Args;
164
205
  readonly success: Success;
165
206
  }
166
207
 
167
208
  export namespace Method {
168
- export type Any = Method<ReadonlyArray<ServiceFreeSchema>, ServiceFreeSchema>;
209
+ export type Any = Method<ReadonlyArray<RpcSchema>, RpcSchema>;
169
210
 
170
- type ArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = Args extends readonly []
211
+ type ArgsFromSchemas<Args extends ReadonlyArray<RpcSchema>> = Args extends readonly []
171
212
  ? []
172
213
  : Args extends readonly [
173
- infer Head extends ServiceFreeSchema,
174
- ...infer Tail extends ReadonlyArray<ServiceFreeSchema>,
214
+ infer Head extends RpcSchema,
215
+ ...infer Tail extends ReadonlyArray<RpcSchema>,
175
216
  ]
176
- ? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
177
- : Array<S.Schema.Type<Args[number]>>;
217
+ ? [SchemaType<Head>, ...ArgsFromSchemas<Tail>]
218
+ : Array<SchemaType<Args[number]>>;
178
219
 
179
- type EncodedArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = {
180
- [Index in keyof Args]: S.Json;
220
+ type EncodedArgsFromSchemas<Args extends ReadonlyArray<RpcSchema>> = {
221
+ [Index in keyof Args]: WireEncoded<Args[Index]>;
181
222
  };
182
223
 
183
224
  export type Args<Self extends Any> = ArgsFromSchemas<Self["args"]>;
184
225
 
185
226
  export type EncodedArgs<Self extends Any> = EncodedArgsFromSchemas<Self["args"]>;
186
227
 
187
- export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
228
+ export type Success<Self extends Any> = SchemaType<Self["success"]>;
188
229
 
189
- export type EncodedSuccess<_Self extends Any> = S.Json;
230
+ export type EncodedSuccess<Self extends Any> = WireEncoded<Self["success"]>;
190
231
  }
191
232
 
192
233
  export type Methods = Record<string, Method.Any>;
@@ -237,16 +278,16 @@ export const assertNoReservedMethods = <
237
278
  }
238
279
  };
239
280
 
240
- export function method<Success extends ServiceFreeSchema>(definition: {
281
+ export function method<Success extends RpcSchema>(definition: {
241
282
  readonly success: Success;
242
283
  }): Method<readonly [], Success>;
243
284
  export function method<
244
- const Args extends ReadonlyArray<ServiceFreeSchema>,
245
- Success extends ServiceFreeSchema,
285
+ const Args extends ReadonlyArray<RpcSchema>,
286
+ Success extends RpcSchema,
246
287
  >(definition: { readonly args: Args; readonly success: Success }): Method<Args, Success>;
247
288
  export function method(definition: {
248
- readonly args?: ReadonlyArray<ServiceFreeSchema>;
249
- readonly success: ServiceFreeSchema;
289
+ readonly args?: ReadonlyArray<RpcSchema>;
290
+ readonly success: RpcSchema;
250
291
  }) {
251
292
  return {
252
293
  args: definition.args ?? [],
@@ -276,9 +317,8 @@ export const decodeArgs = Effect.fnUntraced(function* <
276
317
  });
277
318
  }
278
319
 
279
- const decoded = yield* S.decodeUnknownEffect(wireCodec(S.Tuple(methodDefinition.args)))(
280
- args,
281
- ).pipe(
320
+ const codecs = methodDefinition.args.map((schema) => wireCodec(schema));
321
+ const decoded = yield* S.decodeUnknownEffect(S.Tuple(codecs))(args).pipe(
282
322
  Effect.mapError(
283
323
  (cause) =>
284
324
  new RpcArgumentDecodeError({
@@ -315,9 +355,8 @@ export const encodeArgs = Effect.fnUntraced(function* <
315
355
  });
316
356
  }
317
357
 
318
- const encoded = yield* S.encodeUnknownEffect(wireCodec(S.Tuple(methodDefinition.args)))(
319
- args,
320
- ).pipe(
358
+ const codecs = methodDefinition.args.map((schema) => wireCodec(schema));
359
+ const encoded = yield* S.encodeUnknownEffect(S.Tuple(codecs))(args).pipe(
321
360
  Effect.mapError(
322
361
  (cause) =>
323
362
  new RpcArgumentEncodeError({
package/src/Sandbox.ts CHANGED
@@ -47,6 +47,8 @@ import * as ErrorMessage from "./internal/ErrorMessage";
47
47
  import { CurrentTargets } from "./internal/RpcTargets";
48
48
  import * as RpcTargets from "./RpcTargets";
49
49
 
50
+ export type { BindingNotFoundError, BindingValidationError } from "./Binding";
51
+
50
52
  export type {
51
53
  BackupOptions,
52
54
  CheckChangesOptions,