alchemy 0.7.3 → 0.8.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.
- package/lib/aws/table.js +22 -5
- 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 +2 -0
- package/lib/cloudflare/index.js +2 -0
- 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/worker.js +7 -0
- package/lib/cloudflare/wrangler.json.d.ts +7 -0
- package/lib/cloudflare/wrangler.json.js +10 -0
- package/lib/esbuild/bundle.d.ts +11 -14
- package/lib/esbuild/bundle.js +6 -3
- package/lib/test/bun.js +4 -1
- package/lib/web/vitepress/process-front-matter-files.js +1 -1
- package/package.json +1 -1
- package/src/aws/table.ts +34 -16
- 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 +2 -0
- package/src/cloudflare/vectorize-index.ts +331 -0
- package/src/cloudflare/vectorize-metadata-index.ts +239 -0
- package/src/cloudflare/worker.ts +6 -0
- package/src/cloudflare/wrangler.json.ts +18 -0
- package/src/esbuild/bundle.ts +6 -3
- package/src/test/bun.ts +22 -14
- package/src/web/vitepress/process-front-matter-files.ts +1 -1
package/lib/aws/table.js
CHANGED
|
@@ -45,9 +45,9 @@ export const Table = Resource("dynamo::Table", async function (id, props) {
|
|
|
45
45
|
await ignore(ResourceNotFoundException.name, () => client.send(new DeleteTableCommand({
|
|
46
46
|
TableName: props.tableName,
|
|
47
47
|
})));
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
}, isRetryableError, 10, // Max attempts
|
|
49
|
+
200 // Initial delay in ms
|
|
50
|
+
);
|
|
51
51
|
// Wait for table to be deleted
|
|
52
52
|
let tableDeleted = false;
|
|
53
53
|
let retryCount = 0;
|
|
@@ -143,8 +143,9 @@ export const Table = Resource("dynamo::Table", async function (id, props) {
|
|
|
143
143
|
throw error;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
},
|
|
147
|
-
200
|
|
146
|
+
}, isRetryableError, 10, // Max attempts
|
|
147
|
+
200 // Initial delay in ms
|
|
148
|
+
);
|
|
148
149
|
// Wait for table to be active with timeout
|
|
149
150
|
let tableActive = false;
|
|
150
151
|
let tableDescription;
|
|
@@ -185,3 +186,19 @@ export const Table = Resource("dynamo::Table", async function (id, props) {
|
|
|
185
186
|
});
|
|
186
187
|
}
|
|
187
188
|
});
|
|
189
|
+
const retryableErrors = [
|
|
190
|
+
"ResourceInUseException",
|
|
191
|
+
"ResourceNotFoundException",
|
|
192
|
+
"InternalServerError",
|
|
193
|
+
"ThrottlingException",
|
|
194
|
+
"ProvisionedThroughputExceededException",
|
|
195
|
+
"LimitExceededException",
|
|
196
|
+
"RequestLimitExceeded",
|
|
197
|
+
];
|
|
198
|
+
function isRetryableError(error) {
|
|
199
|
+
return (error instanceof ResourceInUseException ||
|
|
200
|
+
error instanceof InternalServerError ||
|
|
201
|
+
retryableErrors.includes(error?.name) ||
|
|
202
|
+
retryableErrors.includes(error?.code) ||
|
|
203
|
+
error?.$metadata?.httpStatusCode === 500);
|
|
204
|
+
}
|
|
@@ -39,7 +39,7 @@ export class CloudflareApiError extends Error {
|
|
|
39
39
|
*/
|
|
40
40
|
export async function handleApiError(response, action, resourceType, resourceName) {
|
|
41
41
|
const json = await response.json();
|
|
42
|
-
const errors = json
|
|
42
|
+
const errors = json?.errors || [
|
|
43
43
|
{ message: response.statusText },
|
|
44
44
|
];
|
|
45
45
|
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
|
export type Bindings = {
|
|
@@ -19,7 +20,7 @@ export type Bindings = {
|
|
|
19
20
|
/**
|
|
20
21
|
* L2 Binding Resources.
|
|
21
22
|
*/
|
|
22
|
-
export type Binding = Assets | D1Database | DurableObjectNamespace | KVNamespace | Pipeline | Queue | R2Bucket | Secret | string | Worker | Workflow;
|
|
23
|
+
export type Binding = Assets | D1Database | DurableObjectNamespace | KVNamespace | Pipeline | Queue | R2Bucket | Secret | string | VectorizeIndex | Worker | Workflow;
|
|
23
24
|
/**
|
|
24
25
|
* Union type for all Worker binding types (API spec)
|
|
25
26
|
*/
|
|
@@ -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
|
-
export type Bound<T extends Binding> = T extends _DurableObjectNamespace ? DurableObjectNamespace : T extends _KVNamespace ? KVNamespace : T extends _Worker ? Worker : T extends _R2Bucket ? R2Bucket : T extends Secret ? string : T extends Assets ? Service : T extends _Workflow<infer P> ? Workflow<P> : T extends _D1Database ? D1Database : T extends _Queue ? Queue : T extends _Pipeline<infer R> ? Pipeline<R> : Service;
|
|
14
|
+
export type Bound<T extends Binding> = T extends _DurableObjectNamespace ? DurableObjectNamespace : T extends _KVNamespace ? KVNamespace : T extends _Worker ? Worker : T extends _R2Bucket ? R2Bucket : T extends Secret ? string : T extends Assets ? Service : T extends _Workflow<infer P> ? Workflow<P> : T extends _D1Database ? D1Database : T extends _VectorizeIndex ? VectorizeIndex : T extends _Queue ? Queue : T extends _Pipeline<infer R> ? Pipeline<R> : Service;
|
|
@@ -13,6 +13,8 @@ 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";
|
|
16
18
|
export * from "./worker";
|
|
17
19
|
export { Workflow } from "./workflow";
|
|
18
20
|
export * from "./wrangler.json";
|
package/lib/cloudflare/index.js
CHANGED
|
@@ -13,6 +13,8 @@ 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";
|
|
16
18
|
export * from "./worker";
|
|
17
19
|
export { Workflow } from "./workflow";
|
|
18
20
|
export * from "./wrangler.json";
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { Context } from "../context";
|
|
2
|
+
import { Resource } from "../resource";
|
|
3
|
+
import { CloudflareApi, type CloudflareApiOptions } from "./api";
|
|
4
|
+
/**
|
|
5
|
+
* Properties for creating or updating a Vectorize Index
|
|
6
|
+
*/
|
|
7
|
+
export interface VectorizeIndexProps extends CloudflareApiOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Name of the index
|
|
10
|
+
*/
|
|
11
|
+
name: string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional description of the index
|
|
14
|
+
*/
|
|
15
|
+
description?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Dimensions of the vectors
|
|
18
|
+
*/
|
|
19
|
+
dimensions: number;
|
|
20
|
+
/**
|
|
21
|
+
* Distance metric used for vector similarity
|
|
22
|
+
*/
|
|
23
|
+
metric: "cosine" | "euclidean" | "dot_product";
|
|
24
|
+
/**
|
|
25
|
+
* Whether to delete the index if removed
|
|
26
|
+
* If set to false, the index will remain but the resource will be removed from state
|
|
27
|
+
*
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
delete?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether to adopt an existing index with the same name if it exists
|
|
33
|
+
* If true and an index with the same name exists, it will be adopted rather than creating a new one
|
|
34
|
+
*
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
adopt?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Output returned after Vectorize Index creation/update
|
|
41
|
+
*/
|
|
42
|
+
export interface VectorizeIndex extends Resource<"cloudflare::VectorizeIndex">, VectorizeIndexProps {
|
|
43
|
+
type: "vectorize";
|
|
44
|
+
/**
|
|
45
|
+
* The unique identifier for the index (same as name)
|
|
46
|
+
*/
|
|
47
|
+
id: string;
|
|
48
|
+
/**
|
|
49
|
+
* Time at which the index was created
|
|
50
|
+
*/
|
|
51
|
+
createdAt?: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Creates and manages Cloudflare Vectorize Indexes.
|
|
55
|
+
*
|
|
56
|
+
* Vectorize is Cloudflare's vector database that enables vector search within Cloudflare Workers.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* // Create a basic vector index for text embeddings
|
|
60
|
+
* const basicIndex = await VectorizeIndex("text-embeddings", {
|
|
61
|
+
* name: "text-embeddings",
|
|
62
|
+
* config: {
|
|
63
|
+
* dimensions: 768,
|
|
64
|
+
* metric: "cosine"
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // Create a vector index with a description
|
|
70
|
+
* const descIndex = await VectorizeIndex("image-embeddings", {
|
|
71
|
+
* name: "image-embeddings",
|
|
72
|
+
* description: "Vector index for image embeddings using CLIP model",
|
|
73
|
+
* config: {
|
|
74
|
+
* dimensions: 512,
|
|
75
|
+
* metric: "cosine"
|
|
76
|
+
* }
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Adopt an existing index if it already exists instead of failing
|
|
81
|
+
* const existingIndex = await VectorizeIndex("existing-index", {
|
|
82
|
+
* name: "existing-index",
|
|
83
|
+
* adopt: true,
|
|
84
|
+
* config: {
|
|
85
|
+
* dimensions: 1024,
|
|
86
|
+
* metric: "euclidean"
|
|
87
|
+
* }
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* @see https://developers.cloudflare.com/vectorize/
|
|
91
|
+
*/
|
|
92
|
+
export declare const VectorizeIndex: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<VectorizeIndex>, id: string, props: VectorizeIndexProps) => Promise<VectorizeIndex>);
|
|
93
|
+
interface CloudflareVectorizeResponse {
|
|
94
|
+
result: {
|
|
95
|
+
name: string;
|
|
96
|
+
description?: string;
|
|
97
|
+
created_on?: string;
|
|
98
|
+
config: {
|
|
99
|
+
dimensions: number;
|
|
100
|
+
metric: string;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
success: boolean;
|
|
104
|
+
errors: Array<{
|
|
105
|
+
code: number;
|
|
106
|
+
message: string;
|
|
107
|
+
}>;
|
|
108
|
+
messages: string[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create a new Vectorize index
|
|
112
|
+
*/
|
|
113
|
+
export declare function createIndex(api: CloudflareApi, indexName: string, props: VectorizeIndexProps): Promise<CloudflareVectorizeResponse>;
|
|
114
|
+
/**
|
|
115
|
+
* Get a Vectorize index
|
|
116
|
+
*/
|
|
117
|
+
export declare function getIndex(api: CloudflareApi, indexName: string): Promise<CloudflareVectorizeResponse>;
|
|
118
|
+
/**
|
|
119
|
+
* Delete a Vectorize index
|
|
120
|
+
*/
|
|
121
|
+
export declare function deleteIndex(api: CloudflareApi, indexName: string): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* List all Vectorize indexes in an account
|
|
124
|
+
*/
|
|
125
|
+
export declare function listIndexes(api: CloudflareApi): Promise<{
|
|
126
|
+
name: string;
|
|
127
|
+
description?: string;
|
|
128
|
+
}[]>;
|
|
129
|
+
/**
|
|
130
|
+
* Update a Vectorize index
|
|
131
|
+
*
|
|
132
|
+
* Note: The Cloudflare Vectorize API does not support updating indexes.
|
|
133
|
+
* This function will always throw an error.
|
|
134
|
+
*/
|
|
135
|
+
export declare function updateIndex(api: CloudflareApi, indexName: string, props: VectorizeIndexProps): Promise<CloudflareVectorizeResponse>;
|
|
136
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Resource } from "../resource";
|
|
2
|
+
import { CloudflareApi, createCloudflareApi, } from "./api";
|
|
3
|
+
import { CloudflareApiError, handleApiError } from "./api-error";
|
|
4
|
+
/**
|
|
5
|
+
* Creates and manages Cloudflare Vectorize Indexes.
|
|
6
|
+
*
|
|
7
|
+
* Vectorize is Cloudflare's vector database that enables vector search within Cloudflare Workers.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Create a basic vector index for text embeddings
|
|
11
|
+
* const basicIndex = await VectorizeIndex("text-embeddings", {
|
|
12
|
+
* name: "text-embeddings",
|
|
13
|
+
* config: {
|
|
14
|
+
* dimensions: 768,
|
|
15
|
+
* metric: "cosine"
|
|
16
|
+
* }
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Create a vector index with a description
|
|
21
|
+
* const descIndex = await VectorizeIndex("image-embeddings", {
|
|
22
|
+
* name: "image-embeddings",
|
|
23
|
+
* description: "Vector index for image embeddings using CLIP model",
|
|
24
|
+
* config: {
|
|
25
|
+
* dimensions: 512,
|
|
26
|
+
* metric: "cosine"
|
|
27
|
+
* }
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Adopt an existing index if it already exists instead of failing
|
|
32
|
+
* const existingIndex = await VectorizeIndex("existing-index", {
|
|
33
|
+
* name: "existing-index",
|
|
34
|
+
* adopt: true,
|
|
35
|
+
* config: {
|
|
36
|
+
* dimensions: 1024,
|
|
37
|
+
* metric: "euclidean"
|
|
38
|
+
* }
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* @see https://developers.cloudflare.com/vectorize/
|
|
42
|
+
*/
|
|
43
|
+
export const VectorizeIndex = Resource("cloudflare::VectorizeIndex", async function (id, props) {
|
|
44
|
+
const api = await createCloudflareApi(props);
|
|
45
|
+
const indexName = props.name || id;
|
|
46
|
+
if (this.phase === "delete") {
|
|
47
|
+
console.log("Deleting Vectorize index:", indexName);
|
|
48
|
+
if (props.delete !== false) {
|
|
49
|
+
// Delete Vectorize index
|
|
50
|
+
await deleteIndex(api, indexName);
|
|
51
|
+
}
|
|
52
|
+
// Return void (a deleted index has no content)
|
|
53
|
+
return this.destroy();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
let indexData;
|
|
57
|
+
if (this.phase === "create") {
|
|
58
|
+
console.log("Creating Vectorize index:", indexName);
|
|
59
|
+
try {
|
|
60
|
+
indexData = await createIndex(api, indexName, {
|
|
61
|
+
...props,
|
|
62
|
+
name: indexName,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
// Check if this is a "index already exists" error and adopt is enabled
|
|
67
|
+
if (props.adopt &&
|
|
68
|
+
error instanceof CloudflareApiError &&
|
|
69
|
+
error.message.includes("already exists")) {
|
|
70
|
+
console.log(`Index ${indexName} already exists, adopting it`);
|
|
71
|
+
// Find the existing index
|
|
72
|
+
indexData = await getIndex(api, indexName);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// Re-throw the error if adopt is false or it's not a "index already exists" error
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Update operation is not supported by Vectorize API
|
|
82
|
+
throw new Error("Updating Vectorize indexes is not supported by the Cloudflare API. " +
|
|
83
|
+
"To change an index, delete it and create a new one with the desired configuration.");
|
|
84
|
+
}
|
|
85
|
+
return this({
|
|
86
|
+
type: "vectorize",
|
|
87
|
+
id: indexName,
|
|
88
|
+
name: indexName,
|
|
89
|
+
description: props.description,
|
|
90
|
+
dimensions: indexData.result.config.dimensions,
|
|
91
|
+
metric: indexData.result.config.metric,
|
|
92
|
+
accountId: api.accountId,
|
|
93
|
+
createdAt: indexData.result.created_on
|
|
94
|
+
? new Date(indexData.result.created_on).getTime()
|
|
95
|
+
: undefined,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* Create a new Vectorize index
|
|
101
|
+
*/
|
|
102
|
+
export async function createIndex(api, indexName, props) {
|
|
103
|
+
// Create new Vectorize index
|
|
104
|
+
const createPayload = {
|
|
105
|
+
name: indexName,
|
|
106
|
+
config: {
|
|
107
|
+
dimensions: props.dimensions,
|
|
108
|
+
metric: props.metric,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
if (props.description) {
|
|
112
|
+
createPayload.description = props.description;
|
|
113
|
+
}
|
|
114
|
+
const createResponse = await api.post(`/accounts/${api.accountId}/vectorize/v2/indexes`, createPayload);
|
|
115
|
+
if (!createResponse.ok) {
|
|
116
|
+
return await handleApiError(createResponse, "creating", "Vectorize index", indexName);
|
|
117
|
+
}
|
|
118
|
+
return (await createResponse.json());
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get a Vectorize index
|
|
122
|
+
*/
|
|
123
|
+
export async function getIndex(api, indexName) {
|
|
124
|
+
const response = await api.get(`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}`);
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
return await handleApiError(response, "getting", "Vectorize index", indexName);
|
|
127
|
+
}
|
|
128
|
+
return (await response.json());
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Delete a Vectorize index
|
|
132
|
+
*/
|
|
133
|
+
export async function deleteIndex(api, indexName) {
|
|
134
|
+
// Delete Vectorize index
|
|
135
|
+
const deleteResponse = await api.delete(`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}`);
|
|
136
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
137
|
+
const errorData = await deleteResponse.json().catch(() => ({
|
|
138
|
+
errors: [{ message: deleteResponse.statusText }],
|
|
139
|
+
}));
|
|
140
|
+
throw new CloudflareApiError(`Error deleting Vectorize index '${indexName}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`, deleteResponse);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* List all Vectorize indexes in an account
|
|
145
|
+
*/
|
|
146
|
+
export async function listIndexes(api) {
|
|
147
|
+
const response = await api.get(`/accounts/${api.accountId}/vectorize/v2/indexes`);
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new CloudflareApiError(`Failed to list indexes: ${response.statusText}`, response);
|
|
150
|
+
}
|
|
151
|
+
const data = (await response.json());
|
|
152
|
+
if (!data.success) {
|
|
153
|
+
const errorMessage = data.errors?.[0]?.message || "Unknown error";
|
|
154
|
+
throw new Error(`Failed to list indexes: ${errorMessage}`);
|
|
155
|
+
}
|
|
156
|
+
// Transform API response
|
|
157
|
+
return (data.result || []).map((index) => ({
|
|
158
|
+
name: index.name,
|
|
159
|
+
description: index.description,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Update a Vectorize index
|
|
164
|
+
*
|
|
165
|
+
* Note: The Cloudflare Vectorize API does not support updating indexes.
|
|
166
|
+
* This function will always throw an error.
|
|
167
|
+
*/
|
|
168
|
+
export async function updateIndex(api, indexName, props) {
|
|
169
|
+
throw new Error("Updating Vectorize indexes is not supported by the Cloudflare API. To change an index, delete it and create a new one.");
|
|
170
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Context } from "../context";
|
|
2
|
+
import { Resource } from "../resource";
|
|
3
|
+
import { CloudflareApi, type CloudflareApiOptions } from "./api";
|
|
4
|
+
import { VectorizeIndex } from "./vectorize-index";
|
|
5
|
+
/**
|
|
6
|
+
* Properties for creating or deleting a Vectorize Metadata Index
|
|
7
|
+
*/
|
|
8
|
+
export interface VectorizeMetadataIndexProps extends CloudflareApiOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Parent Vectorize Index
|
|
11
|
+
*/
|
|
12
|
+
index: VectorizeIndex;
|
|
13
|
+
/**
|
|
14
|
+
* Name of the property in the metadata to create an index for
|
|
15
|
+
*/
|
|
16
|
+
propertyName: string;
|
|
17
|
+
/**
|
|
18
|
+
* Type of the metadata index
|
|
19
|
+
*/
|
|
20
|
+
indexType: "string" | "number" | "boolean";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Output returned after Vectorize Metadata Index creation/deletion
|
|
24
|
+
*/
|
|
25
|
+
export interface VectorizeMetadataIndex extends Resource<"cloudflare::VectorizeMetadataIndex">, VectorizeMetadataIndexProps {
|
|
26
|
+
/**
|
|
27
|
+
* ID of this metadata index (derived from propertyName)
|
|
28
|
+
*/
|
|
29
|
+
id: string;
|
|
30
|
+
/**
|
|
31
|
+
* Mutation ID returned by the API after creation
|
|
32
|
+
*/
|
|
33
|
+
mutationId?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates and manages Cloudflare Vectorize Metadata Indexes.
|
|
37
|
+
*
|
|
38
|
+
* Vectorize Metadata Indexes enable filtering based on metadata properties when querying vectors.
|
|
39
|
+
* Each Vectorize Index can have up to 10 metadata indexes.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // First create a Vectorize Index
|
|
43
|
+
* const vectorIndex = await VectorizeIndex("documents", {
|
|
44
|
+
* name: "documents",
|
|
45
|
+
* config: {
|
|
46
|
+
* dimensions: 768,
|
|
47
|
+
* metric: "cosine"
|
|
48
|
+
* }
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* // Then create a metadata index for the "category" property
|
|
52
|
+
* const categoryIndex = await VectorizeMetadataIndex("category-index", {
|
|
53
|
+
* index: vectorIndex,
|
|
54
|
+
* propertyName: "category",
|
|
55
|
+
* indexType: "string"
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* // Create a metadata index for a numeric property
|
|
60
|
+
* const yearIndex = await VectorizeMetadataIndex("year-index", {
|
|
61
|
+
* index: vectorIndex,
|
|
62
|
+
* propertyName: "year",
|
|
63
|
+
* indexType: "number"
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* @see https://developers.cloudflare.com/vectorize/
|
|
67
|
+
*/
|
|
68
|
+
export declare const VectorizeMetadataIndex: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<VectorizeMetadataIndex>, id: string, props: VectorizeMetadataIndexProps) => Promise<VectorizeMetadataIndex>);
|
|
69
|
+
interface CloudflareMetadataIndexResponse {
|
|
70
|
+
result: {
|
|
71
|
+
mutationId: string;
|
|
72
|
+
};
|
|
73
|
+
success: boolean;
|
|
74
|
+
errors: Array<{
|
|
75
|
+
code: number;
|
|
76
|
+
message: string;
|
|
77
|
+
}>;
|
|
78
|
+
messages: string[];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a new Vectorize metadata index
|
|
82
|
+
*/
|
|
83
|
+
export declare function createMetadataIndex(api: CloudflareApi, indexName: string, props: VectorizeMetadataIndexProps): Promise<CloudflareMetadataIndexResponse>;
|
|
84
|
+
/**
|
|
85
|
+
* Delete a Vectorize metadata index
|
|
86
|
+
*/
|
|
87
|
+
export declare function deleteMetadataIndex(api: CloudflareApi, indexName: string, propertyName: string): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* List all metadata indexes for a Vectorize index
|
|
90
|
+
*/
|
|
91
|
+
export declare function listMetadataIndexes(api: CloudflareApi, indexName: string): Promise<{
|
|
92
|
+
propertyName: string;
|
|
93
|
+
indexType: "string" | "number" | "boolean";
|
|
94
|
+
}[]>;
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Resource } from "../resource";
|
|
2
|
+
import { CloudflareApi, createCloudflareApi, } from "./api";
|
|
3
|
+
import { CloudflareApiError, handleApiError } from "./api-error";
|
|
4
|
+
import { VectorizeIndex } from "./vectorize-index";
|
|
5
|
+
/**
|
|
6
|
+
* Creates and manages Cloudflare Vectorize Metadata Indexes.
|
|
7
|
+
*
|
|
8
|
+
* Vectorize Metadata Indexes enable filtering based on metadata properties when querying vectors.
|
|
9
|
+
* Each Vectorize Index can have up to 10 metadata indexes.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // First create a Vectorize Index
|
|
13
|
+
* const vectorIndex = await VectorizeIndex("documents", {
|
|
14
|
+
* name: "documents",
|
|
15
|
+
* config: {
|
|
16
|
+
* dimensions: 768,
|
|
17
|
+
* metric: "cosine"
|
|
18
|
+
* }
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Then create a metadata index for the "category" property
|
|
22
|
+
* const categoryIndex = await VectorizeMetadataIndex("category-index", {
|
|
23
|
+
* index: vectorIndex,
|
|
24
|
+
* propertyName: "category",
|
|
25
|
+
* indexType: "string"
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Create a metadata index for a numeric property
|
|
30
|
+
* const yearIndex = await VectorizeMetadataIndex("year-index", {
|
|
31
|
+
* index: vectorIndex,
|
|
32
|
+
* propertyName: "year",
|
|
33
|
+
* indexType: "number"
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* @see https://developers.cloudflare.com/vectorize/
|
|
37
|
+
*/
|
|
38
|
+
export const VectorizeMetadataIndex = Resource("cloudflare::VectorizeMetadataIndex", async function (id, props) {
|
|
39
|
+
const api = await createCloudflareApi(props);
|
|
40
|
+
const indexName = props.index.name;
|
|
41
|
+
const propertyName = props.propertyName;
|
|
42
|
+
if (this.phase === "delete") {
|
|
43
|
+
// Delete metadata index
|
|
44
|
+
try {
|
|
45
|
+
await deleteMetadataIndex(api, indexName, propertyName);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error instanceof CloudflareApiError &&
|
|
49
|
+
error.status === 400 &&
|
|
50
|
+
error.message.includes("does not exist")) {
|
|
51
|
+
// Index doesn't exist, which is what we want
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return this.destroy();
|
|
58
|
+
}
|
|
59
|
+
else if (this.phase === "update") {
|
|
60
|
+
// Update operation is not supported
|
|
61
|
+
throw new Error("Updating Vectorize metadata indexes is not supported by the Cloudflare API. " +
|
|
62
|
+
"To change a metadata index, delete it and create a new one with the desired configuration.");
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const indexData = await createMetadataIndex(api, indexName, props);
|
|
66
|
+
return this({
|
|
67
|
+
id: propertyName, // Use propertyName as ID
|
|
68
|
+
index: props.index,
|
|
69
|
+
propertyName: props.propertyName,
|
|
70
|
+
indexType: props.indexType,
|
|
71
|
+
accountId: api.accountId,
|
|
72
|
+
mutationId: indexData.result.mutationId,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Create a new Vectorize metadata index
|
|
78
|
+
*/
|
|
79
|
+
export async function createMetadataIndex(api, indexName, props) {
|
|
80
|
+
// Create new metadata index
|
|
81
|
+
const createPayload = {
|
|
82
|
+
propertyName: props.propertyName,
|
|
83
|
+
indexType: props.indexType,
|
|
84
|
+
};
|
|
85
|
+
const createResponse = await api.post(`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}/metadata_index/create`, createPayload);
|
|
86
|
+
if (!createResponse.ok) {
|
|
87
|
+
return await handleApiError(createResponse, "creating", "Vectorize metadata index", props.propertyName);
|
|
88
|
+
}
|
|
89
|
+
return (await createResponse.json());
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Delete a Vectorize metadata index
|
|
93
|
+
*/
|
|
94
|
+
export async function deleteMetadataIndex(api, indexName, propertyName) {
|
|
95
|
+
const deleteResponse = await api.post(`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}/metadata_index/delete`, {
|
|
96
|
+
propertyName,
|
|
97
|
+
});
|
|
98
|
+
if (!deleteResponse.ok) {
|
|
99
|
+
await handleApiError(deleteResponse, "deleting", "Vectorize metadata index", propertyName);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* List all metadata indexes for a Vectorize index
|
|
104
|
+
*/
|
|
105
|
+
export async function listMetadataIndexes(api, indexName) {
|
|
106
|
+
const response = await api.get(`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}/metadata_index/list`);
|
|
107
|
+
if (response.status === 410) {
|
|
108
|
+
// Gone
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new CloudflareApiError(`Failed to list metadata indexes: ${response.statusText}`, response);
|
|
113
|
+
}
|
|
114
|
+
const data = (await response.json());
|
|
115
|
+
if (!data.success) {
|
|
116
|
+
const errorMessage = data.errors?.[0]?.message || "Unknown error";
|
|
117
|
+
throw new Error(`Failed to list metadata indexes: ${errorMessage}`);
|
|
118
|
+
}
|
|
119
|
+
return data.result.metadataIndexes;
|
|
120
|
+
}
|
package/lib/cloudflare/worker.js
CHANGED
|
@@ -353,6 +353,13 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
|
|
|
353
353
|
pipeline: binding.name,
|
|
354
354
|
});
|
|
355
355
|
}
|
|
356
|
+
else if (binding.type === "vectorize") {
|
|
357
|
+
meta.bindings.push({
|
|
358
|
+
type: "vectorize",
|
|
359
|
+
name: bindingName,
|
|
360
|
+
index_name: binding.name,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
356
363
|
else {
|
|
357
364
|
// @ts-expect-error - we should never reach here
|
|
358
365
|
throw new Error(`Unsupported binding type: ${binding.type}`);
|
|
@@ -114,6 +114,13 @@ export interface WranglerJsonSpec {
|
|
|
114
114
|
binding: string;
|
|
115
115
|
class_name: string;
|
|
116
116
|
}[];
|
|
117
|
+
/**
|
|
118
|
+
* Vectorize index bindings
|
|
119
|
+
*/
|
|
120
|
+
vectorize_indexes?: {
|
|
121
|
+
binding: string;
|
|
122
|
+
index_name: string;
|
|
123
|
+
}[];
|
|
117
124
|
/**
|
|
118
125
|
* Plain text bindings (vars)
|
|
119
126
|
*/
|
|
@@ -50,6 +50,7 @@ function processBindings(spec, bindings) {
|
|
|
50
50
|
const workflows = [];
|
|
51
51
|
const d1Databases = [];
|
|
52
52
|
const queues = [];
|
|
53
|
+
const vectorizeIndexes = [];
|
|
53
54
|
// Process each binding
|
|
54
55
|
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
55
56
|
if (typeof binding === "string") {
|
|
@@ -120,6 +121,12 @@ function processBindings(spec, bindings) {
|
|
|
120
121
|
queue: binding.name,
|
|
121
122
|
});
|
|
122
123
|
}
|
|
124
|
+
else if (binding.type === "vectorize") {
|
|
125
|
+
vectorizeIndexes.push({
|
|
126
|
+
binding: bindingName,
|
|
127
|
+
index_name: binding.name,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
123
130
|
}
|
|
124
131
|
// Add collected bindings to the spec
|
|
125
132
|
if (kvNamespaces.length > 0) {
|
|
@@ -139,4 +146,7 @@ function processBindings(spec, bindings) {
|
|
|
139
146
|
if (queues.length > 0) {
|
|
140
147
|
spec.queues = queues;
|
|
141
148
|
}
|
|
149
|
+
if (vectorizeIndexes.length > 0) {
|
|
150
|
+
spec.vectorize_indexes = vectorizeIndexes;
|
|
151
|
+
}
|
|
142
152
|
}
|