muya 2.4.4 → 2.4.5
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/cjs/index.js +1 -1
- package/esm/scheduler.js +1 -1
- package/esm/sqlite/create-sqlite.js +1 -1
- package/esm/sqlite/index.js +1 -1
- package/esm/sqlite/table/table.js +2 -2
- package/esm/sqlite/use-sqlite-count.js +1 -0
- package/esm/sqlite/use-sqlite.js +1 -1
- package/package.json +1 -1
- package/src/scheduler.ts +8 -2
- package/src/sqlite/__tests__/use-slite-count.test.tsx +96 -0
- package/src/sqlite/__tests__/use-sqlite.test.tsx +123 -40
- package/src/sqlite/create-sqlite.ts +43 -271
- package/src/sqlite/index.ts +1 -1
- package/src/sqlite/table/table.ts +3 -3
- package/src/sqlite/table/table.types.ts +9 -1
- package/src/sqlite/use-sqlite-count.ts +69 -0
- package/src/sqlite/use-sqlite.ts +149 -90
- package/types/scheduler.d.ts +1 -1
- package/types/sqlite/create-sqlite.d.ts +6 -12
- package/types/sqlite/index.d.ts +1 -1
- package/types/sqlite/table/table.d.ts +1 -1
- package/types/sqlite/table/table.types.d.ts +8 -1
- package/types/sqlite/use-sqlite-count.d.ts +17 -0
- package/types/sqlite/use-sqlite.d.ts +10 -10
|
@@ -1,26 +1,21 @@
|
|
|
1
|
-
/* eslint-disable sonarjs/redundant-type-aliases */
|
|
2
1
|
import { STATE_SCHEDULER } from '../create'
|
|
3
2
|
import { getId } from '../utils/id'
|
|
4
|
-
import { shallow } from '../utils/shallow'
|
|
5
|
-
import { selectSql, type CreateState } from './select-sql'
|
|
6
3
|
import type { Backend } from './table'
|
|
7
|
-
import { createTable
|
|
4
|
+
import { createTable } from './table/table'
|
|
8
5
|
import type { DbOptions, DocType, Key, MutationResult, SearchOptions, Table } from './table/table.types'
|
|
9
6
|
import type { Where } from './table/where'
|
|
10
7
|
|
|
11
|
-
type SearchId = string
|
|
12
|
-
|
|
13
8
|
export interface CreateSqliteOptions<Document extends DocType> extends Omit<DbOptions<Document>, 'backend'> {
|
|
14
9
|
readonly backend: Backend | Promise<Backend>
|
|
15
10
|
}
|
|
16
11
|
|
|
17
|
-
export interface
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
readonly getSnapshot: (searchId: SearchId) => Document[]
|
|
22
|
-
readonly refresh: (searchId: SearchId) => Promise<void>
|
|
12
|
+
export interface MutationItems {
|
|
13
|
+
mutations?: MutationResult[]
|
|
14
|
+
removedAll?: boolean
|
|
15
|
+
}
|
|
23
16
|
|
|
17
|
+
export interface SyncTable<Document extends DocType> {
|
|
18
|
+
readonly subscribe: (listener: (mutation: MutationItems) => void) => () => void
|
|
24
19
|
readonly set: (document: Document) => Promise<MutationResult>
|
|
25
20
|
readonly batchSet: (documents: Document[]) => Promise<MutationResult[]>
|
|
26
21
|
readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>
|
|
@@ -29,21 +24,7 @@ export interface SyncTable<Document extends DocType> {
|
|
|
29
24
|
readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>
|
|
30
25
|
readonly count: (options?: { where?: Where<Document> }) => Promise<number>
|
|
31
26
|
readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>
|
|
32
|
-
readonly
|
|
33
|
-
readonly next: (searchId: SearchId) => Promise<boolean>
|
|
34
|
-
readonly clear: (searchId: SearchId) => void
|
|
35
|
-
readonly load: (searchId: SearchId) => void
|
|
36
|
-
|
|
37
|
-
readonly select: <Params extends unknown[]>(
|
|
38
|
-
compute: (...args: Params) => SearchOptions<Document>,
|
|
39
|
-
) => CreateState<Document, Params>
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface CachedItem<Document extends DocType> {
|
|
43
|
-
items: Document[]
|
|
44
|
-
keys: Set<Key>
|
|
45
|
-
options?: SearchOptions<Document, unknown>
|
|
46
|
-
wasInitialized: boolean
|
|
27
|
+
readonly clear: () => void
|
|
47
28
|
}
|
|
48
29
|
|
|
49
30
|
/**
|
|
@@ -52,17 +33,6 @@ interface CachedItem<Document extends DocType> {
|
|
|
52
33
|
* @returns A SyncTable instance with methods to interact with the underlying Table and manage reactive searches
|
|
53
34
|
*/
|
|
54
35
|
export function createSqliteState<Document extends DocType>(options: CreateSqliteOptions<Document>): SyncTable<Document> {
|
|
55
|
-
const id = getId()
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Get a unique schedule ID for a search ID
|
|
59
|
-
* @param searchId The search ID
|
|
60
|
-
* @returns The unique schedule ID
|
|
61
|
-
*/
|
|
62
|
-
function getScheduleId(searchId: SearchId) {
|
|
63
|
-
return `state-${id}-search-${searchId}`
|
|
64
|
-
}
|
|
65
|
-
|
|
66
36
|
let cachedTable: Table<Document> | undefined
|
|
67
37
|
/**
|
|
68
38
|
* Get or create the underlying table
|
|
@@ -77,213 +47,74 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
77
47
|
return cachedTable
|
|
78
48
|
}
|
|
79
49
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const cachedData = new Map<SearchId, CachedItem<Document>>()
|
|
86
|
-
const listeners = new Map<SearchId, Map<string, () => void>>()
|
|
87
|
-
const iterators = new Map<SearchId, AsyncIterableIterator<NextResult>>()
|
|
88
|
-
|
|
89
|
-
interface GetNextBase {
|
|
90
|
-
readonly newItems?: Document[]
|
|
91
|
-
readonly isOk: boolean
|
|
92
|
-
readonly isDone?: boolean
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
interface GetNextTrue extends GetNextBase {
|
|
96
|
-
readonly isOk: true
|
|
97
|
-
readonly newItems: Document[]
|
|
98
|
-
}
|
|
99
|
-
interface GetNextFalse extends GetNextBase {
|
|
100
|
-
readonly isOk: false
|
|
101
|
-
}
|
|
102
|
-
type GetNext = GetNextTrue | GetNextFalse
|
|
103
|
-
/**
|
|
104
|
-
* Next step in the iterator
|
|
105
|
-
* @param searchId The search ID
|
|
106
|
-
* @param data The data items to process
|
|
107
|
-
* @param shouldReset Whether to reset the items
|
|
108
|
-
* @returns boolean indicating if new items were added
|
|
109
|
-
*/
|
|
110
|
-
async function getNext(searchId: SearchId, data: CachedItem<Document>, shouldReset: boolean): Promise<GetNext> {
|
|
111
|
-
const iterator = iterators.get(searchId)
|
|
112
|
-
const { options: nextOptions = {} } = data
|
|
113
|
-
const { stepSize = DEFAULT_STEP_SIZE } = nextOptions
|
|
114
|
-
if (!iterator) return { isOk: false }
|
|
115
|
-
const newItems: Document[] = []
|
|
116
|
-
if (shouldReset) {
|
|
117
|
-
data.keys.clear()
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
for (let index = 0; index < stepSize; index++) {
|
|
121
|
-
const result = await iterator.next()
|
|
122
|
-
if (result.done) {
|
|
123
|
-
iterators.delete(searchId)
|
|
124
|
-
break
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!data.keys.has(String(result.value.key))) {
|
|
128
|
-
newItems.push(result.value.document)
|
|
129
|
-
data.keys.add(String(result.value.key))
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (newItems.length === 0) return { isOk: true, newItems: shouldReset ? [] : data.items, isDone: true }
|
|
134
|
-
if (shallow(data.items, newItems)) return { isOk: false }
|
|
135
|
-
if (shouldReset) return { isOk: true, newItems }
|
|
136
|
-
return { newItems: [...data.items, ...newItems], isOk: true }
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Notify listeners of up dates
|
|
141
|
-
* @param searchId The search ID to notify
|
|
142
|
-
*/
|
|
143
|
-
function notifyListeners(searchId: SearchId) {
|
|
144
|
-
const searchListeners = listeners.get(searchId)
|
|
145
|
-
if (searchListeners) {
|
|
146
|
-
for (const [, listener] of searchListeners) {
|
|
147
|
-
listener()
|
|
50
|
+
const id = getId()
|
|
51
|
+
STATE_SCHEDULER.add(id, {
|
|
52
|
+
onScheduleDone(unknownItems) {
|
|
53
|
+
if (!unknownItems) {
|
|
54
|
+
return
|
|
148
55
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
* @param searchId The search ID to refresh
|
|
155
|
-
*/
|
|
156
|
-
async function refreshCache(searchId: SearchId) {
|
|
157
|
-
const table = await getTable()
|
|
158
|
-
const data = cachedData.get(searchId)
|
|
159
|
-
if (!data) return
|
|
160
|
-
const { options: refreshOptions } = data
|
|
161
|
-
const iterator = table.search({ ...refreshOptions, select: (document, { rowId, key }) => ({ document, rowId, key }) })
|
|
162
|
-
iterators.set(searchId, iterator)
|
|
163
|
-
const { isOk, newItems } = await getNext(searchId, data, true)
|
|
164
|
-
if (isOk) {
|
|
165
|
-
data.items = newItems
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Refresh the data and notify listeners
|
|
170
|
-
* @param searchId The search ID to refresh
|
|
171
|
-
*/
|
|
172
|
-
async function refresh(searchId: SearchId) {
|
|
173
|
-
await refreshCache(searchId)
|
|
174
|
-
notifyListeners(searchId)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Handle changes to the data
|
|
179
|
-
* @param mutationResult The mutation result
|
|
180
|
-
* @returns A set of search IDs that need to be updated
|
|
181
|
-
*/
|
|
182
|
-
function getChangedKeys(mutationResult: MutationResult) {
|
|
183
|
-
const { key, op } = mutationResult
|
|
184
|
-
// find all cached data with key
|
|
185
|
-
const searchIds = new Set<SearchId>()
|
|
186
|
-
for (const [searchId, { keys }] of cachedData) {
|
|
187
|
-
switch (op) {
|
|
188
|
-
case 'delete':
|
|
189
|
-
case 'update': {
|
|
190
|
-
if (keys.has(String(key))) {
|
|
191
|
-
searchIds.add(searchId)
|
|
192
|
-
}
|
|
193
|
-
break
|
|
56
|
+
const items = unknownItems as MutationItems[]
|
|
57
|
+
const merged: MutationItems = {}
|
|
58
|
+
for (const item of items) {
|
|
59
|
+
if (item.removedAll) {
|
|
60
|
+
merged.removedAll = true
|
|
194
61
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
62
|
+
if (item.mutations) {
|
|
63
|
+
if (!merged.mutations) {
|
|
64
|
+
merged.mutations = []
|
|
65
|
+
}
|
|
66
|
+
merged.mutations.push(...item.mutations)
|
|
199
67
|
}
|
|
200
68
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Handle multiple changes
|
|
207
|
-
* @param mutationResults The array of mutation results
|
|
208
|
-
*/
|
|
209
|
-
async function handleChanges(mutationResults: MutationResult[]) {
|
|
210
|
-
const updateSearchIds = new Set<SearchId>()
|
|
211
|
-
for (const mutationResult of mutationResults) {
|
|
212
|
-
const searchIds = getChangedKeys(mutationResult)
|
|
213
|
-
for (const searchId of searchIds) {
|
|
214
|
-
updateSearchIds.add(searchId)
|
|
69
|
+
for (const listener of listeners) {
|
|
70
|
+
listener(merged)
|
|
215
71
|
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
for (const searchId of updateSearchIds) {
|
|
219
|
-
const scheduleId = getScheduleId(searchId)
|
|
220
|
-
STATE_SCHEDULER.schedule(scheduleId, {})
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const clearSchedulers = new Set<() => void>()
|
|
72
|
+
},
|
|
73
|
+
})
|
|
225
74
|
|
|
226
75
|
/**
|
|
227
|
-
*
|
|
228
|
-
* @param
|
|
229
|
-
* @param registerDataOptions Optional search options
|
|
230
|
-
* @returns The data items for the search ID
|
|
76
|
+
* Notify all subscribers of changes
|
|
77
|
+
* @param item The mutation items to notify subscribers about
|
|
231
78
|
*/
|
|
232
|
-
function
|
|
233
|
-
|
|
234
|
-
const cachedItem: CachedItem<Document> = {
|
|
235
|
-
items: [],
|
|
236
|
-
options: registerDataOptions,
|
|
237
|
-
keys: new Set(),
|
|
238
|
-
wasInitialized: false,
|
|
239
|
-
}
|
|
240
|
-
cachedData.set(searchId, cachedItem)
|
|
241
|
-
}
|
|
242
|
-
const cachedItem = cachedData.get(searchId)!
|
|
243
|
-
|
|
244
|
-
if (registerDataOptions) {
|
|
245
|
-
cachedItem.options = registerDataOptions
|
|
246
|
-
}
|
|
247
|
-
return cachedItem
|
|
79
|
+
function handleChanges(item: MutationItems) {
|
|
80
|
+
STATE_SCHEDULER.schedule(id, item)
|
|
248
81
|
}
|
|
249
82
|
|
|
83
|
+
const listeners = new Set<(mutation: MutationItems) => void>()
|
|
84
|
+
|
|
250
85
|
const state: SyncTable<Document> = {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!cachedItem.wasInitialized) {
|
|
255
|
-
cachedItem.wasInitialized = true
|
|
256
|
-
const scheduleId = getScheduleId(searchId)
|
|
257
|
-
STATE_SCHEDULER.schedule(scheduleId, { searchId })
|
|
258
|
-
}
|
|
86
|
+
subscribe(listener) {
|
|
87
|
+
listeners.add(listener)
|
|
88
|
+
return () => listeners.delete(listener)
|
|
259
89
|
},
|
|
260
|
-
clear(
|
|
261
|
-
|
|
90
|
+
clear() {
|
|
91
|
+
cachedTable?.clear()
|
|
92
|
+
handleChanges({ removedAll: true })
|
|
262
93
|
},
|
|
263
94
|
async set(document) {
|
|
264
95
|
const table = await getTable()
|
|
265
96
|
const changes = await table.set(document)
|
|
266
|
-
|
|
97
|
+
handleChanges({ mutations: [changes] })
|
|
267
98
|
return changes
|
|
268
99
|
},
|
|
269
100
|
async batchSet(documents) {
|
|
270
101
|
const table = await getTable()
|
|
271
102
|
const changes = await table.batchSet(documents)
|
|
272
|
-
|
|
103
|
+
handleChanges({ mutations: changes })
|
|
273
104
|
return changes
|
|
274
105
|
},
|
|
275
106
|
async delete(key) {
|
|
276
107
|
const table = await getTable()
|
|
277
108
|
const changes = await table.delete(key)
|
|
278
109
|
if (changes) {
|
|
279
|
-
|
|
110
|
+
handleChanges({ mutations: [changes] })
|
|
280
111
|
}
|
|
281
112
|
return changes
|
|
282
113
|
},
|
|
283
114
|
async deleteBy(where) {
|
|
284
115
|
const table = await getTable()
|
|
285
116
|
const changes = await table.deleteBy(where)
|
|
286
|
-
|
|
117
|
+
handleChanges({ mutations: changes })
|
|
287
118
|
return changes
|
|
288
119
|
},
|
|
289
120
|
async get(key, selector) {
|
|
@@ -300,65 +131,6 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
300
131
|
const table = await getTable()
|
|
301
132
|
return await table.count(countOptions)
|
|
302
133
|
},
|
|
303
|
-
|
|
304
|
-
updateSearchOptions(searchId, updateSearchOptions) {
|
|
305
|
-
const data = registerData(searchId, updateSearchOptions)
|
|
306
|
-
data.options = updateSearchOptions
|
|
307
|
-
const scheduleId = getScheduleId(searchId)
|
|
308
|
-
STATE_SCHEDULER.schedule(scheduleId, { searchId })
|
|
309
|
-
},
|
|
310
|
-
|
|
311
|
-
subscribe(searchId, componentId, listener) {
|
|
312
|
-
const scheduleId = getScheduleId(searchId)
|
|
313
|
-
const clearScheduler = STATE_SCHEDULER.add(scheduleId, {
|
|
314
|
-
onScheduleDone() {
|
|
315
|
-
refresh(searchId)
|
|
316
|
-
},
|
|
317
|
-
})
|
|
318
|
-
// console.log('Subscribing to searchId:', searchId)
|
|
319
|
-
this.load(searchId)
|
|
320
|
-
clearSchedulers.add(clearScheduler)
|
|
321
|
-
|
|
322
|
-
if (!listeners.has(searchId)) {
|
|
323
|
-
listeners.set(searchId, new Map())
|
|
324
|
-
}
|
|
325
|
-
const searchListeners = listeners.get(searchId)!
|
|
326
|
-
searchListeners.set(componentId, listener)
|
|
327
|
-
|
|
328
|
-
return () => {
|
|
329
|
-
searchListeners.delete(componentId)
|
|
330
|
-
if (searchListeners.size === 0) {
|
|
331
|
-
listeners.delete(searchId)
|
|
332
|
-
}
|
|
333
|
-
clearScheduler()
|
|
334
|
-
}
|
|
335
|
-
},
|
|
336
|
-
getSnapshot(searchId) {
|
|
337
|
-
const data = registerData(searchId)
|
|
338
|
-
return data.items
|
|
339
|
-
},
|
|
340
|
-
refresh,
|
|
341
|
-
destroy() {
|
|
342
|
-
for (const clear of clearSchedulers) clear()
|
|
343
|
-
cachedData.clear()
|
|
344
|
-
listeners.clear()
|
|
345
|
-
},
|
|
346
|
-
async next(searchId) {
|
|
347
|
-
const data = cachedData.get(searchId)
|
|
348
|
-
if (data) {
|
|
349
|
-
const hasNext = await getNext(searchId, data, false)
|
|
350
|
-
if (hasNext.isOk) {
|
|
351
|
-
data.items = hasNext.newItems
|
|
352
|
-
notifyListeners(searchId)
|
|
353
|
-
}
|
|
354
|
-
return hasNext.isDone ?? false
|
|
355
|
-
}
|
|
356
|
-
return false
|
|
357
|
-
},
|
|
358
|
-
|
|
359
|
-
select(compute) {
|
|
360
|
-
return selectSql(state, compute)
|
|
361
|
-
},
|
|
362
134
|
}
|
|
363
135
|
|
|
364
136
|
return state
|
package/src/sqlite/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { Where } from './where'
|
|
|
9
9
|
import { getWhereQuery } from './where'
|
|
10
10
|
|
|
11
11
|
const DELETE_IN_CHUNK = 500
|
|
12
|
-
export const
|
|
12
|
+
export const DEFAULT_PAGE_SIZE = 100
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Convert a dot-separated path to a JSON path
|
|
@@ -239,7 +239,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
|
|
|
239
239
|
offset = 0,
|
|
240
240
|
where,
|
|
241
241
|
select = (document) => document as unknown as Selected,
|
|
242
|
-
|
|
242
|
+
pageSize = DEFAULT_PAGE_SIZE,
|
|
243
243
|
} = options
|
|
244
244
|
|
|
245
245
|
const whereSql = getWhereQuery<Document>(where, tableName)
|
|
@@ -256,7 +256,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
|
|
|
256
256
|
query += hasUserKey ? ` ORDER BY key COLLATE NOCASE ${order.toUpperCase()}` : ` ORDER BY rowid ${order.toUpperCase()}`
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
const batchLimit = limit ? Math.min(
|
|
259
|
+
const batchLimit = limit ? Math.min(pageSize, limit - yielded) : pageSize
|
|
260
260
|
query += ` LIMIT ${batchLimit} OFFSET ${currentOffset}`
|
|
261
261
|
|
|
262
262
|
const results = await backend.select<Array<{ rowid: number; data: string }>>(query)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { SqlSeachOptions } from '../select-sql'
|
|
2
1
|
import type { Backend } from './backend'
|
|
3
2
|
import type { FtsTokenizerOptions } from './tokenizer'
|
|
4
3
|
import type { Where } from './where'
|
|
@@ -7,6 +6,15 @@ import type { Where } from './where'
|
|
|
7
6
|
export type DocType = { [key: string]: any }
|
|
8
7
|
export type KeyTypeAvailable = 'string' | 'number'
|
|
9
8
|
|
|
9
|
+
export interface SqlSeachOptions<Document extends DocType> {
|
|
10
|
+
readonly sortBy?: DotPath<Document>
|
|
11
|
+
readonly order?: 'asc' | 'desc'
|
|
12
|
+
readonly limit?: number
|
|
13
|
+
readonly offset?: number
|
|
14
|
+
readonly where?: Where<Document>
|
|
15
|
+
readonly pageSize?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
// Expand all nested keys into dot-paths
|
|
11
19
|
export type DotPrefix<T extends string> = T extends '' ? '' : `.${T}`
|
|
12
20
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useReducer, useRef, type DependencyList } from 'react'
|
|
2
|
+
import type { SyncTable } from './create-sqlite'
|
|
3
|
+
import type { DocType } from './table/table.types'
|
|
4
|
+
import type { Where } from './table/where'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A React hook to count the number of items in a SyncTable reactively.
|
|
8
|
+
* It updates the count when items are inserted or deleted, but ignores updates.
|
|
9
|
+
* Supports filtering the count using a `where` clause.
|
|
10
|
+
* @param state The SyncTable instance to observe.
|
|
11
|
+
* @param options Optional filtering options.
|
|
12
|
+
* @param options.where A `where` clause to filter the count.
|
|
13
|
+
* @param deps Dependency list to control when to re-run the effect.
|
|
14
|
+
* @returns The current count of items in the table.
|
|
15
|
+
*/
|
|
16
|
+
export function useSqliteCount<Document extends DocType>(
|
|
17
|
+
state: SyncTable<Document>,
|
|
18
|
+
options: { where?: Where<Document> } = {},
|
|
19
|
+
deps: DependencyList = [],
|
|
20
|
+
): number {
|
|
21
|
+
const countRef = useRef(0)
|
|
22
|
+
const [, rerender] = useReducer((c: number) => c + 1, 0)
|
|
23
|
+
|
|
24
|
+
const updateCount = useCallback(async () => {
|
|
25
|
+
const newCount = await state.count(options)
|
|
26
|
+
countRef.current = newCount
|
|
27
|
+
rerender()
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, deps)
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
updateCount()
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, deps)
|
|
35
|
+
|
|
36
|
+
useLayoutEffect(() => {
|
|
37
|
+
const unsubscribe = state.subscribe((item) => {
|
|
38
|
+
const { mutations, removedAll } = item
|
|
39
|
+
if (removedAll) {
|
|
40
|
+
countRef.current = 0
|
|
41
|
+
rerender()
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
if (!mutations) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let shouldUpdate = false
|
|
49
|
+
for (const mutation of mutations) {
|
|
50
|
+
const { op } = mutation
|
|
51
|
+
if (op === 'insert' || op === 'delete') {
|
|
52
|
+
shouldUpdate = true
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (shouldUpdate) {
|
|
58
|
+
updateCount()
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
unsubscribe()
|
|
64
|
+
}
|
|
65
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
66
|
+
}, [state])
|
|
67
|
+
|
|
68
|
+
return countRef.current
|
|
69
|
+
}
|