@mdxui/terminal 2.0.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.
Files changed (191) hide show
  1. package/README.md +571 -0
  2. package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
  3. package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
  4. package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
  5. package/dist/chunk-3EFDH7PK.js +5235 -0
  6. package/dist/chunk-3RG5ZIWI.js +10 -0
  7. package/dist/chunk-3X5IR6WE.js +884 -0
  8. package/dist/chunk-4FV5ZDCE.js +5236 -0
  9. package/dist/chunk-4OVMSF2J.js +243 -0
  10. package/dist/chunk-63FEETIS.js +4048 -0
  11. package/dist/chunk-B43KP7XJ.js +884 -0
  12. package/dist/chunk-BMTJXWUV.js +655 -0
  13. package/dist/chunk-C3SVH4N7.js +882 -0
  14. package/dist/chunk-EVWR7Y47.js +874 -0
  15. package/dist/chunk-F6A5VWUC.js +1285 -0
  16. package/dist/chunk-FD7KW7GE.js +882 -0
  17. package/dist/chunk-GBQ6UD6I.js +655 -0
  18. package/dist/chunk-GMDD3M6U.js +5227 -0
  19. package/dist/chunk-JBHRXOXM.js +1058 -0
  20. package/dist/chunk-JFOO3EYO.js +1182 -0
  21. package/dist/chunk-JQ5H3WXL.js +1291 -0
  22. package/dist/chunk-JQD5NASE.js +234 -0
  23. package/dist/chunk-KRHJP5R7.js +592 -0
  24. package/dist/chunk-KWF6WVJE.js +962 -0
  25. package/dist/chunk-LHYQVN3H.js +1038 -0
  26. package/dist/chunk-M3TLQLGC.js +1032 -0
  27. package/dist/chunk-MVW4Q5OP.js +240 -0
  28. package/dist/chunk-NXCZSWLU.js +1294 -0
  29. package/dist/chunk-O25TNRO6.js +607 -0
  30. package/dist/chunk-PNECDA2I.js +884 -0
  31. package/dist/chunk-QIHWRLJR.js +962 -0
  32. package/dist/chunk-QW5YMQ7K.js +882 -0
  33. package/dist/chunk-R5U7XKVJ.js +16 -0
  34. package/dist/chunk-RP2MVQLR.js +962 -0
  35. package/dist/chunk-TP6RXGXA.js +1087 -0
  36. package/dist/chunk-TQQSTITZ.js +655 -0
  37. package/dist/chunk-X24GWXQV.js +1281 -0
  38. package/dist/components/index.d.ts +802 -0
  39. package/dist/components/index.js +149 -0
  40. package/dist/data/index.d.ts +2554 -0
  41. package/dist/data/index.js +51 -0
  42. package/dist/forms/index.d.ts +1596 -0
  43. package/dist/forms/index.js +464 -0
  44. package/dist/index-CQRFZntR.d.ts +867 -0
  45. package/dist/index.d.ts +579 -0
  46. package/dist/index.js +786 -0
  47. package/dist/interactive-D0JkWosD.d.ts +217 -0
  48. package/dist/keyboard/index.d.ts +2 -0
  49. package/dist/keyboard/index.js +43 -0
  50. package/dist/renderers/index.d.ts +546 -0
  51. package/dist/renderers/index.js +2157 -0
  52. package/dist/storybook/index.d.ts +396 -0
  53. package/dist/storybook/index.js +641 -0
  54. package/dist/theme/index.d.ts +1339 -0
  55. package/dist/theme/index.js +123 -0
  56. package/dist/types-Bxu5PAgA.d.ts +710 -0
  57. package/dist/types-CIlop5Ji.d.ts +701 -0
  58. package/dist/types-Ca8p_p5X.d.ts +710 -0
  59. package/package.json +90 -0
  60. package/src/__tests__/components/data/card.test.ts +458 -0
  61. package/src/__tests__/components/data/list.test.ts +473 -0
  62. package/src/__tests__/components/data/metrics.test.ts +541 -0
  63. package/src/__tests__/components/data/table.test.ts +448 -0
  64. package/src/__tests__/components/input/field.test.ts +555 -0
  65. package/src/__tests__/components/input/form.test.ts +870 -0
  66. package/src/__tests__/components/input/search.test.ts +1238 -0
  67. package/src/__tests__/components/input/select.test.ts +658 -0
  68. package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
  69. package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
  70. package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
  71. package/src/__tests__/components/navigation/tabs.test.ts +995 -0
  72. package/src/__tests__/components.test.tsx +1197 -0
  73. package/src/__tests__/core/compiler.test.ts +986 -0
  74. package/src/__tests__/core/parser.test.ts +785 -0
  75. package/src/__tests__/core/tier-switcher.test.ts +1103 -0
  76. package/src/__tests__/core/types.test.ts +1398 -0
  77. package/src/__tests__/data/collections.test.ts +1337 -0
  78. package/src/__tests__/data/db.test.ts +1265 -0
  79. package/src/__tests__/data/reactive.test.ts +1010 -0
  80. package/src/__tests__/data/sync.test.ts +1614 -0
  81. package/src/__tests__/errors.test.ts +660 -0
  82. package/src/__tests__/forms/integration.test.ts +444 -0
  83. package/src/__tests__/integration.test.ts +905 -0
  84. package/src/__tests__/keyboard.test.ts +1791 -0
  85. package/src/__tests__/renderer.test.ts +489 -0
  86. package/src/__tests__/renderers/ansi-css.test.ts +948 -0
  87. package/src/__tests__/renderers/ansi.test.ts +1366 -0
  88. package/src/__tests__/renderers/ascii.test.ts +1360 -0
  89. package/src/__tests__/renderers/interactive.test.ts +2353 -0
  90. package/src/__tests__/renderers/markdown.test.ts +1483 -0
  91. package/src/__tests__/renderers/text.test.ts +1369 -0
  92. package/src/__tests__/renderers/unicode.test.ts +1307 -0
  93. package/src/__tests__/theme.test.ts +639 -0
  94. package/src/__tests__/utils/assertions.ts +685 -0
  95. package/src/__tests__/utils/index.ts +115 -0
  96. package/src/__tests__/utils/test-renderer.ts +381 -0
  97. package/src/__tests__/utils/utils.test.ts +560 -0
  98. package/src/components/containers/card.ts +56 -0
  99. package/src/components/containers/dialog.ts +53 -0
  100. package/src/components/containers/index.ts +9 -0
  101. package/src/components/containers/panel.ts +59 -0
  102. package/src/components/feedback/badge.ts +40 -0
  103. package/src/components/feedback/index.ts +8 -0
  104. package/src/components/feedback/spinner.ts +23 -0
  105. package/src/components/helpers.ts +81 -0
  106. package/src/components/index.ts +153 -0
  107. package/src/components/layout/breadcrumb.ts +31 -0
  108. package/src/components/layout/index.ts +10 -0
  109. package/src/components/layout/list.ts +29 -0
  110. package/src/components/layout/sidebar.ts +79 -0
  111. package/src/components/layout/table.ts +62 -0
  112. package/src/components/primitives/box.ts +95 -0
  113. package/src/components/primitives/button.ts +54 -0
  114. package/src/components/primitives/index.ts +11 -0
  115. package/src/components/primitives/input.ts +88 -0
  116. package/src/components/primitives/select.ts +97 -0
  117. package/src/components/primitives/text.ts +60 -0
  118. package/src/components/render.ts +155 -0
  119. package/src/components/templates/app.ts +43 -0
  120. package/src/components/templates/index.ts +8 -0
  121. package/src/components/templates/site.ts +54 -0
  122. package/src/components/types.ts +777 -0
  123. package/src/core/compiler.ts +718 -0
  124. package/src/core/parser.ts +127 -0
  125. package/src/core/tier-switcher.ts +607 -0
  126. package/src/core/types.ts +672 -0
  127. package/src/data/collection.ts +316 -0
  128. package/src/data/collections.ts +50 -0
  129. package/src/data/context.tsx +174 -0
  130. package/src/data/db.ts +127 -0
  131. package/src/data/hooks.ts +532 -0
  132. package/src/data/index.ts +138 -0
  133. package/src/data/reactive.ts +1225 -0
  134. package/src/data/saas-collections.ts +375 -0
  135. package/src/data/sync.ts +1213 -0
  136. package/src/data/types.ts +660 -0
  137. package/src/forms/converters.ts +512 -0
  138. package/src/forms/index.ts +133 -0
  139. package/src/forms/schemas.ts +403 -0
  140. package/src/forms/types.ts +476 -0
  141. package/src/index.ts +542 -0
  142. package/src/keyboard/focus.ts +748 -0
  143. package/src/keyboard/index.ts +96 -0
  144. package/src/keyboard/integration.ts +371 -0
  145. package/src/keyboard/manager.ts +377 -0
  146. package/src/keyboard/presets.ts +90 -0
  147. package/src/renderers/ansi-css.ts +576 -0
  148. package/src/renderers/ansi.ts +802 -0
  149. package/src/renderers/ascii.ts +680 -0
  150. package/src/renderers/breadcrumb.ts +480 -0
  151. package/src/renderers/command-palette.ts +802 -0
  152. package/src/renderers/components/field.ts +210 -0
  153. package/src/renderers/components/form.ts +327 -0
  154. package/src/renderers/components/index.ts +21 -0
  155. package/src/renderers/components/search.ts +449 -0
  156. package/src/renderers/components/select.ts +222 -0
  157. package/src/renderers/index.ts +101 -0
  158. package/src/renderers/interactive/component-handlers.ts +622 -0
  159. package/src/renderers/interactive/cursor-manager.ts +147 -0
  160. package/src/renderers/interactive/focus-manager.ts +279 -0
  161. package/src/renderers/interactive/index.ts +661 -0
  162. package/src/renderers/interactive/input-handler.ts +164 -0
  163. package/src/renderers/interactive/keyboard-handler.ts +212 -0
  164. package/src/renderers/interactive/mouse-handler.ts +167 -0
  165. package/src/renderers/interactive/state-manager.ts +109 -0
  166. package/src/renderers/interactive/types.ts +338 -0
  167. package/src/renderers/interactive-string.ts +299 -0
  168. package/src/renderers/interactive.ts +59 -0
  169. package/src/renderers/markdown.ts +950 -0
  170. package/src/renderers/sidebar.ts +549 -0
  171. package/src/renderers/tabs.ts +682 -0
  172. package/src/renderers/text.ts +791 -0
  173. package/src/renderers/unicode.ts +917 -0
  174. package/src/renderers/utils.ts +942 -0
  175. package/src/router/adapters.ts +383 -0
  176. package/src/router/types.ts +140 -0
  177. package/src/router/utils.ts +452 -0
  178. package/src/schemas.ts +205 -0
  179. package/src/storybook/index.ts +91 -0
  180. package/src/storybook/interactive-decorator.tsx +659 -0
  181. package/src/storybook/keyboard-simulator.ts +501 -0
  182. package/src/theme/ansi-codes.ts +80 -0
  183. package/src/theme/box-drawing.ts +132 -0
  184. package/src/theme/color-convert.ts +254 -0
  185. package/src/theme/color-support.ts +321 -0
  186. package/src/theme/index.ts +134 -0
  187. package/src/theme/strip-ansi.ts +50 -0
  188. package/src/theme/tailwind-map.ts +469 -0
  189. package/src/theme/text-styles.ts +206 -0
  190. package/src/theme/theme-system.ts +568 -0
  191. package/src/types.ts +103 -0
