Skip to content

2.0.0-beta.46 - Vectorize

v2.0.0-beta.46 ships Cloudflare Vectorize as a bindable resource — index, metadata indexes, and an Effect-native runtime client — plus an important fix to how the Cloudflare state store bootstraps.

A VectorizeIndex is a globally distributed vector database index. Declare one as a resource, bind it to a Worker, and you get a typed Effect-native client for inserting and querying vector embeddings.

const index = yield* Cloudflare.VectorizeIndex("docs", {
dimensions: 768,
metric: "cosine",
});

Or pin dimensions and metric to a managed embedding model with a preset:

const index = yield* Cloudflare.VectorizeIndex("docs", {
preset: "@cf/baai/bge-base-en-v1.5",
});

The index is immutabledimensions, metric, preset, and description are all fixed at creation, so changing any of them triggers a replacement.

Cloudflare.VectorizeIndex.bind(index) attaches the binding and hands back a client whose methods — upsert, query, queryById, insert, deleteByIds, getByIds, describe — are all yield*-able Effects:

export default class Worker extends Cloudflare.Worker<Worker>()(
"Worker",
{ main: import.meta.filename },
Effect.gen(function* () {
const docs = yield* Cloudflare.VectorizeIndex.bind(index);
return {
fetch: Effect.gen(function* () {
yield* docs.upsert([
{ id: "1", values: [0.1, 0.2, 0.3], metadata: { kind: "doc" } },
]);
const matches = yield* docs.query([0.1, 0.2, 0.3], { topK: 5 });
return yield* HttpServerResponse.json({ count: matches.count });
}),
};
}).pipe(Effect.provide(Cloudflare.VectorizeIndexBindingLive)),
) {}

By default you can’t filter a query on a metadata property — the property has to be indexed first. VectorizeMetadataIndex declares that index. Point it at a parent index, name the property, and give its type:

const index = yield* Cloudflare.VectorizeIndex("docs", {
dimensions: 768,
metric: "cosine",
});
yield* Cloudflare.VectorizeMetadataIndex("kind-index", {
indexName: index.indexName,
propertyName: "kind",
indexType: "string",
});

With the metadata index in place (and created before vectors are inserted), query accepts a filter against that property:

const matches = yield* docs.query([0.1, 0.2, 0.3], {
topK: 3,
filter: { kind: { $eq: "second" } },
});

Metadata indexes are immutable too — changing the property name, type, or parent index replaces them.

Thanks David J. Felix and John Royal for the contribution. (#407)

Fix: state-store bootstrap no longer rotates your keys

Section titled “Fix: state-store bootstrap no longer rotates your keys”

The bug: updating the state store stack always used a fresh local state, so the auth token — and, worse, the encryption key — rotated on every run. beta.46 fixes the bootstrap to read from remote state, which already holds the auth token and encryption key, so neither is regenerated. You can run

Terminal window
bun alchemy cloudflare bootstrap --force

to force an update of everything against the real state without rotating the keys.

The same change hardens a few adjacent failure modes:

  • Decrypt failures degrade gracefully — a failed decrypt now returns undefined instead of throwing.
  • Invalid tokens self-heal — an invalid auth token is detected and refreshed.
  • Missing store recovers — if the profile thinks the state store exists but it’s actually gone, it’s recreated.

(#477)