Subscribe to receive notifications of new posts:

Cloudflare Workersの型を自動生成

2021-11-16

4 min read

これまでは、RustおよびTypeScriptのタイプリポジトリを最新に保つことは困難でした。それらは手動で生成されたため、その結果不正確または期限切れになるリスクがありました。最近まで、workers-typesのリポジトリは、タイプが変更されるたびに手動で更新する必要がありました。また、ほとんど完全なブラウザAPIにタイプ情報を追加する際にも使用していました。このため、ユーザーがWorkersのランタイムでサポートされていないブラウザAPIを使用しようとすると、コンパイルしてもエラーが発生し、混乱が生じていました。

この夏、Brendan Coll氏が当社のインターンとして働いていた間に、それらを生成するための自動化パイプラインを作成したときに、すべてが変わりました。Workers ランタイムを構築するたびにそれが実行され、TypeScriptリポジトリおよび Rustリポジトリのタイプが生成されます。いまでは、すべてが最新かつ正確になります。

簡単な概要

Workersランタイムコードが作成されるたびに、パブリックAPI上でスクリプトが実行され、Rustの型とTypeScriptの型、静的型の中間表現を含むJSONファイルと共に生成されます。これらの型は適切なリポジトリに送信され、ユーザーが独自の型パッケージを作成する場合のためにJSONファイルもアップロードされます。それについては後ほど説明します。

これは、スタティックタイプが常に正確かつ最新であることを意味しています。また、プロジェクトが他の静的型付け言語のWorkersを実行して、私たちの中間表現から彼ら独自のタイプを生成することができます。こちらはCloudflareボットからのPR例です。ランタイム型の変更が検出され、中間表現と共にTypeScriptファイルが更新されます。

自動生成された型の使用

最初に、以下のように「wrangler」を使用して新しいTypeScriptプロジェクトを生成します。

$ wrangler generate my-typescript-worker https://github.com/cloudflare/worker-typescript-template

既にTypeScriptプロジェクトがある場合は、以下を使用して最新バージョンのworkers-typesをインストールします。

$ npm install --save-dev @cloudflare/workers-types

次に、 @cloudflare/workers-types をプロジェクトのtsconfig.jsonファイルに追加します。

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "lib": ["ES2020"],
    "types": ["@cloudflare/workers-types"]
  }
}

その後、ユーザーが選択したIDEで型が自動作成されます。

仕組み

以下は、Workersランタイムのコードベースからのサンプルコードです。

class Blob: public js::Object {
public:
  typedef kj::Array<kj::OneOf<kj::Array<const byte>, kj::String, js::Ref<Blob>>> Bits;
  struct Options {
    js::Optional<kj::String> type;
    JS_STRUCT(type);
  };

  static js::Ref<Blob> constructor(js::Optional<Bits> bits, js::Optional<Options> options);
  
  int getSize();
  js::Ref<Blob> slice(js::Optional<int> start, js::Optional<int> end);

  JS_RESOURCE_TYPE(Blob) {
    JS_READONLY_PROPERTY(size, getSize);
    JS_METHOD(slice);
  }
};

Pythonスクリプトはビルドごとにこのコード上で実行され、識別子、あらゆる引数タイプ、あらゆる戻り値のタイプを含む関数に関する情報が記された抽象構文ツリーが生成されます。

{
  "name": "Blob",
  "kind": "class",
  "members": [
    {
      "name": "size",
      "type": {
        "name": "integer"
      },
      "readonly": true
    },
    {
      "name": "slice",
      "type": {
        "params": [
          {
            "name": "start",
            "type": {
              "name": "integer",
              "optional": true
            }
          },
          {
            "name": "end",
            "type": {
              "name": "integer",
              "optional": true
            }
          }
        ],
        "returns": {
          "name": "Blob"
        }
      }
    }
  ]
}

最後に、TypeScript型のリポジトリに、更新された型のPRが自動的に送信されます。

declare type BlobBits = (ArrayBuffer | string | Blob)[];

interface BlobOptions {
  type?: string;
}

declare class Blob {
  constructor(bits?: BlobBits, options?: BlobOptions);
  readonly size: number;
  slice(start?: number, end?: number, type?: string): Blob;
}

オーバーライド

