alchemy 0.8.0 → 0.9.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.
- package/lib/alchemy.d.ts +11 -18
- package/lib/alchemy.js +34 -39
- package/lib/apply.js +3 -1
- package/lib/cloudflare/api-error.js +1 -1
- package/lib/cloudflare/bindings.d.ts +2 -1
- package/lib/cloudflare/bound.d.ts +2 -1
- package/lib/cloudflare/index.d.ts +3 -0
- package/lib/cloudflare/index.js +3 -0
- package/lib/cloudflare/pipeline.js +0 -3
- package/lib/cloudflare/vectorize-index.d.ts +136 -0
- package/lib/cloudflare/vectorize-index.js +170 -0
- package/lib/cloudflare/vectorize-metadata-index.d.ts +95 -0
- package/lib/cloudflare/vectorize-metadata-index.js +120 -0
- package/lib/cloudflare/vite-site.d.ts +39 -0
- package/lib/cloudflare/vite-site.js +57 -0
- package/lib/cloudflare/worker.js +7 -0
- package/lib/cloudflare/wrangler.json.d.ts +7 -0
- package/lib/cloudflare/wrangler.json.js +11 -1
- package/lib/destroy.js +14 -2
- package/lib/fs/file-system-state-store.d.ts +1 -0
- package/lib/fs/file-system-state-store.js +23 -13
- package/lib/scope.d.ts +1 -1
- package/lib/scope.js +5 -2
- package/lib/state.d.ts +2 -2
- package/lib/test/bun.d.ts +1 -6
- package/lib/test/bun.js +18 -37
- package/package.json +1 -1
- package/src/alchemy.ts +51 -45
- package/src/apply.ts +6 -2
- package/src/cloudflare/api-error.ts +1 -1
- package/src/cloudflare/bindings.ts +2 -0
- package/src/cloudflare/bound.ts +8 -5
- package/src/cloudflare/index.ts +3 -0
- package/src/cloudflare/pipeline.ts +0 -4
- package/src/cloudflare/vectorize-index.ts +331 -0
- package/src/cloudflare/vectorize-metadata-index.ts +239 -0
- package/src/cloudflare/vite-site.ts +111 -0
- package/src/cloudflare/worker.ts +6 -0
- package/src/cloudflare/wrangler.json.ts +19 -1
- package/src/destroy.ts +25 -8
- package/src/fs/file-system-state-store.ts +23 -14
- package/src/scope.ts +9 -5
- package/src/state.ts +3 -3
- package/src/test/bun.ts +31 -54
package/src/alchemy.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { destroy, DestroyedSignal } from "./destroy";
|
|
5
|
+
import type { PendingResource } from "./resource";
|
|
5
6
|
import { Scope } from "./scope";
|
|
6
7
|
import { secret } from "./secret";
|
|
7
8
|
import type { StateStoreType } from "./state";
|
|
8
9
|
|
|
9
|
-
// TODO: support browser
|
|
10
|
-
const DEFAULT_STAGE = process.env.ALCHEMY_STAGE ?? process.env.USER ?? "dev";
|
|
11
|
-
|
|
12
10
|
/**
|
|
13
11
|
* Type alias for semantic highlighting of `alchemy` as a type keyword
|
|
14
12
|
*/
|
|
@@ -37,7 +35,6 @@ export const alchemy: Alchemy = _alchemy as any;
|
|
|
37
35
|
* await app.finalize();
|
|
38
36
|
*/
|
|
39
37
|
export interface Alchemy {
|
|
40
|
-
scope: typeof scope;
|
|
41
38
|
run: typeof run;
|
|
42
39
|
destroy: typeof destroy;
|
|
43
40
|
|
|
@@ -63,7 +60,7 @@ export interface Alchemy {
|
|
|
63
60
|
* password: process.env.SECRET_PASSPHRASE
|
|
64
61
|
* });
|
|
65
62
|
*/
|
|
66
|
-
(
|
|
63
|
+
(appName: string, options?: Omit<AlchemyOptions, "appName">): Promise<Scope>;
|
|
67
64
|
/**
|
|
68
65
|
* Template literal tag that supports file interpolation for documentation.
|
|
69
66
|
* Automatically formats the content and appends file contents as code blocks.
|
|
@@ -95,11 +92,12 @@ async function _alchemy(
|
|
|
95
92
|
): Promise<Scope | string | never> {
|
|
96
93
|
if (typeof args[0] === "string") {
|
|
97
94
|
const [appName, options] = args as [string, AlchemyOptions?];
|
|
98
|
-
const root =
|
|
95
|
+
const root = new Scope({
|
|
99
96
|
...options,
|
|
100
97
|
appName,
|
|
101
98
|
stage: options?.stage,
|
|
102
99
|
});
|
|
100
|
+
root.enter();
|
|
103
101
|
if (options?.phase === "destroy") {
|
|
104
102
|
await destroy(root);
|
|
105
103
|
return process.exit(0);
|
|
@@ -215,7 +213,6 @@ async function _alchemy(
|
|
|
215
213
|
}
|
|
216
214
|
_alchemy.destroy = destroy;
|
|
217
215
|
_alchemy.run = run;
|
|
218
|
-
_alchemy.scope = scope;
|
|
219
216
|
_alchemy.secret = secret;
|
|
220
217
|
_alchemy.env = env;
|
|
221
218
|
|
|
@@ -256,7 +253,6 @@ export interface AlchemyOptions {
|
|
|
256
253
|
* @default false
|
|
257
254
|
*/
|
|
258
255
|
quiet?: boolean;
|
|
259
|
-
|
|
260
256
|
/**
|
|
261
257
|
* A passphrase to use to encrypt/decrypt secrets.
|
|
262
258
|
* Required if using alchemy.secret() in this scope.
|
|
@@ -264,35 +260,16 @@ export interface AlchemyOptions {
|
|
|
264
260
|
password?: string;
|
|
265
261
|
}
|
|
266
262
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
* const resource = await Resource("my-resource", {
|
|
278
|
-
* apiKey: alchemy.secret(process.env.API_KEY)
|
|
279
|
-
* });
|
|
280
|
-
*/
|
|
281
|
-
function scope(
|
|
282
|
-
id: string | undefined,
|
|
283
|
-
options?: AlchemyOptions
|
|
284
|
-
// TODO: maybe we want to allow using _ = await alchemy.scope(import.meta)
|
|
285
|
-
// | [meta: ImportMeta]
|
|
286
|
-
): Scope {
|
|
287
|
-
const scope = new Scope({
|
|
288
|
-
...options,
|
|
289
|
-
appName: options?.appName,
|
|
290
|
-
stage: options?.stage ?? DEFAULT_STAGE,
|
|
291
|
-
scopeName: id,
|
|
292
|
-
parent: options?.parent ?? Scope.get(),
|
|
293
|
-
});
|
|
294
|
-
scope.enter();
|
|
295
|
-
return scope;
|
|
263
|
+
export interface ScopeOptions extends AlchemyOptions {
|
|
264
|
+
enter: boolean;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface RunOptions extends AlchemyOptions {
|
|
268
|
+
/**
|
|
269
|
+
* @default false
|
|
270
|
+
*/
|
|
271
|
+
// TODO(sam): this is an awful hack to differentiate between naked scopes and resources
|
|
272
|
+
isResource?: boolean;
|
|
296
273
|
}
|
|
297
274
|
|
|
298
275
|
/**
|
|
@@ -315,7 +292,7 @@ async function run<T>(
|
|
|
315
292
|
| [id: string, fn: (this: Scope, scope: Scope) => Promise<T>]
|
|
316
293
|
| [
|
|
317
294
|
id: string,
|
|
318
|
-
options:
|
|
295
|
+
options: RunOptions,
|
|
319
296
|
fn: (this: Scope, scope: Scope) => Promise<T>,
|
|
320
297
|
]
|
|
321
298
|
): Promise<T> {
|
|
@@ -324,20 +301,49 @@ async function run<T>(
|
|
|
324
301
|
? [args[0], undefined, args[1]]
|
|
325
302
|
: (args as [
|
|
326
303
|
string,
|
|
327
|
-
|
|
304
|
+
RunOptions,
|
|
328
305
|
(this: Scope, scope: Scope) => Promise<T>,
|
|
329
306
|
]);
|
|
330
|
-
const
|
|
307
|
+
const _scope = new Scope({
|
|
308
|
+
...options,
|
|
309
|
+
scopeName: id,
|
|
310
|
+
});
|
|
331
311
|
try {
|
|
332
|
-
|
|
312
|
+
if (options?.isResource !== true && _scope.parent) {
|
|
313
|
+
// TODO(sam): this is an awful hack to differentiate between naked scopes and resources
|
|
314
|
+
const seq = _scope.parent.seq();
|
|
315
|
+
const output = {
|
|
316
|
+
ID: id,
|
|
317
|
+
FQN: "",
|
|
318
|
+
Kind: "alchemy::Scope",
|
|
319
|
+
Scope: _scope,
|
|
320
|
+
Seq: seq,
|
|
321
|
+
} as const;
|
|
322
|
+
const resource = {
|
|
323
|
+
kind: "scope",
|
|
324
|
+
id,
|
|
325
|
+
seq,
|
|
326
|
+
data: {},
|
|
327
|
+
fqn: "",
|
|
328
|
+
props: {},
|
|
329
|
+
status: "created",
|
|
330
|
+
output,
|
|
331
|
+
} as const;
|
|
332
|
+
await _scope.parent!.state.set(id, resource);
|
|
333
|
+
_scope.parent!.resources.set(
|
|
334
|
+
id,
|
|
335
|
+
Object.assign(Promise.resolve(resource), output) as PendingResource
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return await _scope.run(async () => fn.bind(_scope)(_scope));
|
|
333
339
|
} catch (error) {
|
|
334
340
|
if (!(error instanceof DestroyedSignal)) {
|
|
335
|
-
|
|
336
|
-
|
|
341
|
+
console.log(error);
|
|
342
|
+
_scope.fail();
|
|
337
343
|
}
|
|
338
344
|
throw error;
|
|
339
345
|
} finally {
|
|
340
|
-
await
|
|
346
|
+
await _scope.finalize();
|
|
341
347
|
}
|
|
342
348
|
}
|
|
343
349
|
|
package/src/apply.ts
CHANGED
|
@@ -107,8 +107,12 @@ export async function apply<Out extends Resource>(
|
|
|
107
107
|
},
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
const output = await alchemy.run(
|
|
111
|
-
|
|
110
|
+
const output = await alchemy.run(
|
|
111
|
+
resource.ID,
|
|
112
|
+
{
|
|
113
|
+
isResource: true,
|
|
114
|
+
},
|
|
115
|
+
async () => provider.handler.bind(ctx)(resource.ID, props)
|
|
112
116
|
);
|
|
113
117
|
if (!quiet) {
|
|
114
118
|
console.log(
|
|
@@ -49,7 +49,7 @@ export async function handleApiError(
|
|
|
49
49
|
resourceName: string
|
|
50
50
|
): Promise<never> {
|
|
51
51
|
const json: any = await response.json();
|
|
52
|
-
const errors: { message: string }[] = json
|
|
52
|
+
const errors: { message: string }[] = json?.errors || [
|
|
53
53
|
{ message: response.statusText },
|
|
54
54
|
];
|
|
55
55
|
const errorMessage = `Error ${action} ${resourceType} '${resourceName}': ${errors[0]?.message || response.statusText}`;
|
|
@@ -11,6 +11,7 @@ import type { DurableObjectNamespace } from "./durable-object-namespace";
|
|
|
11
11
|
import type { KVNamespace } from "./kv-namespace";
|
|
12
12
|
import type { Pipeline } from "./pipeline";
|
|
13
13
|
import type { Queue } from "./queue";
|
|
14
|
+
import type { VectorizeIndex } from "./vectorize-index";
|
|
14
15
|
import type { Worker } from "./worker";
|
|
15
16
|
import type { Workflow } from "./workflow";
|
|
16
17
|
|
|
@@ -31,6 +32,7 @@ export type Binding =
|
|
|
31
32
|
| R2Bucket
|
|
32
33
|
| Secret
|
|
33
34
|
| string
|
|
35
|
+
| VectorizeIndex
|
|
34
36
|
| Worker
|
|
35
37
|
| Workflow;
|
|
36
38
|
|
package/src/cloudflare/bound.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { DurableObjectNamespace as _DurableObjectNamespace } from "./durabl
|
|
|
8
8
|
import type { KVNamespace as _KVNamespace } from "./kv-namespace";
|
|
9
9
|
import type { Pipeline as _Pipeline } from "./pipeline";
|
|
10
10
|
import type { Queue as _Queue } from "./queue";
|
|
11
|
+
import type { VectorizeIndex as _VectorizeIndex } from "./vectorize-index";
|
|
11
12
|
import type { Worker as _Worker } from "./worker";
|
|
12
13
|
import type { Workflow as _Workflow } from "./workflow";
|
|
13
14
|
|
|
@@ -27,8 +28,10 @@ export type Bound<T extends Binding> = T extends _DurableObjectNamespace
|
|
|
27
28
|
? Workflow<P>
|
|
28
29
|
: T extends _D1Database
|
|
29
30
|
? D1Database
|
|
30
|
-
: T extends
|
|
31
|
-
?
|
|
32
|
-
: T extends
|
|
33
|
-
?
|
|
34
|
-
:
|
|
31
|
+
: T extends _VectorizeIndex
|
|
32
|
+
? VectorizeIndex
|
|
33
|
+
: T extends _Queue
|
|
34
|
+
? Queue
|
|
35
|
+
: T extends _Pipeline<infer R>
|
|
36
|
+
? Pipeline<R>
|
|
37
|
+
: Service;
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -13,6 +13,9 @@ export * from "./permission-groups";
|
|
|
13
13
|
export * from "./pipeline";
|
|
14
14
|
export * from "./queue";
|
|
15
15
|
export * from "./r2-rest-state-store";
|
|
16
|
+
export * from "./vectorize-index";
|
|
17
|
+
export * from "./vectorize-metadata-index";
|
|
18
|
+
export * from "./vite-site";
|
|
16
19
|
export * from "./worker";
|
|
17
20
|
export { Workflow } from "./workflow";
|
|
18
21
|
export * from "./wrangler.json";
|
|
@@ -318,7 +318,6 @@ export const Pipeline = Resource("cloudflare::Pipeline", async function <
|
|
|
318
318
|
const pipelineName = props.name || id;
|
|
319
319
|
|
|
320
320
|
if (this.phase === "delete") {
|
|
321
|
-
console.log("Deleting Cloudflare Pipeline:", pipelineName);
|
|
322
321
|
if (props.delete !== false) {
|
|
323
322
|
// Delete Pipeline
|
|
324
323
|
await deletePipeline(api, pipelineName);
|
|
@@ -330,13 +329,10 @@ export const Pipeline = Resource("cloudflare::Pipeline", async function <
|
|
|
330
329
|
let pipelineData: CloudflarePipelineResponse;
|
|
331
330
|
|
|
332
331
|
if (this.phase === "create") {
|
|
333
|
-
console.log("Creating Cloudflare Pipeline:", pipelineName);
|
|
334
332
|
pipelineData = await createPipeline(api, pipelineName, props);
|
|
335
333
|
} else {
|
|
336
334
|
// Update operation
|
|
337
335
|
if (this.output?.id) {
|
|
338
|
-
console.log("Updating Cloudflare Pipeline:", pipelineName);
|
|
339
|
-
|
|
340
336
|
// Check if name is being changed, which is not allowed
|
|
341
337
|
if (props.name !== this.output.name) {
|
|
342
338
|
throw new Error(
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import type { Context } from "../context";
|
|
2
|
+
import { Resource } from "../resource";
|
|
3
|
+
import {
|
|
4
|
+
CloudflareApi,
|
|
5
|
+
createCloudflareApi,
|
|
6
|
+
type CloudflareApiOptions,
|
|
7
|
+
} from "./api";
|
|
8
|
+
import { CloudflareApiError, handleApiError } from "./api-error";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Properties for creating or updating a Vectorize Index
|
|
12
|
+
*/
|
|
13
|
+
export interface VectorizeIndexProps extends CloudflareApiOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Name of the index
|
|
16
|
+
*/
|
|
17
|
+
name: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Optional description of the index
|
|
21
|
+
*/
|
|
22
|
+
description?: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Dimensions of the vectors
|
|
26
|
+
*/
|
|
27
|
+
dimensions: number;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Distance metric used for vector similarity
|
|
31
|
+
*/
|
|
32
|
+
metric: "cosine" | "euclidean" | "dot_product";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether to delete the index if removed
|
|
36
|
+
* If set to false, the index will remain but the resource will be removed from state
|
|
37
|
+
*
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
|
+
delete?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether to adopt an existing index with the same name if it exists
|
|
44
|
+
* If true and an index with the same name exists, it will be adopted rather than creating a new one
|
|
45
|
+
*
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
adopt?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Output returned after Vectorize Index creation/update
|
|
53
|
+
*/
|
|
54
|
+
export interface VectorizeIndex
|
|
55
|
+
extends Resource<"cloudflare::VectorizeIndex">,
|
|
56
|
+
VectorizeIndexProps {
|
|
57
|
+
type: "vectorize";
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The unique identifier for the index (same as name)
|
|
61
|
+
*/
|
|
62
|
+
id: string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Time at which the index was created
|
|
66
|
+
*/
|
|
67
|
+
createdAt?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Creates and manages Cloudflare Vectorize Indexes.
|
|
72
|
+
*
|
|
73
|
+
* Vectorize is Cloudflare's vector database that enables vector search within Cloudflare Workers.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Create a basic vector index for text embeddings
|
|
77
|
+
* const basicIndex = await VectorizeIndex("text-embeddings", {
|
|
78
|
+
* name: "text-embeddings",
|
|
79
|
+
* config: {
|
|
80
|
+
* dimensions: 768,
|
|
81
|
+
* metric: "cosine"
|
|
82
|
+
* }
|
|
83
|
+
* });
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* // Create a vector index with a description
|
|
87
|
+
* const descIndex = await VectorizeIndex("image-embeddings", {
|
|
88
|
+
* name: "image-embeddings",
|
|
89
|
+
* description: "Vector index for image embeddings using CLIP model",
|
|
90
|
+
* config: {
|
|
91
|
+
* dimensions: 512,
|
|
92
|
+
* metric: "cosine"
|
|
93
|
+
* }
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // Adopt an existing index if it already exists instead of failing
|
|
98
|
+
* const existingIndex = await VectorizeIndex("existing-index", {
|
|
99
|
+
* name: "existing-index",
|
|
100
|
+
* adopt: true,
|
|
101
|
+
* config: {
|
|
102
|
+
* dimensions: 1024,
|
|
103
|
+
* metric: "euclidean"
|
|
104
|
+
* }
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* @see https://developers.cloudflare.com/vectorize/
|
|
108
|
+
*/
|
|
109
|
+
export const VectorizeIndex = Resource(
|
|
110
|
+
"cloudflare::VectorizeIndex",
|
|
111
|
+
async function (
|
|
112
|
+
this: Context<VectorizeIndex>,
|
|
113
|
+
id: string,
|
|
114
|
+
props: VectorizeIndexProps
|
|
115
|
+
): Promise<VectorizeIndex> {
|
|
116
|
+
const api = await createCloudflareApi(props);
|
|
117
|
+
const indexName = props.name || id;
|
|
118
|
+
|
|
119
|
+
if (this.phase === "delete") {
|
|
120
|
+
console.log("Deleting Vectorize index:", indexName);
|
|
121
|
+
if (props.delete !== false) {
|
|
122
|
+
// Delete Vectorize index
|
|
123
|
+
await deleteIndex(api, indexName);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Return void (a deleted index has no content)
|
|
127
|
+
return this.destroy();
|
|
128
|
+
} else {
|
|
129
|
+
let indexData: CloudflareVectorizeResponse;
|
|
130
|
+
|
|
131
|
+
if (this.phase === "create") {
|
|
132
|
+
console.log("Creating Vectorize index:", indexName);
|
|
133
|
+
try {
|
|
134
|
+
indexData = await createIndex(api, indexName, {
|
|
135
|
+
...props,
|
|
136
|
+
name: indexName,
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// Check if this is a "index already exists" error and adopt is enabled
|
|
140
|
+
if (
|
|
141
|
+
props.adopt &&
|
|
142
|
+
error instanceof CloudflareApiError &&
|
|
143
|
+
error.message.includes("already exists")
|
|
144
|
+
) {
|
|
145
|
+
console.log(`Index ${indexName} already exists, adopting it`);
|
|
146
|
+
// Find the existing index
|
|
147
|
+
indexData = await getIndex(api, indexName);
|
|
148
|
+
} else {
|
|
149
|
+
// Re-throw the error if adopt is false or it's not a "index already exists" error
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
// Update operation is not supported by Vectorize API
|
|
155
|
+
throw new Error(
|
|
156
|
+
"Updating Vectorize indexes is not supported by the Cloudflare API. " +
|
|
157
|
+
"To change an index, delete it and create a new one with the desired configuration."
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return this({
|
|
162
|
+
type: "vectorize",
|
|
163
|
+
id: indexName,
|
|
164
|
+
name: indexName,
|
|
165
|
+
description: props.description,
|
|
166
|
+
dimensions: indexData.result.config.dimensions,
|
|
167
|
+
metric: indexData.result.config.metric as
|
|
168
|
+
| "cosine"
|
|
169
|
+
| "euclidean"
|
|
170
|
+
| "dot_product",
|
|
171
|
+
accountId: api.accountId,
|
|
172
|
+
createdAt: indexData.result.created_on
|
|
173
|
+
? new Date(indexData.result.created_on).getTime()
|
|
174
|
+
: undefined,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
interface CloudflareVectorizeResponse {
|
|
181
|
+
result: {
|
|
182
|
+
name: string;
|
|
183
|
+
description?: string;
|
|
184
|
+
created_on?: string;
|
|
185
|
+
config: {
|
|
186
|
+
dimensions: number;
|
|
187
|
+
metric: string;
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
success: boolean;
|
|
191
|
+
errors: Array<{ code: number; message: string }>;
|
|
192
|
+
messages: string[];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create a new Vectorize index
|
|
197
|
+
*/
|
|
198
|
+
export async function createIndex(
|
|
199
|
+
api: CloudflareApi,
|
|
200
|
+
indexName: string,
|
|
201
|
+
props: VectorizeIndexProps
|
|
202
|
+
): Promise<CloudflareVectorizeResponse> {
|
|
203
|
+
// Create new Vectorize index
|
|
204
|
+
const createPayload: any = {
|
|
205
|
+
name: indexName,
|
|
206
|
+
config: {
|
|
207
|
+
dimensions: props.dimensions,
|
|
208
|
+
metric: props.metric,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (props.description) {
|
|
213
|
+
createPayload.description = props.description;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const createResponse = await api.post(
|
|
217
|
+
`/accounts/${api.accountId}/vectorize/v2/indexes`,
|
|
218
|
+
createPayload
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (!createResponse.ok) {
|
|
222
|
+
return await handleApiError(
|
|
223
|
+
createResponse,
|
|
224
|
+
"creating",
|
|
225
|
+
"Vectorize index",
|
|
226
|
+
indexName
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return (await createResponse.json()) as CloudflareVectorizeResponse;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get a Vectorize index
|
|
235
|
+
*/
|
|
236
|
+
export async function getIndex(
|
|
237
|
+
api: CloudflareApi,
|
|
238
|
+
indexName: string
|
|
239
|
+
): Promise<CloudflareVectorizeResponse> {
|
|
240
|
+
const response = await api.get(
|
|
241
|
+
`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}`
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
return await handleApiError(
|
|
246
|
+
response,
|
|
247
|
+
"getting",
|
|
248
|
+
"Vectorize index",
|
|
249
|
+
indexName
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return (await response.json()) as CloudflareVectorizeResponse;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Delete a Vectorize index
|
|
258
|
+
*/
|
|
259
|
+
export async function deleteIndex(
|
|
260
|
+
api: CloudflareApi,
|
|
261
|
+
indexName: string
|
|
262
|
+
): Promise<void> {
|
|
263
|
+
// Delete Vectorize index
|
|
264
|
+
const deleteResponse = await api.delete(
|
|
265
|
+
`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}`
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
269
|
+
const errorData: any = await deleteResponse.json().catch(() => ({
|
|
270
|
+
errors: [{ message: deleteResponse.statusText }],
|
|
271
|
+
}));
|
|
272
|
+
throw new CloudflareApiError(
|
|
273
|
+
`Error deleting Vectorize index '${indexName}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`,
|
|
274
|
+
deleteResponse
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* List all Vectorize indexes in an account
|
|
281
|
+
*/
|
|
282
|
+
export async function listIndexes(
|
|
283
|
+
api: CloudflareApi
|
|
284
|
+
): Promise<{ name: string; description?: string }[]> {
|
|
285
|
+
const response = await api.get(
|
|
286
|
+
`/accounts/${api.accountId}/vectorize/v2/indexes`
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
throw new CloudflareApiError(
|
|
291
|
+
`Failed to list indexes: ${response.statusText}`,
|
|
292
|
+
response
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const data = (await response.json()) as {
|
|
297
|
+
success: boolean;
|
|
298
|
+
errors?: Array<{ code: number; message: string }>;
|
|
299
|
+
result?: Array<{
|
|
300
|
+
name: string;
|
|
301
|
+
description?: string;
|
|
302
|
+
}>;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (!data.success) {
|
|
306
|
+
const errorMessage = data.errors?.[0]?.message || "Unknown error";
|
|
307
|
+
throw new Error(`Failed to list indexes: ${errorMessage}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Transform API response
|
|
311
|
+
return (data.result || []).map((index) => ({
|
|
312
|
+
name: index.name,
|
|
313
|
+
description: index.description,
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Update a Vectorize index
|
|
319
|
+
*
|
|
320
|
+
* Note: The Cloudflare Vectorize API does not support updating indexes.
|
|
321
|
+
* This function will always throw an error.
|
|
322
|
+
*/
|
|
323
|
+
export async function updateIndex(
|
|
324
|
+
api: CloudflareApi,
|
|
325
|
+
indexName: string,
|
|
326
|
+
props: VectorizeIndexProps
|
|
327
|
+
): Promise<CloudflareVectorizeResponse> {
|
|
328
|
+
throw new Error(
|
|
329
|
+
"Updating Vectorize indexes is not supported by the Cloudflare API. To change an index, delete it and create a new one."
|
|
330
|
+
);
|
|
331
|
+
}
|