@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.
- package/README.md +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- package/src/types.ts +103 -0
|
@@ -0,0 +1,2554 @@
|
|
|
1
|
+
import { ZodSchema, z } from 'zod';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @mdxui/terminal Data Layer Types
|
|
7
|
+
*
|
|
8
|
+
* Type definitions for the TanStack DB-like integration.
|
|
9
|
+
* Provides a type-safe, reactive in-memory database with Zod validation,
|
|
10
|
+
* filtering, sorting, pagination, and optimistic updates.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Comparison operators for filtering database queries
|
|
15
|
+
*
|
|
16
|
+
* @template T - The type of the field being compared
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Using comparison operators in a where clause
|
|
21
|
+
* {
|
|
22
|
+
* $eq: 5, // Equal to 5
|
|
23
|
+
* $ne: 10, // Not equal to 10
|
|
24
|
+
* $gt: 3, // Greater than 3
|
|
25
|
+
* $gte: 3, // Greater than or equal to 3
|
|
26
|
+
* $lt: 100, // Less than 100
|
|
27
|
+
* $lte: 100, // Less than or equal to 100
|
|
28
|
+
* $in: [1, 2, 3], // In array [1, 2, 3]
|
|
29
|
+
* $nin: [1, 2, 3], // Not in array [1, 2, 3]
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
interface ComparisonOperators<T> {
|
|
34
|
+
/** Equality match */
|
|
35
|
+
$eq?: T;
|
|
36
|
+
/** Not equal to */
|
|
37
|
+
$ne?: T;
|
|
38
|
+
/** Greater than */
|
|
39
|
+
$gt?: T;
|
|
40
|
+
/** Greater than or equal to */
|
|
41
|
+
$gte?: T;
|
|
42
|
+
/** Less than */
|
|
43
|
+
$lt?: T;
|
|
44
|
+
/** Less than or equal to */
|
|
45
|
+
$lte?: T;
|
|
46
|
+
/** Value is in array */
|
|
47
|
+
$in?: T[];
|
|
48
|
+
/** Value is not in array */
|
|
49
|
+
$nin?: T[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Field filter - either a direct value for equality or comparison operators
|
|
53
|
+
*
|
|
54
|
+
* @template T - The type of the field
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // Direct equality
|
|
59
|
+
* type Filter = FieldFilter<number> // number
|
|
60
|
+
* const filter: Filter = 5
|
|
61
|
+
*
|
|
62
|
+
* // With operators
|
|
63
|
+
* const filter: Filter = { $gt: 5 }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
type FieldFilter<T> = T | ComparisonOperators<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Where clause for filtering - supports equality, comparison operators, and $or
|
|
69
|
+
*
|
|
70
|
+
* @template T - The document type being queried
|
|
71
|
+
*
|
|
72
|
+
* @remarks
|
|
73
|
+
* - Uses AND logic for multiple field conditions
|
|
74
|
+
* - `$or` is an array of alternative conditions (OR logic)
|
|
75
|
+
* - Can combine $or with other fields (fields AND ($or[...]))
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* // Simple equality
|
|
80
|
+
* const where: WhereClause<User> = { role: 'admin' }
|
|
81
|
+
*
|
|
82
|
+
* // With comparison operators
|
|
83
|
+
* const where: WhereClause<User> = { age: { $gte: 18 } }
|
|
84
|
+
*
|
|
85
|
+
* // Multiple conditions (AND)
|
|
86
|
+
* const where: WhereClause<User> = {
|
|
87
|
+
* role: 'admin',
|
|
88
|
+
* age: { $gte: 18 }
|
|
89
|
+
* }
|
|
90
|
+
*
|
|
91
|
+
* // OR conditions
|
|
92
|
+
* const where: WhereClause<User> = {
|
|
93
|
+
* $or: [
|
|
94
|
+
* { role: 'admin' },
|
|
95
|
+
* { role: 'moderator' }
|
|
96
|
+
* ]
|
|
97
|
+
* }
|
|
98
|
+
*
|
|
99
|
+
* // Combined AND/OR
|
|
100
|
+
* const where: WhereClause<User> = {
|
|
101
|
+
* status: 'active',
|
|
102
|
+
* $or: [
|
|
103
|
+
* { role: 'admin' },
|
|
104
|
+
* { role: 'moderator' }
|
|
105
|
+
* ]
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
type WhereClause<T> = {
|
|
110
|
+
[K in keyof T]?: FieldFilter<T[K]>;
|
|
111
|
+
} & {
|
|
112
|
+
$or?: Array<WhereClause<T>>;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Sort direction for ordering results
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* type Direction = OrderDirection
|
|
120
|
+
* const ascending: Direction = 'asc'
|
|
121
|
+
* const descending: Direction = 'desc'
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
type OrderDirection = 'asc' | 'desc';
|
|
125
|
+
/**
|
|
126
|
+
* Order by clause for sorting results
|
|
127
|
+
*
|
|
128
|
+
* @template T - The document type being sorted
|
|
129
|
+
*
|
|
130
|
+
* @remarks
|
|
131
|
+
* Maps field names to sort directions (asc/desc)
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* // Sort by age ascending
|
|
136
|
+
* const orderBy: OrderByClause<User> = { age: 'asc' }
|
|
137
|
+
*
|
|
138
|
+
* // Sort by multiple fields
|
|
139
|
+
* const orderByArray: OrderByClause<User>[] = [
|
|
140
|
+
* { role: 'asc' },
|
|
141
|
+
* { age: 'desc' }
|
|
142
|
+
* ]
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
type OrderByClause<T> = {
|
|
146
|
+
[K in keyof T]?: OrderDirection;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* A typed in-memory collection with CRUD operations, filtering, sorting, and reactive subscriptions
|
|
150
|
+
*
|
|
151
|
+
* @template T - The document type stored in this collection
|
|
152
|
+
*
|
|
153
|
+
* @remarks
|
|
154
|
+
* - All documents are validated against the schema on insert/update
|
|
155
|
+
* - Provides reactive subscriptions for real-time updates
|
|
156
|
+
* - Supports filtering with where clauses and comparison operators
|
|
157
|
+
* - Supports sorting with orderBy and multiple sort fields
|
|
158
|
+
* - Supports pagination with limit and offset
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* const collection = createCollection<User>({
|
|
163
|
+
* name: 'users',
|
|
164
|
+
* schema: UserSchema
|
|
165
|
+
* })
|
|
166
|
+
*
|
|
167
|
+
* // CRUD operations
|
|
168
|
+
* const user = await collection.insert({ id: '1', name: 'Alice', ... })
|
|
169
|
+
* const updated = await collection.update({ id: '1' }, { name: 'Alicia' })
|
|
170
|
+
* const found = await collection.findOne({ id: '1' })
|
|
171
|
+
* const all = await collection.findMany()
|
|
172
|
+
* await collection.delete({ id: '1' })
|
|
173
|
+
*
|
|
174
|
+
* // Subscribe to changes
|
|
175
|
+
* const unsubscribe = collection.subscribe((data) => {
|
|
176
|
+
* console.log('Collection changed:', data)
|
|
177
|
+
* })
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
interface Collection<T> {
|
|
181
|
+
/** Collection name - used to reference in queries */
|
|
182
|
+
name: string;
|
|
183
|
+
/** Zod schema for validation */
|
|
184
|
+
schema: ZodSchema<T>;
|
|
185
|
+
/** Primary key field name */
|
|
186
|
+
primaryKey: string;
|
|
187
|
+
/** Indexed field names */
|
|
188
|
+
indexes: string[];
|
|
189
|
+
/**
|
|
190
|
+
* Insert a new document into the collection
|
|
191
|
+
*
|
|
192
|
+
* @param data - Document to insert (must match schema)
|
|
193
|
+
* @returns The validated document
|
|
194
|
+
* @throws If document fails schema validation or primary key already exists
|
|
195
|
+
*/
|
|
196
|
+
insert(data: T): Promise<T>;
|
|
197
|
+
/**
|
|
198
|
+
* Update one or more documents matching the filter
|
|
199
|
+
*
|
|
200
|
+
* @param filter - Documents matching this filter will be updated
|
|
201
|
+
* @param data - Partial updates to apply (merged with existing data)
|
|
202
|
+
* @returns Array of updated documents
|
|
203
|
+
* @throws If updated documents fail schema validation
|
|
204
|
+
*/
|
|
205
|
+
update(filter: Partial<T>, data: Partial<T>): Promise<T[]>;
|
|
206
|
+
/**
|
|
207
|
+
* Delete one or more documents matching the filter
|
|
208
|
+
*
|
|
209
|
+
* @param filter - Documents matching this filter will be deleted
|
|
210
|
+
* @returns void
|
|
211
|
+
*/
|
|
212
|
+
delete(filter: Partial<T>): Promise<void>;
|
|
213
|
+
/**
|
|
214
|
+
* Find a single document matching the filter
|
|
215
|
+
*
|
|
216
|
+
* @param filter - Equality filter conditions
|
|
217
|
+
* @returns First matching document or null if not found
|
|
218
|
+
*/
|
|
219
|
+
findOne(filter: Partial<T>): Promise<T | null>;
|
|
220
|
+
/**
|
|
221
|
+
* Find multiple documents with optional filtering, sorting, and pagination
|
|
222
|
+
*
|
|
223
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
224
|
+
* @returns Array of matching documents
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* // Get all
|
|
229
|
+
* const all = await collection.findMany()
|
|
230
|
+
*
|
|
231
|
+
* // With filter
|
|
232
|
+
* const admins = await collection.findMany({
|
|
233
|
+
* where: { role: 'admin' }
|
|
234
|
+
* })
|
|
235
|
+
*
|
|
236
|
+
* // With sorting and pagination
|
|
237
|
+
* const page = await collection.findMany({
|
|
238
|
+
* where: { status: 'active' },
|
|
239
|
+
* orderBy: { createdAt: 'desc' },
|
|
240
|
+
* limit: 20,
|
|
241
|
+
* offset: 0
|
|
242
|
+
* })
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
findMany(options?: FindManyOptions<T>): Promise<T[]>;
|
|
246
|
+
/**
|
|
247
|
+
* Subscribe to collection changes
|
|
248
|
+
*
|
|
249
|
+
* @param callback - Called whenever data changes with current full collection state
|
|
250
|
+
* @returns Unsubscribe function to remove listener
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```typescript
|
|
254
|
+
* const unsubscribe = collection.subscribe((data) => {
|
|
255
|
+
* console.log('Collection now contains:', data)
|
|
256
|
+
* })
|
|
257
|
+
*
|
|
258
|
+
* // Later: stop listening
|
|
259
|
+
* unsubscribe()
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
subscribe(callback: (data: T[]) => void): () => void;
|
|
263
|
+
/** Internal: notify all subscribers of changes */
|
|
264
|
+
_notify(): void;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Sync adapter interface for remote synchronization
|
|
268
|
+
*
|
|
269
|
+
* @remarks
|
|
270
|
+
* Implement this interface to add custom sync behavior (WebSockets, server polling, etc.)
|
|
271
|
+
* The database automatically calls these methods when mutations occur (if optimistic mode enabled)
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* const syncAdapter: SyncAdapter = {
|
|
276
|
+
* async push(changes) {
|
|
277
|
+
* // Send to server
|
|
278
|
+
* await fetch('/api/sync', { method: 'POST', body: JSON.stringify(changes) })
|
|
279
|
+
* },
|
|
280
|
+
* async pull() {
|
|
281
|
+
* // Get from server
|
|
282
|
+
* const res = await fetch('/api/sync')
|
|
283
|
+
* return res.json()
|
|
284
|
+
* },
|
|
285
|
+
* subscribe(callback) {
|
|
286
|
+
* // Listen for remote changes
|
|
287
|
+
* const ws = new WebSocket('wss://...')
|
|
288
|
+
* ws.onmessage = (e) => callback(e.data)
|
|
289
|
+
* return () => ws.close()
|
|
290
|
+
* }
|
|
291
|
+
* }
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
interface SyncAdapter {
|
|
295
|
+
/**
|
|
296
|
+
* Push local changes to remote
|
|
297
|
+
*
|
|
298
|
+
* @param changes - Array of change objects to send to remote
|
|
299
|
+
* @throws Error if push fails - mutation will be rolled back if optimistic
|
|
300
|
+
*/
|
|
301
|
+
push(changes: unknown[]): Promise<void>;
|
|
302
|
+
/**
|
|
303
|
+
* Pull remote changes
|
|
304
|
+
*
|
|
305
|
+
* @returns Array of changes from remote
|
|
306
|
+
*/
|
|
307
|
+
pull(): Promise<unknown[]>;
|
|
308
|
+
/**
|
|
309
|
+
* Subscribe to remote changes
|
|
310
|
+
*
|
|
311
|
+
* @param callback - Called with remote changes
|
|
312
|
+
* @returns Unsubscribe function
|
|
313
|
+
*/
|
|
314
|
+
subscribe(callback: (changes: unknown[]) => void): () => void;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Database configuration for creating a DB instance
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```typescript
|
|
321
|
+
* const config: DBConfig = {
|
|
322
|
+
* collections: [usersCollection, todosCollection],
|
|
323
|
+
* sync: customSyncAdapter
|
|
324
|
+
* }
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
interface DBConfig {
|
|
328
|
+
/** Collections to register in the database */
|
|
329
|
+
collections?: Collection<any>[];
|
|
330
|
+
/** Optional sync adapter for remote synchronization */
|
|
331
|
+
sync?: SyncAdapter;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Database instance - main entry point for accessing collections and syncing
|
|
335
|
+
*
|
|
336
|
+
* @remarks
|
|
337
|
+
* - Collections are accessed by name via `db.collections[name]`
|
|
338
|
+
* - Provides centralized management of all collections
|
|
339
|
+
* - Optional sync adapter for remote data synchronization
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* const db = createDB({
|
|
344
|
+
* collections: [usersCollection, todosCollection],
|
|
345
|
+
* sync: syncAdapter
|
|
346
|
+
* })
|
|
347
|
+
*
|
|
348
|
+
* // Access collections
|
|
349
|
+
* const users = await db.collections.users.findMany()
|
|
350
|
+
*
|
|
351
|
+
* // Clear all data
|
|
352
|
+
* await db.clear()
|
|
353
|
+
*
|
|
354
|
+
* // Close resources
|
|
355
|
+
* db.close()
|
|
356
|
+
* ```
|
|
357
|
+
*/
|
|
358
|
+
interface DB {
|
|
359
|
+
/** Registered collections by name - access with `db.collections[collectionName]` */
|
|
360
|
+
collections: Record<string, Collection<any>>;
|
|
361
|
+
/** Optional sync adapter if configured */
|
|
362
|
+
sync?: SyncAdapter;
|
|
363
|
+
/**
|
|
364
|
+
* Close the database connection and clean up resources
|
|
365
|
+
*/
|
|
366
|
+
close(): void;
|
|
367
|
+
/**
|
|
368
|
+
* Clear all data from all collections
|
|
369
|
+
*
|
|
370
|
+
* @returns Promise that resolves when all data is cleared
|
|
371
|
+
*/
|
|
372
|
+
clear(): Promise<void>;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Options for collection.findMany() - internal collection query
|
|
376
|
+
*
|
|
377
|
+
* @template T - The document type
|
|
378
|
+
*
|
|
379
|
+
* @remarks
|
|
380
|
+
* Unlike `QueryOptions`, this doesn't include `from` since it's called on a collection directly
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```typescript
|
|
384
|
+
* const options: FindManyOptions<User> = {
|
|
385
|
+
* where: { role: 'admin' },
|
|
386
|
+
* orderBy: { name: 'asc' },
|
|
387
|
+
* limit: 10,
|
|
388
|
+
* offset: 0
|
|
389
|
+
* }
|
|
390
|
+
* const users = await collection.findMany(options)
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
interface FindManyOptions<T> {
|
|
394
|
+
/** Filter conditions using WhereClause syntax */
|
|
395
|
+
where?: WhereClause<T>;
|
|
396
|
+
/** Sort order - single field or array of fields with directions */
|
|
397
|
+
orderBy?: OrderByClause<T> | OrderByClause<T>[];
|
|
398
|
+
/** Maximum number of results to return */
|
|
399
|
+
limit?: number;
|
|
400
|
+
/** Number of results to skip (for pagination) */
|
|
401
|
+
offset?: number;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Options for useQuery hook - React hook query interface
|
|
405
|
+
*
|
|
406
|
+
* @template T - The document type being queried
|
|
407
|
+
*
|
|
408
|
+
* @remarks
|
|
409
|
+
* Extends `FindManyOptions` with `from` to specify which collection to query.
|
|
410
|
+
* Called within a `DBProvider` context.
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```typescript
|
|
414
|
+
* const { data, isLoading, error } = useQuery<User>({
|
|
415
|
+
* from: 'users',
|
|
416
|
+
* where: { role: 'admin', age: { $gte: 18 } },
|
|
417
|
+
* orderBy: { name: 'asc' },
|
|
418
|
+
* limit: 20
|
|
419
|
+
* })
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
interface QueryOptions<T> extends FindManyOptions<T> {
|
|
423
|
+
/** Collection name to query from */
|
|
424
|
+
from: string;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Result of useQuery hook - reactive query state and refetch control
|
|
428
|
+
*
|
|
429
|
+
* @template T - The document type
|
|
430
|
+
*
|
|
431
|
+
* @remarks
|
|
432
|
+
* - `data` starts as undefined while loading
|
|
433
|
+
* - `isLoading` is true until first query completes
|
|
434
|
+
* - Automatically updates when collection changes (reactive)
|
|
435
|
+
* - Subscribe callbacks automatically re-apply filters and sorting
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```typescript
|
|
439
|
+
* const { data, isLoading, error, refetch } = useQuery<User>({
|
|
440
|
+
* from: 'users'
|
|
441
|
+
* })
|
|
442
|
+
*
|
|
443
|
+
* if (isLoading) return <Spinner />
|
|
444
|
+
* if (error) return <Error message={error.message} />
|
|
445
|
+
*
|
|
446
|
+
* return (
|
|
447
|
+
* <div>
|
|
448
|
+
* {data?.map(user => (
|
|
449
|
+
* <UserCard key={user.id} user={user} />
|
|
450
|
+
* ))}
|
|
451
|
+
* <button onClick={refetch}>Refresh</button>
|
|
452
|
+
* </div>
|
|
453
|
+
* )
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
interface QueryResult<T> {
|
|
457
|
+
/** Query results - undefined while loading, array when loaded */
|
|
458
|
+
data: T[] | undefined;
|
|
459
|
+
/** True while initial query is executing */
|
|
460
|
+
isLoading: boolean;
|
|
461
|
+
/** Error object if query failed, undefined if successful */
|
|
462
|
+
error: Error | undefined;
|
|
463
|
+
/** Function to manually refetch the query */
|
|
464
|
+
refetch: () => void;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Type of mutation operation
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* type Op = MutationOperation
|
|
472
|
+
* const operation: Op = 'insert'
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
type MutationOperation = 'insert' | 'update' | 'delete';
|
|
476
|
+
/**
|
|
477
|
+
* Options for useMutation hook
|
|
478
|
+
*
|
|
479
|
+
* @remarks
|
|
480
|
+
* - `insert`: Creates new document
|
|
481
|
+
* - `update`: Updates existing documents matching filter
|
|
482
|
+
* - `delete`: Removes documents matching filter
|
|
483
|
+
* - `optimistic`: If true, updates UI immediately before server confirmation
|
|
484
|
+
* (on error, automatically rolls back)
|
|
485
|
+
*
|
|
486
|
+
* @example
|
|
487
|
+
* ```typescript
|
|
488
|
+
* const { mutate, isPending, error } = useMutation({
|
|
489
|
+
* collection: 'users',
|
|
490
|
+
* operation: 'insert',
|
|
491
|
+
* optimistic: true
|
|
492
|
+
* })
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
interface MutationOptions {
|
|
496
|
+
/** Collection name to mutate */
|
|
497
|
+
collection: string;
|
|
498
|
+
/** Type of operation: insert, update, or delete */
|
|
499
|
+
operation: MutationOperation;
|
|
500
|
+
/** Enable optimistic updates (show change immediately, rollback on error) */
|
|
501
|
+
optimistic?: boolean;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Data format for update mutations
|
|
505
|
+
*
|
|
506
|
+
* @template T - The document type
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* ```typescript
|
|
510
|
+
* const updateData: UpdateMutationData<User> = {
|
|
511
|
+
* where: { id: '1' },
|
|
512
|
+
* data: { name: 'Updated Name' }
|
|
513
|
+
* }
|
|
514
|
+
* ```
|
|
515
|
+
*/
|
|
516
|
+
interface UpdateMutationData<T> {
|
|
517
|
+
/** Filter to select which documents to update */
|
|
518
|
+
where: Partial<T>;
|
|
519
|
+
/** Partial updates to apply to matching documents */
|
|
520
|
+
data: Partial<T>;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Data format for delete mutations
|
|
524
|
+
*
|
|
525
|
+
* @template T - The document type (generic for flexibility)
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* ```typescript
|
|
529
|
+
* const deleteData: DeleteMutationData<User> = {
|
|
530
|
+
* id: 'user-123'
|
|
531
|
+
* }
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
interface DeleteMutationData<T> {
|
|
535
|
+
/** ID of document to delete */
|
|
536
|
+
id: string;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Result of useMutation hook - mutation execution and state control
|
|
540
|
+
*
|
|
541
|
+
* @template T - The document type
|
|
542
|
+
*
|
|
543
|
+
* @remarks
|
|
544
|
+
* - Call `mutate()` with data to execute the operation
|
|
545
|
+
* - `isPending` is true while mutation is executing
|
|
546
|
+
* - On error, `error` contains the Error object
|
|
547
|
+
* - Use `reset()` to clear error state after handling
|
|
548
|
+
* - For optimistic updates, local UI updates immediately
|
|
549
|
+
* (automatic rollback if sync fails)
|
|
550
|
+
*
|
|
551
|
+
* @example
|
|
552
|
+
* ```typescript
|
|
553
|
+
* const { mutate, isPending, error, reset } = useMutation<User>({
|
|
554
|
+
* collection: 'users',
|
|
555
|
+
* operation: 'insert'
|
|
556
|
+
* })
|
|
557
|
+
*
|
|
558
|
+
* const handleCreate = async () => {
|
|
559
|
+
* try {
|
|
560
|
+
* await mutate({
|
|
561
|
+
* id: '2',
|
|
562
|
+
* name: 'Bob',
|
|
563
|
+
* email: 'bob@example.com',
|
|
564
|
+
* role: 'user'
|
|
565
|
+
* })
|
|
566
|
+
* } catch (err) {
|
|
567
|
+
* console.error('Failed:', err)
|
|
568
|
+
* }
|
|
569
|
+
* }
|
|
570
|
+
*
|
|
571
|
+
* return (
|
|
572
|
+
* <form onSubmit={handleCreate}>
|
|
573
|
+
* <button disabled={isPending}>
|
|
574
|
+
* {isPending ? 'Creating...' : 'Create'}
|
|
575
|
+
* </button>
|
|
576
|
+
* {error && <Error message={error.message} onDismiss={reset} />}
|
|
577
|
+
* </form>
|
|
578
|
+
* )
|
|
579
|
+
* ```
|
|
580
|
+
*/
|
|
581
|
+
interface MutationResult<T> {
|
|
582
|
+
/**
|
|
583
|
+
* Execute the mutation with data
|
|
584
|
+
* Data format depends on operation: T for insert, UpdateMutationData<T> for update, DeleteMutationData<T> for delete
|
|
585
|
+
*/
|
|
586
|
+
mutate: (data: T | UpdateMutationData<T> | DeleteMutationData<T>) => Promise<void>;
|
|
587
|
+
/** True while mutation is being executed */
|
|
588
|
+
isPending: boolean;
|
|
589
|
+
/** Error object if mutation failed, undefined if successful */
|
|
590
|
+
error: Error | undefined;
|
|
591
|
+
/** Reset mutation state (clears error and pending flags) */
|
|
592
|
+
reset: () => void;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* @mdxui/terminal Database Implementation
|
|
597
|
+
*
|
|
598
|
+
* In-memory database with collection management, sync adapter support,
|
|
599
|
+
* and centralized data access for React applications.
|
|
600
|
+
*/
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Create a database instance with collections and optional sync adapter
|
|
604
|
+
*
|
|
605
|
+
* @param config - Database configuration with collections and sync adapter
|
|
606
|
+
* @returns A fully typed database instance for accessing collections
|
|
607
|
+
*
|
|
608
|
+
* @remarks
|
|
609
|
+
* - Collections are accessed via `db.collections[name]`
|
|
610
|
+
* - All data is stored in-memory (cleared on process exit)
|
|
611
|
+
* - Optional sync adapter can push/pull changes to a remote backend
|
|
612
|
+
* - Use within `DBProvider` for React applications
|
|
613
|
+
* - Thread-safe for concurrent operations
|
|
614
|
+
*
|
|
615
|
+
* @example
|
|
616
|
+
* ```typescript
|
|
617
|
+
* import { z } from 'zod'
|
|
618
|
+
* import { createDB, createCollection } from '@mdxui/terminal'
|
|
619
|
+
*
|
|
620
|
+
* // Define schemas
|
|
621
|
+
* const UserSchema = z.object({
|
|
622
|
+
* id: z.string(),
|
|
623
|
+
* name: z.string(),
|
|
624
|
+
* email: z.string().email(),
|
|
625
|
+
* role: z.enum(['admin', 'user', 'guest'])
|
|
626
|
+
* })
|
|
627
|
+
*
|
|
628
|
+
* // Create collections
|
|
629
|
+
* const usersCollection = createCollection({
|
|
630
|
+
* name: 'users',
|
|
631
|
+
* schema: UserSchema,
|
|
632
|
+
* primaryKey: 'id'
|
|
633
|
+
* })
|
|
634
|
+
*
|
|
635
|
+
* // Create database with collections
|
|
636
|
+
* const db = createDB({
|
|
637
|
+
* collections: [usersCollection],
|
|
638
|
+
* sync: customSyncAdapter // optional
|
|
639
|
+
* })
|
|
640
|
+
*
|
|
641
|
+
* // Access collections
|
|
642
|
+
* const user = await db.collections.users.insert({
|
|
643
|
+
* id: '1',
|
|
644
|
+
* name: 'Alice',
|
|
645
|
+
* email: 'alice@example.com',
|
|
646
|
+
* role: 'admin'
|
|
647
|
+
* })
|
|
648
|
+
*
|
|
649
|
+
* const all = await db.collections.users.findMany()
|
|
650
|
+
* const admins = await db.collections.users.findMany({
|
|
651
|
+
* where: { role: 'admin' }
|
|
652
|
+
* })
|
|
653
|
+
*
|
|
654
|
+
* // Clean up
|
|
655
|
+
* await db.clear()
|
|
656
|
+
* db.close()
|
|
657
|
+
* ```
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* ```typescript
|
|
661
|
+
* // With sync adapter for remote sync
|
|
662
|
+
* const syncAdapter = {
|
|
663
|
+
* async push(changes) {
|
|
664
|
+
* await fetch('/api/sync', {
|
|
665
|
+
* method: 'POST',
|
|
666
|
+
* body: JSON.stringify(changes)
|
|
667
|
+
* })
|
|
668
|
+
* },
|
|
669
|
+
* async pull() {
|
|
670
|
+
* const res = await fetch('/api/sync')
|
|
671
|
+
* return res.json()
|
|
672
|
+
* },
|
|
673
|
+
* subscribe(callback) {
|
|
674
|
+
* const ws = new WebSocket('wss://...')
|
|
675
|
+
* ws.onmessage = (e) => callback(e.data)
|
|
676
|
+
* return () => ws.close()
|
|
677
|
+
* }
|
|
678
|
+
* }
|
|
679
|
+
*
|
|
680
|
+
* const db = createDB({
|
|
681
|
+
* collections: [usersCollection],
|
|
682
|
+
* sync: syncAdapter
|
|
683
|
+
* })
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
declare function createDB(config?: DBConfig): DB;
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* @mdxui/terminal Collection Implementation
|
|
690
|
+
*
|
|
691
|
+
* In-memory collection with Zod validation, reactive subscriptions, filtering,
|
|
692
|
+
* sorting, and pagination support. Provides full CRUD operations with type safety.
|
|
693
|
+
*/
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Configuration for creating a collection
|
|
697
|
+
*
|
|
698
|
+
* @template T - The document type stored in this collection
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```typescript
|
|
702
|
+
* const config: CreateCollectionConfig<User> = {
|
|
703
|
+
* name: 'users',
|
|
704
|
+
* schema: UserSchema,
|
|
705
|
+
* primaryKey: 'id',
|
|
706
|
+
* indexes: ['email', 'role']
|
|
707
|
+
* }
|
|
708
|
+
* ```
|
|
709
|
+
*/
|
|
710
|
+
interface CreateCollectionConfig<T> {
|
|
711
|
+
/** Collection name - used to reference in queries and mutations */
|
|
712
|
+
name: string;
|
|
713
|
+
/** Zod schema for automatic validation on all insert/update operations */
|
|
714
|
+
schema: ZodSchema<T>;
|
|
715
|
+
/** Primary key field (defaults to 'id'). Used for duplicate detection */
|
|
716
|
+
primaryKey?: string;
|
|
717
|
+
/** Fields to index for faster queries (metadata only for future optimization) */
|
|
718
|
+
indexes?: string[];
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Create a typed in-memory collection with Zod validation
|
|
722
|
+
*
|
|
723
|
+
* @template T - The document type (must be a Record for proper typing)
|
|
724
|
+
*
|
|
725
|
+
* @param config - Collection configuration (name, schema, primaryKey, indexes)
|
|
726
|
+
* @returns A fully typed collection instance with CRUD operations and subscriptions
|
|
727
|
+
*
|
|
728
|
+
* @remarks
|
|
729
|
+
* - All documents are validated against the provided Zod schema on insert/update
|
|
730
|
+
* - Primary key defaults to 'id' if not specified
|
|
731
|
+
* - Provides reactive subscriptions for real-time updates
|
|
732
|
+
* - Supports filtering with where clauses and comparison operators
|
|
733
|
+
* - Supports sorting with orderBy and multiple sort fields
|
|
734
|
+
* - Supports pagination with limit and offset
|
|
735
|
+
* - Thread-safe for concurrent operations (in-memory only)
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* ```typescript
|
|
739
|
+
* import { z } from 'zod'
|
|
740
|
+
* import { createCollection } from '@mdxui/terminal'
|
|
741
|
+
*
|
|
742
|
+
* // Define schema
|
|
743
|
+
* const UserSchema = z.object({
|
|
744
|
+
* id: z.string(),
|
|
745
|
+
* name: z.string(),
|
|
746
|
+
* email: z.string().email(),
|
|
747
|
+
* age: z.number().optional(),
|
|
748
|
+
* role: z.enum(['admin', 'user', 'guest'])
|
|
749
|
+
* })
|
|
750
|
+
*
|
|
751
|
+
* type User = z.infer<typeof UserSchema>
|
|
752
|
+
*
|
|
753
|
+
* // Create collection
|
|
754
|
+
* const users = createCollection<User>({
|
|
755
|
+
* name: 'users',
|
|
756
|
+
* schema: UserSchema,
|
|
757
|
+
* primaryKey: 'id',
|
|
758
|
+
* indexes: ['email', 'role']
|
|
759
|
+
* })
|
|
760
|
+
*
|
|
761
|
+
* // CRUD Operations
|
|
762
|
+
* const user = await users.insert({
|
|
763
|
+
* id: '1',
|
|
764
|
+
* name: 'Alice',
|
|
765
|
+
* email: 'alice@example.com',
|
|
766
|
+
* role: 'admin'
|
|
767
|
+
* })
|
|
768
|
+
*
|
|
769
|
+
* const updated = await users.update(
|
|
770
|
+
* { id: '1' },
|
|
771
|
+
* { name: 'Alicia' }
|
|
772
|
+
* )
|
|
773
|
+
*
|
|
774
|
+
* const found = await users.findOne({ id: '1' })
|
|
775
|
+
*
|
|
776
|
+
* const admins = await users.findMany({
|
|
777
|
+
* where: { role: 'admin' },
|
|
778
|
+
* orderBy: { name: 'asc' }
|
|
779
|
+
* })
|
|
780
|
+
*
|
|
781
|
+
* await users.delete({ id: '1' })
|
|
782
|
+
*
|
|
783
|
+
* // Subscribe to changes
|
|
784
|
+
* const unsubscribe = users.subscribe((allUsers) => {
|
|
785
|
+
* console.log('Users updated:', allUsers)
|
|
786
|
+
* })
|
|
787
|
+
* ```
|
|
788
|
+
*/
|
|
789
|
+
declare function createCollection<T extends Record<string, any>>(config: CreateCollectionConfig<T>): Collection<T>;
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* @mdxui/terminal DO Sync Adapter
|
|
793
|
+
*
|
|
794
|
+
* Durable Objects sync adapter for bidirectional data synchronization
|
|
795
|
+
* via WebSocket connection. Provides:
|
|
796
|
+
* - WebSocket connection lifecycle management
|
|
797
|
+
* - Bidirectional data synchronization
|
|
798
|
+
* - Auth header injection for authenticated requests
|
|
799
|
+
* - Optimistic updates with server confirmation
|
|
800
|
+
* - Automatic reconnection with exponential backoff + jitter
|
|
801
|
+
* - Offline mutation queue for resilience
|
|
802
|
+
* - Connection state observable
|
|
803
|
+
* - Error categorization (recoverable vs fatal)
|
|
804
|
+
*
|
|
805
|
+
* @remarks
|
|
806
|
+
* ## Connection State Machine
|
|
807
|
+
*
|
|
808
|
+
* ```
|
|
809
|
+
* ┌─────────────┐ connect() ┌────────────┐
|
|
810
|
+
* │ disconnected│──────────────►│ connecting │
|
|
811
|
+
* └─────────────┘ └────────────┘
|
|
812
|
+
* ▲ │
|
|
813
|
+
* │ │ onopen
|
|
814
|
+
* │ close() ▼
|
|
815
|
+
* │ ┌───────────┐
|
|
816
|
+
* │◄──────────────────────│ connected │
|
|
817
|
+
* │ close() / fatal err └───────────┘
|
|
818
|
+
* │ │
|
|
819
|
+
* │ │ recoverable error / close
|
|
820
|
+
* │ ▼
|
|
821
|
+
* │ ┌─────────────┐
|
|
822
|
+
* │◄─────────────────────│ reconnecting│───► (loop with backoff)
|
|
823
|
+
* │ max attempts └─────────────┘
|
|
824
|
+
* ```
|
|
825
|
+
*
|
|
826
|
+
* ## Error Categories
|
|
827
|
+
*
|
|
828
|
+
* **Recoverable errors** - trigger reconnection:
|
|
829
|
+
* - Network timeouts
|
|
830
|
+
* - Temporary server unavailability
|
|
831
|
+
* - WebSocket close codes 1001 (going away), 1006 (abnormal closure)
|
|
832
|
+
*
|
|
833
|
+
* **Fatal errors** - require user intervention:
|
|
834
|
+
* - Authentication failures (401, 403)
|
|
835
|
+
* - Invalid namespace URL
|
|
836
|
+
* - WebSocket close code 1008 (policy violation)
|
|
837
|
+
* - Close code 4000+ (application-level errors)
|
|
838
|
+
*
|
|
839
|
+
* @module
|
|
840
|
+
*/
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Connection state for monitoring adapter health.
|
|
844
|
+
*
|
|
845
|
+
* State transitions follow a deterministic state machine pattern:
|
|
846
|
+
*
|
|
847
|
+
* @remarks
|
|
848
|
+
* - `disconnected`: No active WebSocket connection. Initial state and terminal state.
|
|
849
|
+
* - `connecting`: WebSocket connection in progress. Transitions to `connected` on success,
|
|
850
|
+
* `disconnected` on fatal error, or `reconnecting` on recoverable error (if enabled).
|
|
851
|
+
* - `connected`: Active WebSocket connection ready for sync. Can transition to
|
|
852
|
+
* `disconnected` on close/fatal error or `reconnecting` on recoverable error.
|
|
853
|
+
* - `reconnecting`: Attempting to re-establish connection after a recoverable failure.
|
|
854
|
+
* Includes exponential backoff with jitter. Transitions to `connecting` when attempting,
|
|
855
|
+
* `disconnected` when max attempts reached or user calls close().
|
|
856
|
+
*
|
|
857
|
+
* @example
|
|
858
|
+
* ```typescript
|
|
859
|
+
* const sync = createDOSync({ namespaceUrl: '...' })
|
|
860
|
+
*
|
|
861
|
+
* sync.onConnectionStateChange((state) => {
|
|
862
|
+
* switch (state) {
|
|
863
|
+
* case 'disconnected':
|
|
864
|
+
* showOfflineIndicator()
|
|
865
|
+
* break
|
|
866
|
+
* case 'connecting':
|
|
867
|
+
* case 'reconnecting':
|
|
868
|
+
* showConnectingSpinner()
|
|
869
|
+
* break
|
|
870
|
+
* case 'connected':
|
|
871
|
+
* hideOfflineIndicator()
|
|
872
|
+
* break
|
|
873
|
+
* }
|
|
874
|
+
* })
|
|
875
|
+
* ```
|
|
876
|
+
*/
|
|
877
|
+
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
|
|
878
|
+
/**
|
|
879
|
+
* Queued mutation for offline resilience.
|
|
880
|
+
*
|
|
881
|
+
* @remarks
|
|
882
|
+
* When the adapter is offline, mutations are queued and automatically
|
|
883
|
+
* retried once the connection is re-established. This ensures no data
|
|
884
|
+
* is lost during temporary disconnections.
|
|
885
|
+
*
|
|
886
|
+
* ## Lifecycle
|
|
887
|
+
* 1. Push fails due to connection error
|
|
888
|
+
* 2. Mutation is added to queue with timestamp
|
|
889
|
+
* 3. Connection is restored
|
|
890
|
+
* 4. Queue is flushed in FIFO order
|
|
891
|
+
* 5. Successful mutations are removed from queue
|
|
892
|
+
* 6. Failed mutations remain with incremented retryCount
|
|
893
|
+
*
|
|
894
|
+
* ## Inspection
|
|
895
|
+
* Use `getQueuedMutations()` to inspect the queue for debugging
|
|
896
|
+
* or displaying pending sync status to users.
|
|
897
|
+
*
|
|
898
|
+
* @example
|
|
899
|
+
* ```typescript
|
|
900
|
+
* // Display pending changes to user
|
|
901
|
+
* const pending = adapter.getQueuedMutations()
|
|
902
|
+
* if (pending.length > 0) {
|
|
903
|
+
* console.log(`${pending.length} changes waiting to sync`)
|
|
904
|
+
* }
|
|
905
|
+
* ```
|
|
906
|
+
*/
|
|
907
|
+
interface QueuedMutation {
|
|
908
|
+
/** Unique identifier for this mutation (auto-generated) */
|
|
909
|
+
id: string;
|
|
910
|
+
/** The changes to sync to the server */
|
|
911
|
+
changes: unknown[];
|
|
912
|
+
/** Timestamp (ms since epoch) when mutation was queued */
|
|
913
|
+
queuedAt: number;
|
|
914
|
+
/** Number of failed retry attempts (0 = first attempt pending) */
|
|
915
|
+
retryCount: number;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Reconnection configuration options.
|
|
919
|
+
*
|
|
920
|
+
* @remarks
|
|
921
|
+
* Configures automatic reconnection behavior with exponential backoff.
|
|
922
|
+
* When enabled, the adapter will automatically attempt to reconnect
|
|
923
|
+
* on connection loss until maxAttempts is reached.
|
|
924
|
+
*
|
|
925
|
+
* ## Defaults
|
|
926
|
+
* - `enabled`: false (opt-in for auto-reconnect)
|
|
927
|
+
* - `maxAttempts`: Infinity (keep trying forever)
|
|
928
|
+
* - `initialDelay`: 1000ms (1 second)
|
|
929
|
+
* - `maxDelay`: 30000ms (30 seconds cap)
|
|
930
|
+
*
|
|
931
|
+
* ## Backoff Formula
|
|
932
|
+
* `delay = min(initialDelay * 2^attempt, maxDelay)`
|
|
933
|
+
*
|
|
934
|
+
* @example
|
|
935
|
+
* ```typescript
|
|
936
|
+
* // Recommended production config
|
|
937
|
+
* const sync = createDOSync({
|
|
938
|
+
* namespaceUrl: '...',
|
|
939
|
+
* reconnect: {
|
|
940
|
+
* enabled: true,
|
|
941
|
+
* maxAttempts: 10,
|
|
942
|
+
* initialDelay: 1000,
|
|
943
|
+
* maxDelay: 30000
|
|
944
|
+
* }
|
|
945
|
+
* })
|
|
946
|
+
* ```
|
|
947
|
+
*/
|
|
948
|
+
interface ReconnectOptions {
|
|
949
|
+
/**
|
|
950
|
+
* Whether to automatically reconnect on connection loss.
|
|
951
|
+
* @defaultValue false
|
|
952
|
+
*/
|
|
953
|
+
enabled?: boolean;
|
|
954
|
+
/**
|
|
955
|
+
* Maximum number of reconnection attempts before giving up.
|
|
956
|
+
* Set to Infinity to retry indefinitely.
|
|
957
|
+
* @defaultValue Infinity
|
|
958
|
+
*/
|
|
959
|
+
maxAttempts?: number;
|
|
960
|
+
/**
|
|
961
|
+
* Initial delay in ms before first reconnection attempt.
|
|
962
|
+
* Subsequent delays are calculated with exponential backoff.
|
|
963
|
+
* @defaultValue 1000
|
|
964
|
+
*/
|
|
965
|
+
initialDelay?: number;
|
|
966
|
+
/**
|
|
967
|
+
* Maximum delay in ms between reconnection attempts (backoff cap).
|
|
968
|
+
* @defaultValue 30000
|
|
969
|
+
*/
|
|
970
|
+
maxDelay?: number;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Conflict resolution strategy for handling server-client data conflicts.
|
|
974
|
+
*
|
|
975
|
+
* @remarks
|
|
976
|
+
* - `server-wins`: Server version is authoritative, client changes discarded
|
|
977
|
+
* - `client-wins`: Client version is authoritative, server changes overwritten
|
|
978
|
+
* - `merge`: Attempt to merge both versions (server determines merge logic)
|
|
979
|
+
* - `throw`: Reject push with conflict error, let caller handle
|
|
980
|
+
* - `custom`: Call `onConflict` callback for custom resolution logic
|
|
981
|
+
*/
|
|
982
|
+
type ConflictResolution = 'server-wins' | 'client-wins' | 'merge' | 'throw' | 'custom';
|
|
983
|
+
/**
|
|
984
|
+
* Conflict details returned by server when client/server versions diverge.
|
|
985
|
+
*/
|
|
986
|
+
interface Conflict {
|
|
987
|
+
/** ID of the conflicting entity */
|
|
988
|
+
id: string;
|
|
989
|
+
/** Server's current version of the entity */
|
|
990
|
+
serverVersion: Record<string, unknown>;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Configuration for creating a DO Sync adapter.
|
|
994
|
+
*
|
|
995
|
+
* @remarks
|
|
996
|
+
* ## Minimal Configuration
|
|
997
|
+
* ```typescript
|
|
998
|
+
* const sync = createDOSync({
|
|
999
|
+
* namespaceUrl: 'https://api.example.com/namespace'
|
|
1000
|
+
* })
|
|
1001
|
+
* ```
|
|
1002
|
+
*
|
|
1003
|
+
* ## Full Configuration
|
|
1004
|
+
* ```typescript
|
|
1005
|
+
* const sync = createDOSync({
|
|
1006
|
+
* namespaceUrl: 'https://api.example.com/namespace',
|
|
1007
|
+
* authToken: 'jwt-token',
|
|
1008
|
+
* reconnect: { enabled: true, maxAttempts: 10 },
|
|
1009
|
+
* conflictResolution: 'server-wins',
|
|
1010
|
+
* requestTimeout: 10000
|
|
1011
|
+
* })
|
|
1012
|
+
* ```
|
|
1013
|
+
*/
|
|
1014
|
+
interface DOSyncConfig {
|
|
1015
|
+
/**
|
|
1016
|
+
* URL of the Durable Objects namespace endpoint.
|
|
1017
|
+
* Will be converted to WebSocket URL (https -> wss, http -> ws).
|
|
1018
|
+
*
|
|
1019
|
+
* @example 'https://api.example.com/namespace'
|
|
1020
|
+
*/
|
|
1021
|
+
namespaceUrl: string;
|
|
1022
|
+
/**
|
|
1023
|
+
* Optional auth token for authenticated connections.
|
|
1024
|
+
* Sent as URL query param and in message payloads.
|
|
1025
|
+
* Can be updated dynamically via `setAuthToken()`.
|
|
1026
|
+
*/
|
|
1027
|
+
authToken?: string;
|
|
1028
|
+
/**
|
|
1029
|
+
* Automatic reconnection options.
|
|
1030
|
+
* @see ReconnectOptions for defaults and configuration
|
|
1031
|
+
*/
|
|
1032
|
+
reconnect?: ReconnectOptions;
|
|
1033
|
+
/**
|
|
1034
|
+
* Strategy for resolving server-client data conflicts.
|
|
1035
|
+
* @defaultValue 'server-wins' (implicit - server decides)
|
|
1036
|
+
*/
|
|
1037
|
+
conflictResolution?: ConflictResolution;
|
|
1038
|
+
/**
|
|
1039
|
+
* Custom conflict resolver callback.
|
|
1040
|
+
* Required when `conflictResolution` is 'custom'.
|
|
1041
|
+
*
|
|
1042
|
+
* @param conflicts - Array of conflicting entities with server versions
|
|
1043
|
+
* @returns Promise resolving to `{ resolved: true }` when conflicts handled
|
|
1044
|
+
*/
|
|
1045
|
+
onConflict?: (conflicts: Conflict[]) => Promise<{
|
|
1046
|
+
resolved: boolean;
|
|
1047
|
+
}>;
|
|
1048
|
+
/**
|
|
1049
|
+
* Request timeout in milliseconds for push/pull operations.
|
|
1050
|
+
* Operations will reject with timeout error if no response received.
|
|
1051
|
+
* @defaultValue 30000 (30 seconds)
|
|
1052
|
+
*/
|
|
1053
|
+
requestTimeout?: number;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Connection state observer callback type.
|
|
1057
|
+
*
|
|
1058
|
+
* @remarks
|
|
1059
|
+
* Called whenever the connection state changes. Useful for UI feedback
|
|
1060
|
+
* (e.g., showing "offline" status, disabling sync buttons, etc.).
|
|
1061
|
+
*
|
|
1062
|
+
* @param state - The new connection state
|
|
1063
|
+
*/
|
|
1064
|
+
type ConnectionStateObserver = (state: ConnectionState) => void;
|
|
1065
|
+
/**
|
|
1066
|
+
* Extended sync adapter with connection management and state monitoring.
|
|
1067
|
+
*
|
|
1068
|
+
* @remarks
|
|
1069
|
+
* Extends the base SyncAdapter with:
|
|
1070
|
+
* - Connection lifecycle management (`close()`)
|
|
1071
|
+
* - Dynamic auth token updates (`setAuthToken()`)
|
|
1072
|
+
* - Reactive connection state (`onConnectionStateChange()`, `getConnectionState()`)
|
|
1073
|
+
* - Offline queue inspection (`getQueuedMutations()`, `getQueueStats()`)
|
|
1074
|
+
*
|
|
1075
|
+
* ## Offline Resilience
|
|
1076
|
+
* When push operations fail due to connection errors, mutations are
|
|
1077
|
+
* automatically queued and retried when the connection is restored.
|
|
1078
|
+
* This ensures no data is lost during temporary disconnections.
|
|
1079
|
+
*
|
|
1080
|
+
* @example
|
|
1081
|
+
* ```typescript
|
|
1082
|
+
* const sync = createDOSync({ namespaceUrl: '...' })
|
|
1083
|
+
*
|
|
1084
|
+
* // React to connection state changes
|
|
1085
|
+
* sync.onConnectionStateChange((state) => {
|
|
1086
|
+
* updateStatusIndicator(state)
|
|
1087
|
+
* })
|
|
1088
|
+
*
|
|
1089
|
+
* // Check pending changes
|
|
1090
|
+
* const { count, oldestAt } = sync.getQueueStats()
|
|
1091
|
+
* if (count > 0) {
|
|
1092
|
+
* showPendingBanner(`${count} changes pending since ${new Date(oldestAt!)}`)
|
|
1093
|
+
* }
|
|
1094
|
+
*
|
|
1095
|
+
* // Cleanup on unmount
|
|
1096
|
+
* sync.close()
|
|
1097
|
+
* ```
|
|
1098
|
+
*/
|
|
1099
|
+
interface DOSyncAdapter extends SyncAdapter {
|
|
1100
|
+
/**
|
|
1101
|
+
* Close the WebSocket connection and stop all reconnection attempts.
|
|
1102
|
+
* Call this when unmounting or navigating away to clean up resources.
|
|
1103
|
+
*/
|
|
1104
|
+
close(): void;
|
|
1105
|
+
/**
|
|
1106
|
+
* Update the auth token dynamically.
|
|
1107
|
+
* Useful for refreshing expired tokens without recreating the adapter.
|
|
1108
|
+
* The new token will be used for subsequent connections.
|
|
1109
|
+
*
|
|
1110
|
+
* @param token - New auth token
|
|
1111
|
+
*/
|
|
1112
|
+
setAuthToken(token: string): void;
|
|
1113
|
+
/**
|
|
1114
|
+
* Subscribe to connection state changes.
|
|
1115
|
+
* Callback is called immediately with current state, then on each change.
|
|
1116
|
+
*
|
|
1117
|
+
* @param callback - Observer function called on state changes
|
|
1118
|
+
* @returns Unsubscribe function
|
|
1119
|
+
*/
|
|
1120
|
+
onConnectionStateChange(callback: ConnectionStateObserver): () => void;
|
|
1121
|
+
/**
|
|
1122
|
+
* Get current connection state without subscribing.
|
|
1123
|
+
* Use for one-time checks; use `onConnectionStateChange()` for reactive updates.
|
|
1124
|
+
*
|
|
1125
|
+
* @returns Current connection state
|
|
1126
|
+
*/
|
|
1127
|
+
getConnectionState(): ConnectionState;
|
|
1128
|
+
/**
|
|
1129
|
+
* Get all mutations currently in the offline queue.
|
|
1130
|
+
* Useful for debugging or displaying pending sync status.
|
|
1131
|
+
*
|
|
1132
|
+
* @returns Array of queued mutations
|
|
1133
|
+
*/
|
|
1134
|
+
getQueuedMutations(): QueuedMutation[];
|
|
1135
|
+
/**
|
|
1136
|
+
* Get summary statistics about the offline queue.
|
|
1137
|
+
*
|
|
1138
|
+
* @returns Object with:
|
|
1139
|
+
* - `count`: Number of pending mutations
|
|
1140
|
+
* - `oldestAt`: Timestamp of oldest mutation (null if empty)
|
|
1141
|
+
*/
|
|
1142
|
+
getQueueStats(): {
|
|
1143
|
+
count: number;
|
|
1144
|
+
oldestAt: number | null;
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Create a Durable Objects sync adapter
|
|
1149
|
+
*
|
|
1150
|
+
* @param config - Configuration options
|
|
1151
|
+
* @returns A sync adapter instance for use with createDB
|
|
1152
|
+
*
|
|
1153
|
+
* @throws Error if namespaceUrl is empty or invalid
|
|
1154
|
+
*
|
|
1155
|
+
* @example
|
|
1156
|
+
* ```typescript
|
|
1157
|
+
* import { createDOSync } from '@mdxui/terminal'
|
|
1158
|
+
*
|
|
1159
|
+
* const sync = createDOSync({
|
|
1160
|
+
* namespaceUrl: 'https://api.example.com/namespace',
|
|
1161
|
+
* authToken: 'your-auth-token',
|
|
1162
|
+
* reconnect: { enabled: true, maxAttempts: 5 }
|
|
1163
|
+
* })
|
|
1164
|
+
*
|
|
1165
|
+
* const db = createDB({
|
|
1166
|
+
* collections: [usersCollection],
|
|
1167
|
+
* sync
|
|
1168
|
+
* })
|
|
1169
|
+
* ```
|
|
1170
|
+
*/
|
|
1171
|
+
declare function createDOSync(config: DOSyncConfig): DOSyncAdapter;
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* @mdxui/terminal Database Context
|
|
1175
|
+
*
|
|
1176
|
+
* React Context API provider for database access throughout component tree.
|
|
1177
|
+
* Enables hooks like `useQuery` and `useMutation` to access the database
|
|
1178
|
+
* without prop drilling.
|
|
1179
|
+
*/
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Props for DBProvider component
|
|
1183
|
+
*
|
|
1184
|
+
* @example
|
|
1185
|
+
* ```typescript
|
|
1186
|
+
* const props: DBProviderProps = {
|
|
1187
|
+
* db: myDatabase,
|
|
1188
|
+
* children: <App />
|
|
1189
|
+
* }
|
|
1190
|
+
* ```
|
|
1191
|
+
*/
|
|
1192
|
+
interface DBProviderProps {
|
|
1193
|
+
/** Database instance to provide to all children */
|
|
1194
|
+
db: DB;
|
|
1195
|
+
/** React components that will have access to the database */
|
|
1196
|
+
children: ReactNode;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Database provider component - wraps your app to provide database context
|
|
1200
|
+
*
|
|
1201
|
+
* Provides database access to all child components via React Context.
|
|
1202
|
+
* Must wrap any component using `useQuery` or `useMutation` hooks.
|
|
1203
|
+
*
|
|
1204
|
+
* @remarks
|
|
1205
|
+
* - Use at the top level of your app (e.g., around `<App />`)
|
|
1206
|
+
* - Can have multiple DBProviders with different database instances
|
|
1207
|
+
* - All useQuery and useMutation hooks must be within a DBProvider
|
|
1208
|
+
* - Throws error if hooks are used outside DBProvider
|
|
1209
|
+
*
|
|
1210
|
+
* @example
|
|
1211
|
+
* ```tsx
|
|
1212
|
+
* import { z } from 'zod'
|
|
1213
|
+
* import { createDB, createCollection, DBProvider } from '@mdxui/terminal'
|
|
1214
|
+
*
|
|
1215
|
+
* // Create database
|
|
1216
|
+
* const db = createDB({
|
|
1217
|
+
* collections: [usersCollection, todosCollection]
|
|
1218
|
+
* })
|
|
1219
|
+
*
|
|
1220
|
+
* // Wrap app with provider
|
|
1221
|
+
* export function App() {
|
|
1222
|
+
* return (
|
|
1223
|
+
* <DBProvider db={db}>
|
|
1224
|
+
* <UserList />
|
|
1225
|
+
* <TodoForm />
|
|
1226
|
+
* </DBProvider>
|
|
1227
|
+
* )
|
|
1228
|
+
* }
|
|
1229
|
+
*
|
|
1230
|
+
* // Now these hooks work:
|
|
1231
|
+
* function UserList() {
|
|
1232
|
+
* const { data, isLoading } = useQuery({
|
|
1233
|
+
* from: 'users',
|
|
1234
|
+
* where: { role: 'admin' }
|
|
1235
|
+
* })
|
|
1236
|
+
*
|
|
1237
|
+
* if (isLoading) return <div>Loading...</div>
|
|
1238
|
+
* return (
|
|
1239
|
+
* <ul>
|
|
1240
|
+
* {data?.map(user => (
|
|
1241
|
+
* <li key={user.id}>{user.name}</li>
|
|
1242
|
+
* ))}
|
|
1243
|
+
* </ul>
|
|
1244
|
+
* )
|
|
1245
|
+
* }
|
|
1246
|
+
*
|
|
1247
|
+
* function TodoForm() {
|
|
1248
|
+
* const { mutate, isPending } = useMutation({
|
|
1249
|
+
* collection: 'todos',
|
|
1250
|
+
* operation: 'insert'
|
|
1251
|
+
* })
|
|
1252
|
+
*
|
|
1253
|
+
* return (
|
|
1254
|
+
* <form onSubmit={(e) => {
|
|
1255
|
+
* e.preventDefault()
|
|
1256
|
+
* mutate({ id: '1', title: 'New', completed: false })
|
|
1257
|
+
* }}>
|
|
1258
|
+
* <button disabled={isPending}>Add Todo</button>
|
|
1259
|
+
* </form>
|
|
1260
|
+
* )
|
|
1261
|
+
* }
|
|
1262
|
+
* ```
|
|
1263
|
+
*/
|
|
1264
|
+
declare function DBProvider({ db, children }: DBProviderProps): React.ReactElement;
|
|
1265
|
+
/**
|
|
1266
|
+
* Hook to access the database instance from context
|
|
1267
|
+
*
|
|
1268
|
+
* @returns The database instance from the nearest DBProvider
|
|
1269
|
+
* @throws Error if used outside a DBProvider component
|
|
1270
|
+
*
|
|
1271
|
+
* @remarks
|
|
1272
|
+
* - Must be called within a component tree wrapped by DBProvider
|
|
1273
|
+
* - Allows direct access to collections (advanced use case)
|
|
1274
|
+
* - Most apps use useQuery/useMutation instead
|
|
1275
|
+
* - Useful for manual collection operations outside hooks
|
|
1276
|
+
*
|
|
1277
|
+
* @example
|
|
1278
|
+
* ```tsx
|
|
1279
|
+
* // Direct database access (advanced)
|
|
1280
|
+
* function MyComponent() {
|
|
1281
|
+
* const db = useDBContext()
|
|
1282
|
+
*
|
|
1283
|
+
* // Manually query
|
|
1284
|
+
* const handleClick = async () => {
|
|
1285
|
+
* const admins = await db.collections.users.findMany({
|
|
1286
|
+
* where: { role: 'admin' }
|
|
1287
|
+
* })
|
|
1288
|
+
* console.log('Admins:', admins)
|
|
1289
|
+
* }
|
|
1290
|
+
*
|
|
1291
|
+
* return <button onClick={handleClick}>Get Admins</button>
|
|
1292
|
+
* }
|
|
1293
|
+
* ```
|
|
1294
|
+
*
|
|
1295
|
+
* @example
|
|
1296
|
+
* ```tsx
|
|
1297
|
+
* // Subscribe to collection changes (advanced)
|
|
1298
|
+
* function CollectionMonitor() {
|
|
1299
|
+
* const db = useDBContext()
|
|
1300
|
+
*
|
|
1301
|
+
* React.useEffect(() => {
|
|
1302
|
+
* const unsubscribe = db.collections.users.subscribe((users) => {
|
|
1303
|
+
* console.log('Users changed:', users)
|
|
1304
|
+
* })
|
|
1305
|
+
* return unsubscribe
|
|
1306
|
+
* }, [db])
|
|
1307
|
+
*
|
|
1308
|
+
* return <div>Monitoring changes...</div>
|
|
1309
|
+
* }
|
|
1310
|
+
* ```
|
|
1311
|
+
*/
|
|
1312
|
+
declare function useDBContext(): DB;
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* @mdxui/terminal Data Hooks
|
|
1316
|
+
*
|
|
1317
|
+
* React hooks for reactive queries and mutations with optimistic updates.
|
|
1318
|
+
* Provides type-safe hooks for querying and mutating database collections
|
|
1319
|
+
* within a DBProvider context.
|
|
1320
|
+
*/
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* React hook for reactive data queries with filtering, sorting, and pagination
|
|
1324
|
+
*
|
|
1325
|
+
* @template T - The document type being queried
|
|
1326
|
+
*
|
|
1327
|
+
* @param options - Query options (collection name, filters, sorting, pagination)
|
|
1328
|
+
* @returns Query result with reactive data, loading/error states, and refetch control
|
|
1329
|
+
*
|
|
1330
|
+
* @remarks
|
|
1331
|
+
* - Must be used within a DBProvider component
|
|
1332
|
+
* - Automatically fetches initial data on mount
|
|
1333
|
+
* - Reactively updates when collection changes (subscriptions)
|
|
1334
|
+
* - Filters, sorting, and pagination are re-applied when data changes
|
|
1335
|
+
* - `data` is undefined while loading, array when loaded
|
|
1336
|
+
* - `isLoading` is true for the initial fetch only
|
|
1337
|
+
* - Manual refetch available via `refetch()` function
|
|
1338
|
+
* - Error state persists until query succeeds
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* ```tsx
|
|
1342
|
+
* // Basic query - get all documents
|
|
1343
|
+
* function AllUsers() {
|
|
1344
|
+
* const { data, isLoading, error } = useQuery({
|
|
1345
|
+
* from: 'users'
|
|
1346
|
+
* })
|
|
1347
|
+
*
|
|
1348
|
+
* if (isLoading) return <div>Loading...</div>
|
|
1349
|
+
* if (error) return <div>Error: {error.message}</div>
|
|
1350
|
+
*
|
|
1351
|
+
* return (
|
|
1352
|
+
* <ul>
|
|
1353
|
+
* {data?.map(user => (
|
|
1354
|
+
* <li key={user.id}>{user.name}</li>
|
|
1355
|
+
* ))}
|
|
1356
|
+
* </ul>
|
|
1357
|
+
* )
|
|
1358
|
+
* }
|
|
1359
|
+
* ```
|
|
1360
|
+
*
|
|
1361
|
+
* @example
|
|
1362
|
+
* ```tsx
|
|
1363
|
+
* // With filtering
|
|
1364
|
+
* function AdminUsers() {
|
|
1365
|
+
* const { data } = useQuery({
|
|
1366
|
+
* from: 'users',
|
|
1367
|
+
* where: { role: 'admin' }
|
|
1368
|
+
* })
|
|
1369
|
+
*
|
|
1370
|
+
* return data?.map(user => <AdminCard key={user.id} user={user} />)
|
|
1371
|
+
* }
|
|
1372
|
+
* ```
|
|
1373
|
+
*
|
|
1374
|
+
* @example
|
|
1375
|
+
* ```tsx
|
|
1376
|
+
* // With sorting and pagination
|
|
1377
|
+
* function UsersPaginated() {
|
|
1378
|
+
* const { data, refetch } = useQuery({
|
|
1379
|
+
* from: 'users',
|
|
1380
|
+
* where: { status: 'active' },
|
|
1381
|
+
* orderBy: { createdAt: 'desc' },
|
|
1382
|
+
* limit: 20,
|
|
1383
|
+
* offset: 0
|
|
1384
|
+
* })
|
|
1385
|
+
*
|
|
1386
|
+
* return (
|
|
1387
|
+
* <div>
|
|
1388
|
+
* <Users data={data} />
|
|
1389
|
+
* <button onClick={refetch}>Refresh</button>
|
|
1390
|
+
* </div>
|
|
1391
|
+
* )
|
|
1392
|
+
* }
|
|
1393
|
+
* ```
|
|
1394
|
+
*
|
|
1395
|
+
* @example
|
|
1396
|
+
* ```tsx
|
|
1397
|
+
* // With comparison operators
|
|
1398
|
+
* function AdultUsers() {
|
|
1399
|
+
* const { data } = useQuery({
|
|
1400
|
+
* from: 'users',
|
|
1401
|
+
* where: { age: { $gte: 18 } }
|
|
1402
|
+
* })
|
|
1403
|
+
*
|
|
1404
|
+
* return data?.map(user => <Card key={user.id} user={user} />)
|
|
1405
|
+
* }
|
|
1406
|
+
* ```
|
|
1407
|
+
*
|
|
1408
|
+
* @example
|
|
1409
|
+
* ```tsx
|
|
1410
|
+
* // With OR conditions
|
|
1411
|
+
* function AdminsOrModerators() {
|
|
1412
|
+
* const { data } = useQuery({
|
|
1413
|
+
* from: 'users',
|
|
1414
|
+
* where: {
|
|
1415
|
+
* $or: [
|
|
1416
|
+
* { role: 'admin' },
|
|
1417
|
+
* { role: 'moderator' }
|
|
1418
|
+
* ]
|
|
1419
|
+
* }
|
|
1420
|
+
* })
|
|
1421
|
+
*
|
|
1422
|
+
* return data?.map(user => <Card key={user.id} user={user} />)
|
|
1423
|
+
* }
|
|
1424
|
+
* ```
|
|
1425
|
+
*
|
|
1426
|
+
* @example
|
|
1427
|
+
* ```tsx
|
|
1428
|
+
* // With multiple sort fields
|
|
1429
|
+
* function UsersSorted() {
|
|
1430
|
+
* const { data } = useQuery({
|
|
1431
|
+
* from: 'users',
|
|
1432
|
+
* orderBy: [
|
|
1433
|
+
* { role: 'asc' },
|
|
1434
|
+
* { name: 'asc' }
|
|
1435
|
+
* ]
|
|
1436
|
+
* })
|
|
1437
|
+
*
|
|
1438
|
+
* return data?.map(user => <Card key={user.id} user={user} />)
|
|
1439
|
+
* }
|
|
1440
|
+
* ```
|
|
1441
|
+
*/
|
|
1442
|
+
declare function useQuery<T extends Record<string, any>>(options: QueryOptions<T>): QueryResult<T>;
|
|
1443
|
+
/**
|
|
1444
|
+
* React hook for data mutations (insert, update, delete) with optimistic updates
|
|
1445
|
+
*
|
|
1446
|
+
* @template T - The document type being mutated
|
|
1447
|
+
*
|
|
1448
|
+
* @param options - Mutation options (collection, operation, optimistic mode)
|
|
1449
|
+
* @returns Mutation result with mutate function, pending/error states, and reset
|
|
1450
|
+
*
|
|
1451
|
+
* @remarks
|
|
1452
|
+
* - Must be used within a DBProvider component
|
|
1453
|
+
* - Supports three operations: insert, update, delete
|
|
1454
|
+
* - Optimistic updates: UI updates immediately, rolls back on error
|
|
1455
|
+
* - Manual updates: UI updates only after server confirmation
|
|
1456
|
+
* - `isPending` is true while mutation is executing
|
|
1457
|
+
* - Error state persists until mutation succeeds or `reset()` is called
|
|
1458
|
+
* - Data format depends on operation type:
|
|
1459
|
+
* - `insert`: T (full document)
|
|
1460
|
+
* - `update`: UpdateMutationData<T> (where + data)
|
|
1461
|
+
* - `delete`: DeleteMutationData<T> (id)
|
|
1462
|
+
* - Sync adapter is called when optimistic=true and sync is configured
|
|
1463
|
+
*
|
|
1464
|
+
* @example
|
|
1465
|
+
* ```tsx
|
|
1466
|
+
* // Insert with optimistic update
|
|
1467
|
+
* function CreateUserForm() {
|
|
1468
|
+
* const { mutate, isPending, error, reset } = useMutation({
|
|
1469
|
+
* collection: 'users',
|
|
1470
|
+
* operation: 'insert',
|
|
1471
|
+
* optimistic: true
|
|
1472
|
+
* })
|
|
1473
|
+
*
|
|
1474
|
+
* const handleSubmit = async (formData) => {
|
|
1475
|
+
* try {
|
|
1476
|
+
* await mutate({
|
|
1477
|
+
* id: crypto.randomUUID(),
|
|
1478
|
+
* name: formData.name,
|
|
1479
|
+
* email: formData.email,
|
|
1480
|
+
* role: 'user'
|
|
1481
|
+
* })
|
|
1482
|
+
* console.log('User created!')
|
|
1483
|
+
* } catch (err) {
|
|
1484
|
+
* console.error('Failed to create user')
|
|
1485
|
+
* }
|
|
1486
|
+
* }
|
|
1487
|
+
*
|
|
1488
|
+
* return (
|
|
1489
|
+
* <form onSubmit={async (e) => {
|
|
1490
|
+
* e.preventDefault()
|
|
1491
|
+
* await handleSubmit(Object.fromEntries(new FormData(e.currentTarget)))
|
|
1492
|
+
* }}>
|
|
1493
|
+
* <input name="name" required />
|
|
1494
|
+
* <input name="email" type="email" required />
|
|
1495
|
+
* <button disabled={isPending}>
|
|
1496
|
+
* {isPending ? 'Creating...' : 'Create'}
|
|
1497
|
+
* </button>
|
|
1498
|
+
* {error && (
|
|
1499
|
+
* <div>
|
|
1500
|
+
* <p>Error: {error.message}</p>
|
|
1501
|
+
* <button onClick={reset}>Dismiss</button>
|
|
1502
|
+
* </div>
|
|
1503
|
+
* )}
|
|
1504
|
+
* </form>
|
|
1505
|
+
* )
|
|
1506
|
+
* }
|
|
1507
|
+
* ```
|
|
1508
|
+
*
|
|
1509
|
+
* @example
|
|
1510
|
+
* ```tsx
|
|
1511
|
+
* // Update mutation
|
|
1512
|
+
* function EditUserForm({ userId }) {
|
|
1513
|
+
* const { mutate, isPending } = useMutation({
|
|
1514
|
+
* collection: 'users',
|
|
1515
|
+
* operation: 'update'
|
|
1516
|
+
* })
|
|
1517
|
+
*
|
|
1518
|
+
* const handleSave = async (updates) => {
|
|
1519
|
+
* await mutate({
|
|
1520
|
+
* where: { id: userId },
|
|
1521
|
+
* data: updates
|
|
1522
|
+
* })
|
|
1523
|
+
* }
|
|
1524
|
+
*
|
|
1525
|
+
* return <Form onSave={handleSave} disabled={isPending} />
|
|
1526
|
+
* }
|
|
1527
|
+
* ```
|
|
1528
|
+
*
|
|
1529
|
+
* @example
|
|
1530
|
+
* ```tsx
|
|
1531
|
+
* // Delete mutation
|
|
1532
|
+
* function DeleteUserButton({ userId }) {
|
|
1533
|
+
* const { mutate, isPending } = useMutation({
|
|
1534
|
+
* collection: 'users',
|
|
1535
|
+
* operation: 'delete'
|
|
1536
|
+
* })
|
|
1537
|
+
*
|
|
1538
|
+
* const handleDelete = async () => {
|
|
1539
|
+
* if (confirm('Really delete?')) {
|
|
1540
|
+
* await mutate({ id: userId })
|
|
1541
|
+
* }
|
|
1542
|
+
* }
|
|
1543
|
+
*
|
|
1544
|
+
* return (
|
|
1545
|
+
* <button onClick={handleDelete} disabled={isPending}>
|
|
1546
|
+
* {isPending ? 'Deleting...' : 'Delete'}
|
|
1547
|
+
* </button>
|
|
1548
|
+
* )
|
|
1549
|
+
* }
|
|
1550
|
+
* ```
|
|
1551
|
+
*
|
|
1552
|
+
* @example
|
|
1553
|
+
* ```tsx
|
|
1554
|
+
* // Optimistic vs non-optimistic
|
|
1555
|
+
* function UserActions() {
|
|
1556
|
+
* // UI updates immediately, auto-rollback on error
|
|
1557
|
+
* const { mutate: optimisticInsert } = useMutation({
|
|
1558
|
+
* collection: 'users',
|
|
1559
|
+
* operation: 'insert',
|
|
1560
|
+
* optimistic: true // Default UI update
|
|
1561
|
+
* })
|
|
1562
|
+
*
|
|
1563
|
+
* // UI updates only after server confirms
|
|
1564
|
+
* const { mutate: carefullInsert } = useMutation({
|
|
1565
|
+
* collection: 'users',
|
|
1566
|
+
* operation: 'insert',
|
|
1567
|
+
* optimistic: false // Wait for server
|
|
1568
|
+
* })
|
|
1569
|
+
*
|
|
1570
|
+
* return (
|
|
1571
|
+
* <div>
|
|
1572
|
+
* <button onClick={() => optimisticInsert(newUser)}>
|
|
1573
|
+
* Quick Add (optimistic)
|
|
1574
|
+
* </button>
|
|
1575
|
+
* <button onClick={() => carefullInsert(newUser)}>
|
|
1576
|
+
* Safe Add (wait for server)
|
|
1577
|
+
* </button>
|
|
1578
|
+
* </div>
|
|
1579
|
+
* )
|
|
1580
|
+
* }
|
|
1581
|
+
* ```
|
|
1582
|
+
*/
|
|
1583
|
+
declare function useMutation<T extends Record<string, any>>(options: MutationOptions): MutationResult<T>;
|
|
1584
|
+
|
|
1585
|
+
/**
|
|
1586
|
+
* @mdxui/terminal Reactive Data Hooks for Data Components
|
|
1587
|
+
*
|
|
1588
|
+
* This module provides React hooks for connecting data components (Table, List,
|
|
1589
|
+
* Card, Metrics) to TanStack DB collections with reactive updates and optimistic
|
|
1590
|
+
* mutations.
|
|
1591
|
+
*
|
|
1592
|
+
* Key features:
|
|
1593
|
+
* - Reactive data binding - UI updates automatically when collection data changes
|
|
1594
|
+
* - Optimistic updates - UI updates immediately, rolls back on error
|
|
1595
|
+
* - Type-safe queries - Full TypeScript support with Zod validation
|
|
1596
|
+
* - Live subscriptions - Real-time updates from collection changes
|
|
1597
|
+
*
|
|
1598
|
+
* @example
|
|
1599
|
+
* ```tsx
|
|
1600
|
+
* import { useReactiveTable, useReactiveList } from '@mdxui/terminal'
|
|
1601
|
+
*
|
|
1602
|
+
* function UsersTable() {
|
|
1603
|
+
* const { data, isLoading, mutate, sort, setSort } = useReactiveTable({
|
|
1604
|
+
* collection: 'users',
|
|
1605
|
+
* where: { status: 'active' },
|
|
1606
|
+
* orderBy: { name: 'asc' },
|
|
1607
|
+
* })
|
|
1608
|
+
*
|
|
1609
|
+
* // Data updates reactively when collection changes
|
|
1610
|
+
* return (
|
|
1611
|
+
* <Table
|
|
1612
|
+
* columns={[
|
|
1613
|
+
* { key: 'name', header: 'Name', sortable: true },
|
|
1614
|
+
* { key: 'email', header: 'Email' },
|
|
1615
|
+
* ]}
|
|
1616
|
+
* data={data}
|
|
1617
|
+
* sortBy={sort.field}
|
|
1618
|
+
* sortDirection={sort.direction}
|
|
1619
|
+
* onSort={(field) => setSort(field)}
|
|
1620
|
+
* onRowAction={(action, row) => {
|
|
1621
|
+
* if (action === 'delete') {
|
|
1622
|
+
* mutate.delete({ id: row.id })
|
|
1623
|
+
* }
|
|
1624
|
+
* }}
|
|
1625
|
+
* />
|
|
1626
|
+
* )
|
|
1627
|
+
* }
|
|
1628
|
+
* ```
|
|
1629
|
+
*/
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Sort state for reactive data components
|
|
1633
|
+
*
|
|
1634
|
+
* @example
|
|
1635
|
+
* ```typescript
|
|
1636
|
+
* const sort: SortState = {
|
|
1637
|
+
* field: 'createdAt',
|
|
1638
|
+
* direction: 'desc',
|
|
1639
|
+
* }
|
|
1640
|
+
* ```
|
|
1641
|
+
*/
|
|
1642
|
+
interface SortState {
|
|
1643
|
+
/** Field to sort by */
|
|
1644
|
+
field: string | null;
|
|
1645
|
+
/** Sort direction: 'asc' or 'desc' */
|
|
1646
|
+
direction: OrderDirection;
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Selection state for reactive data components
|
|
1650
|
+
*
|
|
1651
|
+
* @example
|
|
1652
|
+
* ```typescript
|
|
1653
|
+
* const selection: SelectionState = {
|
|
1654
|
+
* mode: 'multi',
|
|
1655
|
+
* selected: new Set(['user-1', 'user-3']),
|
|
1656
|
+
* }
|
|
1657
|
+
* ```
|
|
1658
|
+
*/
|
|
1659
|
+
interface SelectionState {
|
|
1660
|
+
/** Selection mode: 'single', 'multi', or 'none' */
|
|
1661
|
+
mode: 'single' | 'multi' | 'none';
|
|
1662
|
+
/** Set of selected item IDs */
|
|
1663
|
+
selected: Set<string>;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Pagination state for reactive data components
|
|
1667
|
+
*
|
|
1668
|
+
* @example
|
|
1669
|
+
* ```typescript
|
|
1670
|
+
* const pagination: PaginationState = {
|
|
1671
|
+
* page: 1,
|
|
1672
|
+
* pageSize: 20,
|
|
1673
|
+
* total: 150,
|
|
1674
|
+
* }
|
|
1675
|
+
* ```
|
|
1676
|
+
*/
|
|
1677
|
+
interface PaginationState {
|
|
1678
|
+
/** Current page (1-indexed) */
|
|
1679
|
+
page: number;
|
|
1680
|
+
/** Items per page */
|
|
1681
|
+
pageSize: number;
|
|
1682
|
+
/** Total item count (from query) */
|
|
1683
|
+
total: number;
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Options for useReactiveData hook
|
|
1687
|
+
*
|
|
1688
|
+
* @template T - The document type
|
|
1689
|
+
*
|
|
1690
|
+
* @remarks
|
|
1691
|
+
* Extends QueryOptions with reactive-specific options like selection, sorting,
|
|
1692
|
+
* and pagination control.
|
|
1693
|
+
*
|
|
1694
|
+
* @example
|
|
1695
|
+
* ```typescript
|
|
1696
|
+
* const options: ReactiveDataOptions<User> = {
|
|
1697
|
+
* collection: 'users',
|
|
1698
|
+
* where: { role: 'admin' },
|
|
1699
|
+
* orderBy: { createdAt: 'desc' },
|
|
1700
|
+
* limit: 20,
|
|
1701
|
+
* selectable: 'multi',
|
|
1702
|
+
* optimistic: true,
|
|
1703
|
+
* }
|
|
1704
|
+
* ```
|
|
1705
|
+
*/
|
|
1706
|
+
interface ReactiveDataOptions<T> {
|
|
1707
|
+
/** Collection name to query from */
|
|
1708
|
+
collection: string;
|
|
1709
|
+
/** Filter conditions */
|
|
1710
|
+
where?: WhereClause<T>;
|
|
1711
|
+
/** Sort order */
|
|
1712
|
+
orderBy?: OrderByClause<T> | OrderByClause<T>[];
|
|
1713
|
+
/** Maximum items to return */
|
|
1714
|
+
limit?: number;
|
|
1715
|
+
/** Items to skip (for pagination) */
|
|
1716
|
+
offset?: number;
|
|
1717
|
+
/** Enable selection: 'single', 'multi', or false */
|
|
1718
|
+
selectable?: 'single' | 'multi' | boolean;
|
|
1719
|
+
/** Enable optimistic updates (default: true) */
|
|
1720
|
+
optimistic?: boolean;
|
|
1721
|
+
/** Primary key field (default: 'id') */
|
|
1722
|
+
primaryKey?: keyof T;
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Result of useReactiveData hook
|
|
1726
|
+
*
|
|
1727
|
+
* @template T - The document type
|
|
1728
|
+
*
|
|
1729
|
+
* @remarks
|
|
1730
|
+
* Provides reactive data, loading/error states, mutation functions, and
|
|
1731
|
+
* controls for sorting, selection, and pagination.
|
|
1732
|
+
*
|
|
1733
|
+
* @example
|
|
1734
|
+
* ```typescript
|
|
1735
|
+
* const {
|
|
1736
|
+
* data,
|
|
1737
|
+
* isLoading,
|
|
1738
|
+
* error,
|
|
1739
|
+
* mutate,
|
|
1740
|
+
* sort,
|
|
1741
|
+
* setSort,
|
|
1742
|
+
* selection,
|
|
1743
|
+
* toggleSelect,
|
|
1744
|
+
* pagination,
|
|
1745
|
+
* setPage,
|
|
1746
|
+
* } = useReactiveData<User>({ collection: 'users' })
|
|
1747
|
+
* ```
|
|
1748
|
+
*/
|
|
1749
|
+
interface ReactiveDataResult<T> {
|
|
1750
|
+
/** Reactive data array - updates when collection changes */
|
|
1751
|
+
data: T[];
|
|
1752
|
+
/** True while initial query is executing */
|
|
1753
|
+
isLoading: boolean;
|
|
1754
|
+
/** Error if query or mutation failed */
|
|
1755
|
+
error: Error | undefined;
|
|
1756
|
+
/** Manual refetch function */
|
|
1757
|
+
refetch: () => void;
|
|
1758
|
+
/** Mutation functions for insert, update, delete */
|
|
1759
|
+
mutate: {
|
|
1760
|
+
/** Insert a new document */
|
|
1761
|
+
insert: (data: T) => Promise<void>;
|
|
1762
|
+
/** Update documents matching filter */
|
|
1763
|
+
update: (filter: Partial<T>, updates: Partial<T>) => Promise<void>;
|
|
1764
|
+
/** Delete a document by ID */
|
|
1765
|
+
delete: (filter: {
|
|
1766
|
+
id: string;
|
|
1767
|
+
} | Partial<T>) => Promise<void>;
|
|
1768
|
+
};
|
|
1769
|
+
/** True while any mutation is pending */
|
|
1770
|
+
isMutating: boolean;
|
|
1771
|
+
/** Current sort state */
|
|
1772
|
+
sort: SortState;
|
|
1773
|
+
/** Set sort field (toggles direction if same field) */
|
|
1774
|
+
setSort: (field: string) => void;
|
|
1775
|
+
/** Current selection state */
|
|
1776
|
+
selection: SelectionState;
|
|
1777
|
+
/** Toggle selection for an item */
|
|
1778
|
+
toggleSelect: (id: string) => void;
|
|
1779
|
+
/** Select all items */
|
|
1780
|
+
selectAll: () => void;
|
|
1781
|
+
/** Clear all selections */
|
|
1782
|
+
clearSelection: () => void;
|
|
1783
|
+
/** Current pagination state */
|
|
1784
|
+
pagination: PaginationState;
|
|
1785
|
+
/** Go to a specific page */
|
|
1786
|
+
setPage: (page: number) => void;
|
|
1787
|
+
/** Change page size */
|
|
1788
|
+
setPageSize: (size: number) => void;
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Options for useReactiveTable hook
|
|
1792
|
+
*
|
|
1793
|
+
* @template T - The document type
|
|
1794
|
+
*
|
|
1795
|
+
* @remarks
|
|
1796
|
+
* Table-specific options extending ReactiveDataOptions with column definitions.
|
|
1797
|
+
*/
|
|
1798
|
+
interface ReactiveTableOptions<T> extends ReactiveDataOptions<T> {
|
|
1799
|
+
/** Column definitions for the table */
|
|
1800
|
+
columns?: Array<{
|
|
1801
|
+
key: keyof T;
|
|
1802
|
+
header: string;
|
|
1803
|
+
sortable?: boolean;
|
|
1804
|
+
width?: number;
|
|
1805
|
+
align?: 'left' | 'center' | 'right';
|
|
1806
|
+
}>;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Options for useReactiveList hook
|
|
1810
|
+
*
|
|
1811
|
+
* @template T - The document type
|
|
1812
|
+
*
|
|
1813
|
+
* @remarks
|
|
1814
|
+
* List-specific options extending ReactiveDataOptions.
|
|
1815
|
+
*/
|
|
1816
|
+
interface ReactiveListOptions<T> extends ReactiveDataOptions<T> {
|
|
1817
|
+
/** Field to use as display text */
|
|
1818
|
+
labelField?: keyof T;
|
|
1819
|
+
/** Field to use as icon */
|
|
1820
|
+
iconField?: keyof T;
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Options for useReactiveMetrics hook
|
|
1824
|
+
*
|
|
1825
|
+
* @template T - The document type
|
|
1826
|
+
*
|
|
1827
|
+
* @remarks
|
|
1828
|
+
* Metrics-specific options for aggregated data display.
|
|
1829
|
+
*/
|
|
1830
|
+
interface ReactiveMetricsOptions<T> extends Omit<ReactiveDataOptions<T>, 'limit' | 'offset'> {
|
|
1831
|
+
/** Metrics to compute from collection data */
|
|
1832
|
+
metrics: Array<{
|
|
1833
|
+
/** Unique key for the metric */
|
|
1834
|
+
key: string;
|
|
1835
|
+
/** Display label */
|
|
1836
|
+
label: string;
|
|
1837
|
+
/** Field to aggregate */
|
|
1838
|
+
field: keyof T;
|
|
1839
|
+
/** Aggregation function */
|
|
1840
|
+
aggregate: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'latest';
|
|
1841
|
+
/** Format for display */
|
|
1842
|
+
format?: 'number' | 'currency' | 'percentage';
|
|
1843
|
+
/** Unit suffix */
|
|
1844
|
+
unit?: string;
|
|
1845
|
+
}>;
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Single metric result
|
|
1849
|
+
*/
|
|
1850
|
+
interface MetricValue {
|
|
1851
|
+
/** Metric key */
|
|
1852
|
+
key: string;
|
|
1853
|
+
/** Display label */
|
|
1854
|
+
label: string;
|
|
1855
|
+
/** Computed value */
|
|
1856
|
+
value: number | string;
|
|
1857
|
+
/** Trend direction (computed from previous values if available) */
|
|
1858
|
+
trend?: 'up' | 'down' | 'neutral';
|
|
1859
|
+
/** Trend percentage change */
|
|
1860
|
+
trendValue?: number;
|
|
1861
|
+
/** Optional sparkline data */
|
|
1862
|
+
sparkline?: number[];
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Result of useReactiveMetrics hook
|
|
1866
|
+
*/
|
|
1867
|
+
interface ReactiveMetricsResult {
|
|
1868
|
+
/** Computed metric values */
|
|
1869
|
+
metrics: MetricValue[];
|
|
1870
|
+
/** True while initial query is executing */
|
|
1871
|
+
isLoading: boolean;
|
|
1872
|
+
/** Error if query failed */
|
|
1873
|
+
error: Error | undefined;
|
|
1874
|
+
/** Manual refetch function */
|
|
1875
|
+
refetch: () => void;
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Options for useReactiveCard hook
|
|
1879
|
+
*
|
|
1880
|
+
* @template T - The document type
|
|
1881
|
+
*
|
|
1882
|
+
* @remarks
|
|
1883
|
+
* Card-specific options for single-record display.
|
|
1884
|
+
*/
|
|
1885
|
+
interface ReactiveCardOptions<T> {
|
|
1886
|
+
/** Collection name */
|
|
1887
|
+
collection: string;
|
|
1888
|
+
/** Filter to find single record */
|
|
1889
|
+
where: WhereClause<T>;
|
|
1890
|
+
/** Fields to display as key-value pairs */
|
|
1891
|
+
fields?: Array<{
|
|
1892
|
+
key: keyof T;
|
|
1893
|
+
label: string;
|
|
1894
|
+
format?: 'string' | 'date' | 'currency' | 'badge';
|
|
1895
|
+
}>;
|
|
1896
|
+
/** Enable optimistic updates */
|
|
1897
|
+
optimistic?: boolean;
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Result of useReactiveCard hook
|
|
1901
|
+
*/
|
|
1902
|
+
interface ReactiveCardResult<T> {
|
|
1903
|
+
/** Single record data */
|
|
1904
|
+
data: T | null;
|
|
1905
|
+
/** True while loading */
|
|
1906
|
+
isLoading: boolean;
|
|
1907
|
+
/** Error if query failed */
|
|
1908
|
+
error: Error | undefined;
|
|
1909
|
+
/** Refetch function */
|
|
1910
|
+
refetch: () => void;
|
|
1911
|
+
/** Update the record */
|
|
1912
|
+
update: (updates: Partial<T>) => Promise<void>;
|
|
1913
|
+
/** True while updating */
|
|
1914
|
+
isUpdating: boolean;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Hook for reactive data binding with TanStack DB collections
|
|
1918
|
+
*
|
|
1919
|
+
* Provides reactive data updates, optimistic mutations, sorting, selection,
|
|
1920
|
+
* and pagination for data components like Table and List.
|
|
1921
|
+
*
|
|
1922
|
+
* @template T - The document type (must extend Record<string, any>)
|
|
1923
|
+
*
|
|
1924
|
+
* @param options - Configuration for collection, filters, and behavior
|
|
1925
|
+
* @returns ReactiveDataResult with data, mutations, and state controls
|
|
1926
|
+
*
|
|
1927
|
+
* @remarks
|
|
1928
|
+
* - Data updates automatically when collection changes (via subscriptions)
|
|
1929
|
+
* - Optimistic updates show changes immediately, roll back on error
|
|
1930
|
+
* - Sort state toggles direction when same field is selected
|
|
1931
|
+
* - Selection supports single and multi-select modes
|
|
1932
|
+
* - Pagination is handled client-side with limit/offset
|
|
1933
|
+
*
|
|
1934
|
+
* @example
|
|
1935
|
+
* ```tsx
|
|
1936
|
+
* function UserList() {
|
|
1937
|
+
* const {
|
|
1938
|
+
* data,
|
|
1939
|
+
* isLoading,
|
|
1940
|
+
* mutate,
|
|
1941
|
+
* sort,
|
|
1942
|
+
* setSort,
|
|
1943
|
+
* selection,
|
|
1944
|
+
* toggleSelect,
|
|
1945
|
+
* } = useReactiveData<User>({
|
|
1946
|
+
* collection: 'users',
|
|
1947
|
+
* where: { status: 'active' },
|
|
1948
|
+
* orderBy: { name: 'asc' },
|
|
1949
|
+
* selectable: 'multi',
|
|
1950
|
+
* })
|
|
1951
|
+
*
|
|
1952
|
+
* if (isLoading) return <Spinner />
|
|
1953
|
+
*
|
|
1954
|
+
* return (
|
|
1955
|
+
* <List
|
|
1956
|
+
* items={data.map(user => ({
|
|
1957
|
+
* id: user.id,
|
|
1958
|
+
* text: user.name,
|
|
1959
|
+
* selected: selection.selected.has(user.id),
|
|
1960
|
+
* }))}
|
|
1961
|
+
* onItemClick={(id) => toggleSelect(id)}
|
|
1962
|
+
* onDelete={(id) => mutate.delete({ id })}
|
|
1963
|
+
* />
|
|
1964
|
+
* )
|
|
1965
|
+
* }
|
|
1966
|
+
* ```
|
|
1967
|
+
*
|
|
1968
|
+
* @example
|
|
1969
|
+
* ```tsx
|
|
1970
|
+
* // With optimistic updates
|
|
1971
|
+
* function QuickAddUser() {
|
|
1972
|
+
* const { mutate, isMutating } = useReactiveData<User>({
|
|
1973
|
+
* collection: 'users',
|
|
1974
|
+
* optimistic: true,
|
|
1975
|
+
* })
|
|
1976
|
+
*
|
|
1977
|
+
* const handleAdd = async () => {
|
|
1978
|
+
* // UI updates immediately, rolls back if server fails
|
|
1979
|
+
* await mutate.insert({
|
|
1980
|
+
* id: crypto.randomUUID(),
|
|
1981
|
+
* name: 'New User',
|
|
1982
|
+
* status: 'active',
|
|
1983
|
+
* })
|
|
1984
|
+
* }
|
|
1985
|
+
*
|
|
1986
|
+
* return (
|
|
1987
|
+
* <button onClick={handleAdd} disabled={isMutating}>
|
|
1988
|
+
* {isMutating ? 'Adding...' : 'Add User'}
|
|
1989
|
+
* </button>
|
|
1990
|
+
* )
|
|
1991
|
+
* }
|
|
1992
|
+
* ```
|
|
1993
|
+
*/
|
|
1994
|
+
declare function useReactiveData<T extends Record<string, any>>(options: ReactiveDataOptions<T>): ReactiveDataResult<T>;
|
|
1995
|
+
/**
|
|
1996
|
+
* Hook for reactive table data with TanStack DB integration
|
|
1997
|
+
*
|
|
1998
|
+
* Specialized version of useReactiveData for Table components with
|
|
1999
|
+
* column-aware sorting and selection.
|
|
2000
|
+
*
|
|
2001
|
+
* @template T - The document type
|
|
2002
|
+
*
|
|
2003
|
+
* @param options - Table-specific options including column definitions
|
|
2004
|
+
* @returns ReactiveDataResult with table-optimized behavior
|
|
2005
|
+
*
|
|
2006
|
+
* @example
|
|
2007
|
+
* ```tsx
|
|
2008
|
+
* function UsersTable() {
|
|
2009
|
+
* const {
|
|
2010
|
+
* data,
|
|
2011
|
+
* sort,
|
|
2012
|
+
* setSort,
|
|
2013
|
+
* selection,
|
|
2014
|
+
* toggleSelect,
|
|
2015
|
+
* } = useReactiveTable<User>({
|
|
2016
|
+
* collection: 'users',
|
|
2017
|
+
* columns: [
|
|
2018
|
+
* { key: 'name', header: 'Name', sortable: true },
|
|
2019
|
+
* { key: 'email', header: 'Email' },
|
|
2020
|
+
* { key: 'role', header: 'Role', sortable: true },
|
|
2021
|
+
* ],
|
|
2022
|
+
* selectable: 'multi',
|
|
2023
|
+
* })
|
|
2024
|
+
*
|
|
2025
|
+
* return (
|
|
2026
|
+
* <Table
|
|
2027
|
+
* columns={columns}
|
|
2028
|
+
* data={data}
|
|
2029
|
+
* sortBy={sort.field}
|
|
2030
|
+
* sortDirection={sort.direction}
|
|
2031
|
+
* onSort={setSort}
|
|
2032
|
+
* selectedRows={Array.from(selection.selected)}
|
|
2033
|
+
* onRowSelect={toggleSelect}
|
|
2034
|
+
* />
|
|
2035
|
+
* )
|
|
2036
|
+
* }
|
|
2037
|
+
* ```
|
|
2038
|
+
*/
|
|
2039
|
+
declare function useReactiveTable<T extends Record<string, any>>(options: ReactiveTableOptions<T>): ReactiveDataResult<T>;
|
|
2040
|
+
/**
|
|
2041
|
+
* Hook for reactive list data with TanStack DB integration
|
|
2042
|
+
*
|
|
2043
|
+
* Specialized version of useReactiveData for List components with
|
|
2044
|
+
* item-level selection and actions.
|
|
2045
|
+
*
|
|
2046
|
+
* @template T - The document type
|
|
2047
|
+
*
|
|
2048
|
+
* @param options - List-specific options
|
|
2049
|
+
* @returns ReactiveDataResult with list-optimized behavior
|
|
2050
|
+
*
|
|
2051
|
+
* @example
|
|
2052
|
+
* ```tsx
|
|
2053
|
+
* function TaskList() {
|
|
2054
|
+
* const {
|
|
2055
|
+
* data,
|
|
2056
|
+
* mutate,
|
|
2057
|
+
* toggleSelect,
|
|
2058
|
+
* } = useReactiveList<Task>({
|
|
2059
|
+
* collection: 'tasks',
|
|
2060
|
+
* where: { completed: false },
|
|
2061
|
+
* orderBy: { priority: 'desc' },
|
|
2062
|
+
* labelField: 'title',
|
|
2063
|
+
* selectable: 'single',
|
|
2064
|
+
* })
|
|
2065
|
+
*
|
|
2066
|
+
* return (
|
|
2067
|
+
* <List
|
|
2068
|
+
* items={data.map(task => ({
|
|
2069
|
+
* id: task.id,
|
|
2070
|
+
* content: task.title,
|
|
2071
|
+
* icon: task.priority === 'high' ? 'alert' : 'task',
|
|
2072
|
+
* }))}
|
|
2073
|
+
* onItemClick={(id) => toggleSelect(id)}
|
|
2074
|
+
* onDelete={(id) => mutate.delete({ id })}
|
|
2075
|
+
* />
|
|
2076
|
+
* )
|
|
2077
|
+
* }
|
|
2078
|
+
* ```
|
|
2079
|
+
*/
|
|
2080
|
+
declare function useReactiveList<T extends Record<string, any>>(options: ReactiveListOptions<T>): ReactiveDataResult<T>;
|
|
2081
|
+
/**
|
|
2082
|
+
* Hook for reactive metrics computed from TanStack DB collections
|
|
2083
|
+
*
|
|
2084
|
+
* Aggregates collection data into metric values with optional trend
|
|
2085
|
+
* computation and sparkline data.
|
|
2086
|
+
*
|
|
2087
|
+
* @template T - The document type
|
|
2088
|
+
*
|
|
2089
|
+
* @param options - Metrics configuration with aggregation definitions
|
|
2090
|
+
* @returns ReactiveMetricsResult with computed metric values
|
|
2091
|
+
*
|
|
2092
|
+
* @example
|
|
2093
|
+
* ```tsx
|
|
2094
|
+
* function DashboardMetrics() {
|
|
2095
|
+
* const { metrics, isLoading } = useReactiveMetrics<Order>({
|
|
2096
|
+
* collection: 'orders',
|
|
2097
|
+
* where: { status: 'completed' },
|
|
2098
|
+
* metrics: [
|
|
2099
|
+
* {
|
|
2100
|
+
* key: 'total-orders',
|
|
2101
|
+
* label: 'Total Orders',
|
|
2102
|
+
* field: 'id',
|
|
2103
|
+
* aggregate: 'count',
|
|
2104
|
+
* },
|
|
2105
|
+
* {
|
|
2106
|
+
* key: 'revenue',
|
|
2107
|
+
* label: 'Revenue',
|
|
2108
|
+
* field: 'total',
|
|
2109
|
+
* aggregate: 'sum',
|
|
2110
|
+
* format: 'currency',
|
|
2111
|
+
* },
|
|
2112
|
+
* {
|
|
2113
|
+
* key: 'avg-order',
|
|
2114
|
+
* label: 'Avg Order',
|
|
2115
|
+
* field: 'total',
|
|
2116
|
+
* aggregate: 'avg',
|
|
2117
|
+
* format: 'currency',
|
|
2118
|
+
* },
|
|
2119
|
+
* ],
|
|
2120
|
+
* })
|
|
2121
|
+
*
|
|
2122
|
+
* if (isLoading) return <Spinner />
|
|
2123
|
+
*
|
|
2124
|
+
* return (
|
|
2125
|
+
* <Metrics
|
|
2126
|
+
* metrics={metrics.map(m => ({
|
|
2127
|
+
* label: m.label,
|
|
2128
|
+
* value: m.value,
|
|
2129
|
+
* trend: m.trend,
|
|
2130
|
+
* trendValue: m.trendValue,
|
|
2131
|
+
* }))}
|
|
2132
|
+
* />
|
|
2133
|
+
* )
|
|
2134
|
+
* }
|
|
2135
|
+
* ```
|
|
2136
|
+
*/
|
|
2137
|
+
declare function useReactiveMetrics<T extends Record<string, any>>(options: ReactiveMetricsOptions<T>): ReactiveMetricsResult;
|
|
2138
|
+
/**
|
|
2139
|
+
* Hook for reactive single-record card data with TanStack DB integration
|
|
2140
|
+
*
|
|
2141
|
+
* Fetches and subscribes to a single record from a collection for
|
|
2142
|
+
* Card component display.
|
|
2143
|
+
*
|
|
2144
|
+
* @template T - The document type
|
|
2145
|
+
*
|
|
2146
|
+
* @param options - Card-specific options
|
|
2147
|
+
* @returns ReactiveCardResult with single record and update function
|
|
2148
|
+
*
|
|
2149
|
+
* @example
|
|
2150
|
+
* ```tsx
|
|
2151
|
+
* function UserProfileCard({ userId }: { userId: string }) {
|
|
2152
|
+
* const {
|
|
2153
|
+
* data,
|
|
2154
|
+
* isLoading,
|
|
2155
|
+
* update,
|
|
2156
|
+
* isUpdating,
|
|
2157
|
+
* } = useReactiveCard<User>({
|
|
2158
|
+
* collection: 'users',
|
|
2159
|
+
* where: { id: userId },
|
|
2160
|
+
* fields: [
|
|
2161
|
+
* { key: 'name', label: 'Name' },
|
|
2162
|
+
* { key: 'email', label: 'Email' },
|
|
2163
|
+
* { key: 'role', label: 'Role', format: 'badge' },
|
|
2164
|
+
* ],
|
|
2165
|
+
* })
|
|
2166
|
+
*
|
|
2167
|
+
* if (isLoading) return <Spinner />
|
|
2168
|
+
* if (!data) return <Empty message="User not found" />
|
|
2169
|
+
*
|
|
2170
|
+
* return (
|
|
2171
|
+
* <Card
|
|
2172
|
+
* title={data.name}
|
|
2173
|
+
* subtitle={data.email}
|
|
2174
|
+
* pairs={[
|
|
2175
|
+
* { key: 'Role', value: data.role },
|
|
2176
|
+
* { key: 'Status', value: data.status },
|
|
2177
|
+
* ]}
|
|
2178
|
+
* actions={[
|
|
2179
|
+
* {
|
|
2180
|
+
* label: isUpdating ? 'Saving...' : 'Edit',
|
|
2181
|
+
* action: () => update({ status: 'updated' }),
|
|
2182
|
+
* },
|
|
2183
|
+
* ]}
|
|
2184
|
+
* />
|
|
2185
|
+
* )
|
|
2186
|
+
* }
|
|
2187
|
+
* ```
|
|
2188
|
+
*/
|
|
2189
|
+
declare function useReactiveCard<T extends Record<string, any>>(options: ReactiveCardOptions<T>): ReactiveCardResult<T>;
|
|
2190
|
+
|
|
2191
|
+
/**
|
|
2192
|
+
* @mdxui/terminal SaaS Collections
|
|
2193
|
+
*
|
|
2194
|
+
* Pre-built Zod schemas and collections for common SaaS primitives:
|
|
2195
|
+
* - Users: Authentication and user management
|
|
2196
|
+
* - APIKeys: API key management with permissions
|
|
2197
|
+
* - Webhooks: Webhook configuration and event subscriptions
|
|
2198
|
+
* - Teams: Team/organization management with members
|
|
2199
|
+
* - Usage: Usage metrics tracking
|
|
2200
|
+
*
|
|
2201
|
+
* @example
|
|
2202
|
+
* ```typescript
|
|
2203
|
+
* import {
|
|
2204
|
+
* UsersCollection,
|
|
2205
|
+
* APIKeysCollection,
|
|
2206
|
+
* WebhooksCollection,
|
|
2207
|
+
* TeamsCollection,
|
|
2208
|
+
* UsageCollection,
|
|
2209
|
+
* type User,
|
|
2210
|
+
* type APIKey,
|
|
2211
|
+
* } from '@mdxui/terminal'
|
|
2212
|
+
*
|
|
2213
|
+
* // Create collections
|
|
2214
|
+
* const users = UsersCollection()
|
|
2215
|
+
* const apiKeys = APIKeysCollection()
|
|
2216
|
+
*
|
|
2217
|
+
* // Insert data
|
|
2218
|
+
* await users.insert({
|
|
2219
|
+
* id: 'user-1',
|
|
2220
|
+
* name: 'Alice',
|
|
2221
|
+
* email: 'alice@example.com',
|
|
2222
|
+
* role: 'admin',
|
|
2223
|
+
* createdAt: new Date(),
|
|
2224
|
+
* })
|
|
2225
|
+
*
|
|
2226
|
+
* // Query
|
|
2227
|
+
* const admins = await users.findMany({
|
|
2228
|
+
* where: { role: 'admin' }
|
|
2229
|
+
* })
|
|
2230
|
+
* ```
|
|
2231
|
+
*/
|
|
2232
|
+
|
|
2233
|
+
/**
|
|
2234
|
+
* User role enum - defines access levels
|
|
2235
|
+
*/
|
|
2236
|
+
declare const UserRoleSchema: z.ZodEnum<["admin", "user", "viewer"]>;
|
|
2237
|
+
/**
|
|
2238
|
+
* User schema - validates user account information
|
|
2239
|
+
*
|
|
2240
|
+
* @example
|
|
2241
|
+
* ```typescript
|
|
2242
|
+
* const user = UserSchema.parse({
|
|
2243
|
+
* id: 'user-1',
|
|
2244
|
+
* name: 'Alice',
|
|
2245
|
+
* email: 'alice@example.com',
|
|
2246
|
+
* role: 'admin',
|
|
2247
|
+
* createdAt: new Date(),
|
|
2248
|
+
* })
|
|
2249
|
+
* ```
|
|
2250
|
+
*/
|
|
2251
|
+
declare const UserSchema: z.ZodObject<{
|
|
2252
|
+
/** Unique user identifier */
|
|
2253
|
+
id: z.ZodString;
|
|
2254
|
+
/** User's display name */
|
|
2255
|
+
name: z.ZodString;
|
|
2256
|
+
/** User's email address (validated format) */
|
|
2257
|
+
email: z.ZodString;
|
|
2258
|
+
/** User's role: admin, user, or viewer */
|
|
2259
|
+
role: z.ZodEnum<["admin", "user", "viewer"]>;
|
|
2260
|
+
/** When the user account was created */
|
|
2261
|
+
createdAt: z.ZodDate;
|
|
2262
|
+
}, "strip", z.ZodTypeAny, {
|
|
2263
|
+
email: string;
|
|
2264
|
+
name: string;
|
|
2265
|
+
id: string;
|
|
2266
|
+
role: "user" | "admin" | "viewer";
|
|
2267
|
+
createdAt: Date;
|
|
2268
|
+
}, {
|
|
2269
|
+
email: string;
|
|
2270
|
+
name: string;
|
|
2271
|
+
id: string;
|
|
2272
|
+
role: "user" | "admin" | "viewer";
|
|
2273
|
+
createdAt: Date;
|
|
2274
|
+
}>;
|
|
2275
|
+
/**
|
|
2276
|
+
* APIKey schema - validates API key configuration
|
|
2277
|
+
*
|
|
2278
|
+
* @example
|
|
2279
|
+
* ```typescript
|
|
2280
|
+
* const key = APIKeySchema.parse({
|
|
2281
|
+
* id: 'key-1',
|
|
2282
|
+
* key: 'sk_test_abc123',
|
|
2283
|
+
* name: 'Development',
|
|
2284
|
+
* permissions: ['read:api', 'write:webhooks'],
|
|
2285
|
+
* expiresAt: new Date('2025-12-31'),
|
|
2286
|
+
* })
|
|
2287
|
+
* ```
|
|
2288
|
+
*/
|
|
2289
|
+
declare const APIKeySchema: z.ZodObject<{
|
|
2290
|
+
/** Unique key identifier */
|
|
2291
|
+
id: z.ZodString;
|
|
2292
|
+
/** The actual API key string (secret) */
|
|
2293
|
+
key: z.ZodString;
|
|
2294
|
+
/** Human-readable name for the key */
|
|
2295
|
+
name: z.ZodString;
|
|
2296
|
+
/** Array of permission strings this key grants */
|
|
2297
|
+
permissions: z.ZodArray<z.ZodString, "many">;
|
|
2298
|
+
/** When the key expires */
|
|
2299
|
+
expiresAt: z.ZodDate;
|
|
2300
|
+
}, "strip", z.ZodTypeAny, {
|
|
2301
|
+
name: string;
|
|
2302
|
+
key: string;
|
|
2303
|
+
id: string;
|
|
2304
|
+
permissions: string[];
|
|
2305
|
+
expiresAt: Date;
|
|
2306
|
+
}, {
|
|
2307
|
+
name: string;
|
|
2308
|
+
key: string;
|
|
2309
|
+
id: string;
|
|
2310
|
+
permissions: string[];
|
|
2311
|
+
expiresAt: Date;
|
|
2312
|
+
}>;
|
|
2313
|
+
/**
|
|
2314
|
+
* Webhook schema - validates webhook configuration
|
|
2315
|
+
*
|
|
2316
|
+
* @example
|
|
2317
|
+
* ```typescript
|
|
2318
|
+
* const webhook = WebhookSchema.parse({
|
|
2319
|
+
* id: 'webhook-1',
|
|
2320
|
+
* url: 'https://example.com/webhook',
|
|
2321
|
+
* events: ['user.created', 'user.updated'],
|
|
2322
|
+
* secret: 'whsec_test_abc123',
|
|
2323
|
+
* active: true,
|
|
2324
|
+
* })
|
|
2325
|
+
* ```
|
|
2326
|
+
*/
|
|
2327
|
+
declare const WebhookSchema: z.ZodObject<{
|
|
2328
|
+
/** Unique webhook identifier */
|
|
2329
|
+
id: z.ZodString;
|
|
2330
|
+
/** Webhook endpoint URL (validated format) */
|
|
2331
|
+
url: z.ZodString;
|
|
2332
|
+
/** Events this webhook listens to (non-empty array) */
|
|
2333
|
+
events: z.ZodArray<z.ZodString, "many">;
|
|
2334
|
+
/** Webhook signing secret */
|
|
2335
|
+
secret: z.ZodString;
|
|
2336
|
+
/** Whether the webhook is currently active */
|
|
2337
|
+
active: z.ZodBoolean;
|
|
2338
|
+
}, "strip", z.ZodTypeAny, {
|
|
2339
|
+
url: string;
|
|
2340
|
+
active: boolean;
|
|
2341
|
+
id: string;
|
|
2342
|
+
events: string[];
|
|
2343
|
+
secret: string;
|
|
2344
|
+
}, {
|
|
2345
|
+
url: string;
|
|
2346
|
+
active: boolean;
|
|
2347
|
+
id: string;
|
|
2348
|
+
events: string[];
|
|
2349
|
+
secret: string;
|
|
2350
|
+
}>;
|
|
2351
|
+
/**
|
|
2352
|
+
* Team schema - validates team configuration
|
|
2353
|
+
*
|
|
2354
|
+
* @example
|
|
2355
|
+
* ```typescript
|
|
2356
|
+
* const team = TeamSchema.parse({
|
|
2357
|
+
* id: 'team-1',
|
|
2358
|
+
* name: 'Engineering',
|
|
2359
|
+
* members: ['user-1', 'user-2', 'user-3'],
|
|
2360
|
+
* })
|
|
2361
|
+
* ```
|
|
2362
|
+
*/
|
|
2363
|
+
declare const TeamSchema: z.ZodObject<{
|
|
2364
|
+
/** Unique team identifier */
|
|
2365
|
+
id: z.ZodString;
|
|
2366
|
+
/** Team name */
|
|
2367
|
+
name: z.ZodString;
|
|
2368
|
+
/** Array of user IDs who are members */
|
|
2369
|
+
members: z.ZodArray<z.ZodString, "many">;
|
|
2370
|
+
}, "strip", z.ZodTypeAny, {
|
|
2371
|
+
name: string;
|
|
2372
|
+
id: string;
|
|
2373
|
+
members: string[];
|
|
2374
|
+
}, {
|
|
2375
|
+
name: string;
|
|
2376
|
+
id: string;
|
|
2377
|
+
members: string[];
|
|
2378
|
+
}>;
|
|
2379
|
+
/**
|
|
2380
|
+
* Usage schema - validates usage metrics
|
|
2381
|
+
*
|
|
2382
|
+
* @example
|
|
2383
|
+
* ```typescript
|
|
2384
|
+
* const usage = UsageSchema.parse({
|
|
2385
|
+
* id: 'usage-1',
|
|
2386
|
+
* metric: 'api_calls',
|
|
2387
|
+
* value: 1500,
|
|
2388
|
+
* timestamp: new Date(),
|
|
2389
|
+
* })
|
|
2390
|
+
* ```
|
|
2391
|
+
*/
|
|
2392
|
+
declare const UsageSchema: z.ZodObject<{
|
|
2393
|
+
/** Unique usage record identifier */
|
|
2394
|
+
id: z.ZodString;
|
|
2395
|
+
/** Name of the metric being tracked */
|
|
2396
|
+
metric: z.ZodString;
|
|
2397
|
+
/** Numeric value of the metric */
|
|
2398
|
+
value: z.ZodNumber;
|
|
2399
|
+
/** When this metric was recorded */
|
|
2400
|
+
timestamp: z.ZodDate;
|
|
2401
|
+
}, "strip", z.ZodTypeAny, {
|
|
2402
|
+
value: number;
|
|
2403
|
+
id: string;
|
|
2404
|
+
metric: string;
|
|
2405
|
+
timestamp: Date;
|
|
2406
|
+
}, {
|
|
2407
|
+
value: number;
|
|
2408
|
+
id: string;
|
|
2409
|
+
metric: string;
|
|
2410
|
+
timestamp: Date;
|
|
2411
|
+
}>;
|
|
2412
|
+
/** User type - inferred from UserSchema */
|
|
2413
|
+
type User = z.infer<typeof UserSchema>;
|
|
2414
|
+
/** APIKey type - inferred from APIKeySchema */
|
|
2415
|
+
type APIKey = z.infer<typeof APIKeySchema>;
|
|
2416
|
+
/** Webhook type - inferred from WebhookSchema */
|
|
2417
|
+
type Webhook = z.infer<typeof WebhookSchema>;
|
|
2418
|
+
/** Team type - inferred from TeamSchema */
|
|
2419
|
+
type Team = z.infer<typeof TeamSchema>;
|
|
2420
|
+
/** Usage type - inferred from UsageSchema */
|
|
2421
|
+
type Usage = z.infer<typeof UsageSchema>;
|
|
2422
|
+
/** User role type - admin, user, or viewer */
|
|
2423
|
+
type UserRole = z.infer<typeof UserRoleSchema>;
|
|
2424
|
+
/**
|
|
2425
|
+
* Creates a Users collection with Zod validation
|
|
2426
|
+
*
|
|
2427
|
+
* @returns A typed Collection<User> for managing user accounts
|
|
2428
|
+
*
|
|
2429
|
+
* @example
|
|
2430
|
+
* ```typescript
|
|
2431
|
+
* const users = UsersCollection()
|
|
2432
|
+
*
|
|
2433
|
+
* // Insert a user
|
|
2434
|
+
* const user = await users.insert({
|
|
2435
|
+
* id: 'user-1',
|
|
2436
|
+
* name: 'Alice',
|
|
2437
|
+
* email: 'alice@example.com',
|
|
2438
|
+
* role: 'admin',
|
|
2439
|
+
* createdAt: new Date(),
|
|
2440
|
+
* })
|
|
2441
|
+
*
|
|
2442
|
+
* // Find admins
|
|
2443
|
+
* const admins = await users.findMany({
|
|
2444
|
+
* where: { role: 'admin' }
|
|
2445
|
+
* })
|
|
2446
|
+
*
|
|
2447
|
+
* // Subscribe to changes
|
|
2448
|
+
* const unsubscribe = users.subscribe((allUsers) => {
|
|
2449
|
+
* console.log('Users changed:', allUsers.length)
|
|
2450
|
+
* })
|
|
2451
|
+
* ```
|
|
2452
|
+
*/
|
|
2453
|
+
declare function UsersCollection(): Collection<User>;
|
|
2454
|
+
/**
|
|
2455
|
+
* Creates an APIKeys collection with Zod validation
|
|
2456
|
+
*
|
|
2457
|
+
* @returns A typed Collection<APIKey> for managing API keys
|
|
2458
|
+
*
|
|
2459
|
+
* @example
|
|
2460
|
+
* ```typescript
|
|
2461
|
+
* const apiKeys = APIKeysCollection()
|
|
2462
|
+
*
|
|
2463
|
+
* // Create a new key
|
|
2464
|
+
* const key = await apiKeys.insert({
|
|
2465
|
+
* id: 'key-1',
|
|
2466
|
+
* key: 'sk_test_abc123',
|
|
2467
|
+
* name: 'Development',
|
|
2468
|
+
* permissions: ['read:api'],
|
|
2469
|
+
* expiresAt: new Date('2025-12-31'),
|
|
2470
|
+
* })
|
|
2471
|
+
*
|
|
2472
|
+
* // Find non-expired keys
|
|
2473
|
+
* const validKeys = await apiKeys.findMany({
|
|
2474
|
+
* where: { expiresAt: { $gt: new Date() } }
|
|
2475
|
+
* })
|
|
2476
|
+
* ```
|
|
2477
|
+
*/
|
|
2478
|
+
declare function APIKeysCollection(): Collection<APIKey>;
|
|
2479
|
+
/**
|
|
2480
|
+
* Creates a Webhooks collection with Zod validation
|
|
2481
|
+
*
|
|
2482
|
+
* @returns A typed Collection<Webhook> for managing webhook configurations
|
|
2483
|
+
*
|
|
2484
|
+
* @example
|
|
2485
|
+
* ```typescript
|
|
2486
|
+
* const webhooks = WebhooksCollection()
|
|
2487
|
+
*
|
|
2488
|
+
* // Create a webhook
|
|
2489
|
+
* await webhooks.insert({
|
|
2490
|
+
* id: 'webhook-1',
|
|
2491
|
+
* url: 'https://example.com/webhook',
|
|
2492
|
+
* events: ['user.created'],
|
|
2493
|
+
* secret: 'whsec_abc123',
|
|
2494
|
+
* active: true,
|
|
2495
|
+
* })
|
|
2496
|
+
*
|
|
2497
|
+
* // Find active webhooks
|
|
2498
|
+
* const active = await webhooks.findMany({
|
|
2499
|
+
* where: { active: true }
|
|
2500
|
+
* })
|
|
2501
|
+
* ```
|
|
2502
|
+
*/
|
|
2503
|
+
declare function WebhooksCollection(): Collection<Webhook>;
|
|
2504
|
+
/**
|
|
2505
|
+
* Creates a Teams collection with Zod validation
|
|
2506
|
+
*
|
|
2507
|
+
* @returns A typed Collection<Team> for managing teams and their members
|
|
2508
|
+
*
|
|
2509
|
+
* @example
|
|
2510
|
+
* ```typescript
|
|
2511
|
+
* const teams = TeamsCollection()
|
|
2512
|
+
*
|
|
2513
|
+
* // Create a team
|
|
2514
|
+
* await teams.insert({
|
|
2515
|
+
* id: 'team-1',
|
|
2516
|
+
* name: 'Engineering',
|
|
2517
|
+
* members: ['user-1', 'user-2'],
|
|
2518
|
+
* })
|
|
2519
|
+
*
|
|
2520
|
+
* // Add a member
|
|
2521
|
+
* await teams.update(
|
|
2522
|
+
* { id: 'team-1' },
|
|
2523
|
+
* { members: ['user-1', 'user-2', 'user-3'] }
|
|
2524
|
+
* )
|
|
2525
|
+
* ```
|
|
2526
|
+
*/
|
|
2527
|
+
declare function TeamsCollection(): Collection<Team>;
|
|
2528
|
+
/**
|
|
2529
|
+
* Creates a Usage collection with Zod validation
|
|
2530
|
+
*
|
|
2531
|
+
* @returns A typed Collection<Usage> for tracking usage metrics
|
|
2532
|
+
*
|
|
2533
|
+
* @example
|
|
2534
|
+
* ```typescript
|
|
2535
|
+
* const usage = UsageCollection()
|
|
2536
|
+
*
|
|
2537
|
+
* // Record usage
|
|
2538
|
+
* await usage.insert({
|
|
2539
|
+
* id: 'usage-1',
|
|
2540
|
+
* metric: 'api_calls',
|
|
2541
|
+
* value: 1500,
|
|
2542
|
+
* timestamp: new Date(),
|
|
2543
|
+
* })
|
|
2544
|
+
*
|
|
2545
|
+
* // Get usage for a specific metric
|
|
2546
|
+
* const apiCalls = await usage.findMany({
|
|
2547
|
+
* where: { metric: 'api_calls' },
|
|
2548
|
+
* orderBy: { timestamp: 'desc' }
|
|
2549
|
+
* })
|
|
2550
|
+
* ```
|
|
2551
|
+
*/
|
|
2552
|
+
declare function UsageCollection(): Collection<Usage>;
|
|
2553
|
+
|
|
2554
|
+
export { type APIKey, APIKeySchema, APIKeysCollection, type Collection, type ComparisonOperators, type ConflictResolution, type DB, type DBConfig, DBProvider, type DBProviderProps, type DOSyncAdapter, type DOSyncConfig, type FieldFilter, type FindManyOptions, type MetricValue, type MutationOperation, type MutationOptions, type MutationResult, type OrderByClause, type OrderDirection, type PaginationState, type QueryOptions, type QueryResult, type ReactiveCardOptions, type ReactiveCardResult, type ReactiveDataOptions, type ReactiveDataResult, type ReactiveListOptions, type ReactiveMetricsOptions, type ReactiveMetricsResult, type ReactiveTableOptions, type ReconnectOptions, type SelectionState, type SortState, type SyncAdapter, type Team, TeamSchema, TeamsCollection, type Usage, UsageCollection, UsageSchema, type User, type UserRole, UserRoleSchema, UserSchema, UsersCollection, type Webhook, WebhookSchema, WebhooksCollection, type WhereClause, createCollection, createDB, createDOSync, useDBContext, useMutation, useQuery, useReactiveCard, useReactiveData, useReactiveList, useReactiveMetrics, useReactiveTable };
|