메인 μ½˜ν…μΈ λ‘œ κ±΄λ„ˆλ›°κΈ°
SyncerλŠ” Sonamu의 μžλ™ 생성 μ‹œμŠ€ν…œμž…λ‹ˆλ‹€. 파일 변경을 κ°μ§€ν•˜κ³ , ν•„μš”ν•œ μ½”λ“œλ₯Ό μžλ™μœΌλ‘œ μƒμ„±ν•˜λ©°, νƒ€κ²Ÿ ν”„λ‘œμ νŠΈ(web, app λ“±)에 λ™κΈ°ν™”ν•©λ‹ˆλ‹€.

Syncerλž€?

파일 λ³€κ²½ 감지

Entity, Model, Types 파일 λ³€κ²½ 좔적Checksum 기반 효율적 감지

μžλ™ μ½”λ“œ 생성

νƒ€μž…, μŠ€ν‚€λ§ˆ, API ν΄λΌμ΄μ–ΈνŠΈ 생성Template 기반 생성

HMR 지원

개발 쀑 μ‹€μ‹œκ°„ 반영파일 λ¬΄νš¨ν™” 및 μž¬λ‘œλ“œ

λ©€ν‹° νƒ€κ²Ÿ 동기화

web, app λ“± μ—¬λŸ¬ ν”„λ‘œμ νŠΈ λ™κΈ°ν™”μžλ™ 파일 볡사

Syncer의 λ™μž‘ 흐름

κ°μ‹œ λŒ€μƒ 파일

Syncerκ°€ 변경을 μΆ”μ ν•˜λŠ” 파일 νŒ¨ν„΄μž…λ‹ˆλ‹€:
파일 νƒ€μž…νŒ¨ν„΄μš©λ„
Entitysrc/application/**/*.entity.json데이터 ꡬ쑰 μ •μ˜
Typessrc/application/**/*.types.tsνƒ€μž… μ •μ˜
Modelsrc/application/**/*.model.tsλΉ„μ¦ˆλ‹ˆμŠ€ 둜직
Framesrc/application/**/*.frame.tsν”„λ ˆμž„ 둜직
Functionssrc/application/**/*.functions.tsμœ ν‹Έ ν•¨μˆ˜
Generatedsrc/application/sonamu.generated.tsμƒμ„±λœ 베이슀 파일
Configsrc/sonamu.config.tsSonamu μ„€μ •
Workflowsrc/application/**/*.workflow.tsμ›Œν¬ν”Œλ‘œμš° μ •μ˜
i18nsrc/i18n/**/*.tsλ‹€κ΅­μ–΄ 파일
파일 νŒ¨ν„΄: file-patterns.tsμ—μ„œ κ΄€λ¦¬λ˜λ©°, checksum으둜 λ³€κ²½ 감지

μˆ˜λ™ 동기화

개발 μ„œλ²„ 없이 μˆ˜λ™μœΌλ‘œ 동기화할 수 μžˆμŠ΅λ‹ˆλ‹€.
# 전체 동기화
pnpm sonamu sync

# λ³€κ²½λœ 파일만 동기화
pnpm sonamu sync --check
μ‹€ν–‰ κ²°κ³Ό:
πŸ”„ Syncing files...
βœ… Generated: sonamu.generated.ts
βœ… Generated: sonamu.generated.sso.ts
βœ… Copied: web/src/services/user/user.types.ts
βœ… Copied: web/src/services/sonamu.generated.ts
βœ… Generated: services.generated.ts
βœ… Generated: sonamu.generated.http
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  All files are synced!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

μžλ™ 동기화 (HMR)

개발 μ„œλ²„ μ‹€ν–‰ μ€‘μ—λŠ” 파일 변경이 μžλ™μœΌλ‘œ κ°μ§€λ˜κ³  λ™κΈ°ν™”λ©λ‹ˆλ‹€.
# 개발 μ„œλ²„ μ‹œμž‘
pnpm dev

# 파일 λ³€κ²½ν•˜λ©΄ μžλ™ 동기화
# - user.entity.json λ³€κ²½ β†’ νƒ€μž… μž¬μƒμ„±
# - user.model.ts λ³€κ²½ β†’ API ν΄λΌμ΄μ–ΈνŠΈ μž¬μƒμ„±
HMR 둜그 μ˜ˆμ‹œ:
πŸ”„ Invalidated:
- src/application/user/user.model.ts (with 5 APIs)
- src/application/user/user.types.ts

