1import * as Cloudflare from "@/Cloudflare";
2import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as vectorize from "@distilled.cloud/cloudflare/vectorize";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10
11const { test } = Test.make({ providers: Cloudflare.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18test.provider(
19 "create and delete index with explicit dimensions",
20 (stack) =>
21 Effect.gen(function* () {
22 const { accountId } = yield* yield* CloudflareEnvironment;
23
24 yield* stack.destroy();
25
26 const index = yield* stack.deploy(
27 Cloudflare.Vectorize.Index("DefaultIndex", {
28 dimensions: 768,
29 metric: "cosine",
30 }),
31 );
32
33 expect(index.indexName).toBeDefined();
34 expect(index.dimensions).toEqual(768);
35 expect(index.metric).toEqual("cosine");
36
37 const actual = yield* vectorize.getIndex({
38 accountId,
39 indexName: index.indexName,
40 });
41 expect(actual.name).toEqual(index.indexName);
42 expect(actual.config?.dimensions).toEqual(768);
43
44 yield* stack.destroy();
45
46 yield* waitForDelete(accountId, index.indexName);
47 }).pipe(logLevel),
48 { tags: ["provider:cloudflare", "provider:cloudflare:vectorize", "live"] },
49);
50
51test.provider(
52 "create index from a preset",
53 (stack) =>
54 Effect.gen(function* () {
55 const { accountId } = yield* yield* CloudflareEnvironment;
56
57 yield* stack.destroy();
58
59 const index = yield* stack.deploy(
60 Cloudflare.Vectorize.Index("PresetIndex", {
61 preset: "@cf/baai/bge-base-en-v1.5",
62 description: "preset index",
63 }),
64 );
65
66 const actual = yield* vectorize.getIndex({
67 accountId,
68 indexName: index.indexName,
69 });
70 // bge-base resolves to 768 dimensions.
71 expect(actual.config?.dimensions).toEqual(768);
72 expect(index.description).toEqual("preset index");
73
74 yield* stack.destroy();
75
76 yield* waitForDelete(accountId, index.indexName);
77 }).pipe(logLevel),
78 { tags: ["provider:cloudflare", "provider:cloudflare:vectorize", "live"] },
79);
80
81test.provider(
82 "replaces index when dimensions change",
83 (stack) =>
84 Effect.gen(function* () {
85 const { accountId } = yield* yield* CloudflareEnvironment;
86
87 yield* stack.destroy();
88
89 const index = yield* stack.deploy(
90 Cloudflare.Vectorize.Index("ReplaceIndex", {
91 dimensions: 32,
92 metric: "cosine",
93 }),
94 );
95 expect(index.dimensions).toEqual(32);
96 expect(index.metric).toEqual("cosine");
97
98 const replaced = yield* stack.deploy(
99 Cloudflare.Vectorize.Index("ReplaceIndex", {
100 dimensions: 64,
101 metric: "euclidean",
102 }),
103 );
104
105 const actual = yield* vectorize.getIndex({
106 accountId,
107 indexName: replaced.indexName,
108 });
109 expect(actual.config?.dimensions).toEqual(64);
110 expect(actual.config?.metric).toEqual("euclidean");
111
112 yield* stack.destroy();
113
114 yield* waitForDelete(accountId, replaced.indexName);
115 }).pipe(logLevel),
116 { tags: ["provider:cloudflare", "provider:cloudflare:vectorize", "live"] },
117);
118
119test.provider(
120 "list enumerates the deployed index",
121 (stack) =>
122 Effect.gen(function* () {
123 const { accountId } = yield* yield* CloudflareEnvironment;
124
125 yield* stack.destroy();
126
127 const index = yield* stack.deploy(
128 Cloudflare.Vectorize.Index("ListIndex", {
129 dimensions: 768,
130 metric: "cosine",
131 }),
132 );
133
134 const provider = yield* Provider.findProvider(Cloudflare.Vectorize.Index);
135 const all = yield* provider.list();
136
137 expect(all.some((x) => x.indexName === index.indexName)).toBe(true);
138
139 yield* stack.destroy();
140
141 yield* waitForDelete(accountId, index.indexName);
142 }).pipe(logLevel),
143 { tags: ["provider:cloudflare", "provider:cloudflare:vectorize", "live"] },
144);
145
146const waitForDelete = (accountId: string, indexName: string) =>
147 vectorize.getIndex({ accountId, indexName }).pipe(
148 Effect.flatMap((index) =>
149 index.name === indexName
150 ? Effect.fail({ _tag: "IndexNotDeleted" } as const)
151 : Effect.void,
152 ),
153 Effect.catchTag(["NotFound", "Gone"], () => Effect.void),
154 Effect.retry({
155 while: (e) => e._tag === "IndexNotDeleted",
156 schedule: Schedule.max([
157 Schedule.exponential("500 millis"),
158 Schedule.recurs(10),
159 ]),
160 }),
161 );