@@ -0,0 +1,532 @@
1
+ /**
2
+ * @mdxui/terminal Data Hooks
3
+ *
4
+ * React hooks for reactive queries and mutations with optimistic updates.
5
+ * Provides type-safe hooks for querying and mutating database collections
6
+ * within a DBProvider context.
7
+ */
8
+
9
+ import { useState, useEffect, useCallback, useRef } from 'react'
10
+ import { useDBContext } from './context'
11
+ import type {
12
+ QueryOptions,
13
+ QueryResult,
14
+ MutationOptions,
15
+ MutationResult,
16
+ WhereClause,
17
+ OrderByClause,
18
+ UpdateMutationData,
19
+ DeleteMutationData,
20
+ } from './types'
21
+
22
+ /**
23
+ * Check if a value matches a field filter
24
+ */
25
+ function matchesFilter<T>(value: T, filter: any): boolean {
26
+ if (filter === null || filter === undefined || typeof filter !== 'object') {
27
+ return value === filter
28
+ }
29
+
30
+ const ops = filter as Record<string, any>
31
+
32
+ if ('$eq' in ops && value !== ops.$eq) return false
33
+ if ('$ne' in ops && value === ops.$ne) return false
34
+ if ('$gt' in ops && !(value > ops.$gt)) return false
35
+ if ('$gte' in ops && !(value >= ops.$gte)) return false
36
+ if ('$lt' in ops && !(value < ops.$lt)) return false
37
+ if ('$lte' in ops && !(value <= ops.$lte)) return false
38
+ if ('$in' in ops && !ops.$in.includes(value)) return false
39
+ if ('$nin' in ops && ops.$nin.includes(value)) return false
40
+
41
+ return true
42
+ }
43
+
44
+ /**
45
+ * Check if a document matches a where clause
46
+ */
47
+ function matchesWhere<T extends Record<string, any>>(doc: T, where: WhereClause<T>): boolean {
48
+ if ('$or' in where && where.$or) {
49
+ const orClauses = where.$or as WhereClause<T>[]
50
+ const matchesOr = orClauses.some(clause => matchesWhere(doc, clause))
51
+ if (!matchesOr) return false
52
+
53
+ const { $or, ...rest } = where
54
+ if (Object.keys(rest).length === 0) return true
55
+ return matchesWhere(doc, rest as WhereClause<T>)
56
+ }
57
+
58
+ for (const [key, filter] of Object.entries(where)) {
59
+ if (key === '$or') continue
60
+ const value = doc[key]
61
+ if (!matchesFilter(value, filter)) {
62
+ return false
63
+ }
64
+ }
65
+
66
+ return true
67
+ }
68
+
69
+ /**
70
+ * Sort documents by orderBy clause
71
+ */
72
+ function sortDocuments<T extends Record<string, any>>(
73
+ docs: T[],
74
+ orderBy: OrderByClause<T> | OrderByClause<T>[]
75
+ ): T[] {
76
+ const orderClauses = Array.isArray(orderBy) ? orderBy : [orderBy]
77
+
78
+ return [...docs].sort((a, b) => {
79
+ for (const clause of orderClauses) {
80
+ const [field, direction] = Object.entries(clause)[0]
81
+ const aVal = a[field]
82
+ const bVal = b[field]
83
+
84
+ if (aVal === null || aVal === undefined) {
85
+ if (bVal !== null && bVal !== undefined) return 1
86
+ continue
87
+ }
88
+ if (bVal === null || bVal === undefined) return -1
89
+
90
+ let comparison = 0
91
+ if (typeof aVal === 'string') {
92
+ comparison = aVal.localeCompare(bVal)
93
+ } else if (typeof aVal === 'number') {
94
+ comparison = aVal - bVal
95
+ } else if (aVal instanceof Date) {
96
+ comparison = aVal.getTime() - bVal.getTime()
97
+ } else {
98
+ comparison = String(aVal).localeCompare(String(bVal))
99
+ }
100
+
101
+ if (comparison !== 0) {
102
+ return direction === 'desc' ? -comparison : comparison
103
+ }
104
+ }
105
+ return 0
106
+ })
107
+ }
108
+
109
+ /**
110
+ * React hook for reactive data queries with filtering, sorting, and pagination
111
+ *
112
+ * @template T - The document type being queried
113
+ *
114
+ * @param options - Query options (collection name, filters, sorting, pagination)
115
+ * @returns Query result with reactive data, loading/error states, and refetch control
116
+ *
117
+ * @remarks
118
+ * - Must be used within a DBProvider component
119
+ * - Automatically fetches initial data on mount
120
+ * - Reactively updates when collection changes (subscriptions)
121
+ * - Filters, sorting, and pagination are re-applied when data changes
122
+ * - `data` is undefined while loading, array when loaded
123
+ * - `isLoading` is true for the initial fetch only
124
+ * - Manual refetch available via `refetch()` function
125
+ * - Error state persists until query succeeds
126
+ *
127
+ * @example
128
+ * ```tsx
129
+ * // Basic query - get all documents
130
+ * function AllUsers() {
131
+ * const { data, isLoading, error } = useQuery({
132
+ * from: 'users'
133
+ * })
134
+ *
135
+ * if (isLoading) return <div>Loading...</div>
136
+ * if (error) return <div>Error: {error.message}</div>
137
+ *
138
+ * return (
139
+ * <ul>
140
+ * {data?.map(user => (
141
+ * <li key={user.id}>{user.name}</li>
142
+ * ))}
143
+ * </ul>
144
+ * )
145
+ * }
146
+ * ```
147
+ *
148
+ * @example
149
+ * ```tsx
150
+ * // With filtering
151
+ * function AdminUsers() {
152
+ * const { data } = useQuery({
153
+ * from: 'users',
154
+ * where: { role: 'admin' }
155
+ * })
156
+ *
157
+ * return data?.map(user => <AdminCard key={user.id} user={user} />)
158
+ * }
159
+ * ```
160
+ *
161
+ * @example
162
+ * ```tsx
163
+ * // With sorting and pagination
164
+ * function UsersPaginated() {
165
+ * const { data, refetch } = useQuery({
166
+ * from: 'users',
167
+ * where: { status: 'active' },
168
+ * orderBy: { createdAt: 'desc' },
169
+ * limit: 20,
170
+ * offset: 0
171
+ * })
172
+ *
173
+ * return (
174
+ * <div>
175
+ * <Users data={data} />
176
+ * <button onClick={refetch}>Refresh</button>
177
+ * </div>
178
+ * )
179
+ * }
180
+ * ```
181
+ *
182
+ * @example
183
+ * ```tsx
184
+ * // With comparison operators
185
+ * function AdultUsers() {
186
+ * const { data } = useQuery({
187
+ * from: 'users',
188
+ * where: { age: { $gte: 18 } }
189
+ * })
190
+ *
191
+ * return data?.map(user => <Card key={user.id} user={user} />)
192
+ * }
193
+ * ```
194
+ *
195
+ * @example
196
+ * ```tsx
197
+ * // With OR conditions
198
+ * function AdminsOrModerators() {
199
+ * const { data } = useQuery({
200
+ * from: 'users',
201
+ * where: {
202
+ * $or: [
203
+ * { role: 'admin' },
204
+ * { role: 'moderator' }
205
+ * ]
206
+ * }
207
+ * })
208
+ *
209
+ * return data?.map(user => <Card key={user.id} user={user} />)
210
+ * }
211
+ * ```
212
+ *
213
+ * @example
214
+ * ```tsx
215
+ * // With multiple sort fields
216
+ * function UsersSorted() {
217
+ * const { data } = useQuery({
218
+ * from: 'users',
219
+ * orderBy: [
220
+ * { role: 'asc' },
221
+ * { name: 'asc' }
222
+ * ]
223
+ * })
224
+ *
225
+ * return data?.map(user => <Card key={user.id} user={user} />)
226
+ * }
227
+ * ```
228
+ */
229
+ export function useQuery<T extends Record<string, any>>(
230
+ options: QueryOptions<T>
231
+ ): QueryResult<T> {
232
+ const db = useDBContext()
233
+ const [data, setData] = useState<T[] | undefined>(undefined)
234
+ const [isLoading, setIsLoading] = useState(true)
235
+ const [error, setError] = useState<Error | undefined>(undefined)
236
+
237
+ const { from, where, orderBy, limit, offset } = options
238
+
239
+ // Get collection
240
+ const collection = db.collections[from]
241
+
242
+ const executeQuery = useCallback(async () => {
243
+ if (!collection) {
244
+ setError(new Error(`Collection '${from}' not found`))
245
+ setIsLoading(false)
246
+ return
247
+ }
248
+
249
+ try {
250
+ const result = await collection.findMany({ where, orderBy, limit, offset })
251
+ setData(result)
252
+ setError(undefined)
253
+ } catch (err) {
254
+ setError(err instanceof Error ? err : new Error(String(err)))
255
+ } finally {
256
+ setIsLoading(false)
257
+ }
258
+ }, [collection, from, JSON.stringify(where), JSON.stringify(orderBy), limit, offset])
259
+
260
+ // Initial fetch
261
+ useEffect(() => {
262
+ executeQuery()
263
+ }, [executeQuery])
264
+
265
+ // Subscribe to collection changes
266
+ useEffect(() => {
267
+ if (!collection) return
268
+
269
+ const unsubscribe = collection.subscribe((allData: T[]) => {
270
+ // Re-apply filters when data changes
271
+ let result = allData
272
+
273
+ if (where) {
274
+ result = result.filter(doc => matchesWhere(doc, where))
275
+ }
276
+
277
+ if (orderBy) {
278
+ result = sortDocuments(result, orderBy)
279
+ }
280
+
281
+ if (offset !== undefined) {
282
+ result = result.slice(offset)
283
+ }
284
+
285
+ if (limit !== undefined) {
286
+ result = result.slice(0, limit)
287
+ }
288
+
289
+ setData(result)
290
+ })
291
+
292
+ return unsubscribe
293
+ }, [collection, JSON.stringify(where), JSON.stringify(orderBy), limit, offset])
294
+
295
+ return {
296
+ data,
297
+ isLoading,
298
+ error,
299
+ refetch: executeQuery,
300
+ }
301
+ }
302
+
303
+ /**
304
+ * React hook for data mutations (insert, update, delete) with optimistic updates
305
+ *
306
+ * @template T - The document type being mutated
307
+ *
308
+ * @param options - Mutation options (collection, operation, optimistic mode)
309
+ * @returns Mutation result with mutate function, pending/error states, and reset
310
+ *
311
+ * @remarks
312
+ * - Must be used within a DBProvider component
313
+ * - Supports three operations: insert, update, delete
314
+ * - Optimistic updates: UI updates immediately, rolls back on error
315
+ * - Manual updates: UI updates only after server confirmation
316
+ * - `isPending` is true while mutation is executing
317
+ * - Error state persists until mutation succeeds or `reset()` is called
318
+ * - Data format depends on operation type:
319
+ * - `insert`: T (full document)
320
+ * - `update`: UpdateMutationData<T> (where + data)
321
+ * - `delete`: DeleteMutationData<T> (id)
322
+ * - Sync adapter is called when optimistic=true and sync is configured
323
+ *
324
+ * @example
325
+ * ```tsx
326
+ * // Insert with optimistic update
327
+ * function CreateUserForm() {
328
+ * const { mutate, isPending, error, reset } = useMutation({
329
+ * collection: 'users',
330
+ * operation: 'insert',
331
+ * optimistic: true
332
+ * })
333
+ *
334
+ * const handleSubmit = async (formData) => {
335
+ * try {
336
+ * await mutate({
337
+ * id: crypto.randomUUID(),
338
+ * name: formData.name,
339
+ * email: formData.email,
340
+ * role: 'user'
341
+ * })
342
+ * console.log('User created!')
343
+ * } catch (err) {
344
+ * console.error('Failed to create user')
345
+ * }
346
+ * }
347
+ *
348
+ * return (
349
+ * <form onSubmit={async (e) => {
350
+ * e.preventDefault()
351
+ * await handleSubmit(Object.fromEntries(new FormData(e.currentTarget)))
352
+ * }}>
353
+ * <input name="name" required />
354
+ * <input name="email" type="email" required />
355
+ * <button disabled={isPending}>
356
+ * {isPending ? 'Creating...' : 'Create'}
357
+ * </button>
358
+ * {error && (
359
+ * <div>
360
+ * <p>Error: {error.message}</p>
361
+ * <button onClick={reset}>Dismiss</button>
362
+ * </div>
363
+ * )}
364
+ * </form>
365
+ * )
366
+ * }
367
+ * ```
368
+ *
369
+ * @example
370
+ * ```tsx
371
+ * // Update mutation
372
+ * function EditUserForm({ userId }) {
373
+ * const { mutate, isPending } = useMutation({
374
+ * collection: 'users',
375
+ * operation: 'update'
376
+ * })
377
+ *
378
+ * const handleSave = async (updates) => {
379
+ * await mutate({
380
+ * where: { id: userId },
381
+ * data: updates
382
+ * })
383
+ * }
384
+ *
385
+ * return <Form onSave={handleSave} disabled={isPending} />
386
+ * }
387
+ * ```
388
+ *
389
+ * @example
390
+ * ```tsx
391
+ * // Delete mutation
392
+ * function DeleteUserButton({ userId }) {
393
+ * const { mutate, isPending } = useMutation({
394
+ * collection: 'users',
395
+ * operation: 'delete'
396
+ * })
397
+ *
398
+ * const handleDelete = async () => {
399
+ * if (confirm('Really delete?')) {
400
+ * await mutate({ id: userId })
401
+ * }
402
+ * }
403
+ *
404
+ * return (
405
+ * <button onClick={handleDelete} disabled={isPending}>
406
+ * {isPending ? 'Deleting...' : 'Delete'}
407
+ * </button>
408
+ * )
409
+ * }
410
+ * ```
411
+ *
412
+ * @example
413
+ * ```tsx
414
+ * // Optimistic vs non-optimistic
415
+ * function UserActions() {
416
+ * // UI updates immediately, auto-rollback on error
417
+ * const { mutate: optimisticInsert } = useMutation({
418
+ * collection: 'users',
419
+ * operation: 'insert',
420
+ * optimistic: true // Default UI update
421
+ * })
422
+ *
423
+ * // UI updates only after server confirms
424
+ * const { mutate: carefullInsert } = useMutation({
425
+ * collection: 'users',
426
+ * operation: 'insert',
427
+ * optimistic: false // Wait for server
428
+ * })
429
+ *
430
+ * return (
431
+ * <div>
432
+ * <button onClick={() => optimisticInsert(newUser)}>
433
+ * Quick Add (optimistic)
434
+ * </button>
435
+ * <button onClick={() => carefullInsert(newUser)}>
436
+ * Safe Add (wait for server)
437
+ * </button>
438
+ * </div>
439
+ * )
440
+ * }
441
+ * ```
442
+ */
443
+ export function useMutation<T extends Record<string, any>>(
444
+ options: MutationOptions
445
+ ): MutationResult<T> {
446
+ const db = useDBContext()
447
+ const [isPending, setIsPending] = useState(false)
448
+ const [error, setError] = useState<Error | undefined>(undefined)
449
+
450
+ const { collection: collectionName, operation, optimistic = false } = options
451
+
452
+ // Get collection
453
+ const collection = db.collections[collectionName]
454
+
455
+ // Store for rollback
456
+ const rollbackRef = useRef<(() => void) | null>(null)
457
+
458
+ const mutate = useCallback(
459
+ async (mutationData: T | UpdateMutationData<T> | DeleteMutationData<T>) => {
460
+ if (!collection) {
461
+ throw new Error(`Collection '${collectionName}' not found`)
462
+ }
463
+
464
+ setIsPending(true)
465
+ setError(undefined)
466
+
467
+ // Optimistic update storage
468
+ let rollbackFn: (() => void) | null = null
469
+
470
+ try {
471
+ if (operation === 'insert') {
472
+ const insertData = mutationData as T
473
+
474
+ if (optimistic) {
475
+ // Optimistically add to the local store immediately
476
+ // This triggers the subscription update
477
+ await collection.insert(insertData)
478
+
479
+ // If sync fails, we need to rollback
480
+ if (db.sync) {
481
+ try {
482
+ await db.sync.push([{ type: 'insert', collection: collectionName, data: insertData }])
483
+ } catch (syncError) {
484
+ // Rollback: delete the optimistically inserted data
485
+ const primaryKey = collection.primaryKey || 'id'
486
+ await collection.delete({ [primaryKey]: (insertData as any)[primaryKey] } as any)
487
+ throw syncError
488
+ }
489
+ }
490
+ } else {
491
+ await collection.insert(insertData)
492
+ if (db.sync) {
493
+ await db.sync.push([{ type: 'insert', collection: collectionName, data: insertData }])
494
+ }
495
+ }
496
+ } else if (operation === 'update') {
497
+ const updateData = mutationData as UpdateMutationData<T>
498
+ await collection.update(updateData.where, updateData.data)
499
+ if (db.sync) {
500
+ await db.sync.push([{ type: 'update', collection: collectionName, data: updateData }])
501
+ }
502
+ } else if (operation === 'delete') {
503
+ const deleteData = mutationData as DeleteMutationData<T>
504
+ await collection.delete({ id: deleteData.id } as any)
505
+ if (db.sync) {
506
+ await db.sync.push([{ type: 'delete', collection: collectionName, data: deleteData }])
507
+ }
508
+ }
509
+ } catch (err) {
510
+ const error = err instanceof Error ? err : new Error(String(err))
511
+ setError(error)
512
+ throw error
513
+ } finally {
514
+ setIsPending(false)
515
+ rollbackRef.current = null
516
+ }
517
+ },
518
+ [collection, collectionName, operation, optimistic, db.sync]
519
+ )
520
+
521
+ const reset = useCallback(() => {
522
+ setIsPending(false)
523
+ setError(undefined)
524
+ }, [])
525
+
526
+ return {
527
+ mutate,
528
+ isPending,
529
+ error,
530
+ reset,
531
+ }
532
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @mdxui/terminal Data Layer
3
+ *
4
+ * TanStack DB-like integration for reactive data management.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import {
9
+ * createDB,
10
+ * createCollection,
11
+ * DBProvider,
12
+ * useQuery,
13
+ * useMutation,
14
+ * } from '@mdxui/terminal'
15
+ * import { z } from 'zod'
16
+ *
17
+ * // Define schema
18
+ * const UserSchema = z.object({
19
+ * id: z.string(),
20
+ * name: z.string(),
21
+ * email: z.string().email(),
22
+ * })
23
+ *
24
+ * // Create collection
25
+ * const usersCollection = createCollection({
26
+ * name: 'users',
27
+ * schema: UserSchema,
28
+ * })
29
+ *
30
+ * // Create database
31
+ * const db = createDB({
32
+ * collections: [usersCollection],
33
+ * })
34
+ *
35
+ * // Use in React
36
+ * function App() {
37
+ * return (
38
+ * <DBProvider db={db}>
39
+ * <UserList />
40
+ * </DBProvider>
41
+ * )
42
+ * }
43
+ *
44
+ * function UserList() {
45
+ * const { data, isLoading } = useQuery({
46
+ * from: 'users',
47
+ * where: { role: 'admin' },
48
+ * })
49
+ *
50
+ * const { mutate } = useMutation({
51
+ * collection: 'users',
52
+ * operation: 'insert',
53
+ * })
54
+ *
55
+ * // ...
56
+ * }
57
+ * ```
58
+ */
59
+
60
+ // Core functions
61
+ export { createDB } from './db'
62
+ export { createCollection } from './collection'
63
+ export { createDOSync } from './sync'
64
+ export type { DOSyncConfig, DOSyncAdapter, ReconnectOptions, ConflictResolution } from './sync'
65
+
66
+ // React context
67
+ export { DBProvider, useDBContext } from './context'
68
+ export type { DBProviderProps } from './context'
69
+
70
+ // React hooks
71
+ export { useQuery, useMutation } from './hooks'
72
+
73
+ // Reactive data hooks for data components (Table, List, Card, Metrics)
74
+ export {
75
+ useReactiveData,
76
+ useReactiveTable,
77
+ useReactiveList,
78
+ useReactiveMetrics,
79
+ useReactiveCard,
80
+ } from './reactive'
81
+ export type {
82
+ SortState,
83
+ SelectionState,
84
+ PaginationState,
85
+ ReactiveDataOptions,
86
+ ReactiveDataResult,
87
+ ReactiveTableOptions,
88
+ ReactiveListOptions,
89
+ ReactiveMetricsOptions,
90
+ MetricValue,
91
+ ReactiveMetricsResult,
92
+ ReactiveCardOptions,
93
+ ReactiveCardResult,
94
+ } from './reactive'
95
+
96
+ // SaaS Collections
97
+ export {
98
+ // Factory functions
99
+ UsersCollection,
100
+ APIKeysCollection,
101
+ WebhooksCollection,
102
+ TeamsCollection,
103
+ UsageCollection,
104
+ // Schemas
105
+ UserSchema,
106
+ UserRoleSchema,
107
+ APIKeySchema,
108
+ WebhookSchema,
109
+ TeamSchema,
110
+ UsageSchema,
111
+ } from './saas-collections'
112
+ export type {
113
+ User,
114
+ UserRole,
115
+ APIKey,
116
+ Webhook,
117
+ Team,
118
+ Usage,
119
+ } from './saas-collections'
120
+
121
+ // Types
122
+ export type {
123
+ DB,
124
+ DBConfig,
125
+ Collection,
126
+ SyncAdapter,
127
+ FindManyOptions,
128
+ QueryOptions,
129
+ QueryResult,
130
+ MutationOptions,
131
+ MutationResult,
132
+ WhereClause,
133
+ OrderByClause,
134
+ ComparisonOperators,
135
+ FieldFilter,
136
+ OrderDirection,
137
+ MutationOperation,
138
+ } from './types'