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
|
@@ -0,0 +1,239 @@
|
|
|
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
|
+
import { VectorizeIndex } from "./vectorize-index";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Properties for creating or deleting a Vectorize Metadata Index
|
|
13
|
+
*/
|
|
14
|
+
export interface VectorizeMetadataIndexProps extends CloudflareApiOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Parent Vectorize Index
|
|
17
|
+
*/
|
|
18
|
+
index: VectorizeIndex;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Name of the property in the metadata to create an index for
|
|
22
|
+
*/
|
|
23
|
+
propertyName: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Type of the metadata index
|
|
27
|
+
*/
|
|
28
|
+
indexType: "string" | "number" | "boolean";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Output returned after Vectorize Metadata Index creation/deletion
|
|
33
|
+
*/
|
|
34
|
+
export interface VectorizeMetadataIndex
|
|
35
|
+
extends Resource<"cloudflare::VectorizeMetadataIndex">,
|
|
36
|
+
VectorizeMetadataIndexProps {
|
|
37
|
+
/**
|
|
38
|
+
* ID of this metadata index (derived from propertyName)
|
|
39
|
+
*/
|
|
40
|
+
id: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Mutation ID returned by the API after creation
|
|
44
|
+
*/
|
|
45
|
+
mutationId?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates and manages Cloudflare Vectorize Metadata Indexes.
|
|
50
|
+
*
|
|
51
|
+
* Vectorize Metadata Indexes enable filtering based on metadata properties when querying vectors.
|
|
52
|
+
* Each Vectorize Index can have up to 10 metadata indexes.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // First create a Vectorize Index
|
|
56
|
+
* const vectorIndex = await VectorizeIndex("documents", {
|
|
57
|
+
* name: "documents",
|
|
58
|
+
* config: {
|
|
59
|
+
* dimensions: 768,
|
|
60
|
+
* metric: "cosine"
|
|
61
|
+
* }
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* // Then create a metadata index for the "category" property
|
|
65
|
+
* const categoryIndex = await VectorizeMetadataIndex("category-index", {
|
|
66
|
+
* index: vectorIndex,
|
|
67
|
+
* propertyName: "category",
|
|
68
|
+
* indexType: "string"
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Create a metadata index for a numeric property
|
|
73
|
+
* const yearIndex = await VectorizeMetadataIndex("year-index", {
|
|
74
|
+
* index: vectorIndex,
|
|
75
|
+
* propertyName: "year",
|
|
76
|
+
* indexType: "number"
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* @see https://developers.cloudflare.com/vectorize/
|
|
80
|
+
*/
|
|
81
|
+
export const VectorizeMetadataIndex = Resource(
|
|
82
|
+
"cloudflare::VectorizeMetadataIndex",
|
|
83
|
+
async function (
|
|
84
|
+
this: Context<VectorizeMetadataIndex>,
|
|
85
|
+
id: string,
|
|
86
|
+
props: VectorizeMetadataIndexProps
|
|
87
|
+
): Promise<VectorizeMetadataIndex> {
|
|
88
|
+
const api = await createCloudflareApi(props);
|
|
89
|
+
const indexName = props.index.name;
|
|
90
|
+
const propertyName = props.propertyName;
|
|
91
|
+
|
|
92
|
+
if (this.phase === "delete") {
|
|
93
|
+
// Delete metadata index
|
|
94
|
+
try {
|
|
95
|
+
await deleteMetadataIndex(api, indexName, propertyName);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (
|
|
98
|
+
error instanceof CloudflareApiError &&
|
|
99
|
+
error.status === 400 &&
|
|
100
|
+
error.message.includes("does not exist")
|
|
101
|
+
) {
|
|
102
|
+
// Index doesn't exist, which is what we want
|
|
103
|
+
} else {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return this.destroy();
|
|
108
|
+
} else if (this.phase === "update") {
|
|
109
|
+
// Update operation is not supported
|
|
110
|
+
throw new Error(
|
|
111
|
+
"Updating Vectorize metadata indexes is not supported by the Cloudflare API. " +
|
|
112
|
+
"To change a metadata index, delete it and create a new one with the desired configuration."
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
const indexData = await createMetadataIndex(api, indexName, props);
|
|
116
|
+
|
|
117
|
+
return this({
|
|
118
|
+
id: propertyName, // Use propertyName as ID
|
|
119
|
+
index: props.index,
|
|
120
|
+
propertyName: props.propertyName,
|
|
121
|
+
indexType: props.indexType,
|
|
122
|
+
accountId: api.accountId,
|
|
123
|
+
mutationId: indexData.result.mutationId,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
interface CloudflareMetadataIndexResponse {
|
|
130
|
+
result: {
|
|
131
|
+
mutationId: string;
|
|
132
|
+
};
|
|
133
|
+
success: boolean;
|
|
134
|
+
errors: Array<{ code: number; message: string }>;
|
|
135
|
+
messages: string[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface CloudflareMetadataIndexListResponse {
|
|
139
|
+
result: {
|
|
140
|
+
metadataIndexes: Array<{
|
|
141
|
+
propertyName: string;
|
|
142
|
+
indexType: "string" | "number" | "boolean";
|
|
143
|
+
}>;
|
|
144
|
+
};
|
|
145
|
+
success: boolean;
|
|
146
|
+
errors: Array<{ code: number; message: string }>;
|
|
147
|
+
messages: string[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create a new Vectorize metadata index
|
|
152
|
+
*/
|
|
153
|
+
export async function createMetadataIndex(
|
|
154
|
+
api: CloudflareApi,
|
|
155
|
+
indexName: string,
|
|
156
|
+
props: VectorizeMetadataIndexProps
|
|
157
|
+
): Promise<CloudflareMetadataIndexResponse> {
|
|
158
|
+
// Create new metadata index
|
|
159
|
+
const createPayload = {
|
|
160
|
+
propertyName: props.propertyName,
|
|
161
|
+
indexType: props.indexType,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const createResponse = await api.post(
|
|
165
|
+
`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}/metadata_index/create`,
|
|
166
|
+
createPayload
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (!createResponse.ok) {
|
|
170
|
+
return await handleApiError(
|
|
171
|
+
createResponse,
|
|
172
|
+
"creating",
|
|
173
|
+
"Vectorize metadata index",
|
|
174
|
+
props.propertyName
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return (await createResponse.json()) as CloudflareMetadataIndexResponse;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Delete a Vectorize metadata index
|
|
183
|
+
*/
|
|
184
|
+
export async function deleteMetadataIndex(
|
|
185
|
+
api: CloudflareApi,
|
|
186
|
+
indexName: string,
|
|
187
|
+
propertyName: string
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
const deleteResponse = await api.post(
|
|
190
|
+
`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}/metadata_index/delete`,
|
|
191
|
+
{
|
|
192
|
+
propertyName,
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (!deleteResponse.ok) {
|
|
197
|
+
await handleApiError(
|
|
198
|
+
deleteResponse,
|
|
199
|
+
"deleting",
|
|
200
|
+
"Vectorize metadata index",
|
|
201
|
+
propertyName
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* List all metadata indexes for a Vectorize index
|
|
208
|
+
*/
|
|
209
|
+
export async function listMetadataIndexes(
|
|
210
|
+
api: CloudflareApi,
|
|
211
|
+
indexName: string
|
|
212
|
+
): Promise<
|
|
213
|
+
{ propertyName: string; indexType: "string" | "number" | "boolean" }[]
|
|
214
|
+
> {
|
|
215
|
+
const response = await api.get(
|
|
216
|
+
`/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}/metadata_index/list`
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (response.status === 410) {
|
|
220
|
+
// Gone
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
throw new CloudflareApiError(
|
|
226
|
+
`Failed to list metadata indexes: ${response.statusText}`,
|
|
227
|
+
response
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const data = (await response.json()) as CloudflareMetadataIndexListResponse;
|
|
232
|
+
|
|
233
|
+
if (!data.success) {
|
|
234
|
+
const errorMessage = data.errors?.[0]?.message || "Unknown error";
|
|
235
|
+
throw new Error(`Failed to list metadata indexes: ${errorMessage}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return data.result.metadataIndexes;
|
|
239
|
+
}
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -611,6 +611,12 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
611
611
|
name: bindingName,
|
|
612
612
|
pipeline: binding.name,
|
|
613
613
|
});
|
|
614
|
+
} else if (binding.type === "vectorize") {
|
|
615
|
+
meta.bindings.push({
|
|
616
|
+
type: "vectorize",
|
|
617
|
+
name: bindingName,
|
|
618
|
+
index_name: binding.name,
|
|
619
|
+
});
|
|
614
620
|
} else {
|
|
615
621
|
// @ts-expect-error - we should never reach here
|
|
616
622
|
throw new Error(`Unsupported binding type: ${binding.type}`);
|
|
@@ -187,6 +187,14 @@ export interface WranglerJsonSpec {
|
|
|
187
187
|
class_name: string;
|
|
188
188
|
}[];
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Vectorize index bindings
|
|
192
|
+
*/
|
|
193
|
+
vectorize_indexes?: {
|
|
194
|
+
binding: string;
|
|
195
|
+
index_name: string;
|
|
196
|
+
}[];
|
|
197
|
+
|
|
190
198
|
/**
|
|
191
199
|
* Plain text bindings (vars)
|
|
192
200
|
*/
|
|
@@ -239,6 +247,7 @@ function processBindings(spec: WranglerJsonSpec, bindings: Bindings): void {
|
|
|
239
247
|
database_name: string;
|
|
240
248
|
}[] = [];
|
|
241
249
|
const queues: { binding: string; queue: string }[] = [];
|
|
250
|
+
const vectorizeIndexes: { binding: string; index_name: string }[] = [];
|
|
242
251
|
|
|
243
252
|
// Process each binding
|
|
244
253
|
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
@@ -302,6 +311,11 @@ function processBindings(spec: WranglerJsonSpec, bindings: Bindings): void {
|
|
|
302
311
|
binding: bindingName,
|
|
303
312
|
queue: binding.name,
|
|
304
313
|
});
|
|
314
|
+
} else if (binding.type === "vectorize") {
|
|
315
|
+
vectorizeIndexes.push({
|
|
316
|
+
binding: bindingName,
|
|
317
|
+
index_name: binding.name,
|
|
318
|
+
});
|
|
305
319
|
}
|
|
306
320
|
}
|
|
307
321
|
|
|
@@ -327,4 +341,8 @@ function processBindings(spec: WranglerJsonSpec, bindings: Bindings): void {
|
|
|
327
341
|
if (queues.length > 0) {
|
|
328
342
|
spec.queues = queues;
|
|
329
343
|
}
|
|
344
|
+
|
|
345
|
+
if (vectorizeIndexes.length > 0) {
|
|
346
|
+
spec.vectorize_indexes = vectorizeIndexes;
|
|
347
|
+
}
|
|
330
348
|
}
|
package/src/esbuild/bundle.ts
CHANGED
|
@@ -150,8 +150,8 @@ export const Bundle = Resource(
|
|
|
150
150
|
|
|
151
151
|
export async function bundle(props: BundleProps) {
|
|
152
152
|
const outputPath = getOutputPath(props);
|
|
153
|
-
// Build the bundle
|
|
154
153
|
return await esbuild.build({
|
|
154
|
+
...props.options,
|
|
155
155
|
entryPoints: [props.entryPoint],
|
|
156
156
|
outfile: outputPath,
|
|
157
157
|
bundle: true,
|
|
@@ -159,11 +159,14 @@ export async function bundle(props: BundleProps) {
|
|
|
159
159
|
target: props.target,
|
|
160
160
|
minify: props.minify,
|
|
161
161
|
sourcemap: props.sourcemap,
|
|
162
|
-
external:
|
|
162
|
+
external: [
|
|
163
|
+
"node:async_hooks",
|
|
164
|
+
...(props.external ?? []),
|
|
165
|
+
...(props.options?.external ?? []),
|
|
166
|
+
],
|
|
163
167
|
platform: props.platform,
|
|
164
168
|
metafile: true,
|
|
165
169
|
write: true,
|
|
166
|
-
...props.options,
|
|
167
170
|
});
|
|
168
171
|
}
|
|
169
172
|
|
package/src/test/bun.ts
CHANGED
|
@@ -189,6 +189,10 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
|
189
189
|
) {
|
|
190
190
|
const testName = args[0];
|
|
191
191
|
const _options = typeof args[1] === "object" ? args[1] : undefined;
|
|
192
|
+
const timeout =
|
|
193
|
+
typeof args[args.length - 1] === "number"
|
|
194
|
+
? (args[args.length - 1] as number)
|
|
195
|
+
: 120000;
|
|
192
196
|
const spread = (obj: any) =>
|
|
193
197
|
obj && typeof obj === "object"
|
|
194
198
|
? Object.fromEntries(
|
|
@@ -199,7 +203,7 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
|
199
203
|
: {};
|
|
200
204
|
|
|
201
205
|
// Merge options with defaults
|
|
202
|
-
const options = {
|
|
206
|
+
const options: TestOptions = {
|
|
203
207
|
destroy: true,
|
|
204
208
|
quiet: false,
|
|
205
209
|
password: "test-password",
|
|
@@ -216,20 +220,24 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
|
216
220
|
parent: localTestScope,
|
|
217
221
|
},
|
|
218
222
|
async (scope) => {
|
|
219
|
-
return it(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
223
|
+
return it(
|
|
224
|
+
testName,
|
|
225
|
+
async () => {
|
|
226
|
+
// Enter test scope since bun calls from different scope
|
|
227
|
+
scope.enter();
|
|
228
|
+
try {
|
|
229
|
+
await fn(scope);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.error(err);
|
|
232
|
+
throw err;
|
|
233
|
+
} finally {
|
|
234
|
+
if (options.destroy !== false) {
|
|
235
|
+
await destroy(scope);
|
|
236
|
+
}
|
|
230
237
|
}
|
|
231
|
-
}
|
|
232
|
-
|
|
238
|
+
},
|
|
239
|
+
timeout
|
|
240
|
+
);
|
|
233
241
|
}
|
|
234
242
|
);
|
|
235
243
|
}
|
|
@@ -58,7 +58,7 @@ export async function processFrontmatterFiles(
|
|
|
58
58
|
const frontmatter = frontmatterMatch[1];
|
|
59
59
|
const orderMatch = frontmatter.match(/order:\s*(\d+)/);
|
|
60
60
|
if (orderMatch) {
|
|
61
|
-
order =
|
|
61
|
+
order = parseFloat(orderMatch[1]);
|
|
62
62
|
}
|
|
63
63
|
const titleMatch = frontmatter.match(/title:\s*(.+)/);
|
|
64
64
|
if (titleMatch) {
|