사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
CLI 레퍼런스▾
AkanJS 레퍼런스▾
UI 레퍼런스▾

akanjs/base

akanjs/base에는 model과 signal을 만드는 값 타입과 몇 가지 런타임 helper가 들어 있습니다. Akan의 다른 모듈을 가져오지 않으므로 서버 파일, 클라이언트 파일, common/ 어디서나 import할 수 있습니다.
import { dayjs, enumOf, ID, Int } from "akanjs/base";
문서를 가리키는 id입니다. 24자리 16진수 문자열입니다.
정수와 소수입니다. JavaScript Number는 필드 타입으로 쓸 수 없습니다.
Akan이 검사하지 않는 열린 값입니다. 모양은 타입 인자로 알려 줍니다.
signal 인자나 반환값에 담는 바이트입니다.
signal이 반환하는 그림입니다. HTTP에서는 JSON으로, MCP에서는 image block으로 갑니다.
업로드 mutation의 body로 받는 파일입니다.
dayjsDayjs
날짜 라이브러리와 그 타입입니다. Date 필드의 값은 모두 Dayjs입니다.
정해진 값 목록으로 enum class를 만듭니다.
실행 중인 앱의 이름, 환경, 서버 주소를 알려 줍니다.
getApiPrefixgetWsPrefix
signal과 websocket이 붙는 경로를 알려 줍니다.
id로 찾는 목록입니다. store에 있는 model 목록은 모두 DataList입니다.
타입별로 쓸 수 있는 자리
타입
model 필드
signal 인자
signal 반환값
akanjs/base에서 import
ID
✓
✓
✓
문서 id입니다.
Int · Float
✓
✓
✓
개수와 소수입니다.
Any
✓
✓
✓
모양을 열어 두는 payload입니다.
Binary
✓
✓
저장할 바이트는 File model에 둡니다.
ImageResult
✓
에이전트에게 보여 줄 그림입니다.
Upload
✓
fileUpload: true mutation의 body에서만 씁니다.
import 없이 쓰는 JavaScript 전역
String
✓
✓
✓
일반 문자열입니다.
Boolean
✓
✓
✓
"true", "false", "1", "0" 같은 텍스트도 boolean으로 읽습니다.
Date
✓
✓
✓
값은 JavaScript Date가 아니라 Dayjs입니다.
✓쓸 수 있음쓰지 않음
  • 배열은 타입을 대괄호로 감쌉니다. field([Float])는 소수 목록이고, .body("files", [Upload])는 파일 여러 개를 받습니다.
  • 세 전역은 import하지 않습니다. Akan이 String, Boolean, Date를 확장해 두어서 그대로 타입으로 쓸 수 있습니다.
default가 없을 때의 값
default가 없는 필수 필드는 아래 값에서 시작합니다. .optional() 필드는 null에서 시작합니다.
타입TypeScript 값default가 없을 때
IDstring""
Int · Floatnumber0
AnyT — field<T>(Any)null
Stringstring""
Booleanbooleanfalse
DateDayjsdayjs(new Date(-1))

ID

ID는 문서 id입니다. UUID가 아니라 24자리 16진수 문자열입니다. relation 없이 id만 저장하는 필드와 signal의 id 인자에 씁니다.
파일을 불러오지 않고 id만 들고 있는 file meta scalar입니다:
libs/shared/lib/__scalar/fileMeta/fileMeta.constant.ts
  • relation은 model class로 선언합니다. field(File)은 파일과의 relation을 저장하고, field(ID)는 id 문자열만 저장합니다.
  • 형식을 검사합니다. 16진수 24자리가 아니면 거절합니다. 빈 문자열 ""은 아직 정해지지 않은 id의 자리표시로 통과합니다.
  • 주인 model은 ref로 적습니다. field(ID, { ref: "org", cascade: "removeWith" })로 선언하면 그 org가 삭제될 때 이 문서도 함께 삭제됩니다.

Int

Int는 정수입니다. 카운터, 수량, 페이지 번호, 측정 샘플에 씁니다. 안전한 정수(safe integer)가 아닌 값은 거절합니다.
네 가지를 0부터 세는 access stat scalar입니다:
libs/util/lib/__scalar/accessStat/accessStat.constant.ts
  • Number는 타입이 아닙니다. field(Number)와 .body("x", Number)는 타입 검사에서 막힙니다. Int나 Float을 고릅니다.
  • 텍스트는 숫자로 바뀝니다. query string이나 form으로 온 "3"은 숫자 3으로 들어옵니다.

