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.
Files changed (44) hide show
  1. package/lib/alchemy.d.ts +11 -18
  2. package/lib/alchemy.js +34 -39
  3. package/lib/apply.js +3 -1
  4. package/lib/cloudflare/api-error.js +1 -1
  5. package/lib/cloudflare/bindings.d.ts +2 -1
  6. package/lib/cloudflare/bound.d.ts +2 -1
  7. package/lib/cloudflare/index.d.ts +3 -0
  8. package/lib/cloudflare/index.js +3 -0
  9. package/lib/cloudflare/pipeline.js +0 -3
  10. package/lib/cloudflare/vectorize-index.d.ts +136 -0
  11. package/lib/cloudflare/vectorize-index.js +170 -0
  12. package/lib/cloudflare/vectorize-metadata-index.d.ts +95 -0
  13. package/lib/cloudflare/vectorize-metadata-index.js +120 -0
  14. package/lib/cloudflare/vite-site.d.ts +39 -0
  15. package/lib/cloudflare/vite-site.js +57 -0
  16. package/lib/cloudflare/worker.js +7 -0
  17. package/lib/cloudflare/wrangler.json.d.ts +7 -0
  18. package/lib/cloudflare/wrangler.json.js +11 -1
  19. package/lib/destroy.js +14 -2
  20. package/lib/fs/file-system-state-store.d.ts +1 -0
  21. package/lib/fs/file-system-state-store.js +23 -13
  22. package/lib/scope.d.ts +1 -1
  23. package/lib/scope.js +5 -2
  24. package/lib/state.d.ts +2 -2
  25. package/lib/test/bun.d.ts +1 -6
  26. package/lib/test/bun.js +18 -37
  27. package/package.json +1 -1
  28. package/src/alchemy.ts +51 -45
  29. package/src/apply.ts +6 -2
  30. package/src/cloudflare/api-error.ts +1 -1
  31. package/src/cloudflare/bindings.ts +2 -0
  32. package/src/cloudflare/bound.ts +8 -5
  33. package/src/cloudflare/index.ts +3 -0
  34. package/src/cloudflare/pipeline.ts +0 -4
  35. package/src/cloudflare/vectorize-index.ts +331 -0
  36. package/src/cloudflare/vectorize-metadata-index.ts +239 -0
  37. package/src/cloudflare/vite-site.ts +111 -0
  38. package/src/cloudflare/worker.ts +6 -0
  39. package/src/cloudflare/wrangler.json.ts +19 -1
  40. package/src/destroy.ts +25 -8
  41. package/src/fs/file-system-state-store.ts +23 -14
  42. package/src/scope.ts +9 -5
  43. package/src/state.ts +3 -3
  44. package/src/test/bun.ts +31 -54
package/lib/alchemy.d.ts CHANGED
@@ -28,7 +28,6 @@ export declare const alchemy: Alchemy;
28
28
  * await app.finalize();
29
29
  */
30
30
  export interface Alchemy {
31
- scope: typeof scope;
32
31
  run: typeof run;
33
32
  destroy: typeof destroy;
34
33
  /**
@@ -52,7 +51,7 @@ export interface Alchemy {
52
51
  * password: process.env.SECRET_PASSPHRASE
53
52
  * });
54
53
  */
55
- (...parameters: Parameters<typeof scope>): Promise<Scope>;
54
+ (appName: string, options?: Omit<AlchemyOptions, "appName">): Promise<Scope>;
56
55
  /**
57
56
  * Template literal tag that supports file interpolation for documentation.
58
57
  * Automatically formats the content and appends file contents as code blocks.
@@ -115,21 +114,15 @@ export interface AlchemyOptions {
115
114
  */
116
115
  password?: string;
117
116
  }
118
- /**
119
- * Enter a new scope synchronously.
120
- *
121
- * @example
122
- * // Create a scope with a password for secret handling
123
- * await using scope = alchemy.scope("my-scope", {
124
- * password: process.env.SECRET_PASSPHRASE
125
- * });
126
- *
127
- * // Use secrets within the scope
128
- * const resource = await Resource("my-resource", {
129
- * apiKey: alchemy.secret(process.env.API_KEY)
130
- * });
131
- */
132
- declare function scope(id: string | undefined, options?: AlchemyOptions): Scope;
117
+ export interface ScopeOptions extends AlchemyOptions {
118
+ enter: boolean;
119
+ }
120
+ export interface RunOptions extends AlchemyOptions {
121
+ /**
122
+ * @default false
123
+ */
124
+ isResource?: boolean;
125
+ }
133
126
  /**
134
127
  * Run a function in a new scope asynchronously.
135
128
  * Useful for isolating secret handling with a specific password.
@@ -147,7 +140,7 @@ declare function scope(id: string | undefined, options?: AlchemyOptions): Scope;
147
140
  */
148
141
  declare function run<T>(...args: [id: string, fn: (this: Scope, scope: Scope) => Promise<T>] | [
149
142
  id: string,
150
- options: AlchemyOptions,
143
+ options: RunOptions,
151
144
  fn: (this: Scope, scope: Scope) => Promise<T>
152
145
  ]): Promise<T>;
153
146
  export declare function env<T = string>(name: string, value?: T | undefined, error?: string): Promise<T>;
package/lib/alchemy.js CHANGED
@@ -1,10 +1,8 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { DestroyedSignal, destroy } from "./destroy";
3
+ import { destroy, DestroyedSignal } from "./destroy";
4
4
  import { Scope } from "./scope";
5
5
  import { secret } from "./secret";
6
- // TODO: support browser
7
- const DEFAULT_STAGE = process.env.ALCHEMY_STAGE ?? process.env.USER ?? "dev";
8
6
  export const alchemy = _alchemy;
