TypeScriptとNode
@actql/node は、ActQLをあなたのアプリの中で動かします。サーバーもデーモンもなく、エンジンはライブラリです。接続オプションを変えれば、同じクライアントでリモートのHostやActQL Cloudにもつながります。このガイドでは、日常的に使うAPIを示します。ここに出てくるものにはすべて型が付いていて、細かい部分の説明は型そのものが担います。
npm install @actql/node短いクエリ
いちばん短い道は open() です。状態をディレクトリに置くローカルのエンジンと、一度きりのクエリのための sql() / aql() が手に入ります。
import {open} from '@actql/node';
const db = await open({stateDir: '.actql'});
try {
const rows = await db.sql('SELECT id, name FROM crm.accounts WHERE owner = @owner', {owner: 'kim'}).all();
console.log(rows);
} finally {
await db.close();
}第2引数は常にパラメーターのマップです。aql() はPipe構文(FROM … |> …)を、sql() は普通の SELECT を受け取ります。どちらもResultのPromiseを返し、all() で全件を読み、rows() で順に流し読みします。
for await (const row of (await db.aql('FROM crm.accounts |> LIMIT 1000')).rows()) {
console.log(row.id);
}短いクエリは読むだけで、決して書きません。Effectのあるプログラムは、後述の startSource を通します。
接続する
connect() が完全なクライアントです。ローカルでは open() と同じエンジンで、インストール先を明示的に書きます。
import {connect} from '@actql/node';
const client = await connect({
mode: 'local',
installation: {directory: '.actql'},
profile: 'mock', // 省略すると設定した接続を使う
});リモートのHostには:
const client = await connect({
mode: 'remote',
endpoint: process.env.ACTQL_ENDPOINT, // actql://host.acme.internal
token: process.env.ACTQL_TOKEN,
});token には文字列のほか、文字列を返す関数も渡せるので、短命の認証情報を更新しながら使えます。actql:// の接続文字列でもリモートのHostを選べます。モードが何であれ、それ以外のAPIは同じです。
クライアントは必ず閉じてください。finally の中か、await using を使います。閉じると接続は解放されますが、Runの取り消し、Checkpointへの回答、Resultの削除が起きることはありません。
プログラムを実行する
import {readFile} from 'node:fs/promises';
const source = await readFile('churn-risk.aql', 'utf8');
const execution = await client.startSource(source, {
format: 'aql',
parameters: {owner: 'kim'},
stopBefore: 'commit', // 任意:最初の書き込みの前で止める
});
const state = await execution.wait();
console.log(state.status); // 'succeeded' | 'waiting_checkpoint' | 'failed' | …startSource はプログラムを検査し、永続的なRunを始めます。wait() は次の安定した状態、つまり判断待ち、突き合わせ待ち、または最終状態で解決し、Runの状態を返します。status、待っている間は checkpoint、出そろってからはEffectの項目ごとの結果、そして revision です。Runがすでに安定した状態なら、wait() はその状態ですぐに解決します。その次の状態を待つには wait({since: state.revision}) を使います。succeeded は、すべての計算とEffectに結果が出たという意味です。非同期のEffectは accepted のまま、外部システム側の処理が続いていることがあります。execution.ref はRunの参照です。別のプロセスがこのRunを必要とするなら保存しておきます。
Runが成功したあとに、名前付きのResultを読みます。
const rows = await execution.result('updates').all();result(name) は、プログラムが |> RESULT name で公開したResultを読みます。読み取りだけのプログラムは main を公開します。読み取りには上限があります。大きなResultには {maxRows} を渡すか、rows() で流し読みしてください。
型付きのレコード
レコードはActQLの中では型が付いています。それをTypeScriptまで持ち込むには、Standard Schemaのインターフェースに従うスキーマ(zod、valibot、ArkTypeなど)を all() か rows() に渡します。各レコードはそのスキーマで検証され、結果の型もそれで決まります。
import {z} from 'zod';
const Update = z.object({
id: z.string(),
risk: z.object({value: z.object({level: z.enum(['low', 'medium', 'high']), reason: z.string()})}),
});
const rows = await execution.result('updates').all(Update); // Array<z.infer<typeof Update>>合わないレコードがあれば、スキーマの指摘とともに読み取りが失敗します。値が変換されることはありません。同じ引数は db.sql(…).all(schema) でも使えます。
Checkpoint
ポリシーが書き込みの判断を保留したとき(または stopBefore: 'commit' を渡したとき)、wait() は status: 'waiting_checkpoint' とCheckpointを返します。
const state = await execution.wait();
if (state.status === 'waiting_checkpoint') {
const ckpt = state.checkpoint;
console.log(ckpt.id, ckpt.effect.target, ckpt.items.length);
for (const change of ckpt.preview.changes) {
console.log(change.id, change.field, change.before, '→', change.after);
}
}プレビューは、承認が及ぶ変更の正確な集合です。コードから判断するか、人に渡してこのプロセスを終えるかを選べます。RunはStoreの中で待ちます。
if (ckpt.items.length <= 100) {
await client.approve(ckpt.id);
const final = await execution.wait({since: state.revision});
} else {
await notifyReviewer({runRef: execution.ref, checkpoint: ckpt.id});
// ここで待つものはない。レビュー担当者がCLIか別のプロセスから承認し、
// あとのプロセスが execution.ref にアタッチして結果を読む。
}approve(id) と reject(id) は、クライアントに設定した承認者で判断に署名し、そのプレビューに結び付けます。承認すると同じRunが続きます。却下すると、Runは何も送らずに cancelled で終わります。判断は、StoreかHostを共有するどのプロセスから来てもかまいません。CLIの actql approve でも、Webアプリでも、Slackのボットでも、Runはどれから来たかを区別しません。
コードで判断する
Checkpointを待ってからあとで承認する代わりに、クライアント自身を委譲先にできます。connect() に decide 関数を渡すと、Hostは止まる前にプレビューを渡してそれを呼びます。関数は 'permit'、'reject'、'defer' のどれかを返し、'defer' のときだけCheckpointが作られます。
const client = await connect({
mode: 'local', installation: {directory: '.actql'},
decide: async (decision) => {
if (decision.kind !== 'effect_set') return 'permit'; // 読み取り、LLMの呼び出し、Run自体
return decision.preview.items.length <= 100 ? 'permit' : 'defer';
},
});decision.kind は何を判断するかを示します。'run'(Runの受け付け)、'read'、'infer'、'egress'、'effect_set' です。preview を持つのは effect_set の判断だけで、preview.items は変更されるレコード、preview.fields は書き込まれる項目、preview.target は対象のリソースです。関数ができるのは判断だけで、プレビューを変えることはできません。関数が例外を投げれば、何も送られません。
多くのRunにまたがってCheckpointを一覧して読むレビューの仕組みには、client.checkpoints()、client.reviewCheckpoint(ckpt)、client.resolveCheckpoints(items, {signer}) が、明示的な署名とページングを伴う同じ操作を提供します。
Runにアタッチする
Runは、それを始めたプロセスより長く生きます。同じStoreかHostを使うどのプロセスからでも:
const execution = await client.attach(savedRef); // 以前の execution.ref
const state = await execution.status(); // 待たないstatus() は待たずに現在の状態を返し、wait() は追いかけます。クラッシュのあとも、Runは止まった場所にそのままあります。待っていたCheckpointはまだ待っていて、終わった処理は繰り返されず、結果が失われた書き込みは needs_reconciliation として報告されます。
if (state.status === 'needs_reconciliation') {
const after = await execution.reconcile(); // 外部システムに確認する。再送はしない
console.log(after.status, after.effects);
}execution.cancel() はRunに停止を求めます。停止が確認されると、状態は cancelled になります。
同じプログラムを同じ requestId で始めると、もう一度実行する代わりに既存のRunが返ります。再試行する呼び出し元を安全にするために使ってください。照合はリクエスト全体に対して行われます。同じプログラム、パラメーター、入力、認可のスコープであることが必要で、同じ requestId に違う内容を渡すと RequestConflict になります。黙って再利用されることはありません。
const execution = await client.startSource(source, {format: 'aql', requestId: 'nightly-2026-10-15'});実行せずに検査する
check() は、actql check と同じ静的な検査を行い、理由とその位置を構造化した報告として返します。
const outcome = await client.check(source, {kind: 'text', format: 'aql', scope: 'template_only'});
if (outcome.report.verdict !== 'valid') {
for (const reason of outcome.report.reasons) console.log(reason.code, reason.location);
}plan() は、actql explain が表示する計画をデータとして返します。
ファイルの内容を読むこと、SERIALIZE でファイルを作ること、Resultを受け取ることは、別々のものです。ファイルを扱うがその違いを説明しています。
エラー
失敗はすべて、安定した code を持つ ClientError です。扱う価値のあるもの:
| コード | いつ |
|---|---|
CheckFailed | プログラムが無効。報告が添付される |
RequestHeld | Runが始まる前にポリシーが判断を保留した(Run前のCheckpoint)。ほかのCheckpointと同じように解決してから、Runに attach する |
RequestConflict | その requestId が、別のプログラム、パラメーター、入力、またはスコープで使われていた |
SubmissionUnknown | 開始への応答が失われた。新しいリクエストIDで始め直さず、リクエストを照会する |
ResolutionUnknown | 承認への応答が失われた。同じ項目を再送すれば、Hostは繰り返しだと認識し、二重に適用しない |
ClientClosed | クライアントが閉じられている |
これらに共通するルールは、「応答が失われたことは、何も起きなかったことを意味しない」です。リクエストやRunはその識別情報で照会し、書き込みの再試行として再送はしないでください。
次に読む
- クイックスタート:解約リスクのプログラムを最初から最後まで
- ローカルとリモートのHost:接続、認証情報、ポリシー
- Runのライフサイクルと復旧:すべての状態と、そのときにすること
- Python:同じAPIをPythonから
Source: actql/actql/docs/guides/node.md at d904416674e4 · Edit on GitHub