Statement 카탈로그
최종 수정: 2026년 7월 23일
statements 배열의 각 원소가 하나의 문(statement)입니다. 이 문서는 17종 문의 필드, 동작, 결과를 정리합니다. 모든 값 자리는 값 표현식의 규칙(참조, 리터럴, JsonLogic, 로케일 맵)을 따릅니다.
Statement 요약
| 분류 | type | 한 줄 요약 |
|---|---|---|
| 리소스 쓰기 | ResourceCreate | Content/Media 생성(선택적으로 발행) |
ResourceUpdate | Content/Media 필드 전체 교체(안 준 field·locale 삭제) | |
ResourcePatch | Content/Media 필드 부분 병합(지정 field·locale만. 리터럴 null은 삭제) | |
ResourceDelete | 삭제(Draft·Archived만. Published면 먼저 unpublish) | |
ResourcePublish / ResourceUnpublish | 발행 / 발행 취소 | |
ResourceArchive / ResourceUnarchive | 보관 / 보관 해제 | |
| 리소스 읽기 | ResourceRead | id로 단건 조회 |
ResourceFind | 필터로 첫 매치 단건(없으면 null) | |
ResourcePageRead | 필터/정렬/페이지 조회({ items, next }) | |
| 외부 | Http | 외부 HTTP 호출({ status, body }). Async 전용 |
| 변수 | SetVar | script-scoped 변수 선언/갱신 |
| 제어 흐름 | If | 조건 분기 |
Loop | 반복(foreach / while / counted) | |
Parallel | 브랜치 동시 실행 | |
Return | 결과 반환과 조기 종료 | |
Try | 예외 처리(catch/finally) |
순환 호출은 최대 3회. 위 리소스 쓰기 문(
ResourceCreate·ResourceUpdate·ResourcePublish등)은 변경 이벤트를 일으키고, 그 이벤트가 Webhook을 통해 다시 Script를 실행할 수 있습니다. 이렇게 이어지는 연쇄(Script → 이벤트 → Webhook → Script → …)는 최대 3회까지만 이어지고, 그 이상은 자동으로 끊겨 무한 순환을 막습니다.
공통 필드
{ "type": "<StatementType>", "name": "<선택, script 내 고유>", /* ...타입별 필드... */ }type: 판별자입니다. 위 표의 값 중 하나입니다(필수).name: 선택입니다. 붙이면 결과가/<name>으로 컨텍스트에 바인딩되어 이후 statement가{ /<name>/... }로 참조합니다. 결과를 안 쓰면 생략합니다.- 바인딩 이름 규칙:
name(그리고Loop의as)은 컨텍스트 루트에 그대로 얹히는 키라 저장 시 검증됩니다. 빈 문자열이 아니어야 하고/·~를 포함하지 않아야 하며(JSON Pointer 키로 쓸 수 있어야 함), 예약 루트(payload·vars·error)와 같을 수 없고, 한 Script 안에서 유일해야 합니다. 위반 시 각각WGL400033(형식)·WGL400032(예약어)·WGL400034(중복)로 저장이 거부됩니다.
엔티티 참조 형태
contentType, target 같은 엔티티 참조는 { "sys": { "id": <값표현식> } } 한 형태로 통일합니다. sys.id만 필요하고 대상 타입은 resource에서 추론합니다(sys.type과 sys.targetType은 생략).
contentType.sys.id는 보통 리터럴입니다(예:"ct_post").target.sys.id는 보통{ /ptr }값표현식입니다(런타임 resolve. 예:{ /payload/sys/id }).
resource
리소스 계열 문은 대상 종류를 resource: "Content" | "Media"로 지정합니다.
리소스 쓰기
모든 쓰기 문은 propagateEvents(기본 false)를 갖습니다. true로 두면 그 쓰기가 자신의 EntityEvent를 발행합니다(검색 색인, Webhook 등 후속 트리거). 기본은 발행 안 함(조용한 시스템 쓰기)입니다.
ResourceCreate
Content 또는 Media를 만듭니다. Content와 Media는 fields 모델을 공유하며, 값은 로케일 맵입니다.
| 필드 | 대상 | 설명 |
|---|---|---|
resource | 공통 | "Content" 또는 "Media" (필수) |
contentType | Content | 만들 Content Type({ sys: { id } }). Content일 때 필수 |
fields | 공통 | 필드 맵 { "<field>": { "<locale>": 값 } }. populate 필드마다 기본 로케일 버킷 필수. Content 키는 Content Type 정의를 따르고, Media 키는 고정(title·description·file) |
locale | 공통 | (편의) 주면 fields 각 값을 { <locale>: 값 }으로 자동 래핑 |
publish | 공통 | 쓰기 후 발행(CDA/ACDA 노출). 기본 true |
Mediafile:fields.file.{locale}값은 인제스트 지시{ "source": <값표현식>, "encoding": "url"|"base64" }입니다(둘 다 필수). 파일이 포함된 Media 쓰기는 Async 전용입니다(백그라운드에서 엔진이 인라인 처리 후 발행. url과 base64 공통). 파일 없는(fileless) Media도 만들 수 있습니다.publish:true인데 파일이 없거나 처리 미완이면 발행 단계에서 에러이고,publish:false면 그대로Draft입니다.- 결과(
name바인딩): 만들어진 리소스입니다.{ /<name>/sys/id },{ /<name>/fields/<field>/<locale> }.
// Content
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"fields": { "title": { "en-US": "{ /payload/fields/title }" } }, "publish": true, "name": "post" }
// Media. file은 인제스트 지시 (Async 전용)
{ "type": "ResourceCreate", "resource": "Media",
"fields": {
"title": { "en-US": "{ /payload/fields/prompt }" },
"file": { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } }
}, "name": "img" }ResourceUpdate
대상 Content나 Media의 필드를 전체 교체합니다(PUT). fields에 준 것이 그대로 새 필드가 되고 여기 없는 field와 locale은 지워집니다. 일부만 바꾸려면 ResourcePatch를 씁니다.
| 필드 | 설명 |
|---|---|
resource | "Content" 또는 "Media" |
target | 대상({ sys: { id } }, 필수). id는 보통 { /ptr } |
fields | 쓸 필드 전체. 값은 로케일 맵. 전체 교체이므로 여기 없는 field와 locale은 제거됩니다. Media file은 인제스트 지시(위 ResourceCreate 참조). 나열한 파일은 항상 재인제스트하고, 안 준 locale 파일은 삭제 |
locale | (편의) fields 자동 래핑 |
version | (선택) 값표현식(Int). 낙관적 잠금. 주면 대상의 현재 sys.version과 일치할 때만 갱신하고 불일치면 버전 충돌 에러로 abort합니다(Try로 catch 가능). 생략 시 검사 없음(last-write-wins) |
publish | 갱신 후 republish. 기본 true |
Media에서 metadata만 바꾸려고 Update를 쓰면 file이 빠져 파일이 전부 삭제됩니다(전체 교체이므로). 부분 변경은 반드시 ResourcePatch를 씁니다. 파일이 포함된 Update는 Async 전용입니다.
{ "type": "ResourceUpdate", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "title": { "en-US": "Hello", "ko-KR": "안녕" }, "status": { "en-US": "published" } } }ResourcePatch
대상 Content나 Media의 필드를 부분 병합합니다(PATCH). fields에 준 필드(그리고 그 안의 로케일)만 덮어쓰고, 언급하지 않은 필드와 로케일은 그대로 유지합니다. 값 형태, locale, version, publish는 ResourceUpdate와 동일합니다.
| 필드 | 설명 |
|---|---|
resource | "Content" 또는 "Media" |
target | 대상({ sys: { id } }, 필수). id는 보통 { /ptr } |
fields | 덮어쓸 필드. 값은 로케일 맵. 지정한 필드와 로케일 버킷만 갱신(나머지 유지). 값이 리터럴 null이면 그 (field, locale)를 삭제. Media file은 인제스트 지시(위 ResourceCreate 참조) |
locale | (편의) fields 자동 래핑 |
version | (선택) ResourceUpdate와 동일(낙관적 잠금) |
publish | 갱신 후 republish. 기본 true |
- 특정 로케일이나 파일 삭제: 값에 리터럴
null을 줍니다. 예:"title": { "fr-FR": null }(fr-FR 제목 삭제),"file": { "en-US": null }(en-US 파일 삭제). 값표현식이 런타임에 null로 평가되는 것은 삭제가 아니라 에러입니다(리터럴 null만 삭제). - Media
file에 인제스트 지시를 주면 그 로케일 파일을 교체합니다(Async 전용). 파일을 안 주면 유지합니다.
// viewCount(en-US)만 +1. title, 다른 로케일 등 나머지는 그대로 유지
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "+": [ "{ /payload/fields/viewCount }", 1 ] } } } }ResourceDelete
대상을 삭제합니다. Draft와 Archived 상태만 삭제할 수 있습니다. Published나 Changed면 거부되므로 먼저 ResourceUnpublish 해야 합니다(Media는 파일 처리 중(busy)이면 거부). auto-unpublish하지 않습니다(CMA/ACMA 동일).
| 필드 | 설명 |
|---|---|
resource | "Content" 또는 "Media" |
target | 대상({ sys: { id } }, 필수) |
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }ResourcePublish, ResourceUnpublish, ResourceArchive, ResourceUnarchive
대상의 발행과 보관 상태를 독립적으로 제어합니다. 넷 다 필드는 동일합니다. 각 작업의 status 전제조건은 CMA/ACMA와 동일합니다(publish는 Archived 불가이고 파일 처리 완료 필요, unpublish는 Published·Changed만, archive는 Draft만, unarchive는 Archived만).
| 필드 | 설명 |
|---|---|
resource | "Content" 또는 "Media" |
target | 대상({ sys: { id } }, 필수) |
version | (선택) 값표현식(Int). 낙관적 잠금. 주면 현재 sys.version과 일치할 때만 수행 |
{ "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }
{ "type": "ResourceUnpublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }
{ "type": "ResourceArchive", "resource": "Media", "target": { "sys": { "id": "{ /m/sys/id }" } } }리소스 읽기
읽기 문은 상태를 바꾸지 않습니다(propagateEvents 없음).
세 읽기 문 모두 from(선택, 기본 Current)으로 어느 저장본을 읽을지 정합니다. Current는 콘텐츠 스튜디오가 보는 최신 초안(CMA/ACMA가 읽는 값)이고, Published는 발행 스냅샷(CDA/ACDA가 전달하는, 마지막 발행 시점의 값)입니다.
ResourceFind와 ResourcePageRead는 여기에 더해 advanced(선택, 기본 false)로 고급 검색(Advanced Search)을 켤 수 있습니다. Content 전용이라 Media 읽기에서는 무시됩니다. 켜면 where의 regex·near·within 연산자와 텍스트 전문검색(전문검색이 켜진 LongText 필드는 eq가 값이 포함된 항목까지 부분·유사 매칭으로 찾음), 그리고 fields.* 정렬을 쓸 수 있습니다. 끄면 이 세 연산자는 거부되고 텍스트 eq는 정확 일치로 동작하며, prefix와 비교·목록 연산자는 고급 검색과 무관하게 됩니다. 방금 만들거나 고친 항목은 고급 검색 반영에 잠깐(약 1초) 걸려 바로 다음 고급 검색 조회에서 안 잡힐 수 있습니다. 방금 쓴 항목을 곧바로 읽어야 하면 id로 ResourceRead(기본 저장본, 반영 지연 없음)를 쓰거나 쓰기가 돌려준 sys.id로 조회합니다.
where와 order에서 콘텐츠 필드는 fields.<field>로 씁니다(맨 이름만으로는 인식되지 않습니다). fields.<field>에는 space 기본 로케일이 자동으로 적용되므로 로케일을 직접 붙이지 않습니다. 아래 예시의 fields.status, fields.slug가 그대로 기본 로케일 조회입니다. 특정(비기본) 로케일만 노릴 때에만 fields.<field>.<locale>(예: fields.title.ko-KR)로 명시합니다. sys.*(sys.createdAt 등)와 createdBy(:self)는 fields. 없이 그대로 씁니다. 자세한 규칙은 값 표현식의 where·order 로케일에 있습니다.
ResourceRead
id로 단건 조회입니다(get-by-id). 결과는 리소스 전체를 이름에 바인딩합니다.
| 필드 | 설명 |
|---|---|
resource | "Content" 또는 "Media" |
target | 대상({ sys: { id } }). id는 값표현식 |
from | (선택) Current(기본, 최신 초안) 또는 Published(발행 스냅샷) |
- 결과:
{ /<name>/sys/id },{ /<name>/fields/<field>/<locale> }를 직접 참조합니다(items/0불필요). - 대상이 없으면 에러입니다.
Try로 감싸 처리할 수 있습니다.
{ "type": "ResourceRead", "resource": "Content",
"target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" }ResourceFind
필터로 첫 매치 단건을 읽습니다. 없으면 null입니다. 유니크 업무키(slug, email, sku)로 한 건 찾을 때 씁니다.
| 필드 | 설명 |
|---|---|
resource | "Content" 또는 "Media" |
contentType | (Content) 검색 범위 Content Type({ sys: { id } }) |
where | 필터. { "<field>": { "<op>": <값> } } 형식. 쓸 수 있는 연산자는 연산자 목록을 따릅니다(regex·near·within은 advanced 필요). createdBy: ":self" 지원 |
order | 여럿 매치 시 "첫 번째"를 결정하는 정렬(예: "-sys.createdAt") |
from | (선택) Current(기본, 최신 초안) 또는 Published(발행 스냅샷) |
advanced | (선택) 고급 검색(Advanced Search)으로 실행. Content 전용(Media 무시). 기본 false. 위 리소스 읽기 설명 참조 |
- 결과: 첫 매치 리소스를 이름에 바인딩합니다.
{ /<name>/fields/<field>/<locale> }로 직접 참조합니다. 없으면null이므로{ "==": [ "{ /<name> }", null ] }로 존재 여부를 분기합니다(find-then-upsert의 전형).
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" }ResourcePageRead
필터, 정렬, 페이지 조회입니다.
| 필드 | 설명 |
|---|---|
resource | "Content" 또는 "Media" |
contentType | (Content) 검색 범위 Content Type |
where | 필터. { "<field>": { "<op>": <값> } } 형식. 쓸 수 있는 연산자는 연산자 목록을 따릅니다(regex·near·within은 advanced 필요). createdBy: ":self" 지원 |
order | 정렬(예: "-sys.createdAt") |
limit | 페이지 크기(100 이하) |
cursor | 다음 페이지는 이전 결과의 next |
from | (선택) Current(기본, 최신 초안) 또는 Published(발행 스냅샷) |
advanced | (선택) 고급 검색(Advanced Search)으로 실행. Content 전용(Media 무시). 기본 false. 위 리소스 읽기 설명 참조 |
- 결과:
{ items, next }입니다.{ /<name>/items/0/... }, 다음 페이지는{ /<name>/next }. - 전체 순회는
Loop while "{ /vars/hasMore }"와cursor,SetVar누적으로 합니다(쿡북 참조).
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"where": { "fields.status": { "eq": "draft" } }, "order": "-sys.createdAt", "limit": 100, "name": "page" }외부
Http
외부 HTTP를 호출합니다. Http가 있으면 executionMode는 Async여야 합니다(ExternalIo).
| 필드 | 설명 |
|---|---|
method | "GET", "POST", "PUT", "PATCH", "DELETE" |
url | 대상 URL(값표현식. { /ptr } 삽입 가능) |
headers | [{ "key", "value", "secret"? }]. value는 값표현식. secret:true 헤더는 CMA(관리자) 전용으로 취급되어 최종 사용자에게 노출되지 않고 전송 직전에만 복호화됩니다 |
body | 요청 body(값표현식 또는 JSON) |
timeoutMs | 이 호출의 타임아웃(ms) |
retry | 응답 status가 400 이상이면 재시도할 횟수. 기본 0, 상한은 maxHttpRetry(기본 2) |
ignoreStatusCode | (재시도까지 마친) 최종 status가 400 이상일 때 이 호출을 실패로 볼지. 기본 false면 실패로 처리되어 Try/catch 대상이 됩니다. true면 실패로 보지 않고 { status, body }를 그대로 바인딩합니다(호출자가 status로 직접 분기) |
- 결과:
{ status, body }입니다.{ /<name>/status },{ /<name>/body/... }. - 응답 크기 상한: 응답 본문은 최대 10MiB입니다. 초과하면 이 호출은 예외로 실패해 다른 런타임 실패처럼
Try/catch로 처리할 수 있습니다(크기 기준 실패라ignoreStatusCode로는 무시되지 않습니다).
{ "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, "retry": 1, "name": "resp" }변수
SetVar
script-scoped 가변 변수를 선언하거나 갱신합니다. { /vars/<var> }로 참조합니다(JsonLogic엔 변수 선언이 없어 statement로 제공합니다).
| 필드 | 설명 |
|---|---|
var | 변수 이름. { /vars/<var> }로 참조 |
value | 값표현식. 자기 자신을 참조해 누적할 수 있습니다 |
{ "type": "SetVar", "var": "total", "value": 0 }
{ "type": "SetVar", "var": "total", "value": { "+": [ "{ /vars/total }", "{ /row/qty }" ] } } // 누적
{ "type": "SetVar", "var": "ids", "value": { "merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] } } // 배열 수집제어 흐름
If
조건 분기입니다. condition은 JsonLogic이고, 참과 거짓은 참과 거짓 판정 규칙을 따릅니다.
| 필드 | 설명 |
|---|---|
condition | JsonLogic(boolean으로 평가) |
then | 참일 때 실행할 Statement 배열 |
else | (선택) 거짓일 때 실행할 Statement 배열 |
{ "type": "If",
"condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" } } ],
"else": [ /* ... */ ] }Loop
반복입니다. 모드 하나를 고릅니다. over(foreach), while(조건), for(카운트)입니다. 어느 모드든 엔진이 maxIterations로 상한을 강제합니다(무한 루프 방지). body 안에서 외부 호출(Http, Media 파일 인제스트)은 금지됩니다.
| 필드 | 설명 |
|---|---|
over | foreach: 배열로 resolve되는 값표현식 |
while | 조건: JsonLogic(참인 동안 반복) |
for | 카운트: { "from", "to", "step"? }. from부터 to까지 포함, step 기본 1 |
maxIterations | 엔진이 강제하는 최대 반복 수(필수) |
as | 현재 항목이나 인덱스를 바인딩할 이름({ /<as> }) |
body | 반복 본문 Statement 배열 |
// foreach
{ "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 }" } } } ] }
// while
{ "type": "Loop", "while": "{ /vars/hasMore }", "maxIterations": 1000, "body": [ /* ... */ ] }
// counted (1..10 step 2)
{ "type": "Loop", "for": { "from": 1, "to": 10, "step": 2 }, "as": "i", "maxIterations": 100, "body": [ /* ... */ ] }Parallel
브랜치들을 동시 실행하고 조인 후 진행합니다. 브랜치 간 참조는 불가합니다(의존이 있으면 순차로 배치합니다).
| 필드 | 설명 |
|---|---|
branches | Statement[][]. 각 원소가 한 브랜치(문 배열) |
{ "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" } ]
] }Return
일반 프로그래밍의 return입니다. Script의 결과를 호출자에게 돌려주고 그 지점에서 정상 종료합니다.
| 필드 | 설명 |
|---|---|
value | (선택) 반환할 값표현식 |
isError | 기본 false. true면 value가 응답의 error로 나옵니다(아니면 return) |
statusCode | 응답 상태 코드. 기본 200 |
Return에 도달하지 못하면 반환값이 없습니다. 결과를 주려면value를 명시합니다.- 예외나 throw가 아니라 정상 종료라
catch대상이 아닙니다(Try안에서도 Script 전체를 종료하되finally는 실행합니다). - guard도 이 문으로 표현합니다.
If와then:[Return](조건 위반 시 반환하고 이후 미실행)이고, 여러 용도 중 하나입니다.
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 }
{ "type": "Return", "value": { "reason": "payment failed" }, "isError": true, "statusCode": 402 }Try
예외 처리입니다.
| 필드 | 설명 |
|---|---|
body | 시도할 Statement 배열 |
catch | (선택) body 실패 시 실행. /error에 { message, statement } 노출 |
finally | (선택) 성공과 실패에 무관하게 항상 실행 |
catch가 처리하면 Script는 중단되지 않습니다.catch없는 실패만 Script를 중단시킵니다(보상 시도 포함).- 무엇이 "실패"인지, 보상(compensation)의 한계는 실행 시맨틱, 제약, 보안에서 다룹니다.
{ "type": "Try",
"body": [ { "type": "Http", "method": "POST", "url": "https://primary.api/gen", "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/text }" } } } ],
"catch": [ { "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "생성 실패" }, "error": { "en-US": "{ /error/message }" } } } ],
"finally": [ /* 항상 실행 */ ] }관련 문서
- 값 표현식: 위 모든 필드가 따르는 값 규칙.
- 실행 시맨틱, 제약, 보안: 실행 순서, 에러, 정적 제약, 보안.
- 쿡북: 이 문들을 조합한 완결 예시.
- Script 개요: 최상위 구조와 실행 모드.
