sonamuFilterλ Entityμ νλλ₯Ό νμ
μμ νκ² νν°λ§νλ κΈ°λ₯μ
λλ€. νν° κ°λ₯ν νλμ νμ©λλ μ°μ°μκ° Entity μ μμμ μλμΌλ‘ μΆλ‘ λμ΄ μλͺ»λ νν°λ§ μλλ₯Ό νμ
λ 벨과 λ°νμ λͺ¨λμμ λ°©μ§ν©λλ€.
sonamuFilterλ Entityμ propsλ₯Ό κΈ°λ°μΌλ‘ μλ μμ±λ©λλ€. κ°μ νλ(virtual fields)λ νν°λ§ λμμμ μ μΈλλ©°, FKλ₯Ό μμ±νλ κ΄κ³(BelongsToOne, hasJoinColumnμ΄ μλ OneToOne)λ {κ΄κ³λͺ
}_id ννλ‘ νν°λ§ν μ μμ΅λλ€.
κΈ°λ³Έ μ¬μ©λ²
// λ¨μ κ° νν° (eqμ λμΌ)
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
status: "in_progress"
}
});
// μ°μ°μ μ¬μ©
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
budget: { gt: 10000 },
name: { contains: "AI" }
}
});
νν° νμ
FilterQuery
κ° Entityμ λν΄ FilterQuery<T> νμ
μ΄ μλ μμ±λ©λλ€:
// μλ μμ±λ νμ
(sonamu.generated.ts)
export type ProjectListParams = {
// ...κΈ°μ‘΄ νλΌλ―Έν°λ€
sonamuFilter?: FilterQuery<FilterNumericOverride<ProjectBaseSchema>>;
};
FilterCondition
νλ νμ
μ λ°λΌ μ¬μ© κ°λ₯ν μ°μ°μκ° κ²°μ λ©λλ€:
type FilterCondition<T> =
| T // μ§μ κ° (eqμ λμΌ)
| { eq?: T } // κ°μ
| { ne?: T } // κ°μ§ μμ
| { gt?: T } // λ³΄λ€ νΌ
| { gte?: T } // λ³΄λ€ ν¬κ±°λ κ°μ
| { lt?: T } // λ³΄λ€ μμ
| { lte?: T } // λ³΄λ€ μκ±°λ κ°μ
| { in?: T[] } // ν¬ν¨
| { notIn?: T[] } // λ―Έν¬ν¨
| { between?: [T, T] } // λ²μ
| { contains?: string } // ν¬ν¨ (λ¬Έμμ΄)
| { startsWith?: string } // μμ (λ¬Έμμ΄)
| { endsWith?: string } // λ (λ¬Έμμ΄)
| { isNull?: boolean } // NULL μ¬λΆ
| { isNotNull?: boolean } // NOT NULL μ¬λΆ
| { before?: Date } // μ΄μ (λ μ§)
| { after?: Date } // μ΄ν (λ μ§)
νμ
λ³ μ§μ μ°μ°μ
| νμ
| μ§μ μ°μ°μ |
|---|
string | eq, ne, contains, startsWith, endsWith, in, notIn, isNull, isNotNull |
integer | eq, ne, gt, gte, lt, lte, in, notIn, between, isNull, isNotNull |
numeric | eq, ne, gt, gte, lt, lte, in, notIn, between, isNull, isNotNull |
boolean | eq, ne, isNull, isNotNull |
date | eq, ne, before, after, between, isNull, isNotNull |
datetime | eq, ne, before, after, between, isNull, isNotNull |
enum | eq, ne, in, notIn, isNull, isNotNull |
json | isNull, isNotNull |
μ¬μ© μμ
μ«μ νν°λ§
// μ νν κ°
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: { id: 1 }
});
// λΉκ΅ μ°μ°μ
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
budget: { gt: 10000 } // budget > 10000
}
});
// λ²μ 쑰건
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
budget: { between: [5000, 20000] } // 5000 <= budget <= 20000
}
});
// μ¬λ¬ κ° μ€ νλ
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
id: { in: [1, 2, 3] }
}
});
λ¬Έμμ΄ νν°λ§
// λΆλΆ μΌμΉ (LIKE '%keyword%')
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
name: { contains: "AI" }
}
});
// μ λμ¬ μΌμΉ (LIKE 'keyword%')
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
name: { startsWith: "Project" }
}
});
// μ λ―Έμ¬ μΌμΉ (LIKE '%keyword')
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
name: { endsWith: "2024" }
}
});
λ μ§ νν°λ§
// νΉμ λ μ§ μ΄μ
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
deadline: { before: new Date("2024-12-31") }
}
});
// νΉμ λ μ§ μ΄ν
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
created_at: { after: new Date("2024-01-01") }
}
});
// λ μ§ λ²μ
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
deadline: {
between: [new Date("2024-01-01"), new Date("2024-12-31")]
}
}
});
Enum νν°λ§
// λ¨μΌ κ°
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
status: "in_progress"
}
});
// μ¬λ¬ κ° μ€ νλ
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
status: { in: ["planning", "in_progress"] }
}
});
// νΉμ κ° μ μΈ
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
status: { notIn: ["cancelled", "completed"] }
}
});
NULL 체ν¬
// NULLμΈ κ²½μ°
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
description: { isNull: true }
}
});
// NULLμ΄ μλ κ²½μ°
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
description: { isNotNull: true }
}
});
λ³΅ν© μ‘°κ±΄ (AND)
// μ¬λ¬ νλ 쑰건μ ANDλ‘ κ²°ν©λ©λλ€
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
status: { in: ["planning", "in_progress"] },
budget: { gt: 5000 },
name: { contains: "AI" }
}
});
// WHERE status IN ('planning', 'in_progress')
// AND budget > 5000
// AND name LIKE '%AI%'
FK (Foreign Key) νν°λ§
FKλ₯Ό μμ±νλ κ΄κ³(BelongsToOne, hasJoinColumnμ΄ μλ OneToOne)λ {κ΄κ³λͺ
}_id ννλ‘ νν°λ§ν μ μμ΅λλ€. λ΄λΆμ μΌλ‘ integer νμ
μ κ°μ propμΌλ‘ λ³νλμ΄ λͺ¨λ μ«μ μ°μ°μλ₯Ό μ§μν©λλ€.
// νΉμ μ§μμ΄ λ΄λΉνλ νλ‘μ νΈ μ‘°ν
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
employee_id: 5 // employee κ΄κ³μ FK
}
});
// μ¬λ¬ μ§μμ΄ λ΄λΉνλ νλ‘μ νΈ μ‘°ν
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
employee_id: { in: [1, 2, 3] }
}
});
// λ΄λΉμκ° μλ νλ‘μ νΈ μ‘°ν
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
employee_id: { isNull: true }
}
});
// FKμ λ€λ₯Έ 쑰건 μ‘°ν©
const { rows } = await ProjectModel.findMany("A", {
sonamuFilter: {
employee_id: { in: [1, 2, 3] },
status: "in_progress",
budget: { gt: 10000 }
}
});
FK νν°λ§μ κ΄κ³λͺ
μ΄ μλ {κ΄κ³λͺ
}_id νμμ μ¬μ©ν΄μΌ ν©λλ€. μλ₯Ό λ€μ΄ employee κ΄κ³κ° μλ€λ©΄ employeeκ° μλ employee_idλ‘ νν°λ§ν©λλ€.
URL 쿼리 μ€νΈλ§
νλ‘ νΈμλμμ URL 쿼리 μ€νΈλ§μΌλ‘ νν°λ₯Ό μ λ¬ν μ μμ΅λλ€:
GET /api/projects?sonamuFilter[status]=in_progress&sonamuFilter[budget][gt]=10000
Fastifyκ° νμ±ν λ¬Έμμ΄μ μλμΌλ‘ μ μ ν νμ
μΌλ‘ λ³νλ©λλ€:
// URLμμ μ λ¬λ κ°
{ status: "in_progress", budget: { gt: "10000" } }
// μλ λ³ν ν
{ status: "in_progress", budget: { gt: 10000 } }
μ ν¨μ± κ²μ¦
sonamuFilterλ λ€μμ κ²μ¦ν©λλ€:
1. νν° κ°λ₯ν νλ
Entityμ propsμ μ μλ νλλ§ νν°λ§ν μ μμ΅λλ€. κ°μ νλλ νν°λ§ λμμμ μ μΈλ©λλ€. λ¨, FKλ₯Ό μμ±νλ κ΄κ³(BelongsToOne, hasJoinColumnμ΄ μλ OneToOne)λ {κ΄κ³λͺ
}_id ννλ‘ νν°λ§ν μ μμ΅λλ€.
// β
FKλ₯Ό μμ±νλ κ΄κ³λ {κ΄κ³λͺ
}_idλ‘ νν°λ§ κ°λ₯
await ProjectModel.findMany("A", {
sonamuFilter: { employee_id: { in: [1, 2, 3] } } // employee κ΄κ³μ FK
});
// β μλ¬: νλ 'employee'λ νν°λ§ν μ μμ΅λλ€ (κ΄κ³λͺ
μ체λ λΆκ°)
await ProjectModel.findMany("A", {
sonamuFilter: { employee: { eq: 1 } }
});
// β μλ¬: νλ 'virtual_test'λ νν°λ§ν μ μμ΅λλ€
await ProjectModel.findMany("A", {
sonamuFilter: { virtual_test: { eq: 1 } } // κ°μ νλ
});
2. μ§μνλ μ°μ°μ
νλ νμ
μ λ§λ μ°μ°μλ§ μ¬μ©ν μ μμ΅λλ€.
// β μλ¬: νλ 'name'(νμ
: string)λ 'between' μ°μ°μλ₯Ό μ§μνμ§ μμ΅λλ€
await ProjectModel.findMany("A", {
sonamuFilter: { name: { between: ["a", "z"] } }
});
3. Enum κ°
Enum νμ
μ νλλ μ μλ κ°λ§ νμ©ν©λλ€.
// β μλ¬: νλ 'status'μ κ° 'invalid_status'λ μ ν¨νμ§ μμ΅λλ€
await ProjectModel.findMany("A", {
sonamuFilter: { status: "invalid_status" }
});
κΈ°μ‘΄ νν°μμ κ΄κ³
sonamuFilterλ κΈ°μ‘΄ ListParamsμ 컀μ€ν
νν°μ ν¨κ» μ¬μ©ν μ μμ΅λλ€:
const { rows } = await ProjectModel.findMany("A", {
// κΈ°μ‘΄ 컀μ€ν
νν°
id: [1, 2, 3],
status: "active",
// sonamuFilter
sonamuFilter: {
budget: { gt: 10000 },
name: { contains: "AI" }
}
});
κΈ°μ‘΄ νν°μ sonamuFilterκ° κ°μ νλλ₯Ό νν°λ§νλ©΄ λ μ‘°κ±΄μ΄ λͺ¨λ ANDλ‘ μ μ©λ©λλ€. μλμΉ μμ κ²°κ³Όλ₯Ό λ°©μ§νλ €λ©΄ κ°μ νλμ λν μ€λ³΅ νν°λ§μ νΌνμΈμ.
λ΄λΆ λμ
1. νν° μ κ·ν (normalizeFilterQuery)
URL 쿼리 μ€νΈλ§μΌλ‘ μ λ¬λ λ¬Έμμ΄ κ°μ μ μ ν νμ
μΌλ‘ λ³νν©λλ€.
normalizeFilterQuery({ budget: { gt: "10000" } })
// β { budget: { gt: 10000 } }
2. μ ν¨μ± κ²μ¦ (validateSonamuFilters)
Entityμ νν° λ©νλ°μ΄ν°λ₯Ό κΈ°λ°μΌλ‘ νν° μΏΌλ¦¬λ₯Ό κ²μ¦ν©λλ€.
3. 쿼리 μ μ© (applySonamuFilters)
κ²μ¦λ νν° μ‘°κ±΄μ Puri 쿼리 λΉλμ μ μ©ν©λλ€.
// BaseModelClass λ΄λΆ ꡬν
protected applySonamuFilters<TEntity>(
qb: Puri<any, any, any>,
filters?: FilterQuery<TEntity>,
): void {
if (!filters) return;
const entity = EntityManager.get(this.modelName);
const filterableProps = entity.getFilterableProps();
// 1. νν° κ²μ¦
validateSonamuFilters(filters, filterableProps);
// 2. νν° μ μ©
for (const [field, condition] of Object.entries(filters)) {
const fullField = entity.getFullFieldName(field);
if (typeof condition !== "object") {
qb.where(fullField, condition);
} else {
for (const [operator, value] of Object.entries(condition)) {
this.applyOperator(qb, fullField, operator, value);
}
}
}
}
λ€μ λ¨κ³