Sonamu ํ๋ก์ ํธ๋ TypeScript๋ก ์์ฑ๋๋ฉฐ, API์ Web ๊ฐ๊ฐ ๋
๋ฆฝ์ ์ธ tsconfig.json์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ๊ฐ ํ๊ฒฝ์ ์ต์ ํ๋ ์ค์ ์ ์ฌ์ฉํฉ๋๋ค.
ํ๋ก์ ํธ ๊ตฌ์กฐ
API์ Web์ ๊ฐ๊ฐ ๋
๋ฆฝ์ ์ธ TypeScript ํ๋ก์ ํธ์
๋๋ค. ์ค์ ์ด ์๋ก ๋ถ๋ฆฌ๋์ด ์์ต๋๋ค.
API ์๋ฒ ์ค์
๊ธฐ๋ณธ tsconfig.json
{
"compilerOptions": {
/* Basic Options */
"target": "esnext",
"module": "esnext",
"outDir": "dist",
"sourceMap": true,
"lib": ["esnext", "dom"],
/* Strict Type-Checking Options */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
/* Additional Checks */
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
/* Module Resolution Options */
"moduleResolution": "bundler",
"esModuleInterop": true,
/* Experimental Options */
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
/* Advanced Options */
"forceConsistentCasingInFileNames": true,
"noErrorTruncation": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"noUncheckedIndexedAccess": true
},
"include": ["src/**/*.ts", "src/**/*.json"],
"exclude": [
"node_modules",
"dist/*",
"public",
// "src/**/*.test.ts", // ํ
์คํธ ํ์ผ๋ ํ์
์ฒดํฌํ๋ฏ๋ก ์ฃผ์ ์ฒ๋ฆฌ
"**/__mocks__/**",
"vite.config.ts"
]
}
์ฃผ์ ์ต์
์ค๋ช
๋ชจ๋ ์ค์
๋ฐ์ฝ๋ ์ดํฐ
Strict ๋ชจ๋
์ถ๊ฐ ์ฒดํฌ
{
"target": "esnext", // ์ต์ ECMAScript ๋ฌธ๋ฒ ์ฌ์ฉ
"module": "esnext", // ES ๋ชจ๋ ์ถ๋ ฅ
"moduleResolution": "bundler" // Vite/esbuild์ ํธํ
}
moduleResolution: โbundlerโ
- Node.js์
node ๋ชจ๋ ๋์ ๋ฒ๋ค๋ฌ ๋ชจ๋ ์ฌ์ฉ
- Vite์ esbuild๊ฐ ๋ชจ๋์ ํด์ํ๋ ๋ฐฉ์๊ณผ ์ผ์น
package.json์ exports ํ๋ ์ง์
moduleResolution: "node"๋ฅผ ์ฌ์ฉํ๋ฉด Vite์ ์ถฉ๋ํ ์ ์์ต๋๋ค.
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
Sonamu API์์ ํ์
- API ๋ฉ์๋์
@api ๋ฐ์ฝ๋ ์ดํฐ ์ฌ์ฉ
- ํธ๋์ญ์
์
@transactional ๋ฐ์ฝ๋ ์ดํฐ ์ฌ์ฉ
- ํ์ผ ์
๋ก๋์
@upload ๋ฐ์ฝ๋ ์ดํฐ ์ฌ์ฉ
์ฌ์ฉ ์์:import { BaseModelClass, api, transactional } from "sonamu";
class UserModelClass extends BaseModelClass<
UserSubsetKey,
UserSubsetMapping,
UserSubsetQueries
> {
@api({ httpMethod: "GET" })
async findById(subset: UserSubsetKey, id: number) {
const rdb = this.getPuri("r");
return rdb.table("users").where("id", id).first();
}
@api({ httpMethod: "POST" })
@transactional()
async save(data: UserSaveParams) {
const wdb = this.getDB("w");
return this.upsert(wdb, data);
}
}
export const UserModel = new UserModelClass();
Entity๋ entity.json ํ์ผ๋ก ์ ์ํ๋ฉฐ, TypeScript ๋ฐ์ฝ๋ ์ดํฐ๋ ์ฌ์ฉํ์ง ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ Entity ์ ์ํ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ์ธ์. {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
Sonamu๋ ๋ชจ๋ strict ์ต์
์ ํ์ฑํํฉ๋๋ค.
- ํ์
์์ ์ฑ ๊ทน๋ํ
- ๋ฐํ์ ์๋ฌ ์ฌ์ ๋ฐฉ์ง
- ์ฝ๋ ํ์ง ํฅ์
์์ธํ ๋ด์ฉ์ ํ์
์ฒดํฌ๋ฅผ ์ฐธ๊ณ ํ์ธ์.{
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
"noUncheckedIndexedAccess": true
}
์ฝ๋ ํ์ง ํฅ์ ์ต์
:
noUnusedLocals: ์ฌ์ฉํ์ง ์๋ ๋ณ์ ๊ฒฝ๊ณ
noUnusedParameters: ์ฌ์ฉํ์ง ์๋ ํ๋ผ๋ฏธํฐ ๊ฒฝ๊ณ
noImplicitReturns: ๋ชจ๋ ๊ฒฝ๋ก์์ return ํ์
noUncheckedIndexedAccess: ๋ฐฐ์ด/๊ฐ์ฒด ์ ๊ทผ ์ undefined ์ฒดํฌ
ํ์ฅ ์ค์ ํ์ผ๋ค
Sonamu API๋ ์ฝ๋ ์์ฑ์ ์ํด ์ถ๊ฐ tsconfig ํ์ผ์ ์ฌ์ฉํฉ๋๋ค.
tsconfig.schemas.json - ์คํค๋ง ์์ฑ์ฉ
api/tsconfig.schemas.json
{
"extends": "./tsconfig.json",
"include": ["src/sonamu.generated.ts"],
"exclude": ["src/**/*.types.ts"]
}
์ฉ๋:
sonamu.generated.ts ํ์ผ ์์ฑ
- Entity ์คํค๋ง ํ์
์์ฑ
- DB ์คํค๋ง โ TypeScript ํ์
๋ณํ
์ฌ์ฉ ์์ :# Sonamu๊ฐ ์๋์ผ๋ก ์ฌ์ฉ
pnpm sonamu entity:load
tsconfig.types.json - ํ์
์์ฑ์ฉ
{
"extends": "./tsconfig.json",
"include": ["src/**/*.types.ts"],
"references": [{ "path": "./tsconfig.schemas.json" }]
}
์ฉ๋:
- API ์๋ต ํ์
์์ฑ
- Request/Response ํ์
์ ์
- ํ๋ก ํธ์๋์ ํ์
๊ณต์
์ฌ์ฉ ์์ :# Web์ผ๋ก ํ์
๋๊ธฐํ
pnpm sonamu sync:types
Web (React) ์ค์
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src", "src/routeTree.gen.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
API์์ ์ฐจ์ด์
| ์ต์
| API | Web | ์ด์ |
|---|
moduleResolution | bundler | Bundler | Vite๊ฐ ๋ฒ๋ค๋ฌ ๋ฐฉ์ ์ฌ์ฉ |
jsx | - | react-jsx | React 17+ JSX Transform |
noEmit | false | true | Vite๊ฐ ๋น๋ ๋ด๋น |
isolatedModules | - | true | Vite์ esbuild ์๊ตฌ์ฌํญ |
paths | - | @/* | ์ ๋ ๊ฒฝ๋ก import |
API๋ moduleResolution: "bundler" (์๋ฌธ์), Web์ moduleResolution: "Bundler" (๋๋ฌธ์)๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ ๋ค Vite๊ฐ ๋ฒ๋ค๋ฌ ๋ฐฉ์์ผ๋ก ๋ชจ๋์ ํด์ํฉ๋๋ค.
Path Mapping
{
"paths": {
"@/*": ["./src/*"]
}
}
์ ๋ ๊ฒฝ๋ก๋ก importํ ์ ์์ต๋๋ค:
// โ ์๋ ๊ฒฝ๋ก
import { Button } from "../../../components/Button";
// โ
์ ๋ ๊ฒฝ๋ก
import { Button } from "@/components/Button";
์์ธํ ๋ด์ฉ์ Path Mapping์ ์ฐธ๊ณ ํ์ธ์.
ํ์
์ฒดํฌ ์คํ
cd api
# ํ์
์ฒดํฌ๋ง
pnpm tsc --noEmit
# Watch ๋ชจ๋
pnpm tsc --noEmit --watch
cd web
# ํ์
์ฒดํฌ๋ง
pnpm tsc --noEmit
# Watch ๋ชจ๋
pnpm tsc --noEmit --watch
# ํ๋ก์ ํธ ๋ฃจํธ์์
pnpm -r tsc --noEmit
# API์ Web์ ๋์์ ์ฒดํฌ
๊ฐ๋ฐ ์ค์๋ --watch ๋ชจ๋๋ก ์ค์๊ฐ ํ์
์ฒดํฌ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
์ผ๋ฐ์ ์ธ ๋ฌธ์ ํด๊ฒฐ
๋ชจ๋์ ์ฐพ์ ์ ์์
์ฆ์:Cannot find module 'sonamu' or its corresponding type declarations.
์์ธ:
node_modules๊ฐ ์ค์น๋์ง ์์
- ์๋ชป๋ import ๊ฒฝ๋ก
ํด๊ฒฐ:# ์์กด์ฑ ์ฌ์ค์น
pnpm install
# ํ์
์ ์ ํ์ธ
ls node_modules/sonamu/dist/*.d.ts
์ฆ์:Experimental support for decorators is a feature that is subject to change
์์ธ:
experimentalDecorators๊ฐ ๋นํ์ฑํ๋จ
ํด๊ฒฐ:{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
์ฆ์:Cannot use JSX unless the '--jsx' flag is provided.
์์ธ:
jsx ์ต์
์ด ์ค์ ๋์ง ์์
ํด๊ฒฐ:{
"compilerOptions": {
"jsx": "react-jsx"
}
}
React 17+ ํ์ ์ค์
Path Mapping ์๋ ์ ํจ
์ฆ์:Cannot find module '@/components/Button'
์์ธ:
paths ์ค์ ๋๋ฝ
baseUrl ์ค์ ํ์
ํด๊ฒฐ:{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Vite ์ค์ ๋ ํ์:import path from "path";
export default {
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
};
์ปค์คํฐ๋ง์ด์ง
์ถ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์
{
"compilerOptions": {
"lib": ["esnext", "dom", "dom.iterable", "webworker"]
}
}
๋ ์๊ฒฉํ ์ฒดํฌ
{
"compilerOptions": {
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}
์ด ์ต์
๋ค์ ๊ธฐ์กด ์ฝ๋์ ํธํ์ฑ ๋ฌธ์ ๋ฅผ ์ผ์ผํฌ ์ ์์ต๋๋ค.
include/exclude ํจํด
{
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.json"
],
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/__mocks__/**"
]
}
๋ค์ ๋จ๊ณ