아티팩트 SDK
아티팩트 블록에서 사용할 수 있는 Suits SDK API를 알아보세요.
개요
Artifact 블록의 iframe 안에서는 window.suits 객체를 통해 Suits SDK를 사용할 수 있습니다. 별도의 import나 설치 없이, 코드 작성 즉시 사용 가능합니다.
// iframe 내부 어디서든 바로 사용
const rows = await suits.fetchRows("dm_abc123");
suits.toast.success("데이터를 불러왔습니다.");Suits SDK는 Artifact 블록의 iframe에 자동으로 주입됩니다. 별도의 스크립트 로드나 초기화가 필요하지 않습니다.
초기화 데이터
window.suits 객체에는 블록이 로드될 때 다음 데이터가 자동으로 주입됩니다.
| 속성 | 타입 | 설명 |
|---|---|---|
suits.user | { id: string, name: string | null } | 현재 로그인한 사용자 정보 |
suits.theme | { mode: 'light' | 'dark' } | 현재 테마 모드 |
suits.block | { id: string, workspaceId: string, pageId: string } | 현재 블록 정보 |
suits.allowedWorkflows | [{ id: string, alias: string }] | 블록에 등록된 워크플로우 목록 |
suits.globalFilters | [{ id, name, type, value }] | 페이지의 글로벌 필터 현재 값 |
// 초기화 데이터 접근 예시
console.log(suits.user.name); // "홍길동"
console.log(suits.theme.mode); // "light" 또는 "dark"
console.log(suits.block.workspaceId); // "ws_abc123"데이터 읽기 API
데이터를 읽으려면 블록 설정에서 해당 데이터모델 또는 통계 정의를 데이터소스로 등록해야 합니다. read 이상의 접근 권한이 필요합니다.
suits.fetchRows(dataModelId, options?)
데이터모델에서 행 데이터를 조회합니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| dataModelId | string | 예 | 데이터모델 ID |
| options | object | 아니오 | 조회 옵션 |
반환값
Promise<{ rows: [...] }>예시
const result = await suits.fetchRows("dm_abc123");
console.log(result.rows); // [{ id: "row_1", data: { ... } }, ...]suits.fetchStatData(statDefId, options?)
통계 정의의 실행 결과 데이터를 조회합니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| statDefId | string | 예 | 통계 정의 ID |
| options | object | 아니오 | 조회 옵션 |
반환값
Promise<{ ... }> // 통계 정의에 따른 결과 데이터예시
const stats = await suits.fetchStatData("stat_monthly_revenue");
console.log(stats);suits.fetchDataModelSchema(dataModelId)
데이터모델의 프로퍼티(컬럼) 정보를 조회합니다. 컬럼 이름, 타입, 옵션 등의 스키마 정보를 확인할 때 사용합니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| dataModelId | string | 예 | 데이터모델 ID |
반환값
Promise<{ properties: [{ id, name, type, ... }] }>예시
const schema = await suits.fetchDataModelSchema("dm_abc123");
schema.properties.forEach((prop) => {
console.log(`${prop.name} (${prop.type})`);
});
// 회사명 (TEXT)
// 매출 (CURRENCY)
// 상태 (SELECT)suits.getGlobalFilters()
페이지에 설정된 글로벌 필터의 현재 값을 조회합니다.
반환값
Promise<[{ id: string, name: string, type: string, value: any }]>예시
const filters = await suits.getGlobalFilters();
filters.forEach((f) => {
console.log(`${f.name}: ${f.value}`);
});
// 기간: 2026-03
// 팀: 영업1팀데이터 쓰기 API
데이터를 쓰려면 블록 설정에서 해당 데이터모델을 readwrite 권한으로 데이터소스에 등록해야 합니다.
read 권한으로 등록된 데이터소스에 쓰기 API를 호출하면 "Unauthorized data source" 에러가 발생합니다.
suits.addRow(dataModelId, values)
데이터모델에 새 행을 추가합니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| dataModelId | string | 예 | 데이터모델 ID |
| values | object | 예 | 프로퍼티 ID를 키로 하는 값 객체 |
예시
const result = await suits.addRow("dm_abc123", {
prop_001: "신규 고객사",
prop_002: "[email protected]",
prop_003: 50000000,
prop_004: "lead",
});
console.log(result.rowId); // 생성된 행 IDsuits.updateRow(dataModelId, rowId, values)
기존 행의 데이터를 수정합니다. 변경할 프로퍼티만 포함하면 됩니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| dataModelId | string | 예 | 데이터모델 ID |
| rowId | string | 예 | 수정할 행 ID |
| values | object | 예 | 변경할 프로퍼티 ID와 값 |
예시
await suits.updateRow("dm_abc123", "row_111", {
prop_004: "active",
prop_003: 75000000,
});
suits.toast.success("상태가 업데이트되었습니다.");suits.deleteRow(dataModelId, rowId)
행을 삭제합니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| dataModelId | string | 예 | 데이터모델 ID |
| rowId | string | 예 | 삭제할 행 ID |
예시
await suits.deleteRow("dm_abc123", "row_111");
suits.toast("행이 삭제되었습니다.");워크플로우 API
워크플로우를 실행하려면 블록 설정에서 해당 워크플로우를 허용 워크플로우(allowedWorkflows)에 등록해야 합니다.
등록되지 않은 워크플로우를 실행하려 하면 "Unauthorized workflow" 에러가 발생합니다.
suits.executeWorkflow(workflowId, triggerData?)
워크플로우를 실행합니다. Fire-and-forget 방식으로, 실행을 요청한 뒤 결과를 기다리지 않습니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| workflowId | string | 예 | 워크플로우 ID |
| triggerData | object | 아니오 | 워크플로우에 전달할 데이터 |
예시
const workflowId = suits.getWorkflowId("sendNotification");
await suits.executeWorkflow(workflowId, {
recipient: "[email protected]",
message: "주문이 접수되었습니다.",
});
suits.toast.info("알림을 발송 중입니다.");suits.executeWorkflowAndWait(workflowId, triggerData?, options?)
워크플로우를 실행하고, 완료될 때까지 대기합니다. SSE 스트림을 통해 결과를 실시간으로 수신하며, 마지막 노드의 실행 결과가 반환됩니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| workflowId | string | 예 | 워크플로우 ID |
| triggerData | object | 아니오 | 워크플로우에 전달할 데이터 |
| options | object | 아니오 | 실행 옵션 |
options 속성
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| timeout | number | 360000 (6분) | 타임아웃 (밀리초) |
반환값
Promise<lastNodeResult> // 워크플로우 마지막 노드의 실행 결과예시
const workflowId = suits.getWorkflowId("analyzeData");
const result = await suits.executeWorkflowAndWait(workflowId, {
dataModelId: "dm_abc123",
period: "2026-03",
});
console.log(result); // 마지막 노드의 실행 결과
// 타임아웃 지정
const result2 = await suits.executeWorkflowAndWait(workflowId, null, {
timeout: 120000, // 2분
});장시간 실행되는 워크플로우의 경우, 기본 타임아웃(6분)을 초과하면 에러가 발생합니다. 필요에 따라 timeout 옵션을 조정하세요.
suits.getWorkflowId(alias)
워크플로우의 별칭(alias)으로 ID를 조회합니다. allowedWorkflows에 등록된 워크플로우 중에서 검색합니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| alias | string | 예 | 워크플로우 별칭 |
반환값
string | null // 워크플로우 ID 또는 null (일치하는 별칭이 없을 때)예시
const workflowId = suits.getWorkflowId("processOrder");
if (workflowId) {
await suits.executeWorkflow(workflowId, { orderId: "12345" });
} else {
suits.toast.error("워크플로우를 찾을 수 없습니다.");
}getWorkflowId는 동기 함수입니다. await 없이 바로 사용할 수 있습니다.
UI API
suits.toast(message, options?)
토스트 알림을 표시합니다. 4가지 변형을 제공합니다.
| 메서드 | 설명 |
|---|---|
suits.toast(message, options?) | 기본 토스트 |
suits.toast.success(message, options?) | 성공 토스트 |
suits.toast.error(message, options?) | 에러 토스트 |
suits.toast.info(message, options?) | 정보 토스트 |
suits.toast.warning(message, options?) | 경고 토스트 |
options 속성
| 이름 | 타입 | 설명 |
|---|---|---|
| description | string | 토스트 하단에 표시할 부가 설명 |
예시
suits.toast("저장되었습니다.");
suits.toast.success("주문이 완료되었습니다.", {
description: "주문번호: ORD-2026-001",
});
suits.toast.error("저장에 실패했습니다.");
suits.toast.info("데이터를 불러오는 중입니다.");
suits.toast.warning("입력값을 확인해주세요.");suits.openModal(options)
모달 창을 엽니다. 모달 내부도 독립된 iframe으로 실행되며, Suits SDK를 동일하게 사용할 수 있습니다.
options 속성
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| title | string | 예 | - | 모달 제목 |
| html | string | 예 | - | 모달 본문 HTML |
| css | string | 아니오 | "" | 모달 스타일 CSS |
| js | string | 아니오 | "" | 모달 동작 JavaScript |
| width | number | 아니오 | 640 | 모달 너비 (px) |
| cdnDependencies | string[] | 아니오 | [] | CDN 라이브러리 URL 목록 |
반환값
Promise<any> // suits.closeModal(result)로 전달된 값예시
const result = await suits.openModal({
title: "고객 정보 입력",
html: `
<form id="form">
<label>이름</label>
<input type="text" id="name" class="bc-input" />
<label>이메일</label>
<input type="email" id="email" class="bc-input" />
<button type="submit" class="bc-button">확인</button>
</form>
`,
js: `
document.getElementById("form").addEventListener("submit", (e) => {
e.preventDefault();
suits.closeModal({
name: document.getElementById("name").value,
email: document.getElementById("email").value,
});
});
`,
width: 480,
});
// result: { name: "홍길동", email: "[email protected]" }
if (result) {
await suits.addRow("dm_customers", {
prop_001: result.name,
prop_002: result.email,
});
}모달 내에서 suits.openModal을 다시 호출할 수 없습니다. 모달 중첩은 지원되지 않습니다.
suits.closeModal(result?)
현재 열린 모달을 닫습니다. 모달 iframe 내부에서만 호출할 수 있습니다.
매개변수
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| result | any | 아니오 | openModal의 Promise에 전달할 반환값 |
예시
// 모달 내부의 JS에서 호출
suits.closeModal({ confirmed: true, selectedId: "row_123" });
// 취소 버튼
document.getElementById("cancel").addEventListener("click", () => {
suits.closeModal(null);
});이벤트 리스너
suits.onGlobalFiltersChanged
페이지의 글로벌 필터 값이 변경될 때 호출되는 콜백입니다. 함수를 할당하면 필터가 변경될 때마다 자동으로 호출됩니다.
suits.onGlobalFiltersChanged = (filters) => {
console.log("필터 변경:", filters);
// filters: [{ id, name, type, value }, ...]
// 필터 값에 따라 데이터를 다시 조회
const periodFilter = filters.find((f) => f.name === "기간");
if (periodFilter) {
loadData(periodFilter.value);
}
};테마 변경 감지
테마가 변경되면 suits.theme.mode가 자동으로 업데이트되고, <html> 태그에 dark 클래스가 토글됩니다. CSS 변수를 사용하고 있다면 별도 처리 없이 자동으로 테마가 반영됩니다.
커스텀 로직이 필요한 경우에는 CSS의 .dark 선택자를 활용하세요.
/* 라이트 모드 */
.chart-line { stroke: #333; }
/* 다크 모드 */
.dark .chart-line { stroke: #eee; }// JavaScript에서 현재 테마 확인
if (suits.theme.mode === "dark") {
// 다크 모드 전용 로직
}사용자 정보
suits.getCurrentUser()
현재 로그인한 사용자의 정보를 반환합니다.
반환값
Promise<{ id: string, name: string | null } | null>예시
const user = await suits.getCurrentUser();
if (user) {
console.log(`${user.name}님, 환영합니다.`);
}Rate Limiting
SDK API 호출에는 속도 제한이 적용됩니다. 제한을 초과하면 "Rate limit exceeded" 에러가 발생합니다.
| 카테고리 | 해당 API | 제한 |
|---|---|---|
| READ | fetchRows, fetchStatData, fetchDataModelSchema, getGlobalFilters | 5초당 최대 10회 |
| WRITE | addRow, updateRow, deleteRow, executeWorkflow, executeWorkflowAndWait | 10초당 최대 5회 |
| TOAST | toast, toast.success, toast.error, toast.info, toast.warning | 5초당 최대 5회 (전역) |
TOAST의 Rate Limit은 전역으로 적용됩니다. 같은 페이지의 여러 Artifact 블록이 공유하므로, 한 페이지에 여러 블록이 있을 경우 전체 합산으로 제한됩니다.
예제: 고객 목록 대시보드
데이터모델에서 고객 데이터를 조회하여 테이블로 표시하고, 행 추가 기능을 제공하는 예제입니다.
HTML
<div class="container">
<h2>고객 목록</h2>
<button id="add-btn" class="bc-button">고객 추가</button>
<table class="bc-table">
<thead>
<tr>
<th>회사명</th>
<th>이메일</th>
<th>상태</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
</div>CSS
.container {
max-width: 800px;
margin: 0 auto;
}
h2 {
margin-bottom: 12px;
}
#add-btn {
margin-bottom: 16px;
}JS
const DATA_MODEL_ID = "dm_abc123";
// 데이터 조회 및 테이블 렌더링
async function loadCustomers() {
try {
const { rows } = await suits.fetchRows(DATA_MODEL_ID);
const tbody = document.getElementById("table-body");
tbody.innerHTML = rows
.map(
(row) => `
<tr>
<td>${row.data.prop_001 || ""}</td>
<td>${row.data.prop_002 || ""}</td>
<td>${row.data.prop_004 || ""}</td>
</tr>
`
)
.join("");
} catch (err) {
suits.toast.error("데이터를 불러오지 못했습니다.", {
description: err.message,
});
}
}
// 고객 추가 버튼
document.getElementById("add-btn").addEventListener("click", async () => {
const result = await suits.openModal({
title: "고객 추가",
html: `
<form id="form" style="display:flex;flex-direction:column;gap:12px;">
<input id="name" class="bc-input" placeholder="회사명" required />
<input id="email" class="bc-input" placeholder="이메일" type="email" required />
<div style="display:flex;gap:8px;justify-content:flex-end;">
<button type="button" id="cancel" class="bc-button bc-button-outline">취소</button>
<button type="submit" class="bc-button">추가</button>
</div>
</form>
`,
js: `
document.getElementById("form").addEventListener("submit", (e) => {
e.preventDefault();
suits.closeModal({
name: document.getElementById("name").value,
email: document.getElementById("email").value,
});
});
document.getElementById("cancel").addEventListener("click", () => {
suits.closeModal(null);
});
`,
width: 400,
});
if (result) {
try {
await suits.addRow(DATA_MODEL_ID, {
prop_001: result.name,
prop_002: result.email,
prop_004: "lead",
});
suits.toast.success("고객이 추가되었습니다.");
loadCustomers(); // 테이블 새로고침
} catch (err) {
suits.toast.error("고객 추가에 실패했습니다.");
}
}
});
// 글로벌 필터 변경 시 데이터 새로고침
suits.onGlobalFiltersChanged = () => {
loadCustomers();
};
// 초기 로드
loadCustomers();다음 단계
- Artifact 블록 -- 블록 생성 및 설정 가이드
- 데이터 블록 -- 통계 블록, 데이터모델 블록
- 워크플로우 -- 워크플로우 가이드