目次
ガイド

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プログラムが無効。報告が添付される
RequestHeldRunが始まる前にポリシーが判断を保留した(Run前のCheckpoint)。ほかのCheckpointと同じように解決してから、Runに attach する
RequestConflictその requestId が、別のプログラム、パラメーター、入力、またはスコープで使われていた
SubmissionUnknown開始への応答が失われた。新しいリクエストIDで始め直さず、リクエストを照会する
ResolutionUnknown承認への応答が失われた。同じ項目を再送すれば、Hostは繰り返しだと認識し、二重に適用しない
ClientClosedクライアントが閉じられている

これらに共通するルールは、「応答が失われたことは、何も起きなかったことを意味しない」です。リクエストやRunはその識別情報で照会し、書き込みの再試行として再送はしないでください。

次に読む

Source: actql/actql/docs/guides/node.md at d904416674e4 · Edit on GitHub