1import * as Cloudflare from "@/Cloudflare";
2import * as Alchemy from "@/index";
3import * as Effect from "effect/Effect";
4import * as path from "pathe";
5import AiSearchEffectBindingsWorker from "./effect-bindings-worker.ts";
6
7/**
8 * Deploys two Workers covering both AI Search binding flavors via both
9 * access styles:
10 * - The async worker takes the bindings through `env` (`SEARCH` is a
11 * single-instance `ai_search` binding; `NS` is an `ai_search_namespace`
12 * binding) and reads them as raw runtime objects.
13 * - The Effect worker attaches the same two binding flavors via
14 * `Cloudflare.AI.Search.Search(...)` / `Cloudflare.AI.SearchNamespace(...)`
15 * and reads them through the Effect-native client.
16 *
17 * The single-instance binding is sourced from the `AiSearch` construct (not
18 * the low-level `AiSearchInstance`), proving the construct's result is usable
19 * anywhere an `AiSearchInstance` is — here, passed straight to a Worker's
20 * `env`. The construct's `source` references the bucket and the worker's
21 * `env` references the search + namespace, so the engine orders the deploy
22 * bucket → instance/namespace → worker.
23 */
24export default Alchemy.Stack(
25 "AiSearchBindingsStack",
26 {
27 providers: Cloudflare.providers(),
28 state: Cloudflare.state(),
29 },
30 Effect.gen(function* () {
31 const bucket = yield* Cloudflare.R2.Bucket("AiSearchBindingBucket", {
32 forceDestroy: true,
33 });
34 const namespace = yield* Cloudflare.AI.SearchNamespace(
35 "AiSearchBindingNs",
36 {},
37 );
38 const search = yield* Cloudflare.AI.Search("AiSearchBindingInstance", {
39 source: bucket,
40 });
41 const asyncWorker = yield* Cloudflare.Worker("AiSearchBindingsWorker", {
42 main: path.resolve(import.meta.dirname, "bindings-worker.ts"),
43 workersDev: true,
44 env: { SEARCH: search, NS: namespace },
45 });
46 const effectWorker = yield* AiSearchEffectBindingsWorker;
47 return {
48 url: asyncWorker.url.as<string>(),
49 effectUrl: effectWorker.url.as<string>(),
50 };
51 }),
52);