Azure Functions を TypeScript で書いてみる。

Mac で、Azure Functions をいじろうと思って、C# もかけるけど、まずは、node + Azure Functions CLI の組み合わせで、書いてみようと思った。簡単な、 CosmosDB に接続するサンプル。

# 噂の Callback hell

一番簡単そうな、CosmosDB の node からのチュートリアルをやってみた。

* [Build a Node.js web application using Azure Cosmos DB](https://docs.microsoft.com/en-us/azure/cosmos-db/documentdb-nodejs-application)

コードが見当たらなかったので、自分で書いたのを GitHub に上げておいた。

* [ToDo](https://github.com/TsuyoshiUshio/ToDo)

ちなみに、こんな感じのが３ファイルぐらい必要だった。単なる ToDo に、、、orz

_taskDao.js_

```js
var DocumentDBClient = require('documentdb').DocumentClient;
var docdbUtils = require('./docdbUtils');

function TaskDao(documentDBClient, databaseId, collectionId) {
    this.client = documentDBClient;
    this.databaseId = databaseId;
    this.collectionId = collectionId;

    this.database = null;
    this.collection = null;
}

TaskDao.prototype = {
    init: function (callback) {
        var self = this;

        docdbUtils.getOrCreateDatabase(self.client, self.databaseId, function(err, db) {
            if (err) {
                callback(err);
            } else {
                self.database = db;
                docdbUtils.getOrCreateCollection(self.client, self.database._self, self.collectionId, function (err, coll) {
                    if (err) {
                        callback(err);
                    } else {
                        self.collection = coll;
                    }
                });
            }
        });
    },
    find: function (querySpec, callback) {
        var self = this;
        self.client.queryDocuments(self.collection._self, querySpec).toArray(function (err, results) {
            if (err) {
                callback(err);
            } else {
                callback(null, results);
            }
        });
    },

    addItem: function (item, callback) {
        var self = this;

        item.date = Date.now();
        item.completed = false;

        self.client.createDocument(self.collection._self, item, function (err, results) {
            if (err) {
                callback(err);
            } else {
                callback(null, results);
            }
        });
    },

    updateItem: function (itemId, callback) {
        var self = this;

        self.getItem(itemId, function (err, doc) {
            if (err) {
                callback(err);
            } else {
                doc.completed = true;
                self.client.replaceDocument(doc._self, doc, function(err, replaced) {
                    if (err) {
                        callback(err);
                    } else {
                        callback(null, replaced);
                    }
                });
            }
        });
    },
    
    getItem: function (itemId, callback) {
        var self = this;

        var querySpec = {
            query: 'SELECT * FROM root r WHERE r.id = @id',
            parameters: [{
                name: '@id',
                value: itemId
            }]
        };

        self.client.queryDocuments(self.collection._self, querySpec).toArray(function (err, results) {
            if (err) {
                callback(err);
            } else {
                callback(null, results[0])
            }
        });
    }
};

module.exports = TaskDao;
```

私の感想は、、、これが、コールバックヘルと言うやつか、、、こんなしょーもないことするのに、こんなコードが要るとは、、、辛すぎる。だった。そうだ、TypeScript だったらどうだろう。楽じゃないかな？

ライブラリを調べてみるとあった！

* [jcormont/documentdb-typescript](https://github.com/jcormont/documentdb-typescript)

こ、これだ、これしかない！　これを使って、Functions を書いてみよう。

# Azure Functions を TypeScript 対応にしてみる

とりあえず、HttpTrigger を TypeScript に変えてみる。ちなみに、Azure Functions の TypeScript は、現在 Preview になって要るが、Azure Functions CLI の、2.0はまだ対応していない。自力だ。

## Function App を作成

```
$ func init
Writing .gitignore
Writing host.json
Writing local.settings.json
Created launch.json

Initialized empty Git repository in /Users/ushio/Codes/AzureFunctions/some/.git/
```
## Functions を作成

```
$ func new
Select a language: 
1. C#
2. JavaScript
Choose option: 2
JavaScript
Select a template: 
1. BlobTrigger
2. HttpTrigger
3. QueueTrigger
4. TimerTrigger
Choose option: 2
HttpTrigger
Function name: [HttpTriggerJS] 
Writing /Users/ushio/Codes/AzureFunctions/some/HttpTriggerJS/index.js
Writing /Users/ushio/Codes/AzureFunctions/some/HttpTriggerJS/sample.dat
Writing /Users/ushio/Codes/AzureFunctions/some/HttpTriggerJS/function.json
```

テンプレートが生成された

## TypeScript 環境を作る

```
cd HttpTriggerJS
npm init
tsc --init
cp index.js index.ts
```

`index.ts` を次のように変える

_index.ts_

```
export async function run (context: any, req: any) {
```

`typeconfig.json` を次のように変える。これをしないと、async メソッドは、promise を返さないとエラーになる。ところが、最初の１つ目のfunction では返しても仕方がないケースがあるので、新しいバージョンの TypeScript ではエラーにならなくなっている。

```json
{
  "compilerOptions": {
    /* Basic Options */                       
    "target": "ES2015",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd' or 'es2015'. */
    "lib": [ "es2015"],   
``` 

## can't find `process` error

もしかすると、こんなエラーに出会うかもしれない。

```
index.ts(7,34): error TS2304: Cannot find name 'process'.
index.ts(7,65): error TS2304: Cannot find name 'process'.
```

このケースは、これで解消する。そもそも `process` 見つからんとかおかしいよね。これはバグ。

```
npm install --save-dev @types/node
```

下記の議論をみてみると詳細が書いてありJます。

* [TypeScript: read arguments from command line - error TS2304: Cannot find name 'process'](https://stackoverflow.com/questions/35551185/typescript-read-arguments-from-command-line-error-ts2304-cannot-find-name-p)

下記のエラーは先ほどの`typeconfig.json` の設定で解消されます。

```
error TS2705: An async function or method in ES5/ES3 requires the 'Promise' constructor.  Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your `--lib` option.
```

# ついに Azure Functions 

ここまで設定すると、無事、CosmosDB にアクセスするファンクションが動きました。簡単！

```ts
import * as DB from "documentdb-typescript";


export async function run (context: any, req: any) {
    context.log('JavaScript HTTP trigger function processed a request.');

    const client = new DB.Client(process.env["COSMOS_DB_HOST"], process.env["COSMOS_DB_KEY"]);
    client.enableConsoleLog = true;

    await client.openAsync();
    console.log(await client.getAccountInfoAsync());
    var dbs = await client.listDatabasesAsync();

    if (req.query.name || (req.body && req.body.name)) {
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello! " + (req.query.name || req.body.name) + dbs.map(db => db.id)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
    context.done();
};
```

ちなみに、上記のは、`documentdb-typescript` のサンプルそのままだが、最初は、サンプルのままだと、エラーだった。Issue を書いて、解決したので、プルリクエストを書いておいた。

[TypeError: Cannot read property 'getDatabaseAccount' of undefined](https://github.com/jcormont/documentdb-typescript/issues/14)

次のコミットが参考になります。 [commit](https://github.com/TsuyoshiUshio/TypeScriptCosmosSpike/commit/7eaf996c4ce05c93df3ea9586e3836fed672bf5a).

# ToDo

残念ながら、TypeScript のローカルデバッグはできません。（生成されたjsはできると思うけど）。どうやったらできるか考えてみます。

# Resource

実は、このサンプルを GitHub においています。

* [TsuyoshiUshio/TypeScriptCosmosSpike](https://github.com/TsuyoshiUshio/TypeScriptCosmosSpike)