Statement 카탈로그

최종 수정: 2026년 7월 23일

statements 배열의 각 원소가 하나의 문(statement)입니다. 이 문서는 17종 문의 필드, 동작, 결과를 정리합니다. 모든 값 자리는 값 표현식의 규칙(참조, 리터럴, JsonLogic, 로케일 맵)을 따릅니다.

Statement 요약

분류type한 줄 요약
리소스 쓰기ResourceCreateContent/Media 생성(선택적으로 발행)
ResourceUpdateContent/Media 필드 전체 교체(안 준 field·locale 삭제)
ResourcePatchContent/Media 필드 부분 병합(지정 field·locale만. 리터럴 null은 삭제)
ResourceDelete삭제(Draft·Archived만. Published면 먼저 unpublish)
ResourcePublish / ResourceUnpublish발행 / 발행 취소
ResourceArchive / ResourceUnarchive보관 / 보관 해제
리소스 읽기ResourceReadid로 단건 조회
ResourceFind필터로 첫 매치 단건(없으면 null)
ResourcePageRead필터/정렬/페이지 조회({ items, next })
외부Http외부 HTTP 호출({ status, body }). Async 전용
변수SetVarscript-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(그리고 Loopas)은 컨텍스트 루트에 그대로 얹히는 키라 저장 시 검증됩니다. 빈 문자열이 아니어야 하고 /·~를 포함하지 않아야 하며(JSON Pointer 키로 쓸 수 있어야 함), 예약 루트(payload·vars·error)와 같을 수 없고, 한 Script 안에서 유일해야 합니다. 위반 시 각각 WGL400033(형식)·WGL400032(예약어)·WGL400034(중복)로 저장이 거부됩니다.

엔티티 참조 형태

contentType, target 같은 엔티티 참조는 { "sys": { "id": <값표현식> } } 한 형태로 통일합니다. sys.id만 필요하고 대상 타입은 resource에서 추론합니다(sys.typesys.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를 만듭니다. ContentMediafields 모델을 공유하며, 값은 로케일 맵입니다.

필드대상설명
resource공통"Content" 또는 "Media" (필수)
contentTypeContent만들 Content Type({ sys: { id } }). Content일 때 필수
fields공통필드 맵 { "<field>": { "<locale>": 값 } }. populate 필드마다 기본 로케일 버킷 필수. Content 키는 Content Type 정의를 따르고, Media 키는 고정(title·description·file)
locale공통(편의) 주면 fields 각 값을 { <locale>: 값 }으로 자동 래핑
publish공통쓰기 후 발행(CDA/ACDA 노출). 기본 true
  • Media file: 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

대상 ContentMedia의 필드를 전체 교체합니다(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

대상 ContentMedia의 필드를 부분 병합합니다(PATCH). fields에 준 필드(그리고 그 안의 로케일)만 덮어쓰고, 언급하지 않은 필드와 로케일은 그대로 유지합니다. 값 형태, locale, version, publishResourceUpdate와 동일합니다.

필드설명
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

대상을 삭제합니다. DraftArchived 상태만 삭제할 수 있습니다. 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가 전달하는, 마지막 발행 시점의 값)입니다.

ResourceFindResourcePageRead는 여기에 더해 advanced(선택, 기본 false)로 고급 검색(Advanced Search)을 켤 수 있습니다. Content 전용이라 Media 읽기에서는 무시됩니다. 켜면 whereregex·near·within 연산자와 텍스트 전문검색(전문검색이 켜진 LongText 필드는 eq가 값이 포함된 항목까지 부분·유사 매칭으로 찾음), 그리고 fields.* 정렬을 쓸 수 있습니다. 끄면 이 세 연산자는 거부되고 텍스트 eq는 정확 일치로 동작하며, prefix와 비교·목록 연산자는 고급 검색과 무관하게 됩니다. 방금 만들거나 고친 항목은 고급 검색 반영에 잠깐(약 1초) 걸려 바로 다음 고급 검색 조회에서 안 잡힐 수 있습니다. 방금 쓴 항목을 곧바로 읽어야 하면 id로 ResourceRead(기본 저장본, 반영 지연 없음)를 쓰거나 쓰기가 돌려준 sys.id로 조회합니다.

whereorder에서 콘텐츠 필드는 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·withinadvanced 필요). 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·withinadvanced 필요). 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가 있으면 executionModeAsync여야 합니다(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이고, 참과 거짓은 참과 거짓 판정 규칙을 따릅니다.

필드설명
conditionJsonLogic(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 파일 인제스트)은 금지됩니다.

필드설명
overforeach: 배열로 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

브랜치들을 동시 실행하고 조인 후 진행합니다. 브랜치 간 참조는 불가합니다(의존이 있으면 순차로 배치합니다).

필드설명
branchesStatement[][]. 각 원소가 한 브랜치(문 배열)
{ "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. truevalue가 응답의 error로 나옵니다(아니면 return)
statusCode응답 상태 코드. 기본 200
  • Return에 도달하지 못하면 반환값이 없습니다. 결과를 주려면 value를 명시합니다.
  • 예외나 throw가 아니라 정상 종료catch 대상이 아닙니다(Try 안에서도 Script 전체를 종료하되 finally는 실행합니다).
  • guard도 이 문으로 표현합니다. Ifthen:[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": [ /* 항상 실행 */ ] }