사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
앱 & 라이브러리▾
도메인▾
스칼라▾
scalar.constant.ts
스칼라 constant는 가격이나 주소처럼 다른 모델 안에 들어가는 작은 값의 모양을 선언합니다. 그 값에 필드를 더하거나 빼거나 바꿀 때 이 파일을 엽니다.
service, signal, store 파일을 열지 않아도 읽힐 만큼 단순하게 유지합니다. 대부분은
via(), field(), 기본값과 선택 필드 몇 개, 가끔 작은 enum 하나면 충분합니다.이 페이지에서 쓰는 말
용어설명
스칼라
가격, 주소, 좌표처럼 다른 모델 안에 함께 저장되는 작은 값 객체입니다.
상위 모델
Price를 가진 Product처럼, 스칼라를 품고 있는 모델입니다.via()
필드 목록을 클래스로 만들어 주며,
akanjs/constant에서 가져옵니다.field(Type)
값 하나와 그 타입을 선언하고, 기본값 같은 옵션도 여기에 붙입니다.
enumOf()
허용하는 값의 고정 목록을 선언하며,
akanjs/base에서 가져옵니다.클래스는 다섯 개가 아니라 하나
모듈 constant는 레코드를 따로 저장하는 모델을 위한 것이라 클래스를 다섯 개 선언합니다. 스칼라는 따로 저장되는 레코드가 없으니 클래스 하나와 그 enum이면 됩니다:
model.constant.ts
lib/<model>/<model>.constant.ts- 클래스 다섯 개:
XInput → XObject → LightX → X → XInsight id,createdAt,updatedAt,removedAt기본 필드가 붙습니다.- 독립된 레코드로 저장됩니다.
scalar.constant.ts
lib/__scalar/<scalar>/<scalar>.constant.tsvia()클래스 하나와 그 enum이 전부입니다.- 기본 필드가 없습니다.
- 상위 모델 레코드의 일부로 저장됩니다.
기본 형태
via()에 값마다 field(Type)을 하나씩 돌려주는 함수를 넘기고, 그 결과를 상속한 클래스를 export합니다:apps/myapp/lib/__scalar/price/price.constant.ts
- 클래스 이름은 폴더 이름을 PascalCase로 바꾼 것입니다.
__scalar/price/는Price를,__scalar/contactInfo/는ContactInfo를 export합니다. - 쓰는 모델이 아니라 값 자체에 이름을 붙입니다.
Price는 상품, 주문, 청구서 어디에나 맞지만,ProductPrice는 그중 하나에 묶여 버립니다. via는akanjs/constant에서,Int,Float,ID,Any,enumOf는akanjs/base에서 가져옵니다.String,Boolean,Date는 전역이라 import가 필요 없습니다.
필드 타입
타입은 값이 무엇인지에 맞춰 고릅니다. 마지막 두 줄은 직접 만든 클래스입니다:
타입용도
StringBooleanDate
문자열, 참/거짓, 날짜와 시각입니다.
Int
개수나 수량 같은 정수입니다.
Float
금액이나 경도처럼 소수가 있는 수입니다.
ID
fileId처럼 다른 레코드의 id입니다.Any
모양이 정말로 정해지지 않은 값으로, 필드로 적을 수 없을 때만 씁니다.
Map
문자열 key로 값을 찾는 맵이며, 값 타입을
{ of: String }처럼 꼭 적습니다.Currency
같은 파일의
enumOf() 클래스로, 정해진 값 중 하나입니다.Coordinate
다른 스칼라로, 그 constant 파일에서 가져와 값으로 품습니다.


