muya 2.4.3 → 2.4.4
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
import{STATE_SCHEDULER as
|
|
1
|
+
import{STATE_SCHEDULER as y}from"../create";import{getId as v}from"../utils/id";import{shallow as N}from"../utils/shallow";import{selectSql as C}from"./select-sql";import{createTable as M,DEFAULT_STEP_SIZE as B}from"./table/table";function L(g){const x=v();function u(e){return`state-${x}-search-${e}`}let f;async function r(){if(!f){const{backend:e,...t}=g,n=e instanceof Promise?await e:e;f=await M({backend:n,...t})}return f}const s=new Map,d=new Map,S=new Map;async function p(e,t,n){const a=S.get(e),{options:o={}}=t,{stepSize:c=B}=o;if(!a)return{isOk:!1};const i=[];n&&t.keys.clear();for(let h=0;h<c;h++){const l=await a.next();if(l.done){S.delete(e);break}t.keys.has(String(l.value.key))||(i.push(l.value.document),t.keys.add(String(l.value.key)))}return i.length===0?{isOk:!0,newItems:n?[]:t.items,isDone:!0}:N(t.items,i)?{isOk:!1}:n?{isOk:!0,newItems:i}:{newItems:[...t.items,...i],isOk:!0}}function w(e){const t=d.get(e);if(t)for(const[,n]of t)n()}async function O(e){const t=await r(),n=s.get(e);if(!n)return;const{options:a}=n,o=t.search({...a,select:(h,{rowId:l,key:P})=>({document:h,rowId:l,key:P})});S.set(e,o);const{isOk:c,newItems:i}=await p(e,n,!0);c&&(n.items=i)}async function b(e){await O(e),w(e)}function T(e){const{key:t,op:n}=e,a=new Set;for(const[o,{keys:c}]of s)switch(n){case"delete":case"update":{c.has(String(t))&&a.add(o);break}case"insert":{a.add(o);break}}return a}async function m(e){const t=new Set;for(const n of e){const a=T(n);for(const o of a)t.add(o)}for(const n of t){const a=u(n);y.schedule(a,{})}}const D=new Set;function I(e,t){if(!s.has(e)){const a={items:[],options:t,keys:new Set,wasInitialized:!1};s.set(e,a)}const n=s.get(e);return t&&(n.options=t),n}const k={load(e){const t=s.get(e);if(t&&!t.wasInitialized){t.wasInitialized=!0;const n=u(e);y.schedule(n,{searchId:e})}},clear(e){s.delete(e)},async set(e){const n=await(await r()).set(e);return await m([n]),n},async batchSet(e){const n=await(await r()).batchSet(e);return await m(n),n},async delete(e){const n=await(await r()).delete(e);return n&&await m([n]),n},async deleteBy(e){const n=await(await r()).deleteBy(e);return await m(n),n},async get(e,t){return(await r()).get(e,t)},async*search(e={}){const t=await r();for await(const n of t.search(e))yield n},async count(e){return await(await r()).count(e)},updateSearchOptions(e,t){const n=I(e,t);n.options=t;const a=u(e);y.schedule(a,{searchId:e})},subscribe(e,t,n){const a=u(e),o=y.add(a,{onScheduleDone(){b(e)}});this.load(e),D.add(o),d.has(e)||d.set(e,new Map);const c=d.get(e);return c.set(t,n),()=>{c.delete(t),c.size===0&&d.delete(e),o()}},getSnapshot(e){return I(e).items},refresh:b,destroy(){for(const e of D)e();s.clear(),d.clear()},async next(e){const t=s.get(e);if(t){const n=await p(e,t,!1);return n.isOk&&(t.items=n.newItems,w(e)),n.isDone??!1}return!1},select(e){return C(k,e)}};return k}export{L as createSqliteState};
|
package/package.json
CHANGED
|
@@ -230,13 +230,13 @@ describe('use-sqlite-state', () => {
|
|
|
230
230
|
})
|
|
231
231
|
|
|
232
232
|
act(() => {
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
while (!result.current[1].next()) {
|
|
234
|
+
/* empty */
|
|
235
235
|
}
|
|
236
236
|
})
|
|
237
237
|
|
|
238
238
|
await waitFor(() => {
|
|
239
|
-
expect(reRenders).toBe(
|
|
239
|
+
expect(reRenders).toBe(3)
|
|
240
240
|
expect(result.current[0].length).toBe(ITEMS_COUNT)
|
|
241
241
|
})
|
|
242
242
|
|
|
@@ -244,7 +244,7 @@ describe('use-sqlite-state', () => {
|
|
|
244
244
|
result.current[1].reset()
|
|
245
245
|
})
|
|
246
246
|
await waitFor(() => {
|
|
247
|
-
expect(reRenders).toBe(
|
|
247
|
+
expect(reRenders).toBe(4)
|
|
248
248
|
expect(result.current[0].length).toBe(stepSize)
|
|
249
249
|
})
|
|
250
250
|
})
|
|
@@ -305,7 +305,7 @@ describe('use-sqlite-state', () => {
|
|
|
305
305
|
)
|
|
306
306
|
})
|
|
307
307
|
await waitFor(() => {
|
|
308
|
-
expect(reRenders).toBe(
|
|
308
|
+
expect(reRenders).toBe(1)
|
|
309
309
|
expect(result1.current[0].length).toBe(0)
|
|
310
310
|
})
|
|
311
311
|
|
|
@@ -341,7 +341,7 @@ describe('use-sqlite-state', () => {
|
|
|
341
341
|
})
|
|
342
342
|
|
|
343
343
|
await waitFor(() => {
|
|
344
|
-
expect(reRenders).toBe(
|
|
344
|
+
expect(reRenders).toBe(1)
|
|
345
345
|
expect(result.current[0].length).toBe(0)
|
|
346
346
|
})
|
|
347
347
|
|
|
@@ -358,7 +358,7 @@ describe('use-sqlite-state', () => {
|
|
|
358
358
|
sql.set({ person: { id: 'some_id', name: 'Alice', age: 31 } })
|
|
359
359
|
})
|
|
360
360
|
await waitFor(() => {
|
|
361
|
-
|
|
361
|
+
expect(reRenders).toBe(4)
|
|
362
362
|
expect(result.current[0]).toEqual([{ person: { id: 'some_id', name: 'Alice', age: 31 } }])
|
|
363
363
|
})
|
|
364
364
|
|
|
@@ -32,16 +32,18 @@ export interface SyncTable<Document extends DocType> {
|
|
|
32
32
|
readonly destroy: () => void
|
|
33
33
|
readonly next: (searchId: SearchId) => Promise<boolean>
|
|
34
34
|
readonly clear: (searchId: SearchId) => void
|
|
35
|
+
readonly load: (searchId: SearchId) => void
|
|
35
36
|
|
|
36
37
|
readonly select: <Params extends unknown[]>(
|
|
37
38
|
compute: (...args: Params) => SearchOptions<Document>,
|
|
38
39
|
) => CreateState<Document, Params>
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
interface
|
|
42
|
+
interface CachedItem<Document extends DocType> {
|
|
42
43
|
items: Document[]
|
|
43
44
|
keys: Set<Key>
|
|
44
45
|
options?: SearchOptions<Document, unknown>
|
|
46
|
+
wasInitialized: boolean
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/**
|
|
@@ -80,22 +82,40 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
80
82
|
key: Key
|
|
81
83
|
}
|
|
82
84
|
// const emitter = createEmitter<Table<Document>>()
|
|
83
|
-
const cachedData = new Map<SearchId,
|
|
85
|
+
const cachedData = new Map<SearchId, CachedItem<Document>>()
|
|
84
86
|
const listeners = new Map<SearchId, Map<string, () => void>>()
|
|
85
87
|
const iterators = new Map<SearchId, AsyncIterableIterator<NextResult>>()
|
|
86
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
|
|
87
103
|
/**
|
|
88
104
|
* Next step in the iterator
|
|
89
105
|
* @param searchId The search ID
|
|
90
106
|
* @param data The data items to process
|
|
107
|
+
* @param shouldReset Whether to reset the items
|
|
91
108
|
* @returns boolean indicating if new items were added
|
|
92
109
|
*/
|
|
93
|
-
async function
|
|
110
|
+
async function getNext(searchId: SearchId, data: CachedItem<Document>, shouldReset: boolean): Promise<GetNext> {
|
|
94
111
|
const iterator = iterators.get(searchId)
|
|
95
112
|
const { options: nextOptions = {} } = data
|
|
96
113
|
const { stepSize = DEFAULT_STEP_SIZE } = nextOptions
|
|
97
|
-
if (!iterator) return false
|
|
114
|
+
if (!iterator) return { isOk: false }
|
|
98
115
|
const newItems: Document[] = []
|
|
116
|
+
if (shouldReset) {
|
|
117
|
+
data.keys.clear()
|
|
118
|
+
}
|
|
99
119
|
|
|
100
120
|
for (let index = 0; index < stepSize; index++) {
|
|
101
121
|
const result = await iterator.next()
|
|
@@ -110,10 +130,10 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
110
130
|
}
|
|
111
131
|
}
|
|
112
132
|
|
|
113
|
-
if (newItems.length === 0) return
|
|
114
|
-
if (shallow(data.items, newItems)) return false
|
|
115
|
-
|
|
116
|
-
return true
|
|
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 }
|
|
117
137
|
}
|
|
118
138
|
|
|
119
139
|
/**
|
|
@@ -140,9 +160,10 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
140
160
|
const { options: refreshOptions } = data
|
|
141
161
|
const iterator = table.search({ ...refreshOptions, select: (document, { rowId, key }) => ({ document, rowId, key }) })
|
|
142
162
|
iterators.set(searchId, iterator)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
163
|
+
const { isOk, newItems } = await getNext(searchId, data, true)
|
|
164
|
+
if (isOk) {
|
|
165
|
+
data.items = newItems
|
|
166
|
+
}
|
|
146
167
|
}
|
|
147
168
|
/**
|
|
148
169
|
* Refresh the data and notify listeners
|
|
@@ -158,7 +179,7 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
158
179
|
* @param mutationResult The mutation result
|
|
159
180
|
* @returns A set of search IDs that need to be updated
|
|
160
181
|
*/
|
|
161
|
-
function
|
|
182
|
+
function getChangedKeys(mutationResult: MutationResult) {
|
|
162
183
|
const { key, op } = mutationResult
|
|
163
184
|
// find all cached data with key
|
|
164
185
|
const searchIds = new Set<SearchId>()
|
|
@@ -188,16 +209,15 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
188
209
|
async function handleChanges(mutationResults: MutationResult[]) {
|
|
189
210
|
const updateSearchIds = new Set<SearchId>()
|
|
190
211
|
for (const mutationResult of mutationResults) {
|
|
191
|
-
const searchIds =
|
|
212
|
+
const searchIds = getChangedKeys(mutationResult)
|
|
192
213
|
for (const searchId of searchIds) {
|
|
193
214
|
updateSearchIds.add(searchId)
|
|
194
215
|
}
|
|
195
216
|
}
|
|
196
217
|
|
|
197
|
-
// const promises = []
|
|
198
218
|
for (const searchId of updateSearchIds) {
|
|
199
219
|
const scheduleId = getScheduleId(searchId)
|
|
200
|
-
STATE_SCHEDULER.schedule(scheduleId, {
|
|
220
|
+
STATE_SCHEDULER.schedule(scheduleId, {})
|
|
201
221
|
}
|
|
202
222
|
}
|
|
203
223
|
|
|
@@ -211,19 +231,32 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
211
231
|
*/
|
|
212
232
|
function registerData(searchId: SearchId, registerDataOptions?: SearchOptions<Document, unknown>) {
|
|
213
233
|
if (!cachedData.has(searchId)) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
const cachedItem: CachedItem<Document> = {
|
|
235
|
+
items: [],
|
|
236
|
+
options: registerDataOptions,
|
|
237
|
+
keys: new Set(),
|
|
238
|
+
wasInitialized: false,
|
|
217
239
|
}
|
|
240
|
+
cachedData.set(searchId, cachedItem)
|
|
218
241
|
}
|
|
219
|
-
const
|
|
242
|
+
const cachedItem = cachedData.get(searchId)!
|
|
243
|
+
|
|
220
244
|
if (registerDataOptions) {
|
|
221
|
-
|
|
245
|
+
cachedItem.options = registerDataOptions
|
|
222
246
|
}
|
|
223
|
-
return
|
|
247
|
+
return cachedItem
|
|
224
248
|
}
|
|
225
249
|
|
|
226
250
|
const state: SyncTable<Document> = {
|
|
251
|
+
load(searchId: SearchId) {
|
|
252
|
+
const cachedItem = cachedData.get(searchId)
|
|
253
|
+
if (!cachedItem) return
|
|
254
|
+
if (!cachedItem.wasInitialized) {
|
|
255
|
+
cachedItem.wasInitialized = true
|
|
256
|
+
const scheduleId = getScheduleId(searchId)
|
|
257
|
+
STATE_SCHEDULER.schedule(scheduleId, { searchId })
|
|
258
|
+
}
|
|
259
|
+
},
|
|
227
260
|
clear(searchId: SearchId) {
|
|
228
261
|
cachedData.delete(searchId)
|
|
229
262
|
},
|
|
@@ -282,6 +315,8 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
282
315
|
refresh(searchId)
|
|
283
316
|
},
|
|
284
317
|
})
|
|
318
|
+
// console.log('Subscribing to searchId:', searchId)
|
|
319
|
+
this.load(searchId)
|
|
285
320
|
clearSchedulers.add(clearScheduler)
|
|
286
321
|
|
|
287
322
|
if (!listeners.has(searchId)) {
|
|
@@ -289,6 +324,7 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
289
324
|
}
|
|
290
325
|
const searchListeners = listeners.get(searchId)!
|
|
291
326
|
searchListeners.set(componentId, listener)
|
|
327
|
+
|
|
292
328
|
return () => {
|
|
293
329
|
searchListeners.delete(componentId)
|
|
294
330
|
if (searchListeners.size === 0) {
|
|
@@ -310,11 +346,12 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
310
346
|
async next(searchId) {
|
|
311
347
|
const data = cachedData.get(searchId)
|
|
312
348
|
if (data) {
|
|
313
|
-
const hasNext = await
|
|
314
|
-
if (hasNext) {
|
|
349
|
+
const hasNext = await getNext(searchId, data, false)
|
|
350
|
+
if (hasNext.isOk) {
|
|
351
|
+
data.items = hasNext.newItems
|
|
315
352
|
notifyListeners(searchId)
|
|
316
353
|
}
|
|
317
|
-
return hasNext
|
|
354
|
+
return hasNext.isDone ?? false
|
|
318
355
|
}
|
|
319
356
|
return false
|
|
320
357
|
},
|
package/src/sqlite/use-sqlite.ts
CHANGED
|
@@ -6,7 +6,15 @@ import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/w
|
|
|
6
6
|
import type { SqlSeachOptions } from './select-sql'
|
|
7
7
|
|
|
8
8
|
export interface SqLiteActions {
|
|
9
|
+
/**
|
|
10
|
+
* Load the next page of results and return if isDone to show more results.
|
|
11
|
+
* @returns isDone: boolean
|
|
12
|
+
*/
|
|
9
13
|
readonly next: () => Promise<boolean>
|
|
14
|
+
/**
|
|
15
|
+
* Reset the pagination and load the first page of results.
|
|
16
|
+
* @returns void
|
|
17
|
+
*/
|
|
10
18
|
readonly reset: () => Promise<void>
|
|
11
19
|
}
|
|
12
20
|
|
|
@@ -73,6 +81,7 @@ export function useSqliteValue<Document extends DocType, Selected = Document>(
|
|
|
73
81
|
}, deps)
|
|
74
82
|
|
|
75
83
|
useEffect(() => {
|
|
84
|
+
// state.load(searchId)
|
|
76
85
|
return () => {
|
|
77
86
|
state.clear(searchId)
|
|
78
87
|
}
|
|
@@ -23,6 +23,7 @@ export interface SyncTable<Document extends DocType> {
|
|
|
23
23
|
readonly destroy: () => void;
|
|
24
24
|
readonly next: (searchId: SearchId) => Promise<boolean>;
|
|
25
25
|
readonly clear: (searchId: SearchId) => void;
|
|
26
|
+
readonly load: (searchId: SearchId) => void;
|
|
26
27
|
readonly select: <Params extends unknown[]>(compute: (...args: Params) => SearchOptions<Document>) => CreateState<Document, Params>;
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
@@ -3,7 +3,15 @@ import type { SyncTable } from './create-sqlite';
|
|
|
3
3
|
import type { DocType } from './table/table.types';
|
|
4
4
|
import type { SqlSeachOptions } from './select-sql';
|
|
5
5
|
export interface SqLiteActions {
|
|
6
|
+
/**
|
|
7
|
+
* Load the next page of results and return if isDone to show more results.
|
|
8
|
+
* @returns isDone: boolean
|
|
9
|
+
*/
|
|
6
10
|
readonly next: () => Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Reset the pagination and load the first page of results.
|
|
13
|
+
* @returns void
|
|
14
|
+
*/
|
|
7
15
|
readonly reset: () => Promise<void>;
|
|
8
16
|
}
|
|
9
17
|
export interface UseSearchOptions<Document extends DocType, Selected = Document> extends SqlSeachOptions<Document> {
|