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

akanjs/common

akanjs/common은 플랫폼에 기대지 않는 작은 도우미 모음입니다. 페이지, store, 서비스, CLI 스크립트 어디서나 같은 import로 씁니다.
import { Logger, sleep, isEmail } from "akanjs/common";
이 페이지에서 다루는 것
레벨이 있는 로그를 쓰고, 등록한 sink로 넘깁니다.
지정한 밀리초만큼 기다립니다.
첫 글자의 대소문자만 바꿉니다.
한국 전화번호에 대시를 넣고, 대시가 들어간 형식인지 검사합니다.
문자열이 이메일 주소 형식인지 검사합니다.
Akan 서버가 아닌 REST API를 호출합니다.
items[0].name 같은 경로로 중첩된 값을 읽고 씁니다.
목록에서 무작위로 하나 또는 여러 개를 고릅니다.
그 밖의 도우미
clamp
숫자를 min과 max 사이로 맞춥니다.
formatNumber
숫자 문자열에 천 단위 쉼표를 넣고, 소수 부분은 쓴 그대로 둡니다.
isValidDate
YYYY-MM-DD 문자열, Date, Dayjs가 날짜로 읽히는지 알려 주지만, 2024-02-30도 통과합니다.
isDayjs
값이 Dayjs인지 알려 줍니다.
splitVersionmergeVersion
"1.2.3"을 major, minor, patch로 나누고, 다시 합칩니다.
objectifyplainFieldsOf
메서드를 뺀 데이터 필드만 복사하며, 모델의 Date 필드까지 담는 것은 plainFieldsOf뿐입니다.
deepObjectify
중첩된 값까지 평범한 객체로 복사하며, serializable이나 convertDate를 주면 JSON으로 보낼 수 있는 형태가 됩니다.
decodeJwtPayload
서명을 확인하지 않고 JWT payload를 읽으므로, 권한 판단에 믿으면 안 됩니다.
isThenable
값을 await할 수 있는지 알려 줍니다.
interpolateTranslation
{name} 자리표시자를 채우고, 값이 없는 자리표시자는 그대로 둡니다.
같은 경로에는 프레임워크가 직접 쓰는 route 규칙 도우미와 통신 계약도 들어 있습니다. 앱 코드에서 쓸 일은 거의 없습니다.

Logger

레벨별로 로그를 남기는 Akan의 로거입니다. 서비스에는 클래스 이름이 붙은 this.logger가, adapt() 어댑터에는 등록 키 이름이 붙은 this.logger가 이미 있습니다. 그 밖의 곳에서는 new Logger("Name")를 만들거나 정적 메서드를 부릅니다.
다음은 인스턴스, 정적 호출, 구조화 레코드, sink를 모두 쓰는 스크립트입니다.
apps/myapp/script/syncInvoices.ts
  • 레벨은 낮은 것부터 여섯 가지입니다. trace, verbose, debug, info, warn, error 순입니다. 콘솔 레벨 이상인 줄만 출력되고, error는 stderr로 나갑니다.
  • 두 번째 인자는 context입니다. logger.warn("retrying charge", "stripe")는 메시지 앞에 [stripe]를 찍습니다.
  • 비밀처럼 보이는 키는 가려집니다. attrs 키에 password, token, secret, cookie, api key 같은 말이 들어 있으면 어떤 sink에 닿기 전에 [redacted]로 바뀝니다.
  • sink에는 항상 하한을 줍니다. minLevel이 없는 sink는 AKAN_LOG_FILE_LEVEL(기본 trace)까지 모든 레벨을 받으므로, verbose 호출마다 렌더링이 일어납니다.