Number와 Binary는 필드 타입이 아닙니다. 숫자는 Int나 Float로 씁니다. field(Number)는 타입 검사를 통과하지 못합니다. 바이트는 필드에 저장할 수 없으니 File 모델을 참조합니다.기본값과 선택 필드
알맞은 시작 값이 있어야 하는 필드에는 기본값을 줍니다. 상위 모델이 그 필드 없이도 완전하다면
.optional()을 붙입니다:apps/myapp/lib/__scalar/price/price.constant.ts
currency의 기본값은 흔히 쓰는 값인"KRW"입니다. 그래서 새 가격은 채워진 채로 시작합니다.memo는 선택 필드입니다. 모든 가격에 메모가 필요하지는 않기 때문입니다.
새 값의 시작 값
기본값도
.optional()도 없는 필드는 필수입니다. 필드를 어떻게 쓰느냐에 따라 새 값의 시작 값과, 비워 둔 채 상위 모델을 저장할 수 있는지가 정해집니다:field(String)기본값 ""
필수라서, 비어 있으면 상위 모델의 폼이 저장되지 않습니다.
field(Float)기본값 0
필수지만
0도 값이라 그대로 저장되며, field(Int)도 같습니다.field(Boolean)기본값 false
필수지만
false도 값이라 그대로 저장됩니다.field(String, { default: "KRW" })기본값 "KRW"
필수지만 채워진 채로 시작하고, 지우면 저장이 막힙니다.
field(String).optional()기본값 null
선택 필드이며, 빈 문자열은
null로 저장됩니다.field([String])기본값 []
빈 목록도 올바른 값이라 그대로 저장됩니다.
- 단순한 값은 그대로, 만들어야 하는 값은 함수로 씁니다.
default: 0은 그대로 쓰고, 날짜는default: () => dayjs()로 써서 새 값마다 자기 시각을 갖게 합니다. default: ""를 주면 String 필드도 선택 필드가 됩니다.note: field(String, { default: "" })는 빈 메모를 받아들입니다.
배열 필드
값이 원래 같은 항목을 여러 개 담는다면 타입을 대괄호로 감쌉니다. 작은 예로, 연락처 하나에는 이메일이 여러 개일 수 있으므로
emails는 field([String])입니다:apps/myapp/lib/__scalar/contactInfo/contactInfo.constant.ts
- 배열은
[]로 시작합니다. 새ContactInfo의emails는null이 아니라 빈 목록입니다. - 어떤 필드 타입이든 배열로 만들 수 있습니다.
[Int],[Currency]같은 enum,[Coordinate]같은 다른 스칼라도 됩니다. - 개수는
minlength와maxlength로 제한합니다. 배열에서는 상위 모델의 폼이 저장할 수 있는 항목 수를 제한합니다. - 스칼라 자체가 여러 개라면 배열은 상위 모델 쪽에 둡니다. 상위 모델이
contacts: field([ContactInfo])로 쓰고, 스칼라는 연락처 하나로 남습니다.
enum 필드
필드에 정해진 값 중 하나만 들어가야 한다면
enumOf()를 씁니다. enum은 같은 파일의 스칼라 클래스 위에 선언합니다:apps/myapp/lib/__scalar/price/price.constant.ts
- enum 이름은 클래스 이름을 camelCase로 씁니다.
Currency는"currency",libs/shared의LeaveType은"leaveType"입니다. - dictionary가 이 이름을 그대로 씁니다.
price.dictionary.ts는.enum<Currency>("currency", …)에서 값마다 레이블을 붙이고, 다른 문자열을 쓰면 타입 에러입니다. - 짧고, 바뀌지 않고, 겹치지 않게 짓습니다. 컴포넌트가
l("currency.KRW")로 레이블을 읽으므로 이름을 바꾸면 깨지고, 다른 모델이나 enum과 같은 이름을 써서도 안 됩니다. - 값 목록 끝에는
as const를 붙입니다. 빠지면 모든 값이string으로 넓어져,default: "KRW"가 목록에 있는 값인지 검사하지 못합니다.
작은 헬퍼 메서드
동작이 값 자체에 속한다면 작은 메서드를 클래스에 둘 수 있습니다. 서버 요청, 데이터베이스 호출, 외부 서비스 없이 순수하게 유지합니다.
enum과 헬퍼 하나까지 넣은 파일 전체입니다:
apps/myapp/lib/__scalar/price/price.constant.ts
- 인스턴스 메서드는 값 하나를 다룹니다. 모델 인스턴스 안의 스칼라도 자기 클래스로 만들어지므로
product.price.isFree()처럼 부를 수 있습니다. - 여러 값을 함께 다루면
static메서드로 둡니다.libs/util의Coordinate.getDistanceKm(a, b)는 두 좌표 사이 거리를 잽니다.
constant 파일에 쓸 수 없는 것
이 파일은 서버와 브라우저 양쪽에서 실행되므로, lint는 양쪽 규칙을 모두 적용합니다:
쓸 수 없는 것이유와 대신 쓸 것
import dayjs from "dayjs"
외부 패키지이므로,
akanjs/base의 dayjs처럼 re-export된 것을 가져옵니다.*.service.ts*.document.tssrvkit/db
서버 전용 코드이며, 그 일은 service에 맡깁니다.
*.store.tsui/st
클라이언트 전용 코드이며, 그 일은 store와 컴포넌트에 맡깁니다.
#private
constant 파일에서는 쓸 수 없으니 TypeScript의
private 메서드로 씁니다.//!
이 주석은 번들에 실려 브라우저까지 가므로, 대신
// FIXME:를 씁니다.다음에 읽을 곳