@sqlite-sync/core 0.12.2 → 0.13.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/dist/{reset-state-t0tTGqoh.js → crdt-sync-producer-BY-WobgO.js} +55 -241
- package/dist/crdt-sync-producer-BY-WobgO.js.map +1 -0
- package/dist/{crdt-sync-remote-source-CPXzmSIS.js → crdt-sync-remote-source-yeTEkOjH.js} +344 -12
- package/dist/crdt-sync-remote-source-yeTEkOjH.js.map +1 -0
- package/dist/{crdt-table-schema-BRm_WdcW.js → crdt-table-schema-D6Yf61Ex.js} +37 -13
- package/dist/crdt-table-schema-D6Yf61Ex.js.map +1 -0
- package/dist/{index-BHsKLqcK.d.ts → index-CH4lf3iX.d.ts} +166 -72
- package/dist/index.common-C8W9VKSY.js +276 -0
- package/dist/index.common-C8W9VKSY.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +20 -677
- package/dist/index.js.map +1 -1
- package/dist/index.native.d.ts +2 -0
- package/dist/index.native.js +9 -0
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +2 -2
- package/dist/local.d.ts +48 -0
- package/dist/local.js +255 -0
- package/dist/local.js.map +1 -0
- package/dist/{make-crdt-table-C6l0F-o_.js → make-crdt-table-DYCXOYB7.js} +13 -14
- package/dist/make-crdt-table-DYCXOYB7.js.map +1 -0
- package/dist/reset-state-CPOi_45B.js +220 -0
- package/dist/reset-state-CPOi_45B.js.map +1 -0
- package/dist/server.d.ts +8 -3
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/sqlite-reactive-db-DLS1xRZp.js +296 -0
- package/dist/sqlite-reactive-db-DLS1xRZp.js.map +1 -0
- package/dist/synced-db-database-B5M7MrBD.js +81 -0
- package/dist/synced-db-database-B5M7MrBD.js.map +1 -0
- package/dist/{system-schema-Dvl_BLId.js → system-schema-CXf3FmxN.js} +142 -58
- package/dist/system-schema-CXf3FmxN.js.map +1 -0
- package/dist/worker.d.ts +2 -10
- package/dist/worker.js +11 -177
- package/dist/worker.js.map +1 -1
- package/dist/ws-remote-source-BbPWrHB6.js +161 -0
- package/dist/ws-remote-source-BbPWrHB6.js.map +1 -0
- package/dist/ws-remote-source-DFBhDc6Z.d.ts +12 -0
- package/package.json +9 -1
- package/dist/crdt-sync-remote-source-CPXzmSIS.js.map +0 -1
- package/dist/crdt-table-schema-BRm_WdcW.js.map +0 -1
- package/dist/make-crdt-table-C6l0F-o_.js.map +0 -1
- package/dist/reset-state-t0tTGqoh.js.map +0 -1
- package/dist/system-schema-Dvl_BLId.js.map +0 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { _ as BoundMap, h as SQLiteDbWrapper, v as startPerformanceLogger } from "./system-schema-CXf3FmxN.js";
|
|
2
|
+
import { s as createTypedEventTarget } from "./crdt-table-schema-D6Yf61Ex.js";
|
|
3
|
+
//#region src/memory-db/sqlite-reactive-db.ts
|
|
4
|
+
var SQLiteReactiveDb = class SQLiteReactiveDb {
|
|
5
|
+
db;
|
|
6
|
+
driver;
|
|
7
|
+
logger;
|
|
8
|
+
tablesUsedStatement = null;
|
|
9
|
+
eventTarget = createTypedEventTarget();
|
|
10
|
+
isDisposed = false;
|
|
11
|
+
mutationVersion = 0;
|
|
12
|
+
tableMutationVersions = /* @__PURE__ */ new Map();
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
this.driver = opts.driver;
|
|
15
|
+
this.logger = opts.logger;
|
|
16
|
+
this.db = new SQLiteDbWrapper({
|
|
17
|
+
driver: opts.driver,
|
|
18
|
+
logger: this.logger,
|
|
19
|
+
loggerPrefix: opts.loggerPrefix ?? "memory"
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
static create(opts) {
|
|
23
|
+
const perf = startPerformanceLogger(opts.logger, "system");
|
|
24
|
+
const db = new SQLiteReactiveDb(opts);
|
|
25
|
+
if (opts.snapshot) db.useSnapshot(opts.snapshot);
|
|
26
|
+
db.registerDbHooks();
|
|
27
|
+
perf.logEnd("createSQLiteMemoryDb", "success", "system");
|
|
28
|
+
return db;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* When false, table mutation versions only advance after the writing statement
|
|
32
|
+
* returns, so they cannot tell whether that statement wrote to a table.
|
|
33
|
+
*/
|
|
34
|
+
get hasSynchronousHooks() {
|
|
35
|
+
return this.driver.synchronousHooks;
|
|
36
|
+
}
|
|
37
|
+
liveQueryStatements = new BoundMap({
|
|
38
|
+
maxSize: 100,
|
|
39
|
+
onRemove(_, value) {
|
|
40
|
+
value.finalize();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
sharedLiveQueries = /* @__PURE__ */ new Map();
|
|
44
|
+
createLiveQuery(query) {
|
|
45
|
+
let rowsVersion = 0;
|
|
46
|
+
const fetchRows = (parameters) => {
|
|
47
|
+
let statement = this.liveQueryStatements.get(query.sql);
|
|
48
|
+
if (!statement) {
|
|
49
|
+
statement = this.db.prepare(query.sql);
|
|
50
|
+
this.liveQueryStatements.set(query.sql, statement);
|
|
51
|
+
}
|
|
52
|
+
rowsVersion = this.mutationVersion;
|
|
53
|
+
return statement.execute(parameters);
|
|
54
|
+
};
|
|
55
|
+
let rows = null;
|
|
56
|
+
const getRows = () => {
|
|
57
|
+
if (!rows) rows = fetchRows(query.parameters);
|
|
58
|
+
return rows;
|
|
59
|
+
};
|
|
60
|
+
let subscriber = null;
|
|
61
|
+
let lastParameters = query.parameters;
|
|
62
|
+
const refresh = (parameters) => {
|
|
63
|
+
if (parameters) lastParameters = parameters;
|
|
64
|
+
rows = fetchRows(lastParameters);
|
|
65
|
+
subscriber?.();
|
|
66
|
+
};
|
|
67
|
+
const subscribe = (onchange) => {
|
|
68
|
+
if (subscriber) throw new Error("Subscriber already exists");
|
|
69
|
+
subscriber = onchange;
|
|
70
|
+
const subscription = this.subscribeToQueryChanges({
|
|
71
|
+
sql: query.sql,
|
|
72
|
+
onDataChange: refresh
|
|
73
|
+
});
|
|
74
|
+
if (rows && this.mutationVersion !== rowsVersion) refresh();
|
|
75
|
+
return () => {
|
|
76
|
+
subscription.unsubscribe();
|
|
77
|
+
subscriber = null;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
getRows,
|
|
82
|
+
refresh,
|
|
83
|
+
subscribe
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
getSharedLiveQuery(query) {
|
|
87
|
+
const existingEntry = this.sharedLiveQueries.get(query.sql)?.find((entry) => parametersAreEqual(entry.parameters, query.parameters));
|
|
88
|
+
if (existingEntry) {
|
|
89
|
+
if (existingEntry.listeners.size === 0) this.scheduleSharedLiveQueryCleanup(existingEntry);
|
|
90
|
+
return existingEntry;
|
|
91
|
+
}
|
|
92
|
+
const liveQuery = this.createLiveQuery(query);
|
|
93
|
+
const entry = {
|
|
94
|
+
sql: query.sql,
|
|
95
|
+
parameters: query.parameters,
|
|
96
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
97
|
+
unsubscribeFromLiveQuery: null,
|
|
98
|
+
cleanupTimeout: null,
|
|
99
|
+
getRows: () => liveQuery.getRows(),
|
|
100
|
+
refresh: () => {
|
|
101
|
+
liveQuery.refresh();
|
|
102
|
+
},
|
|
103
|
+
getSubscriberCount: () => entry.listeners.size,
|
|
104
|
+
subscribe: (onchange) => {
|
|
105
|
+
this.cancelSharedLiveQueryCleanup(entry);
|
|
106
|
+
this.registerSharedLiveQueryEntry(entry);
|
|
107
|
+
entry.listeners.add(onchange);
|
|
108
|
+
if (!entry.unsubscribeFromLiveQuery) entry.unsubscribeFromLiveQuery = liveQuery.subscribe(() => {
|
|
109
|
+
for (const listener of entry.listeners) listener();
|
|
110
|
+
});
|
|
111
|
+
return () => {
|
|
112
|
+
entry.listeners.delete(onchange);
|
|
113
|
+
if (entry.listeners.size === 0) {
|
|
114
|
+
entry.unsubscribeFromLiveQuery?.();
|
|
115
|
+
entry.unsubscribeFromLiveQuery = null;
|
|
116
|
+
this.scheduleSharedLiveQueryCleanup(entry);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
this.registerSharedLiveQueryEntry(entry);
|
|
122
|
+
this.scheduleSharedLiveQueryCleanup(entry);
|
|
123
|
+
return entry;
|
|
124
|
+
}
|
|
125
|
+
registerSharedLiveQueryEntry(entry) {
|
|
126
|
+
const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql) ?? [];
|
|
127
|
+
if (!matchingSqlEntries.includes(entry)) {
|
|
128
|
+
matchingSqlEntries.push(entry);
|
|
129
|
+
this.sharedLiveQueries.set(entry.sql, matchingSqlEntries);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
getSharedLiveQueriesSnapshot() {
|
|
133
|
+
const snapshot = [];
|
|
134
|
+
for (const entries of this.sharedLiveQueries.values()) for (const entry of entries) snapshot.push({
|
|
135
|
+
sql: entry.sql,
|
|
136
|
+
parameters: entry.parameters,
|
|
137
|
+
subscriberCount: entry.listeners.size
|
|
138
|
+
});
|
|
139
|
+
return snapshot;
|
|
140
|
+
}
|
|
141
|
+
scheduleSharedLiveQueryCleanup(entry) {
|
|
142
|
+
this.cancelSharedLiveQueryCleanup(entry);
|
|
143
|
+
entry.cleanupTimeout = setTimeout(() => {
|
|
144
|
+
entry.cleanupTimeout = null;
|
|
145
|
+
this.releaseSharedLiveQuery(entry);
|
|
146
|
+
}, 0);
|
|
147
|
+
}
|
|
148
|
+
cancelSharedLiveQueryCleanup(entry) {
|
|
149
|
+
if (entry.cleanupTimeout) {
|
|
150
|
+
clearTimeout(entry.cleanupTimeout);
|
|
151
|
+
entry.cleanupTimeout = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
releaseSharedLiveQuery(entry) {
|
|
155
|
+
if (entry.listeners.size > 0 || entry.unsubscribeFromLiveQuery) return;
|
|
156
|
+
const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql);
|
|
157
|
+
if (!matchingSqlEntries) return;
|
|
158
|
+
const nextEntries = matchingSqlEntries.filter((candidate) => candidate !== entry);
|
|
159
|
+
if (nextEntries.length > 0) this.sharedLiveQueries.set(entry.sql, nextEntries);
|
|
160
|
+
else this.sharedLiveQueries.delete(entry.sql);
|
|
161
|
+
}
|
|
162
|
+
subscribeToQueryChanges(params) {
|
|
163
|
+
const { sql, onDataChange } = params;
|
|
164
|
+
const tables = this.getTablesUsed(sql);
|
|
165
|
+
const readTables = /* @__PURE__ */ new Set();
|
|
166
|
+
for (const table of tables) if (!readTables.has(table.name)) readTables.add(table.name);
|
|
167
|
+
else if (table.isWrite) throw new Error("This query writes and reads from the same table. This may cause infinite loops.");
|
|
168
|
+
const notifyDataChange = createDebouncedCallback(() => {
|
|
169
|
+
onDataChange();
|
|
170
|
+
}, 30);
|
|
171
|
+
for (const table of readTables) this.eventTarget.addEventListener(`table:${table}`, notifyDataChange);
|
|
172
|
+
this.eventTarget.addEventListener("any-table-changed", notifyDataChange);
|
|
173
|
+
return { unsubscribe: () => {
|
|
174
|
+
for (const table of readTables) this.eventTarget.removeEventListener(`table:${table}`, notifyDataChange);
|
|
175
|
+
this.eventTarget.removeEventListener("any-table-changed", notifyDataChange);
|
|
176
|
+
} };
|
|
177
|
+
}
|
|
178
|
+
subscribeToTableChanges(table, onChanges) {
|
|
179
|
+
this.eventTarget.addEventListener(`table:${table}`, onChanges);
|
|
180
|
+
this.eventTarget.addEventListener("any-table-changed", onChanges);
|
|
181
|
+
return { unsubscribe: () => {
|
|
182
|
+
this.eventTarget.removeEventListener(`table:${table}`, onChanges);
|
|
183
|
+
this.eventTarget.removeEventListener("any-table-changed", onChanges);
|
|
184
|
+
} };
|
|
185
|
+
}
|
|
186
|
+
getTablesUsed(query) {
|
|
187
|
+
if (!this.tablesUsedStatement) this.tablesUsedStatement = this.db.prepare("select t.tbl_name as name, u.wr as isWrite from tables_used(?) as u inner join sqlite_master as t on t.name = u.name where u.schema = 'main'", { loggerLevel: "system" });
|
|
188
|
+
const tables = this.tablesUsedStatement.execute([query]);
|
|
189
|
+
if (tables.length === 0 && query.toLowerCase().includes("delete")) tables.push(...this.getClearedTables(query));
|
|
190
|
+
return tables;
|
|
191
|
+
}
|
|
192
|
+
getClearedTables(query) {
|
|
193
|
+
const operations = this.db.execute(`EXPLAIN ${query.split(";")[0]}`, { loggerLevel: "system" }).rows;
|
|
194
|
+
const clearedTablesRootPages = /* @__PURE__ */ new Set();
|
|
195
|
+
for (const operation of operations) if (operation.opcode === "Clear" && operation.p2 === 0) clearedTablesRootPages.add(operation.p1);
|
|
196
|
+
if (clearedTablesRootPages.size === 0) return [];
|
|
197
|
+
return this.db.execute(`select t.tbl_name as name, true as isWrite from sqlite_master as t where t.rootpage in (${Array.from(clearedTablesRootPages).join(",")})`, { loggerLevel: "system" }).rows;
|
|
198
|
+
}
|
|
199
|
+
addEventListener(type, listener) {
|
|
200
|
+
this.eventTarget.addEventListener(type, listener);
|
|
201
|
+
}
|
|
202
|
+
removeEventListener(type, listener) {
|
|
203
|
+
this.eventTarget.removeEventListener(type, listener);
|
|
204
|
+
}
|
|
205
|
+
getTableMutationVersion(table) {
|
|
206
|
+
return this.tableMutationVersions.get(table) ?? 0;
|
|
207
|
+
}
|
|
208
|
+
notifyTableSubscribers(tables = null) {
|
|
209
|
+
if (!tables) {
|
|
210
|
+
this.eventTarget.dispatchEvent("any-table-changed", void 0);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
for (const table of tables) this.eventTarget.dispatchEvent(`table:${table}`, void 0);
|
|
214
|
+
}
|
|
215
|
+
registerDbHooks() {
|
|
216
|
+
let updateQueue = /* @__PURE__ */ new Set();
|
|
217
|
+
this.driver.setHooks({
|
|
218
|
+
onUpdate: (table) => {
|
|
219
|
+
if (this.isDisposed) return;
|
|
220
|
+
updateQueue.add(table);
|
|
221
|
+
this.tableMutationVersions.set(table, ++this.mutationVersion);
|
|
222
|
+
},
|
|
223
|
+
onRollback: () => {
|
|
224
|
+
if (this.isDisposed || updateQueue.size === 0) return;
|
|
225
|
+
updateQueue.clear();
|
|
226
|
+
this.eventTarget.dispatchEvent("transaction-rolled-back", void 0);
|
|
227
|
+
},
|
|
228
|
+
onCommit: () => {
|
|
229
|
+
if (this.isDisposed || updateQueue.size === 0) return;
|
|
230
|
+
const tables = updateQueue;
|
|
231
|
+
updateQueue = /* @__PURE__ */ new Set();
|
|
232
|
+
this.eventTarget.dispatchEvent("transaction-committed", void 0);
|
|
233
|
+
queueMicrotask(() => {
|
|
234
|
+
if (!this.isDisposed) this.notifyTableSubscribers(tables);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
createSnapshot() {
|
|
240
|
+
const perf = startPerformanceLogger(this.logger);
|
|
241
|
+
const snapshot = this.db.createSnapshot();
|
|
242
|
+
perf.logEnd("createSnapshot", `snapshot size: ${snapshot.byteLength}`, "info");
|
|
243
|
+
return snapshot;
|
|
244
|
+
}
|
|
245
|
+
useSnapshot(snapshot) {
|
|
246
|
+
this.db.useSnapshot(snapshot);
|
|
247
|
+
this.mutationVersion++;
|
|
248
|
+
this.notifyTableSubscribers();
|
|
249
|
+
}
|
|
250
|
+
dispose() {
|
|
251
|
+
this.isDisposed = true;
|
|
252
|
+
for (const entries of this.sharedLiveQueries.values()) for (const entry of entries) {
|
|
253
|
+
this.cancelSharedLiveQueryCleanup(entry);
|
|
254
|
+
entry.unsubscribeFromLiveQuery?.();
|
|
255
|
+
entry.unsubscribeFromLiveQuery = null;
|
|
256
|
+
entry.listeners.clear();
|
|
257
|
+
}
|
|
258
|
+
this.sharedLiveQueries.clear();
|
|
259
|
+
this.liveQueryStatements.clear();
|
|
260
|
+
if (this.tablesUsedStatement) {
|
|
261
|
+
this.tablesUsedStatement.finalize();
|
|
262
|
+
this.tablesUsedStatement = null;
|
|
263
|
+
}
|
|
264
|
+
this.driver.setHooks(null);
|
|
265
|
+
this.db.close();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
function createDebouncedCallback(callback, delay) {
|
|
269
|
+
let timeout = null;
|
|
270
|
+
let shouldCallWithoutDelay = true;
|
|
271
|
+
return (...args) => {
|
|
272
|
+
if (shouldCallWithoutDelay) {
|
|
273
|
+
callback(...args);
|
|
274
|
+
shouldCallWithoutDelay = false;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const effect = () => {
|
|
278
|
+
timeout = null;
|
|
279
|
+
shouldCallWithoutDelay = true;
|
|
280
|
+
return callback(...args);
|
|
281
|
+
};
|
|
282
|
+
if (timeout) clearTimeout(timeout);
|
|
283
|
+
timeout = setTimeout(effect, delay);
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function parametersAreEqual(a, b) {
|
|
287
|
+
if (a === b) return true;
|
|
288
|
+
if (!a || !b) return false;
|
|
289
|
+
if (a.length !== b.length) return false;
|
|
290
|
+
for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
export { parametersAreEqual as n, SQLiteReactiveDb as t };
|
|
295
|
+
|
|
296
|
+
//# sourceMappingURL=sqlite-reactive-db-DLS1xRZp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-reactive-db-DLS1xRZp.js","names":[],"sources":["../src/memory-db/sqlite-reactive-db.ts"],"sourcesContent":["import { BoundMap } from \"../bound-map\";\nimport { type Logger, startPerformanceLogger } from \"../logger\";\nimport { type PreparedStatement, SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport type { SqliteDriver } from \"../sqlite-driver\";\nimport { createTypedEventTarget, type TypedEvent } from \"../utils\";\n\ntype TableName<Database> = keyof Database extends string ? keyof Database : never;\n\nexport type SQLiteReactiveDbOptions = {\n driver: SqliteDriver;\n snapshot?: Uint8Array<ArrayBufferLike>;\n logger: Logger;\n loggerPrefix?: string;\n};\n\ntype EventsMap = {\n \"transaction-committed\": undefined;\n \"transaction-rolled-back\": undefined;\n \"any-table-changed\": undefined;\n} & Record<`table:${string}`, void>;\n\ntype LiveQuery<TResult> = {\n getRows: () => TResult[];\n refresh: () => void;\n subscribe: (onchange: () => void) => () => void;\n};\n\nexport type SharedLiveQuery<TResult> = LiveQuery<TResult> & {\n readonly sql: string;\n readonly parameters: readonly unknown[];\n getSubscriberCount: () => number;\n};\n\ntype SharedLiveQueryEntry<TResult> = SharedLiveQuery<TResult> & {\n listeners: Set<() => void>;\n unsubscribeFromLiveQuery: (() => void) | null;\n cleanupTimeout: ReturnType<typeof setTimeout> | null;\n};\n\nexport type SharedLiveQuerySnapshot = {\n sql: string;\n parameters: readonly unknown[];\n subscriberCount: number;\n};\n\nexport class SQLiteReactiveDb<Database> {\n readonly db: SQLiteDbWrapper<Database>;\n private readonly driver: SqliteDriver;\n\n private readonly logger: Logger;\n\n private tablesUsedStatement: PreparedStatement<[string], { name: string; isWrite: boolean }> | null = null;\n\n private eventTarget = createTypedEventTarget<EventsMap>();\n private isDisposed = false;\n private mutationVersion = 0;\n private tableMutationVersions = new Map<string, number>();\n\n private constructor(opts: SQLiteReactiveDbOptions) {\n this.driver = opts.driver;\n this.logger = opts.logger;\n\n this.db = new SQLiteDbWrapper({\n driver: opts.driver,\n logger: this.logger,\n loggerPrefix: opts.loggerPrefix ?? \"memory\",\n });\n }\n\n static create<Database>(opts: SQLiteReactiveDbOptions) {\n const perf = startPerformanceLogger(opts.logger, \"system\");\n\n const db = new SQLiteReactiveDb<Database>(opts);\n\n if (opts.snapshot) {\n db.useSnapshot(opts.snapshot);\n }\n db.registerDbHooks();\n\n perf.logEnd(\"createSQLiteMemoryDb\", \"success\", \"system\");\n\n return db;\n }\n\n /**\n * When false, table mutation versions only advance after the writing statement\n * returns, so they cannot tell whether that statement wrote to a table.\n */\n get hasSynchronousHooks() {\n return this.driver.synchronousHooks;\n }\n\n private liveQueryStatements = new BoundMap<string, PreparedStatement<any[], unknown>>({\n maxSize: 100,\n onRemove(_, value) {\n value.finalize();\n },\n });\n\n private sharedLiveQueries = new Map<string, SharedLiveQueryEntry<unknown>[]>();\n\n createLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }) {\n let rowsVersion = 0;\n\n const fetchRows = (parameters: readonly unknown[]) => {\n let statement = this.liveQueryStatements.get(query.sql);\n if (!statement) {\n statement = this.db.prepare<any[], any>(query.sql);\n this.liveQueryStatements.set(query.sql, statement);\n }\n rowsVersion = this.mutationVersion;\n return statement.execute(parameters as any) as TResult[];\n };\n\n let rows: TResult[] | null = null;\n\n const getRows = () => {\n if (!rows) {\n rows = fetchRows(query.parameters);\n }\n return rows;\n };\n\n let subscriber: (() => void) | null = null;\n\n let lastParameters: readonly unknown[] = query.parameters;\n const refresh = (parameters?: readonly unknown[]) => {\n if (parameters) {\n lastParameters = parameters;\n }\n rows = fetchRows(lastParameters);\n subscriber?.();\n };\n\n const subscribe = (onchange: () => void) => {\n if (subscriber) {\n throw new Error(\"Subscriber already exists\");\n }\n\n subscriber = onchange;\n const subscription = this.subscribeToQueryChanges({\n sql: query.sql,\n onDataChange: refresh,\n });\n\n // Tables may have changed while nothing was subscribed, e.g. between React's render and commit.\n if (rows && this.mutationVersion !== rowsVersion) {\n refresh();\n }\n\n return () => {\n subscription.unsubscribe();\n subscriber = null;\n };\n };\n\n return { getRows, refresh, subscribe };\n }\n\n getSharedLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }) {\n const existingEntry = this.sharedLiveQueries\n .get(query.sql)\n ?.find((entry) => parametersAreEqual(entry.parameters, query.parameters));\n if (existingEntry) {\n if (existingEntry.listeners.size === 0) {\n this.scheduleSharedLiveQueryCleanup(existingEntry);\n }\n return existingEntry as SharedLiveQuery<TResult>;\n }\n\n const liveQuery = this.createLiveQuery<TResult>(query);\n const entry: SharedLiveQueryEntry<TResult> = {\n sql: query.sql,\n parameters: query.parameters,\n listeners: new Set(),\n unsubscribeFromLiveQuery: null,\n cleanupTimeout: null,\n getRows: () => liveQuery.getRows(),\n refresh: () => {\n liveQuery.refresh();\n },\n getSubscriberCount: () => entry.listeners.size,\n subscribe: (onchange) => {\n this.cancelSharedLiveQueryCleanup(entry);\n this.registerSharedLiveQueryEntry(entry as SharedLiveQueryEntry<unknown>);\n entry.listeners.add(onchange);\n\n if (!entry.unsubscribeFromLiveQuery) {\n entry.unsubscribeFromLiveQuery = liveQuery.subscribe(() => {\n for (const listener of entry.listeners) {\n listener();\n }\n });\n }\n\n return () => {\n entry.listeners.delete(onchange);\n\n if (entry.listeners.size === 0) {\n entry.unsubscribeFromLiveQuery?.();\n entry.unsubscribeFromLiveQuery = null;\n this.scheduleSharedLiveQueryCleanup(entry);\n }\n };\n },\n };\n\n this.registerSharedLiveQueryEntry(entry as SharedLiveQueryEntry<unknown>);\n\n // Evict the entry if no subscriber attaches by the next tick.\n this.scheduleSharedLiveQueryCleanup(entry as SharedLiveQueryEntry<unknown>);\n\n return entry;\n }\n\n private registerSharedLiveQueryEntry(entry: SharedLiveQueryEntry<unknown>) {\n const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql) ?? [];\n if (!matchingSqlEntries.includes(entry)) {\n matchingSqlEntries.push(entry);\n this.sharedLiveQueries.set(entry.sql, matchingSqlEntries);\n }\n }\n\n getSharedLiveQueriesSnapshot(): SharedLiveQuerySnapshot[] {\n const snapshot: SharedLiveQuerySnapshot[] = [];\n for (const entries of this.sharedLiveQueries.values()) {\n for (const entry of entries) {\n snapshot.push({\n sql: entry.sql,\n parameters: entry.parameters,\n subscriberCount: entry.listeners.size,\n });\n }\n }\n return snapshot;\n }\n\n private scheduleSharedLiveQueryCleanup(entry: SharedLiveQueryEntry<unknown>) {\n this.cancelSharedLiveQueryCleanup(entry);\n entry.cleanupTimeout = setTimeout(() => {\n entry.cleanupTimeout = null;\n this.releaseSharedLiveQuery(entry);\n }, 0);\n }\n\n private cancelSharedLiveQueryCleanup(entry: SharedLiveQueryEntry<unknown>) {\n if (entry.cleanupTimeout) {\n clearTimeout(entry.cleanupTimeout);\n entry.cleanupTimeout = null;\n }\n }\n\n private releaseSharedLiveQuery(entry: SharedLiveQueryEntry<unknown>) {\n if (entry.listeners.size > 0 || entry.unsubscribeFromLiveQuery) {\n return;\n }\n\n const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql);\n if (!matchingSqlEntries) {\n return;\n }\n\n const nextEntries = matchingSqlEntries.filter((candidate) => candidate !== entry);\n if (nextEntries.length > 0) {\n this.sharedLiveQueries.set(entry.sql, nextEntries);\n } else {\n this.sharedLiveQueries.delete(entry.sql);\n }\n }\n\n subscribeToQueryChanges(params: { sql: string; onDataChange: () => void }) {\n const { sql, onDataChange } = params;\n\n const tables = this.getTablesUsed(sql);\n const readTables = new Set<string>();\n for (const table of tables) {\n if (!readTables.has(table.name)) {\n readTables.add(table.name);\n } else if (table.isWrite) {\n throw new Error(\"This query writes and reads from the same table. This may cause infinite loops.\");\n }\n }\n\n const notifyDataChange = createDebouncedCallback(() => {\n onDataChange();\n }, 30);\n\n for (const table of readTables) {\n this.eventTarget.addEventListener(`table:${table}`, notifyDataChange);\n }\n this.eventTarget.addEventListener(\"any-table-changed\", notifyDataChange);\n\n return {\n unsubscribe: () => {\n for (const table of readTables) {\n this.eventTarget.removeEventListener(`table:${table}`, notifyDataChange);\n }\n this.eventTarget.removeEventListener(\"any-table-changed\", notifyDataChange);\n },\n };\n }\n\n subscribeToTableChanges(table: string, onChanges: () => void) {\n this.eventTarget.addEventListener(`table:${table}`, onChanges);\n this.eventTarget.addEventListener(\"any-table-changed\", onChanges);\n return {\n unsubscribe: () => {\n this.eventTarget.removeEventListener(`table:${table}`, onChanges);\n this.eventTarget.removeEventListener(\"any-table-changed\", onChanges);\n },\n };\n }\n\n getTablesUsed(query: string) {\n if (!this.tablesUsedStatement) {\n this.tablesUsedStatement = this.db.prepare<[string], { name: string; isWrite: boolean }>(\n \"select t.tbl_name as name, u.wr as isWrite from tables_used(?) as u inner join sqlite_master as t on t.name = u.name where u.schema = 'main'\",\n { loggerLevel: \"system\" },\n );\n }\n\n const tables = this.tablesUsedStatement.execute([query]);\n\n if (tables.length === 0 && query.toLowerCase().includes(\"delete\")) {\n // tables_used function does not work with delete queries that clear entire tables\n tables.push(...this.getClearedTables(query));\n }\n\n return tables;\n }\n\n private getClearedTables(query: string) {\n const operations = this.db.execute<{\n opcode: string;\n p1: number;\n p2: number;\n }>(`EXPLAIN ${query.split(\";\")[0]}`, { loggerLevel: \"system\" }).rows;\n\n const clearedTablesRootPages = new Set<number>();\n for (const operation of operations) {\n if (operation.opcode === \"Clear\" && operation.p2 === 0) {\n clearedTablesRootPages.add(operation.p1);\n }\n }\n\n if (clearedTablesRootPages.size === 0) {\n return [];\n }\n\n const tableNames = this.db.execute<{ name: string; isWrite: boolean }>(\n `select t.tbl_name as name, true as isWrite from sqlite_master as t where t.rootpage in (${Array.from(\n clearedTablesRootPages,\n ).join(\",\")})`,\n { loggerLevel: \"system\" },\n ).rows;\n\n return tableNames;\n }\n\n addEventListener<K extends keyof EventsMap>(type: K, listener: (event: TypedEvent<EventsMap[K]>) => void) {\n this.eventTarget.addEventListener(type, listener);\n }\n\n removeEventListener<K extends keyof EventsMap>(type: K, listener: (event: TypedEvent<EventsMap[K]>) => void) {\n this.eventTarget.removeEventListener(type, listener);\n }\n\n getTableMutationVersion(table: string) {\n return this.tableMutationVersions.get(table) ?? 0;\n }\n\n private notifyTableSubscribers(tables: (TableName<Database> | (string & {}))[] | Set<string> | null = null) {\n if (!tables) {\n this.eventTarget.dispatchEvent(\"any-table-changed\", undefined);\n return;\n }\n\n for (const table of tables) {\n this.eventTarget.dispatchEvent(`table:${table}`, undefined);\n }\n }\n\n private registerDbHooks() {\n let updateQueue = new Set<string>();\n\n // Asynchronous drivers can deliver hooks that were queued before dispose.\n this.driver.setHooks({\n onUpdate: (table) => {\n if (this.isDisposed) {\n return;\n }\n updateQueue.add(table);\n this.tableMutationVersions.set(table, ++this.mutationVersion);\n },\n onRollback: () => {\n if (this.isDisposed || updateQueue.size === 0) {\n return;\n }\n\n updateQueue.clear();\n this.eventTarget.dispatchEvent(\"transaction-rolled-back\", undefined);\n },\n onCommit: () => {\n if (this.isDisposed || updateQueue.size === 0) {\n return;\n }\n\n const tables = updateQueue;\n updateQueue = new Set<string>();\n this.eventTarget.dispatchEvent(\"transaction-committed\", undefined);\n\n queueMicrotask(() => {\n if (!this.isDisposed) {\n this.notifyTableSubscribers(tables);\n }\n });\n },\n });\n }\n\n createSnapshot() {\n const perf = startPerformanceLogger(this.logger);\n const snapshot = this.db.createSnapshot();\n perf.logEnd(\"createSnapshot\", `snapshot size: ${snapshot.byteLength}`, \"info\");\n\n return snapshot;\n }\n\n useSnapshot(snapshot: Uint8Array<ArrayBufferLike>) {\n this.db.useSnapshot(snapshot);\n this.mutationVersion++;\n this.notifyTableSubscribers();\n }\n\n dispose() {\n this.isDisposed = true;\n for (const entries of this.sharedLiveQueries.values()) {\n for (const entry of entries) {\n this.cancelSharedLiveQueryCleanup(entry);\n entry.unsubscribeFromLiveQuery?.();\n entry.unsubscribeFromLiveQuery = null;\n entry.listeners.clear();\n }\n }\n this.sharedLiveQueries.clear();\n this.liveQueryStatements.clear();\n if (this.tablesUsedStatement) {\n this.tablesUsedStatement.finalize();\n this.tablesUsedStatement = null;\n }\n this.driver.setHooks(null);\n this.db.close();\n }\n}\n\nfunction createDebouncedCallback<TArgs extends unknown[]>(callback: (...args: TArgs) => void, delay: number) {\n let timeout: unknown | null = null;\n let shouldCallWithoutDelay = true;\n\n return (...args: TArgs) => {\n if (shouldCallWithoutDelay) {\n callback(...args);\n shouldCallWithoutDelay = false;\n return;\n }\n\n const effect = () => {\n timeout = null;\n shouldCallWithoutDelay = true;\n return callback(...args);\n };\n\n if (timeout) {\n clearTimeout(timeout as any);\n }\n\n timeout = setTimeout(effect, delay);\n };\n}\n\nexport function parametersAreEqual(a: readonly unknown[] | undefined, b: readonly unknown[] | undefined): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) {\n return false;\n }\n }\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/db-id.ts
|
|
2
|
+
const dbIdRegex = /^[a-zA-Z][a-zA-Z\-0-9]{2,63}$/;
|
|
3
|
+
function validateDbId(dbId) {
|
|
4
|
+
if (!dbIdRegex.test(dbId)) throw new Error("Invalid dbId. Must be between 3 and 64 characters long and start with a letter.");
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/devtools-registry.ts
|
|
8
|
+
const devtoolsRegistrySymbol = Symbol.for("@sqlite-sync/devtools");
|
|
9
|
+
function createSQLiteSyncDevtoolsRegistry() {
|
|
10
|
+
const instances = /* @__PURE__ */ new Map();
|
|
11
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
12
|
+
let snapshot = { instances: [] };
|
|
13
|
+
const notify = () => {
|
|
14
|
+
for (const listener of listeners) listener();
|
|
15
|
+
};
|
|
16
|
+
const updateSnapshot = () => {
|
|
17
|
+
snapshot = { instances: Array.from(instances.values()) };
|
|
18
|
+
notify();
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
version: 1,
|
|
22
|
+
instances,
|
|
23
|
+
subscribe(listener) {
|
|
24
|
+
listeners.add(listener);
|
|
25
|
+
return () => {
|
|
26
|
+
listeners.delete(listener);
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
getSnapshot() {
|
|
30
|
+
return snapshot;
|
|
31
|
+
},
|
|
32
|
+
register(instance) {
|
|
33
|
+
instances.set(instance.instanceId, instance);
|
|
34
|
+
updateSnapshot();
|
|
35
|
+
let isUnregistered = false;
|
|
36
|
+
return () => {
|
|
37
|
+
if (isUnregistered) return;
|
|
38
|
+
isUnregistered = true;
|
|
39
|
+
if (!instances.delete(instance.instanceId)) return;
|
|
40
|
+
updateSnapshot();
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function getOrCreateSQLiteSyncDevtoolsRegistry() {
|
|
46
|
+
const registryGlobal = globalThis;
|
|
47
|
+
registryGlobal[devtoolsRegistrySymbol] ??= createSQLiteSyncDevtoolsRegistry();
|
|
48
|
+
return registryGlobal[devtoolsRegistrySymbol];
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/synced-db-database.ts
|
|
52
|
+
function createSqlExecutor(tx, unsafe) {
|
|
53
|
+
return {
|
|
54
|
+
execute: (query) => tx.execute(query, unsafe ? { skipAfterMutatingStatement: true } : void 0),
|
|
55
|
+
executeKysely: (factory) => tx.executeKysely(factory, unsafe ? { skipAfterMutatingStatement: true } : void 0)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** @internal Exported for focused facade tests, but not from the package entry point. */
|
|
59
|
+
function createSyncedDbDatabase(reactiveDb) {
|
|
60
|
+
const safeExecutor = createSqlExecutor(reactiveDb.db, false);
|
|
61
|
+
const unsafeExecutor = createSqlExecutor(reactiveDb.db, true);
|
|
62
|
+
return {
|
|
63
|
+
...safeExecutor,
|
|
64
|
+
unsafe: unsafeExecutor,
|
|
65
|
+
executeTransaction: (callback) => reactiveDb.db.executeTransaction((tx) => {
|
|
66
|
+
const safeTransactionExecutor = createSqlExecutor(tx, false);
|
|
67
|
+
const unsafeTransactionExecutor = createSqlExecutor(tx, true);
|
|
68
|
+
return callback({
|
|
69
|
+
...safeTransactionExecutor,
|
|
70
|
+
unsafe: unsafeTransactionExecutor,
|
|
71
|
+
sql: tx.sql.bind(tx)
|
|
72
|
+
});
|
|
73
|
+
}),
|
|
74
|
+
createLiveQuery: reactiveDb.createLiveQuery.bind(reactiveDb),
|
|
75
|
+
getSharedLiveQuery: reactiveDb.getSharedLiveQuery.bind(reactiveDb)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { getOrCreateSQLiteSyncDevtoolsRegistry as n, validateDbId as r, createSyncedDbDatabase as t };
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=synced-db-database-B5M7MrBD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synced-db-database-B5M7MrBD.js","names":[],"sources":["../src/db-id.ts","../src/devtools-registry.ts","../src/synced-db-database.ts"],"sourcesContent":["const dbIdRegex = /^[a-zA-Z][a-zA-Z\\-0-9]{2,63}$/;\n\nexport function validateDbId(dbId: string) {\n if (!dbIdRegex.test(dbId)) {\n throw new Error(\"Invalid dbId. Must be between 3 and 64 characters long and start with a letter.\");\n }\n}\n","import type { SyncedDb } from \"./sync-db\";\n\nconst devtoolsRegistrySymbol = Symbol.for(\"@sqlite-sync/devtools\");\n\nexport type SQLiteSyncDevtoolsInstance = {\n instanceId: string;\n dbId: string;\n createdAt: number;\n instance: SyncedDb<any>;\n};\n\nexport type SQLiteSyncDevtoolsSnapshot = {\n instances: readonly SQLiteSyncDevtoolsInstance[];\n};\n\nexport type SQLiteSyncDevtoolsRegistry = {\n version: 1;\n instances: Map<string, SQLiteSyncDevtoolsInstance>;\n subscribe(listener: () => void): () => void;\n getSnapshot(): SQLiteSyncDevtoolsSnapshot;\n register(instance: SQLiteSyncDevtoolsInstance): () => void;\n};\n\ntype RegistryGlobal = typeof globalThis & {\n [key: symbol]: SQLiteSyncDevtoolsRegistry | undefined;\n};\n\nfunction createSQLiteSyncDevtoolsRegistry(): SQLiteSyncDevtoolsRegistry {\n const instances = new Map<string, SQLiteSyncDevtoolsInstance>();\n const listeners = new Set<() => void>();\n let snapshot: SQLiteSyncDevtoolsSnapshot = {\n instances: [],\n };\n\n const notify = () => {\n for (const listener of listeners) {\n listener();\n }\n };\n\n const updateSnapshot = () => {\n snapshot = {\n instances: Array.from(instances.values()),\n };\n notify();\n };\n\n return {\n version: 1,\n instances,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n getSnapshot() {\n return snapshot;\n },\n register(instance) {\n instances.set(instance.instanceId, instance);\n updateSnapshot();\n\n let isUnregistered = false;\n return () => {\n if (isUnregistered) return;\n isUnregistered = true;\n\n if (!instances.delete(instance.instanceId)) return;\n updateSnapshot();\n };\n },\n };\n}\n\nexport function getOrCreateSQLiteSyncDevtoolsRegistry(): SQLiteSyncDevtoolsRegistry {\n const registryGlobal = globalThis as RegistryGlobal;\n\n registryGlobal[devtoolsRegistrySymbol] ??= createSQLiteSyncDevtoolsRegistry();\n\n return registryGlobal[devtoolsRegistrySymbol];\n}\n","import type { Compilable, CompiledQuery } from \"kysely\";\nimport type { SharedLiveQuery, SQLiteReactiveDb } from \"./memory-db/sqlite-reactive-db\";\nimport type {\n ExecuteParams,\n ExecuteResult,\n KyselyQueryFactory,\n QueryBuilderOutput,\n SQLiteTransactionWrapper,\n} from \"./sqlite-db-wrapper\";\n\n/** SQL execution that participates in CRDT intent draining. */\nexport type SyncedDbSqlExecutor<Database> = {\n execute<TResult = unknown>(query: ExecuteParams | string | CompiledQuery<TResult>): ExecuteResult<TResult>;\n executeKysely<TQuery extends Compilable<TResult>, TResult = QueryBuilderOutput<TQuery>>(\n factory: KyselyQueryFactory<Database, TQuery, TResult>,\n ): ExecuteResult<TResult>;\n};\n\n/**\n * SQL execution that deliberately bypasses CRDT intent draining.\n *\n * Mutating a synced view through this executor can leave undrained intents and\n * must only be done by sqlite-sync internals or recovery tooling.\n */\nexport type UnsafeSyncedDbSqlExecutor<Database> = SyncedDbSqlExecutor<Database>;\n\nexport type SyncedDbTransaction<Database> = SyncedDbSqlExecutor<Database> & {\n unsafe: UnsafeSyncedDbSqlExecutor<Database>;\n sql<TResult = unknown>(\n templateOrString: TemplateStringsArray | string,\n ...parameters: unknown[]\n ): ExecuteResult<TResult>;\n};\n\nexport type SyncedDbDatabase<Database> = SyncedDbSqlExecutor<Database> & {\n unsafe: UnsafeSyncedDbSqlExecutor<Database>;\n executeTransaction<TResult>(callback: (tx: SyncedDbTransaction<Database>) => TResult): TResult;\n createLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }): {\n getRows: () => TResult[];\n refresh: (parameters?: readonly unknown[]) => void;\n subscribe: (onchange: () => void) => () => void;\n };\n getSharedLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }): SharedLiveQuery<TResult>;\n};\n\nfunction createSqlExecutor<Database>(\n tx: Pick<SQLiteTransactionWrapper<Database>, \"execute\" | \"executeKysely\">,\n unsafe: boolean,\n): SyncedDbSqlExecutor<Database> {\n return {\n execute: (query) => tx.execute(query, unsafe ? { skipAfterMutatingStatement: true } : undefined),\n executeKysely: (factory) => tx.executeKysely(factory, unsafe ? { skipAfterMutatingStatement: true } : undefined),\n };\n}\n\n/** @internal Exported for focused facade tests, but not from the package entry point. */\nexport function createSyncedDbDatabase<Database>(reactiveDb: SQLiteReactiveDb<Database>): SyncedDbDatabase<Database> {\n const safeExecutor = createSqlExecutor(reactiveDb.db, false);\n const unsafeExecutor = createSqlExecutor(reactiveDb.db, true);\n\n return {\n ...safeExecutor,\n unsafe: unsafeExecutor,\n executeTransaction: (callback) =>\n reactiveDb.db.executeTransaction((tx) => {\n const safeTransactionExecutor = createSqlExecutor(tx, false);\n const unsafeTransactionExecutor = createSqlExecutor(tx, true);\n return callback({\n ...safeTransactionExecutor,\n unsafe: unsafeTransactionExecutor,\n sql: tx.sql.bind(tx),\n });\n }),\n createLiveQuery: reactiveDb.createLiveQuery.bind(reactiveDb),\n getSharedLiveQuery: reactiveDb.getSharedLiveQuery.bind(reactiveDb),\n };\n}\n"],"mappings":";AAAA,MAAM,YAAY;AAElB,SAAgB,aAAa,MAAc;CACzC,IAAI,CAAC,UAAU,KAAK,IAAI,GACtB,MAAM,IAAI,MAAM,iFAAiF;AAErG;;;ACJA,MAAM,yBAAyB,OAAO,IAAI,uBAAuB;AAyBjE,SAAS,mCAA+D;CACtE,MAAM,4BAAY,IAAI,IAAwC;CAC9D,MAAM,4BAAY,IAAI,IAAgB;CACtC,IAAI,WAAuC,EACzC,WAAW,CAAC,EACd;CAEA,MAAM,eAAe;EACnB,KAAK,MAAM,YAAY,WACrB,SAAS;CAEb;CAEA,MAAM,uBAAuB;EAC3B,WAAW,EACT,WAAW,MAAM,KAAK,UAAU,OAAO,CAAC,EAC1C;EACA,OAAO;CACT;CAEA,OAAO;EACL,SAAS;EACT;EACA,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,aAAa;IACX,UAAU,OAAO,QAAQ;GAC3B;EACF;EACA,cAAc;GACZ,OAAO;EACT;EACA,SAAS,UAAU;GACjB,UAAU,IAAI,SAAS,YAAY,QAAQ;GAC3C,eAAe;GAEf,IAAI,iBAAiB;GACrB,aAAa;IACX,IAAI,gBAAgB;IACpB,iBAAiB;IAEjB,IAAI,CAAC,UAAU,OAAO,SAAS,UAAU,GAAG;IAC5C,eAAe;GACjB;EACF;CACF;AACF;AAEA,SAAgB,wCAAoE;CAClF,MAAM,iBAAiB;CAEvB,eAAe,4BAA4B,iCAAiC;CAE5E,OAAO,eAAe;AACxB;;;ACpCA,SAAS,kBACP,IACA,QAC+B;CAC/B,OAAO;EACL,UAAU,UAAU,GAAG,QAAQ,OAAO,SAAS,EAAE,4BAA4B,KAAK,IAAI,KAAA,CAAS;EAC/F,gBAAgB,YAAY,GAAG,cAAc,SAAS,SAAS,EAAE,4BAA4B,KAAK,IAAI,KAAA,CAAS;CACjH;AACF;;AAGA,SAAgB,uBAAiC,YAAoE;CACnH,MAAM,eAAe,kBAAkB,WAAW,IAAI,KAAK;CAC3D,MAAM,iBAAiB,kBAAkB,WAAW,IAAI,IAAI;CAE5D,OAAO;EACL,GAAG;EACH,QAAQ;EACR,qBAAqB,aACnB,WAAW,GAAG,oBAAoB,OAAO;GACvC,MAAM,0BAA0B,kBAAkB,IAAI,KAAK;GAC3D,MAAM,4BAA4B,kBAAkB,IAAI,IAAI;GAC5D,OAAO,SAAS;IACd,GAAG;IACH,QAAQ;IACR,KAAK,GAAG,IAAI,KAAK,EAAE;GACrB,CAAC;EACH,CAAC;EACH,iBAAiB,WAAW,gBAAgB,KAAK,UAAU;EAC3D,oBAAoB,WAAW,mBAAmB,KAAK,UAAU;CACnE;AACF"}
|