사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
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 인자나 반환값에 담는 바이트입니다.
업로드 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에 둡니다.
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에 올라가지 않습니다.

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