一部のケースで、TypeScriptはC++ランタイムでサポートされない概念をサポートします。つまり、ジェネリックおよび関数のオーバーロードです。これらの場合、生成された型をpartial宣言でオーバーライドします。例えば、DurableObjectStorageではgetterおよびsetter関数のためにジェネリックが多用されます。

declare abstract class DurableObjectStorage {
	 get<T = unknown>(key: string, options?: DurableObjectStorageOperationsGetOptions): Promise<T | undefined>;
	 get<T = unknown>(keys: string[], options?: DurableObjectStorageOperationsGetOptions): Promise<Map<string, T>>;
	 
	 list<T = unknown>(options?: DurableObjectStorageOperationsListOptions): Promise<Map<string, T>>;
	 
	 put<T>(key: string, value: T, options?: DurableObjectStorageOperationsPutOptions): Promise<void>;
	 put<T>(entries: Record<string, T>, options?: DurableObjectStorageOperationsPutOptions): Promise<void>;
	 
	 delete(key: string, options?: DurableObjectStorageOperationsPutOptions): Promise<boolean>;
	 delete(keys: string[], options?: DurableObjectStorageOperationsPutOptions): Promise<number>;
	 
	 transaction<T>(closure: (txn: DurableObjectTransaction) => Promise<T>): Promise<T>;
	}

また、 Markdownを使用して型のオーバーライドを記述することもできます。こちらはKVNamespaceの型をオーバーライドする例です。

独自のタイプの作成

JSON IR (中間表現)はTypeScriptの型と共にオープンソース化され、このGiHubリポジトリ内に置かれています。また、IRの書式を示す、タイプスキーマ自体もオープンソース化しました。自分の言語のWorkersタイプを作成する場合は、「正規化された」データ構造で宣言を記述するIRを取得し、そこから型を生成することができます。

`workers.json`内の宣言には、関数シグネチャーを導出するための要素と、識別子、引数型、戻り値型、エラー管理などの、コード生成に必要な他の要素が含まれています。具体的なユースケースとしては、WebAssemblyをコンパイルする言語のための外部関数宣言を生成して、Workersランタイムから利用可能な一連の関数呼び出しを正確にインポートすることです。

まとめ

CloudflareはTypeScriptおよびRustのエコシステムのサポートに大きな関心を持っています。Brendan氏は両方の言語の型情報を常に最新かつ正確に保つためのツールを作成しました。また、JSONフォーマットの型情報自体をオープンソース化しているため、関心のある方はどなたでもお好みの任意の言語で型データを作成することができます!

We protect entire corporate networks, help customers build Internet-scale applications efficiently, accelerate any website or Internet application, ward off DDoS attacks, keep hackers at bay, and can help you on your journey to Zero Trust.

Visit 1.1.1.1 from any device to get started with our free app that makes your Internet faster and safer.

To learn more about our mission to help build a better Internet, start here. If you're looking for a new career direction, check out our open positions.
Cloudflare Workers (JP)Serverless (JP)Product News (JP)日本語Full Stack Week (JP)

Follow on X

Brendan Coll|@_mrbbot
Jonathan Kuperman|@jkup
Cloudflare|@cloudflare

Related posts

April 05, 2024 1:01 PM

Browser Rendering APIのGA化、そしてCloudflare Snippets、SWRの展開、Workers for Platformsの全ユーザーへの提供

Browser Rendering APIをセッション管理の改善と共にすべての有料Workers顧客に提供開始...

April 04, 2024 1:05 PM

プロダクションの安全のための新たなツール — Gradual Deployments、ソースマップ、Rate Limiting、そして新たなSDK

本日、私たちは利用者がより多くの力を手にするための、段階的デプロイメント、Tail Workersのソースマップスタックトレース、新しいレート制限API、刷新されたAPI SDK、Durable Objectsの5つのアップデートを発表しました。それぞれがミッションクリティカルな本番サービスを念頭に置いて構築されています...

April 02, 2024 1:01 PM

Workers AIのレベルアップ:一般提供とさらなる新機能

本日、私たちは、Workers AI、Cloudflareの推論プラットフォームの一般公開、LoRAを使用した細かく調整されたモデルのサポート、HuggingFaceからのワンクリックデプロイなど、一連の発表を行うことを嬉しく思います。Cloudflare WorkersがPythonプログラミング言語などをサポートするようになりました...