CoVectors
CoVectors let you store and query high‑dimensional vectors directly in Jazz apps. They are ideal for semantic search, or personalization features that work offline, sync across devices, and remain end‑to‑end encrypted.
The Journal example demonstrates semantic search using of CoVector.
CoVectors are defined using co.vector()
, and are often used as fields in a CoMap within a CoList (making it easy to perform vector search across list items).
import {
import co
co,import z
z } from "jazz-tools"; constconst Embedding: co.Vector
Embedding =import co
co.vector(384); // Define 384-dimensional embedding const
function vector(dimensions: number): co.Vector export vector
Document =
const Document: co.Map<{ content: z.z.ZodString; embedding: co.Vector; }, unknown, Account | Group>
import co
co.map({
map<{ content: z.z.ZodString; embedding: co.Vector; }>(shape: { content: z.z.ZodString; embedding: co.Vector; }): co.Map<{ content: z.z.ZodString; embedding: co.Vector; }, unknown, Account | Group> export map
content: z.z.ZodString
content:import z
z.string(),
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload) export string
embedding: co.Vector
embedding:const Embedding: co.Vector
Embedding, }); export constDocumentsList =
const DocumentsList: co.List<co.Map<{ content: z.z.ZodString; embedding: co.Vector; }, unknown, Account | Group>>
import co
co.list(
list<co.Map<{ content: z.z.ZodString; embedding: co.Vector; }, unknown, Account | Group>>(element: co.Map<{ content: z.z.ZodString; embedding: co.Vector; }, unknown, Account | Group>): co.List<...> export list
Document);
const Document: co.Map<{ content: z.z.ZodString; embedding: co.Vector; }, unknown, Account | Group>
The number of dimensions matches the embedding model used in your app. Many small sentence transformers produce 384‑dim vectors; others use 512, 768, 1024 or more.
Creating CoVectors
You can create vectors in your Jazz application from an array of numbers, or Float32Array instance.
// Generate embeddings (bring your own embeddings model) const
const vectorData: number[]
vectorData = awaitconst createEmbedding: (text: string) => Promise<number[]>
createEmbedding("Text"); constnewDocument =
const newDocument: { readonly content: string; readonly embedding: Readonly<CoVector>; } & CoMap
Document.
const Document: co.Map<{ content: z.z.ZodString; embedding: co.Vector; }, unknown, Account | Group>
create({
CoMapSchema<{ content: ZodString; embedding: CoVectorSchema; }, unknown, Account | Group>.create(init: { content: string; embedding: Readonly<CoVector>; }, options?: { owner?: Group; unique?: CoValueUniqueness["uniqueness"]; } | Group): { ...; } & CoMap (+1 overload)
content: string
content: "Text",embedding: Readonly<CoVector>
embedding:const Embedding: co.Vector
Embedding.
CoVectorSchema.create(vector: number[] | Float32Array, options?: { owner: Group; } | Group): CoVectorInstance (+1 overload)
Create a `CoVector` from a given vector.create(const vectorData: number[]
vectorData), });documents.
const documents: CoListInstance<co.Map<{ content: z.z.ZodString; embedding: co.Vector; }, unknown, Account | Group>>
$jazz.
CoList<{ readonly content: string; readonly embedding: Readonly<CoVector>; } & CoMap>.$jazz: CoListJazzApi<CoListInstance<co.Map<{ content: z.z.ZodString; embedding: co.Vector; }, unknown, Account | Group>>>
CoListJazzApi<CoListInstance<CoMapSchema<{ content: ZodString; embedding: CoVectorSchema; }, unknown, Account | Group>>>.push(...items: (({ readonly content: string; readonly embedding: Readonly<CoVector>; } & CoMap) | CoMapInit<{ readonly content: string; readonly embedding: Readonly<CoVector>; } & CoMap>)[]): number
Appends new elements to the end of an array, and returns the new length of the array.push(newDocument);
const newDocument: { readonly content: string; readonly embedding: Readonly<CoVector>; } & CoMap
Ownership
Like other CoValues, you can specify ownership when creating CoVectors.
// Create with shared ownership const
const teamGroup: Group
teamGroup =class Group
Group.create();
Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Group
const teamGroup: Group
teamGroup.Group.addMember(member: Account, role: AccountRole): void (+3 overloads)
addMember(colleagueAccount, "writer"); const
const colleagueAccount: Account | ({ readonly [x: string]: any; } & Account)
const teamList: Readonly<CoVector>
teamList =import co
co.vector(384).
function vector(dimensions: number): co.Vector export vector
CoVectorSchema.create(vector: number[] | Float32Array, options?: { owner: Group; } | Group): CoVectorInstance (+1 overload)
Create a `CoVector` from a given vector.create(const vector: number[]
vector, {owner: Group
owner:const teamGroup: Group
teamGroup });
See Groups as permission scopes for more information on how to use groups to control access to CoVectors.
Immutability
CoVectors cannot be changed after creation. Instead, create a new CoVector with the updated values and replace the previous one.
Semantic Search
Semantic search lets you find data based on meaning, not just keywords. In Jazz, you can easily sort results by how similar they are to your search query.
You can load your data using the .load
method, then compute and sort the results by similarity to your query embedding:
Wrapping each item with its similarity score makes it easy to sort, filter, and display the most relevant results. This approach is widely used in vector search and recommendation systems, since it keeps both the data and its relevance together for further processing or display.
Cosine Similarity
To compare how similar two vectors are, we use their cosine similarity. This returns a value between -1
and 1
, describing how similar the vectors are:
1
means the vectors are identical0
means the vectors are orthogonal (i.e. no similarity)-1
means the vectors are opposite direction (perfectly dissimilar).
If you sort items by their cosine similarity, the ones which are most similar will appear at the top of the list.
Jazz provides a built-in $jazz.cosineSimilarity
method to calculate this for you.
Embedding Models
CoVectors handles storage and search, you provide the vectors. Generate embeddings with any model you prefer (Hugging Face, OpenAI, custom, etc).
Recommended: Run models locally for privacy and offline support using Transformers.js. Check our Journal app example to see how to do this.
The following models offer a good balance between accuracy and performance:
- Xenova/all-MiniLM-L6-v2 — 384 dimensions, ~23 MB
- Xenova/paraphrase-multilingual-mpnet-base-v2 — 768 dimensions, ~279 MB
- mixedbread-ai/mxbai-embed-large-v1 — 1024 dimensions, ~337 MB
- Browse more models →
Alternatively, you can generate embeddings using server-side or commercial APIs (such as OpenAI or Anthropic).
Best Practices
Changing embedding models
Always use the same embedding model for all vectors you intend to compare.
Mixing vectors from different models (or even different versions of the same model) will result in meaningless similarity scores, as the vector spaces are not compatible.
If you need to switch models, consider storing the model identifier alongside each vector, and re-embedding your data as needed.