あれとアレは混ぜるな危険

日々精進をしているふり

TypeScript で幸せになる Mobile Services

よいしょ~っ、はるたまです。年末は忘年会が多いので、そろそろ爆睡したいです。2012年のWindows Azure Advent Calendar のネタということで、ちょっと書いてみましょうか。

はてさて、最近Windows Azureに入ってきた新サービス「Mobile Services」。iOSとWindows 8とWindows Phone(と、そのうちAndroidも)のプッシュ通知を一撃で解決してくれたり、MicrosoftアカウントとFacebookTwitterGoogleアカウントの認証をなんとなく一網打尽にしてくれたり。今時の言葉にすると、モバイル端末向けのBaaS(Backend As A Service)として使えちゃいます。

この中で、SQL Databaseに対しての操作をREST経由ででできるような機能というのも入っています。こんな感じの画面でWebからスクリプトを書いて、クライアントからリクエストを受け取った時に、実際に何をどうするのか書いていくわけです。

これを何で書いていくかというと、実体はJavaScriptです。これがちょっと厄介で、この画面で正直にJavaScriptを書こうとすると、ちょっとTypoしても実際に動かすまでわからないということが起こったりするわけです。インテリセンスも効かないし!

じゃあどうしましょうか?ということで、ここに書いてあるオブジェクトの定義で、TypeScriptの定義ファイルでも書いてみようとなるわけです。
TypeScript自体の説明はここを見ていただければ。
http://harutama.hatenablog.com/entry/2012/12/04/230827

で、できあがったっぽいのがこんな感じ。

//出来上がったものはmobileservice.d.tsみたいな名前で保存しておきましょう

declare var console: {
    log(formatString: string, obj?: object[]);
    info(formatString: string, obj?: object[]);
    warn(formatString: string, obj: object[]);
    error(formatString: string, obj: object[]);
}

declare class Options {
    success: (results: any) =>undefined;
    error: (err: any) =>undefined;
}

declare interface User {
    getIdentities(): any;
    accessTokens: any;
    level: string;
    userId: string;
}

declare interface Request {
    execute():undefined;
    execute(options:Options):undefined;
    respond():undefined;
    respond(err:Object):undefined;
    respond(StatusCode: StatusCodes, body: Object): undefined;
}

declare interface Query{
    orderBy(arg: string[]): Query;
    orderByDescending(arg: string[]): Query;
    read(options: Options): undefined;
    select(col: string[]): Query;
    select(func: () =>Object): Query;
    skip(recordCount: number): Query;
    take(recordCount:number): Query;
    where(object: Object): Query;
    where(func: (currentUserId: Object) =>bool): Query;
}

//一応enumは使えるけどexperimentalっぽい
declare enum statusCodes {
    OK = 200,
    CREATED = 201,
    ACCEPTED = 202,
    NO_CONTENT = 203,
    BAD_REQUEST = 400,
    UNAUTHORIZED = 401,
    FORBIDDEN = 403,
    NOT_FOUND = 404,
    CONFLICT = 409,
    INTERNAL_SERVER_ERROR = 500
}

declare interface Table {
    delete (itemOrId: Object, options: Options): undefined;
    insert(item: Object, options: Options): undefined;
    orderBy(arg: string[]): Query;
    orderByDescending(arg: string[]): Query;
    read(options: Options): undefined;
    select(query: string): Query;
    select(func: () =>undefined): Query;//todo
    skip(recordCount: number): Query;
    take(recordCount: number): Query;
    where(obj: Object): Query;
    where(func: () =>undefined): Query;//todo
    update(item: Object, options: Options): undefined;
}

declare var mssql: {
    open(options: Options);
    query(sql: string, options: Options);
    query(sql: string, params: Object[], options: Options);
    queryRaw(sql: string, options: Options);
    queryRaw(sql: string, params: Object[], options: Options);
}

declare var push: {
    apns: {
        send(deviceToken: any, payload: Object, options: Options): undefined;
        getFeedback(completion: Options): undefined;
    };
    mpns: { };
    wns: { };
}

declare var tables: {
    getTable(tableName:string): Table;
}

こんなものを作っておけば、Mobile Services側のJavaScriptをTypescriptで書くときに、VisualStudioのインテリセンスが助けてくれるようになります。例えばここにあるような

function insert(item, user, request) {
    if (!item.approved) {
        handleUnapprovedItem(item, request);
    } else {
        request.execute();
    }
}

function handleUnapprovedItem(item, request) {
    // Implementation 
}

こんなJavaScriptをTypeScript的に、こんな感じで書き換えてあげると

/// <reference path="mobileservice.d.ts"/>

function insert(item: any, user: User, request: Request): undefined {
    if (!item.approved) {
        handleUnapprovedItem(item, request);
    } else {
        request.execute();
    }
}

function handleUnapprovedItem(item, request) {
    // Implementation 
}

インテリセンスの効いたVisualStudioでサクサク開発できるようになります。このTypeScriptをJavaScriptにコンパイルすると

/// <reference path="mobileservice.d.ts"/>
function insert(item, user, request) {
    if(!item.approved) {
        handleUnapprovedItem(item, request);
    } else {
        request.execute();
    }
}
function handleUnapprovedItem(item, request) {
    // Implementation
    }

こんな風に元のJavaScriptと同じようなものが出来上がってきます。

こんな感じで、定義ファイルをいっぱい用意すればハッピーな感じになりますので、みんなTypeScriptを使えばいいと思うよ。