summaryrefslogtreecommitdiff
path: root/components/client-table-v2/adapter/drizzle-table-adapter.ts
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-12-03 08:30:24 +0900
committerjoonhoekim <26rote@gmail.com>2025-12-03 08:30:24 +0900
commit5fea18182821dfcc3203c5ea4bb0548ec995718a (patch)
tree7faf241404f34dbd8cb4052b4be56137607eed41 /components/client-table-v2/adapter/drizzle-table-adapter.ts
parent7bdddbacf8610140c0c9db7ccb09d546203ce380 (diff)
(김준회) 서버사이드 페칭 작업을 위한 어댑터 초안
Diffstat (limited to 'components/client-table-v2/adapter/drizzle-table-adapter.ts')
-rw-r--r--components/client-table-v2/adapter/drizzle-table-adapter.ts173
1 files changed, 173 insertions, 0 deletions
diff --git a/components/client-table-v2/adapter/drizzle-table-adapter.ts b/components/client-table-v2/adapter/drizzle-table-adapter.ts
new file mode 100644
index 00000000..05bf4c5f
--- /dev/null
+++ b/components/client-table-v2/adapter/drizzle-table-adapter.ts
@@ -0,0 +1,173 @@
+import {
+ ColumnFiltersState,
+ SortingState,
+ PaginationState,
+ GroupingState,
+ ColumnDef
+} from "@tanstack/react-table";
+import {
+ SQL,
+ and,
+ or,
+ eq,
+ ilike,
+ gt,
+ lt,
+ gte,
+ lte,
+ inArray,
+ asc,
+ desc,
+ getTableColumns,
+} from "drizzle-orm";
+import { PgTable, PgView, PgColumn } from "drizzle-orm/pg-core";
+
+// Helper to detect if value is empty or undefined
+const isEmpty = (value: any) => value === undefined || value === null || value === "";
+
+export interface DrizzleTableState {
+ sorting?: SortingState;
+ columnFilters?: ColumnFiltersState;
+ globalFilter?: string;
+ pagination?: PaginationState;
+ grouping?: GroupingState;
+}
+
+export class DrizzleTableAdapter<TData> {
+ private columnMap: Map<string, PgColumn>;
+
+ constructor(
+ private table: PgTable | PgView,
+ private columns: ColumnDef<TData, any>[]
+ ) {
+ // Create a map of accessorKey -> Drizzle Column for fast lookup
+ this.columnMap = new Map();
+ // @ts-ignore - getTableColumns works on views in newer drizzle versions or we can cast
+ const drizzleColumns = getTableColumns(table as any);
+
+ columns.forEach(col => {
+ // We currently only support accessorKey which maps directly to a DB column
+ if ('accessorKey' in col && typeof col.accessorKey === 'string') {
+ const dbCol = drizzleColumns[col.accessorKey];
+ if (dbCol) {
+ this.columnMap.set(col.accessorKey, dbCol);
+ }
+ }
+ });
+ }
+
+ private getColumn(columnId: string): PgColumn | undefined {
+ return this.columnMap.get(columnId);
+ }
+
+ /**
+ * Build the WHERE clause based on column filters and global filter
+ */
+ getWhere(columnFilters?: ColumnFiltersState, globalFilter?: string): SQL | undefined {
+ const conditions: SQL[] = [];
+
+ // 1. Column Filters
+ if (columnFilters) {
+ for (const filter of columnFilters) {
+ const column = this.getColumn(filter.id);
+ if (!column) continue;
+
+ const value = filter.value;
+ if (isEmpty(value)) continue;
+
+ // Handle Array (range or multiple select)
+ if (Array.isArray(value)) {
+ // Range filter (e.g. [min, max])
+ if (value.length === 2 && (typeof value[0] === 'number' || typeof value[0] === 'string')) {
+ const [min, max] = value;
+ if (!isEmpty(min) && !isEmpty(max)) {
+ conditions.push(and(gte(column, min), lte(column, max))!);
+ } else if (!isEmpty(min)) {
+ conditions.push(gte(column, min)!);
+ } else if (!isEmpty(max)) {
+ conditions.push(lte(column, max)!);
+ }
+ }
+ // Multi-select (IN)
+ else if (value.length > 0) {
+ conditions.push(inArray(column, value)!);
+ }
+ }
+ // Boolean
+ else if (typeof value === 'boolean') {
+ conditions.push(eq(column, value)!);
+ }
+ // Number
+ else if (typeof value === 'number') {
+ conditions.push(eq(column, value)!);
+ }
+ // String (Search)
+ else if (typeof value === 'string') {
+ conditions.push(ilike(column, `%${value}%`)!);
+ }
+ }
+ }
+
+ // 2. Global Filter
+ if (globalFilter) {
+ const searchConditions: SQL[] = [];
+ this.columnMap.forEach((column) => {
+ // Implicitly supports only text-compatible columns for ilike
+ // Drizzle might throw if type mismatch, so user should be aware
+ searchConditions.push(ilike(column, `%${globalFilter}%`));
+ });
+
+ if (searchConditions.length > 0) {
+ conditions.push(or(...searchConditions)!);
+ }
+ }
+
+ return conditions.length > 0 ? and(...conditions) : undefined;
+ }
+
+ /**
+ * Build the ORDER BY clause
+ */
+ getOrderBy(sorting?: SortingState): SQL[] {
+ if (!sorting || !sorting.length) return [];
+
+ return sorting.map((sort) => {
+ const column = this.getColumn(sort.id);
+ if (!column) return null;
+ return sort.desc ? desc(column) : asc(column);
+ }).filter(Boolean) as SQL[];
+ }
+
+ /**
+ * Build the GROUP BY clause
+ */
+ getGroupBy(grouping?: GroupingState): SQL[] {
+ if (!grouping || !grouping.length) return [];
+
+ return grouping.map(g => this.getColumn(g)).filter(Boolean) as SQL[];
+ }
+
+ /**
+ * Get Limit and Offset
+ */
+ getPagination(pagination?: PaginationState) {
+ if (!pagination) return { limit: 10, offset: 0 };
+ return {
+ limit: pagination.pageSize,
+ offset: pagination.pageIndex * pagination.pageSize,
+ };
+ }
+
+ /**
+ * Helper to apply all state to a query builder.
+ * Returns the modifier objects that can be passed to drizzle query builder.
+ */
+ getQueryParts(state: DrizzleTableState) {
+ return {
+ where: this.getWhere(state.columnFilters, state.globalFilter),
+ orderBy: this.getOrderBy(state.sorting),
+ groupBy: this.getGroupBy(state.grouping),
+ ...this.getPagination(state.pagination)
+ };
+ }
+}