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.
@@ -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
+ }
@@ -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
  }
@@ -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: props.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(testName, async () => {
220
- // Enter test scope since bun calls from different scope
221
- scope.enter();
222
- try {
223
- await fn(scope);
224
- } catch (err) {
225
- console.error(err);
226
- throw err;
227
- } finally {
228
- if (options.destroy !== false) {
229
- await destroy(scope);
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 = parseInt(orderMatch[1]);
61
+ order = parseFloat(orderMatch[1]);
62
62
  }
63
63
  const titleMatch = frontmatter.match(/title:\s*(.+)/);
64
64
  if (titleMatch) {