βœ… Generated: sonamu.generated.sso.ts
βœ… Copied: web/src/services/user/user.types.ts
βœ… Generated: services.generated.ts

✨ HMR completed in 234ms

Syncer 이벀트

SyncerλŠ” EventEmitter둜 이벀트λ₯Ό λ°œμƒμ‹œν‚΅λ‹ˆλ‹€.
import { Sonamu } from "sonamu";

// HMR μ™„λ£Œ 이벀트 ꡬ독
Sonamu.syncer.eventEmitter.on("onHMRCompleted", () => {
  console.log("βœ… HMR completed!");
  // μΆ”κ°€ μž‘μ—… μˆ˜ν–‰...
});
이벀트 μ’…λ₯˜:
μ΄λ²€νŠΈλ°œμƒ μ‹œμ μ‚¬μš© μ˜ˆμ‹œ
onHMRCompletedHMR μ™„λ£Œ μ‹œν›„μ† μž‘μ—… 트리거

Syncer μ•‘μ…˜

파일 λ³€κ²½ μœ ν˜•μ— 따라 λ‹€λ₯Έ μ•‘μ…˜μ΄ μ‹€ν–‰λ©λ‹ˆλ‹€.

1. Entity λ³€κ²½ (handleEntityChange)

Entity 파일이 λ³€κ²½λ˜λ©΄ μŠ€ν‚€λ§ˆλ₯Ό μž¬μƒμ„±ν•©λ‹ˆλ‹€. 트리거: *.entity.json 파일 λ³€κ²½ μ•‘μ…˜:
  1. EntityManager μž¬λ‘œλ“œ
  2. μƒˆ Entityλ©΄ *.types.ts 생성
  3. μŠ€ν‚€λ§ˆ 파일 생성:
    • sonamu.generated.ts
    • sonamu.generated.sso.ts
  4. νƒ€κ²Ÿμ— 파일 볡사
// Syncer λ‚΄λΆ€ 둜직
async handleEntityChange(diffGroups, diffTypes) {
  await EntityManager.reload();
  
  // μƒˆ Entity νƒ€μž… 파일 생성
  if (entity.parentId === undefined && !(await exists(typeFilePath))) {
    await generateTemplate("init_types", { entityId });
  }
  
  // μŠ€ν‚€λ§ˆ 생성
  await this.actionGenerateSchemas();
}

2. Types/Functions/Generated λ³€κ²½

νƒ€μž… 파일이 λ³€κ²½λ˜λ©΄ νƒ€κ²Ÿμ— λ³΅μ‚¬ν•©λ‹ˆλ‹€. 트리거: *.types.ts, *.functions.ts, *.generated.ts λ³€κ²½ μ•‘μ…˜:
  1. λ³€κ²½λœ 파일 λͺ©λ‘ μˆ˜μ§‘
  2. 각 νƒ€κ²Ÿ(web, app)에 볡사
  3. sonamu importλ₯Ό ./sonamu.shared둜 λ³€κ²½
// 볡사 μ‹œ import λ³€κ²½
// Before (api)
import { BaseModelClass } from "sonamu";

// After (web/app)
import { BaseModelClass } from "./sonamu.shared";
sonamu.shared.ts: web/appμ—λŠ” sonamu νŒ¨ν‚€μ§€κ°€ μ—†μœΌλ―€λ‘œ, 곡톡 μœ ν‹Έλ¦¬ν‹°λ₯Ό shared 파일둜 μ œκ³΅ν•©λ‹ˆλ‹€.

3. Model/Frame λ³€κ²½

Model 파일이 λ³€κ²½λ˜λ©΄ API ν΄λΌμ΄μ–ΈνŠΈλ₯Ό μž¬μƒμ„±ν•©λ‹ˆλ‹€. 트리거: *.model.ts, *.frame.ts λ³€κ²½ μ•‘μ…˜:
  1. Model, Types, APIs μž¬λ‘œλ“œ
  2. API ν΄λΌμ΄μ–ΈνŠΈ 생성 (services.generated.ts)
  3. HTTP ν…ŒμŠ€νŠΈ 파일 생성 (sonamu.generated.http)
  4. SSR 파일 μž¬μƒμ„± (queries.generated.ts, entry-server.generated.tsx)
async handleModelOrFrameChange(diffGroups) {
  // λͺ¨λ“ˆ μž¬λ‘œλ“œ
  await this.autoloadModels();
  await this.autoloadTypes();
  await this.autoloadApis();
  
  // μ„œλΉ„μŠ€ 생성
  await this.actionGenerateServices(params);
  await this.actionGenerateHttps();
  
  // SSR 파일 μž¬μƒμ„±
  await generateTemplate("queries", {}, { overwrite: true });
  await generateTemplate("entry_server", {}, { overwrite: true });
}

