クックブック (実践例集)
最終更新: 2026年7月17日
さまざまなシナリオを完結した ScriptDefinition として示します。文法の根拠は Statement カタログ と 値式、実行と制約は 実行セマンティクス、制約、セキュリティ を参照してください。すべての例は書き込み fields の値がロケールマップ({ "<locale>": 値 })であり、例のロケールは en-US に統一しています。外部呼び出し(Http)や Media ファイルインジェストを含む例は executionMode が "Async" です。
目次
- 基本 CRUD: 1. Content の作成と公開 · 2. 計算値で update · 3. 読み取り専用 GET · 4. 単件取得後に guard して承認
- 検索と upsert: 5. slug upsert · 6. 動的フィールドキーとロケール patch
- 外部 API: 7. クレジット先行差し引き(CAS)と LLM 呼び出し・返金 · 8. 画像 URL を Media に · 9. base64 画像を Media に · 10. モデレーション後に条件付き処理 · 11. try/catch fallback
- 並列: 12. 並列呼び出し後に統合 · 13. 加入審査
- 反復と集計: 14. 配列入力で N 件作成 · 15. counted loop シード · 16. cascade 削除 · 17. ループ累積合計 · 18. 全ページネーション走査 · 19. id バッチ収集
- サガと並行性: 20. 決済サガ · 21. 楽観ロック CAS
基本 CRUD
1. Content の作成と公開
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"fields": { "title": { "en-US": "{ /payload/fields/title }" }, "body": { "en-US": "{ /payload/fields/body }" } },
"publish": true, "name": "post" },
{ "type": "Return", "value": { "id": "{ /post/sys/id }" }, "statusCode": 201 } ] }2. 計算値で update (閲覧数 +1)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "+": [ "{ /payload/fields/viewCount }", 1 ] } } } } ] }3. 読み取り専用 GET: 自分の注文一覧
{ "method": "Get", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"where": { "createdBy": ":self" }, "order": "-sys.createdAt", "limit": 20, "name": "orders" },
{ "type": "Return", "value": { "orders": "{ /orders/items }", "next": "{ /orders/next }" } } ] }Script は書き込みだけでなく 読み取り BFF エンドポイント としても使います。createdBy: ":self" で「自分のものだけ」を取得し、結果をそのまま返します。
4. 単件取得後に guard して承認
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceRead", "resource": "Content", "target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" },
{ "type": "If", "condition": { "!=": [ "{ /order/fields/status/en-US }", "pending" ] },
"then": [ { "type": "Return", "value": { "reason": "not pending" }, "isError": true, "statusCode": 409 } ] },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "approved" } }, "publish": true },
{ "type": "Return", "value": { "ok": true } } ] }ResourceRead で単件を名前にバインドすると { /order/fields/... } で直接参照できます(items/0 は不要)。存在しなければ取得でエラーになります(Try で囲めます)。
検索と upsert
5. slug upsert (find-then-upsert)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" },
{ "type": "If", "condition": { "!!": "{ /found/sys/id }" },
"then": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /found/sys/id }" } },
"fields": { "body": { "en-US": "{ /payload/fields/body }" } } },
{ "type": "Return", "value": { "id": "{ /found/sys/id }", "op": "updated" } } ],
"else": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"fields": { "slug": { "en-US": "{ /payload/fields/slug }" }, "body": { "en-US": "{ /payload/fields/body }" } }, "name": "created" },
{ "type": "Return", "value": { "id": "{ /created/sys/id }", "op": "created" }, "statusCode": 201 } ] } ] }ResourceFind は最初のマッチを直接バインドし(なければ null)、{ "!!": "{ /found/sys/id }" } で存在有無を分岐します。
6. 動的フィールドキーと動的ロケール patch
{ "method": "Patch", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "{ /payload/fields/fieldKey }": { "{ /payload/fields/locale }": "{ /payload/fields/value }" } } } ] }フィールドの キー とロケールの バケットキー の両方が { /ptr } 参照です。翻訳を特定のロケールバケットに差し込むときに使います。
外部 API
7. クレジット guard、先行差し引き(CAS)、LLM 呼び出し、失敗時の返金 (代表例)
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_wallet" } },
"where": { "createdBy": ":self" }, "name": "wallet" },
{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "isError": true, "statusCode": 402 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"version": "{ /wallet/sys/version }",
"fields": { "balance": { "en-US": { "-": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } },
"name": "charged" } ],
"catch": [ { "type": "Return", "value": { "ok": false, "reason": "version conflict, 再試行" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
"headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 15000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/choices/0/message/content }" } }, "name": "out" },
{ "type": "Return", "value": { "ok": true, "id": "{ /out/sys/id }", "remaining": "{ /charged/fields/balance/en-US }" } } ],
"catch": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"fields": { "balance": { "en-US": { "+": [ "{ /charged/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } } },
{ "type": "Return", "value": { "ok": false, "reason": "generation failed, refunded" }, "isError": true, "statusCode": 502 } ] } ] }guard で残高が十分かを確認したうえで、外部呼び出しより先に差し引きます。差し引きは wallet の sys.version で楽観ロック(CAS)をかけます。読み取った残高と差し引きの間に別の実行が wallet を変更していた場合、バージョン不一致で abort し、catch が 409 を返します。外部呼び出しは行わないため、同時リクエストが二重に差し引かれることはありません。差し引きが確定した後にのみ LLM を呼び出し、その呼び出しが失敗した場合は catch で差し引き分(cost)を戻して加算し 返金(補償)したうえで 502 を返します。取り消せない外部呼び出しの前に課金を確定し、失敗したときだけ補償する順序です。秘密鍵は secret:true ヘッダーに置きます。補償の限界は 実行セマンティクスのトランザクションなしと補償 を参照してください。
8. 画像(URL)を Media にして Content に添付
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "title": { "en-US": "{ /payload/fields/prompt }" },
"file": { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } } }, "name": "img" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_artwork" } },
"fields": { "prompt": { "en-US": "{ /payload/fields/prompt }" }, "image": { "en-US": "{ /img/sys/id }" } } } ] }Media を name で作成し、ResourceCreate(Content) が { /img/sys/id } を参照フィールドに差し込みます。Media も Content と同じ fields モデルを使います。file はインジェスト指示 { source, encoding } です。ファイルインジェストなので Async です。
9. base64 画像を Media に
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "file": { "en-US": { "source": "{ /gen/body/data/0/b64_json }", "encoding": "base64" } } } } ] }10. モデレーション後に条件付きで publish または削除
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.mod.com/check",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "text": "{ /payload/fields/body }" }, "name": "mod" },
{ "type": "If", "condition": { "==": [ "{ /mod/body/flagged }", true ] },
"then": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ],
"else": [ { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] } ] }11. try/catch: 外部失敗時に fallback
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://primary.api/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 8000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/text }" }, "source": { "en-US": "primary" } } } ],
"catch": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "生成に失敗しました" }, "error": { "en-US": "{ /error/message }" }, "source": { "en-US": "fallback" } } } ] } ] }並列
12. 並列の外部呼び出し 2 件を合わせて Content
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "GET", "url": "https://api.a.com/x", "name": "a" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.b.com/y", "name": "b" } ] ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_merged" } },
"fields": { "left": { "en-US": "{ /a/body/value }" }, "right": { "en-US": "{ /b/body/value }" } } } ] }13. 加入審査: 並列スコア後に and 判定
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "POST", "url": "https://api.fraud.com/score",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "email": "{ /payload/fields/email }" }, "name": "fraud" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.credit.com/v1/{ /payload/fields/userId }/score",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ], "name": "credit" } ] ] },
{ "type": "If",
"condition": { "and": [ { "<": [ "{ /fraud/body/risk }", 0.5 ] }, { ">=": [ "{ /credit/body/score }", 700 ] } ] },
"then": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"fields": { "email": { "en-US": "{ /payload/fields/email }" }, "status": { "en-US": "approved" } } },
{ "type": "Return", "value": { "decision": "approved" }, "statusCode": 201 } ],
"else": [ { "type": "Return", "value": { "decision": "manual-review" }, "statusCode": 202 } ] } ] }URL パスにも { /ptr } を挿入できます。ブランチの結果はジョイン後に参照します。外部呼び出しは 2 件です(3 件以下)。
反復と集計
14. 配列入力で N 件の Content (Loop over)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "Loop", "over": "{ /payload/fields/items }", "as": "item", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_item" } },
"fields": { "name": { "en-US": "{ /item/name }" }, "qty": { "en-US": "{ /item/qty }" } } } ] } ] }15. counted loop (for): スロットシード
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "Loop", "for": { "from": 1, "to": 5 }, "as": "i", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_slot" } },
"fields": { "index": { "en-US": "{ /i }" }, "status": { "en-US": "open" } } } ] } ] }for は from から to まで 含む 範囲です(整数リテラル、step はデフォルト 1)。as が現在のカウンターを { /i } にバインドします。
16. cascade 削除 (PageRead, Loop, Delete)
{ "method": "Delete", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
"where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "limit": 100, "name": "comments" },
{ "type": "Loop", "over": "{ /comments/items }", "as": "c", "maxIterations": 100,
"body": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /c/sys/id }" } } } ] },
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] }100 件を超える場合は 18. 全ページネーション走査 で処理します。
17. ループ累積: SetVar 合計
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "total", "value": 0 },
{ "type": "Loop", "over": "{ /payload/fields/items }", "as": "row", "maxIterations": 100,
"body": [ { "type": "SetVar", "var": "total", "value": { "+": [ "{ /vars/total }", "{ /row/qty }" ] } } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_summary" } },
"fields": { "totalQty": { "en-US": "{ /vars/total }" } } } ] }18. 全ページネーション走査
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "cursor", "value": null },
{ "type": "SetVar", "var": "hasMore", "value": true },
{ "type": "Loop", "while": "{ /vars/hasMore }", "maxIterations": 1000,
"body": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"where": { "fields.status": { "eq": "draft" } }, "limit": 100, "cursor": "{ /vars/cursor }", "name": "page" },
{ "type": "Loop", "over": "{ /page/items }", "as": "p", "maxIterations": 100,
"body": [ { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /p/sys/id }" } } } ] },
{ "type": "SetVar", "var": "cursor", "value": "{ /page/next }" },
{ "type": "SetVar", "var": "hasMore", "value": { "!!": "{ /page/next }" } } ] } ] }cursor と while、SetVar で全ページを走査します。外部呼び出しは Loop の body 内では禁止のため、ここではリソース操作のみを使います。
19. メールアドレス一覧で id をバッチ収集 (merge)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "ids", "value": [] },
{ "type": "SetVar", "var": "missing", "value": [] },
{ "type": "Loop", "over": "{ /payload/fields/emails }", "as": "email", "maxIterations": 100,
"body": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"where": { "fields.email": { "eq": "{ /email }" } }, "name": "acc" },
{ "type": "If", "condition": { "!!": "{ /acc/sys/id }" },
"then": [ { "type": "SetVar", "var": "ids", "value": { "merge": [ "{ /vars/ids }", [ "{ /acc/sys/id }" ] ] } } ],
"else": [ { "type": "SetVar", "var": "missing", "value": { "merge": [ "{ /vars/missing }", [ "{ /email }" ] ] } } ] } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_campaign" } },
"fields": { "recipients": { "en-US": "{ /vars/ids }" }, "unresolved": { "en-US": "{ /vars/missing }" } } } ] }読み取り(ResourceFind) は外部呼び出しではないため Loop の body で許可されます。存在と不在をそれぞれ merge で累積します。
サガと並行性
20. 決済サガ (Try/catch/finally)
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"fields": { "sku": { "en-US": "{ /payload/fields/sku }" }, "status": { "en-US": "reserved" } },
"publish": false, "name": "order" },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.pay.com/charge",
"headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
"body": { "amount": "{ /payload/fields/amount }", "ref": "{ /order/sys/id }" }, "timeoutMs": 10000, "name": "pay" },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "paid" }, "txId": { "en-US": "{ /pay/body/transactionId }" } }, "publish": true },
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 } ],
"catch": [
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } } },
{ "type": "Return", "value": { "reason": "payment failed", "detail": "{ /error/message }" }, "isError": true, "statusCode": 402 } ],
"finally": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_paylog" } },
"fields": { "orderRef": { "en-US": "{ /order/sys/id }" }, "amount": { "en-US": "{ /payload/fields/amount }" } } } ] } ] }予約(draft)後、決済成功時は確定と publish と 201、失敗時は catch が予約削除(補償)と 402、finally は常にログです。削除補償は新しい sys.id になるため参照が壊れる限界の範囲です(実行セマンティクスのトランザクションなしと補償 を参照)。
21. 楽観ロック CAS
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_stock" } },
"where": { "fields.sku": { "eq": "{ /payload/fields/sku }" } }, "limit": 1, "name": "stock" },
{ "type": "If", "condition": { "<": [ "{ /stock/items/0/fields/qty/en-US }", "{ /payload/fields/amount }" ] },
"then": [ { "type": "Return", "value": { "reason": "out of stock" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /stock/items/0/sys/id }" } },
"version": "{ /stock/items/0/sys/version }",
"fields": { "qty": { "en-US": { "-": [ "{ /stock/items/0/fields/qty/en-US }", "{ /payload/fields/amount }" ] } } } },
{ "type": "Return", "value": { "ok": true } } ],
"catch": [
{ "type": "Return", "value": { "reason": "version conflict, 再試行" }, "isError": true, "statusCode": 409 } ] } ] }在庫を読み取って新鮮な sys.version を確保したうえで、そのバージョンで差し引きます(version)。読み取りと書き込みの間に別の実行が値を変更していた場合、バージョン不一致で abort され、catch が 409 を返します。在庫不足の guard は Try の外です(正常な早期終了)。
関連ドキュメント
- Statement カタログ: 例で使われた文のフィールドと結果。
- 値式: 参照、JsonLogic、ロケールマップのルール。
- 実行セマンティクス、制約、セキュリティ: 実行順序、補償、楽観ロック、制約。
- Script 概要: 最上位の構造と実行モード。
