Document Store

Store structured, JSON-like records inside your Cloud Function's private MongoDB collection.

A Document Store is a database designed to manage data in flexible, self-contained records. Instead of rigid tables with fixed columns, each record can have its own structure. Each Cloud Function has its own private Document Store, backed by a MongoDB collection.

Characteristics

  • The store is private to your Cloud Function. No other function can read or write its data.
  • The store accepts any JSON-serialisable data.
  • A local Document Store is started automatically when you run your function in development.
  • In production, data persists until your function deletes it.
  • In local development the store resets at every restart of the Ingress. This means that all the data that was uploaded during local testing will be lost. For this reason, it is recommended to only use non-essential test data during local development.
  • The store and its available methods require ixoncdkingress >= 0.0.10 to work.

When you start your Cloud Function locally, you should see:

ixoncdkingress - INFO - Starting DocumentDB server
ixoncdkingress - INFO - DocumentDB server started successfully

These messages confirm the local store is running.

Accessing the Document Store client

The DocumentDBClient is available on the FunctionContext as document_db_client:

from ixoncdkingress.function.context import FunctionContext

@FunctionContext.expose
def document_test(context: FunctionContext):
    print(context.document_db_client)
🚧

The client can be None

The store is not always available. Always check that context.document_db_client is not None before using it. Cases where it is None:

  • In development, when calling the function without a company set on the context (a company ID and company name are required for the store to be available).
  • When the local MongoDB container fails to boot.
❗️

Timezones

All dates, times, and datetimes are returned as naive objects (no timezone). All values are stored in UTC and should be interpreted as UTC. Always use UTC when storing times.

Document IDs

When you insert a document, the store automatically assigns a BSON ObjectId as _id.

Nested documents and IDs

If needed, like in case of nested documents, you can also create an _id yourself before saving.

Check whether your document is a nested or not: these do not receive an automatic _id. If you need one, assign it yourself with bson.objectid.ObjectId() before saving.

from bson.objectid import ObjectId

_id = ObjectId()
document_to_save = {'_id': _id, 'text': 'test document'}

Receiving an _id as a string

This is the most common cause of find_one or delete_one functions not returning/deleting a document.

If your function receives an _id from the caller as a string (e.g. from a UI Component), you must parse it back to an ObjectId before using it in a filter — otherwise the store will not match the document.

from bson.objectid import ObjectId

@FunctionContext.expose
def remove(context: FunctionContext, note_id: str):
   if context.document_db_client:
         context.document_db_client.delete_one({'_id': ObjectId(note_id)})

Available methods

MethodParametersDescription
insert_onedocument: DictInsert a single document.
insert_manydocuments: Iterable[Dict]Insert multiple documents.
update_onefilter_map: Mapping, update: MappingUpdate a single matching document.
update_manyfilter_map: Mapping, update: MappingUpdate all matching documents.
delete_onefilter_map: MappingDelete a single matching document.
delete_manyfilter_map: MappingDelete all matching documents.
find_onefilter_map: MappingReturn one matching document, or None.
findfilter_map: MappingReturn all matching documents.

Example

Here is a small CRUD pattern over a notes collection, with the matching Svelte UI Component to drive it:

from typing import Any, List, TypedDict
from bson.json_util import dumps
from ixoncdkingress.function.context import FunctionContext

@FunctionContext.expose
def find_text(context: FunctionContext, id: int) -> str | None:
    # Find one document by its ID.
    # Return None if it doesn't exist, otherwise return its JSON representation.
    document = context.document_db_client.find_one(filter_map={'id': id})
    if document is None:
        return None
    return dumps(document)

@FunctionContext.expose
def find_all(context: FunctionContext) -> str:
    return dumps(context.document_db_client.find())

@FunctionContext.expose
def add_or_update_text(context: FunctionContext, id: int, text: str) -> None:
    # Check whether the document already exists
    existing = context.document_db_client.find_one(filter_map={'id': id})

    # If the document already exists, update its text;
    # otherwise insert a new document.
    if existing is not None:
        context.document_db_client.update_one({'id': id}, {'$set': {'text': text}})
    else:
        context.document_db_client.insert_one({'id': id, 'text': text})

class Document(TypedDict):
    id: int
    text: str

@FunctionContext.expose
def insert_many(context: FunctionContext, documents: List[Document]) -> None:
    context.document_db_client.insert_many(documents)
<script>
  import { onMount } from "svelte";
  export let context;
  let client;

  async function callBackendFunction(functionName, parameters = {}) {
    try {
      return await client.call(functionName, parameters);
    } catch (error) {
      console.error(`Error calling ${functionName}:`, error);
    }
  }

  onMount(() => {
    client = context.createBackendComponentClient();
    (async () => {
      await callBackendFunction("functions.notes.find_all");
      await callBackendFunction("functions.notes.add_or_update_text", { id: 0, text: "Hello, World!" });
      await callBackendFunction("functions.notes.insert_many", {
        documents: [{ id: 1, text: "First text" }, { id: 2, text: "Second text" }],
      });
    })();
  });
</script>

<main>
  <h1>Frontend Component</h1>
  <p>Open the terminal to see the results of the backend calls.</p>
</main>

Inspecting the local store

You can browse the contents of your local Document Store with MongoDB Compass or the MongoDB extension for VS Code. This can only be done for a local instance of the Document Store.

For complex queries, see Advanced Document Store Querying.