@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,1291 @@
|
|
|
1
|
+
// src/data/db.ts
|
|
2
|
+
function createDB(config) {
|
|
3
|
+
const collections = {};
|
|
4
|
+
if (config?.collections) {
|
|
5
|
+
for (const collection of config.collections) {
|
|
6
|
+
collections[collection.name] = collection;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const db = {
|
|
10
|
+
collections,
|
|
11
|
+
sync: config?.sync,
|
|
12
|
+
close() {
|
|
13
|
+
},
|
|
14
|
+
async clear() {
|
|
15
|
+
for (const collection of Object.values(collections)) {
|
|
16
|
+
const docs = await collection.findMany();
|
|
17
|
+
for (const doc of docs) {
|
|
18
|
+
const primaryKey = collection.primaryKey || "id";
|
|
19
|
+
await collection.delete({ [primaryKey]: doc[primaryKey] });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return db;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/data/collection.ts
|
|
28
|
+
function matchesFilter(value, filter) {
|
|
29
|
+
if (filter === null || filter === void 0 || typeof filter !== "object") {
|
|
30
|
+
return value === filter;
|
|
31
|
+
}
|
|
32
|
+
const ops = filter;
|
|
33
|
+
if ("$eq" in ops && value !== ops.$eq) return false;
|
|
34
|
+
if ("$ne" in ops && value === ops.$ne) return false;
|
|
35
|
+
if ("$gt" in ops && !(value > ops.$gt)) return false;
|
|
36
|
+
if ("$gte" in ops && !(value >= ops.$gte)) return false;
|
|
37
|
+
if ("$lt" in ops && !(value < ops.$lt)) return false;
|
|
38
|
+
if ("$lte" in ops && !(value <= ops.$lte)) return false;
|
|
39
|
+
if ("$in" in ops && !ops.$in.includes(value)) return false;
|
|
40
|
+
if ("$nin" in ops && ops.$nin.includes(value)) return false;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
function matchesWhere(doc, where) {
|
|
44
|
+
if ("$or" in where && where.$or) {
|
|
45
|
+
const orClauses = where.$or;
|
|
46
|
+
const matchesOr = orClauses.some((clause) => matchesWhere(doc, clause));
|
|
47
|
+
if (!matchesOr) return false;
|
|
48
|
+
const { $or, ...rest } = where;
|
|
49
|
+
if (Object.keys(rest).length === 0) return true;
|
|
50
|
+
return matchesWhere(doc, rest);
|
|
51
|
+
}
|
|
52
|
+
for (const [key, filter] of Object.entries(where)) {
|
|
53
|
+
if (key === "$or") continue;
|
|
54
|
+
const value = doc[key];
|
|
55
|
+
if (!matchesFilter(value, filter)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
function sortDocuments(docs, orderBy) {
|
|
62
|
+
const orderClauses = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
63
|
+
return [...docs].sort((a, b) => {
|
|
64
|
+
for (const clause of orderClauses) {
|
|
65
|
+
const [field, direction] = Object.entries(clause)[0];
|
|
66
|
+
const aVal = a[field];
|
|
67
|
+
const bVal = b[field];
|
|
68
|
+
if (aVal === null || aVal === void 0) {
|
|
69
|
+
if (bVal !== null && bVal !== void 0) return 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (bVal === null || bVal === void 0) return -1;
|
|
73
|
+
let comparison = 0;
|
|
74
|
+
if (typeof aVal === "string") {
|
|
75
|
+
comparison = aVal.localeCompare(bVal);
|
|
76
|
+
} else if (typeof aVal === "number") {
|
|
77
|
+
comparison = aVal - bVal;
|
|
78
|
+
} else if (aVal instanceof Date) {
|
|
79
|
+
comparison = aVal.getTime() - bVal.getTime();
|
|
80
|
+
} else {
|
|
81
|
+
comparison = String(aVal).localeCompare(String(bVal));
|
|
82
|
+
}
|
|
83
|
+
if (comparison !== 0) {
|
|
84
|
+
return direction === "desc" ? -comparison : comparison;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return 0;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function createCollection(config) {
|
|
91
|
+
const { name, schema, primaryKey = "id", indexes = [] } = config;
|
|
92
|
+
const data = /* @__PURE__ */ new Map();
|
|
93
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
94
|
+
const collection = {
|
|
95
|
+
name,
|
|
96
|
+
schema,
|
|
97
|
+
primaryKey,
|
|
98
|
+
indexes,
|
|
99
|
+
async insert(doc) {
|
|
100
|
+
const validated = schema.parse(doc);
|
|
101
|
+
const id = validated[primaryKey];
|
|
102
|
+
if (data.has(String(id))) {
|
|
103
|
+
throw new Error(`Duplicate ${primaryKey}: ${id}`);
|
|
104
|
+
}
|
|
105
|
+
data.set(String(id), validated);
|
|
106
|
+
collection._notify();
|
|
107
|
+
return validated;
|
|
108
|
+
},
|
|
109
|
+
async update(filter, updates) {
|
|
110
|
+
const updated = [];
|
|
111
|
+
for (const [key, doc] of data.entries()) {
|
|
112
|
+
if (matchesWhere(doc, filter)) {
|
|
113
|
+
const newDoc = { ...doc, ...updates };
|
|
114
|
+
const validated = schema.parse(newDoc);
|
|
115
|
+
data.set(key, validated);
|
|
116
|
+
updated.push(validated);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (updated.length > 0) {
|
|
120
|
+
collection._notify();
|
|
121
|
+
}
|
|
122
|
+
return updated;
|
|
123
|
+
},
|
|
124
|
+
async delete(filter) {
|
|
125
|
+
let deleted = false;
|
|
126
|
+
for (const [key, doc] of data.entries()) {
|
|
127
|
+
if (matchesWhere(doc, filter)) {
|
|
128
|
+
data.delete(key);
|
|
129
|
+
deleted = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (deleted) {
|
|
133
|
+
collection._notify();
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
async findOne(filter) {
|
|
137
|
+
for (const doc of data.values()) {
|
|
138
|
+
if (matchesWhere(doc, filter)) {
|
|
139
|
+
return doc;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
},
|
|
144
|
+
async findMany(options) {
|
|
145
|
+
let results = Array.from(data.values());
|
|
146
|
+
if (options?.where) {
|
|
147
|
+
results = results.filter((doc) => matchesWhere(doc, options.where));
|
|
148
|
+
}
|
|
149
|
+
if (options?.orderBy) {
|
|
150
|
+
results = sortDocuments(results, options.orderBy);
|
|
151
|
+
}
|
|
152
|
+
if (options?.offset !== void 0) {
|
|
153
|
+
results = results.slice(options.offset);
|
|
154
|
+
}
|
|
155
|
+
if (options?.limit !== void 0) {
|
|
156
|
+
results = results.slice(0, options.limit);
|
|
157
|
+
}
|
|
158
|
+
return results;
|
|
159
|
+
},
|
|
160
|
+
subscribe(callback) {
|
|
161
|
+
subscribers.add(callback);
|
|
162
|
+
return () => {
|
|
163
|
+
subscribers.delete(callback);
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
_notify() {
|
|
167
|
+
const allData = Array.from(data.values());
|
|
168
|
+
for (const callback of subscribers) {
|
|
169
|
+
callback(allData);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
return collection;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/data/sync.ts
|
|
177
|
+
function createDOSync(config) {
|
|
178
|
+
if (!config.namespaceUrl) {
|
|
179
|
+
throw new Error("namespaceUrl is required");
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
new URL(config.namespaceUrl);
|
|
183
|
+
} catch {
|
|
184
|
+
throw new Error("Invalid namespaceUrl format");
|
|
185
|
+
}
|
|
186
|
+
let ws = null;
|
|
187
|
+
let authToken = config.authToken;
|
|
188
|
+
let messageId = 0;
|
|
189
|
+
let reconnectAttempts = 0;
|
|
190
|
+
let reconnectTimeout = null;
|
|
191
|
+
let isClosed = false;
|
|
192
|
+
let connectionState = "disconnected";
|
|
193
|
+
const pendingPush = /* @__PURE__ */ new Map();
|
|
194
|
+
const pendingPull = /* @__PURE__ */ new Map();
|
|
195
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
196
|
+
const connectionStateObservers = /* @__PURE__ */ new Set();
|
|
197
|
+
const mutationQueue = /* @__PURE__ */ new Map();
|
|
198
|
+
const reconnectOptions = {
|
|
199
|
+
enabled: config.reconnect?.enabled ?? false,
|
|
200
|
+
maxAttempts: config.reconnect?.maxAttempts ?? Infinity,
|
|
201
|
+
initialDelay: config.reconnect?.initialDelay ?? 1e3,
|
|
202
|
+
maxDelay: config.reconnect?.maxDelay ?? 3e4
|
|
203
|
+
};
|
|
204
|
+
const requestTimeout = config.requestTimeout ?? 3e4;
|
|
205
|
+
function notifyConnectionStateChange(newState) {
|
|
206
|
+
if (newState !== connectionState) {
|
|
207
|
+
connectionState = newState;
|
|
208
|
+
for (const observer of connectionStateObservers) {
|
|
209
|
+
observer(connectionState);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function queueMutation(changes) {
|
|
214
|
+
const id = `queued-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
215
|
+
const mutation = {
|
|
216
|
+
id,
|
|
217
|
+
changes,
|
|
218
|
+
queuedAt: Date.now(),
|
|
219
|
+
retryCount: 0
|
|
220
|
+
};
|
|
221
|
+
mutationQueue.set(id, mutation);
|
|
222
|
+
return mutation;
|
|
223
|
+
}
|
|
224
|
+
async function flushMutationQueue() {
|
|
225
|
+
const mutations = Array.from(mutationQueue.values());
|
|
226
|
+
for (const mutation of mutations) {
|
|
227
|
+
try {
|
|
228
|
+
await push(mutation.changes);
|
|
229
|
+
mutationQueue.delete(mutation.id);
|
|
230
|
+
} catch {
|
|
231
|
+
mutation.retryCount++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function getWebSocketUrl() {
|
|
236
|
+
const url = new URL(config.namespaceUrl);
|
|
237
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
238
|
+
if (authToken) {
|
|
239
|
+
url.searchParams.set("token", authToken);
|
|
240
|
+
}
|
|
241
|
+
return url.toString();
|
|
242
|
+
}
|
|
243
|
+
function nextMessageId() {
|
|
244
|
+
messageId++;
|
|
245
|
+
return `push-${messageId}`;
|
|
246
|
+
}
|
|
247
|
+
function handleMessage(event) {
|
|
248
|
+
let message;
|
|
249
|
+
try {
|
|
250
|
+
message = JSON.parse(event.data);
|
|
251
|
+
} catch {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
switch (message.type) {
|
|
255
|
+
case "ack": {
|
|
256
|
+
const pending = pendingPush.get(message.id);
|
|
257
|
+
if (pending) {
|
|
258
|
+
if (pending.timeoutId) {
|
|
259
|
+
clearTimeout(pending.timeoutId);
|
|
260
|
+
}
|
|
261
|
+
pendingPush.delete(message.id);
|
|
262
|
+
if (message.status === "error") {
|
|
263
|
+
pending.reject(new Error(message.error || "Push failed"));
|
|
264
|
+
} else if (message.status === "conflict") {
|
|
265
|
+
if (config.conflictResolution === "throw") {
|
|
266
|
+
pending.reject(new Error("Conflict detected"));
|
|
267
|
+
} else if (config.conflictResolution === "custom" && config.onConflict && message.conflicts) {
|
|
268
|
+
config.onConflict(message.conflicts).then(() => pending.resolve());
|
|
269
|
+
} else {
|
|
270
|
+
pending.resolve();
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
pending.resolve();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case "pull-response": {
|
|
279
|
+
const entries = Array.from(pendingPull.entries());
|
|
280
|
+
if (entries.length > 0) {
|
|
281
|
+
const [id, pending] = entries[0];
|
|
282
|
+
if (pending.timeoutId) {
|
|
283
|
+
clearTimeout(pending.timeoutId);
|
|
284
|
+
}
|
|
285
|
+
pendingPull.delete(id);
|
|
286
|
+
pending.resolve(message.changes);
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case "sync": {
|
|
291
|
+
for (const callback of subscribers) {
|
|
292
|
+
callback(message.changes);
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function handleError() {
|
|
299
|
+
notifyConnectionStateChange("disconnected");
|
|
300
|
+
for (const [id, pending] of pendingPush) {
|
|
301
|
+
if (pending.timeoutId) {
|
|
302
|
+
clearTimeout(pending.timeoutId);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
for (const [id, pending] of pendingPull) {
|
|
306
|
+
if (pending.timeoutId) {
|
|
307
|
+
clearTimeout(pending.timeoutId);
|
|
308
|
+
}
|
|
309
|
+
pending.reject(new Error("WebSocket error"));
|
|
310
|
+
}
|
|
311
|
+
pendingPull.clear();
|
|
312
|
+
scheduleReconnect();
|
|
313
|
+
}
|
|
314
|
+
function handleClose() {
|
|
315
|
+
ws = null;
|
|
316
|
+
notifyConnectionStateChange("disconnected");
|
|
317
|
+
if (!isClosed) {
|
|
318
|
+
scheduleReconnect();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function scheduleReconnect() {
|
|
322
|
+
if (!reconnectOptions.enabled || isClosed) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (reconnectAttempts >= reconnectOptions.maxAttempts) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
notifyConnectionStateChange("reconnecting");
|
|
329
|
+
const delay = Math.min(
|
|
330
|
+
reconnectOptions.initialDelay * Math.pow(2, reconnectAttempts),
|
|
331
|
+
reconnectOptions.maxDelay
|
|
332
|
+
);
|
|
333
|
+
reconnectAttempts++;
|
|
334
|
+
reconnectTimeout = setTimeout(() => {
|
|
335
|
+
if (!isClosed) {
|
|
336
|
+
connect();
|
|
337
|
+
}
|
|
338
|
+
}, delay);
|
|
339
|
+
}
|
|
340
|
+
const WS_CONNECTING = 0;
|
|
341
|
+
const WS_OPEN = 1;
|
|
342
|
+
const WS_CLOSING = 2;
|
|
343
|
+
const WS_CLOSED = 3;
|
|
344
|
+
function connect() {
|
|
345
|
+
if (ws && ws.readyState === WS_OPEN) {
|
|
346
|
+
return ws;
|
|
347
|
+
}
|
|
348
|
+
if (ws && ws.readyState === WS_CONNECTING) {
|
|
349
|
+
return ws;
|
|
350
|
+
}
|
|
351
|
+
notifyConnectionStateChange("connecting");
|
|
352
|
+
ws = new WebSocket(getWebSocketUrl());
|
|
353
|
+
ws.onopen = () => {
|
|
354
|
+
reconnectAttempts = 0;
|
|
355
|
+
notifyConnectionStateChange("connected");
|
|
356
|
+
flushMutationQueue().catch(() => {
|
|
357
|
+
});
|
|
358
|
+
};
|
|
359
|
+
ws.onmessage = handleMessage;
|
|
360
|
+
ws.onerror = handleError;
|
|
361
|
+
ws.onclose = handleClose;
|
|
362
|
+
return ws;
|
|
363
|
+
}
|
|
364
|
+
function ensureConnected() {
|
|
365
|
+
return new Promise((resolve, reject) => {
|
|
366
|
+
const socket = connect();
|
|
367
|
+
if (socket.readyState === WS_OPEN) {
|
|
368
|
+
resolve(socket);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const originalOnOpen = socket.onopen;
|
|
372
|
+
const originalOnError = socket.onerror;
|
|
373
|
+
socket.onopen = (event) => {
|
|
374
|
+
socket.onopen = originalOnOpen;
|
|
375
|
+
socket.onerror = originalOnError;
|
|
376
|
+
if (originalOnOpen) {
|
|
377
|
+
originalOnOpen.call(socket, event);
|
|
378
|
+
}
|
|
379
|
+
resolve(socket);
|
|
380
|
+
};
|
|
381
|
+
socket.onerror = (event) => {
|
|
382
|
+
socket.onopen = originalOnOpen;
|
|
383
|
+
socket.onerror = originalOnError;
|
|
384
|
+
if (originalOnError) {
|
|
385
|
+
originalOnError.call(socket, event);
|
|
386
|
+
}
|
|
387
|
+
reject(new Error("WebSocket connection failed"));
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
function push(changes) {
|
|
392
|
+
const id = nextMessageId();
|
|
393
|
+
const isEmptyPush = changes.length === 0;
|
|
394
|
+
return new Promise((resolve, reject) => {
|
|
395
|
+
const message = {
|
|
396
|
+
type: "push",
|
|
397
|
+
id,
|
|
398
|
+
changes
|
|
399
|
+
};
|
|
400
|
+
if (authToken) {
|
|
401
|
+
message.auth = { token: authToken };
|
|
402
|
+
}
|
|
403
|
+
let timeoutId;
|
|
404
|
+
if (!isEmptyPush && requestTimeout > 0) {
|
|
405
|
+
timeoutId = setTimeout(() => {
|
|
406
|
+
const pending = pendingPush.get(id);
|
|
407
|
+
if (pending) {
|
|
408
|
+
pendingPush.delete(id);
|
|
409
|
+
pending.reject(new Error("Push timeout"));
|
|
410
|
+
}
|
|
411
|
+
}, requestTimeout);
|
|
412
|
+
}
|
|
413
|
+
if (!isEmptyPush) {
|
|
414
|
+
pendingPush.set(id, { resolve, reject, timeoutId });
|
|
415
|
+
}
|
|
416
|
+
ensureConnected().then((socket) => {
|
|
417
|
+
socket.send(JSON.stringify(message));
|
|
418
|
+
if (isEmptyPush) {
|
|
419
|
+
resolve();
|
|
420
|
+
}
|
|
421
|
+
}).catch((error) => {
|
|
422
|
+
if (!isEmptyPush) {
|
|
423
|
+
pendingPush.delete(id);
|
|
424
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
425
|
+
queueMutation(changes);
|
|
426
|
+
resolve();
|
|
427
|
+
} else {
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
function pull() {
|
|
433
|
+
const id = nextMessageId();
|
|
434
|
+
return new Promise((resolve, reject) => {
|
|
435
|
+
const message = {
|
|
436
|
+
type: "pull",
|
|
437
|
+
id
|
|
438
|
+
};
|
|
439
|
+
if (authToken) {
|
|
440
|
+
message.auth = { token: authToken };
|
|
441
|
+
}
|
|
442
|
+
let timeoutId;
|
|
443
|
+
if (requestTimeout > 0) {
|
|
444
|
+
timeoutId = setTimeout(() => {
|
|
445
|
+
const pending = pendingPull.get(id);
|
|
446
|
+
if (pending) {
|
|
447
|
+
pendingPull.delete(id);
|
|
448
|
+
pending.reject(new Error("Pull timeout"));
|
|
449
|
+
}
|
|
450
|
+
}, requestTimeout);
|
|
451
|
+
}
|
|
452
|
+
pendingPull.set(id, { resolve, reject, timeoutId });
|
|
453
|
+
ensureConnected().then((socket) => {
|
|
454
|
+
socket.send(JSON.stringify(message));
|
|
455
|
+
}).catch((error) => {
|
|
456
|
+
pendingPull.delete(id);
|
|
457
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
458
|
+
reject(error);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
function subscribe(callback) {
|
|
463
|
+
subscribers.add(callback);
|
|
464
|
+
connect();
|
|
465
|
+
return () => {
|
|
466
|
+
subscribers.delete(callback);
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function close() {
|
|
470
|
+
isClosed = true;
|
|
471
|
+
if (reconnectTimeout) {
|
|
472
|
+
clearTimeout(reconnectTimeout);
|
|
473
|
+
reconnectTimeout = null;
|
|
474
|
+
}
|
|
475
|
+
if (ws) {
|
|
476
|
+
ws.close();
|
|
477
|
+
ws = null;
|
|
478
|
+
}
|
|
479
|
+
notifyConnectionStateChange("disconnected");
|
|
480
|
+
}
|
|
481
|
+
function setAuthToken(token) {
|
|
482
|
+
authToken = token;
|
|
483
|
+
}
|
|
484
|
+
function onConnectionStateChange(callback) {
|
|
485
|
+
connectionStateObservers.add(callback);
|
|
486
|
+
callback(connectionState);
|
|
487
|
+
return () => {
|
|
488
|
+
connectionStateObservers.delete(callback);
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function getConnectionState() {
|
|
492
|
+
return connectionState;
|
|
493
|
+
}
|
|
494
|
+
function getQueuedMutations() {
|
|
495
|
+
return Array.from(mutationQueue.values());
|
|
496
|
+
}
|
|
497
|
+
function getQueueStats() {
|
|
498
|
+
const mutations = Array.from(mutationQueue.values());
|
|
499
|
+
if (mutations.length === 0) {
|
|
500
|
+
return { count: 0, oldestAt: null };
|
|
501
|
+
}
|
|
502
|
+
const oldest = Math.min(...mutations.map((m) => m.queuedAt));
|
|
503
|
+
return { count: mutations.length, oldestAt: oldest };
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
push,
|
|
507
|
+
pull,
|
|
508
|
+
subscribe,
|
|
509
|
+
close,
|
|
510
|
+
setAuthToken,
|
|
511
|
+
onConnectionStateChange,
|
|
512
|
+
getConnectionState,
|
|
513
|
+
getQueuedMutations,
|
|
514
|
+
getQueueStats
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/data/context.tsx
|
|
519
|
+
import * as React from "react";
|
|
520
|
+
import { createContext, useContext } from "react";
|
|
521
|
+
var DBContext = createContext(void 0);
|
|
522
|
+
function DBProvider({ db, children }) {
|
|
523
|
+
return React.createElement(DBContext.Provider, { value: db }, children);
|
|
524
|
+
}
|
|
525
|
+
function useDBContext() {
|
|
526
|
+
const db = useContext(DBContext);
|
|
527
|
+
if (!db) {
|
|
528
|
+
throw new Error(
|
|
529
|
+
"useDBContext must be used within a DBProvider. Wrap your component with <DBProvider db={...}> at a higher level in the component tree."
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
return db;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/data/hooks.ts
|
|
536
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
537
|
+
function matchesFilter2(value, filter) {
|
|
538
|
+
if (filter === null || filter === void 0 || typeof filter !== "object") {
|
|
539
|
+
return value === filter;
|
|
540
|
+
}
|
|
541
|
+
const ops = filter;
|
|
542
|
+
if ("$eq" in ops && value !== ops.$eq) return false;
|
|
543
|
+
if ("$ne" in ops && value === ops.$ne) return false;
|
|
544
|
+
if ("$gt" in ops && !(value > ops.$gt)) return false;
|
|
545
|
+
if ("$gte" in ops && !(value >= ops.$gte)) return false;
|
|
546
|
+
if ("$lt" in ops && !(value < ops.$lt)) return false;
|
|
547
|
+
if ("$lte" in ops && !(value <= ops.$lte)) return false;
|
|
548
|
+
if ("$in" in ops && !ops.$in.includes(value)) return false;
|
|
549
|
+
if ("$nin" in ops && ops.$nin.includes(value)) return false;
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
function matchesWhere2(doc, where) {
|
|
553
|
+
if ("$or" in where && where.$or) {
|
|
554
|
+
const orClauses = where.$or;
|
|
555
|
+
const matchesOr = orClauses.some((clause) => matchesWhere2(doc, clause));
|
|
556
|
+
if (!matchesOr) return false;
|
|
557
|
+
const { $or, ...rest } = where;
|
|
558
|
+
if (Object.keys(rest).length === 0) return true;
|
|
559
|
+
return matchesWhere2(doc, rest);
|
|
560
|
+
}
|
|
561
|
+
for (const [key, filter] of Object.entries(where)) {
|
|
562
|
+
if (key === "$or") continue;
|
|
563
|
+
const value = doc[key];
|
|
564
|
+
if (!matchesFilter2(value, filter)) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
function sortDocuments2(docs, orderBy) {
|
|
571
|
+
const orderClauses = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
572
|
+
return [...docs].sort((a, b) => {
|
|
573
|
+
for (const clause of orderClauses) {
|
|
574
|
+
const [field, direction] = Object.entries(clause)[0];
|
|
575
|
+
const aVal = a[field];
|
|
576
|
+
const bVal = b[field];
|
|
577
|
+
if (aVal === null || aVal === void 0) {
|
|
578
|
+
if (bVal !== null && bVal !== void 0) return 1;
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (bVal === null || bVal === void 0) return -1;
|
|
582
|
+
let comparison = 0;
|
|
583
|
+
if (typeof aVal === "string") {
|
|
584
|
+
comparison = aVal.localeCompare(bVal);
|
|
585
|
+
} else if (typeof aVal === "number") {
|
|
586
|
+
comparison = aVal - bVal;
|
|
587
|
+
} else if (aVal instanceof Date) {
|
|
588
|
+
comparison = aVal.getTime() - bVal.getTime();
|
|
589
|
+
} else {
|
|
590
|
+
comparison = String(aVal).localeCompare(String(bVal));
|
|
591
|
+
}
|
|
592
|
+
if (comparison !== 0) {
|
|
593
|
+
return direction === "desc" ? -comparison : comparison;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return 0;
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
function useQuery(options) {
|
|
600
|
+
const db = useDBContext();
|
|
601
|
+
const [data, setData] = useState(void 0);
|
|
602
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
603
|
+
const [error, setError] = useState(void 0);
|
|
604
|
+
const { from, where, orderBy, limit, offset } = options;
|
|
605
|
+
const collection = db.collections[from];
|
|
606
|
+
const executeQuery = useCallback(async () => {
|
|
607
|
+
if (!collection) {
|
|
608
|
+
setError(new Error(`Collection '${from}' not found`));
|
|
609
|
+
setIsLoading(false);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
const result = await collection.findMany({ where, orderBy, limit, offset });
|
|
614
|
+
setData(result);
|
|
615
|
+
setError(void 0);
|
|
616
|
+
} catch (err) {
|
|
617
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
618
|
+
} finally {
|
|
619
|
+
setIsLoading(false);
|
|
620
|
+
}
|
|
621
|
+
}, [collection, from, JSON.stringify(where), JSON.stringify(orderBy), limit, offset]);
|
|
622
|
+
useEffect(() => {
|
|
623
|
+
executeQuery();
|
|
624
|
+
}, [executeQuery]);
|
|
625
|
+
useEffect(() => {
|
|
626
|
+
if (!collection) return;
|
|
627
|
+
const unsubscribe = collection.subscribe((allData) => {
|
|
628
|
+
let result = allData;
|
|
629
|
+
if (where) {
|
|
630
|
+
result = result.filter((doc) => matchesWhere2(doc, where));
|
|
631
|
+
}
|
|
632
|
+
if (orderBy) {
|
|
633
|
+
result = sortDocuments2(result, orderBy);
|
|
634
|
+
}
|
|
635
|
+
if (offset !== void 0) {
|
|
636
|
+
result = result.slice(offset);
|
|
637
|
+
}
|
|
638
|
+
if (limit !== void 0) {
|
|
639
|
+
result = result.slice(0, limit);
|
|
640
|
+
}
|
|
641
|
+
setData(result);
|
|
642
|
+
});
|
|
643
|
+
return unsubscribe;
|
|
644
|
+
}, [collection, JSON.stringify(where), JSON.stringify(orderBy), limit, offset]);
|
|
645
|
+
return {
|
|
646
|
+
data,
|
|
647
|
+
isLoading,
|
|
648
|
+
error,
|
|
649
|
+
refetch: executeQuery
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function useMutation(options) {
|
|
653
|
+
const db = useDBContext();
|
|
654
|
+
const [isPending, setIsPending] = useState(false);
|
|
655
|
+
const [error, setError] = useState(void 0);
|
|
656
|
+
const { collection: collectionName, operation, optimistic = false } = options;
|
|
657
|
+
const collection = db.collections[collectionName];
|
|
658
|
+
const rollbackRef = useRef(null);
|
|
659
|
+
const mutate = useCallback(
|
|
660
|
+
async (mutationData) => {
|
|
661
|
+
if (!collection) {
|
|
662
|
+
throw new Error(`Collection '${collectionName}' not found`);
|
|
663
|
+
}
|
|
664
|
+
setIsPending(true);
|
|
665
|
+
setError(void 0);
|
|
666
|
+
let rollbackFn = null;
|
|
667
|
+
try {
|
|
668
|
+
if (operation === "insert") {
|
|
669
|
+
const insertData = mutationData;
|
|
670
|
+
if (optimistic) {
|
|
671
|
+
await collection.insert(insertData);
|
|
672
|
+
if (db.sync) {
|
|
673
|
+
try {
|
|
674
|
+
await db.sync.push([{ type: "insert", collection: collectionName, data: insertData }]);
|
|
675
|
+
} catch (syncError) {
|
|
676
|
+
const primaryKey = collection.primaryKey || "id";
|
|
677
|
+
await collection.delete({ [primaryKey]: insertData[primaryKey] });
|
|
678
|
+
throw syncError;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
} else {
|
|
682
|
+
await collection.insert(insertData);
|
|
683
|
+
if (db.sync) {
|
|
684
|
+
await db.sync.push([{ type: "insert", collection: collectionName, data: insertData }]);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
} else if (operation === "update") {
|
|
688
|
+
const updateData = mutationData;
|
|
689
|
+
await collection.update(updateData.where, updateData.data);
|
|
690
|
+
if (db.sync) {
|
|
691
|
+
await db.sync.push([{ type: "update", collection: collectionName, data: updateData }]);
|
|
692
|
+
}
|
|
693
|
+
} else if (operation === "delete") {
|
|
694
|
+
const deleteData = mutationData;
|
|
695
|
+
await collection.delete({ id: deleteData.id });
|
|
696
|
+
if (db.sync) {
|
|
697
|
+
await db.sync.push([{ type: "delete", collection: collectionName, data: deleteData }]);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
} catch (err) {
|
|
701
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
702
|
+
setError(error2);
|
|
703
|
+
throw error2;
|
|
704
|
+
} finally {
|
|
705
|
+
setIsPending(false);
|
|
706
|
+
rollbackRef.current = null;
|
|
707
|
+
}
|
|
708
|
+
},
|
|
709
|
+
[collection, collectionName, operation, optimistic, db.sync]
|
|
710
|
+
);
|
|
711
|
+
const reset = useCallback(() => {
|
|
712
|
+
setIsPending(false);
|
|
713
|
+
setError(void 0);
|
|
714
|
+
}, []);
|
|
715
|
+
return {
|
|
716
|
+
mutate,
|
|
717
|
+
isPending,
|
|
718
|
+
error,
|
|
719
|
+
reset
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/data/reactive.ts
|
|
724
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
725
|
+
function useReactiveData(options) {
|
|
726
|
+
const db = useDBContext();
|
|
727
|
+
const {
|
|
728
|
+
collection: collectionName,
|
|
729
|
+
where,
|
|
730
|
+
orderBy: initialOrderBy,
|
|
731
|
+
limit,
|
|
732
|
+
offset,
|
|
733
|
+
selectable = false,
|
|
734
|
+
optimistic = true,
|
|
735
|
+
primaryKey = "id"
|
|
736
|
+
} = options;
|
|
737
|
+
const [data, setData] = useState2([]);
|
|
738
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
739
|
+
const [error, setError] = useState2(void 0);
|
|
740
|
+
const [isMutating, setIsMutating] = useState2(false);
|
|
741
|
+
const [sort, setSortState] = useState2(() => {
|
|
742
|
+
if (!initialOrderBy) return { field: null, direction: "asc" };
|
|
743
|
+
const orderClauses = Array.isArray(initialOrderBy) ? initialOrderBy : [initialOrderBy];
|
|
744
|
+
if (orderClauses.length === 0) return { field: null, direction: "asc" };
|
|
745
|
+
const [field, direction] = Object.entries(orderClauses[0])[0];
|
|
746
|
+
return { field, direction };
|
|
747
|
+
});
|
|
748
|
+
const [selection, setSelection] = useState2({
|
|
749
|
+
mode: selectable === true ? "single" : selectable === false ? "none" : selectable,
|
|
750
|
+
selected: /* @__PURE__ */ new Set()
|
|
751
|
+
});
|
|
752
|
+
const [pagination, setPagination] = useState2({
|
|
753
|
+
page: 1,
|
|
754
|
+
pageSize: limit ?? 20,
|
|
755
|
+
total: 0
|
|
756
|
+
});
|
|
757
|
+
const rollbackDataRef = useRef2([]);
|
|
758
|
+
const collection = db.collections[collectionName];
|
|
759
|
+
const currentOrderBy = sort.field ? { [sort.field]: sort.direction } : initialOrderBy;
|
|
760
|
+
const executeQuery = useCallback2(async () => {
|
|
761
|
+
if (!collection) {
|
|
762
|
+
setError(new Error(`Collection '${collectionName}' not found`));
|
|
763
|
+
setIsLoading(false);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
try {
|
|
767
|
+
const result = await collection.findMany({
|
|
768
|
+
where,
|
|
769
|
+
orderBy: currentOrderBy,
|
|
770
|
+
limit,
|
|
771
|
+
offset
|
|
772
|
+
});
|
|
773
|
+
setData(result);
|
|
774
|
+
setPagination((prev) => ({ ...prev, total: result.length }));
|
|
775
|
+
setError(void 0);
|
|
776
|
+
} catch (err) {
|
|
777
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
778
|
+
} finally {
|
|
779
|
+
setIsLoading(false);
|
|
780
|
+
}
|
|
781
|
+
}, [collection, collectionName, JSON.stringify(where), JSON.stringify(currentOrderBy), limit, offset]);
|
|
782
|
+
useEffect2(() => {
|
|
783
|
+
executeQuery();
|
|
784
|
+
}, [executeQuery]);
|
|
785
|
+
useEffect2(() => {
|
|
786
|
+
if (!collection) return;
|
|
787
|
+
const unsubscribe = collection.subscribe((allData) => {
|
|
788
|
+
let result = allData;
|
|
789
|
+
if (where) {
|
|
790
|
+
result = result.filter((doc) => matchesWhere3(doc, where));
|
|
791
|
+
}
|
|
792
|
+
if (currentOrderBy) {
|
|
793
|
+
result = sortDocuments3(result, currentOrderBy);
|
|
794
|
+
}
|
|
795
|
+
if (offset !== void 0) {
|
|
796
|
+
result = result.slice(offset);
|
|
797
|
+
}
|
|
798
|
+
if (limit !== void 0) {
|
|
799
|
+
result = result.slice(0, limit);
|
|
800
|
+
}
|
|
801
|
+
setData(result);
|
|
802
|
+
setPagination((prev) => ({ ...prev, total: result.length }));
|
|
803
|
+
});
|
|
804
|
+
return unsubscribe;
|
|
805
|
+
}, [collection, JSON.stringify(where), JSON.stringify(currentOrderBy), limit, offset]);
|
|
806
|
+
const mutate = {
|
|
807
|
+
insert: useCallback2(async (newData) => {
|
|
808
|
+
if (!collection) {
|
|
809
|
+
throw new Error(`Collection '${collectionName}' not found`);
|
|
810
|
+
}
|
|
811
|
+
setIsMutating(true);
|
|
812
|
+
if (optimistic) {
|
|
813
|
+
rollbackDataRef.current = [...data];
|
|
814
|
+
setData((prev) => [...prev, newData]);
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
await collection.insert(newData);
|
|
818
|
+
} catch (err) {
|
|
819
|
+
if (optimistic) {
|
|
820
|
+
setData(rollbackDataRef.current);
|
|
821
|
+
}
|
|
822
|
+
throw err;
|
|
823
|
+
} finally {
|
|
824
|
+
setIsMutating(false);
|
|
825
|
+
}
|
|
826
|
+
}, [collection, collectionName, data, optimistic]),
|
|
827
|
+
update: useCallback2(async (filter, updates) => {
|
|
828
|
+
if (!collection) {
|
|
829
|
+
throw new Error(`Collection '${collectionName}' not found`);
|
|
830
|
+
}
|
|
831
|
+
setIsMutating(true);
|
|
832
|
+
if (optimistic) {
|
|
833
|
+
rollbackDataRef.current = [...data];
|
|
834
|
+
setData((prev) => prev.map((item) => {
|
|
835
|
+
const matches = Object.entries(filter).every(([key, val]) => item[key] === val);
|
|
836
|
+
return matches ? { ...item, ...updates } : item;
|
|
837
|
+
}));
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
await collection.update(filter, updates);
|
|
841
|
+
} catch (err) {
|
|
842
|
+
if (optimistic) {
|
|
843
|
+
setData(rollbackDataRef.current);
|
|
844
|
+
}
|
|
845
|
+
throw err;
|
|
846
|
+
} finally {
|
|
847
|
+
setIsMutating(false);
|
|
848
|
+
}
|
|
849
|
+
}, [collection, collectionName, data, optimistic]),
|
|
850
|
+
delete: useCallback2(async (filter) => {
|
|
851
|
+
if (!collection) {
|
|
852
|
+
throw new Error(`Collection '${collectionName}' not found`);
|
|
853
|
+
}
|
|
854
|
+
setIsMutating(true);
|
|
855
|
+
if (optimistic) {
|
|
856
|
+
rollbackDataRef.current = [...data];
|
|
857
|
+
setData((prev) => prev.filter((item) => {
|
|
858
|
+
const matches = Object.entries(filter).every(([key, val]) => item[key] === val);
|
|
859
|
+
return !matches;
|
|
860
|
+
}));
|
|
861
|
+
}
|
|
862
|
+
try {
|
|
863
|
+
await collection.delete(filter);
|
|
864
|
+
} catch (err) {
|
|
865
|
+
if (optimistic) {
|
|
866
|
+
setData(rollbackDataRef.current);
|
|
867
|
+
}
|
|
868
|
+
throw err;
|
|
869
|
+
} finally {
|
|
870
|
+
setIsMutating(false);
|
|
871
|
+
}
|
|
872
|
+
}, [collection, collectionName, data, optimistic])
|
|
873
|
+
};
|
|
874
|
+
const setSort = useCallback2((field) => {
|
|
875
|
+
setSortState((prev) => ({
|
|
876
|
+
field,
|
|
877
|
+
direction: prev.field === field && prev.direction === "asc" ? "desc" : "asc"
|
|
878
|
+
}));
|
|
879
|
+
}, []);
|
|
880
|
+
const toggleSelect = useCallback2((id) => {
|
|
881
|
+
setSelection((prev) => {
|
|
882
|
+
const newSelected = new Set(prev.selected);
|
|
883
|
+
if (prev.mode === "single") {
|
|
884
|
+
if (newSelected.has(id)) {
|
|
885
|
+
newSelected.clear();
|
|
886
|
+
} else {
|
|
887
|
+
newSelected.clear();
|
|
888
|
+
newSelected.add(id);
|
|
889
|
+
}
|
|
890
|
+
} else if (prev.mode === "multi") {
|
|
891
|
+
if (newSelected.has(id)) {
|
|
892
|
+
newSelected.delete(id);
|
|
893
|
+
} else {
|
|
894
|
+
newSelected.add(id);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return { ...prev, selected: newSelected };
|
|
898
|
+
});
|
|
899
|
+
}, []);
|
|
900
|
+
const selectAll = useCallback2(() => {
|
|
901
|
+
if (selection.mode === "none") return;
|
|
902
|
+
const allIds = data.map((item) => String(item[primaryKey]));
|
|
903
|
+
setSelection((prev) => ({ ...prev, selected: new Set(allIds) }));
|
|
904
|
+
}, [data, primaryKey, selection.mode]);
|
|
905
|
+
const clearSelection = useCallback2(() => {
|
|
906
|
+
setSelection((prev) => ({ ...prev, selected: /* @__PURE__ */ new Set() }));
|
|
907
|
+
}, []);
|
|
908
|
+
const setPage = useCallback2((page) => {
|
|
909
|
+
setPagination((prev) => ({ ...prev, page }));
|
|
910
|
+
}, []);
|
|
911
|
+
const setPageSize = useCallback2((size) => {
|
|
912
|
+
setPagination((prev) => ({ ...prev, pageSize: size, page: 1 }));
|
|
913
|
+
}, []);
|
|
914
|
+
return {
|
|
915
|
+
data,
|
|
916
|
+
isLoading,
|
|
917
|
+
error,
|
|
918
|
+
refetch: executeQuery,
|
|
919
|
+
mutate,
|
|
920
|
+
isMutating,
|
|
921
|
+
sort,
|
|
922
|
+
setSort,
|
|
923
|
+
selection,
|
|
924
|
+
toggleSelect,
|
|
925
|
+
selectAll,
|
|
926
|
+
clearSelection,
|
|
927
|
+
pagination,
|
|
928
|
+
setPage,
|
|
929
|
+
setPageSize
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
function useReactiveTable(options) {
|
|
933
|
+
return useReactiveData(options);
|
|
934
|
+
}
|
|
935
|
+
function useReactiveList(options) {
|
|
936
|
+
return useReactiveData(options);
|
|
937
|
+
}
|
|
938
|
+
function useReactiveMetrics(options) {
|
|
939
|
+
const db = useDBContext();
|
|
940
|
+
const { collection: collectionName, where, metrics: metricDefs } = options;
|
|
941
|
+
const [metrics, setMetrics] = useState2([]);
|
|
942
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
943
|
+
const [error, setError] = useState2(void 0);
|
|
944
|
+
const historyRef = useRef2(/* @__PURE__ */ new Map());
|
|
945
|
+
const collection = db.collections[collectionName];
|
|
946
|
+
const computeMetrics = useCallback2(async () => {
|
|
947
|
+
if (!collection) {
|
|
948
|
+
setError(new Error(`Collection '${collectionName}' not found`));
|
|
949
|
+
setIsLoading(false);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
try {
|
|
953
|
+
const data = await collection.findMany({ where });
|
|
954
|
+
const computed = metricDefs.map((def) => {
|
|
955
|
+
const values = data.map((doc) => {
|
|
956
|
+
const val = doc[def.field];
|
|
957
|
+
return typeof val === "number" ? val : 0;
|
|
958
|
+
});
|
|
959
|
+
let value;
|
|
960
|
+
switch (def.aggregate) {
|
|
961
|
+
case "count":
|
|
962
|
+
value = data.length;
|
|
963
|
+
break;
|
|
964
|
+
case "sum":
|
|
965
|
+
value = values.reduce((a, b) => a + b, 0);
|
|
966
|
+
break;
|
|
967
|
+
case "avg":
|
|
968
|
+
value = values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0;
|
|
969
|
+
break;
|
|
970
|
+
case "min":
|
|
971
|
+
value = values.length > 0 ? Math.min(...values) : 0;
|
|
972
|
+
break;
|
|
973
|
+
case "max":
|
|
974
|
+
value = values.length > 0 ? Math.max(...values) : 0;
|
|
975
|
+
break;
|
|
976
|
+
case "latest":
|
|
977
|
+
value = values.length > 0 ? values[values.length - 1] : 0;
|
|
978
|
+
break;
|
|
979
|
+
default:
|
|
980
|
+
value = 0;
|
|
981
|
+
}
|
|
982
|
+
const history = historyRef.current.get(def.key) || [];
|
|
983
|
+
history.push(value);
|
|
984
|
+
if (history.length > 20) history.shift();
|
|
985
|
+
historyRef.current.set(def.key, history);
|
|
986
|
+
let trend = "neutral";
|
|
987
|
+
let trendValue;
|
|
988
|
+
if (history.length >= 2) {
|
|
989
|
+
const prev = history[history.length - 2];
|
|
990
|
+
const current = history[history.length - 1];
|
|
991
|
+
if (prev !== 0) {
|
|
992
|
+
trendValue = (current - prev) / prev * 100;
|
|
993
|
+
trend = trendValue > 0 ? "up" : trendValue < 0 ? "down" : "neutral";
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
let formattedValue = value;
|
|
997
|
+
if (def.format === "currency") {
|
|
998
|
+
formattedValue = `$${value.toLocaleString()}`;
|
|
999
|
+
} else if (def.format === "percentage") {
|
|
1000
|
+
formattedValue = `${value.toFixed(1)}%`;
|
|
1001
|
+
}
|
|
1002
|
+
if (def.unit) {
|
|
1003
|
+
formattedValue = `${formattedValue} ${def.unit}`;
|
|
1004
|
+
}
|
|
1005
|
+
return {
|
|
1006
|
+
key: def.key,
|
|
1007
|
+
label: def.label,
|
|
1008
|
+
value: formattedValue,
|
|
1009
|
+
trend,
|
|
1010
|
+
trendValue,
|
|
1011
|
+
sparkline: [...history]
|
|
1012
|
+
};
|
|
1013
|
+
});
|
|
1014
|
+
setMetrics(computed);
|
|
1015
|
+
setError(void 0);
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1018
|
+
} finally {
|
|
1019
|
+
setIsLoading(false);
|
|
1020
|
+
}
|
|
1021
|
+
}, [collection, collectionName, JSON.stringify(where), JSON.stringify(metricDefs)]);
|
|
1022
|
+
useEffect2(() => {
|
|
1023
|
+
computeMetrics();
|
|
1024
|
+
}, [computeMetrics]);
|
|
1025
|
+
useEffect2(() => {
|
|
1026
|
+
if (!collection) return;
|
|
1027
|
+
const unsubscribe = collection.subscribe(() => {
|
|
1028
|
+
computeMetrics();
|
|
1029
|
+
});
|
|
1030
|
+
return unsubscribe;
|
|
1031
|
+
}, [collection, computeMetrics]);
|
|
1032
|
+
return {
|
|
1033
|
+
metrics,
|
|
1034
|
+
isLoading,
|
|
1035
|
+
error,
|
|
1036
|
+
refetch: computeMetrics
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function useReactiveCard(options) {
|
|
1040
|
+
const db = useDBContext();
|
|
1041
|
+
const { collection: collectionName, where, optimistic = true } = options;
|
|
1042
|
+
const [data, setData] = useState2(null);
|
|
1043
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
1044
|
+
const [error, setError] = useState2(void 0);
|
|
1045
|
+
const [isUpdating, setIsUpdating] = useState2(false);
|
|
1046
|
+
const rollbackRef = useRef2(null);
|
|
1047
|
+
const collection = db.collections[collectionName];
|
|
1048
|
+
const fetchData = useCallback2(async () => {
|
|
1049
|
+
if (!collection) {
|
|
1050
|
+
setError(new Error(`Collection '${collectionName}' not found`));
|
|
1051
|
+
setIsLoading(false);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
try {
|
|
1055
|
+
const result = await collection.findOne(where);
|
|
1056
|
+
setData(result);
|
|
1057
|
+
setError(void 0);
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1060
|
+
} finally {
|
|
1061
|
+
setIsLoading(false);
|
|
1062
|
+
}
|
|
1063
|
+
}, [collection, collectionName, JSON.stringify(where)]);
|
|
1064
|
+
useEffect2(() => {
|
|
1065
|
+
fetchData();
|
|
1066
|
+
}, [fetchData]);
|
|
1067
|
+
useEffect2(() => {
|
|
1068
|
+
if (!collection) return;
|
|
1069
|
+
const unsubscribe = collection.subscribe((allData) => {
|
|
1070
|
+
const found = allData.find(
|
|
1071
|
+
(doc) => Object.entries(where).every(([key, val]) => doc[key] === val)
|
|
1072
|
+
);
|
|
1073
|
+
setData(found ?? null);
|
|
1074
|
+
});
|
|
1075
|
+
return unsubscribe;
|
|
1076
|
+
}, [collection, JSON.stringify(where)]);
|
|
1077
|
+
const update = useCallback2(async (updates) => {
|
|
1078
|
+
if (!collection || !data) {
|
|
1079
|
+
throw new Error("Cannot update: no data or collection");
|
|
1080
|
+
}
|
|
1081
|
+
setIsUpdating(true);
|
|
1082
|
+
if (optimistic) {
|
|
1083
|
+
rollbackRef.current = data;
|
|
1084
|
+
setData((prev) => prev ? { ...prev, ...updates } : null);
|
|
1085
|
+
}
|
|
1086
|
+
try {
|
|
1087
|
+
await collection.update(where, updates);
|
|
1088
|
+
} catch (err) {
|
|
1089
|
+
if (optimistic && rollbackRef.current) {
|
|
1090
|
+
setData(rollbackRef.current);
|
|
1091
|
+
}
|
|
1092
|
+
throw err;
|
|
1093
|
+
} finally {
|
|
1094
|
+
setIsUpdating(false);
|
|
1095
|
+
}
|
|
1096
|
+
}, [collection, data, where, optimistic]);
|
|
1097
|
+
return {
|
|
1098
|
+
data,
|
|
1099
|
+
isLoading,
|
|
1100
|
+
error,
|
|
1101
|
+
refetch: fetchData,
|
|
1102
|
+
update,
|
|
1103
|
+
isUpdating
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function matchesFilter3(value, filter) {
|
|
1107
|
+
if (filter === null || filter === void 0 || typeof filter !== "object") {
|
|
1108
|
+
return value === filter;
|
|
1109
|
+
}
|
|
1110
|
+
const ops = filter;
|
|
1111
|
+
if ("$eq" in ops && value !== ops.$eq) return false;
|
|
1112
|
+
if ("$ne" in ops && value === ops.$ne) return false;
|
|
1113
|
+
if ("$gt" in ops && !(value > ops.$gt)) return false;
|
|
1114
|
+
if ("$gte" in ops && !(value >= ops.$gte)) return false;
|
|
1115
|
+
if ("$lt" in ops && !(value < ops.$lt)) return false;
|
|
1116
|
+
if ("$lte" in ops && !(value <= ops.$lte)) return false;
|
|
1117
|
+
if ("$in" in ops && !ops.$in.includes(value)) return false;
|
|
1118
|
+
if ("$nin" in ops && ops.$nin.includes(value)) return false;
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
function matchesWhere3(doc, where) {
|
|
1122
|
+
if ("$or" in where && where.$or) {
|
|
1123
|
+
const orClauses = where.$or;
|
|
1124
|
+
const matchesOr = orClauses.some((clause) => matchesWhere3(doc, clause));
|
|
1125
|
+
if (!matchesOr) return false;
|
|
1126
|
+
const { $or, ...rest } = where;
|
|
1127
|
+
if (Object.keys(rest).length === 0) return true;
|
|
1128
|
+
return matchesWhere3(doc, rest);
|
|
1129
|
+
}
|
|
1130
|
+
for (const [key, filter] of Object.entries(where)) {
|
|
1131
|
+
if (key === "$or") continue;
|
|
1132
|
+
const value = doc[key];
|
|
1133
|
+
if (!matchesFilter3(value, filter)) {
|
|
1134
|
+
return false;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return true;
|
|
1138
|
+
}
|
|
1139
|
+
function sortDocuments3(docs, orderBy) {
|
|
1140
|
+
const orderClauses = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
1141
|
+
return [...docs].sort((a, b) => {
|
|
1142
|
+
for (const clause of orderClauses) {
|
|
1143
|
+
const [field, direction] = Object.entries(clause)[0];
|
|
1144
|
+
const aVal = a[field];
|
|
1145
|
+
const bVal = b[field];
|
|
1146
|
+
if (aVal === null || aVal === void 0) {
|
|
1147
|
+
if (bVal !== null && bVal !== void 0) return 1;
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (bVal === null || bVal === void 0) return -1;
|
|
1151
|
+
let comparison = 0;
|
|
1152
|
+
if (typeof aVal === "string") {
|
|
1153
|
+
comparison = aVal.localeCompare(bVal);
|
|
1154
|
+
} else if (typeof aVal === "number") {
|
|
1155
|
+
comparison = aVal - bVal;
|
|
1156
|
+
} else if (aVal instanceof Date) {
|
|
1157
|
+
comparison = aVal.getTime() - bVal.getTime();
|
|
1158
|
+
} else {
|
|
1159
|
+
comparison = String(aVal).localeCompare(String(bVal));
|
|
1160
|
+
}
|
|
1161
|
+
if (comparison !== 0) {
|
|
1162
|
+
return direction === "desc" ? -comparison : comparison;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return 0;
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// src/data/saas-collections.ts
|
|
1170
|
+
import { z } from "zod";
|
|
1171
|
+
var UserRoleSchema = z.enum(["admin", "user", "viewer"]);
|
|
1172
|
+
var UserSchema = z.object({
|
|
1173
|
+
/** Unique user identifier */
|
|
1174
|
+
id: z.string(),
|
|
1175
|
+
/** User's display name */
|
|
1176
|
+
name: z.string(),
|
|
1177
|
+
/** User's email address (validated format) */
|
|
1178
|
+
email: z.string().email(),
|
|
1179
|
+
/** User's role: admin, user, or viewer */
|
|
1180
|
+
role: UserRoleSchema,
|
|
1181
|
+
/** When the user account was created */
|
|
1182
|
+
createdAt: z.date()
|
|
1183
|
+
});
|
|
1184
|
+
var APIKeySchema = z.object({
|
|
1185
|
+
/** Unique key identifier */
|
|
1186
|
+
id: z.string(),
|
|
1187
|
+
/** The actual API key string (secret) */
|
|
1188
|
+
key: z.string(),
|
|
1189
|
+
/** Human-readable name for the key */
|
|
1190
|
+
name: z.string(),
|
|
1191
|
+
/** Array of permission strings this key grants */
|
|
1192
|
+
permissions: z.array(z.string()),
|
|
1193
|
+
/** When the key expires */
|
|
1194
|
+
expiresAt: z.date()
|
|
1195
|
+
});
|
|
1196
|
+
var WebhookSchema = z.object({
|
|
1197
|
+
/** Unique webhook identifier */
|
|
1198
|
+
id: z.string(),
|
|
1199
|
+
/** Webhook endpoint URL (validated format) */
|
|
1200
|
+
url: z.string().url(),
|
|
1201
|
+
/** Events this webhook listens to (non-empty array) */
|
|
1202
|
+
events: z.array(z.string()).min(1),
|
|
1203
|
+
/** Webhook signing secret */
|
|
1204
|
+
secret: z.string(),
|
|
1205
|
+
/** Whether the webhook is currently active */
|
|
1206
|
+
active: z.boolean()
|
|
1207
|
+
});
|
|
1208
|
+
var TeamSchema = z.object({
|
|
1209
|
+
/** Unique team identifier */
|
|
1210
|
+
id: z.string(),
|
|
1211
|
+
/** Team name */
|
|
1212
|
+
name: z.string(),
|
|
1213
|
+
/** Array of user IDs who are members */
|
|
1214
|
+
members: z.array(z.string())
|
|
1215
|
+
});
|
|
1216
|
+
var UsageSchema = z.object({
|
|
1217
|
+
/** Unique usage record identifier */
|
|
1218
|
+
id: z.string(),
|
|
1219
|
+
/** Name of the metric being tracked */
|
|
1220
|
+
metric: z.string(),
|
|
1221
|
+
/** Numeric value of the metric */
|
|
1222
|
+
value: z.number(),
|
|
1223
|
+
/** When this metric was recorded */
|
|
1224
|
+
timestamp: z.date()
|
|
1225
|
+
});
|
|
1226
|
+
function UsersCollection() {
|
|
1227
|
+
return createCollection({
|
|
1228
|
+
name: "users",
|
|
1229
|
+
schema: UserSchema,
|
|
1230
|
+
primaryKey: "id",
|
|
1231
|
+
indexes: ["email", "role"]
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
function APIKeysCollection() {
|
|
1235
|
+
return createCollection({
|
|
1236
|
+
name: "apiKeys",
|
|
1237
|
+
schema: APIKeySchema,
|
|
1238
|
+
primaryKey: "id",
|
|
1239
|
+
indexes: ["key", "name"]
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
function WebhooksCollection() {
|
|
1243
|
+
return createCollection({
|
|
1244
|
+
name: "webhooks",
|
|
1245
|
+
schema: WebhookSchema,
|
|
1246
|
+
primaryKey: "id",
|
|
1247
|
+
indexes: ["url", "active"]
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
function TeamsCollection() {
|
|
1251
|
+
return createCollection({
|
|
1252
|
+
name: "teams",
|
|
1253
|
+
schema: TeamSchema,
|
|
1254
|
+
primaryKey: "id",
|
|
1255
|
+
indexes: ["name"]
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
function UsageCollection() {
|
|
1259
|
+
return createCollection({
|
|
1260
|
+
name: "usage",
|
|
1261
|
+
schema: UsageSchema,
|
|
1262
|
+
primaryKey: "id",
|
|
1263
|
+
indexes: ["metric", "timestamp"]
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
export {
|
|
1268
|
+
createDB,
|
|
1269
|
+
createCollection,
|
|
1270
|
+
createDOSync,
|
|
1271
|
+
DBProvider,
|
|
1272
|
+
useDBContext,
|
|
1273
|
+
useQuery,
|
|
1274
|
+
useMutation,
|
|
1275
|
+
useReactiveData,
|
|
1276
|
+
useReactiveTable,
|
|
1277
|
+
useReactiveList,
|
|
1278
|
+
useReactiveMetrics,
|
|
1279
|
+
useReactiveCard,
|
|
1280
|
+
UserRoleSchema,
|
|
1281
|
+
UserSchema,
|
|
1282
|
+
APIKeySchema,
|
|
1283
|
+
WebhookSchema,
|
|
1284
|
+
TeamSchema,
|
|
1285
|
+
UsageSchema,
|
|
1286
|
+
UsersCollection,
|
|
1287
|
+
APIKeysCollection,
|
|
1288
|
+
WebhooksCollection,
|
|
1289
|
+
TeamsCollection,
|
|
1290
|
+
UsageCollection
|
|
1291
|
+
};
|