Float

Float은 유한한 숫자입니다. 좌표, 비율, 잔액, 리소스 측정값처럼 소수가 의미 있는 값에 씁니다. NaN과 Infinity는 거절합니다.
경도·위도 쌍과 고도를 담는 coordinate scalar입니다:
libs/util/lib/__scalar/coordinate/coordinate.constant.ts
  • 정수 값은 Int로 둡니다. 개수, 수량, 인덱스는 float으로 저장할 수 있더라도 Int로 선언합니다.
  • 텍스트는 숫자로 바뀝니다. query string으로 온 "1.5"는 1.5로 들어옵니다.

Any

Any는 Akan이 모양을 검사하지 않는 값입니다. 외부 연동 payload나 형식이 자유로운 metadata에 씁니다. 모양이 정해져 있다면 실제 필드로 선언합니다.
body 모양은 열어 두되 타입은 유지하는 event payload입니다:
apps/myapp/lib/__scalar/eventPayload/eventPayload.constant.ts
  • 모양은 타입 인자로 알려 줍니다. field<Record<string, unknown>>처럼 쓰면 TypeScript 타입이 유지됩니다. 실행 중에 검사하지는 않습니다.
  • 객체 default는 함수로 줍니다. 리터럴 {}는 모든 인스턴스가 같은 객체 하나를 나눠 씁니다. () => ({})로 주면 인스턴스마다 새로 만듭니다.
  • 에이전트에게는 공개되지 않습니다. Any를 반환하거나 필수 인자로 Any를 받는 signal은 MCP에 올라가지 않습니다.

Binary

Binary는 signal 인자나 반환값으로 바이트를 그대로 주고받습니다. 서버와 클라이언트 모두 Uint8Array이고, Node의 Buffer도 Uint8Array라서 그대로 넘길 수 있습니다.
요청과 응답
JSON 안에서 base64 문자열로 오갑니다. 양쪽 모두 base64와 바이트를 다 받습니다.
pubsub(Binary)
JSON도 base64도 없이 websocket binary frame으로 보냅니다. 반환 타입 전체가 Binary일 때만 해당합니다.
최신 프레임만 받으면 되는 room과, 모든 프레임을 받아야 하는 room을 둔 stream endpoint입니다:
apps/myapp/lib/_stream/stream.signal.ts
  • 느린 구독자는 최신 프레임만 받습니다. 기본적으로 pubsub(Binary) room은 가장 최근 프레임만 남깁니다. 텔레메트리나 영상에 맞는 동작입니다.
  • backpressure: "queue"는 모든 프레임을 보냅니다. delta처럼 하나도 빠지면 안 되는 흐름에 씁니다. 대신 전송 버퍼가 가장 느린 구독자만큼 커집니다.
  • 에이전트에게는 공개되지 않습니다. Binary를 반환하는 signal은 MCP에 올라가지 않습니다.
  • Binary.toBase64(bytes)와 Binary.fromBase64(text)가 그 wire 코덱입니다. 런타임에 Buffer가 있으면 그것을, 브라우저에서는 btoa / atob를 쓰므로, 어느 쪽에서든 base64가 필요하면 따로 구현하지 말고 이것을 부릅니다.

ImageResult

ImageResult는 endpoint가 { data, mimeType, caption? }으로 반환하는 그림입니다. data는 data: 접두사가 없는 padding된 base64이고, mimeType은 Anthropic과 OpenAI 모델이 받는 image/png, image/jpeg, image/webp, image/gif 중 하나입니다.
apps/myapp/lib/_screen/screen.signal.ts
  • 모델은 그림을 봅니다. tools/call은 caption을 text block으로, 그림을 image block으로 답하고, [ImageResult]면 그림마다 한 쌍씩 보냅니다. base64를 반복하는 outputSchema, structuredContent, JSON text block은 없습니다.
  • 나머지는 JSON을 읽습니다. HTTP와 MCP App view에서는 JSON 객체로 답합니다. page prompt와 in-page agent는 mimeType과 caption만 읽습니다.
  • 모양이 틀린 값은 보내지 않습니다. data: URL이나 목록에 없는 타입이면 일반 실패로 답하고, 로그에 ImageResult에 필요한 모양을 남깁니다.
  • 저장할 수 없습니다. Binary처럼 field(ImageResult)는 class build에서 실패합니다. 저장할 그림은 File model에 둡니다.

