Skip to content

Collections

Bazaar provides document-orientated (NoSQL) and schema-less databases scoped to a user and app. A database contains collections which are analogous to tables in a relational database. Collections are made up of documents, or docs, analogous to rows in a relational database.

Using a Collection

To use a collection, create a collection object. By default, a collection object interacts with the database of the authenticated user.

A collection will be lazily created the first time someone tries to interact with it.

const exampleCollection = bzr.collection("example-collection");
// Use the collection
const allDocs = await exampleCollection.getAll();

When using TypeScript, you can specify the type of the collection doc.

type TaskDoc = {
id: string; // required
name: string;
completed: boolean;
};
const tasksCollection = bzr.collection<TaskDoc>("tasks");

Collection doc types must include an id of type string. That is, it must extend or conform to the Doc type.

type Doc = {
id: string;
};

Use a Specific User’s Collection

To use the collection of a specific user, pass an options argument to set a user ID. If not explicitly set, the user ID defaults to the authenticated user’s.

const options = { userId: "collection-owners-user-id" };
const exampleCollection = bzr.collection("example-collection", options);

Handle Lazy Collection Creation

When a collection is lazily created, you might want to take some action. For example, to set permissions or insert some initial data. You can do this as follows:

const options = {
onCreate: async () => {
exampleCollection.insertOne({ id: 1, task: "Create first Bazaar app!" });
},
};
const exampleCollection = bzr.collection("example-collection", options);

Get all Docs

Get all docs from a collection.

const docs = await exampleCollection.getAll(filter, options);

The filter and options parameters are optional:

const allDocs = await exampleCollection.getAll();

A filter allows you to get only a subset of documents. Learn how Filtering works.

Use the options parameter to set offsets and order to further control returned docs.

const filter = {}; // No filter
const options = { startOffset: 10, endOffset: 20, orderBy: { height: "desc" } };
const docs = await exampleCollection.getAll(filter, options);

Get Docs by Page

Offsets are useful, but for a more ergonomic way to work with pagination, there is getPage.

const pageNumber = 2;
const pageSize = 20;
const paginatedDocs = await exampleCollection.getPage(pageNumber, pageSize);

Like getAll, getPage also takes optional filter and options parameters.

const paginatedDocs = await exampleCollection.getPage(
pageNumber,
pageSize,
filter,
options,
);

The options parameter allows you to order the returned docs.

const options = { orderBy: { height: "desc" } };

Get a Doc

Get a single doc.

const doc = await exampleCollection.getOne("example-doc-id");

If no document with this ID exists, null is returned.

Insert a Doc

Insert a new doc with an auto-generated ID.

const docId = await exampleCollection.insertOne({ name: "Skylar" });

Insert a new doc with a specific ID.

const docId = await exampleCollection.insertOne({ id: "1", name: "Skylar" });

The ID must be unique; attempting to insert a document with an existing ID will fail.

Update a Doc

Update a doc.

const message = await exampleCollection.updateOne(docId, partialDoc);

For example, if you have a doc:

const doc = {
id: "1",
name: "Leo",
age: 40,
weightKg: 80,
};

And update as follows:

const message = await exampleCollection.updateOne("1", { weightKg: 82 });

The result will be:

const doc = {
id: "1",
name: "Leo",
age: 40,
weightKg: 82,
};

Replace a Doc

Replace an existing doc with a new one. The new doc completely overwrites the existing doc.

const newDoc = { id: "example-doc-id", name: "New Name", completed: false };
const message = await exampleCollection.replaceOne("example-doc-id", newDoc);

Delete All Docs

Delete all documents in a collection. This action cannot be undone.

const message = await exampleCollection.deleteAll(filter);

See filter docs.

Delete a Doc

Delete a specific document from a collection by its ID.

const message = await exampleCollection.deleteOne("example-doc-id");

Subscribe to Collection Change Events

Receive realtime updates whenever any document in the collection changes. Returns an unsubscribe function.

const unsubscribe = await exampleCollection.subscribeAll(filter, listener);

Similar to the getAll method, the filter is optional and can be empty. See filter docs.

Subscribe Listener

The listener is an object that contains optional callbacks to be run when a document is added, updated, or deleted. It has the following type:

type SubscribeListener<T extends Doc> = {
onAdd?: (doc: T) => void;
onChange?: (oldDoc: T, newDoc: T) => void;
onDelete?: (doc: T) => void;
onInitial?: (doc: T) => void;
};

onInitial usage notes:

  • When a listener is first invoked and an onInitial function is provided, it will be called for each existing document in the collection.
  • The onInitial callback serves as a convenient alternative to manually fetching and iterating over all documents in a collection.
  • onInitial is particularly useful for initializing local state with existing data from the collection.