4. Config λ³€κ²½

μ„€μ • 파일이 λ³€κ²½λ˜λ©΄ ν™˜κ²½ λ³€μˆ˜λ₯Ό λ™κΈ°ν™”ν•©λ‹ˆλ‹€. 트리거: sonamu.config.ts λ³€κ²½ μ•‘μ…˜:
  1. .sonamu.env 파일 생성/μ—…λ°μ΄νŠΈ
  2. 각 νƒ€κ²Ÿμ— 볡사
web/.sonamu.env
API_HOST=localhost
API_PORT=3000

5. Workflow λ³€κ²½

μ›Œν¬ν”Œλ‘œμš° 파일이 λ³€κ²½λ˜λ©΄ μž¬λ‘œλ“œν•©λ‹ˆλ‹€. 트리거: *.workflow.ts λ³€κ²½ μ•‘μ…˜: μ›Œν¬ν”Œλ‘œμš° μž¬λ‘œλ“œ 및 동기화

6. i18n λ³€κ²½

λ‹€κ΅­μ–΄ 파일이 λ³€κ²½λ˜λ©΄ SD νŒŒμΌμ„ μž¬μƒμ„±ν•©λ‹ˆλ‹€. 트리거: src/i18n/**/*.ts λ³€κ²½ μ•‘μ…˜:
  1. Locale νŒŒμΌμ„ νƒ€κ²Ÿμ— 볡사
  2. sd.generated.ts 생성 (api, web, app)

Checksum 기반 λ³€κ²½ 감지

SyncerλŠ” 파일의 checksum을 μ €μž₯ν•˜μ—¬ 효율적으둜 변경을 κ°μ§€ν•©λ‹ˆλ‹€.
// .sonamu-checksums.json
{
  "src/application/user/user.entity.json": "a7f4b3e2c1d5...",
  "src/application/user/user.types.ts": "c3d1e4f5a6b7...",
  "src/application/user/user.model.ts": "e5f6a7b8c9d0..."
}
μž‘λ™ 방식:
  1. 파일 λ‚΄μš©μ˜ SHA-256 ν•΄μ‹œ 계산
  2. μ €μž₯된 checksumκ³Ό 비ꡐ
  3. λ‹€λ₯΄λ©΄ λ³€κ²½μœΌλ‘œ νŒλ‹¨
  4. 동기화 ν›„ checksum κ°±μ‹ 
μž₯점:
  • λΉ λ₯Έ λ³€κ²½ 감지 (파일 λ‚΄μš© 비ꡐ λΆˆν•„μš”)
  • μ •ν™•ν•œ λ³€κ²½ 좔적 (timestamp 영ν–₯ μ—†μŒ)
  • μ—¬λŸ¬ 파일 λ™μ‹œ λ³€κ²½ 처리

Syncer API

SyncerλŠ” ν”„λ‘œκ·Έλž˜λ° λ°©μ‹μœΌλ‘œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μˆ˜λ™ 동기화

import { Sonamu } from "sonamu";

// 전체 동기화
await Sonamu.syncer.sync();

// νŠΉμ • 파일 동기화
await Sonamu.syncer.syncFromWatcher(
  "change",
  "/path/to/user.entity.json"
);

ν…œν”Œλ¦Ώ 생성

// Entity 생성
await Sonamu.syncer.createEntity({
  entityId: "User",
  table: "users",
  props: [
    { name: "id", type: "integer" },
    { name: "email", type: "string" },
  ],
  subsets: {
    A: ["id", "email"],
  },
  enums: {},
  indexes: [],
});

// ν…œν”Œλ¦Ώ 생성
await Sonamu.syncer.generateTemplate(
  "model",
  { entityId: "User" },
  { overwrite: false }
);

// μ»€μŠ€ν…€ ν…œν”Œλ¦Ώ λ Œλ”λ§
const files = await Sonamu.syncer.renderTemplate(
  "view_list",
  { entityId: "User" }
);

λͺ¨λ“ˆ λ‘œλ“œ

// νƒ€μž… λ‘œλ“œ
await Sonamu.syncer.autoloadTypes();
console.log(Sonamu.syncer.types);

// Model λ‘œλ“œ
await Sonamu.syncer.autoloadModels();
console.log(Sonamu.syncer.models);

