Cosmos DB server-side with TypeScript
Tags: cosmosdb, azure, nosql, database
Published: 2022-11-06 20:15:24

## 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](http://azure.github.io/azure-cosmosdb-js-server/index.html) takes a lot of work to consume.

```javascript
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);
        }
    }
}
```

For these reasons, I converted the official *JDocs* to *TypeScript* type definitions and published an *NPM* package, [azure-cosmosdb-js-server-types](https://github.com/matteobortolazzo/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:

```json
{
  ...
  "compilerOptions": {
    ...
    "types": [ "azure-cosmosdb-js-server-types" ]
  }
}
```

Now we can use `getContext` or `__` without any import:

```typescript
﻿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
        })
    }
}
```

## 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:

    ```json
    {
        "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:

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

    ```json
    {
        "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.

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

   ```xml
   <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>
   ```

   > This code installs packages only if needed.

1. Finally, we add a step to compile the *TypeScript* files:

   ```xml
   <Target Name="NpmRunBuild" DependsOnTargets="NpmInstall" BeforeTargets="BeforeBuild">
        <Exec Command="npm run build -- --outDir $(OutDir)Scripts" />
    </Target>
   ```

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