Skip to main content
Manage persistent state across tasks and task runs using mongo-like collections operations. Often your application will have a notion of some sort of stored state, collections are where this state can be stored, then updated, searched and filtered from any task at any time. When running locally, collections use SQLite (stored in .compose/stage.db). When deployed, each Compose app gets its own fully isolated Postgres database, which stores collections data as well as other internal Compose state. The API is identical in both environments.

Examples

import { TaskContext } from "compose";

type Dog = {
  breed: string;
  color: string;
};

export async function main({ collection, fetch }: TaskContext) {
  const dogsCollection = await collection<Dog>("dogs", [
    { path: "color", type: "text" },
  ]);

  // Get existing state
  const brownDogs = await dogsCollection.findMany({ color: "brown" });

  // Get some data
  const newData = await fetch<Dog>("https://api.dogs.com/v1/dogs/labrador");

  // Update state
  await dogsCollection.insertOne(newData);

  return {
    success: true,
  };
}

Full Interface

export type ScalarIndexType = "text" | "numeric" | "boolean" | "timestamptz";

export interface CollectionIndexSpec {
  path: string;
  type: ScalarIndexType;
  unique?: boolean;
}

export interface FindOptions {
  limit?: number;
  offset?: number;
}

// Filter helpers for comparison operators
export type FilterHelper =
  | "$gt"
  | "$gte"
  | "$lt"
  | "$lte"
  | "$in"
  | "$ne"
  | "$nin"
  | "$exists";
export type HelperValue = Partial<
  Record<FilterHelper, string | number | boolean | string[] | number[]>
>;
export type FilterValue = string | number | boolean | HelperValue;
export type Filter = Record<string, FilterValue>;

export type WithId<T> = T & { id: string };

export interface Collection<TDoc = unknown> {
  readonly name: string;
  insertOne(doc: TDoc, opts?: { id?: string }): Promise<{ id: string }>;
  findOne(filter: Filter): Promise<WithId<TDoc> | null>;
  findMany(filter: Filter, options?: FindOptions): Promise<Array<WithId<TDoc>>>;
  getById(id: string): Promise<WithId<TDoc> | null>;
  /**
   * @param opts.upsert - Defaults to true. Set to false to throw if document doesn't exist.
   */
  setById(
    id: string,
    doc: TDoc,
    opts?: { upsert?: boolean },
  ): Promise<{ id: string; upserted?: boolean; matched?: number }>;
  deleteById(id: string): Promise<{ deletedCount: number }>;
  drop(): Promise<void>;
}
The collection function is available on TaskContext:
collection: <T>(
  name: string,
  indexes?: CollectionIndexSpec[],
) => Promise<Collection<T>>;

Filter operators

The Filter type supports comparison operators beyond simple equality:
// Equality (default)
await dogs.findMany({ color: "brown" });

// Comparison operators
await dogs.findMany({ age: { $gt: 3 } });        // greater than
await dogs.findMany({ age: { $gte: 3 } });       // greater than or equal
await dogs.findMany({ age: { $lt: 10 } });       // less than
await dogs.findMany({ age: { $lte: 10 } });      // less than or equal
await dogs.findMany({ color: { $ne: "brown" } }); // not equal

// Set membership
await dogs.findMany({ breed: { $in: ["labrador", "golden"] } });  // in set
await dogs.findMany({ breed: { $nin: ["chihuahua"] } });          // not in set

// Field existence
await dogs.findMany({ nickname: { $exists: true } });

Reserved collection names

The following names are reserved for internal use and cannot be used as collection names: events, wallets, stage, monitored_transactions, runs, context_functions.

Next Steps

EVM

Interact with EVM blockchains and smart contracts.

env

Set and access environment variables.