| 1 | /// <reference types="@cloudflare/workers-types" /> |
| 2 | |
| 3 | import type { Pipeline } from "cloudflare:pipelines"; |
| 4 | import type * as Effect from "effect/Effect"; |
| 5 | import type { Redacted } from "effect/Redacted"; |
| 6 | import type * as Stream from "effect/Stream"; |
| 7 | import type { Output } from "../../Output.ts"; |
| 8 | // Aliased so the ambient Cloudflare `Rpc` namespace (from |
| 9 | // @cloudflare/workers-types, referenced above) stays reachable for |
| 10 | // `Rpc.DurableObjectBranded`. |
| 11 | import type { Rpc as AlchemyRpc } from "../../Rpc.ts"; |
| 12 | import type { WorkflowBinding, WorkflowLike } from "../Workflows/Workflow.ts"; |
| 13 | // NOTE: import the service modules directly rather than `import * as Cloudflare |
| 14 | // from "../index.ts"`. Importing the whole Cloudflare barrel here creates a |
| 15 | // circular re-export when the barrel does `export * from "./Workers/index.ts"` |
| 16 | // (the barrel must enumerate this module, which imports the barrel back), which |
| 17 | // collapses inference of the large `providers()` collection to `unknown`. |
| 18 | import type * as AI from "../AI/index.ts"; |
| 19 | import type * as AnalyticsEngine from "../AnalyticsEngine/index.ts"; |
| 20 | import type * as ArtifactsNs from "../Artifacts/index.ts"; |
| 21 | import type { Container } from "../Containers/Container.ts"; |
| 22 | import type * as D1 from "../D1/index.ts"; |
| 23 | import type * as Email from "../Email/index.ts"; |
| 24 | import type * as FlagshipNs from "../Flagship/index.ts"; |
| 25 | import type * as HyperdriveNs from "../Hyperdrive/index.ts"; |
| 26 | import type * as ImagesNs from "../Images/index.ts"; |
| 27 | import type * as KV from "../KV/index.ts"; |
| 28 | import type * as PipelinesNs from "../Pipelines/index.ts"; |
| 29 | import type * as Queues from "../Queues/index.ts"; |
| 30 | import type * as R2 from "../R2/index.ts"; |
| 31 | import type * as StreamNs from "../Stream/index.ts"; |
| 32 | import type { VpcService } from "../VpcService/VpcService.ts"; |
| 33 | import type { VpcServiceLookup } from "../VpcService/VpcServiceLookup.ts"; |
| 34 | import type { DispatchNamespace as DispatchNamespaceResource } from "../WorkersForPlatforms/DispatchNamespace.ts"; |
| 35 | import type { AIBinding } from "./AIBinding.ts"; |
| 36 | import type { Assets } from "./Assets.ts"; |
| 37 | import type * as WorkerOnlyBinding from "./Binding.ts"; |
| 38 | import type { BrowserBinding } from "./BrowserBinding.ts"; |
| 39 | import type { DurableObjectLike } from "./DurableObject.ts"; |
| 40 | import type { RateLimitBinding } from "./RateLimitBinding.ts"; |
| 41 | import type { RpcErrorEnvelope, RpcStreamEnvelope } from "./Rpc.ts"; |
| 42 | import type { SecretKeyBinding } from "./SecretKeyBinding.ts"; |
| 43 | import type { VersionMetadataBinding } from "./VersionMetadataBinding.ts"; |
| 44 | import type { Worker } from "./Worker.ts"; |
| 45 | import type { WorkerEntrypointBinding } from "./WorkerEntrypoint.ts"; |
| 46 | import type { WorkerLoader as WorkerLoaderResource } from "./WorkerLoader.ts"; |
| 47 | |
| 48 | export type InferEnv<W> = |
| 49 | W extends Effect.Effect<infer A, infer _E, infer _R> |
| 50 | ? InferEnv<A> |
| 51 | : W extends Worker<any> |
| 52 | ? InferEnv<Exclude<W["Props"]["env"], undefined>> |
| 53 | : W extends { readonly "~alchemy/WorkerEnv": infer Env } |
| 54 | ? InferEnv<Env> |
| 55 | : { |
| 56 | [k in keyof W]: GetBindingType<W[k]>; |
| 57 | }; |
| 58 | |
| 59 | export type GetBindingType<T> = |
| 60 | // A named-entrypoint service binding (`Cloudflare.WorkerEntrypoint`). |
| 61 | // Tested first: the marker *contains* a Worker, so the later Worker |
| 62 | // branches must never see it. |
| 63 | T extends WorkerEntrypointBinding<infer Entrypoint> |
| 64 | ? Service<Entrypoint> |
| 65 | : // A Container bound in `env` is a container-backed Durable Object class — |
| 66 | // the runtime binding is the class's namespace. Must be tested BEFORE the |
| 67 | // generic Effect unwrap: a Container declaration is itself an Effect. |
| 68 | T extends Container.Decl<any, any, any, any, infer DOShape> |
| 69 | ? DurableObjectNamespace<DOShape & Rpc.DurableObjectBranded> |
| 70 | : // A Worker-only binding lifted by `.pipe(Alchemy.remote())` — the brand |
| 71 | // carries the underlying binding value type; recurse on it. Must also |
| 72 | // be tested BEFORE the generic Effect unwrap (its Effect `A` is the |
| 73 | // runtime client, not the binding value). |
| 74 | T extends WorkerOnlyBinding.BindingEffect<infer B> |
| 75 | ? GetBindingType<B> |
| 76 | : // `R2.S3Credentials` is an Effect whose `A` is the Effect-native |
| 77 | // accessor; on an async Worker's `env` it arrives as a JSON string |
| 78 | // (a `secret_text` when deployed). Tested BEFORE the Effect unwrap. |
| 79 | T extends R2.S3Credentials |
| 80 | ? string |
| 81 | : // `Worker.URL` (an Effect resolving to a deferred string accessor) needs |
| 82 | // no case of its own: the generic Effect unwrap below reduces it to |
| 83 | // `string` via the fallthrough. |
| 84 | T extends |
| 85 | | Output<infer A, infer _Req> |
| 86 | | Effect.Effect<infer A, infer _E, infer _R> |
| 87 | ? GetBindingType<A> |
| 88 | : T extends FlagshipNs.App |
| 89 | ? Flagship |
| 90 | : T extends Assets |
| 91 | ? Service |
| 92 | : T extends AlchemyRpc<infer Shape extends object> |
| 93 | ? RpcWireShape<Shape> & Service |
| 94 | : T extends D1.Database |
| 95 | ? D1Database |
| 96 | : T extends R2.Bucket |
| 97 | ? R2Bucket |
| 98 | : T extends KV.Namespace |
| 99 | ? KVNamespace |
| 100 | : T extends DispatchNamespaceResource |
| 101 | ? DispatchNamespace |
| 102 | : T extends Queues.Queue |
| 103 | ? Queue<unknown> |
| 104 | : T extends AI.Gateway |
| 105 | ? Ai |
| 106 | : T extends AIBinding |
| 107 | ? Ai |
| 108 | : T extends AI.Search |
| 109 | ? AiSearchInstance |
| 110 | : T extends AI.SearchNamespace |
| 111 | ? AiSearchNamespace |
| 112 | : T extends Email.SendEmail |
| 113 | ? SendEmail |
| 114 | : T extends AnalyticsEngine.Dataset |
| 115 | ? AnalyticsEngineDataset |
| 116 | : T extends ArtifactsNs.Namespace |
| 117 | ? Artifacts |
| 118 | : T extends RateLimitBinding |
| 119 | ? RateLimit |
| 120 | : T extends SecretKeyBinding |
| 121 | ? CryptoKey |
| 122 | : T extends ImagesNs.ImagesBinding |
| 123 | ? ImagesBinding |
| 124 | : T extends BrowserBinding |
| 125 | ? BrowserRun |
| 126 | : // The ambient global `StreamBinding` from |
| 127 | // @cloudflare/workers-types (the alchemy binding value |
| 128 | // type of the same name is only reachable as |
| 129 | // `StreamNs.StreamBinding`). |
| 130 | T extends StreamNs.StreamBinding |
| 131 | ? StreamBinding |
| 132 | : T extends HyperdriveNs.Connection |
| 133 | ? Hyperdrive |
| 134 | : T extends VersionMetadataBinding |
| 135 | ? WorkerVersionMetadata |
| 136 | : T extends WorkerLoaderResource |
| 137 | ? WorkerLoader |
| 138 | : T extends |
| 139 | | WorkflowLike< |
| 140 | infer Params |
| 141 | > |
| 142 | | WorkflowBinding< |
| 143 | infer Params |
| 144 | > |
| 145 | ? Workflow<Params> |
| 146 | : T extends DurableObjectLike |
| 147 | ? DurableObjectNamespace< |
| 148 | Exclude< |
| 149 | T["Shape"], |
| 150 | undefined |
| 151 | > |
| 152 | > |
| 153 | : T extends |
| 154 | | VpcService |
| 155 | | VpcServiceLookup |
| 156 | ? Fetcher |
| 157 | : T extends |
| 158 | | PipelinesNs.Stream |
| 159 | | PipelinesNs.LegacyPipeline |
| 160 | ? Pipeline |
| 161 | : T extends Redacted<any> |
| 162 | ? // redacteds are always stored as secret_text, so are always string |
| 163 | // we JSON.stringify when not a Redacted<string> |
| 164 | string |
| 165 | : T; |
| 166 | |
| 167 | /** |
| 168 | * Cloudflare service-binding wire shape for an Effect-native Worker. |
| 169 | * |
| 170 | * Effect/Stream return values are encoded as envelopes on the wire (see |
| 171 | * `RpcErrorEnvelope`, `RpcStreamEnvelope`), so the mapped types reflect what |
| 172 | * the raw binding actually resolves to. `fetch` is dropped from the user |
| 173 | * shape and re-introduced via `Service` so callers get the standard |
| 174 | * `(input, init?) => Promise<Response>` signature. |
| 175 | * |
| 176 | * Use {@link toRpcAsync} to wrap a binding into a Promise<T>-flavored view |
| 177 | * where envelopes are decoded for you. |
| 178 | */ |
| 179 | export type RpcWireShape<Shape> = { |
| 180 | [K in keyof Shape as K extends "fetch" ? never : K]: Shape[K] extends ( |
| 181 | ...args: infer A |
| 182 | ) => Effect.Effect<infer T, any, any> |
| 183 | ? (...args: A) => Promise<T | RpcErrorEnvelope> |
| 184 | : Shape[K] extends (...args: infer A) => Stream.Stream<any, any, any> |
| 185 | ? (...args: A) => Promise<RpcStreamEnvelope | RpcErrorEnvelope> |
| 186 | : Shape[K] extends Effect.Effect<infer T, any, any> |
| 187 | ? Promise<T | RpcErrorEnvelope> |
| 188 | : Shape[K] extends Stream.Stream<any, any, any> |
| 189 | ? Promise<RpcStreamEnvelope | RpcErrorEnvelope> |
| 190 | : Shape[K] extends (...args: infer A) => infer R |
| 191 | ? (...args: A) => Promise<Awaited<R>> |
| 192 | : Promise<Shape[K]>; |
| 193 | }; |