Upload

Upload는 업로드 mutation의 body로 받는 파일이고, 다른 곳에는 쓰지 않습니다. libs/shared를 쓰는 앱에는 이 mutation이 이미 있습니다:
libs/shared/lib/file/file.signal.ts
  • fileUpload: true가 있어야 합니다. Upload는 이 표시가 붙은 mutation의 body에서만 유효하고, 그 mutation은 MCP에 올라가지 않습니다.
  • 앱에 하나만 둡니다. 자동 생성되는 fetch.add<Model>Files(fileList)와 store action upload<Field>On<Model>(fileList)이 모두 fileUpload: true mutation으로 보냅니다. 둘이면 첫 번째만 쓰입니다.
  • body 구성은 정해져 있습니다. 클라이언트는 항상 files, metas, type, parentId를 보냅니다. fileList에는 File[]나 input.files의 FileList를 넘깁니다.
  • model은 File을 참조합니다. field(Upload)가 아니라 image: field(File).optional()이나 images: field([File])로 선언합니다.

dayjs / Dayjs

akanjs/base는 dayjs 함수와 Dayjs 타입을 다시 내보냅니다. Date 필드의 값은 모두 Dayjs라서 document, store, service, UI가 같은 API를 씁니다.
레코드가 만들어지는 순간을 default로 갖는 날짜 필드입니다:
libs/util/lib/__scalar/accessLog/accessLog.constant.ts
값을 읽을 때는 평범한 dayjs 코드입니다. 타입도 같은 곳에서 import합니다:
apps/myapp/common/dayLabel.ts
  • akanjs/base에서 import합니다. page와 module 파일은 외부 패키지를 직접 import할 수 없어서, 거기서 import dayjs from "dayjs"는 lint에 걸립니다.
  • "지금"은 함수로 줍니다. default: () => dayjs()는 레코드마다 실행되지만, default: dayjs()는 모듈을 불러온 시각에 고정됩니다.

enumOf

enumOf(name, values)는 정해진 값 목록으로 enum class를 만듭니다. 이 class를 필드나 인자 타입으로 쓰고, static helper로 목록을 읽습니다.
한 번 선언하고 필드 타입으로 쓰는 job 상태입니다:
apps/myapp/lib/job/job.constant.ts
static helper
values
선언한 그대로의 값 목록입니다.
has(value)
값이 목록에 있는지 알려 줍니다.
indexOf(value)
값의 위치입니다. 목록에 없는 값이면 에러를 던집니다.
find(fn)findIndex(fn)
배열 메서드와 같지만, 맞는 값이 없으면 에러를 던집니다.
filter(fn)map(fn)forEach(fn)
배열 메서드와 똑같습니다.
JobStatus["value"]
값 하나의 타입입니다. 목록의 union이 됩니다.
  • 이름은 camelCase, 목록에는 as const를 붙입니다. 첫 인자가 enum의 이름입니다. as const가 없으면 값 타입이 string으로 넓어집니다.
  • 값 목록이 타입을 정합니다. 문자열이면 String, 정수면 Int, 그 밖의 숫자면 Float enum이 됩니다. 빈 목록은 에러를 던집니다.
  • signal 인자는 검사됩니다. enum 타입 인자는 목록에 없는 값을 거절합니다.
  • 표시 이름은 dictionary에 둡니다. 각 값의 번역은 module dictionary의 .enum() 단계에 적습니다.

getEnv

