インストール
依存関係はありません。fetch、WebSocket、node:crypto を使い、Node 23.6 以降なら TypeScript のソースをそのまま実行できます。npm のパッケージ名は glac で、TypeScript を実行しないランタイムのために JavaScript と型宣言も含みます。
npm install glac最初の数行
import { Glac, Sandbox } from "glac";
const glac = new Glac(); // GLAC_API_KEY and GLAC_API_URL; or new Glac({ apiKey, baseUrl })
const app = await glac.cube.deploy("ghcr.io/acme/api:1.4", { name: "api", port: 8080 }); // Cube: waits until it serves
console.log(app.url);
const sbx = await glac.sandboxes.create({ image: "python:3.12" }); // Sandbox
console.log((await sbx.run("python3 -c 'print(6*7)'")).stdout); // res.stdout, res.stderr, res.exitCode
const drive = await glac.drives.create({ name: "data", sizeBytes: 10 * 2 ** 30 }); // Drive: disks that outlive sandboxes
console.log((await Sandbox.run("echo hello")).stdout); // the shorthand: a sandbox for one command, on the default clientクライアント
new Glac({ apiKey, baseUrl, retries }) は、1 つのアドレスに対する 1 つのキーです。指定しないものは GLAC_API_KEY と GLAC_API_URL から読みます。製品ごとに名前空間があります。
| 名前空間 | 説明 | 呼び出し |
|---|---|---|
glac.cube |
キューブ:それぞれ固有のアドレスでサービスとして動くイメージ(API ではアプリ、/v1/apps)。glac.workers は別名で、非推奨 |
deploy(image, options), create(options), get(idOrName), getOrCreate(name, options), list() |
glac.sandboxes |
サンドボックス(/v1/sandboxes) |
run(command, options), create(options), get(idOrName), getOrCreate(name, options), list({ status, metadata }) |
glac.sandboxes.snapshots |
サンドボックスのスナップショット | get(idOrName), list({ sandboxId }) |
glac.sandboxes.sshKeys |
サンドボックスとアプリのインスタンスに入るための SSH キー | create(publicKey, { name, sandboxId, expiresAt }), get(idOrName), list({ sandboxId }) |
glac.drives |
ドライブ:サンドボックスより長く残るプロジェクトのディスクで、一度に 1 つのサンドボックスが読み書き可能でマウントする。そのスナップショットは、いくつでも読み取り専用でマウントできる(/v1/drives、コントロールプレーンの機能) |
create({ name, sizeBytes, region, durability }), get(idOrName), list(), update(drive, { name }), delete(drive), attach(sandbox, drive, path, { mode }), detach(sandbox, drive, { force }), snapshot(drive, { name, fsConsistent }), snapshots(drive), getSnapshot(drive, snapshot), deleteSnapshot(drive, snapshot), fork(drive, { name, fromSnapshot }), restore(drive, snapshot), attachSnapshot(sandbox, snapshot, path, { drive }), detachSnapshot(sandbox, snapshot, { force }) |
glac.volumes |
ボリューム:ドライブの下にあるディスク(/v1/volumes)。移行のために残されている:単独で動くノードと共有ボリューム |
create({ name, sizeBytes, shared, fromBackup }), get(idOrName), list(), backup(volume, { expiresInDays }), backups(volume) |
glac.backups |
ボリュームのバックアップ:ある瞬間のボリューム全体のコピーで、オブジェクトストアにあり、ボリュームより長く残る(/v1/backups、コントロールプレーンの機能) |
list({ volume }), get(backup), delete(backup), restore(backup, { name }) |
glac.secrets |
プロジェクトのシークレット | put(name, value, { allowedHosts }), update(name, allowedHosts), list(), delete(name) |
glac.variables |
プロジェクトの共有変数 | put(name, value), list(), delete(name) |
glac.deploys |
プロジェクトのデプロイ(/v1/deploys、コントロールプレーンの機能):複数のサービスを、それぞれが待つものの順にまとめてデプロイし、まとめて切り替える(プロジェクトのデプロイ) |
create(services, { wait, timeoutSeconds })、get(id)、list():create は各サービスのバージョンのフィールドを API の名前で受け取り({} ならそのまま)、デプロイを待ち(置き換わったデプロイがあればそれを追う)、失敗すると deploy、service、デプロイの code を持つ DeployError をスローする |
glac.domains |
プロジェクトの独自ドメイン(/v1/domains、コントロールプレーンの機能):アプリやサンドボックスのポートのための名前 |
create(domain, { app } or { sandbox, port }), get(idOrName), list({ app, sandbox }), delete(idOrName) |
glac.webhooks, glac.keys |
プロジェクトのイベントの送信先(/v1/webhooks)と API キー(/v1/keys)。コントロールプレーンの機能 |
create、get、list、update(Webhook のみ)、delete |
glac.destinations |
プロジェクトのアプリのログ、メトリクス、トレースのエクスポート先(/v1/destinations)。コントロールプレーンの機能 |
create, get, list, update, delete, test |
プラットフォーム自体の呼び出しはクライアントのメソッドです:glac.account()、updateAccount()、usage()、billing()、updateBilling()、balance()、updateBalance()、topUp(usd)、charges()、ledger()、project()、events()。SDK がラップしていない API は glac.json("GET", "/v1/webhooks") で、一覧なら glac.pages(path) で呼びます。呼び出しが返すもの(Sandbox、App、Drive、Volume)はクライアントを保持し、自身のメソッドでそれを使います。
短縮形:Sandbox.run、Sandbox.create、Sandbox.get、Sandbox.getOrCreate、Sandbox.list は、デフォルトのクライアントでの glac.sandboxes の呼び出しで、クライアントは最初に GLAC_API_KEY と GLAC_API_URL から作られます。{ client } で別のクライアントを指定できます。短縮形を持つ製品はほかにありません。
undefined のオプションは送られません。一部のオプションでは null に意味があります。sleepAfterSeconds: null はスリープしない、更新での sleepNoticeMs: null はスリープ前にプログラムへの通知をもう行わない、env: { KEY: null } はキーを削除する、egress: null はすべてのネットワークルールを削除する、という意味です。
キューブ
キューブ(API ではアプリ)は、固有のアドレスでサービスとして動くイメージで、インスタンスはリクエストに必要なだけ動き、リクエストがなければ 1 つもありません。SDK の App がそれです。
import { DeployError, Glac } from "glac";
const glac = new Glac();
const app = await glac.cube.deploy("ghcr.io/acme/api:1.4", { name: "api", port: 8080 });
try {
await app.deploy({ image: "ghcr.io/acme/api:1.5" }); // a new version, waited for
} catch (e) {
if (!(e instanceof DeployError)) throw e; // it did not come up: the traffic stays where it was
}
for await (const line of app.logs({ follow: true })) if (line.kind === "output") console.log(line.text);| 呼び出し | 内容 |
|---|---|
glac.cube.deploy(image, { name, ...every option of create, wait, timeoutSeconds }) |
glac deploy と同じ:name のアプリに対しては、指定したアプリ自体の設定を先に変更し(PATCH)、残りからバージョンを作る。どのアプリも持たない名前や、名前を指定しない場合は、最初のバージョンを持つ新しいアプリを作る。wait: false でなければバージョンを待ち、失敗すると DeployError をスローし、アプリを返す |
glac.cube.create(...), get(idOrName), getOrCreate(name, options), list() |
アプリを作成する(image を指定すると、最初のバージョンを待つ)。1 つを取得する。何もデプロイせずに取得または作成する。一覧。drives は、唯一のインスタンスが読み書き可能でマウントするドライブ(多数のインスタンスやキーを持つアプリは 409 drive_single_writer で拒否される)。keyDrives は、キーを使うアプリのキーごとのインスタンスに専用のドライブを持たせる。ドライブは最初のインスタンスとともに作られ、キーとともに削除される |
app.deploy(...), app.rollback(version), app.update(changes), app.versions(), app.delete() |
指定したもののバージョン。トラフィックを別のバージョンに戻す。変更(バージョンを構成するものが変わる場合はバージョン)。バージョンの一覧。削除 |
app.managedBy |
アプリを単独で変更するもの(ManagedBy)。アプリが属するデータベースなら { object: "database", id: "db-…" }:ここでの更新、デプロイ、ロールバック、削除は 409 managed_by_database で拒否される(ログの読み取りと削除はほかのアプリと同じ)。サンドボックス製品なら { object: "sandboxes", id: null }:プロジェクトのデフォルトのサンドボックスアプリ sbx-sandbox、またはサンドボックスの作成元のイメージのアプリで、そのインスタンスはプロジェクトのサンドボックス。デフォルトのアプリのイメージの指定以外の変更はすべて 409 managed_by_sandboxes で拒否され、削除はできる。どれにも属さないアプリでは null |
glac.cube.create、deploy、app.deploy、app.update の runtime |
バージョンのランタイム設定(ServiceRuntime):ulimits({ nofile: 65535 })、sysctls、tmpfs、dns、shm_size_bytes。全体が置き換わる(更新で null ならなし) |
app.logs({ follow, since, ... }), app.logUsage(), app.logsState, app.instance(key) |
ログ(for await。接続が切れても自動で戻る)、ログが数えた量、ログを保持する各マシンが、今そこで出力されるものを長期保存しているかどうか(ログ)、キー別インスタンス |
instance.upgrade({ version, when, keepDays }), app.upgradeInstance(key, { version, when, keepDays }); await upgrade.wait({ timeoutSeconds }); instance.upgrades(), app.instanceUpgrades(key), app.instanceUpgrade(key, id) |
キーのインスタンスを、アプリの別のバージョンへその場で移す。version で指定しなければアプリの現在のバージョン(ID、番号、または Version。準備ができたもので、古いものも可)。ID、ノード、キー、アドレス、トークン、専用のドライブとその中身はそのままで、ディスク、マシン、プロセスが新しいバージョンのものになり、開いていた接続は終わり、切り替えの間、リクエストはエッジで保留される。Upgrade に解決される(status は preparing、switching、done、failed。fromVersion、toVersion、timings、error、putBack。kind(upgrade または revert)、when、keepDays、rollback(リバートに必要なもの。keepDays の間保持される)、reverts、batch。API でインスタンスのスリープを待つよう求めたアップグレードは、スリープするまで waiting で、取り消された場合(API の DELETE、またはバッチの停止)は cancelled。取り消されたアップグレードは終了しており、その待機は失敗したものと同じく、コード cancelled で例外になる)。インスタンスがすでにそのバージョンなら null。wait() は、新しいバージョンが立ち上がらなかった場合に UpgradeError(コードは upgrade_failed か upgrade_not_here)をスローする。そのときインスタンスは元のバージョンのまま。instance.refresh() は、versionId も含めてインスタンスを読み直す。when: "asleep" は、インスタンスがスリープするまで待ち(waiting)、スリープした時点で始めるため、リクエストは 1 つも保留されない。keepDays(0〜365 日、指定しなければ 7 日)は、完了後にリバートに必要なものを保持する日数で、スナップショットと同じく課金される。インスタンスとは別のドライブやボリュームをサービスがマウントするバージョンは、その場でのアップグレードにはならず、upgrade_not_here で failed になる |
upgrade.cancel(), app.cancelInstanceUpgrade(key, upgrade); upgrade.revert({ keepDays }), app.revertInstanceUpgrade(key, upgrade, { keepDays }) |
待機中(インスタンスのスリープ待ち、またはバッチ内の順番待ち)のアップグレードを取り消す:cancelled になり、インスタンスは元のバージョンのまま(始まったものは 409 upgrade_started)。リバート:アップグレードが保持していたもの(ディスク、アップグレード前の状態のドライブ(先に現在の中身のスナップショットを作る)、移行元のバージョン)にインスタンスを戻す。kind が revert で、reverts が元のアップグレードを示す新しい Upgrade。アップグレードと同じように進み、待てる。戻り先がもう保持されていなければ 409 rollback_gone で拒否される |
app.upgradeInstances({ version, fromVersions, keys, when, keepDays, concurrency, maxFailures }); app.upgradeBatches(), app.upgradeBatch(id), app.stopUpgradeBatch(id); batch.wait({ timeoutSeconds }), batch.refresh(), batch.stop() |
アプリの複数のキーを 1 つのバージョン(指定しなければアプリの現在のバージョン)へまとめてアップグレードするバッチ。指定すれば fromVersions のもの、keys に含まれるものが対象。それぞれインスタンスがスリープした時点で(when: "asleep"。デフォルト)、または順番が来た時点で("now")、同時に concurrency 個ずつ(4。1 つのノードでは 2)行い、maxFailures(5)回失敗した時点で failed として止まる。UpgradeBatch に解決される:status(running、done、failed、stopped)、stopReason、counts、upgrades(各メンバーのキー、アップグレード、ステータス、エラー)、skipped、origin(api、またはアプリの keys が upgrade_on_deploy を指定している場合の deploy)。wait() はどう終わっても解決し、例外はスローしない。スリープしないインスタンスのメンバーがあるとバッチは running のままなので、timeoutSeconds で wait_timeout のエラーがスローされる。stop() は待機中のメンバーを取り消す |
app.openInstancePort(key, port, { from, protocol }), app.instancePorts(key), app.closeInstancePort(key, port) |
インスタンスがどこにあっても、キーのインスタンスが持つ専用のパブリック IPv6 アドレスで開いているポート:そのサンドボックスの openPort、ports、closePort を、アプリとキーで指定(キー別インスタンス)。専用のアドレスのないインスタンスは拒否される(409 no_ipv6) |
glac.cube.create、deploy、app.update の checkpoint。app.checkpoint。sbx.checkpoint |
アプリのキー別インスタンスの稼働中のチェックポイント(AppCheckpoint、{ everySeconds }:1〜600 秒。省略するか null なら 10 秒。app.update({ checkpoint: null }) で解除)と、インスタンスのチェックポイントの状況(SandboxCheckpoint:everySeconds、lastAt、behindSeconds、bytesPerSecond、storedBytes、error。なければ null) |
app.metrics({ start, end, step }) |
アプリを処理するマシンが計測した値。1 分ごと(step: "hour" なら 1 時間ごと):ステータスのクラス別のリクエスト、所要時間、インスタンスとその使用量(メトリクス)。API のオブジェクトを返す |
glac.cube.create、deploy、getOrCreate、app.update の traces。app.traces |
アプリのリクエストの一定割合について作られるプラットフォームのスパンで、インスタンス自身のスパンはその子になる(AppTraceSettings:{ sampleRate: 0.1, trustIncoming: false }。指定しなければなし)。"traces" を受け付けるプロジェクトのエクスポート先に送られる(トレース)。アプリの設定 |
app.createKeySecret(), app.keySecrets(), app.keySecret(id), app.deleteKeySecret(id), signKey(secret, appId, key, { expiresAt, path }) |
アプリのキーの署名に使うシークレット(KeySecret。secret を持つのは作成直後のものだけ)と、それで署名した、エッジを通るリクエストが運ぶ形のキー(署名付きキー)。path: true で、パスのセグメントとして書く |
サンドボックス
| 呼び出し | 内容 |
|---|---|
glac.sandboxes.create({ image, snapshot, name, env, metadata, volumes, drives, publicPorts, sleepAfterSeconds, sleepNoticeMs, sleepHoldMaxMs, expiresAt, egress, imagePullSecret, start, startCommand, ready, projectNetwork, hostname, stop, checkpoint }) |
サンドボックスを作成する。どのオプションも省略可能。drives: [{ path, drive }] でドライブを、[{ path, drive, snapshot }] でドライブのスナップショットを読み取り専用でマウントする(後述)。起動時に始めるサービス:start: true(イメージ自身の起動コマンド)か startCommand を、ready: { tcp: true, port: 5432 }(または { http: "/health", port: 8080 }、{ command: "…" })とともに指定。hostname でマシンに名前を付け、stop: { signal, graceMs } でサービスの止め方を指定する。projectNetwork: true で最初からプロジェクトのプライベートネットワークの sbx.projectAddress につなぐ。ボリュームの copy: true は、イメージのそのパスにあるものを新しいボリュームに入れる(サンドボックスの作成) |
sbx.appId, sbx.versionId, sbx.key, sbx.imageDigest |
コントロールプレーンで作ったサンドボックスはアプリのインスタンスである。イメージを指定せずに作った場合はプロジェクトのデフォルトのサンドボックスアプリ(sbx-sandbox)、それ以外はそのイメージのアプリ((await glac.apps.get(sbx.appId)).instances() は、そのアプリのサンドボックスをそれぞれのキーの下に一覧表示する)。サンドボックスが属するそのアプリのバージョン、作成元のイメージ(その後移動したタグはアプリの新しいバージョンになり、それ以降に作ったサンドボックスはそちらに属する)、そこでのキー、名前または ID、ダイジェストで示すイメージ。作成のレスポンスとすべての読み取りで示され、保持される。テンプレートやスナップショットから作ったものと、フォークでは null |
sbx.startCommand, instance.startCommand |
ゲストが最後に報告した、起動コマンドの実行状況(StartCommandState):status(running、restarting_at に再起動した restarting、exited)、restarts(キーのインスタンスとステートフルなサービスのインスタンスは、コマンドが自ら終わるとマシン内で再起動され、起動のたびにイベント instance.restarted が発生する)、last_exit(status、signal、at)。ノードが何も報告していなければ null |
create と sbx.update の suspendAfterMs、wakeDailyAt。sbx.suspendAfterMs、sbx.wakeDailyAt |
アイドルで停止した後、メモリを保存して本当にスリープするまで一時停止したままでいる時間(1 秒〜1 日。ノードは最大 10 分に抑える)と、休止中に、復帰を待つ自身の処理のために毎日復帰させる時刻(UTC、"HH:MM")。更新で null を指定すると、それぞれ元に戻る |
create と sbx.update の onExpiry、heldAsleep。sbx.onExpiry、sbx.heldAsleep |
expiresAt の時刻が来たときの動作:サンドボックスを "delete"(デフォルト)、"sleep"(スリープさせて保持し、リクエストではいつもどおり復帰)、"hold"(同じく、スリープのまま保持)。そしてスリープ中にポートへのリクエストで復帰させるかどうか。スリープのまま保持されている間、プレビューアドレスは 502 sandbox_paused を返し、API の呼び出しでは復帰する。更新で null を指定すると、それぞれ元に戻る |
create と run の init: "systemd"。sbx.init |
システム全体:イメージ自身の systemd がサンドボックスの PID 1 になる(image を指定しなければ、systemd と OpenSSH 入りのプラットフォームの Ubuntu)。パッケージ、systemctl で起動するサービス、cron、ジャーナルが通常のマシンと同じように動き、専用のマシン ID を持ち、スリープ、復帰、移動を経ても保たれる。start、startCommand、ready とは併用できず(400 invalid_start)、snapshot とも併用できない。sbx.init は、systemd なら "systemd"、プラットフォームの init なら null |
glac.sandboxes.run(command, options) |
1 つのコマンドのためのサンドボックス:作成、利用、削除 |
glac.sandboxes.get(idOrName), getOrCreate(name, options), list({ status, metadata }) |
1 つを取得。取得または作成。一覧 |
sbx.run(command, { cwd, env, user, timeoutSeconds, stdin, onStdout, onStderr, signal }) |
実行して待つ。Result を返す |
sbx.exec(command, { stdinOpen, tty }) |
開始して Command を返す:wait()、output(from)、write()、kill() |
sbx.terminal({ cols, rows, join, onData }) |
WebSocket によるターミナル:write、resize、close |
sbx.files |
read、readText、write、list、stat、exists、mkdir、move、remove、そして setAttributes(path, { mode, sizeBytes, modifiedAt }):ファイルのモード、長さ、最終更新時刻 |
sbx.files.upload(localPath, path), download(path, localPath), readStream(path, { offset, length }), write(path, blobOrStream) |
どんな長さのファイルも 1 回の呼び出しで扱い、こちらでも API でも、ファイル全体をどこかに保持することはない。upload はローカルファイルを読みながら送り、download は届くそばからバイトをローカルファイルに書き込み(すべてのバイトが届いた時点で所定の場所に置く)、readStream はバイトの ReadableStream を返す。write は、Blob、ReadableStream、バイトの非同期イテラブルも受け付ける。レスポンスが示した長さに満たないまま終わったストリームは、終わりでエラーになるので、読み直す |
sbx.url(port), sbx.rotateUrls(), sbx.token({ scopes, expiresInSeconds }) |
ポートへのリンク(ローカルで署名)。リンクの新しいシークレット。このサンドボックス用のトークン |
sbx.ipv6、sbx.openPort(port, { from, protocol })、sbx.ports("tcp")、sbx.closePort(port, { protocol })、create と update の inbound: […] |
専用のパブリック IPv6 アドレス(ある場合。なければ null)。指定した送信元(IPv6 のプレフィックスかアドレス。全員なら "::/0")に対して開いたポートと、その接続先。開いているポートの一覧。ポートを閉じる(開いていたかどうかを返す)。一覧全体。受信はデフォルトで閉じており、開いたものはスリープ中も移動しても開いたまま。ネットワークを参照 |
sbx.revokeToken(token) |
そのトークンだけがそれ以降拒否され、ほかのトークンはそれまでどおり使える。token() が返したもの(SandboxToken。その id である tok_… で特定される)、トークンそのもの、またはその ID で指定。1 つのサンドボックスで保持できる、期限の切れていない個別の無効化は 1,000 個まで(超えると 409 too_many_revoked_tokens。その場合は revokeTokens() を使う)。tokenId と expiresAt を持つ TokenRevocation を返す |
sbx.revokeTokens() |
それまでにこのサンドボックス用に発行したトークンがすべて使えなくなる(401 invalid_token)。プレビューリンクや Cookie の中のトークン、SSH セッションが持つトークンも含む。その後に作ったトークンは使える。API キーが必要。TokenRevocation(sandboxId、generation、revokedAt)を返す |
sbx.update(changes), sbx.sleep(), sbx.restart(), sbx.delete() |
変更。すぐにスリープ。ディスクからの起動。削除 |
glac.sandboxes.create と sbx.update の checkpoint。sbx.checkpoint |
稼働中のチェックポイント({ everySeconds: 10 }。更新で null なら解除)。マシンが失われてもプロセスごと再開できる。状況。チェックポイントを取れるマシンがなければ 409 checkpoints_unavailable |
sbx.diskWrites |
今日(UTC の 1 日)のディスクの書き込み量と、全速で書き込める 1 日の割り当て量(SandboxDiskWrites):todayBytes(うちチェックポイントのコピーがホストのディスクに書き込んだ量が checkpointCopyBytes)、fullSpeedUntilBytes(割り当て量。ノードに別の指定がなければ 100 GiB。null ならノードは何も減速しないが、数えることは同じ)、limited と limitedUntil(Date)(割り当てを超えると、その日が終わるまでディスクは毎秒 2 MiB で書き込みを受け付け、失敗する書き込みはない)。ディスクをオブジェクトストアにコピーしないノードでは null で、何も数えない。ドライブへの書き込みはここでは数えない(ドライブへの書き込みに料金はかからない) |
sbx.snapshot(), sbx.fork(), sbx.restore(snapshot), glac.sandboxes.snapshots, snap.update({ name, expiresAt }) |
スナップショット、フォーク、その場でのスナップショットへの復元。スナップショットの一覧。別の名前(null ならなし)、または自動で削除される時刻(null なら削除しない) |
glac.sandboxes.sshKeys.create(publicKey, { sandboxId, expiresAt }) |
1 つのサンドボックス用、またはプロジェクトのすべてのサンドボックス用の SSH キー。秘密鍵は、何かを送る前に拒否される |
sbx.runCode(code), sbx.agent("claude"), new Desktop(sbx) |
コードの実行、コーディングエージェント、デスクトップのサンドボックスの画面 |
出力の逐次受け取り、ファイル、スナップショットとフォーク:
const built = await sbx.run("make", { onStdout: (text) => process.stdout.write(text) });
const cmd = await sbx.exec("node server.js"); // do not wait
for await (const piece of cmd.output()) process.stdout.write(piece.text);
await sbx.files.write("/tmp/notes.txt", "hello\n");
const before = await sbx.snapshot({ name: "before-upgrade" }); // memory, processes and disk
const [copy] = await sbx.fork(); // goes on from this moment
await sbx.restore(before); // back to the snapshot
await copy.delete();ドライブ
ドライブは、サンドボックスより長く残るプロジェクトのディスクです。サンドボックスの作成時か実行中に、一度に 1 つのサンドボックスが読み書き可能でマウントし、サンドボックスがなくなると中身ごと保持され、次のサンドボックスがマウントできます。次のサンドボックスは同じリージョンのもの、またはマシンがホームのオブジェクトストアを読める別のリージョンのもの(後述)です。書き込まれるそばからリージョンのオブジェクトストアにコピーされるため、別のノード上のサンドボックスもマウントできます。その場合は先にそこへ運ばれます。課金されるのは書き込まれたものだけです(usedBytes)。ドライブはコントロールプレーンの機能で(/v1/drives)、単独で動くノードに対しては、どの呼び出しもノードの 404 no_such_route をスローし、そのノードのディスクはボリュームです(後述)。
const drive = await glac.drives.create({ name: "data", sizeBytes: 10 * 2 ** 30 }); // or none: 64 GiB, or 5 GiB on Free
const sbx = await glac.sandboxes.create({ drives: [{ path: "/data", drive: drive.id }] });
await sbx.files.write("/data/notes.txt", "hello\n");
await glac.drives.detach(sbx, drive);
const other = await glac.sandboxes.create();
await glac.drives.attach(other, drive, "/data"); // the same Drive, in another sandboxDrive は、id(vol-…。ドライブはボリュームでもあります)、name、sizeBytes、usedBytes、attachedTo(サンドボックス、パス、モード)、region、home、status、lease(唯一の書き込み元のノード、エポック、期限)、appId と key(キー別インスタンスのもの)、parentDriveId と rootDriveId(フォークの場合:フォーク元のドライブ(その後削除されている場合もある)と系統の最初のドライブ。フォークでないドライブでは null)、managedBy(ManagedBy。データベースのドライブのもの。それ以外では null)、そして専用の attach、detach、update({ name })、refresh()、delete() を持ちます。
- ドライブは一度に 1 つのサンドボックスのものです。ほかのサンドボックスが持っているドライブは拒否され(409
drive_attached)、アタッチ中のドライブの削除も拒否されます。キー別インスタンスのドライブは、キーとともに削除されます(409key_drive)。 - データベースのドライブ(
drive.managedBy)を読み書き可能でマウントできるのはデータベースのインスタンスだけで、名前の変更、復元、削除はデータベースを通じて行います。ここでのそれらの呼び出しと、読み書き可能なマウントは拒否されます(409managed_by_database)。そのスナップショットとフォークはプロジェクトで取得でき、スナップショットは誰でも読み取り専用でマウントできます。 - 別のノードが保持しているドライブは、先にサンドボックスのノードへ運ばれます。オブジェクトストアがまだ現在の状態を保持していない間(503
copy_behind)や、運べなかった場合(503drive_not_brought)、呼び出しはretryableです。クライアントはretriesの指定どおりに再試行し、それでも失敗すれば例外をスローするので、少し後に再度要求してください。 - リージョンをまたぐ場合(slice D9)。ドライブのホーム(
home)とは、そのコピーを保持するオブジェクトストアのリージョンで、作成されたリージョンです。別のリージョンのサンドボックスも、そのリージョンのマシンがホームのストアを読める場合はマウントできます(読めなければ 409not_on_one_machine)。ドライブはバージョンとして手放され、そこで引き継がれ、ゲストが読むのに合わせてホームのストアから読み取られ、ゲストが書き込むものは今いるリージョンのストアに送られます(region)。ホームは、そこに 1 時間いると追従します。glac.drives.moveHome(drive)(drive.moveHome())を使えばすぐに移ります。マシンがホームのストアに残っているものを自分のストアにコピーし、何も残らなくなった時点でhomeが新しいリージョンになります。drive.homeMoveが移動の進み具合を示します(asked_at、asked_by、copied_objects、copied_bytes、objects_left、error)。古いホームのコピーは、保持しているどのバージョンも必要としなくなった時点で消えます(1 日後)。ドライブのホームが移るのは最大で 6 時間に 1 回で(409home_moved_recently)、ホームが現在のリージョンにあるドライブでは 409home_hereになります。 - プランの制限:無料プランでは最大 10 GiB のドライブを 3 つ(合計 15 GiB)、有料プランでは最大 1 TiB のドライブを 500 個です(403
drive_limit_reached)。 glac.usage({ drive })、kind: "drive"、groupBy: "drive"は、ドライブが保持した量、書き込まれた量、キャッシュ外から読まれた量を報告し、glac.usage({ snapshot: "dsnap-…" })はスナップショットが単独で保持した量を報告します。イベントdrive.created、drive.deleted、drive.attached、drive.detachedはEventDriveかEventDriveAttachedを、drive.snapshot.createdとdrive.snapshot.deletedはEventDriveSnapshotを運びます。
**スナップショット、フォーク、復元。**ドライブのスナップショットはオブジェクトストア内のドライブのバージョンで、何もコピーされません。実行中のサンドボックスが書き込んでいるドライブは、書き込みを短時間止めてスナップショットを取ります。fsConsistent: true ならサンドボックスのエージェントがファイルシステムを凍結し(consistency は "frozen"。エージェントが凍結できなければ 409 not_frozen なので、指定せずに取ってください)、そうでなければマシンを一時停止します("paused"。停電時と同じ状態のドライブですが、ファイルシステムのジャーナルはそのためにあります)。実行中のサンドボックスが持っていないドライブは、そのまま取ります("at_rest")。止める時間は最大 10 秒で、超えると呼び出しは 503 unavailable(retryable)で拒否されます。
const snap = await drive.snapshot({ name: "before" }); // a version in the object store: nothing is copied
const reader = await glac.sandboxes.create({ drives: [{ path: "/before", drive: drive.id, snapshot: snap }] }); // read-only
await glac.drives.attachSnapshot(sbx, snap, "/before"); // into a running sandbox too, as many as want it
const copy = await snap.fork({ name: "data-2" }); // a new Drive, mountable at once
await glac.drives.detach(other, drive);
await drive.restore(snap); // detached first: back to the snapshot| 呼び出し | 内容 |
|---|---|
glac.drives.snapshot(drive, { name, fsConsistent }), snapshots(drive), allSnapshots({ driveDeleted }), getSnapshot(drive, snapshot), deleteSnapshot(drive, snapshot) |
スナップショットを取る(名前は一意でなくてよい)。ドライブのスナップショットを新しい順に(ドライブの削除後はその ID で)、またはプロジェクトの全ドライブのスナップショットを一覧表示する(driveDeleted: true ならその後削除されたドライブのものだけ)。ID で 1 つを取得する(ドライブにその ID のスナップショットがなければ 404 snapshot_not_found)。1 つを削除する:そこから作ったフォークは必要なものを保持し、それをマウントしているサンドボックスは、デタッチされるまでマウントを保つ |
glac.drives.fork(drive, { name, fromSnapshot }) |
スナップショット、またはドライブの現在の状態を最初のバージョンとする新しいドライブ。何もコピーされず、すぐにマウントできる。サイズは元のドライブと同じで、作成と同じく、プランのドライブ数の上限の対象(403 drive_limit_reached)。その parentDriveId と rootDriveId が作成元を示す |
glac.drives.restore(drive, snapshot) |
ドライブをスナップショットの 1 つに戻し、ドライブを返す |
glac.drives.create({ historySeconds }), update(drive, { historySeconds }), versions(drive, { at }), fork(drive, { at }), restore(drive, undefined, { at }); drive.versions({ at }), drive.historySeconds, drive.history |
ドライブのバージョンのウィンドウ(slice DB8):時刻を指定して、どこまで遡って復元またはフォークできるか。3600〜86400 秒(指定しなければなし。オフにすると、保持していたものは消える)。書き込みがあった 30 秒ごとに、ある時点のドライブであるバージョンが 1 つウィンドウに加わり、それらが単独で保持するものは、スナップショットと同じく課金される。時刻を指定した復元やフォークでは、その時刻以前の最新のバージョンが使われる。その時刻を指定してバージョンの一覧を呼び出すと、そのバージョンが示される。versions は DriveVersion を新しい順に返す。restore はスナップショットか { at } のどちらか一方を受け付ける(両方、またはどちらもない場合はここで TypeError)。historySeconds: null でウィンドウをオフにする |
glac.drives.create({ cacheBytes }), update(drive, { cacheBytes }); drive.update({ cacheBytes }), drive.cacheBytes, drive.cachedBytes |
ドライブのキャッシュ:ノードが NVMe に保持するドライブの最大量。指定しなければ全量(ドライブのサイズ)。0 まで減らせ、それを超えた分はオブジェクトストアだけにあり、ゲストが読むとそこから読み取られる(最も長く読まれていないものから手放し、ストアにまだないものは手放さない)。キャッシュは、ドライブが保持するデータとは別に、埋まっている量に応じて 1 時間ごとに課金される。小さくすると、超えた分はストアが保持した時点で手放される(実行中のサンドボックスが全量キャッシュでマウントしたドライブでは、そのサンドボックスが次にスリープまたは停止した時点から)。大きくすると、それ以降にゲストが読んだものを保持する。API ではドライブをボリューム(/v1/volumes)と呼び、クライアントは /v1/drives でアクセスする(以前と同じように応答する)。更新で cacheBytes: null を指定すると、再び全量になる。コントロールプレーンがキャッシュについて何も示さない場合、drive.cacheBytes はドライブのサイズ。drive.cachedBytes は、現在ノードの NVMe にある量(どのノードも保持していなければ null)。使用量のレポートのグループと合計には、キャッシュが保持した量 cache_gb_hours がある |
glac.drives.attachSnapshot(sandbox, snapshot, path, { drive }), detachSnapshot(sandbox, snapshot, { force }) |
実行中のサンドボックスにスナップショットを読み取り専用でマウントし、スナップショットの ID でマウントを外す。どちらも DriveSnapshot を返す。DriveSnapshot はドライブを示すが、スナップショットの ID だけなら drive が必要で、なければ何かを送る前に拒否される(400 missing_field) |
drive.snapshot(), drive.snapshots(), drive.fork(), drive.restore(snapshot); snap.refresh(), snap.delete(), snap.fork({ name }), snap.restore(), snap.attach(sandbox, path), snap.detach(sandbox) |
同じことを Drive と DriveSnapshot に対して行う。後者は id(dsnap-…)、name、driveId、consistency、usedBytes(ドライブが保持していた量)、exclusiveBytes(単独で保持する量)、exclusiveAt、version、createdAt を持つ |
- スナップショットはドライブより長く残ります。ドライブの削除後も、ドライブの ID で一覧表示、読み取り、フォーク、読み取り専用でのマウントができます。単独で保持する量(
exclusiveBytes)に対して、1 時間ごとに、ドライブのデータの料金(1 GB-月あたり $0.02)で、ドライブが削除された後も、スナップショットが削除されるまで課金されます。 - フォークは、ほかのドライブと同じく、
usedBytes、つまり論理的な内容全体で課金されます。10 GB のスナップショットのフォークは、10 GB のドライブとして課金されます。 - 復元は、ドライブの最新のバージョンの後に、スナップショットの内容を持つ新しいバージョンを書き込みます。その間のバージョンは、スナップショットかフォークが保持している限り残るため、古いスナップショットに復元した後に新しいスナップショットに復元することもできます。ドライブの現在の中身のうち、どのスナップショットも保持していないものは失われるので、先にスナップショットを取ってください。サンドボックスが読み書き可能で持っているドライブは復元できません(409
drive_attached)。先にデタッチしてください。 - 読み取り専用のマウント:ドライブのリージョンのどのマシン上でも、ドライブを誰が持っていても、ドライブが削除された後でも、いくつものサンドボックスがマウントできます。ブロックはゲストが読むのに合わせてオブジェクトストアから無料で届き、マウントしたサンドボックスの下に表示されます(
glac.usage({ sandbox })、drive_cold_read_bytes)。そのマシンがすでに保持しているものにも料金はかかりません。ゲストは書き込めず、それをマウントしたサンドボックスは別のマシンへ移動できません。サンドボックスは、それをvolumesの中にview_ofとともに示します。 glac.drives.createのdurabilityは、standard、またはゲストの fsync が、それ以前のすべての書き込みがオブジェクトストアに届いてから戻るstrictです。ドライブのdurabilityが設定を示し、作成後は変わりません(書き込みの耐久性)。- オブジェクトストアに何もコピーしないノードには、スナップショット、フォーク、復元はありません(409
not_copied)。アプリのバージョンはドライブを読み書き可能でしかマウントせず、スナップショットの読み取り専用のマウントはサンドボックス用です。
glac.volumes は API の /v1/volumes で、ドライブの下にあるディスクです。移行のために残されています。これにしかないのは、ディスクがボリュームである単独で動くノードと、複数のサンドボックスが同時にマウントするファイルシステムである共有ボリュームです。サンドボックスやアプリは、volumes: [{ path: "/data", volume: vol.id }] としてマウントします。
const team = await glac.volumes.create({ name: "team", shared: true }); // no size is needed
const box = await glac.sandboxes.create({ volumes: [{ path: "/team", volume: team.id }] });**ボリュームのバックアップ。**バックアップは、ある瞬間のボリューム(ドライブもボリュームです)のコピーで、オブジェクトストアに、期限が来るまで保持されます。呼び出しによって、ボリュームのルールに従って毎日、またはサービスの新しいバージョンがインスタンスを置き換える前に取得されます。ドライブのスナップショットと違い、それ自体が独立したコピーで、それだけからボリュームを作ることができ、元のボリュームより長く残ります。ボリュームのバックアップは前回以降に変わったものだけを保持し、複数のバックアップが必要とするブロックは、そのうち最も古いもので 1 回だけ課金されます。ストアがすべてを受け取るまでは making、その後は ready で、それ以降はストアが保持する量(storedBytes)に応じて、スリープ中のサンドボックスが保持するものと同じ料金で課金されます(バックアップ)。バックアップはコントロールプレーンの機能です。
const b = await glac.volumes.backup("data", { expiresInDays: 7 }); // "making", then "ready"
while ((await b.refresh()).status === "making") await new Promise((r) => setTimeout(r, 1000));
const back = await glac.backups.restore(b, { name: "data-back" }); // a new volume: "restoring", then "ready"glac.volumes.backup(volume, { expiresInDays })(またはvol.backup())は、今すぐバックアップを取ります。expiresInDays(1〜365 日)、またはボリュームのルールの日数(設定しなければ 30 日)保持されます。glac.volumes.backups(volume)は、ボリュームのバックアップを新しい順に一覧表示します(削除済みのボリュームのものは、その ID で)。オブジェクトストアに何もコピーしないノードにはバックアップはありません(409not_copied)。glac.backups.list({ volume })は、削除済みのボリュームのものを含め、プロジェクトのバックアップを新しい順に一覧表示します。get(backup)、delete(backup)(それ以降は課金されず、そこから作ったボリュームは残ります)、そしてrestore(backup, { name })は準備ができたバックアップから新しいボリュームを作り、glac.volumes.create({ fromBackup, name })と同じです(作成中は 409backup_not_ready、失敗後は 409backup_unusable)。Backupは、id(bak-…)、volumeId、status(making、ready、failed)、reason(manual、daily、before_version)、storedBytes(課金の基準)、ownBytes(削除すると減る量)、sharedBytes、volumeName(その時点のボリュームの名前)、consistency(frozen、crash)、frozenMs(ボリュームの書き込み元がコピーのために待った時間。凍結しなかった場合は null)、sizeBytes(ボリュームが保持していた量)、volumeSizeBytes(保持できた最大量。そこから作るボリュームが保持する量)、shared、region、failure、createdAt、readyAt(課金の開始時刻)、expiresAt、そしてrefresh()、restore({ name })、delete()を持ちます。- バックアップから作るボリュームは、バックアップ元のボリュームと同じサイズと種類で(
fromBackupではsizeBytesとsharedを指定しません)、バックアップのリージョンに作られます。statusは、バックアップ全体を保持するまではrestoring、その後はreadyで、それまではアタッチできません。fromBackupがバックアップを示します。配置先のマシンで復元できない場合、コントロールプレーンはリージョンの別のマシンにもう一度作ります。そのときinfo.restore_attemptは 2 になり、info.first_failureが最初の試行に失敗した理由を示します。それも失敗するとfailed(info.failure)になり、削除しかできません。 glac.usage({ backup })、kind: "backup"、groupBy: "backup"は、バックアップが保持した量を、状態の計測項目(state_gb_hours)で報告します。glac.billing()のmeters.state.backups_gb_hoursは、その計測項目のうちバックアップの分を示します。
ドメイン
プロジェクトのドメインは、デフォルトのアドレスと同じくアプリへ、またはサンドボックスのポートへ向かう名前です。ドメインはコントロールプレーンの機能です。
| 呼び出し | 内容 |
|---|---|
glac.domains.create(domain, { app }) または { sandbox, port }、get(idOrName)、list({ app, sandbox })、domain.update(...)、domain.refresh()、domain.delete() |
アプリまたはサンドボックスのポートのための名前。verification.records のレコードが DNS に現れるまでは pending_verification(独自ドメイン)。プロジェクトにすでにあれば 409 domain_exists |
const name = await glac.domains.create("api.example.com", { app: "api" });ブラウザ
**未公開。**Glac のブラウザはまだ利用できません。以下は公開に先立つ、現時点での SDK です。
セッションは Glac がホストする Chromium で、Playwright、Puppeteer など任意の Chrome DevTools Protocol(CDP)クライアントから外部で操作します。クライアントの接続中は稼働し、最後のクライアントが離れて sleep_after_seconds が過ぎると、ページを保持したままスリープします。CDP クライアントに必要なのは cdpUrl だけです。認証情報はアドレスのホスト名に含まれるため、セッションの JSON には含まれません。
await using session = await glac.browsers.create({ name: "agent-42" }); // closed and deleted at the end of the block
const browser = await session.connect(); // Playwright, where it is installed
const page = browser.contexts()[0].pages()[0];
await page.goto("https://example.com");-
glac.browsers.create({ kind, name, region, sleep_after_seconds, connection_idle_seconds, delete_after_idle_seconds, metadata, profile, proxy })は稼働中のセッションを作ります(kind:デフォルトのchromium-headless、画面付きのchromium、またはレイアウトエンジンのないブラウザであるlightpanda)。既存のセッションの名前を指定すると、そのセッションがページとともに復帰して返されます。profile: { name, save, version }は、セッションを返す前にプロファイルを読み込みます(後述)。get(idOrName)、list({ status, kind, name })(status:awake、asleep、unavailable、failed)、update(session, changes)、delete(session)、sleep(session)、rotateUrl(session)があり、BrowserSession自体も同じものを持ちます。実行前にサンドボックスが失敗したものはセッションにならず、作成は 503session_failedで拒否されます。 -
session.statusは、awake、asleep、unavailable(ノードに到達できない)、failed(作成に応答した後でサンドボックスが失敗した。たとえばブラウザが停止して戻らなかった)のいずれかで、失敗した場合はsession.errorが理由を示します(ノードが返したとおりの{ code, message }。失敗していなければnull)。イベントbrowser.failedもそれを伝えます(データの型はEventBrowserFailed)。 -
session.cdpUrlは、chromium.connectOverCDPや Puppeteer のbrowserURLに渡すものです。rotateUrl()は新しいアドレスを発行します。古いアドレスはそれ以降拒否され、それで入った接続は切断されます。 -
session.connect({ chromium, timeoutMs })は Playwright を接続します。インストールされていればplaywrightかplaywright-coreを(どちらもこのパッケージの依存関係ではありません)、なければ指定したchromiumを使います。close()はそのブラウザを閉じます。セッションは残り、接続中のクライアントがいなくなるとスリープします。 -
session.liveUrlは、ページをブラウザでライブで見る場所です(ヘッドレスのセッションでは変化に合わせて描画されるタブ、画面ありのセッションでは画面)。閲覧者のマウスとキーボードはページに届きます。await session.live({ interactive: false })は、ページに何も届かない専用のアドレスを返し、人に見せる用途向けです。閲覧者がいる間、セッションは稼働し続けます。どちらもrotateUrlで新しくなり、キーのurlスコープが必要です。 -
プロファイル(Cookie、Local Storage、IndexedDB、Session Storage、設定。プラットフォームがプロジェクトのために保持):
create({ profile: { name: "acme-login", save: true } })はプロファイルを読み込み、セッションの削除時に保存し直します(delete({ saveProfile: false })なら保存しません)。session.saveProfile(name?)は今すぐ保存します(ブラウザは再起動し、接続中のクライアントは接続し直します。その間のプロファイルのほかの保存は 409profile_busyで拒否されます)。session.profileはセッションが持っているプロファイルです。glac.browsers.profiles.list()、get(name)、delete(name)もあります。 -
自前のプロキシ(有料プランのみ。無料プランでは 403
proxy_not_on_plan):create({ proxy: "http://user:password@proxy.example.com:3128" })(または{ url })を指定すると、セッションのポート 80 と 443 への接続は、ブラウザの隣にあるリレーによって、CONNECTを受け付けるその HTTP プロキシを経由して送信され、ブラウザには何も知らされません。そこでは QUIC は拒否されるため、ブラウザは TCP 上の HTTP で通信します。session.update({ proxy })は、実行中のセッションでブラウザを再起動せずにプロキシを変更し(それまでのプロキシ経由の接続は切断されます)、{ proxy: null }でプロキシを外します。認証情報はプラットフォームが保持し、どのレスポンスにも含まれません。session.proxyは{ url, username }で、session.proxyBytesはプロキシを双方向に通過した量です(方向ごとの値はproxySentBytesとproxyReceivedBytes)。プロキシがトンネルを受け付けないページには、HTTP なら 502proxy_failedが返ります。自前のプロキシを参照してください。
セッションは、ほかのインスタンスと同じく、使用したキューブのリソースで課金されます。処理している間の CPU、稼働中のメモリ、送信トラフィックです(料金とプラン)。session.awakeSeconds は稼働していた時間です。プロキシを通過したものは、ほかと同じくセッションのトラフィックです。セッションはコントロールプレーンの機能です。
データベース
データベースはマネージド Postgres です。ドライブ上で動く専用の PostgreSQL で、何も要求がないときはスリープし、接続があればセッションを保ったまま復帰します。約 2 秒で作成され、ルートブランチ main と、オーナー glacdb_owner(glac_superuser のメンバーで、スーパーユーザーではない)を持ちます。オーナーのパスワードは create と resetPassword のレスポンスにしか含まれず、どこにも保存されません。そのどちらかが返したデータベースの connectionString() には、パスワードが含まれます。
import pg from "pg";
const db = await glac.databases.create("shop"); // ready, with the owner's password
const client = new pg.Client({ connectionString: db.connectionString() }); // from a sandbox or a service of the project
await client.connect();
await client.query("create table orders (id bigserial primary key, total numeric)");glac.databases.create(name, { version, region, database_name, size, storage, sleep_after_seconds, suspend_after_seconds, wake_daily_at, metadata })は、データベースを作成し、最初の起動を待ちます:ready、またはcreating(準備ができるまでdb.refresh())。version:16、17、18(デフォルト)。size: { vcpus, memory_bytes }、上限(デフォルトは 1 vCPU と 1 GiB)。storage: { size_bytes, durability }、ドライブで、strict(デフォルト:コミットはオブジェクトストアに届いてから戻る)かstandard。suspend_after_seconds:メモリを保存して本当にスリープするまで一時停止したままでいる時間で、その間の接続には 1〜2 ミリ秒で応答する。1〜86400 秒(デフォルトは 300 秒)。wake_daily_at:休止中に毎日復帰させる時刻(UTC、"HH:MM")(pg_cronのジョブは、いずれにせよそれぞれの時刻に復帰させる)。既存のデータベースの名前を指定すると、パスワードなしでそのデータベースが返されます。update(db, { suspend_after_seconds, wake_daily_at })(とdb.update)は、この 2 つを全ブランチまとめて変更し、nullでそれぞれ元に戻します(300 と、なし)。無料プランでは、Neon の無料プランがゼロまでスケールするのと同じく、データベースの休止の設定は固定です。最後のクエリの 1 秒後に一時停止し(sleep_after_secondsは 1)、その 5 分後にスリープします(suspend_after_secondsは 300)。ほかの値は、作成でも変更でも 400invalid_fieldで拒否されます。glac.databases.createBranch(db, name, { parent, expires_at })(とdb.createBranch(name, { parent, expires_at }))は独自のブランチを作ります。親(parentで別のものを指定しなければmain)の現在の状態を、何もコピーせずに引き継ぎ、プロジェクトのネットワーク上の<database>-<name>に専用の PostgreSQL、エンドポイント、ドライブを持ちます。expires_atを指定すると、その時刻に自動で削除されます。ロールとパスワードは親のもので、作成元のDatabaseがオーナーのパスワードを持っていれば、ブランチのconnectionString()にもそれが含まれます。deleteBranch(db, branch)(db.deleteBranch(branch)、branch.delete())は PostgreSQL を止めてドライブを削除します。main(409root_branch)や、ほかのブランチの作成元になったブランチ(409has_branches)は削除できません。glac.databases.create(name, { history_seconds })、update(db, { history_seconds })、restore(db, { at, exact })、createBranch(db, name, { from: { at, exact } })(とdb.update、db.restore、db.historySeconds、db.history):履歴(slice DB8 と DB5)で、時刻を指定して、ルートブランチをどこまで遡って復元できるか、またはブランチを作れるかです。指定しなければ 1 日で、有料プランでは 0(なし)〜30 日、無料プランでは 1 日です。復元はルートブランチをその場で戻します。分ちょうどの時刻やバージョン自体の時点なら、その時刻以前で最新の、ドライブのウィンドウのバージョンへ(書き込みがあった 30 秒ごとに 1 つ、最大 1 日分)。それより前や秒単位の時刻、またはexact: trueを指定した場合は、WAL アーカイブによってその時点そのものへミリ秒単位で戻します(WAL をその時点まで再生します。数秒〜数分かかり、呼び出しは待ちます)。エンドポイント、接続文字列、オーナーのパスワードは変わらず、接続は切断され、復元前の状態は独自のブランチmain_old_<the restore's moment>として保持されます。新しいバージョンが 5 分以内に応答しなければ取り消されます(503database_restore_failed)。時刻を指定したブランチは、代わりにその時点から作られます。履歴にない時刻は 400invalid_historyになり、そのメッセージは、ウィンドウとアーカイブがいつから遡れるかを示します。秒単位の時刻(またはexact)はアーカイブから戻すか拒否され、その前のウィンドウのバージョンが使われることはありません。アーカイブが届かない場合(アーカイブを保持しないデータベース、または最も古いベースより前の時刻)は 400invalid_history、アーカイブのストアがセグメントを拒否している間は 503database_archive_unavailable(再試行可能)です。どの拒否も、その時刻以前のウィンドウのバージョンを示すので、代わりにそのバージョン自体の時点を指定できます。ストアがブロックを返さない場合は 503drive_unreadable(再試行可能)で拒否され、何も作られません。オブジェクトストアの障害が長引き、ドライブがログのために保持できる量を超える WAL が待つことになると、データベースを動かし続けるためにその WAL は手放されます。これがアーカイブの欠落で、db.history.gaps(DatabaseHistoryGap:from、to、from_lsn、to_lsn)に示されます。手放した WAL の開始時刻、アーカイブがどの時刻も復元できない期間の終わり(その後に取った最初のベースの終わり。それまでは null)、ログ内での開始位置と終了位置です。欠落の間の時刻は、ドライブのウィンドウからしか復元できず(分ちょうどの時刻、またはバージョン自体の時点)、秒単位や exact では 400invalid_historyになります。欠落はイベントでも伝えられます。最初に見つかったとき(データベースの稼働中に、欠落ごとに 1 回)にdatabase.history_gap、その後アーカイブが再び復元できるようになったときにdatabase.history_resumedです。そのdataはEventDatabaseGapです。データベース、欠落のfrom、to(前者では null)、from_lsnとto_lsn、segments(手放した 16 MB のセグメントの数)、そして前者ではerror(オブジェクトストアの最後の拒否の内容)です。create(name, { tiering: { enabled: true } })とupdate(db, { tiering: { enabled } })は、階層型ストレージのオンとオフを切り替えます(slice DB18:有料プランのみ。そのために各ブランチの PostgreSQL が 1 回再起動します)。データベースのinfo.tieringは、オンかどうかと、現在の課金の基準(tiered_bytes)を示します。get(idOrName)、list({ name })、delete(db, { finalSnapshot })(finalSnapshot: falseでない限り、ドライブのスナップショットdb.finalSnapshotを 7 日間保持)、branches(db)(最初にmain、次に独自のブランチを古い順)、branch(db, "main")、sleep(db, "main")、wake(db, "main")(メモリは保存され、復帰したときにはセッションもそのまま。接続でも復帰する)、resetPassword(db, { role })(新しいパスワードはこのレスポンスにだけ含まれる。古いパスワードは次のログインから拒否される)があります。Database自体も同じものを持ちます。glac.databases.roles(db)(とdb.roles())は、ルートブランチのログインするロールを、PostgreSQL 自体から読み取って一覧表示します({ name, owner })。オーナーと、作成したロール(CREATE ROLE … LOGIN)で、プラットフォームのロールは含みません。resetRolePassword(db, role)(とdb.resetRolePassword(role))は、そのどれかに新しいパスワードを与え、それはこのレスポンスにだけ含まれます。ロールのpasswordと、そのロールでのルートブランチの接続文字列(connectionString())を持つDatabaseRoleSecretです。オーナーの接続文字列は、API が返すデータベースから作られます。独自のブランチは、作成時のパスワードを保持します。db.connectionString({ password, pooled, private })は、node-postgres(connectionString)、psql、libpq にそのまま渡せる文字列です。API が返す場合はパブリックなもの、そうでなければプロジェクトのネットワーク上のもので、一緒に返されたレスポンスのオーナーのパスワード、またはpassword(パーセントエンコード)入りです。パブリックなもののsslmode=requireは、現在の node-postgres が受け付ける形のsslmode=verify-fullで示されます。db.defaultBranchは、API が返すとおりの接続文字列(connectionUri、pooledUri、privateUri)、endpoint、state(awake、paused、asleep)を持ちます。
データベースには、プロジェクトのネットワーク上で、そのサンドボックス(project_network で作成)とサービスから名前でアクセスします。これが private_uri です。エッジが PostgreSQL を提供する場合は、パブリックアドレスでどこからでもアクセスできます(connection_uri、ポート 5432、TLS のみ。プーラーは pooled_uri)。提供するエッジがない場合や、データベースに名前を付けるリージョンがない場合は null で、接続文字列は、それらがあればそれらを使います。データベースの JSON にはパスワードは含まれません。データベースはコントロールプレーンの機能です。
マネージドアプリケーション
カタログのアプリケーション(たとえば Rivet)は、プロジェクトに専用のキューブのリソースとしてインストールされます。アプリ、ボリューム、シークレット、バケットで、インストールのラベルが付き、変更はインストールを通じてのみ行い、それぞれのリソースとして課金されます(マネージドアプリケーション)。インストールのレスポンスには、秘密のもの(管理者トークン、それを含むエンドポイント)も含め、すべての出力値があり、読み取りでそれらが表示されるのは admin スコープを持つキーだけです。インストールのオブジェクトは、以前のレスポンスが返し、後のレスポンスが隠した出力値をそれぞれ保持します。
const rivet = await glac.catalogue.get("rivet"); // its parameters, and a month of it in Cube's meters
console.log(rivet.calculator?.month);
const engine = await glac.installations.create("rivet", { parameters: { disk_gib: 20 } }); // waits until it is ready
console.log(engine.outputs.endpoint); // RIVET_ENDPOINT, its token in it| 呼び出し | 内容 |
|---|---|
glac.catalogue.list(), glac.catalogue.get(app, { version }) |
カタログのアプリケーションの一覧。それぞれ CatalogueApp(app、version、title、summary、verified、licence、versions、parameters:JSON Schema、outputs、month を含む calculator)。1 つのアプリケーション(最新バージョン、または指定したもの)と、その definition |
glac.installations.create(app, { name, version, region, parameters, requires, wait, timeoutSeconds }) |
インストールし、準備ができるまで待つ(wait: false ならすぐに戻る)。失敗すると、その説明と installation とともに InstallationError をスローする。timeoutSeconds を過ぎてもまだ作成中なら、コード wait_timeout の GlacError |
glac.installations.list({ app }), glac.installations.get(idOrName) |
プロジェクトのインストールの一覧(新しい順)。1 つのインストール |
installation.update({ version, parameters }, { wait }), installation.delete(), refresh(), wait({ timeoutSeconds }) |
別のバージョンへの変更、またはパラメーターのマージパッチによる変更(null でデフォルトに戻す)。構成するものごとの削除(status:deleted または deleting) |
installation.status, failure, outputs, secretOutputs, parameters, apps, volumes, buckets, requires, requiredBy |
API が返すとおり |
マネージドアプリケーションはコントロールプレーンの機能です。
シークレットと変数
glac.secrets.put(name, value, { allowedHosts }) は、どのレスポンスも返さない値を保持し、その値は指定したホストにだけ送られます。アプリには環境変数として渡します(secrets: { DATABASE_URL: "db_url" })。glac.variables.put(name, value) は共有変数で、アプリの環境変数から ${{ shared.NAME }} として参照します。
プロジェクト、アカウント、請求
glac.project() と glac.events()。glac.account() と glac.usage()、そして updateAccount() は、ほかの設定とともに、アカウントの通知の送信先({ email })と通知の言語({ language: "de" }:“en”、“de”、“fr”、または再び請求先住所に従うなら null)を設定します。アカウントの email_verified はアドレスが確認済みかどうかを、email_undeliverable(EmailUndeliverable:why、since、detail)はそのアドレスへのメールが不達だったことを示し、その間、通知は保留されます(通知の送信先)。keeps(AccountKeeps。コントロールプレーンの背後にある場合)は、キーのプロジェクトが保持するドライブとボリュームを、作成時に適用される数値で、それぞれ持っている量とともに示します(volumes、volume_bytes、volumes_bytes(上限なしなら limit は null)、unsized_bytes:上限の引き上げを参照)。glac.billing()、glac.balance()、glac.topUp(usd)、glac.charges()、glac.ledger()、そしてプランと自動チャージを変更する updateBilling() と updateBalance() もあります。請求とプロジェクトはコントロールプレーンの機能です。
**イベントのデータ。**コントロールプレーンのイベントの種類ごとに、そのデータ(event.data)のインターフェースがあり、説明のスキーマにちなんだ名前が付いています(event.data as EventDatabase)。データベースのもの:EventDatabase(database.created:region と PostgreSQL の version 付き。database.deleted:7 日間保持されるドライブのスナップショット final_snapshot_id 付き)、EventDatabaseBranch(database.branch_created:parent 付き。database.branch_deleted:reason 付き)、EventDatabaseRestored(database.restored:時点 at と、復元前の状態を保持するブランチ old_branch_id)、EventDatabaseArchive(database.archive_failing:since、連続して失敗したセグメントの数 failures、その間に復元で遡れる範囲 archived_until、オブジェクトストアの error。database.archive_recovered:recovered_at)、EventDatabaseGap。ほかも同様です。サンドボックスのもの(EventSandboxSlept、EventSandboxWoke、EventSandboxMoved、EventSandboxDeleted など)、アプリとそのキーのもの(EventApp、EventAppFailed、EventKeyCreated など)、プロジェクトのデプロイとキー、バックアップ、ドメイン、エクスポート先、ドライブのスナップショットのもの(EventProjectDeployed、EventBackup、EventDomain、EventDestination、EventDriveSnapshot など)、そしてアカウントのプラン、チャージ、返金、撤回、残高、クレジット、ストレージ、送信量の上限、利用上限額、保留、未払いのもの(EventSubscriptionPeriod、EventTopupCollected、EventWithdrawalReceived、EventBalance、EventInDebt など)です。その種類のすべてのイベントにあるわけではないフィールドは、省略可能です。
Webhook、API キー、利用上限額、プロジェクト。glac.webhooks.create(url, { events, enabled }) は、プロジェクトのイベントを自前のアドレスに送ります(各配信に署名する secret は、このレスポンスにだけ含まれます)。get、list、update、delete もあります。glac.keys.create({ name, scopes, expiresAt }) は API キーを作ります(キーそのもの key は、このレスポンスにだけ含まれます)。get、list、delete もあります。glac.limits() と glac.updateLimits(usd) は、プロジェクトの利用上限額と今月の支出額で(null で上限を外します)、sbx.limits() と sbx.updateLimits(usd) はサンドボックス自体のものです。glac.requestLimit(limit, wanted, why) は、アカウントの申請による上限の引き上げを 1 つ申請します(名前で指定する上限、"volumes"、"volume_bytes"。バイト数の 2 つはバイト単位。同じ上限の申請が待機中なら 409 limit_request_pending)。glac.limitRequests({ limit, cursor }) と glac.limitRequest(id) は、申請をオペレーターの回答とともに読み取ります(LimitRequest:status は pending、granted、refused。granted、answer)。instance.restarted、instance.upgraded、instance.reverted、instance.upgrade_failed、limit_request.granted と .refused のイベントのデータは、EventInstanceRestarted、EventInstanceUpgraded、EventInstanceReverted、EventInstanceUpgradeFailed、EventLimitRequest です。glac.updateProject({ deleteAfterIdleDays: 30 }) は、スリープが何日続いたらサンドボックスを削除するかを設定します(null なら削除しない)。コントロールプレーンの機能です。Webhook と API キー、プロジェクトの上限の設定、updateProject には admin スコープを持つキーが必要で、サンドボックスの上限の設定には write が必要です。
ログのエクスポート先、監査ログ、請求関連の書類。glac.destinations は、プロジェクトのアプリのログ(とメトリクス、リクエストのトレース:signals: ["logs", "traces"])のエクスポート先(OpenTelemetry のエンドポイント)です:create(name, { endpoint, headers, ... })(作成時にすぐ 1 件のレコードで試す:status.connected)、get、list、update、delete、test(すぐに 1 件送る)。ヘッダーの値は認証情報で、エクスポート先の作成時か変更時に送られ、どのレスポンスにも含まれません(返されるのはヘッダーの名前だけです)。glac.audit({ limit, cursor }) は、プロジェクトの監査ログの 1 ページ分を新しい順に返します(実行者、メソッド、パス、ステータス)。読み取り以外のすべての呼び出し(拒否されたものも含む)と、オペレーターによるアプリのログの読み取りすべてです。glac.statement("2026-09") は 1 か月の明細(指定しなければ今月。その月の Date も可)で、データベースごとの行(databases:その月の使用量、履歴と階層化データを別記したもの、それを定価で換算した額)を含みます。glac.invoices() は Stripe にある請求書(PDF のリンク付き)、glac.notices() はアカウントに送られたメール、glac.withdrawals() は今撤回できる購入と行った撤回(EEA または英国の消費者は、購入から 14 日以内であれば撤回できます)、glac.withdraw(chargeId, { name, email }) は購入者自身による撤回の意思表示で、すぐに受理されます。コントロールプレーンの機能です。エクスポート先の作成、変更、テスト、削除、監査ログの読み取り、撤回には admin スコープを持つキーが、それ以外には読み取りのできるキーが必要です。
**すぐに受け取る購入(startNow)。**欧州経済領域または英国の消費者(確認済み、または確認中の税番号を持たないアカウント)は、購入から 14 日以内であれば撤回でき、購入したものがすぐに提供されるのは、本人が明示的に求めた場合だけです。topUp(20, { startNow: true })、updateBilling({ plan: "paid", startNow: true })(無料プランから購入する有料プラン)、updateBalance({ autoReload: true, startNow: true })(自動チャージがオンの間のすべての自動チャージ)が、その要求を、コンソールのチェックボックスの横に表示される文言(GET /v1/billing/withdrawals の right.start_now にもあるもの)で伝えます。
撤回できる 14 日間のうちに、この購入をすぐに提供するよう求めます。撤回した場合は、それまでに提供された分を支払います。チャージのクレジットをすべて使った時点で全額が提供されたことになり、それ以降は撤回できなくなることを理解しています。
サービスはすぐに始まります。14 日以内なら購入者はまだ撤回でき、その場合はそれまでに提供された分を支払います。すべてが提供されると(チャージのクレジットをすべて使うと)、撤回する権利はなくなります。この要求は、購入する本人が求めたときだけ指定してください。SDK は、指定されない限り決して送りません。指定がなければ、そのようなアカウントは拒否されます。400 start_now_required(param は start_now)の GlacError で、国名を挙げ、要求の文言を引用したメッセージが付き、何も購入されません。要求なしで自動チャージをオンにしたアカウントでは何もチャージされず、月に 1 回通知されます(イベント auto_reload.no_request。そのデータは EventAutoReloadNoRequest)。
エラー
0 以外の終了コードは結果です。res.exitCode を読んでください。API のエラーは、status、type、code、param、retryable、requestId、message を持つ GlacError をスローします。DeployError(立ち上がらなかったバージョン、または失敗したプロジェクトのデプロイ)と LogsError(読めなくなったログ)も GlacError です。
知っておくべき動作
- **スリープ中のサンドボックス。**サンドボックスがスリープしていることを理由に失敗する呼び出しはありません。サンドボックスが復帰し、呼び出しはそのまま続きます。復帰させるための呼び出しはありません。
- **再試行。**応答がない場合や
retryableのエラーは、0.2 秒から倍々に間隔を空けて 2 回再試行されます(retries)。すべてのPOSTにはIdempotency-Keyが付きます。バイトのボディを持つ呼び出しは再試行されません。 - Python SDK にあって、まだないもの:
cmd.wait()の制限時間とクライアントのタイムアウト、生の出力バイト、使用量の正確な 10 進数の合計。これまでのところ、Node でしか動かしていません。