메서드
logger.info(msg, context?)
trace, verbose, debug, info, warn, error 레벨마다 메서드가 하나씩 있습니다.
Logger.info(msg, context?, name?)
같은 메서드를 정적으로 부르며, name을 생략하면 App입니다.
Logger.setLevel(level)
실행 중에 콘솔 레벨을 바꿉니다.
Logger.shouldLog(level)
만들기 비싼 메시지를 조립하기 전에, 그 레벨의 줄이 어디로든 나가는지 알려 줍니다.
Logger.addSink(sink, { minLevel })
minLevel 이상의 레코드를 함수에 넘기고, 등록을 해제하는 함수를 돌려줍니다.
Logger.removeSink(sink)
그 sink로 레코드를 넘기지 않습니다.
Logger.emit({ level, name, message, attrs })
메시지 뒤에 key=value 속성을 붙인 레코드 하나를 씁니다.
환경 변수
AKAN_PUBLIC_LOG_LEVELLogLevel기본값 info
콘솔 레벨이며, 이보다 낮은 줄은 출력하지 않습니다.
AKAN_LOG_STDOUT_LEVELLogLevel기본값 AKAN_PUBLIC_LOG_LEVEL
컨테이너 stdout으로 나가는 레벨이며, 지정하면 AKAN_PUBLIC_LOG_LEVEL보다 우선합니다.
AKAN_LOG_FILE_LEVELLogLevel기본값 trace
minLevel을 정하지 않은 sink가 받는 가장 낮은 레벨입니다.

sleep

sleep(ms)는 ms밀리초 뒤에 resolve되는 Promise를 돌려줍니다. 폴링, 재시도 대기, 테스트, CLI의 클라우드 로그인 루프에서 씁니다.
공용 파일 도우미는 업로드가 uploading 상태를 벗어날 때까지 이렇게 폴링합니다.
libs/shared/webkit/addFileUntilActive.ts
  • 프로세스를 멈추지 않습니다. 기다리는 동안 다른 작업은 계속 돌고, await한 함수만 멈춥니다.

capitalize / lowerlize

첫 글자의 대소문자만 바꾸고 나머지는 그대로 둡니다. story 같은 모델 이름을 클래스식 Story로 바꾸거나 되돌릴 때 씁니다.
apps/myapp/common/modelNames.ts

formatPhone / isPhoneNumber

formatPhone은 입력 중인 한국 전화번호에 대시를 넣고, isPhoneNumber는 대시가 들어간 형식만 통과시킵니다. Field.Phone이 둘 다 이미 쓰므로 폼에서 직접 부를 일은 드뭅니다.
호출
↳ 결과
formatPhone("0101234567")
"010-123-4567"
formatPhone("010-123-45678")
"010-1234-5678"
formatPhone("01012345678")
"01012345678"
isPhoneNumber("010-1234-5678")
true
isPhoneNumber("031-123-4567")
true
isPhoneNumber("01012345678")
false
isPhoneNumber("02-1234-5678")
false
  • 지역번호가 아니라 글자 수를 봅니다. 10자면 3-3-4로, 13자면 대시를 지우고 3-4-4로 나눕니다. 대시 없는 11자리를 포함해 다른 길이는 그대로 돌려줍니다.
  • 10과 13인 이유. 010-123-4567 뒤에 숫자를 하나 더 치면 13자가 되고, 이때 010-1234-5678로 다시 나뉩니다.
  • 서울 02 번호는 맞지 않습니다. formatPhone("0212345678")은 021-234-5678이 되고, 대시를 넣은 02 번호도 isPhoneNumber를 통과하지 못합니다.
폼에서는 Field.Phone을 연결합니다. 입력하는 동안 형식을 맞추고, 잘못된 번호면 오류를 보여 줍니다.
apps/myapp/lib/user/User.Template.tsx

isEmail

isEmail은 문자열이 이메일 주소 형식인지 알려 줍니다. null, undefined, 빈 문자열에는 false를 돌려주므로 앞에서 따로 확인할 필요가 없습니다.
호출
↳ 결과
isEmail("user@example.com")
true
isEmail("user.name@example.co.kr")
true
isEmail("user+tag@example.com")
false
isEmail("user@example.c")
false
isEmail(null)
false
  • +는 받지 않습니다. @ 앞에는 영문자, 숫자, _, ., -만 올 수 있어서 +가 들어간 주소는 떨어집니다.
  • 마지막 도메인 조각은 2~8자여야 합니다. .com, .co.kr 같은 형태입니다.
  • Input.Email은 이미 이 검사를 합니다. 잘못된 이메일 메시지도 보여 줍니다. 버튼이나 동작을 막을 때만 직접 부릅니다.
공용 가입 폼도 같은 방식으로 버튼을 막습니다.
apps/myapp/lib/org/Org.Util.tsx

RestClient

