// AppFileGateway — AppRunner のファイル I/O を抽象化する。 // // AppRunner は同じサンドボックス iframe + postMessage ブリッジを、認証版(スペース // API・書き込み可)と公開版(app-share トークン API・read-only)の両方で使い回す。 // その差分を吸収するのがこの gateway。 // // READ-ONLY の表現: // read-only は writeFile/deleteFile プロパティの「不在」で表す(実行時 false を返す // メソッドではない)。AppRunner は `gateway.writeFile` が undefined かどうかで書き込み // 経路の有無を判定する。公開版は両メソッドを持たず、ブリッジが read-only エラーを返す。 // サーバ側でも公開 API は GET のみなので、これは UI 側の二重防御に過ぎない。 import { fetchSpaceFileContent, fetchSpaceFiles, getSpaceFileRawUrl, writeSpaceFile, deleteSpaceFiles, fetchAppShareFileContent, fetchAppShareFiles, getAppShareRawUrl, type LocalFileEntry, } from '../../api'; export interface AppFileGateway { /** テキスト読み取り(workspace 相対パス)。 */ fetchContent(path: string): Promise; /** ディレクトリ一覧(workspace 相対パス)。 */ listFiles(dir: string): Promise<{ entries: LocalFileEntry[] }>; /** raw アセット URL(rewriteRelativeAssets / img src 用)。 */ rawUrl(path: string): string; /** 書き込み(read-only gateway では未定義)。 */ writeFile?(path: string, content: string): Promise<{ ok: boolean }>; /** 削除(read-only gateway では未定義)。 */ deleteFile?(path: string): Promise<{ ok: boolean }>; } /** 認証版(スペース API)。write/delete あり。 */ export function createSpaceGateway(spaceId: string): AppFileGateway { return { fetchContent: (path) => fetchSpaceFileContent(spaceId, path), listFiles: (dir) => fetchSpaceFiles(spaceId, dir), rawUrl: (path) => getSpaceFileRawUrl(spaceId, path), async writeFile(path, content) { await writeSpaceFile(spaceId, path, { content }); return { ok: true }; }, async deleteFile(path) { await deleteSpaceFiles(spaceId, [path]); return { ok: true }; }, }; } /** 公開版(app-share トークン API)。read-only=write/delete は未定義。 */ export function createPublicAppGateway(token: string): AppFileGateway { return { fetchContent: (path) => fetchAppShareFileContent(token, path), listFiles: (dir) => fetchAppShareFiles(token, dir), rawUrl: (path) => getAppShareRawUrl(token, path), // writeFile / deleteFile はあえて未定義(read-only)。 }; }