getEnv()는 실행 중인 코드에 앱 정보를 알려 줍니다. 앱 이름, 환경, 웹 서버와 API 서버의 주소입니다. 첫 호출 때 환경 변수를 읽고, 그 뒤에는 캐시한 같은 객체를 돌려줍니다.
appNamestring
AKAN_PUBLIC_APP_NAME에서 읽습니다. 필수입니다.
repoNamestring
AKAN_PUBLIC_REPO_NAME에서 읽습니다. 필수입니다.
serveDomainstring
AKAN_PUBLIC_SERVE_DOMAIN에서 읽습니다. 필수입니다.
environment"testing" | "debug" | "develop" | "main" | "local"기본값 "debug"
AKAN_PUBLIC_ENV에서 읽습니다.
operationMode"local" | "edge" | "cloud" | "module"기본값 "cloud"
AKAN_PUBLIC_OPERATION_MODE에서 읽습니다. environment가 "local"이면 "local"입니다.
databaseMode"single" | "multiple" | "cluster" | undefined
AKAN_DATABASE_MODE에서 읽고, 없으면 앱이 선언한 모드입니다. 브라우저에서는 undefined입니다.
side"server" | "client"
지금 코드가 서버에서 도는지 브라우저에서 도는지 알려 줍니다.
renderMode"ssr" | "csr"기본값 "csr"
AKAN_PUBLIC_RENDER_ENV에서 읽습니다.
apiPrefixstring기본값 "/api"
getApiPrefix()가 돌려주는 값입니다.
wsPrefixstring기본값 "/ws"
getWsPrefix()가 돌려주는 값입니다.
clientHttpUristring
http://localhost:8282 같은 웹 origin입니다. clientHost, clientPort로도 나뉘어 있습니다.
serverHttpUristring
prefix까지 붙은 API 주소입니다. 예: http://localhost:8282/api
serverWsUristring
경로가 없는 websocket origin입니다. 예: ws://localhost:8282
env로 앱의 공개 호스트를 만드는 helper입니다:
apps/myapp/srvkit/publicHost.ts
  • 서버와 브라우저 양쪽에서 동작합니다. 어느 쪽인지는 side로 알 수 있습니다.
  • 타입도 함께 export됩니다. ClientEnv는 getEnv()의 반환 타입, Environment는 환경 이름의 union, BackendEnv는 서버 옵션의 타입입니다.

getApiPrefix / getWsPrefix

getApiPrefix()는 signal이 붙는 경로를, getWsPrefix()는 그 아래 websocket 경로를 돌려줍니다. 앱이 두 경로를 옮길 수 있으므로 /api나 /ws를 직접 쓰지 말고 이 함수로 URL을 만듭니다.
위치설정
↳ 따르는 쪽
main.tsnew AkanApp({ prefix, websocketPrefix })
서버의 route, 그리고 서버가 렌더링하는 모든 페이지입니다.
akan.config.tsapi: { prefix, websocketPrefix }
서버가 렌더링하지 않는 미리 빌드한 CSR 셸과 네이티브 앱 번들입니다.
(설정 없음)"/api" · "/ws"
기본값입니다.
prefix 아래의 signal endpoint로 form을 보내는 OAuth 동의 페이지입니다:
libs/shared/page/oauth/consent/_index.tsx
  • 앞에는 슬래시가 있고 뒤에는 없습니다. 뒤에 "/path"를 붙이면 됩니다. 빈 값이나 / 하나는 설정하지 않은 것으로 봅니다.
  • websocket은 API prefix 아래에 있습니다. 클라이언트는 serverHttpUri 뒤에 getWsPrefix()를 붙인 곳에 연결하며, 기본값은 ws://localhost:8282/api/ws입니다.
  • URL을 만드는 자리에서 부릅니다. 옮긴 prefix는 브라우저까지 전달되므로 prop으로 내려줄 필요가 없습니다.

DataList

DataList는 light model을 담고 id로도 행을 찾는 목록입니다. slice가 store에 두는 목록인 <model>List, <model>InitList, <model>Selection이 모두 DataList입니다.
수정된 admin을 목록에 다시 넣는 store action입니다:
libs/shared/lib/admin/admin.store.ts
메서드
new DataList(rows)
배열로 목록을 만듭니다. id가 겹치면 마지막 행이 남습니다.
set(row)
같은 id의 행을 바꾸거나, 없으면 뒤에 붙입니다. 이 목록을 직접 바꾸고 그대로 돌려줍니다.
delete(id)
그 id의 행을 뺍니다. 이 목록을 직접 바꾸고 그대로 돌려줍니다.
save()
같은 행을 담은 새 DataList입니다. this.set()에는 이것을 넘깁니다.
get(id)pick(id)
그 id의 행입니다. 없으면 get은 undefined를 돌려주고 pick은 에러를 던집니다.
has(id)indexOf(id)at(idx)pickAt(idx)
id나 위치로 찾습니다. indexOf와 pickAt은 찾지 못하면 에러를 던집니다.
filterslicesort
새 DataList를 돌려줍니다.
mapforEachfindsomeeveryreduce
배열 메서드와 똑같습니다. for...of도 됩니다.
valueslength
행을 담은 일반 배열과 행 개수입니다.

MIT 라이선스 하에 배포되었습니다.

내 AI에 이 문서 연결하기

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js 모든 권리 보유.시스템 관리자bassman