RestClient는 Akan 서버가 아닌 REST API를 부르는 작은 fetch 래퍼입니다. base URL, 공통 헤더, 타임아웃을 한곳에 두고, JSON을 보내고 읽는 일을 대신합니다.
생성자 옵션
옵션 객체를 넘기거나 base URL만 넘깁니다. new RestClient("https://api.example.com")은 { baseUrl: "https://api.example.com" }의 줄임입니다.
baseUrlstring
상대 경로 앞에 붙고, http(s)로 시작하는 절대 URL에는 붙지 않습니다.
headersHeadersInit
모든 요청에 실리며, 호출에서 준 headers와 겹치면 호출 쪽을 씁니다.
timeoutnumber (ms)
이보다 오래 걸리는 요청을 중단하며, 없으면 제한이 없고 호출에서 준 timeout이 우선합니다.
메서드
get<T>(url, options?)
GET을 보내고 응답 본문으로 resolve합니다.
post<T>(url, data?, options?)
data를 본문에 담아 POST를 보냅니다.
put<T>(url, data?, options?)
data를 본문에 담아 PUT을 보냅니다.
delete<T>(url, options?)
DELETE를 보냅니다.
공통 헤더와 타임아웃을 두고, 호출 하나에만 헤더를 더하는 예시입니다.
apps/myapp/srvkit/exampleApi.ts
  • 요청 본문. 평범한 객체는 Content-Type: application/json과 함께 JSON으로 보냅니다. 문자열, FormData, URLSearchParams, Blob, ArrayBuffer는 그대로 보냅니다.
  • 응답. JSON content type이면 파싱하고, 나머지는 텍스트로 resolve합니다. 204와 빈 본문은 undefined입니다.
  • 실패. 2xx가 아닌 응답은 본문을 메시지로 담은 평범한 Error로 reject됩니다. 어댑터에서는 잡아서 logger.error로 남기고 null을 돌려줍니다.
  • 메서드는 네 가지뿐입니다. patch는 없습니다. options에는 credentials, cache 같은 다른 fetch 설정도 넣을 수 있습니다.

pathGet / pathSet

경로 문자열로 객체 깊숙한 곳의 값을 읽고 씁니다. 필드 이름을 변수로 들고 있을 때처럼, 경로가 코드가 아니라 데이터일 때 씁니다.
pathGet(path, obj, separator = ".", fallback = null)
path에 있는 값을 돌려주고, 중간에 값이 없거나 null이면 fallback을 돌려줍니다.
pathSet(obj, path, value)
없는 객체와 배열을 만들어 가며 value를 제자리에 쓰고, 같은 obj를 돌려줍니다.
같은 경로를 세 가지로 쓰는 예와, 없는 단계를 만들어 가며 쓰는 예입니다.
apps/myapp/common/profilePath.ts
  • 인자 순서가 다릅니다. pathGet은 경로가 먼저, pathSet은 객체가 먼저입니다.
  • 세 가지 표기, 같은 경로. links[0].url, links.0.url, ["links", 0, "url"]은 같은 값에 닿습니다. pathGet에 직접 separator를 주면 대괄호 표기는 꺼집니다.
  • Map 필드도 됩니다. Map 값은 속성이 아니라 get, set으로 읽고 씁니다.

randomPick / randomPicks

목록에서 무작위로 항목을 고릅니다. Akan의 테스트 데이터 생성기 sampleOf도 enum 필드를 randomPick으로 채웁니다.
randomPick(list)
무작위 항목 하나를 고르며, 빈 목록이면 undefined입니다.
randomPicks(list, count = 1, allowDuplicate = false)
무작위 항목 count개를 고르며, allowDuplicate를 켜지 않으면 같은 항목을 두 번 고르지 않습니다.
하나 고르기, 서로 다른 두 개 고르기, 중복을 허용해 세 개 고르기입니다.
apps/myapp/lib/story.signal.spec.ts
  • 짧은 목록은 그대로 돌아옵니다. 중복을 끈 채 count가 목록 길이 이상이면, 섞지도 복사하지도 않은 같은 배열을 돌려줍니다.
  • 비밀값에는 쓰지 않습니다. Math.random을 쓰므로 토큰이나 인증 코드를 만들면 안 됩니다.

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

내 AI에 이 문서 연결하기

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