@makitt.io/mds-mcp-server 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -34
- package/dist/catalog.d.ts +16 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +383 -0
- package/dist/catalog.js.map +1 -0
- package/dist/data/catalog.json +41643 -4127
- package/dist/data/playbook/ai-fill.md +61 -48
- package/dist/data/playbook/anti-patterns.md +112 -110
- package/dist/data/playbook/array-input.md +94 -49
- package/dist/data/playbook/async-states.md +71 -61
- package/dist/data/playbook/data-grid.md +118 -101
- package/dist/data/playbook/feedback.md +103 -84
- package/dist/data/playbook/form.md +164 -134
- package/dist/data/playbook/overlay.md +97 -88
- package/dist/data/playbook/page-layout.md +95 -76
- package/dist/data/playbook/responsive-tokens.md +77 -58
- package/dist/data/recipes/admin-list-page.md +86 -0
- package/dist/data/recipes/async-state-section.md +60 -0
- package/dist/data/recipes/dashboard-overview.md +65 -0
- package/dist/data/recipes/detail-drawer.md +69 -0
- package/dist/data/recipes/modal-form.md +67 -0
- package/dist/data/recipes/settings-form-page.md +79 -0
- package/dist/index.d.ts +4 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -339
- package/dist/index.js.map +1 -1
- package/dist/loaders.d.ts +8 -0
- package/dist/loaders.d.ts.map +1 -0
- package/dist/loaders.js +120 -0
- package/dist/loaders.js.map +1 -0
- package/dist/recipes.d.ts +13 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/recipes.js +82 -0
- package/dist/recipes.js.map +1 -0
- package/dist/responses.d.ts +8 -0
- package/dist/responses.d.ts.map +1 -0
- package/dist/responses.js +25 -0
- package/dist/responses.js.map +1 -0
- package/dist/text.d.ts +4 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +20 -0
- package/dist/text.js.map +1 -0
- package/dist/tool-definitions.d.ts +3 -0
- package/dist/tool-definitions.d.ts.map +1 -0
- package/dist/tool-definitions.js +199 -0
- package/dist/tool-definitions.js.map +1 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +233 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +107 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +20 -16
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
list / table 데이터 표시에 대한 약속.
|
|
4
4
|
|
|
5
|
-
> mds 의 list 컴포넌트 = **DataGrid** (full-feature) + **Table** (custom
|
|
6
|
-
> 99% list 페이지 = DataGrid. Table 은 특수 case.
|
|
5
|
+
> mds 의 list 컴포넌트 = **DataGrid** (full-feature) + **Table** (custom
|
|
6
|
+
> column). 99% list 페이지 = DataGrid. Table 은 특수 case.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## 1. DataGrid vs Table 선택
|
|
11
11
|
|
|
12
|
-
| 케이스
|
|
13
|
-
|
|
14
|
-
| 셀러 admin 의 list 페이지 (customers / orders / products) | **DataGrid**
|
|
15
|
-
| URL state 동기화 필요 (deep link)
|
|
16
|
-
| Server-side filter/sort
|
|
17
|
-
| 단순 정보 표시 (정적 list, 보통 5-10 row)
|
|
18
|
-
| 페이지의 한 섹션 안 작은 list (예: 고객의 최근 주문 3개)
|
|
19
|
-
| 매우 custom layout (column 별 다른 component)
|
|
20
|
-
| Tree / nested rows
|
|
12
|
+
| 케이스 | 답 | 이유 |
|
|
13
|
+
| --------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
14
|
+
| 셀러 admin 의 list 페이지 (customers / orders / products) | **DataGrid** | filter / search / sort / pagination / selection / bulk action / loading / empty / error 자동 |
|
|
15
|
+
| URL state 동기화 필요 (deep link) | **DataGrid** | controlled props (page / sort / filter / search) 모두 노출 |
|
|
16
|
+
| Server-side filter/sort | **DataGrid** `mode="server"` | totalItems / onPageChange / onSortChange caller 처리 |
|
|
17
|
+
| 단순 정보 표시 (정적 list, 보통 5-10 row) | **Table** | DataGrid 의 toolbar 과잉 |
|
|
18
|
+
| 페이지의 한 섹션 안 작은 list (예: 고객의 최근 주문 3개) | **Table** | 페이지의 다른 컨텍스트와 통합 |
|
|
19
|
+
| 매우 custom layout (column 별 다른 component) | **Table** + caller column render |
|
|
20
|
+
| Tree / nested rows | **Tree** (compounds) — Table 아님 |
|
|
21
21
|
|
|
22
22
|
> **Default = DataGrid**. Table 은 의식적 결정.
|
|
23
23
|
|
|
@@ -34,7 +34,12 @@ list / table 데이터 표시에 대한 약속.
|
|
|
34
34
|
title="주문"
|
|
35
35
|
filters={[
|
|
36
36
|
{ value: 'all', label: '전체', count: 248, predicate: () => true },
|
|
37
|
-
{
|
|
37
|
+
{
|
|
38
|
+
value: 'pending',
|
|
39
|
+
label: '결제 대기',
|
|
40
|
+
count: 12,
|
|
41
|
+
predicate: (o) => o.status === 'pending',
|
|
42
|
+
},
|
|
38
43
|
]}
|
|
39
44
|
defaultFilter="all"
|
|
40
45
|
searchableKeys={['id', 'customer']}
|
|
@@ -52,7 +57,7 @@ list / table 데이터 표시에 대한 약속.
|
|
|
52
57
|
onRowClick={(o) => navigate(`/orders/${o.id}`)}
|
|
53
58
|
density="default"
|
|
54
59
|
stickyHeader
|
|
55
|
-
storageKey="orders-grid"
|
|
60
|
+
storageKey="orders-grid" // localStorage column widths / hidden
|
|
56
61
|
/>
|
|
57
62
|
```
|
|
58
63
|
|
|
@@ -69,28 +74,28 @@ const columns: ColumnDef<Order>[] = [
|
|
|
69
74
|
|
|
70
75
|
### Column 패턴 (셀러 admin 표준)
|
|
71
76
|
|
|
72
|
-
| Column 종류
|
|
73
|
-
|
|
74
|
-
| ID / 번호
|
|
75
|
-
| 사람 (고객 / 사용자) | `PersonCell`
|
|
76
|
-
| 상태
|
|
77
|
-
| 금액
|
|
78
|
-
| 날짜
|
|
79
|
-
| 액션
|
|
80
|
-
| 진행률
|
|
81
|
-
| 추세
|
|
77
|
+
| Column 종류 | 컴포넌트 | 예 |
|
|
78
|
+
| -------------------- | -------------------------------- | --------------------------------- |
|
|
79
|
+
| ID / 번호 | `Code size="xs"` | `#HE-2719` |
|
|
80
|
+
| 사람 (고객 / 사용자) | `PersonCell` | avatar + name + sub |
|
|
81
|
+
| 상태 | `Pill tone={...}` | success / warning / danger / info |
|
|
82
|
+
| 금액 | tabular-nums + right-align | `₩123,456` |
|
|
83
|
+
| 날짜 | `relativeDate` 또는 `formatDate` | `3일 전` / `2026-05-01` |
|
|
84
|
+
| 액션 | `IconButton` / `Menu` | 행 끝 |
|
|
85
|
+
| 진행률 | `ProgressRow` | bar + pct |
|
|
86
|
+
| 추세 | `Sparkline` | mini chart |
|
|
82
87
|
|
|
83
88
|
### Column key 명명 표준 (필수)
|
|
84
89
|
|
|
85
|
-
| 표준 key
|
|
86
|
-
|
|
87
|
-
| `id`
|
|
88
|
-
| `name` / `title`
|
|
89
|
-
| `customer`
|
|
90
|
-
| `status`
|
|
91
|
-
| `amount`
|
|
92
|
-
| `createdAt` / `updatedAt` | 날짜 (camelCase 필수)
|
|
93
|
-
| `actions`
|
|
90
|
+
| 표준 key | 의도 | 비고 |
|
|
91
|
+
| ------------------------- | ------------------------------- | ----------------------------------------------------------------------- |
|
|
92
|
+
| `id` | entity primary id | snake_case 금지 (`created_at` ❌ → `createdAt`) |
|
|
93
|
+
| `name` / `title` | entity 의 식별용 이름 / 제목 | 도메인 별 alias (`orderNumber` / `productName`) 는 의도가 명확하면 허용 |
|
|
94
|
+
| `customer` | actor (PersonCell) | — |
|
|
95
|
+
| `status` | Pill tone enum | — |
|
|
96
|
+
| `amount` | 금액 (right-align mono) | `total` 같은 alias 허용 단 의도 일관성 우선 |
|
|
97
|
+
| `createdAt` / `updatedAt` | 날짜 (camelCase 필수) | `created_at` 같은 snake_case 금지 |
|
|
98
|
+
| `actions` | row actions (IconButton / Menu) | 모든 list 의 마지막 column 으로 고정 |
|
|
94
99
|
|
|
95
100
|
→ **camelCase 필수**. snake_case (`created_at`) / mixed naming 금지.
|
|
96
101
|
|
|
@@ -101,17 +106,19 @@ const columns: ColumnDef<Order>[] = [
|
|
|
101
106
|
```ts
|
|
102
107
|
// 표준 순서 예 (order list)
|
|
103
108
|
const columns = [
|
|
104
|
-
{ key: 'id',
|
|
105
|
-
{ key: 'product',
|
|
106
|
-
{ key: 'customer',
|
|
107
|
-
{ key: 'status',
|
|
108
|
-
{ key: 'amount',
|
|
109
|
-
{ key: 'createdAt',
|
|
110
|
-
{ key: 'actions',
|
|
109
|
+
{ key: 'id', header: '주문 번호' }, // 1. entity-id
|
|
110
|
+
{ key: 'product', header: '상품' }, // 2. entity-info
|
|
111
|
+
{ key: 'customer', header: '고객' }, // 3. actor
|
|
112
|
+
{ key: 'status', header: '상태' }, // 4. status
|
|
113
|
+
{ key: 'amount', header: '금액', align: 'right' }, // 5. amount
|
|
114
|
+
{ key: 'createdAt', header: '주문일' }, // 6. metadata
|
|
115
|
+
{ key: 'actions', header: '', align: 'right' }, // 7. actions (마지막)
|
|
111
116
|
];
|
|
112
117
|
```
|
|
113
118
|
|
|
114
|
-
> 일부 도메인 (예: blog) 은 amount 가 없을 수 있음 → metadata 직전 으로 의도된
|
|
119
|
+
> 일부 도메인 (예: blog) 은 amount 가 없을 수 있음 → metadata 직전 으로 의도된
|
|
120
|
+
> 생략 OK. 단 — `id → ... → actions` 의 **앞 / 뒤 anchor** 는 모든 list 에서
|
|
121
|
+
> 동일.
|
|
115
122
|
|
|
116
123
|
### Actions column 활용
|
|
117
124
|
|
|
@@ -144,14 +151,14 @@ const columns = [
|
|
|
144
151
|
|
|
145
152
|
## 3. Mobile 변형 (Responsive 표준)
|
|
146
153
|
|
|
147
|
-
| Desktop (≥ md)
|
|
148
|
-
|
|
149
|
-
| DataGrid table 형
|
|
150
|
-
| Sticky header
|
|
151
|
-
| Toolbar (filter pills + search + actions) | toolbar collapse — filter drawer / search expand
|
|
152
|
-
| Pagination (page numbers)
|
|
153
|
-
| selection (checkbox column)
|
|
154
|
-
| actions column (Menu)
|
|
154
|
+
| Desktop (≥ md) | Mobile (< md) |
|
|
155
|
+
| ----------------------------------------- | ---------------------------------------------------------- |
|
|
156
|
+
| DataGrid table 형 | **Card list** (각 row → card, primary column 1-2개만 표시) |
|
|
157
|
+
| Sticky header | `position: sticky` 그대로 |
|
|
158
|
+
| Toolbar (filter pills + search + actions) | toolbar collapse — filter drawer / search expand |
|
|
159
|
+
| Pagination (page numbers) | infinite scroll 또는 numeric only |
|
|
160
|
+
| selection (checkbox column) | hidden — long-press 또는 명시 toggle |
|
|
161
|
+
| actions column (Menu) | swipe-actions 또는 row-bottom IconButton |
|
|
155
162
|
|
|
156
163
|
### Mobile 변환 mechanism
|
|
157
164
|
|
|
@@ -161,92 +168,101 @@ const columns = [
|
|
|
161
168
|
columns={columns}
|
|
162
169
|
// Mobile = card 형식 으로 자동 변환
|
|
163
170
|
mobileView={{
|
|
164
|
-
title: (row) => row.name,
|
|
165
|
-
subtitle: (row) => row.email,
|
|
166
|
-
statusKey: 'status',
|
|
167
|
-
actionsKey: 'actions',
|
|
171
|
+
title: (row) => row.name, // card primary
|
|
172
|
+
subtitle: (row) => row.email, // card secondary
|
|
173
|
+
statusKey: 'status', // status badge 표시 column
|
|
174
|
+
actionsKey: 'actions', // 우측 swipe actions
|
|
168
175
|
}}
|
|
169
176
|
/>
|
|
170
177
|
```
|
|
171
178
|
|
|
172
|
-
- **Default**: Tailwind preset 의 token responsive (text-3xl / space
|
|
173
|
-
|
|
179
|
+
- **Default**: Tailwind preset 의 token responsive (text-3xl / space-\* 등
|
|
180
|
+
mobile fallback 자동 — responsive-tokens.md)
|
|
181
|
+
- **DataGrid 의 mobile = card list 자동 변환** — `mobileView` prop 만 지정하면
|
|
182
|
+
caller 가 별도 layout 작성 없이 mobile 의 card 표시 가능
|
|
174
183
|
- **Breakpoint = md (768px)** — `useIsBreakpointUp('md')` 내부 활용
|
|
175
184
|
|
|
176
|
-
> 현재 상태 — DataGrid 의 `mobileView` prop 은 **TBD: mds 의 DataGrid 에 추가
|
|
185
|
+
> 현재 상태 — DataGrid 의 `mobileView` prop 은 **TBD: mds 의 DataGrid 에 추가
|
|
186
|
+
> 필요** (지금은 desktop only). 임시로는 caller 가 `useBreakpoint()` + 별도
|
|
187
|
+
> CardList 직접 구현.
|
|
177
188
|
|
|
178
189
|
---
|
|
179
190
|
|
|
180
191
|
## 4. Loading / Error / Empty state — async-states.md §1 참조
|
|
181
192
|
|
|
182
|
-
| state
|
|
183
|
-
|
|
184
|
-
| loading (초기 fetch)
|
|
185
|
-
| loading 더 보기 (page 2) | 부분 skeleton + 기존 row 유지
|
|
186
|
-
| error
|
|
187
|
-
| empty (필터 결과 0)
|
|
188
|
-
| empty (한 번도 없음)
|
|
193
|
+
| state | 컴포넌트 |
|
|
194
|
+
| ------------------------ | ------------------------------------------------------------------------- |
|
|
195
|
+
| loading (초기 fetch) | `<SkeletonRows>` (DataGrid 자동) |
|
|
196
|
+
| loading 더 보기 (page 2) | 부분 skeleton + 기존 row 유지 |
|
|
197
|
+
| error | `<ErrorState error={err} onRetry={refetch}>` (DataGrid `errorState` prop) |
|
|
198
|
+
| empty (필터 결과 0) | `<EmptyState variant="search" action={resetFilters}>` |
|
|
199
|
+
| empty (한 번도 없음) | `<EmptyState variant="onboarding" action={createX}>` |
|
|
189
200
|
|
|
190
201
|
---
|
|
191
202
|
|
|
192
203
|
## 5. 결정 표 (lookup)
|
|
193
204
|
|
|
194
|
-
| 케이스
|
|
195
|
-
|
|
196
|
-
| "page list 표시"
|
|
197
|
-
| "5개 정도 단순 table"
|
|
198
|
-
| "tree / nested"
|
|
199
|
-
| "row click → 상세"
|
|
200
|
-
| "row click → 옆 편집 (목록 유지)" | DataGrid `onRowClick={() => openDrawer(...)}`
|
|
201
|
-
| "URL filter / page 동기화"
|
|
202
|
-
| "Server-side"
|
|
203
|
-
| "bulk action"
|
|
204
|
-
| "column resize / hide / reorder"
|
|
205
|
-
| "column custom component"
|
|
206
|
-
| "특정 column 만 sortable"
|
|
207
|
-
| "1 필드 inline 편집"
|
|
205
|
+
| 케이스 | 답 |
|
|
206
|
+
| --------------------------------- | -------------------------------------------------------------------------------------- |
|
|
207
|
+
| "page list 표시" | DataGrid |
|
|
208
|
+
| "5개 정도 단순 table" | Table |
|
|
209
|
+
| "tree / nested" | Tree (compounds) |
|
|
210
|
+
| "row click → 상세" | DataGrid `onRowClick={navigate(...)}` |
|
|
211
|
+
| "row click → 옆 편집 (목록 유지)" | DataGrid `onRowClick={() => openDrawer(...)}` |
|
|
212
|
+
| "URL filter / page 동기화" | DataGrid controlled props (`page`, `onPageChange` etc.) |
|
|
213
|
+
| "Server-side" | DataGrid `mode="server" totalItems={N}` |
|
|
214
|
+
| "bulk action" | DataGrid `selectable bulkActions={(selected) => ...}` |
|
|
215
|
+
| "column resize / hide / reorder" | DataGrid `storageKey` + 자동 |
|
|
216
|
+
| "column custom component" | `column.render` prop |
|
|
217
|
+
| "특정 column 만 sortable" | `column.sortable: true` |
|
|
218
|
+
| "1 필드 inline 편집" | DataGrid column.render = controlled input + form.md §5 (Inline 편집 — Enter 명시 저장) |
|
|
208
219
|
|
|
209
220
|
---
|
|
210
221
|
|
|
211
222
|
## 6. 안티 패턴
|
|
212
223
|
|
|
213
|
-
- ❌ **DataGrid 안에 form (모든 row 의 input)** — 큰 form 은 별도 페이지
|
|
214
|
-
-
|
|
224
|
+
- ❌ **DataGrid 안에 form (모든 row 의 input)** — 큰 form 은 별도 페이지
|
|
225
|
+
(page-form). DataGrid 는 list / inline 편집 1 필드만
|
|
226
|
+
- ❌ **Table 으로 DataGrid 흉내** — toolbar / filter / pagination 다 caller 구현
|
|
227
|
+
= 중복. DataGrid 사용
|
|
215
228
|
- ❌ **DataGrid 의 column 50+** — 사용자 스캔 불가. 일부 hidden + caller 가 선택
|
|
216
|
-
- ❌ **Loading 시 empty state 표시** — async-states.md §2 — 로딩 중
|
|
217
|
-
|
|
229
|
+
- ❌ **Loading 시 empty state 표시** — async-states.md §2 — 로딩 중
|
|
230
|
+
ErrorState/EmptyState 금지
|
|
231
|
+
- ❌ **row 클릭 + checkbox 동시 작동** — 한쪽만. selection 모드 시 row click
|
|
232
|
+
막힘 또는 다른 의미
|
|
218
233
|
- ❌ **pagination size 가 50+** — render 부담 + a11y. 최대 20-30 권장
|
|
219
234
|
- ❌ **column header 가 너무 길거나 wrap** — fixed height, ellipsis
|
|
220
|
-
- ❌ **selection state 가 page 이동 후 사라짐** — DataGrid 의 selectedIds Set
|
|
235
|
+
- ❌ **selection state 가 page 이동 후 사라짐** — DataGrid 의 selectedIds Set
|
|
236
|
+
유지 (cross-page)
|
|
221
237
|
|
|
222
238
|
---
|
|
223
239
|
|
|
224
240
|
## 7. Cross-cutting
|
|
225
241
|
|
|
226
|
-
| Axis
|
|
227
|
-
|
|
228
|
-
| **Responsive** | DataGrid → card list mobile (TBD 자동 변환)
|
|
229
|
-
| **A11y**
|
|
230
|
-
| **i18n**
|
|
231
|
-
| **AI fill**
|
|
232
|
-
| **Catalog**
|
|
233
|
-
| **Telemetry**
|
|
242
|
+
| Axis | 적용 |
|
|
243
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
244
|
+
| **Responsive** | DataGrid → card list mobile (TBD 자동 변환) |
|
|
245
|
+
| **A11y** | `<table role="table">` 자동 / column header `<th scope="col">` / sortable button aria-sort / selection checkbox aria-label |
|
|
246
|
+
| **i18n** | column label / filter label / empty message 모두 `t()` |
|
|
247
|
+
| **AI fill** | DataGrid 의 filter 가 AI 자동 제안 가능 (Step 4.8) |
|
|
248
|
+
| **Catalog** | DataGrid props (columns / filters / mode / state props) 자동 catalog (Step 5) |
|
|
249
|
+
| **Telemetry** | row click / sort / filter / search / bulk action 자동 log (Step 7) |
|
|
234
250
|
|
|
235
251
|
---
|
|
236
252
|
|
|
237
253
|
## 8. apps/web 의 실 사용 (Step 7 Web Playbook 의 baseline)
|
|
238
254
|
|
|
239
|
-
| Page
|
|
240
|
-
|
|
241
|
-
| `/merchant/customers`
|
|
242
|
-
| `/merchant/orders`
|
|
243
|
-
| `/merchant/payment/transactions`
|
|
244
|
-
| `/merchant/payment/refunds`
|
|
245
|
-
| `/merchant/content/blogs` (list)
|
|
246
|
-
| `/merchant/content/announcements` | DataGrid
|
|
247
|
-
| `/merchant/content/banners`
|
|
248
|
-
| `/admin/dead-letters`
|
|
249
|
-
| `/admin/exchange-rates`
|
|
255
|
+
| Page | DataGrid 사용 |
|
|
256
|
+
| --------------------------------- | ------------------------------------------------------------------------- |
|
|
257
|
+
| `/merchant/customers` | DataGrid (filter / search / row click → drawer / bulk action) |
|
|
258
|
+
| `/merchant/orders` | DataGrid (filter / search / row click → detail page / status update bulk) |
|
|
259
|
+
| `/merchant/payment/transactions` | DataGrid (server mode + URL state) |
|
|
260
|
+
| `/merchant/payment/refunds` | 동일 |
|
|
261
|
+
| `/merchant/content/blogs` (list) | DataGrid |
|
|
262
|
+
| `/merchant/content/announcements` | DataGrid |
|
|
263
|
+
| `/merchant/content/banners` | DataGrid |
|
|
264
|
+
| `/admin/dead-letters` | DataGrid (admin) |
|
|
265
|
+
| `/admin/exchange-rates` | DataGrid (admin) |
|
|
250
266
|
|
|
251
267
|
→ 20+ 페이지가 DataGrid 사용. 균일성 보장 = Web Playbook (Step 7) 의 표준.
|
|
252
268
|
|
|
@@ -256,7 +272,8 @@ const columns = [
|
|
|
256
272
|
|
|
257
273
|
1. **Mobile 자동 변환** — DataGrid → card list 의 자동 변환 컴포넌트 (CardList?)
|
|
258
274
|
2. **Long-press multi-select** — 모바일 의 selection 패턴
|
|
259
|
-
3. **Inline 편집 컴포넌트** — DataGrid cell 의 EditCell wrapper 추가 (Enter /
|
|
275
|
+
3. **Inline 편집 컴포넌트** — DataGrid cell 의 EditCell wrapper 추가 (Enter /
|
|
276
|
+
체크 명시 저장)
|
|
260
277
|
4. **Sticky column** — first / last column 의 sticky (가로 scroll 시)
|
|
261
278
|
5. **Virtual scroll** — row 1000+ 시 virtualization (현재 mds 미지원)
|
|
262
279
|
6. **Group by** — row grouping (예: status 별 group)
|