JavaScriptでAzure Table Storageを操作する

JavaScriptでAzure Table Storageを使用するには、2022年5月現在、[**@azure/data-tables**](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/tables/data-tables)というライブラリを使用します。（従来は、[**azure-storage**](https://github.com/Azure/azure-storage-node)というライブラリを使用していましたが、現在は非推奨となっています。）

# 利用可能な環境

[GitHub](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/tables/data-tables#prerequisites)には、

> Currently supported environments:
> * LTS versions of Node.js
> * Latest versions of Safari, Chrome, Edge and Firefox

と書かれていますので、サーバサイド（Node.js）、フロントサイド（ブラウザ）のどちらからでも利用できるようです。

# ライブラリのインストール

一般的なNodeライブラリと同様に書きコマンドでインストール可能です。

```bash:ライブラリのインストール
npm install @azure/data-tables
```

# ストレージに対する操作

テーブル自体の作成や削除・リスト操作は、ストレージクライアントを生成することで可能となります。

## ストレージクライアントの生成

以下のコードで、Table Storageのクライアントを生成できます。

```javascript:クライアントの生成
// パッケージのインポート
const { TableServiceClient } = require('@azure/data-tables');

// 認証情報インスタンスの生成
const credential = /* ～～～ この後ご紹介するいずれかの方法で生成 ～～～ */

// クライアントの生成
const serviceClient = new TableServiceClient('https://example.table.core.windows.net', credential);
```

`TableServiceClient()`コンストラクタの第1引数には、Table StorageのURLを指定します。通常は、`https://{ストレージアカウントの名前}.table.core.windows.net`です。第2引数には、認証情報が格納されたインスタンスを指定します。

認証情報インスタンスは、いくつか方法で生成することができます。

### アクセスキーで認証する場合

```javascript:アクセスキーで認証する場合
// パッケージのインポート
const { AzureNamedKeyCredential } = require('@azure/data-tables');

// 認証情報インスタンスの生成
const credential = new AzureNamedKeyCredential('example', 'ABcdeFGhiJkLmNoPQRSTuvwxYZ123==');
```
`AzureNamedKeyCredential()`コンストラクタで生成します。第1引数にはストレージアカウントの名前、第2引数にはアクセスキーを指定します。セキュリティの関係上、フロントサイドからはこの認証方法は利用できません。実際の場面では、下記のようにアクセスキーをコードにベタ書きするのではなく、環境変数等から読み込むようにしましょう。

### Azure ADで認証する場合

割愛

### Shared Access Signatureで認証する場合

```javascript:Shared Access Signatureで認証する場合
// パッケージのインポート
const { AzureSASCredential } = require('@azure/data-tables');

// 認証情報インスタンスの生成
const credential = new AzureSASCredential('?sv=2020-08-04&ss=x&srt=xxx&sp=xxxxxxx&se=2022-05-xxTxx:xx:xxZ&st=2022-05-xxTxx:xx:xxZ&spr=https&sig=abcdEfgHIjk');
```

`AzureSASCredential()`コンストラクタで生成します。引数にはSASトークンを指定します。

## テーブル一覧の取得

以降のコードは、ストレージクライアント(`TableServiceClient`)のインスタンスである`serviceClient`がグローバルですでに生成済みである前提となります。

```javascript:テーブル一覧の取得
// テーブル一覧をコンソール上に表示する非同期メソッド
const listTables = async () => {
  // テーブル一覧の非同期イテレータを取得
  const tables = serviceClient.listTables();
  // for文で1テーブルずつ名前を表示
  for await (const table of tables) {
    console.log(table.name);
  }
}

listTables();
```

ストレージクライアントの`listTables()`メソッドを呼び出すと、テーブル一覧の非同期イテレータを取得できます。非同期イテレータですので、[for await ... of](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for-await...of)文を使用することで、テーブルの情報を1つずつ取得することができます。

上記コードでは、1つの`table`に対して、`name`プロパティを参照することで、テーブル名を取得しています。

## テーブルの新規作成

```javascript:テーブルの新規作成
// テーブル新規作成する非同期メソッド
const createTable = async () => {
  // 'ExampleTable'という名前のテーブルを作成
  await serviceClient.createTable('ExampleTable');
}

createTable();
```

ストレージクライアントの`createTable()`メソッドを呼び出すと、テーブルを新規作成できます。第1引数には、テーブル名を指定します。

すでに同名のテーブルが存在する場合は新規作成されません。その際、例外はスローされません。

## テーブルの削除

```javascript:テーブルの削除
// テーブル削除する非同期メソッド
const deleteTable = async () => {
  // 'ExampleTable'という名前のテーブルを削除
  await serviceClient.deleteTable('ExampleTable');
}

deleteTable();
```

ストレージクライアントの`deleteTable()`メソッドを呼び出すと、テーブルを削除できます。第1引数には、テーブル名を指定します。

# テーブルに対する操作

ある特定のテーブルへのエンティティ追加・更新・削除などの操作は、テーブルクライアントを生成することで可能となります。

## テーブルクライアントの生成

以下のコードで、Tableのクライアントを生成できます。

```javascript:クライアントの生成
// パッケージのインポート
const { TableClient } = require('@azure/data-tables');

// 認証情報インスタンスの生成
const credential = /* ～～～ ストレージクライアントの生成の節をご覧ください ～～～ */

// クライアントの生成
const client = new TableClient('https://example.table.core.windows.net', 'ExampleTable', credential);
```

`TableClient()`コンストラクタの第1引数には、Table StorageのURLを指定します。第2引数には、テーブル名を指定します。第3引数には、認証情報が格納されたインスタンスを指定します。認証情報インスタンスは、[ストレージクライアントの生成](#ストレージクライアントの生成)時と同じ方法で生成可能ですので、そちらをご覧ください。

## エンティティ一覧の取得

以降のコードは、テーブルクライアント(`TableClient`)のインスタンスである`client`がグローバルですでに生成済みである前提となります。

```javascript:エンティティ一覧の取得
// エンティティ一覧をコンソール上に表示する非同期メソッド
const listEntities = async () => {
  // エンティティ一覧の非同期イテレータを取得
  const entities = client.listEntities();
  // for文で1エンティティずつ表示
  for await (const entity of entities) {
    console.log(`パーティションキー:${entity.partitionKey}, 行キー:${entity.rowKey}, タイムスタンプ:${entity.timestamp}, hoge列:${entity.hoge}`);
  }
}

listEntities();
```

テーブルクライアントの`listEntities()`メソッドを呼び出すと、エンティティ一覧の非同期イテレータを取得できます。非同期イテレータですので、[for await ... of](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for-await...of)文を使用することで、エンティティを1つずつ取得することができます。

エンティティインスタンスには、`partitionKey`(パーティションキー)・`rowKey`(行キー)・`timestamp`プロパティが存在します。また、独自定義した列(上記コードでは`hoge`)の値も、その名前のプロパティにアクセスすることで取得できます。

## エンティティの新規作成

```javascript:エンティティの新規作成
// エンティティ新規作成する非同期メソッド
const createEntity = async () => {
  await client.createEntity({
    partitionKey: 'ExamplePartition',
    rowKey: '001',
    hoge: 'なんとか',
  });
}

createEntity();
```

テーブルクライアントの`createEntity()`メソッドを呼び出すと、エンティティを新規作成できます。第1引数には、実際に追加するオブジェクトを指定します。オブジェクトには、少なくとも`partitionKey`(パーティションキー)・`rowKey`(行キー)プロパティを含めてください。

すでに同じパーティションキー・行キーのエンティティが存在する場合は例外がスローされます。

## エンティティの削除

```javascript:エンティティの削除
// エンティティ削除する非同期メソッド
const deleteEntity = async () => {
  await client.deleteEntity('ExamplePartition', '001');
}

deleteEntity();
```

テーブルクライアントの`deleteEntity()`メソッドを呼び出すと、エンティティを削除できます。第1引数にはパーティションキー、第2引数には行キーを指定します。

指定されたパーティションキー・行キーのエンティティが存在しない場合は、エラーがスローされます。

## エンティティの取得

```javascript:エンティティの取得
// エンティティを取得する非同期メソッド
const getEntity = async () => {
  await client.getEntity('ExamplePartition', '001');
}

const hoge = getEntity();
```
テーブルクライアントの`getEntity()`メソッドを呼び出すと、エンティティを取得できます。第1引数にはパーティションキー、第2引数には行キーを指定します。該当のエンティティが存在した場合は、当該エンティティのオブジェクトが以下のように返されます。

```
{
  "odata.metadata": "https://example.table.core.windows.net/$metadata#ExampleTable/@Element",
  "etag": "W/\"datetime'2022-05-03T13%3A30%3A32.1673908Z'\"",
  "partitionKey": "ExamplePartition",
  "rowKey": "001",
  "timestamp": "2022-05-03T13:30:32.1673908Z",
  "hoge": "なんとか"
}
```

指定されたパーティションキー・行キーのエンティティが存在しない場合は、エラーがスローされます。

## 条件を指定してエンティティ一覧を取得

```javascript:条件を指定してエンティティ一覧を取得
// エンティティ一覧をコンソール上に表示する非同期メソッド
const listEntities = async () => {
  // エンティティ一覧の非同期イテレータを取得
  const entities = client.listEntities({
    queryOptions: {
      filter: "hoge eq 'なんとか'"
    },
  });
  // for文で1エンティティずつ表示
  for await (const entity of entities) {
    console.log(`パーティションキー:${entity.partitionKey}, 行キー:${entity.rowKey}, タイムスタンプ:${entity.timestamp}, hoge列:${entity.hoge}`);
  }
}

listEntities();
```

テーブルクライアントの`listEntities()`メソッド呼び出し時に、第1引数にオプションオブジェクトを指定することができます。オプションオブジェクトに、`queryOptions`プロパティを用意することで、クエリ指定することができます。当該プロパティのオブジェクト内にさらに`filter`プロパティを指定することで、抽出するエンティティを絞ることができます（SQLでいうところのWHERE句）。当該プロパティには、ODataクエリ文字列を指定します。

# 参考

* [GitHub: Azure Tables client library for JavaScript](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/tables/data-tables)
* [API Reference: @azure/data-tables package](https://docs.microsoft.com/ja-jp/javascript/api/@azure/data-tables/?view=azure-node-latest)