9
7
  /**
10
8
  * Implementation of the alchemy function that handles both application scoping
@@ -13,11 +11,12 @@ export const alchemy = _alchemy;
13
11
  async function _alchemy(...args) {
14
12
  if (typeof args[0] === "string") {
15
13
  const [appName, options] = args;
16
- const root = scope(undefined, {
14
+ const root = new Scope({
17
15
  ...options,
18
16
  appName,
19
17
  stage: options?.stage,
20
18
  });
19
+ root.enter();
21
20
  if (options?.phase === "destroy") {
22
21
  await destroy(root);
23
22
  return process.exit(0);
@@ -119,37 +118,8 @@ async function _alchemy(...args) {
119
118
  }
120
119
  _alchemy.destroy = destroy;
121
120
  _alchemy.run = run;
122
- _alchemy.scope = scope;
123
121
  _alchemy.secret = secret;
124
122
  _alchemy.env = env;
125
- /**
126
- * Enter a new scope synchronously.
127
- *
128
- * @example
129
- * // Create a scope with a password for secret handling
130
- * await using scope = alchemy.scope("my-scope", {
131
- * password: process.env.SECRET_PASSPHRASE
132
- * });
133
- *
134
- * // Use secrets within the scope
135
- * const resource = await Resource("my-resource", {
136
- * apiKey: alchemy.secret(process.env.API_KEY)
137
- * });
138
- */
139
- function scope(id, options
140
- // TODO: maybe we want to allow using _ = await alchemy.scope(import.meta)
141
- // | [meta: ImportMeta]
142
- ) {
143
- const scope = new Scope({
144
- ...options,
145
- appName: options?.appName,
146
- stage: options?.stage ?? DEFAULT_STAGE,
147
- scopeName: id,
148
- parent: options?.parent ?? Scope.get(),
149
- });
150
- scope.enter();
151
- return scope;
152
- }
153
123
  /**
154
124
  * Run a function in a new scope asynchronously.
155
125
  * Useful for isolating secret handling with a specific password.
@@ -169,20 +139,45 @@ async function run(...args) {
169
139
  const [id, options, fn] = typeof args[1] === "function"
170
140
  ? [args[0], undefined, args[1]]
171
141
  : args;
172
- const scope = alchemy.scope(id, options);
142
+ const _scope = new Scope({
143
+ ...options,
144
+ scopeName: id,
145
+ });
173
146
  try {
174
- return await fn.bind(scope)(scope);
147
+ if (options?.isResource !== true && _scope.parent) {
148
+ // TODO(sam): this is an awful hack to differentiate between naked scopes and resources
149
+ const seq = _scope.parent.seq();
150
+ const output = {
151
+ ID: id,
152
+ FQN: "",
153
+ Kind: "alchemy::Scope",
154
+ Scope: _scope,
155
+ Seq: seq,
156
+ };
157
+ const resource = {
158
+ kind: "scope",
159
+ id,
160
+ seq,
161
+ data: {},
162
+ fqn: "",
163
+ props: {},
164
+ status: "created",
165
+ output,
166
+ };
167
+ await _scope.parent.state.set(id, resource);
168
+ _scope.parent.resources.set(id, Object.assign(Promise.resolve(resource), output));
169
+ }
170
+ return await _scope.run(async () => fn.bind(_scope)(_scope));
175
171
  }
176
172
  catch (error) {
177
173
  if (!(error instanceof DestroyedSignal)) {
178
- scope.fail();
179
- }
180
- else {
174
+ console.log(error);
175
+ _scope.fail();
181
176
  }
182
177
  throw error;
183
178
  }
184
179
  finally {
185
- await scope.finalize();
180
+ await _scope.finalize();
186
181
  }
187
182
  }
188
183
  export async function env(name, value, error) {
package/lib/apply.js CHANGED
@@ -75,7 +75,9 @@ export async function apply(resource, props, options) {
75
75
  isReplaced = true;
76
76
  },
77
77
  });
78
- const output = await alchemy.run(resource.ID, async () => provider.handler.bind(ctx)(resource.ID, props));
78
+ const output = await alchemy.run(resource.ID, {
79
+ isResource: true,
80
+ }, async () => provider.handler.bind(ctx)(resource.ID, props));
79
81
  if (!quiet) {
80
82
  console.log(`${phase === "create" ? "Created" : "Updated"}: "${resource.FQN}"`);
81
83
  }
@@ -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.errors || [
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,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";
@@ -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";
@@ -71,7 +71,6 @@ export const Pipeline = Resource("cloudflare::Pipeline", async function (id, pro
71
71
  const api = await createCloudflareApi(props);
72
72
  const pipelineName = props.name || id;
73
73
  if (this.phase === "delete") {
74
- console.log("Deleting Cloudflare Pipeline:", pipelineName);
75
74
  if (props.delete !== false) {
76
75
  // Delete Pipeline
77
76
  await deletePipeline(api, pipelineName);
@@ -82,13 +81,11 @@ export const Pipeline = Resource("cloudflare::Pipeline", async function (id, pro
82
81
  else {
83
82
  let pipelineData;
84
83
  if (this.phase === "create") {
85
- console.log("Creating Cloudflare Pipeline:", pipelineName);
86
84
  pipelineData = await createPipeline(api, pipelineName, props);
87
85
  }
88
86
  else {
89
87
  // Update operation
90
88
  if (this.output?.id) {
91
- console.log("Updating Cloudflare Pipeline:", pipelineName);
92
89
  // Check if name is being changed, which is not allowed
93
90
  if (props.name !== this.output.name) {
94
91
  throw new Error("Cannot update Pipeline name after creation. Pipeline name is immutable.");
@@ -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 {};