zion-baas について
zion-baasは、Zion.appでフロントエンドアプリを構築するためのスキルです。GraphQL APIを通じた認証、プロジェクト情報、ランタイム操作を扱い、クエリやサブスクリプション、スキーマ確認用のスクリプトも含みます。
主な機能
- 01ユーザー名または開発者トークンで認証
- 02プロジェクトとGraphQLスキーマの取得
- 03コマンドラインでクエリと変更を実行
- 04WebSocketでサブスクリプションを監視
- 05プロジェクト認証情報とユーザートークンを保存
活用シーン
- 01Zion.appのフロントエンド認証を設定
- 02プロジェクトを探してデータモデルを確認
- 03コマンドラインでクエリや変更をテスト
- 04GraphQLサブスクリプションのイベントを受信
MCP 対応
READMEではスキーマ確認にzion MCP serverを使う方法に触れていますが、このスキルがMCPクライアントに提供する機能は説明されていません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ functorz-tech/ zion-baas-skill --skill zion-baasインストール後、Claude Code で/zion-baasを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.DS_StoreREADME.mdscripts/meta.tsfetchRuntimeToken.tsauthenticate.tsauthenticateEmail.tspackage.jsonsubscribe.tsgql.tstsconfig.json詳細
Zion.app ヘッドレス BaaS スキル
概要
このスキルでは、ヘッドレスの Backend-as-a-Service(BaaS)として Zion.app を利用するフロントエンドアプリケーションの構築方法を説明します。Zion は、すべてのバックエンドとのやり取り(データベース、actionflow、サードパーティ API、AI エージェント)を、単一の統合 GraphQL API を通じて提供します。
- HTTP URL:
https://zion-app.functorz.com/zero/{projectExId}/api/graphql-v2 - WebSocket URL:
wss://zion-app.functorz.com/zero/{projectExId}/api/graphql-subscription
トークンの取得と認証(重要)
認証が必要なエンドポイントとやり取りするには、ログインまたは登録して JWT トークンを取得する必要があります。認証されていないリクエストには、匿名ユーザーロールが割り当てられます。JWT は 2 つの方法で取得できます。1 つはユーザー名とパスワードでログインする方法です。もう 1 つは、Meta API をクエリしてランタイムバックエンドトークンを取得する方法です。FETCH_DATA_VISUALIZER で返されるトークンが、ランタイムバックエンドトークンです。
1. ユーザー名での登録とログイン
ユーザーにユーザー名とパスワードを尋ねてください。
mutation AuthenticateWithUsername($username: String!, $password: String!, $register: Boolean!) {
authenticateWithUsername(username: $username, password: $password, register: $register) {
account { id, permissionRoles }
jwt { token }
}
}
注:どちらのミューテーションも、account 型のサブセットである FZ_Account を返します。含まれるのは email、id、permissionRoles、phoneNumber、profileImageUrl、roles、username のみです。
Zion.app での開発者認証(Meta API)
Zion プラットフォーム(Meta API)と直接やり取りしてプロジェクトスキーマの取得やプロジェクト一覧の取得を行う場合、またはランタイムバックエンドに管理者として認証する場合は、次の手順に従ってください。
1. 開発者 JWT トークンを取得する
開発者トークンは、OAuth フローまたはメールアドレスとパスワードでのログインの 2 つの方法で取得できます。
オプション A:OAuth フロー
OAuth コールバックを受信するローカル HTTP サーバーをセットアップします。ブラウザーで Zion の認証エンドポイント(https://auth.functorz.com/login)を開き、token パラメーターが届くまで待ちます。
同梱の認証スクリプトを実行できます。
cd ~/.agents/skills/zion-baas-skill/scripts
npm run auth
オプション B:メールアドレスとパスワードでログイン
Meta API を使用して、メールアドレスとパスワードで直接ログインできます。
cd ~/.agents/skills/zion-baas-skill/scripts
npm run auth:email <email> <password>
2. Meta API のクエリとランタイムバックエンドトークンの取得
開発者 JWT トークンを Bearer トークンとして Meta API(https://zionbackend.functorz.com/api/graphql)に対して使用し、スキーマまたはデータビジュアライザートークンを取得します。データビジュアライザートークンを使うと、プロジェクトのランタイムバックエンド(zeroUrl)への管理者アクセスが可能になります。
トークンの取得には、同梱のスクリプトを実行できます。このスクリプトを使用するには、開発者トークンが .zion/credentials.yaml に存在している必要があります。
cd ~/.agents/skills/zion-baas-skill/scripts
npm run fetch-token -- <projectExId>
同梱の meta スクリプトを使って、Meta API 経由でプロジェクトを検索したり、スキーマを取得したりすることもできます。
cd ~/.agents/skills/zion-baas-skill/scripts
# Search projects (returns project names and exIds)
npm run meta -- search-projects "optional search term"
# Fetch project schema (returns data models, actionflows, apis)
npm run meta -- fetch-schema <projectExId>
認証情報の管理と状態の永続化
すべての認証情報とプロジェクトの状態は、ユーザーのプロジェクトのルートにある .zion ディレクトリ内に、YAML 形式で必ず永続化してください。通常は .zion/credentials.yaml という名前のファイルに保存します。
必須の YAML 形式
認証情報ファイルは、次の構造に従う必要があります。
# .zion/credentials.yaml
developer_token:
token: "<your_developer_jwt_token>" # Used to communicate with zionbackend.functorz.com
expiry: "<timestamp_or_date_of_expiry>"
project:
exId: "<project_ex_id>"
name: "<project_name>"
admin_token:
token: "<runtime_backend_admin_token>"
expiry: "<timestamp_or_date_of_expiry>"
other_users:
- user_id: "<user_id>"
user_tags:
- "<notable_information_about_the_user_1>"
token:
token: "<user_jwt_token>"
expiry: "<timestamp_or_date_of_expiry>"
developer_token:OAuth フローで取得する JWT トークンです。Meta API(zionbackend.functorz.com)へのアクセスに使用します。project:プロジェクトの主要な識別子(exId、name)、ランタイムバックエンド用のadmin_token(データビジュアライザートークン)、およびother_usersを含みます。other_users:認証済みユーザー(テストアカウントなど)のリストです。user_id、用途を識別するためのuser_tags、およびtokenを含みます。expiry:保存されるすべてのトークンには、有効期限を設定する必要があります。
CLI での GraphQL クエリとサブスクリプションの実行
同梱のスクリプトを使うと、フロントエンドのボイラープレートを作成せずに、コマンドラインから GraphQL クエリ、ミューテーション、サブスクリプションをすばやくテストできます。これらのスクリプトは、プロジェクトの .zion/credentials.yaml から適切なトークンを自動的に読み込みます。
1. クエリまたはミューテーションを実行する
GraphQL クエリまたはミューテーションを文字列として渡します。
cd ~/.agents/skills/zion-baas-skill/scripts
npm run gql -- <projectExId> <role> '<query_string>' '<optional_variables_json>'
<role>:admin(データビジュアライザートークンを使用)、anonymous(トークンなし)、または特定のuser_id(.zion/credentials.yamlのother_usersからトークンを取得)を指定できます。
例:
npm run gql -- myProjectEx123 admin 'query GetProject($id: Int!) { project(id: $id) { name } }' '{"id": 1}'
2. サブスクリプションをリッスンする
GraphQL サブスクリプションを文字列として渡します。
cd ~/.agents/skills/zion-baas-skill/scripts
npm run subscribe -- <projectExId> <role> '<query_string>' '<optional_variables_json>'
スクリプトは WebSocket 接続を確立し、終了するまで(Ctrl+C)受信したイベントを継続的に出力します。
データベースと GraphQL スキーマのルール
- 前提を決めつける前に、スキーマをイントロスペクトするために
zion MCP serverまたはwebfetch/curlを使用してください。
GraphQL スキーマは PostgreSQL データモデルから直接、自動的に生成されます。
重大な見落としを防ぐためのルール: 構造、使用可能なフィールド、Enum 値(
[Enum:ROUNDING_MODE]など)、またはエンドポイントの特定の引数に少しでも確信が持てない場合は、前提を決めつける前に、必ず提供されたエンドポイント URL を介して webfetch/curl ツールで実際の GraphQL スキーマをイントロスペクトしてください。
1. 命名規則とルート操作
各テーブルから、対応するルートクエリ、ミューテーション、サブスクリプションのフィールドが生成されます。テーブル名が [table] の場合:
クエリ
[table]:リストを取得します(where、order_by、distinct_on、limit、offsetに対応)。[table]_by_pk:主キー(id)で単一レコードを取得します。[table]_aggregate:集計クエリ(count、sum、avg、min、max)。[table]_group_by:指定したフィールドに基づいてレコードをグループ化します。fz_[table]_by_[column]:テーブルにgeo_point列が含まれている場合に、自動生成される空間近接検索です。- Relay API:設定されている場合、カーソルベースのページネーションを行うクエリを生成し、
Connection_[table]オブジェクトを返します。
ミューテーション
insert_[table]、insert_[table]_one:レコードを作成します。ネストした挿入とon_conflictに対応します(constraintとupdate_columnsが必要です)。update_[table]、update_[table]_by_pk:レコードを変更します。_set(置換)と_inc(数値の増加)を使用します。一括更新ではwhereが必須(!)です。(注:Hasura の JSONB 更新演算子(_append/_delete_keyなど)には対応していません)。delete_[table]、delete_[table]_by_pk:レコードを削除します。一括削除ではwhereが必須(!)です。export_[table]:テーブルのデータエクスポートタスクを開始します。
サブスクリプション
[table]、[table]_by_pk、[table]_aggregate:対応する標準クエリを反映したライブクエリです。
2. 列の型とデータマッピング
プリミティブ型
text→String、integer→Int、bigint、bigserial→bigint、float8→Float8、decimal→Decimal、boolean→Boolean、jsonb→jsonb。- 時間/日付:
timestamptz、timetz、date、interval。 - 地理情報:
geo_point→geography。
複合型(メディア)
メディア列(image、file、video)は構造上 1:N のリレーションですが、フィールドとして機能します。
- 単一メディア:
[column]_idとして保存され(例:cover_image_id)、システムアセットテーブル(FZ_Image、FZ_File、FZ_Video)を参照します。 - メディアリスト:
image_list、video_list、file_listなどの型は GraphQL 配列にマッピングされ、[column]_idsとして保存されます(例:gallery_images_ids)。
システム管理列
id、created_at、updated_at は読み取り専用で、自動的に管理されます。ミューテーション入力(_set、_inc、insert 入力)には使用できません。
3. リレーションシップ
リレーションシップは外部キーによって定義され、GraphQL のネストされたフィールドを決定します。
- 1:1(1 対 1):単一のネストされたオブジェクトを返します(例:
meta: post_meta)。 - 1:N(1 対多):配列(例:
post_tags: [post_tag])と集計オブジェクト(例:post_tags_aggregate)を返します。
4. フィルタリング(where 句)
フィルターは [table]_bool_exp に基づきます。
論理演算子
_and: [bool_exp]、_or: [bool_exp]、_not: bool_exp
リレーションシップフィルター
リレーションシップのフィールド名を直接指定して、関連付けをたどります。値はネストされた [related_table]_bool_exp オブジェクトです。
- 1 対 1 のリレーションシップ(1:1、N:1):単一の関連レコードのフィールドを基準に親レコードをフィルターします。
(例:作成者の名前が「John」である投稿を検索する場合:
author: { name: { _eq: ... } }) - 多対多のリレーションシップ(1:N、N:M):
EXISTSのセマンティクスを使用します。配列内の関連レコードのいずれかがネストされた条件に一致する場合、親レコードが返されます。 (例:「Tech」という名前のタグが少なくとも 1 つ付いた投稿を検索する場合:post_tags: { tag: { name: { _eq: ... } } })
比較述語(厳密なパターン)
Zion は厳密な演算子優先パターンを使用します。述語は演算子で始める必要があります。汎用演算子の場合、オペランドのラッパー型は、必ずしも列の型ではなく、最終的に評価される型と一致している必要があります。
構造:
{
"_operator": {
"operand_type": {
"left_operand": { ... },
"right_operand": { ... }
}
}
}
- 演算子:
- 比較(汎用):
_eq、_neq、_gt、_lt、_gte、_lte - 配列(汎用):
_in、_nin - NULL 判定(汎用、単項):
_is_null、_is_not_null - テキスト(文字列パターン):
_like、_nlike、_ilike、_nilike、_similar、_nsimilar - JSONB:
_contains、_contained_in、_has_key、_has_keys_any、_has_keys_all - ブール値:
_is_true、_is_false - コレクション:
_is_empty、_is_not_empty
- 比較(汎用):
- オペランドの定義(
left_operand/right_operand):- リテラル:
{"literal": value} - 列:
{"column": "field_name"} - 関数:
{"function_name": { ...args }}
- リテラル:
- オペランドの型(最終値によって決定):
bigint_operand、text_operand、boolean_operand、timestamptz_operandなど
述語の例(タイムスタンプから月を抽出し、値が 12 かどうかを確認する):
{
"_eq": {
"bigint_operand": {
"left_operand": {
"extract_timestamptz": { "time": { "column": "created_at" }, "unit": "MONTH" }
},
"right_operand": { "literal": "12" }
}
}
}
5. 集計、ウィンドウ関数、並べ替え
集計([table]_aggregate)
次の 2 つの主要なフィールドを含む集計クエリーオブジェクトを返します。
nodes:クエリーのwhere、limit、offset、order_byの条件に一致する生データ行を含む、実際のテーブルオブジェクト([table!]!)の配列です。aggregate:一致した行に対する統計計算を含むオブジェクトです。count(columns: [Enum], distinct: Boolean):- 列が指定されていない場合、すべての行を数えます(
COUNT(*))。 distinct: trueで 1 列以上が指定されている場合、指定された列の一意な値または一意な組み合わせを数えます。distinct: falseで複数の列が指定されている場合、システムは配列の最初の列のみを評価して数えるよう制限します。
- 列が指定されていない場合、すべての行を数えます(
sum、avg:数値列でのみ使用できます。max、min:比較可能な列(数値、時刻、テキスト)で使用できます。
ウィンドウ関数
ROW_NUMBER、RANK、DENSE_RANK、NTH_VALUE などの高度な分析処理は、特定の数式入力およびウィンドウフレーム入力でサポートされています。
並べ替え(order_by)
次の並べ替えに対応しています。
- 直接指定した列:
{ title: asc } - 関連する 1:1 レコード:
{ author: { name: desc } } - N:1 レコードの集計:
{ post_tags_aggregate: { count: desc } } - ベクトル検索:
TEXT_COLUMN_VECTOR_SORT拡張機能が適用されている場合、テキスト列で類似度による並べ替えがサポートされることがあります。
6. 数式関数(オペランド)
関数は、引数を大文字の関数名で囲むことで、オペランドのラッパー内で使用します(例:{"EXTRACT_TIMESTAMPTZ": { "time": ..., "unit": ... }})。
入力制約とセマンティクス
- [TYPE]:指定された型の列またはスカラーオペランドが必要です。
- [TYPE?]:オペランドは省略可能です(通常、デフォルト値は
0またはnullです)。 - [ANY]:任意の型の列またはスカラーオペランドを受け付けます。
- [NUMERIC]:
BIGINT、INTEGER、DECIMAL、FLOAT8、またはBIGSERIALを受け付けます。 - [COMPARABLE]:
NUMERIC型、TEXT、DATE、TIMESTAMPTZ、TIMETZ、またはINTERVALを受け付けます。 - [ANY[]]:任意の型の配列オペランドを受け付けます。
- [Enum:NAME]:[NAME] の定義に一致する明示的な Enum 値が必要です(例:
[Enum:DATE_UNIT]にはYEAR、MONTHなどが必要です)。 - ネストされた関数:出力型が想定される入力型と一致する場合、引数には多くの場合、他の関数の出力を指定できます。
操作
CONCAT(items: [TEXT[]])SUBSTRING(source_text: [TEXT], start_index: [BIGINT], end_index: [BIGINT])LEFT(source_text: [TEXT], length: [BIGINT])RIGHT(source_text: [TEXT], length: [BIGINT])LOWER(source_text: [TEXT])UPPER(source_text: [TEXT])TRIM(text: [TEXT])TRIM_TRAILING_ZERO(source_text: [TEXT])REPEAT(text: [TEXT], times: [BIGINT])ENCODE_URL(text: [TEXT])DECODE_URL(text: [TEXT])ARRAY_CONCAT(first_array: [ANY[]], second_array: [ANY[]])SLICE(array: [ANY[]], start_index: [BIGINT], length: [BIGINT])UNIQUE(array: [ANY[]])COALESCE(array: [ANY[]])
検索と置換
REPLACE_OCCURRENCES(source_text: [TEXT], search_text: [TEXT], replace_text: [TEXT], max_replacements: [BIGINT])REPLACE_AT_POSITION(source_text: [TEXT], start_index: [BIGINT], length: [BIGINT], replace_text: [TEXT])POSITION(source_text: [TEXT], search_text: [TEXT])CONTAINS(source_text: [TEXT], search_text: [TEXT])
正規表現
REGEX_EXTRACT(text: [TEXT], regex: [TEXT])REGEX_REPLACE(text: [TEXT], regex: [TEXT], replacement: [TEXT])REGEX_EXTRACT_ALL(text: [TEXT], regex: [TEXT])REGEX_MATCH(text: [TEXT], regex: [TEXT])
書式設定とユーティリティ
TEXT_DECIMAL_FORMAT(number: [DECIMAL], fraction_digits: [BIGINT], rounding_mode: [Enum:ROUNDING_MODE], clear_trailing_zeros: [BOOLEAN])NUMBER_FORMAT(number: [DECIMAL], fraction_digits: [BIGINT], format: [Enum:NUMBER_FORMAT])STRING_LEN(source_text: [TEXT])RANDOM_TEXT(min_length: [BIGINT], max_length: [BIGINT], include_numbers: [BOOLEAN], include_lower_case: [BOOLEAN], include_upper_case: [BOOLEAN])UUID()JOIN(array: [TEXT[]], separator: [TEXT])SPLIT(source_text: [TEXT], delimiter: [TEXT])ARRAY_LEN(array: [ANY[]])
算術
ADD(value0: [NUMERIC], value1: [NUMERIC])SUBTRACT(minuend: [NUMERIC], subtrahend: [NUMERIC])MULTIPLY(value0: [NUMERIC], value1: [NUMERIC])DIVIDE(dividend: [DECIMAL], divisor: [DECIMAL])MODULO(dividend: [NUMERIC], divisor: [NUMERIC])ABS(number: [NUMERIC])POW(base: [NUMERIC], exponent: [NUMERIC])LOG(base: [DECIMAL], argument: [DECIMAL])
丸めと書式
ROUND_UP(number: [DECIMAL])ROUND_DOWN(number: [DECIMAL])DECIMAL_FORMAT(number: [DECIMAL], fraction_digits: [BIGINT], rounding_mode: [Enum:ROUNDING_MODE])
生成関数
RANDOM_BIGINT(min_length: [BIGINT], max_length: [BIGINT])SEQUENCE(start: [BIGINT], end: [BIGINT], step: [BIGINT])
現在時刻
CURRENT_DATE()CURRENT_TIMETZ()CURRENT_TIMESTAMPTZ()
Constructors
MAKE_DATE(years: [BIGINT], months: [BIGINT], days: [BIGINT])MAKE_TIMETZ(hours: [BIGINT?], minutes: [BIGINT?], seconds: [BIGINT?], milliseconds: [BIGINT?])MAKE_TIMESTAMPTZ(years: [BIGINT], months: [BIGINT], days: [BIGINT], hours: [BIGINT?], minutes: [BIGINT?], seconds: [BIGINT?], milliseconds: [BIGINT?])MAKE_INTERVAL(years: [BIGINT], months: [BIGINT], weeks: [BIGINT], days: [BIGINT], hours: [BIGINT], minutes: [BIGINT], seconds: [BIGINT], milliseconds: [BIGINT])FROM_DATE_AND_TIMETZ(date: [DATE], timetz: [TIMETZ])
Extraction & Formatting
EXTRACT_DATE(time: [DATE], unit: [Enum:DATE_UNIT])EXTRACT_TIMETZ(time: [TIMETZ], unit: [Enum:TIME_UNIT])EXTRACT_TIMESTAMPTZ(time: [TIMESTAMPTZ], unit: [Enum:TIMESTAMP_UNIT])DATE_FORMAT(time: [DATE], format: [TEXT], language: [Enum:LANGUAGE])TIMETZ_FORMAT(time: [TIMETZ], format: [TEXT], language: [Enum:LANGUAGE])TIMESTAMPTZ_FORMAT(time: [TIMESTAMPTZ], format: [TEXT], language: [Enum:LANGUAGE])DURATION_FORMAT(duration: [DECIMAL], unit: [Enum:DURATION_UNIT], format: [TEXT])RELATIVE_DATE(time: [DATE], language: [Enum:LANGUAGE], hide_suffix: [BOOLEAN])RELATIVE_TIMESTAMPTZ(time: [TIMESTAMPTZ], language: [Enum:LANGUAGE], hide_suffix: [BOOLEAN])
Calculations & Deltas
DELTA_DATE(date: [DATE], increase: [BOOLEAN], years: [BIGINT?], months: [BIGINT?], days: [BIGINT?])DELTA_TIMETZ(timetz: [TIMETZ], increase: [BOOLEAN], hours: [BIGINT?], minutes: [BIGINT?], seconds: [BIGINT?], milliseconds: [BIGINT?])DELTA_TIMESTAMPTZ(timestamptz: [TIMESTAMPTZ], increase: [BOOLEAN], years: [BIGINT?], months: [BIGINT?], days: [BIGINT?], hours: [BIGINT?], minutes: [BIGINT?], seconds: [BIGINT?], milliseconds: [BIGINT?])EXTRACT_DATE_DURATION(start_time: [DATE], end_time: [DATE], unit: [Enum:DATE_UNIT])EXTRACT_TIMETZ_DURATION(start_time: [TIMETZ], end_time: [TIMETZ], unit: [Enum:TIME_UNIT])EXTRACT_TIMESTAMPTZ_DURATION(start_time: [TIMESTAMPTZ], end_time: [TIMESTAMPTZ], unit: [Enum:TIMESTAMP_UNIT])
Conversions
FROM_TIMESTAMPTZ_TO_DATE(timestamptz: [TIMESTAMPTZ])FROM_TIMESTAMPTZ_TO_TIMETZ(timestamptz: [TIMESTAMPTZ])
Geography
FROM_COORDINATES(latitude: [DECIMAL], longitude: [DECIMAL])GEO_DISTANCE(point0: [GEO_POINT], point1: [GEO_POINT], unit: [Enum:GEO_DISTANCE_UNIT])GEO_LONGITUDE(geo: [GEO_POINT])GEO_LATITUDE(geo: [GEO_POINT])
Aggregates
SUM(array: [NUMERIC[]])AVG(array: [NUMERIC[]])MAX(value0: [COMPARABLE], value1: [COMPARABLE])MIN(value0: [COMPARABLE], value1: [COMPARABLE])GREATEST(array: [COMPARABLE[]])LEAST(array: [COMPARABLE[]])
Element Access
ITEM(array: [ANY[]], index: [BIGINT])FIRST_ITEM(array: [ANY[]])LAST_ITEM(array: [ANY[]])RANDOM_ITEM(array: [ANY[]])ARRAY_POSITION(array: [ANY[]], item: [ANY])
JSONB
JSON_EXTRACT_BY_DOT_NOTATION_JSONPATH(json: [JSONB], path: [TEXT])
Casting
CAST_FROM_TEXT(value: [TEXT])CAST_COLUMN_TO_TEXT(value: [ANY])CAST_ARRAY_TO_TEXT(value: [ANY[]])CAST_TO_BIGINT(value: [ANY])CAST_TO_DECIMAL(value: [ANY])
Vector Search
EMBEDDING_VECTOR_DISTANCE(embedded_text_column: [Enum:EMBEDDING_COLUMN], text: [TEXT], distance_function: [Enum:VECTOR_DISTANCE])
System
NULL_VALUE()
明示的な列挙型の定義
引数で[Enum:NAME]が必要な場合は、以下の正確な文字列値のいずれかを使用する必要があります。
- [Enum:DATE_UNIT]:
YEAR, MONTH, DAY, WEEK - [Enum:DURATION_UNIT]:
DAY, HOUR, MINUTE, SECOND, MILLISECOND - [Enum:EMBEDDING_COLUMN]:
(Dynamically generated based on columns with TEXT_COLUMN_VECTOR_SORT extension) - [Enum:GEO_DISTANCE_UNIT]:
METER, KILOMETER, MILE - [Enum:LANGUAGE]:
EN, ZH - [Enum:NUMBER_FORMAT]:
THOUSANDS_SEPARATOR, PERCENT - [Enum:ROUNDING_MODE]:
HALF_EVEN, HALF_UP, HALF_DOWN, UP, DOWN, CEILING, FLOOR - [Enum:TIMESTAMP_UNIT]:
YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND, WEEK - [Enum:TIME_UNIT]:
HOUR, MINUTE, SECOND, MILLISECOND - [Enum:VECTOR_DISTANCE]:
EUCLIDEAN, COSINE
Actionflow プロトコル
概要
zion-app.functorz.com はフロントエンドから開始できる直接的な CRUD 操作をすでにサポートしていますが、バックエンドの操作の多くは複数のステップからなり、実行に時間がかかる場合があり、非同期で実行する必要があることもあります。そのため、zion-app.functorz.com はこうしたシナリオ向けに actionflow もサポートしています。actionflow は、actionflow ノードで構成される有向非巡回グラフです。これらのノードは、操作(データベースへの挿入、別の actionflow の呼び出しなど)または制御フローの変更(条件分岐やループ)を表します。actionflow には、actionflow 全体の引数と戻り値を定義する input ノードと output ノードという、2 つの特殊なノードもあります。
actionflow の動作モードには、同期と非同期の 2 種類があります。同期 actionflow は単一のデータベーストランザクション内で実行されるため、予期しないエラーが発生すると、データベースへのすべての変更がロールバックされます。同期 actionflow には、データベース接続を占有し続けないよう、実行時間の制限があります。非同期 actionflow は各ノードを新しいデータベーストランザクション内で実行するため、ロールバックの仕組みはありませんが、長時間の HTTP 呼び出し、特に数分かかることもある LLM API への呼び出しなど、実行に時間がかかるタスクに適しています。actionflow 内では、zion-app.functorz.com に組み込まれた AI エージェントを呼び出すノードは、非同期 actionflow 内にのみ追加できます。
Actionflow の呼び出しプロセス
actionflow を呼び出すには、その ID、引数のリスト、および任意のバージョンを取得する必要があります。これらはプロジェクトのスキーマ内にあります。 actionflow の呼び出し方法は、その種類によって異なります。
同期 actionflow
同期 actionflow は通常の GraphQL ミューテーションを介して呼び出せます。結果は同じ HTTP リクエストのレスポンスで返されます。 リクエスト:
mutation someOperationName ($args: Json!) {
fz_invoke_action_flow(actionFlowId: "d3ea4f95-5d34-46e1-b940-91c4028caff5", versionId: 3, args: $args)
}
{
"args": {
"yaml": "post_link:\n url: \"https://zion-app.functorz.com\"\n",
"img_id": 1020000000000111
}
}
このクエリでは、$args は actionflow の input ノードに記載された引数に対応します。 レスポンス:
{
"data": {
"fz_invoke_action_flow": {
"img": {
"id": 1020000000000090,
"url": "https://fz-zion-static.functorz.com/202510252359/a64a7eb4793728a1977d3ea9e7b7e4e8/project/2000000000521152/import/1110000000000001/image/636.jpg"
},
"url": "https://zion-app.functorz.com"
}
}
}
非同期 actionflow
非同期 actionflow は GraphQL ミューテーションを介して実行されますが、結果は同じ HTTP リクエストのレスポンスでは返されません。代わりに、対応するタスクの ID を含む fz_create_action_flow_task が返されます。この ID を使用して、別の GraphQL サブスクリプションで結果を購読します。 ミューテーションリクエスト:
mutation mh49tgie($args: Json!) {
fz_create_action_flow_task(actionFlowId: "2a9068c5-8ee3-4dad-b3a4-5f3a6d365a2f", versionId: 4, args: $args)
}
{
"args": {
"int": 123,
"img_id": 1020000000000116,
"some_text": "Dreamer",
"datetime_with_timezone": "2025-10-23T20:13:00-07:00"
}
}
ミューテーションレスポンス: { "data": { "fz_create_action_flow_task": 1150000000000148 } }
サブスクリプションリクエスト:
subscription fz_listen_action_flow_result($taskId: Long!) {
fz_listen_action_flow_result(taskId: $taskId) {
__typename"
output
status
}
}
{ "taskId" : 1150000000000148 }
サブスクリプションレスポンス:
{
"data": {
"fz_listen_action_flow_result": {
"__typename": "ActionFlowTaskResult",
"output": {
"img": {
"id": 1020000000000089,
"url": "https://fz-zion-static.functorz.com/202510262359/3a5f04371bf68d6c94bb890879101f0a/project/2000000000521152/import/1110000000000001/image/637.jpg"
},
"xyz": {
"type": "Point",
"coordinates": [
131,
22
]
}
},
"status": "COMPLETED"
}
}
}
最終結果("output" 内)が返される前に、GraphQL サブスクリプションから複数のメッセージが送信される場合があり、それぞれ異なるステータス値を含むことがあります。 status フィールドには、以下の遷移ルールがあります。
switch (status) {
case CREATED -> Set.of(PROCESSING);
case PROCESSING -> Set.of(COMPLETED, FAILED);
default -> Set.of();
};
AI エージェントプロトコル
概要
Zion.app には統合型の AI エージェントビルダーがあり、マルチモーダル(テキスト、動画、画像)の入力と出力、プロンプトテンプレート、コンテキストの取得(データベースおよびサードパーティ API 経由)、ツールの使用(アクションフロー、サードパーティ API、その他の AI エージェント)、構造化出力(対応する JSONSchema に基づく JSON)をサポートしています。
AI エージェントの結果は、出力の設定に応じて GraphQL サービスから異なる方法で返されます。具体的には、ストリーミング出力かどうか、および構造化出力かどうかによって異なります。構造化出力はストリーミングできませんが、プレーンテキストはストリーミングの有無を選択できます。構造化出力には、JSON の型を記述する JSONSchema を指定する必要があります。
AI エージェントを呼び出すには、プロジェクトスキーマから ID と入力引数を取得する必要があります。Zion.app のエージェントビルダーで作成した AI エージェントは、GraphQL API 経由で非同期にのみ呼び出せます。
ストリーミング出力の呼び出しプロセス
このプロセスを説明するために、出力がストリーミング形式のプレーンテキストである AI エージェントの設定例を使用します。設定は次のとおりです。
{
"id": "mgzzu8jp",
"summary": "An example summary of what the agent does",
"inputs": {
"mgzzufo2": {
"type": "VIDEO",
"displayName": "the_video",
},
"mh4cjjcf": {
"type": "TEXT",
"displayName": "text",
},
"mh4cjkyv": {
"type": "BIGINT",
"displayName": "some_int",
},
"mh4cjoof": {
"type": "array",
"itemType": "IMAGE",
"displayName": "images",
}
},
"output": "Unstructured Text"
}
- AI エージェントを開始するためのミューテーションを送信し、引数を inputArgs として、ID を zAIConfigId として渡します。レスポンス値には、対応する会話の ID のみが含まれます。inputArgs のキーは、スキーマの inputs オブジェクトにあるキーと一致している必要があります。画像、動画、その他のバイナリーアセット型の入力パラメーター、およびこれらの型の配列は、少し異なる方法で処理されます。inputArgs オブジェクト内のキー名には
_idサフィックスが付きます。例として、次の設定の場合
{
"inputs": {
"mgzzufo2": {
"type": "VIDEO",
"displayName": "the_video",
}
}
}
次に対応します。
{
"inputArgs": {
"mgzzufo2_id": 1030000000000002,
}
}
ミューテーションリクエスト:
クエリ:
```gql
mutation ZAICreateConversation($inputArgs: Map_String_ObjectScalar!, $zaiConfigId: String!) {
fz_zai_create_conversation(inputArgs: $inputArgs, zaiConfigId: $zaiConfigId)
}
```
変数:
```json
{
"inputArgs": {
"mgzzufo2_id": 1030000000000002,
"mh4cjjcf": "ほんの少しのテキスト",
"mh4cjkyv": 23,
"mh4cjoof_id": [
1020000000000097,
1020000000000111,
1020000000000120
]
},
"zaiConfigId": "mgzzu8jp"
}
```
ミューテーションレスポンス:
```json
{
"data": {
"fz_zai_create_conversation": 1480
}
}
```
2. 取得した会話 ID を使用して、前回の AI エージェント呼び出しの結果をサブスクライブします。複数のメッセージを受信する場合があります。メッセージのステータスは IN_PROGRESS から STREAMING へ遷移し、最終的に COMPLETED になります。最後のメッセージは常に COMPLETED ステータスを示し、その data フィールドには、以前のすべての STEAMING メッセージの data フィールドの出力を統合した内容が含まれます。
推論内容を出力するモデルの場合、実際の出力と同様に動作します。つまり、部分的な推論内容が reasoningContent フィールドに複数のメッセージとして先に出力され、すべての準備が整うと、reasoningContent の全内容が COMPLETED メッセージで再度出力されます。
サブスクリプションリクエスト:
クエリ:
gql subscription ZaiListenConversationResult($conversationId: Long!) { fz_zai_listen_conversation_result(conversationId: $conversationId) { conversationId status reasoningContent images { id __typename } data __typename } }
変数:
json { "conversationId": 1480 }
サブスクリプションのレスポンスメッセージ:
json { "data": { "fz_zai_listen_conversation_result": { "__typename": "ConversationResult", "conversationId": 1480, "data": null, "images": null, "reasoningContent": null, "status": "IN_PROGRESS" } } }
json { "data":{ "fz_zai_listen_conversation_result": { "__typename": "ConversationResult", "conversationId": 1480, "data": "このコレクションには、3 枚の画像と短い動画が含まれています。2 枚の写真には、有名な中国のコメディアン兼俳優、趙本山が写っています。3 枚目の", "images": null, "reasoningContent": null, "status": "STREAMING" } } }
json { "data":{ "fz_zai_listen_conversation_result": { "__typename": "ConversationResult", "conversationId": 1480, "data": "画像は「SHOHOKU」のバスケットボールジャージを着た若い女性のアニメイラストで、『SLAM DUNK』シリーズの赤木晴子に似ています。", "images": null, "reasoningContent": null, "status": "STREAMING" } } }
json { "data":{ "fz_zai_listen_conversation_result": { "__typename": "ConversationResult", "conversationId": 1480, "data": "このコレクションには、3 枚の画像と短い動画が含まれています。2 枚の写真には、有名な中国のコメディアン兼俳優、趙本山が写っています。3 枚目の画像は「SHOHOKU」のバスケットボールジャージを着た若い女性のアニメイラストで、『SLAM DUNK』シリーズの赤木晴子に似ています。", "images": null, "reasoningContent": null, "status": "COMPLETED" } } }
ストリーミングなしのプレーンテキスト出力の呼び出しプロセス
-
ミューテーションの手順は、ストリーミング出力の呼び出しプロセスと同じです。つまり、ミューテーション fz_zai_create_conversation(inputArgs: $inputArgs, zaiConfigId: $zaiConfigId) を送信して、会話 ID を取得します。
-
STREAMING 状態のメッセージはありません。つまり、サーバーから IN_PROGRESS ステータスのメッセージが送信され、その直後に最終結果を含む COMPLETED メッセージが送信されます。
画像出力モデルを使用する AI エージェントの呼び出しプロセス
gemini-2.5-flash-image のように、画像出力に対応したモデルもあります。
これらの呼び出しプロセスは、プレーンテキストの場合と同じです。ただし、COMPLETED メッセージでは images フィールドにコンテンツが格納されます。画像
ストリーミングの設定にかかわらず、出力はプレーンテキストのみの場合と変わりません。COMPLETED メッセージは次のようになります。
{
"data": {
"fz_zai_listen_conversation_result": {
"__typename": "ConversationResult",
"conversationId": 1494,
"data": "I merged the three images into one, combining elements from each to create a new, unique image.\n",
"images": [
{
"__typename": "FZ_Image",
"id": 1020000000000164
}
],
"reasoningContent": null,
"status": "COMPLETED"
}
}
}
images 内の FZ_Image の ID は、Zion のファイル/アセットシステムにおける ID を表します。zion-binary-asset-upload-rules を参照してください。
構造化出力の呼び出しプロセス
構造化出力を行う AI エージェントは、ストリーミングに対応していません。また、設定には必ず JSONSchema が含まれます。
{
"output": {
"type": "object",
"properties": {
"httpLink": {
"type": "string"
},
"reasoning": {
"type": "string"
}
},
"required": [
"httpLink",
"reasoning"
]
}
}
GraphQL サーバーから「STREAMING」状態のメッセージが送信されることはありません。data フィールドに JSONSchema を満たす JSON が格納された「COMPLETED」メッセージが 1 つ送信されます。 例:
{
"httplink": "https://www.google.com/calendar/event?eid=MTcxN2U3cHAzaDFtYTdxYzd0bGV0aHNvYmsgamlhbmd5YW9rYWlqb2huQG0",
"explanation": "No existing events were found on 2025-10-24 in America/Los_Angeles, so there are no conflicts. Preference is mornings; scheduled 08:00–08:10 at Los Altos High school. With no adjacent events, transit checks to previous and next events are trivially satisfied."
}
会話の継続
AI エージェントが結果を返した後(status = COMPLETED)、fz_zai_send_ai_message を呼び出すことで会話を継続できます。
同じ conversationId に対する fz_zai_listen_conversation_result のサブスクリプションは、引き続きメッセージを受信します。
例:
ミューテーションリクエスト:
mutation continue($conversationId: Long!, $text: String) {
fz_zai_send_ai_message(conversationId: $conversationId, text: $text)
}
Variables:
{
"conversationId": 1480,
"text": "make it about the sun"
}
対応する fz_zai_listen_conversation_result からのレスポンスは、その後も引き続き送信されます。AI エージェントとの会話を開始したときと同様に、IN_PROGRESS -> (STREAMING) -> COMPLETED のステータス遷移をたどります。
会話の停止
「IN_PROGRESS」または「STREAMING」状態の会話は、fz_zai_stop_responding を呼び出して停止できます。この呼び出しは常に true を返します。
「COMPLETED」状態の会話に対して呼び出すと、GraphQL レスポンスの errors フィールド内で 400 エラーがスローされます。
例:
ミューテーションリクエスト:
mutation continue($conversationId: Long!) {
fz_zai_stop_responding(conversationId: $conversationId)
}
Variables:
{
"conversationId": 1480
}
バイナリアセットのアップロード
すべてのバイナリアセット(画像、動画、ファイル)は、オブジェクトストレージサービス(例:S3)に保存されます。保存先のパスは Zion のデータベースに記録されます。他のテーブルでこれらのアセットを参照する場合、保存するのはアセットのパスや URL ではなく、Zion ID のみにしてください。
アップロードワークフロー
バイナリアセットをアップロードして Zion ID を取得するには、次の厳密な 2 段階の手順に従う必要があります。
ステップ 1:署名付きアップロード URL を取得する
-
ファイルの MD5 ハッシュ(生の 128 ビットハッシュ)を計算し、Base64 エンコードします。
-
署名付きアップロード URL をリクエストする適切な GraphQL ミューテーションを呼び出します。アセットの種類に応じたミューテーションを使用してください。
- 画像には
imagePresignedUrl - 動画には
videoPresignedUrl - その他のファイルには
filePresignedUrl
次の値を指定します。
- Base64 エンコードされた MD5 ハッシュ
- ファイル形式/拡張子(下記の
MediaFormatを参照) - (任意)アクセス制御(下記の
CannedAccessControlListを参照)
GraphQL ミューテーションの例
mutation GetImageUploadUrl($md5: String!, $suffix: MediaFormat!, $acl: CannedAccessControlList) { imagePresignedUrl(imgMd5Base64: $md5, imageSuffix: $suffix, acl: $acl) { imageId uploadUrl uploadHeaders } } mutation GetVideoUploadUrl($md5: String!, $format: MediaFormat!, $acl: CannedAccessControlList) { videoPresignedUrl(videoMd5Base64: $md5, videoFormat: $format, acl: $acl) { videoId uploadUrl uploadHeaders } } mutation GetFileUploadUrl($md5: String!, $format: MediaFormat!, $name: String, $suffix: String, $sizeBytes: Int, $acl: CannedAccessControlList) { filePresignedUrl( md5Base64: $md5 format: $format name: $name suffix: $suffix sizeBytes: $sizeBytes acl: $acl ) { fileId uploadHeaders uploadUrl } }CannedAccessControlList(推奨:PRIVATE):- AUTHENTICATE_READ, AWS_EXEC_READ, BUCKET_OWNER_FULL_CONTROL, BUCKET_OWNER_READ, DEFAULT, LOG_DELIVERY_WRITE, PRIVATE, PUBLIC_READ, PUBLIC_READ_WRITE
MediaFormat:- CSS, CSV, DOC, DOCX, GIF, HTML, ICO, JPEG, JPG, JSON, MOV, MP3, MP4, OTHER, PDF, PNG, PPT, PPTX, SVG, TXT, WAV, WEBP, XLS, XLSX, XML
- 画像には
ステップ 2:ファイルをアップロードして返された ID を使用する
- ミューテーションのレスポンスには、以下が含まれます。
- アセット固有の ID(
imageId、videoId、またはfileId) - 署名付きの
uploadUrl - 必要な
uploadHeaders
- アセット固有の ID(
- ファイルをアップロードする:
- 生のファイルデータを指定して、
uploadUrlに対して HTTPPUTリクエストを実行します - ミューテーションのレスポンスに含まれる
uploadHeadersを指定します
- 生のファイルデータを指定して、
- アセットを参照する:
- 返された ID を、Zion のデータミューテーションで対応する
*_idフィールドの値として使用します(例:cover_image_id: returnedImageId)
- 返された ID を、Zion のデータミューテーションで対応する
注記: この 2 段階のプロセスは、Zion.app でのすべてのメディアアップロードで必須です。
サードパーティ API
Zion.app で構築されたプロジェクトには、多数のサードパーティ HTTP API をインポートできます。これらは大まかに(常に当てはまるわけではありませんが)HTTP GET と POST のセマンティクスに対応する、クエリとミューテーションの 2 つのカテゴリーに分けられます。
各 API は、次のデータ構造に保存されます。
type ScalarType = 'string' | 'boolean' | 'number' | 'integer';
type TypeDefinition =
| ScalarType
| { [key: string]: TypeDefinition | TypeDefinition[] };
interface ThirdPartyApiConfig {
id: string;
name: string;
operation: 'query' | 'mutation';
inputs: { [key: string]: TypeDefinition };
outputs: { [key: string]: TypeDefinition };
}
注:ThirdPartyApiConfig 内の operation フィールドの値によって、ルート GraphQL フィールドが決まります。つまり、query の場合は query operation_${id}、mutation の場合は mutation operation_${id} です。
呼び出しプロセス
ユーザーから削除の指示がない限り、各入力を指定する必要があります。
例:
次の TPA 設定の場合:
{
"id": "lzb3ownk",
"inputs": {
"body": {
"summary": "string",
"location": "string",
"description": "string",
"start": {
"dateTime": "string",
"timeZone": "string"
},
"end": {
"dateTime": "string",
"timeZone": "string"
},
"attendees": [
"string"
]
},
"Authorization": "string"
},
"outputs": {
"body": {
"kind": "string",
"etag": "string",
"id": "string",
"status": "string",
"htmlLink": "string",
"created": "string",
"updated": "string",
"summary": "string",
"description": "string",
"location": "string",
"creator": {
"email": "string",
"self": "boolean"
},
"organizer": {
"email": "string",
"self": "boolean"
},
"start": {
"dateTime": "string",
"timeZone": "string"
},
"end": {
"dateTime": "string",
"timeZone": "string"
},
"iCalUID": "string",
"sequence": "number",
"reminders": {
"useDefault": "boolean"
},
"eventType": "string"
}
},
"operation": "mutation"
}
対応する GraphQL クエリは次のとおりです。
mutation request_${nonce}($summary: String, $location: String, $description: String, $start_dateTime: String, $start_timeZone: String, $end_dateTime: String, $end_timeZone: String, $attendees:[String], $Authorization: String) {
operation_lzb3ownk(fz_body: {}, arg1: $_1, arg2: $_2) {
responseCode
field_200_json {
{subFieldSelections}
}
}
}
field_200_json は、サードパーティ API から派生するすべての GraphQL 操作で共通の固定フィールドです。これは、すべての 2xx レスポンスコードで有効なレスポンスを表します。
responseCode サブフィールドは常に確認してください。5xx または 4xx のコードが返された場合、field_200_json が空になるためです。