For example, instead of:

const docs = await bzr.exampleCollection.getAll();
docs.forEach((doc) => addToLocalState(doc));

You can use:

bzr.exampleCollection.subscribe({
onInitial: (doc) => addToLocalState(doc),
// ... other callbacks as needed
});

This approach simplifies the process of initially populating local state with existing documents from the collection.

Subscribe to Doc Change Events

Receive realtime updates for a specific document in the collection.

const docId = "example-doc-id";
const unsubscribe = await exampleCollection.subscribeOne(docId, listener);

Returns an unsubscribe function.

See Subscribe Listener docs.

Subscribe Listener Helpers

When subscribing to change events, a common use case is to keep a local array or object in sync with the database. For those cases you can utilize the arrayMirrorSubscribeListener and objectMirrorSubscribeListener subscribe listener helpers to avoid writing boilerplate to update arrays and objects.

const unsubscribe = await exampleCollection.subscribeAll(
{},
arrayMirrorSubscribeListener(exampleArray),
);

These helpers can also take a subscribe listener as a second argument, allowing you to customize any of the listener callbacks.

Reserved field names and values

All special field names and values that are used throughout Bazaar start with a $ sign. As such, we recommend not using field names or values that start with a $.

In documents, currently the only reserved value is $now. It will resolve to a UTC timestamp on the server. This allows you to generate a predictable timestamp in your data no mater in which timestamp the app is running.

Filtering

You can filter the document queries with fine-grained control.

Comparison Operatiors

By default, filter values are checked for equality. The filter

const filter = {
isActive: true,
};

will return all documents where isActive === true.

Comparison operator can also be specified explicitly. The equality filter above would then look as follows:

const filter = {
isActive: { $eq: true },
};

Filters currently support the following comparison operators:

  • $eq: equal
  • $ne: not equal
  • $gt: greater than
  • $ge: greater than or equal
  • $lt: less than
  • $le: less than or equal

Filter with $contains Operator

The $contains operator is used to check if an array field in the documents contains a specified value.

// Select documents where the 'tags' array field contains 'urgent'
const filter = {
tags: { $contains: "urgent" },
};

Filter with AND Conditions

// Select documents where `age` is greater than 30 and `isActive` is true
const filter = {
age: { $gt: 30 },
isActive: true,
};

For simple filters, this is enough. For more complex filtering queries you can also use the $and key:

// Select documents where `age` is greater than 30 and `isActive` is true
const filter = {
$and: [{ age: { $gt: 30 } }, { isActive: true }],
};

Filter with OR Conditions

// Select documents where `status` is either 'pending' or 'completed'
const filter = {
$or: [{ status: "pending" }, { status: "completed" }],
};

Filter with NOT Condition

// Select documents where `category` is not 'archived'
const filter = {
$not: { category: "archived" },
};

Nested Filter Conditions

// Select documents where `manager` is 'John' and either `yearsOfExperience` is greater than 5 or (`department` is 'HR' and `location` is 'New York')
const filter = {
manager: "John",
$or: [
{ yearsOfExperience: { $gt: 5 } },
{ $and: [{ department: "HR" }, { location: "New York" }] },
],
};

And for a more complex example:

/**
* Select documents where either `height` is between 80 and 140 (exclusive)
* or `weight` is between 10 and 25 (exclusive), and additionally, `age` is less than 55.
*/
const filter = {
$or: [
{
height: {
$gt: 80,
$lt: 140,
},
},
{
weight: {
$gt: 10,
$lt: 25,
},
},
],
age: { $lt: 55 },
};

Ordering (Sorting)

The orderBy option allows you to sort the results fetched from a collection using the getAll or getPage methods. By defining the order criteria for one or more fields, you can organize the returned documents according to specific requirements.

orderBy is an object where each key represents a field name to sort by, and the value is either OrderByType.ASC for ascending order or OrderByType.DESC for descending order.

You can sort by single or multiple fields, specifying the order for each field individually:

// Sorting by 'height' in descending order, then by 'age' in ascending order
const orderBy = { height: OrderByType.DESC, age: OrderByType.ASC };
const sortedDocs = await exampleCollection.getAll(filter, { orderBy });

Managing Collections

Generally, collections are created lazily and don’t need additional management. However, in some cases manually creating, dropping, or listing collections is useful.

Create a Collection

Create collections manually using the bzr.collections.create method.

const message = await bzr.collections.create("example-collection-name");

Drop a Collection

Drop, or delete, a collection and all of its docs.

const message = await bzr.collections.drop("example-collection-name");

List Collections

List the names of all collections.

const collectionsNames = await bzr.collections.list();