// API λ‘œλ“œ
await Sonamu.syncer.autoloadApis();
console.log(Sonamu.syncer.apis);

// Workflow λ‘œλ“œ
await Sonamu.syncer.autoloadWorkflows();
console.log(Sonamu.syncer.workflows);

Syncer μ„€μ •

sonamu.config.tsμ—μ„œ Syncer λ™μž‘μ„ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
api/src/sonamu.config.ts
export default {
  sync: {
    // 동기화 νƒ€κ²Ÿ ν”„λ‘œμ νŠΈ
    targets: ["web", "app"],
  },
  
  // API μ„œλ²„ μ„€μ • (ν™˜κ²½ λ³€μˆ˜ 생성에 μ‚¬μš©)
  server: {
    baseUrl: "http://localhost:3000",
    listen: {
      host: "localhost",
      port: 3000,
    },
  },
};

개발 μ›Œν¬ν”Œλ‘œμš°

Syncerλ₯Ό ν™œμš©ν•œ 일반적인 개발 νλ¦„μž…λ‹ˆλ‹€:
1

개발 μ„œλ²„ μ‹œμž‘

pnpm dev
Syncerκ°€ 파일 변경을 κ°μ‹œν•©λ‹ˆλ‹€.
2

Entity μ •μ˜

Sonamu UIμ—μ„œ Entityλ₯Ό μ •μ˜ν•˜κ±°λ‚˜ .entity.json νŒŒμΌμ„ μˆ˜μ •ν•©λ‹ˆλ‹€.μžλ™ μ‹€ν–‰:
  • *.types.ts 생성
  • sonamu.generated.ts κ°±μ‹ 
  • sonamu.generated.sso.ts κ°±μ‹ 
  • νƒ€κ²Ÿμ— 파일 볡사
3

Model μž‘μ„±

λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ Model에 μž‘μ„±ν•˜κ³  @api λ°μ½”λ ˆμ΄ν„°λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.μžλ™ μ‹€ν–‰:
  • services.generated.ts κ°±μ‹ 
  • sonamu.generated.http κ°±μ‹ 
  • SSR 파일 κ°±μ‹ 
4

ν”„λ‘ νŠΈμ—”λ“œ 개발

μƒμ„±λœ API ν΄λΌμ΄μ–ΈνŠΈμ™€ Typesλ₯Ό μ‚¬μš©ν•˜μ—¬ UIλ₯Ό κ°œλ°œν•©λ‹ˆλ‹€.
import { useUsers } from "@/services/services.generated";
import type { User } from "@/services/user/user.types";

function UserList() {
  const { data } = useUsers();
  // ...
}
5

μ‹€μ‹œκ°„ 반영

Modelμ΄λ‚˜ Entityλ₯Ό μˆ˜μ •ν•˜λ©΄ HMR둜 μ¦‰μ‹œ λ°˜μ˜λ©λ‹ˆλ‹€.λΈŒλΌμš°μ € μƒˆλ‘œκ³ μΉ¨ 없이 변경사항 확인 κ°€λŠ₯!

문제 ν•΄κ²°

Syncerκ°€ λ™μž‘ν•˜μ§€ μ•Šμ„ λ•Œ

확인 사항:
  1. 개발 μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지 확인
  2. 파일이 κ°μ‹œ λŒ€μƒ νŒ¨ν„΄μ— ν¬ν•¨λ˜λŠ”μ§€ 확인
  3. Checksum 파일 μ‚­μ œ ν›„ μž¬μ‹œλ„
rm .sonamu-checksums.json
pnpm sonamu sync
확인 사항:
  1. sonamu.config.ts의 sync.targets 확인
  2. νƒ€κ²Ÿ 디렉토리 쑴재 확인
  3. νƒ€κ²Ÿ 디렉토리에 src/services/ 디렉토리 생성
mkdir -p web/src/services
mkdir -p app/src/services
μ΅œμ ν™” 방법:
  1. Node.js μ΅œμ‹  버전 μ‚¬μš© (v22+)
  2. SSD μ‚¬μš© (HDDλŠ” 느림)
  3. λΆˆν•„μš”ν•œ 파일 μ œμ™Έ
  4. TypeScript ν”„λ‘œμ νŠΈ 뢄리 (monorepo)
원인: sonamu importκ°€ ./sonamu.shared둜 λ³€ν™˜λ˜μ§€ μ•ŠμŒν•΄κ²°:
# κ°•μ œ μž¬λ™κΈ°ν™”
pnpm sonamu sync --force

λ‹€μŒ 단계