@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,316 @@
1
+ /**
2
+ * @mdxui/terminal Collection Implementation
3
+ *
4
+ * In-memory collection with Zod validation, reactive subscriptions, filtering,
5
+ * sorting, and pagination support. Provides full CRUD operations with type safety.
6
+ */
7
+
8
+ import type { ZodSchema } from 'zod'
9
+ import type { Collection, FindManyOptions, WhereClause, OrderByClause, FieldFilter } from './types'
10
+
11
+ /**
12
+ * Configuration for creating a collection
13
+ *
14
+ * @template T - The document type stored in this collection
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const config: CreateCollectionConfig<User> = {
19
+ * name: 'users',
20
+ * schema: UserSchema,
21
+ * primaryKey: 'id',
22
+ * indexes: ['email', 'role']
23
+ * }
24
+ * ```
25
+ */
26
+ export interface CreateCollectionConfig<T> {
27
+ /** Collection name - used to reference in queries and mutations */
28
+ name: string
29
+ /** Zod schema for automatic validation on all insert/update operations */
30
+ schema: ZodSchema<T>
31
+ /** Primary key field (defaults to 'id'). Used for duplicate detection */
32
+ primaryKey?: string
33
+ /** Fields to index for faster queries (metadata only for future optimization) */
34
+ indexes?: string[]
35
+ }
36
+
37
+ /**
38
+ * Check if a value matches a field filter
39
+ */
40
+ function matchesFilter<T>(value: T, filter: FieldFilter<T>): boolean {
41
+ // Direct equality
42
+ if (filter === null || filter === undefined || typeof filter !== 'object') {
43
+ return value === filter
44
+ }
45
+
46
+ // Comparison operators
47
+ const ops = filter as Record<string, any>
48
+
49
+ if ('$eq' in ops && value !== ops.$eq) return false
50
+ if ('$ne' in ops && value === ops.$ne) return false
51
+ if ('$gt' in ops && !(value > ops.$gt)) return false
52
+ if ('$gte' in ops && !(value >= ops.$gte)) return false
53
+ if ('$lt' in ops && !(value < ops.$lt)) return false
54
+ if ('$lte' in ops && !(value <= ops.$lte)) return false
55
+ if ('$in' in ops && !ops.$in.includes(value)) return false
56
+ if ('$nin' in ops && ops.$nin.includes(value)) return false
57
+
58
+ return true
59
+ }
60
+
61
+ /**
62
+ * Check if a document matches a where clause
63
+ */
64
+ function matchesWhere<T extends Record<string, any>>(doc: T, where: WhereClause<T>): boolean {
65
+ // Handle $or operator
66
+ if ('$or' in where && where.$or) {
67
+ const orClauses = where.$or as WhereClause<T>[]
68
+ const matchesOr = orClauses.some(clause => matchesWhere(doc, clause))
69
+ if (!matchesOr) return false
70
+
71
+ // Check remaining fields (AND with the $or result)
72
+ const { $or, ...rest } = where
73
+ if (Object.keys(rest).length === 0) return true
74
+ return matchesWhere(doc, rest as WhereClause<T>)
75
+ }
76
+
77
+ // Check all field conditions (AND)
78
+ for (const [key, filter] of Object.entries(where)) {
79
+ if (key === '$or') continue
80
+ const value = doc[key]
81
+ if (!matchesFilter(value, filter as FieldFilter<any>)) {
82
+ return false
83
+ }
84
+ }
85
+
86
+ return true
87
+ }
88
+
89
+ /**
90
+ * Sort documents by orderBy clause
91
+ */
92
+ function sortDocuments<T extends Record<string, any>>(
93
+ docs: T[],
94
+ orderBy: OrderByClause<T> | OrderByClause<T>[]
95
+ ): T[] {
96
+ const orderClauses = Array.isArray(orderBy) ? orderBy : [orderBy]
97
+
98
+ return [...docs].sort((a, b) => {
99
+ for (const clause of orderClauses) {
100
+ const [field, direction] = Object.entries(clause)[0]
101
+ const aVal = a[field]
102
+ const bVal = b[field]
103
+
104
+ // Handle null/undefined - put them last
105
+ if (aVal === null || aVal === undefined) {
106
+ if (bVal !== null && bVal !== undefined) return 1
107
+ continue
108
+ }
109
+ if (bVal === null || bVal === undefined) return -1
110
+
111
+ // Compare values
112
+ let comparison = 0
113
+ if (typeof aVal === 'string') {
114
+ comparison = aVal.localeCompare(bVal)
115
+ } else if (typeof aVal === 'number') {
116
+ comparison = aVal - bVal
117
+ } else if (aVal instanceof Date) {
118
+ comparison = aVal.getTime() - bVal.getTime()
119
+ } else {
120
+ comparison = String(aVal).localeCompare(String(bVal))
121
+ }
122
+
123
+ if (comparison !== 0) {
124
+ return direction === 'desc' ? -comparison : comparison
125
+ }
126
+ }
127
+ return 0
128
+ })
129
+ }
130
+
131
+ /**
132
+ * Create a typed in-memory collection with Zod validation
133
+ *
134
+ * @template T - The document type (must be a Record for proper typing)
135
+ *
136
+ * @param config - Collection configuration (name, schema, primaryKey, indexes)
137
+ * @returns A fully typed collection instance with CRUD operations and subscriptions
138
+ *
139
+ * @remarks
140
+ * - All documents are validated against the provided Zod schema on insert/update
141
+ * - Primary key defaults to 'id' if not specified
142
+ * - Provides reactive subscriptions for real-time updates
143
+ * - Supports filtering with where clauses and comparison operators
144
+ * - Supports sorting with orderBy and multiple sort fields
145
+ * - Supports pagination with limit and offset
146
+ * - Thread-safe for concurrent operations (in-memory only)
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * import { z } from 'zod'
151
+ * import { createCollection } from '@mdxui/terminal'
152
+ *
153
+ * // Define schema
154
+ * const UserSchema = z.object({
155
+ * id: z.string(),
156
+ * name: z.string(),
157
+ * email: z.string().email(),
158
+ * age: z.number().optional(),
159
+ * role: z.enum(['admin', 'user', 'guest'])
160
+ * })
161
+ *
162
+ * type User = z.infer<typeof UserSchema>
163
+ *
164
+ * // Create collection
165
+ * const users = createCollection<User>({
166
+ * name: 'users',
167
+ * schema: UserSchema,
168
+ * primaryKey: 'id',
169
+ * indexes: ['email', 'role']
170
+ * })
171
+ *
172
+ * // CRUD Operations
173
+ * const user = await users.insert({
174
+ * id: '1',
175
+ * name: 'Alice',
176
+ * email: 'alice@example.com',
177
+ * role: 'admin'
178
+ * })
179
+ *
180
+ * const updated = await users.update(
181
+ * { id: '1' },
182
+ * { name: 'Alicia' }
183
+ * )
184
+ *
185
+ * const found = await users.findOne({ id: '1' })
186
+ *
187
+ * const admins = await users.findMany({
188
+ * where: { role: 'admin' },
189
+ * orderBy: { name: 'asc' }
190
+ * })
191
+ *
192
+ * await users.delete({ id: '1' })
193
+ *
194
+ * // Subscribe to changes
195
+ * const unsubscribe = users.subscribe((allUsers) => {
196
+ * console.log('Users updated:', allUsers)
197
+ * })
198
+ * ```
199
+ */
200
+ export function createCollection<T extends Record<string, any>>(
201
+ config: CreateCollectionConfig<T>
202
+ ): Collection<T> {
203
+ const { name, schema, primaryKey = 'id', indexes = [] } = config
204
+
205
+ // In-memory storage
206
+ const data = new Map<string, T>()
207
+ const subscribers = new Set<(data: T[]) => void>()
208
+
209
+ const collection: Collection<T> = {
210
+ name,
211
+ schema,
212
+ primaryKey: primaryKey as string,
213
+ indexes: indexes as string[],
214
+
215
+ async insert(doc: T): Promise<T> {
216
+ // Validate against schema
217
+ const validated = schema.parse(doc)
218
+ const id = validated[primaryKey as keyof T]
219
+
220
+ // Check for duplicates
221
+ if (data.has(String(id))) {
222
+ throw new Error(`Duplicate ${primaryKey}: ${id}`)
223
+ }
224
+
225
+ data.set(String(id), validated)
226
+ collection._notify()
227
+ return validated
228
+ },
229
+
230
+ async update(filter: Partial<T>, updates: Partial<T>): Promise<T[]> {
231
+ const updated: T[] = []
232
+
233
+ for (const [key, doc] of data.entries()) {
234
+ if (matchesWhere(doc, filter as WhereClause<T>)) {
235
+ const newDoc = { ...doc, ...updates }
236
+ // Validate updated document
237
+ const validated = schema.parse(newDoc)
238
+ data.set(key, validated)
239
+ updated.push(validated)
240
+ }
241
+ }
242
+
243
+ if (updated.length > 0) {
244
+ collection._notify()
245
+ }
246
+
247
+ return updated
248
+ },
249
+
250
+ async delete(filter: Partial<T>): Promise<void> {
251
+ let deleted = false
252
+
253
+ for (const [key, doc] of data.entries()) {
254
+ if (matchesWhere(doc, filter as WhereClause<T>)) {
255
+ data.delete(key)
256
+ deleted = true
257
+ }
258
+ }
259
+
260
+ if (deleted) {
261
+ collection._notify()
262
+ }
263
+ },
264
+
265
+ async findOne(filter: Partial<T>): Promise<T | null> {
266
+ for (const doc of data.values()) {
267
+ if (matchesWhere(doc, filter as WhereClause<T>)) {
268
+ return doc
269
+ }
270
+ }
271
+ return null
272
+ },
273
+
274
+ async findMany(options?: FindManyOptions<T>): Promise<T[]> {
275
+ let results = Array.from(data.values())
276
+
277
+ // Apply where filter
278
+ if (options?.where) {
279
+ results = results.filter(doc => matchesWhere(doc, options.where!))
280
+ }
281
+
282
+ // Apply ordering
283
+ if (options?.orderBy) {
284
+ results = sortDocuments(results, options.orderBy)
285
+ }
286
+
287
+ // Apply offset
288
+ if (options?.offset !== undefined) {
289
+ results = results.slice(options.offset)
290
+ }
291
+
292
+ // Apply limit
293
+ if (options?.limit !== undefined) {
294
+ results = results.slice(0, options.limit)
295
+ }
296
+
297
+ return results
298
+ },
299
+
300
+ subscribe(callback: (data: T[]) => void): () => void {
301
+ subscribers.add(callback)
302
+ return () => {
303
+ subscribers.delete(callback)
304
+ }
305
+ },
306
+
307
+ _notify(): void {
308
+ const allData = Array.from(data.values())
309
+ for (const callback of subscribers) {
310
+ callback(allData)
311
+ }
312
+ },
313
+ }
314
+
315
+ return collection
316
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @mdxui/terminal SaaS Collections
3
+ *
4
+ * TDD RED Phase: This is a stub file that will cause tests to fail with meaningful errors.
5
+ * The GREEN phase implementation will replace this with actual schemas and collections.
6
+ *
7
+ * Pre-built Zod schemas and collections for common SaaS primitives:
8
+ * - Users: Authentication and user management
9
+ * - APIKeys: API key management with permissions
10
+ * - Webhooks: Webhook configuration and event subscriptions
11
+ * - Teams: Team/organization management with members and plans
12
+ */
13
+
14
+ import { z } from 'zod'
15
+ import { createCollection } from './collection'
16
+ import type { Collection } from './types'
17
+
18
+ // ============================================================================
19
+ // RED PHASE STUBS - These will fail validation tests
20
+ // ============================================================================
21
+
22
+ // Placeholder schemas that will fail the tests
23
+ // GREEN phase will implement proper schemas
24
+
25
+ export const UserSchema = z.object({})
26
+ export const APIKeySchema = z.object({})
27
+ export const WebhookSchema = z.object({})
28
+ export const TeamSchema = z.object({})
29
+
30
+ // Type exports
31
+ export type User = z.infer<typeof UserSchema>
32
+ export type APIKey = z.infer<typeof APIKeySchema>
33
+ export type Webhook = z.infer<typeof WebhookSchema>
34
+ export type Team = z.infer<typeof TeamSchema>
35
+
36
+ // Placeholder collections that will fail
37
+ export const usersCollection = undefined as unknown as Collection<User>
38
+ export const apiKeysCollection = undefined as unknown as Collection<APIKey>
39
+ export const webhooksCollection = undefined as unknown as Collection<Webhook>
40
+ export const teamsCollection = undefined as unknown as Collection<Team>
41
+
42
+ // Factory function placeholder
43
+ export function createSaaSCollections() {
44
+ return {
45
+ users: undefined as unknown as Collection<User>,
46
+ apiKeys: undefined as unknown as Collection<APIKey>,
47
+ webhooks: undefined as unknown as Collection<Webhook>,
48
+ teams: undefined as unknown as Collection<Team>,
49
+ }
50
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * @mdxui/terminal Database Context
3
+ *
4
+ * React Context API provider for database access throughout component tree.
5
+ * Enables hooks like `useQuery` and `useMutation` to access the database
6
+ * without prop drilling.
7
+ */
8
+
9
+ import * as React from 'react'
10
+ import { createContext, useContext, type ReactNode } from 'react'
11
+ import type { DB } from './types'
12
+
13
+ /**
14
+ * React Context for database instance
15
+ *
16
+ * @remarks
17
+ * Created as undefined by default - must be provided by DBProvider wrapper.
18
+ * Used internally by useQuery and useMutation hooks.
19
+ *
20
+ * @internal
21
+ */
22
+ const DBContext = createContext<DB | undefined>(undefined)
23
+
24
+ /**
25
+ * Props for DBProvider component
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const props: DBProviderProps = {
30
+ * db: myDatabase,
31
+ * children: <App />
32
+ * }
33
+ * ```
34
+ */
35
+ export interface DBProviderProps {
36
+ /** Database instance to provide to all children */
37
+ db: DB
38
+ /** React components that will have access to the database */
39
+ children: ReactNode
40
+ }
41
+
42
+ /**
43
+ * Database provider component - wraps your app to provide database context
44
+ *
45
+ * Provides database access to all child components via React Context.
46
+ * Must wrap any component using `useQuery` or `useMutation` hooks.
47
+ *
48
+ * @remarks
49
+ * - Use at the top level of your app (e.g., around `<App />`)
50
+ * - Can have multiple DBProviders with different database instances
51
+ * - All useQuery and useMutation hooks must be within a DBProvider
52
+ * - Throws error if hooks are used outside DBProvider
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * import { z } from 'zod'
57
+ * import { createDB, createCollection, DBProvider } from '@mdxui/terminal'
58
+ *
59
+ * // Create database
60
+ * const db = createDB({
61
+ * collections: [usersCollection, todosCollection]
62
+ * })
63
+ *
64
+ * // Wrap app with provider
65
+ * export function App() {
66
+ * return (
67
+ * <DBProvider db={db}>
68
+ * <UserList />
69
+ * <TodoForm />
70
+ * </DBProvider>
71
+ * )
72
+ * }
73
+ *
74
+ * // Now these hooks work:
75
+ * function UserList() {
76
+ * const { data, isLoading } = useQuery({
77
+ * from: 'users',
78
+ * where: { role: 'admin' }
79
+ * })
80
+ *
81
+ * if (isLoading) return <div>Loading...</div>
82
+ * return (
83
+ * <ul>
84
+ * {data?.map(user => (
85
+ * <li key={user.id}>{user.name}</li>
86
+ * ))}
87
+ * </ul>
88
+ * )
89
+ * }
90
+ *
91
+ * function TodoForm() {
92
+ * const { mutate, isPending } = useMutation({
93
+ * collection: 'todos',
94
+ * operation: 'insert'
95
+ * })
96
+ *
97
+ * return (
98
+ * <form onSubmit={(e) => {
99
+ * e.preventDefault()
100
+ * mutate({ id: '1', title: 'New', completed: false })
101
+ * }}>
102
+ * <button disabled={isPending}>Add Todo</button>
103
+ * </form>
104
+ * )
105
+ * }
106
+ * ```
107
+ */
108
+ export function DBProvider({ db, children }: DBProviderProps): React.ReactElement {
109
+ return React.createElement(DBContext.Provider, { value: db }, children)
110
+ }
111
+
112
+ /**
113
+ * Hook to access the database instance from context
114
+ *
115
+ * @returns The database instance from the nearest DBProvider
116
+ * @throws Error if used outside a DBProvider component
117
+ *
118
+ * @remarks
119
+ * - Must be called within a component tree wrapped by DBProvider
120
+ * - Allows direct access to collections (advanced use case)
121
+ * - Most apps use useQuery/useMutation instead
122
+ * - Useful for manual collection operations outside hooks
123
+ *
124
+ * @example
125
+ * ```tsx
126
+ * // Direct database access (advanced)
127
+ * function MyComponent() {
128
+ * const db = useDBContext()
129
+ *
130
+ * // Manually query
131
+ * const handleClick = async () => {
132
+ * const admins = await db.collections.users.findMany({
133
+ * where: { role: 'admin' }
134
+ * })
135
+ * console.log('Admins:', admins)
136
+ * }
137
+ *
138
+ * return <button onClick={handleClick}>Get Admins</button>
139
+ * }
140
+ * ```
141
+ *
142
+ * @example
143
+ * ```tsx
144
+ * // Subscribe to collection changes (advanced)
145
+ * function CollectionMonitor() {
146
+ * const db = useDBContext()
147
+ *
148
+ * React.useEffect(() => {
149
+ * const unsubscribe = db.collections.users.subscribe((users) => {
150
+ * console.log('Users changed:', users)
151
+ * })
152
+ * return unsubscribe
153
+ * }, [db])
154
+ *
155
+ * return <div>Monitoring changes...</div>
156
+ * }
157
+ * ```
158
+ */
159
+ export function useDBContext(): DB {
160
+ const db = useContext(DBContext)
161
+ if (!db) {
162
+ throw new Error(
163
+ 'useDBContext must be used within a DBProvider. ' +
164
+ 'Wrap your component with <DBProvider db={...}> at a higher level in the component tree.'
165
+ )
166
+ }
167
+ return db
168
+ }
169
+
170
+ /**
171
+ * Internal: Export context for use by other hooks
172
+ * @internal
173
+ */
174
+ export { DBContext }
package/src/data/db.ts ADDED
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @mdxui/terminal Database Implementation
3
+ *
4
+ * In-memory database with collection management, sync adapter support,
5
+ * and centralized data access for React applications.
6
+ */
7
+
8
+ import type { DB, DBConfig, SyncAdapter, Collection } from './types'
9
+
10
+ /**
11
+ * Create a database instance with collections and optional sync adapter
12
+ *
13
+ * @param config - Database configuration with collections and sync adapter
14
+ * @returns A fully typed database instance for accessing collections
15
+ *
16
+ * @remarks
17
+ * - Collections are accessed via `db.collections[name]`
18
+ * - All data is stored in-memory (cleared on process exit)
19
+ * - Optional sync adapter can push/pull changes to a remote backend
20
+ * - Use within `DBProvider` for React applications
21
+ * - Thread-safe for concurrent operations
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { z } from 'zod'
26
+ * import { createDB, createCollection } from '@mdxui/terminal'
27
+ *
28
+ * // Define schemas
29
+ * const UserSchema = z.object({
30
+ * id: z.string(),
31
+ * name: z.string(),
32
+ * email: z.string().email(),
33
+ * role: z.enum(['admin', 'user', 'guest'])
34
+ * })
35
+ *
36
+ * // Create collections
37
+ * const usersCollection = createCollection({
38
+ * name: 'users',
39
+ * schema: UserSchema,
40
+ * primaryKey: 'id'
41
+ * })
42
+ *
43
+ * // Create database with collections
44
+ * const db = createDB({
45
+ * collections: [usersCollection],
46
+ * sync: customSyncAdapter // optional
47
+ * })
48
+ *
49
+ * // Access collections
50
+ * const user = await db.collections.users.insert({
51
+ * id: '1',
52
+ * name: 'Alice',
53
+ * email: 'alice@example.com',
54
+ * role: 'admin'
55
+ * })
56
+ *
57
+ * const all = await db.collections.users.findMany()
58
+ * const admins = await db.collections.users.findMany({
59
+ * where: { role: 'admin' }
60
+ * })
61
+ *
62
+ * // Clean up
63
+ * await db.clear()
64
+ * db.close()
65
+ * ```
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * // With sync adapter for remote sync
70
+ * const syncAdapter = {
71
+ * async push(changes) {
72
+ * await fetch('/api/sync', {
73
+ * method: 'POST',
74
+ * body: JSON.stringify(changes)
75
+ * })
76
+ * },
77
+ * async pull() {
78
+ * const res = await fetch('/api/sync')
79
+ * return res.json()
80
+ * },
81
+ * subscribe(callback) {
82
+ * const ws = new WebSocket('wss://...')
83
+ * ws.onmessage = (e) => callback(e.data)
84
+ * return () => ws.close()
85
+ * }
86
+ * }
87
+ *
88
+ * const db = createDB({
89
+ * collections: [usersCollection],
90
+ * sync: syncAdapter
91
+ * })
92
+ * ```
93
+ */
94
+ export function createDB(config?: DBConfig): DB {
95
+ const collections: Record<string, Collection<any>> = {}
96
+
97
+ // Register collections
98
+ if (config?.collections) {
99
+ for (const collection of config.collections) {
100
+ collections[collection.name] = collection
101
+ }
102
+ }
103
+
104
+ const db: DB = {
105
+ collections,
106
+ sync: config?.sync,
107
+
108
+ close(): void {
109
+ // Clean up resources
110
+ // In a real implementation, this would close connections
111
+ },
112
+
113
+ async clear(): Promise<void> {
114
+ // Clear all data in all collections
115
+ for (const collection of Object.values(collections)) {
116
+ // Get all documents and delete them
117
+ const docs = await collection.findMany()
118
+ for (const doc of docs) {
119
+ const primaryKey = collection.primaryKey || 'id'
120
+ await collection.delete({ [primaryKey]: doc[primaryKey] } as any)
121
+ }
122
+ }
123
+ },
124
+ }
125
+
126
+ return db
127
+ }