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 f}from"../create";import{getId as P}from"../utils/id";import{shallow as v}from"../utils/shallow";import{selectSql as M}from"./select-sql";import{createTable as O,DEFAULT_STEP_SIZE as R}from"./table/table";function q(g){const k=P();function m(e){return`state-${k}-search-${e}`}let h;async function s(){if(!h){const{backend:e,...n}=g,t=e instanceof Promise?await e:e;h=await O({backend:t,...n})}return h}const r=new Map,i=new Map,y=new Map;async function p(e,n){const t=y.get(e),{options:a={}}=n,{stepSize:c=R}=a;if(!t)return!1;const o=[];for(let u=0;u<c;u++){const d=await t.next();if(d.done){y.delete(e);break}n.keys.has(String(d.value.key))||(o.push(d.value.document),n.keys.add(String(d.value.key)))}return o.length===0||v(n.items,o)?!1:(n.items=[...n.items,...o],!0)}function b(e){const n=i.get(e);if(n)for(const[,t]of n)t()}async function x(e){const n=await s(),t=r.get(e);if(!t)return;const{options:a}=t,c=n.search({...a,select:(o,{rowId:u,key:d})=>({document:o,rowId:u,key:d})});y.set(e,c),t.keys=new Set,t.items=[],await p(e,t)}async function S(e){await x(e),b(e)}function T(e){const{key:n,op:t}=e,a=new Set;for(const[c,{keys:o}]of r)switch(t){case"delete":case"update":{o.has(String(n))&&a.add(c);break}case"insert":{a.add(c);break}}return a}async function l(e){const n=new Set;for(const t of e){const a=T(t);for(const c of a)n.add(c)}for(const t of n){const a=m(t);f.schedule(a,{searchId:t})}}const w=new Set;function D(e,n){r.has(e)||(r.set(e,{items:[],options:n,keys:new Set}),n&&S(e));const t=r.get(e);return n&&(t.options=n),t}const I={clear(e){r.delete(e)},async set(e){const t=await(await s()).set(e);return await l([t]),t},async batchSet(e){const t=await(await s()).batchSet(e);return await l(t),t},async delete(e){const t=await(await s()).delete(e);return t&&await l([t]),t},async deleteBy(e){const t=await(await s()).deleteBy(e);return await l(t),t},async get(e,n){return(await s()).get(e,n)},async*search(e={}){const n=await s();for await(const t of n.search(e))yield t},async count(e){return await(await s()).count(e)},updateSearchOptions(e,n){const t=D(e,n);t.options=n;const a=m(e);f.schedule(a,{searchId:e})},subscribe(e,n,t){const a=m(e),c=f.add(a,{onScheduleDone(){S(e)}});w.add(c),i.has(e)||i.set(e,new Map);const o=i.get(e);return o.set(n,t),()=>{o.delete(n),o.size===0&&i.delete(e),c()}},getSnapshot(e){return D(e).items},refresh:S,destroy(){for(const e of w)e();r.clear(),i.clear()},async next(e){const n=r.get(e);if(n){const t=await p(e,n);return t&&b(e),t}return!1},select(e){return M(I,e)}};return I}export{q as createSqliteState};
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muya",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
4
4
  "author": "samuel.gjabel@gmail.com",
5
5
  "repository": "https://github.com/samuelgjabel/muya",
6
6
  "main": "cjs/index.js",
@@ -230,13 +230,13 @@ describe('use-sqlite-state', () => {
230
230
  })
231
231
 
232
232
  act(() => {
233
- for (let index = 0; index < ITEMS_COUNT / stepSize; index++) {
234
- result.current[1].next()
233
+ while (!result.current[1].next()) {
234
+ /* empty */
235
235
  }
236
236
  })
237
237
 
238
238
  await waitFor(() => {
239
- expect(reRenders).toBe(4)
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(5)
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(2)
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(2)
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
- // expect(reRenders).toBe(4)
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 DataItems<Document extends DocType> {
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, DataItems<Document>>()
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 next(searchId: SearchId, data: DataItems<Document>): Promise<boolean> {
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 false
114
- if (shallow(data.items, newItems)) return false
115
- data.items = [...data.items, ...newItems]
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
- data.keys = new Set()
144
- data.items = []
145
- await next(searchId, data)
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 handleChange(mutationResult: MutationResult) {
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 = handleChange(mutationResult)
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, { searchId })
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
- cachedData.set(searchId, { items: [], options: registerDataOptions, keys: new Set() })
215
- if (registerDataOptions) {
216
- refresh(searchId)
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 data = cachedData.get(searchId)!
242
+ const cachedItem = cachedData.get(searchId)!
243
+
220
244
  if (registerDataOptions) {
221
- data.options = registerDataOptions
245
+ cachedItem.options = registerDataOptions
222
246
  }
223
- return data
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 next(searchId, data)
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
  },
@@ -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> {