Cosmos DB server-side with TypeScript

matteobortolazzo

Matteo Bortolazzo

Posted on November 6, 2022

Cosmos DB server-side with TypeScript

Server-side programming

An underestimated aspect of Cosmos DB is its server-side components:

  • Stored procedures
  • Triggers
  • User-defined functions (UDFs)

These are all JavaScript functions that run natively in Cosmos DB's query engine (close to Edge 18).

Stored procedures

These are close to the ones we find in RDMSs run in a transactional context with a logical partition. We can read and write documents within the partition. If the operation throws any exception, the engine rolls back the changes.

Triggers

Triggers are functions that we can run before or after specific document operations.

For example, we can:

  • Add properties to a request before the document is created.
  • Only allow the update of a document if the request is correct.
  • Update a second document (within the same partition) after the document is updated.
  • Rollback document changes if a post-trigger fails some checks.

User-defined functions

UDFs are functions we can use in queries with clauses like SELECT or WHERE. We can take full advantage of a modern JavaScript engine and its APIs.

The JavaScript experience

Most of us are used to types. Types self-document the code, and with the help of IDEs, they make development quicker.

Cosmos DB has no official TypeScript type definitions. So, when I started working on the stored procedures, I quickly felt the need for them. Mainly because there are just a bunch of examples and the official JDocs takes a lot of work to consume.

function createToDoItems(items) {
    var collection = getContext().getCollection();
    var collectionLink = collection.getSelfLink();
    var count = 0;

    if (!items) throw new Error("The array is undefined or null.");

    var numItems = items.length;

    if (numItems == 0) {
        getContext().getResponse().setBody(0);
        return;
    }

    tryCreate(items[count], callback);

    function tryCreate(item, callback) {
        var options = { disableAutomaticIdGeneration: false };

        var isAccepted = collection.createDocument(collectionLink, item, options, callback);

        if (!isAccepted) getContext().getResponse().setBody(count);
    }

    function callback(err, item, options) {
        if (err) throw err;
        count++;
        if (count >= numItems) {
            getContext().getResponse().setBody(count);
        } else {
            tryCreate(items[count], callback);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

For these reasons, I converted the official JDocs to TypeScript type definitions and published an NPM package, azure-cosmosdb-js-server-types

Use types

Cosmos DB requires functions to be self-contained. We cannot reference external packages and import types from them. We need to import types during builds.

Run npm install azure-cosmosdb-js-server-types, then update the tsconfig.json file to include the new types:

{
  ...
  "compilerOptions": {
    ...
    "types": [ "azure-cosmosdb-js-server-types" ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we can use getContext or __ without any import:

interface User {
    name: string;
    age: number;
    addresses: Address[];
}

interface Address {
    city: string;
}

function runQuery() {
    const result = __.chain<User>()
        .filter(doc => doc.age > 30)
        .sortBy(user => user.age)
        .map(user => user.addresses)
        .flatten<Address>()
        .value(null, callback)
    if (!result.isAccepted)
        throw new Error("The call was not accepted");

    function callback(err: Error, items: Address[]) {
        if (err) throw err;

        // or getContext().getResponse().setBody({    
        __.response.setBody({
            result: items
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Compile it in .NET builds

If we are a .NET developer, depending on the use case, we might want to compile scripts when building APIs or console applications. Luckily, it's pretty easy.

  1. Add a package.json file in the project folder with dependencies to TypeScript and the type definitions:

    {
        "scripts": {
            "build": "tsc"
        },
        "dependencies": {
            "azure-cosmosdb-js-server-types": "^1.0.0",
            "typescript": "^4.8.4"
        }
    }
    

    We also add a script to compile TypeScript with the tsc command:

  2. Add a tsconfig.json file in the project folder. Here we tell TypeScript how it should compile.

    {
        "compileOnSave": true,
        "compilerOptions": {
            "noImplicitAny": false,
            "noEmitOnError": true,
            "removeComments": false,
            "sourceMap": false,
            "target": "es5",
            "types": [ "azure-cosmosdb-js-server-types" ]
        },
        "include": [
            "./Scripts/**/*"
        ]
    }
    

    In the include section we specify the folder where the TypeScript files are located.

  3. In the csproj file we need to add a step to install the dependencies:

   <Target Name="NpmInstall" Inputs="package.json" Outputs="node_modules/.install-stamp">
        <Exec Command="npm ci" Condition="'$(RestorePackagesWithLockFile)' == 'true'" />
        <Exec Command="npm install" Condition="'$(RestorePackagesWithLockFile)' != 'true'" />
        <Touch Files="node_modules/.install-stamp" AlwaysCreate="true" />
    </Target>
Enter fullscreen mode Exit fullscreen mode

This code installs packages only if needed.

  1. Finally, we add a step to compile the TypeScript files:
   <Target Name="NpmRunBuild" DependsOnTargets="NpmInstall" BeforeTargets="BeforeBuild">
        <Exec Command="npm run build -- --outDir $(OutDir)Scripts" />
    </Target>
Enter fullscreen mode Exit fullscreen mode

The JavaScript files will be generated in a Scripts folder in the project's output folder, e.g. bin\Debug\net6.0. We use the --outDir option to specify the output folder and $(OutDir) to get the project's output folder.

Final words

You can find a working example on GitHub https://github.com/matteobortolazzo/azure-cosmosdb-js-server-types-example.

I hope this article has been helpful to you. If you have any questions or suggestions, please leave a comment below.

💖 💪 🙅 🚩
matteobortolazzo
Matteo Bortolazzo

Posted on November 6, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Cosmos DB server-side with TypeScript
cosmosdb Cosmos DB server-side with TypeScript

November 6, 2022

Autoscale for Cosmos DB
cosmosdb Autoscale for Cosmos DB

July 31, 2019