sveltekit-conventions
npx skills add https://github.com/dev-goraebap/sveltekit-server-skill --skill sveltekit-conventions
Agent 安装分布
Skill 文档
SvelteKit Server Layer Architecture
1. ì¶ì² 기ì ì¤í
| ìì | ì¶ì² | ì´ì |
|---|---|---|
| ORM | Drizzle ORM | ê²½ë, íì ìì , SQL-like APIë¡ ì§ê´ì |
| ì¸ì¦ | better-auth | ì¸ì ê´ë¦¬, OTP, ìì ë¡ê·¸ì¸ ë± ë´ì¥ |
íë¡ì í¸ ìí©ì ë°ë¼ ë¤ë¥¸ ë구를 ì íí ì ìë¤. ìë ìí¤í ì² í¨í´ì ORMì 무ê´íê² ì ì©ëë¤.
2. ì Active Recordì¸ê°
SvelteKitì NestJS, Spring Bootì²ë¼ OOP ê¸°ë° DI 컨í
ì´ë를 ì ê³µíì§ ìëë¤. ì´ íê²½ìì ORMì ì§ì ì¬ì©íë©´ ëì¼í DB ì¡°ì ë¡ì§ì´ +server.ts, +page.server.ts ì¬ê¸°ì 기 무ë¶ë³íê² í¼ì§ë¤.
Active Record í¨í´ì¼ë¡ ëë©ì¸ ë¡ì§ì 모ë¸ì 캡ìííë©´ ì´ ë¬¸ì 를 í´ê²°íë¤.
- 모ë¸ì´ ìì²´ì ì¼ë¡ ORMì importíë¯ë¡ DI ìì´ë ìì§ë ëì ì½ëê° ëë¤.
+server.tsììë ëë©ì¸ 모ë¸ì ë©ìëë§ í¸ì¶íë¤. SQL/ORM ì½ëê° ë¼ì°í¸ íì¼ì ë ¸ì¶ëì§ ìëë¤.- í ì´ë¸ ë¨ìë¡ ì± ìì´ ë¶ë¦¬ëì´ ë³ê²½ ìí¥ ë²ìê° ëª ííë¤.
3. ìë² ë ì´ì´ 구조
$lib/server/
db/ â ORM ì¤ì + ìë¸ëë©ì¸ë³ ì¤í¤ë§
index.ts â DB ì°ê²° ì¸ì¤í´ì¤
organization-schema.ts â ì: positions, departments, members
auth-schema.ts â ì: user, session, account
leave-schema.ts â ì: leaveTypes, leaveUsages
approval-schema.ts â ì: approvalRequests, approvalSteps
domain/ â Active Record ëª¨ë¸ (ìë¸ëë©ì¸ë³ í´ë)
organization/
member.ts
department.ts
position.ts
auth/
user.ts
leave/
leave-type.ts
leave-policy.ts
approval/
approval-request.ts
approval-chain-rule.ts
infra/
service/ â Query Service (ì¡°í ì ì© ë·°ëª¨ë¸)
member-query.service.ts
leave-query.service.ts
organization-query.service.ts
$lib/entities/ â ëë©ì¸ íì
+ ìì í¬í¼ (ìë²/í´ë¼ì´ì¸í¸ ê³µì )
4. ì¤í¤ë§ ì¡°ì§: ìë¸ëë©ì¸ ë¶ë¥
íë¡ì í¸ ëë©ì¸ì ë¶ìíì¬ ìë¸ëë©ì¸ì ìë³íê³ , ìë¸ëë©ì¸ë³ë¡ ì¤í¤ë§ íì¼ì ë¶ë¦¬íë¤.
ë¶ë¥ ìì¹
- í¨ê» ë³ê²½ëë í ì´ë¸ì íëì ìë¸ëë©ì¸ì¼ë¡ 묶ëë¤.
- ìë¸ëë©ì¸ ê° FKë íì©íë, ì¤í¤ë§ íì¼ì ë¶ë¦¬íë¤.
- íì¼ëª
ì
{ìë¸ëë©ì¸}-schema.tsíìì ë°ë¥¸ë¤.
ìì (HR ê´ë¦¬ ì±)
| ìë¸ëë©ì¸ | ì¤í¤ë§ íì¼ | í¬í¨ í ì´ë¸ |
|---|---|---|
| ì¡°ì§ ê´ë¦¬ | organization-schema.ts |
positions, departments, members |
| ì¸ì¦ | auth-schema.ts |
user, session, account, verification |
| ì°ì°¨ ê´ë¦¬ | leave-schema.ts |
leaveTypes, leaveUsages, leaveAdjustments |
| ê²°ì¬ | approval-schema.ts |
approvalChainRules, approvalRequests, approvalSteps |
5. Domain Model (Active Record)
ìë¸ëë©ì¸ í´ë íìì í ì´ë¸ ë¨ìë¡ ëª¨ë¸ì ë°°ì¹íë¤.
ìì¹
- ì기 í ì´ë¸ ëì CUD + ë¨ì ì¡°íë§ ë´ë¹íë¤.
create()ë ë´ë¶ìì íìê°(sortOrder, createdAt ë±)ì ìë ê³ì°íë¤. í¸ì¶ì¸¡ì ì¸ë¶ì¬íì ë ¸ì¶íì§ ìëë¤.- ëë©ì¸ ì ì±
ë¡ì§ë ì¬ê¸°ì ìì¹íë¤ (ì:
leave-policy.tsì ì°ì°¨ ì¼ì ê³ì°). - ë³µì¡í ì¡°í(í¬ë¡ì¤ ëë©ì¸ ì¡°ì¸, ì§ê³ ë±)ë ë£ì§ ìê³ Query Serviceë¡ ììíë¤.
- ORMì ì§ì importíë¤. DIê° íìíì§ ìë¤.
ìì
// $lib/server/domain/organization/department.ts
import { db } from '$lib/server/db';
import { departments } from '$lib/server/db/organization-schema';
import { eq, max } from 'drizzle-orm';
export class Department {
static async create(data: { name: string }) {
// sortOrder ìë ê³ì° â í¸ì¶ì¸¡ì ì´ë¥¼ ì íì ìë¤
const [last] = await db
.select({ maxSort: max(departments.sortOrder) })
.from(departments);
const sortOrder = (last?.maxSort ?? 0) + 1;
const [created] = await db
.insert(departments)
.values({ name: data.name, sortOrder })
.returning();
return created;
}
static async update(id: string, data: { name: string }) {
const [updated] = await db
.update(departments)
.set({ name: data.name })
.where(eq(departments.id, id))
.returning();
return updated;
}
static async delete(id: string) {
await db.delete(departments).where(eq(departments.id, id));
}
}
6. Query Service (ì¡°í ì ì©)
infra/service/ íìì ìì¹íë¤. íë©´ì íìí 뷰모ë¸ì ìì ë¡ê² ì¡°í©íì¬ ë°ííë¤.
ìì¹
- í¬ë¡ì¤ ëë©ì¸ ì¡°ì¸ íì© â ì¡°íë SQL ì¡°ì¸ì´ë¯ë¡ ëë©ì¸ ê²½ê³ë¥¼ ê°ì íì§ ìëë¤.
- ë·°ëª¨ë¸ ì¸í°íì´ì¤(íì
)ë ê°ì íì¼ì ì ìíë¤. íë¡ í¸ìì
import typeì¼ë¡ ì¬ì©íë¤ (ì»´íì¼ íì ì ê±°). - ë©ìëëª
ì ì©ë를 ëë¬ë¸ë¤:
listPage(),listOptions(),getDetail()ë±.
ìì
// $lib/server/infra/service/member-query.service.ts
import { db } from '$lib/server/db';
import { members } from '$lib/server/db/organization-schema';
import { departments, positions } from '$lib/server/db/organization-schema';
export interface MemberView {
id: string;
name: string;
departmentName: string | null;
positionName: string | null;
}
export interface MemberPage {
items: MemberView[];
total: number;
}
export class MemberQueryService {
static async listPage(params: {
page: number;
size: number;
search?: string;
}): Promise<MemberPage> {
// í¬ë¡ì¤ ëë©ì¸ ì¡°ì¸ â ì¡°í ì ì©ì´ë¯ë¡ íì©
const query = db
.select({
id: members.id,
name: members.name,
departmentName: departments.name,
positionName: positions.name,
})
.from(members)
.leftJoin(departments, eq(members.departmentId, departments.id))
.leftJoin(positions, eq(members.positionId, positions.id));
// ... íí°ë§, íì´ì§, ì´ ê±´ì ê³ì°
}
}
íì ê´ë¦¬
- ORM ì¤í¤ë§ìì íì
ì ì¶ì¶íë¤ (Drizzle:
InferSelectModel, Prisma: generated types ë±). $lib/entities/ììimport typeì¼ë¡ re-exportíë¤.import typeì SvelteKitì$lib/server/ë³´í¸ ì íì ê±¸ë¦¬ì§ ìëë¤.
7. ë°ì´í° íë¦ ê·ì¹
| ìí | ìì¹ | í¸ì¶ ëì |
|---|---|---|
| ì½ê¸° (R) | +page.server.ts load / +layout.server.ts load |
Query Service |
| ì°ê¸° (CUD) | routes/api/**/+server.ts REST ìëí¬ì¸í¸ |
Domain Model |
íµì¬ ê·ì¹
routes/api/**/+server.tsìì ì§ì ORM ì¡°ì ê¸ì§ â ë°ëì Domain Modelì íµí´.+page.server.tsloadìì ì§ì ORM ì¡°ì ê¸ì§ â ë°ëì Query Service를 íµí´.routes/api/ìì ì¡°íê° íìíë©´ Query Service를 ì¬ì©í´ë ëë¤.+page.server.tsìë loadë§ ëë¤. form actionsë ì¬ì©íì§ ìëë¤.- Layout load â ê²½ë ìµì ë°ì´í° (ì ë í¸, ëë¡ë¤ì´ ë± ê³µì 참조 ë°ì´í°)
- Page load â íì´ì§ ì ì© ë·°ëª¨ë¸ (목ë¡, ìì¸, íì´ì§ ë±)
ì form actions를 ì°ì§ ìëê°?
form actionsë SvelteKitì ê°íê² ê²°í©ëë¤. REST APIë¡ ë¶ë¦¬íë©´:
- ì¶í ë°±ìë를 ë³ë ìë²(NestJS, Go, Spring ë±)ë¡ ë¶ë¦¬í ë API ìëí¬ì¸í¸ë¥¼ ê·¸ëë¡ ë§ì´ê·¸ë ì´ì í ì ìë¤.
- Flutter, React Native ë± ì¸ë¶ í´ë¼ì´ì¸í¸ììë ëì¼ ìëí¬ì¸í¸ë¥¼ ì¬ì¬ì©í ì ìë¤.
- íë¡ í¸ìëì ë°±ìëì ê´ì¬ì¬ê° ëª íí ë¶ë¦¬ëë¤.
8. ìëí¬ì¸í¸ í¨í´
REST ìëí¬ì¸í¸ 구조 (routes/api/**/+server.ts)
import { json } from '@sveltejs/kit';
import { z } from 'zod';
import { Department } from '$lib/server/domain/organization/department';
const createSchema = z.object({ name: z.string().min(1) });
export const POST = async ({ request }) => {
const data = await request.json();
const result = createSchema.safeParse(data);
if (!result.success) {
return json({ error: 'ë¶ìëª
ì ì
ë ¥í´ì£¼ì¸ì.' }, { status: 400 });
}
const department = await Department.create(result.data);
return json(department);
};
ìì¹
- ê²ì¦ â ëë©ì¸ ëª¨ë¸ í¸ì¶ â JSON ìëµ í¨í´ì ë°ë¥¸ë¤.
- ê°
+server.tsë ë 립ì ì´ë¤. ë¤ë¥¸ ìëí¬ì¸í¸ë¥¼ í¸ì¶íì§ ìëë¤. - í¬ë¡ì¤ ëë©ì¸ ë°ì´í°ê° íìíë©´ ORM 쿼리 ë 벨ìì ì¡°ì¸íë¤.
- ìì² ê²ì¦ ë¼ì´ë¸ë¬ë¦¬ë ìì ë¡ê² ì ííë¤ (Zod, Valibot, ArkType ë±). ì¤í¤ë§ë
+server.tsíì¼ ìë¨ì ì ìíë¤.