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 coco, import zz } from "jazz-tools";

const const Embedding: co.VectorEmbedding = import coco.
function vector(dimensions: number): co.Vector
export vector
vector
(384); // Define 384-dimensional embedding
const
const Document: co.Map<{
    content: z.z.ZodString;
    embedding: co.Vector;
}, unknown, Account | Group>
Document
= import coco.
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
map
({
content: z.z.ZodStringcontent: import zz.
function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload)
export string
string
(),
embedding: co.Vectorembedding: const Embedding: co.VectorEmbedding, }); export const
const DocumentsList: co.List<co.Map<{
    content: z.z.ZodString;
    embedding: co.Vector;
}, unknown, Account | Group>>
DocumentsList
= import coco.
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
list
(
const Document: co.Map<{
    content: z.z.ZodString;
    embedding: co.Vector;
}, unknown, Account | Group>
Document
);

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 = await const createEmbedding: (text: string) => Promise<number[]>createEmbedding("Text");

const 
const newDocument: {
    readonly content: string;
    readonly embedding: Readonly<CoVector>;
} & CoMap
newDocument
=
const Document: co.Map<{
    content: z.z.ZodString;
    embedding: co.Vector;
}, unknown, Account | Group>
Document
.
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)
create
({
content: stringcontent: "Text", embedding: Readonly<CoVector>embedding: const Embedding: co.VectorEmbedding.
CoVectorSchema.create(vector: number[] | Float32Array, options?: {
    owner: Group;
} | Group): CoVectorInstance (+1 overload)
Create a `CoVector` from a given vector.
create
(const vectorData: number[]vectorData),
});
const documents: CoListInstance<co.Map<{
    content: z.z.ZodString;
    embedding: co.Vector;
}, unknown, Account | Group>>
documents
.
CoList<{ readonly content: string; readonly embedding: Readonly<CoVector>; } & CoMap>.$jazz: CoListJazzApi<CoListInstance<co.Map<{
    content: z.z.ZodString;
    embedding: co.Vector;
}, unknown, Account | Group>>>
$jazz
.
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.
@paramitems New elements to add to the array.@categoryContent
push
(
const newDocument: {
    readonly content: string;
    readonly embedding: Readonly<CoVector>;
} & CoMap
newDocument
);

Ownership

Like other CoValues, you can specify ownership when creating CoVectors.

// Create with shared ownership
const const teamGroup: GroupteamGroup = class Group
@categoryIdentity & Permissions
Group
.
Group.create<Group>(this: CoValueClass<Group>, options?: {
    owner: Account;
} | Account): Group
create
();
const teamGroup: GroupteamGroup.Group.addMember(member: Account, role: AccountRole): void (+3 overloads)addMember(
const colleagueAccount: Account | ({
    readonly [x: string]: any;
} & Account)
colleagueAccount
, "writer");
const const teamList: Readonly<CoVector>teamList = import coco.
function vector(dimensions: number): co.Vector
export vector
vector
(384).
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: Groupowner: const teamGroup: GroupteamGroup });

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 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.

No content available for tab:

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 identical
  • 0 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:

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.