muya 2.4.1 → 2.4.3
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/esm/sqlite/create-sqlite.js +1 -1
- package/esm/sqlite/select-sql.js +1 -1
- package/esm/sqlite/use-sqlite.js +1 -1
- package/package.json +1 -1
- package/src/sqlite/__tests__/use-sqlite.test.tsx +39 -6
- package/src/sqlite/create-sqlite.ts +13 -6
- package/src/sqlite/select-sql.ts +2 -2
- package/src/sqlite/use-sqlite.ts +43 -10
- package/types/sqlite/create-sqlite.d.ts +1 -1
- package/esm/sqlite/__tests__/use-sqlite.test.js +0 -1
- package/src/sqlite/__tests__/use-sqlite.test.ts +0 -274
|
@@ -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
|
|
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};
|
package/esm/sqlite/select-sql.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createState as
|
|
1
|
+
import{createState as l}from"../create-state";let n=0;function o(){return n++,`${n.toString(36)}-sql`}function S(r,a){const{subscribe:c,updateSearchOptions:s}=r,m=o();return(...u)=>{const e=o(),p=c(e,m,()=>{t.emitter.emit()}),i=a(...u),t=l({destroy(){p(),t.emitter.clear(),t.cache.current=void 0},get(){return r.getSnapshot(e)},getSnapshot(){return r.getSnapshot(e)}});return s(e,i),t}}export{S as selectSql};
|
package/esm/sqlite/use-sqlite.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback as
|
|
1
|
+
import{useCallback as f,useDebugValue as p,useEffect as a,useId as y,useLayoutEffect as D,useMemo as S}from"react";import{isError as h,isPromise as x}from"../utils/is";import{useSyncExternalStoreWithSelector as b}from"use-sync-external-store/shim/with-selector";function q(t){const{limit:o,offset:s,order:c,sortBy:e,where:i,stepSize:u,select:d}=t;let n="";return o!==void 0&&(n+=`l${o}`),s!==void 0&&(n+=`o${s}`),c!==void 0&&(n+=`r${c}`),e!==void 0&&(n+=`s${e}`),i!==void 0&&(n+=`w${JSON.stringify(i)}`),u!==void 0&&(n+=`t${u}`),d!==void 0&&(n+=`f${d.toString()}`),n}function w(t,o={},s=[]){const{select:c}=o,e=S(()=>q({...o,select:void 0}),[o]),i=y();D(()=>{t.updateSearchOptions(e,{...o,select:void 0})},s),a(()=>()=>{t.clear(e)},[]);const u=f(l=>c?l.map(c):l,[c]),d=f(l=>t.subscribe(e,i,l),[t,e,i]),n=f(()=>t.getSnapshot(e),[t,e]),r=b(d,n,n,u);if(p(r),x(r)||h(r))throw r;const m=S(()=>({next:()=>t.next(e),reset:()=>t.refresh(e)}),[e,t]);return[r,m]}export{w as useSqliteValue};
|
package/package.json
CHANGED
|
@@ -110,7 +110,7 @@ describe('use-sqlite-state', () => {
|
|
|
110
110
|
})
|
|
111
111
|
|
|
112
112
|
it('should support like in where clause and update results', async () => {
|
|
113
|
-
const sql = createSqliteState<Person>({ backend, tableName: '
|
|
113
|
+
const sql = createSqliteState<Person>({ backend, tableName: 'State3Hook', key: 'id' })
|
|
114
114
|
await sql.batchSet([
|
|
115
115
|
{ id: '1', name: 'Alice', age: 30 },
|
|
116
116
|
{ id: '2', name: 'Alicia', age: 25 },
|
|
@@ -136,8 +136,8 @@ describe('use-sqlite-state', () => {
|
|
|
136
136
|
expect(reRenders).toBeGreaterThanOrEqual(2)
|
|
137
137
|
})
|
|
138
138
|
|
|
139
|
-
it('should update results when changing order and limit options', async () => {
|
|
140
|
-
const sql = createSqliteState<Person>({ backend, tableName: '
|
|
139
|
+
it('should update results when changing order and limit options 1', async () => {
|
|
140
|
+
const sql = createSqliteState<Person>({ backend, tableName: 'State44Hook', key: 'id' })
|
|
141
141
|
await sql.batchSet([
|
|
142
142
|
{ id: '1', name: 'Alice', age: 30 },
|
|
143
143
|
{ id: '2', name: 'Bob', age: 25 },
|
|
@@ -165,7 +165,7 @@ describe('use-sqlite-state', () => {
|
|
|
165
165
|
})
|
|
166
166
|
|
|
167
167
|
it('should support actions.next and actions.refresh', async () => {
|
|
168
|
-
const sql = createSqliteState<Person>({ backend, tableName: '
|
|
168
|
+
const sql = createSqliteState<Person>({ backend, tableName: 'State5Hook', key: 'id' })
|
|
169
169
|
await sql.batchSet([
|
|
170
170
|
{ id: '1', name: 'Alice', age: 30 },
|
|
171
171
|
{ id: '2', name: 'Bob', age: 25 },
|
|
@@ -180,7 +180,7 @@ describe('use-sqlite-state', () => {
|
|
|
180
180
|
})
|
|
181
181
|
})
|
|
182
182
|
it('should handle thousands of records', async () => {
|
|
183
|
-
const sql = createSqliteState<Person>({ backend, tableName: '
|
|
183
|
+
const sql = createSqliteState<Person>({ backend, tableName: 'State6Hook', key: 'id' })
|
|
184
184
|
const people: Person[] = []
|
|
185
185
|
const ITEMS_COUNT = 1000
|
|
186
186
|
for (let index = 1; index <= ITEMS_COUNT; index++) {
|
|
@@ -211,7 +211,7 @@ describe('use-sqlite-state', () => {
|
|
|
211
211
|
})
|
|
212
212
|
|
|
213
213
|
it('should handle thousands of records with single update', async () => {
|
|
214
|
-
const sql = createSqliteState<Person>({ backend, tableName: '
|
|
214
|
+
const sql = createSqliteState<Person>({ backend, tableName: 'State6Hook', key: 'id' })
|
|
215
215
|
const people: Person[] = []
|
|
216
216
|
const ITEMS_COUNT = 10_000
|
|
217
217
|
const stepSize = 5000
|
|
@@ -373,4 +373,37 @@ describe('use-sqlite-state', () => {
|
|
|
373
373
|
|
|
374
374
|
// add another item
|
|
375
375
|
})
|
|
376
|
+
it('should test reset', async () => {
|
|
377
|
+
const sql = createSqliteState<Person>({ backend, tableName: 'State11', key: 'id' })
|
|
378
|
+
let reRenders = 0
|
|
379
|
+
|
|
380
|
+
await sql.set({ id: 'initial', name: 'initial', age: 1 })
|
|
381
|
+
const { result } = renderHook(() => {
|
|
382
|
+
reRenders++
|
|
383
|
+
// eslint-disable-next-line unicorn/prevent-abbreviations
|
|
384
|
+
const res = useSqliteValue(sql, {}, [])
|
|
385
|
+
return res
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
await waitFor(() => {
|
|
389
|
+
expect(reRenders).toBe(2)
|
|
390
|
+
expect(result.current[0].length).toBe(1)
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
act(() => {
|
|
394
|
+
sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
395
|
+
})
|
|
396
|
+
await waitFor(() => {
|
|
397
|
+
expect(reRenders).toBe(3)
|
|
398
|
+
expect(result.current[0].length).toBe(2)
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
act(() => {
|
|
402
|
+
result.current[1].reset()
|
|
403
|
+
})
|
|
404
|
+
await waitFor(() => {
|
|
405
|
+
expect(result.current[0].length).toBe(2)
|
|
406
|
+
expect(reRenders).toBe(4)
|
|
407
|
+
})
|
|
408
|
+
})
|
|
376
409
|
})
|
|
@@ -17,7 +17,7 @@ export interface CreateSqliteOptions<Document extends DocType> extends Omit<DbOp
|
|
|
17
17
|
export interface SyncTable<Document extends DocType> {
|
|
18
18
|
// readonly registerSearch: <Selected = Document>(searchId: SearchId, options: SearchOptions<Document, Selected>) => () => void
|
|
19
19
|
readonly updateSearchOptions: <Selected = Document>(searchId: SearchId, options: SearchOptions<Document, Selected>) => void
|
|
20
|
-
readonly subscribe: (searchId: SearchId, listener: () => void) => () => void
|
|
20
|
+
readonly subscribe: (searchId: SearchId, componentId: string, listener: () => void) => () => void
|
|
21
21
|
readonly getSnapshot: (searchId: SearchId) => Document[]
|
|
22
22
|
readonly refresh: (searchId: SearchId) => Promise<void>
|
|
23
23
|
|
|
@@ -81,7 +81,7 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
81
81
|
}
|
|
82
82
|
// const emitter = createEmitter<Table<Document>>()
|
|
83
83
|
const cachedData = new Map<SearchId, DataItems<Document>>()
|
|
84
|
-
const listeners = new Map<SearchId, () => void
|
|
84
|
+
const listeners = new Map<SearchId, Map<string, () => void>>()
|
|
85
85
|
const iterators = new Map<SearchId, AsyncIterableIterator<NextResult>>()
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -123,7 +123,9 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
123
123
|
function notifyListeners(searchId: SearchId) {
|
|
124
124
|
const searchListeners = listeners.get(searchId)
|
|
125
125
|
if (searchListeners) {
|
|
126
|
-
searchListeners
|
|
126
|
+
for (const [, listener] of searchListeners) {
|
|
127
|
+
listener()
|
|
128
|
+
}
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -273,7 +275,7 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
273
275
|
STATE_SCHEDULER.schedule(scheduleId, { searchId })
|
|
274
276
|
},
|
|
275
277
|
|
|
276
|
-
subscribe(searchId, listener) {
|
|
278
|
+
subscribe(searchId, componentId, listener) {
|
|
277
279
|
const scheduleId = getScheduleId(searchId)
|
|
278
280
|
const clearScheduler = STATE_SCHEDULER.add(scheduleId, {
|
|
279
281
|
onScheduleDone() {
|
|
@@ -283,10 +285,15 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
|
|
|
283
285
|
clearSchedulers.add(clearScheduler)
|
|
284
286
|
|
|
285
287
|
if (!listeners.has(searchId)) {
|
|
286
|
-
listeners.set(searchId,
|
|
288
|
+
listeners.set(searchId, new Map())
|
|
287
289
|
}
|
|
290
|
+
const searchListeners = listeners.get(searchId)!
|
|
291
|
+
searchListeners.set(componentId, listener)
|
|
288
292
|
return () => {
|
|
289
|
-
|
|
293
|
+
searchListeners.delete(componentId)
|
|
294
|
+
if (searchListeners.size === 0) {
|
|
295
|
+
listeners.delete(searchId)
|
|
296
|
+
}
|
|
290
297
|
clearScheduler()
|
|
291
298
|
}
|
|
292
299
|
},
|
package/src/sqlite/select-sql.ts
CHANGED
|
@@ -36,10 +36,10 @@ export function selectSql<Document extends DocType, Params extends unknown[] = [
|
|
|
36
36
|
compute: (...args: Params) => SqlSeachOptions<Document>,
|
|
37
37
|
): CreateState<Document, Params> {
|
|
38
38
|
const { subscribe, updateSearchOptions } = state
|
|
39
|
-
|
|
39
|
+
const componentId = getStateId()
|
|
40
40
|
const result: CreateState<Document, Params> = (...params) => {
|
|
41
41
|
const searchId = getStateId()
|
|
42
|
-
const destroy = subscribe(searchId, () => {
|
|
42
|
+
const destroy = subscribe(searchId, componentId, () => {
|
|
43
43
|
getState.emitter.emit()
|
|
44
44
|
})
|
|
45
45
|
|
package/src/sqlite/use-sqlite.ts
CHANGED
|
@@ -17,6 +17,38 @@ export interface UseSearchOptions<Document extends DocType, Selected = Document>
|
|
|
17
17
|
readonly select?: (document: Document) => Selected
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Generate a cache key based on the search options to uniquely identify the query
|
|
22
|
+
* @param options The search options to generate the key from
|
|
23
|
+
* @returns A string representing the unique cache key for the given search options
|
|
24
|
+
*/
|
|
25
|
+
function generateCacheKey(options: UseSearchOptions<DocType, unknown>): string {
|
|
26
|
+
const { limit, offset, order, sortBy, where, stepSize, select } = options
|
|
27
|
+
let key = ''
|
|
28
|
+
if (limit !== undefined) {
|
|
29
|
+
key += `l${limit}`
|
|
30
|
+
}
|
|
31
|
+
if (offset !== undefined) {
|
|
32
|
+
key += `o${offset}`
|
|
33
|
+
}
|
|
34
|
+
if (order !== undefined) {
|
|
35
|
+
key += `r${order}`
|
|
36
|
+
}
|
|
37
|
+
if (sortBy !== undefined) {
|
|
38
|
+
key += `s${sortBy}`
|
|
39
|
+
}
|
|
40
|
+
if (where !== undefined) {
|
|
41
|
+
key += `w${JSON.stringify(where)}`
|
|
42
|
+
}
|
|
43
|
+
if (stepSize !== undefined) {
|
|
44
|
+
key += `t${stepSize}`
|
|
45
|
+
}
|
|
46
|
+
if (select !== undefined) {
|
|
47
|
+
key += `f${select.toString()}`
|
|
48
|
+
}
|
|
49
|
+
return key
|
|
50
|
+
}
|
|
51
|
+
|
|
20
52
|
/**
|
|
21
53
|
* React hook to subscribe to a SyncTable and get its current snapshot, with optional search options and selector for derived state
|
|
22
54
|
* @param state The SyncTable to subscribe to
|
|
@@ -32,16 +64,17 @@ export function useSqliteValue<Document extends DocType, Selected = Document>(
|
|
|
32
64
|
): [undefined extends Selected ? Document[] : Selected[], SqLiteActions] {
|
|
33
65
|
const { select } = options
|
|
34
66
|
|
|
35
|
-
const
|
|
67
|
+
const searchId = useMemo(() => generateCacheKey({ ...options, select: undefined }), [options])
|
|
68
|
+
const componentId = useId()
|
|
36
69
|
|
|
37
70
|
useLayoutEffect(() => {
|
|
38
|
-
state.updateSearchOptions(
|
|
71
|
+
state.updateSearchOptions(searchId, { ...options, select: undefined })
|
|
39
72
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
73
|
}, deps)
|
|
41
74
|
|
|
42
75
|
useEffect(() => {
|
|
43
76
|
return () => {
|
|
44
|
-
state.clear(
|
|
77
|
+
state.clear(searchId)
|
|
45
78
|
}
|
|
46
79
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
47
80
|
}, [])
|
|
@@ -56,14 +89,14 @@ export function useSqliteValue<Document extends DocType, Selected = Document>(
|
|
|
56
89
|
|
|
57
90
|
const subscribe = useCallback(
|
|
58
91
|
(onStorageChange: () => void) => {
|
|
59
|
-
return state.subscribe(
|
|
92
|
+
return state.subscribe(searchId, componentId, onStorageChange)
|
|
60
93
|
},
|
|
61
|
-
[state,
|
|
94
|
+
[state, searchId, componentId],
|
|
62
95
|
)
|
|
63
96
|
|
|
64
97
|
const getSnapshot = useCallback(() => {
|
|
65
|
-
return state.getSnapshot(
|
|
66
|
-
}, [state,
|
|
98
|
+
return state.getSnapshot(searchId)
|
|
99
|
+
}, [state, searchId])
|
|
67
100
|
|
|
68
101
|
const value = useSyncExternalStoreWithSelector<Document[], Selected[]>(subscribe, getSnapshot, getSnapshot, selector)
|
|
69
102
|
|
|
@@ -77,9 +110,9 @@ export function useSqliteValue<Document extends DocType, Selected = Document>(
|
|
|
77
110
|
|
|
78
111
|
const actions = useMemo((): SqLiteActions => {
|
|
79
112
|
return {
|
|
80
|
-
next: () => state.next(
|
|
81
|
-
reset: () => state.refresh(
|
|
113
|
+
next: () => state.next(searchId),
|
|
114
|
+
reset: () => state.refresh(searchId),
|
|
82
115
|
}
|
|
83
|
-
}, [
|
|
116
|
+
}, [searchId, state])
|
|
84
117
|
return [value as undefined extends Selected ? Document[] : Selected[], actions]
|
|
85
118
|
}
|
|
@@ -8,7 +8,7 @@ export interface CreateSqliteOptions<Document extends DocType> extends Omit<DbOp
|
|
|
8
8
|
}
|
|
9
9
|
export interface SyncTable<Document extends DocType> {
|
|
10
10
|
readonly updateSearchOptions: <Selected = Document>(searchId: SearchId, options: SearchOptions<Document, Selected>) => void;
|
|
11
|
-
readonly subscribe: (searchId: SearchId, listener: () => void) => () => void;
|
|
11
|
+
readonly subscribe: (searchId: SearchId, componentId: string, listener: () => void) => () => void;
|
|
12
12
|
readonly getSnapshot: (searchId: SearchId) => Document[];
|
|
13
13
|
readonly refresh: (searchId: SearchId) => Promise<void>;
|
|
14
14
|
readonly set: (document: Document) => Promise<MutationResult>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{act as s,renderHook as c}from"@testing-library/react-hooks";import{createSqliteState as i}from"../create-sqlite";import{useSqliteValue as l}from"../use-sqlite";import{waitFor as o}from"@testing-library/react";import{bunMemoryBackend as g}from"../table/bun-backend";import{useState as x}from"react";import{DEFAULT_STEP_SIZE as m}from"../table/table";const u=g();describe("use-sqlite-state",()=>{it("should get basic value states",async()=>{const a=i({backend:u,tableName:"State1",key:"id"});let t=0;const{result:n}=c(()=>(t++,l(a,{},[])));expect(t).toBe(1),s(()=>{a.set({id:"1",name:"Alice",age:30})}),await o(()=>{expect(n.current[0]).toEqual([{id:"1",name:"Alice",age:30}]),expect(t).toBe(3)}),s(()=>{a.set({id:"1",name:"Alice2",age:30})}),await o(()=>{expect(n.current[0]).toEqual([{id:"1",name:"Alice2",age:30}]),expect(t).toBe(4)}),s(()=>{a.delete("1")}),await o(()=>{expect(n.current[0]).toEqual([]),expect(t).toBe(5)}),s(()=>{a.set({id:"1",name:"Alice",age:30}),a.set({id:"2",name:"Bob",age:25})}),await o(()=>{expect(n.current[0].length).toBe(2),expect(t).toBe(6)})}),it("should use where clause changed via state",async()=>{const a=i({backend:u,tableName:"State2",key:"id"});await a.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25},{id:"3",name:"Carol",age:40}]);let t=0;const{result:n}=c(()=>{t++;const[r,e]=x(20);return[l(a,{where:{age:{gt:r}},sorBy:"age"},[r]),e]});await o(()=>{expect(n.current[0][0].map(r=>r.name)).toEqual(["Alice","Bob","Carol"]),expect(t).toBe(2)}),s(()=>{n.current[1](29)}),await o(()=>{expect(n.current[0][0].map(r=>r.name)).toEqual(["Alice","Carol"]),expect(t).toBe(4)})}),it("should support like in where clause and update results",async()=>{const a=i({backend:u,tableName:"State3",key:"id"});await a.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Alicia",age:25},{id:"3",name:"Bob",age:40}]);let t=0;const{result:n,rerender:r}=c(({like:e})=>(t++,l(a,{where:{name:{like:e}}},[e])),{initialProps:{like:"%Ali%"}});await o(()=>{expect(n.current[0].map(e=>e.name)).toEqual(["Alice","Alicia"])}),s(()=>{r({like:"%Bob%"})}),await o(()=>{expect(n.current[0].map(e=>e.name)).toEqual(["Bob"])}),expect(t).toBeGreaterThanOrEqual(2)}),it("should update results when changing order and limit options",async()=>{const a=i({backend:u,tableName:"State4",key:"id"});await a.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25},{id:"3",name:"Carol",age:40}]);const{result:t,rerender:n}=c(({order:r,limit:e})=>l(a,{sorBy:"age",order:r,limit:e},[r,e]),{initialProps:{order:"asc",limit:2}});await o(()=>{expect(t.current[0].map(r=>r.name)).toEqual(["Alice","Bob"])}),s(()=>{n({order:"desc",limit:2})}),await o(()=>{expect(t.current[0].map(r=>r.name)).toEqual(["Alice","Bob"])}),s(()=>{n({order:"desc",limit:1})}),await o(()=>{expect(t.current[0].map(r=>r.name)).toEqual(["Carol"])})}),it("should support actions.next and actions.refresh",async()=>{const a=i({backend:u,tableName:"State5",key:"id"});await a.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25}]);const{result:t}=c(()=>l(a,{},[]));await o(()=>{expect(typeof t.current[1].next).toBe("function"),expect(typeof t.current[1].reset).toBe("function"),expect(t.current[1].reset()).resolves.toBeUndefined(),expect(t.current[1].next()).resolves.toBeFalsy()})}),it("should handle thousands of records",async()=>{const a=i({backend:u,tableName:"State6",key:"id"}),t=[],n=1e3;for(let e=1;e<=n;e++)t.push({id:e.toString(),name:`Person${e}`,age:20+e%50});await a.batchSet(t);const{result:r}=c(()=>l(a,{},[]));await o(()=>{expect(r.current[0].length).toBe(m)});for(let e=0;e<n/m;e++)s(()=>{r.current[1].next()}),await o(()=>{expect(r.current[0].length).toBe(Math.min(m*(e+2),n))});s(()=>{r.current[1].reset()}),await o(()=>{expect(r.current[0].length).toBe(m)})}),it("should handle thousands of records with single update",async()=>{const a=i({backend:u,tableName:"State6",key:"id"}),t=[],n=1e4,r=5e3;for(let d=1;d<=n;d++)t.push({id:d.toString(),name:`Person${d}`,age:20+d%50});await a.batchSet(t);let e=0;const{result:p}=c(()=>(e++,l(a,{stepSize:r},[])));await o(()=>{expect(e).toBe(2),expect(p.current[0].length).toBe(r)}),s(()=>{for(let d=0;d<n/r;d++)p.current[1].next()}),await o(()=>{expect(e).toBe(4),expect(p.current[0].length).toBe(n)}),s(()=>{p.current[1].reset()}),await o(()=>{expect(e).toBe(5),expect(p.current[0].length).toBe(r)})}),it("should change ordering",async()=>{const a=i({backend:u,tableName:"State7",key:"id",indexes:["age"]}),t=[];for(let e=1;e<=100;e++)t.push({id:e.toString(),name:`Person${e}`,age:20+e%50});await a.batchSet(t);const{result:n,rerender:r}=c(({order:e})=>l(a,{sorBy:"age",order:e},[e]),{initialProps:{order:"asc"}});await o(()=>{expect(n.current[0][0].age).toBe(21)}),s(()=>{r({order:"desc"})}),await o(()=>{expect(n.current[0][0].age).toBe(69)})}),it("should support selector in options",async()=>{const a=i({backend:u,tableName:"State8",key:"id"});await a.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25},{id:"3",name:"Carol",age:40}]);const{result:t}=c(()=>l(a,{sorBy:"age",select:n=>n.name},[]));await o(()=>{expect(t.current[0]).toEqual(["Alice","Bob","Carol"])})})});
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from '@testing-library/react-hooks'
|
|
2
|
-
import { createSqliteState } from '../create-sqlite'
|
|
3
|
-
import { useSqliteValue } from '../use-sqlite'
|
|
4
|
-
import { waitFor } from '@testing-library/react'
|
|
5
|
-
import { bunMemoryBackend } from '../table/bun-backend'
|
|
6
|
-
import { useState } from 'react'
|
|
7
|
-
import { DEFAULT_STEP_SIZE } from '../table/table'
|
|
8
|
-
|
|
9
|
-
const backend = bunMemoryBackend()
|
|
10
|
-
interface Person {
|
|
11
|
-
id: string
|
|
12
|
-
name: string
|
|
13
|
-
age: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('use-sqlite-state', () => {
|
|
17
|
-
it('should get basic value states', async () => {
|
|
18
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State1', key: 'id' })
|
|
19
|
-
let reRenders = 0
|
|
20
|
-
const { result } = renderHook(() => {
|
|
21
|
-
reRenders++
|
|
22
|
-
return useSqliteValue(sql, {}, [])
|
|
23
|
-
})
|
|
24
|
-
// expect(result.current).toEqual([])
|
|
25
|
-
|
|
26
|
-
expect(reRenders).toBe(1)
|
|
27
|
-
|
|
28
|
-
act(() => {
|
|
29
|
-
sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
30
|
-
})
|
|
31
|
-
await waitFor(() => {
|
|
32
|
-
expect(result.current[0]).toEqual([{ id: '1', name: 'Alice', age: 30 }])
|
|
33
|
-
expect(reRenders).toBe(3)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
act(() => {
|
|
37
|
-
sql.set({ id: '1', name: 'Alice2', age: 30 })
|
|
38
|
-
})
|
|
39
|
-
await waitFor(() => {
|
|
40
|
-
expect(result.current[0]).toEqual([{ id: '1', name: 'Alice2', age: 30 }])
|
|
41
|
-
expect(reRenders).toBe(4)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// delete item
|
|
45
|
-
act(() => {
|
|
46
|
-
sql.delete('1')
|
|
47
|
-
})
|
|
48
|
-
await waitFor(() => {
|
|
49
|
-
expect(result.current[0]).toEqual([])
|
|
50
|
-
expect(reRenders).toBe(5)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// add two items
|
|
54
|
-
act(() => {
|
|
55
|
-
sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
56
|
-
sql.set({ id: '2', name: 'Bob', age: 25 })
|
|
57
|
-
})
|
|
58
|
-
await waitFor(() => {
|
|
59
|
-
expect(result.current[0].length).toBe(2)
|
|
60
|
-
expect(reRenders).toBe(6)
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('should use where clause changed via state', async () => {
|
|
65
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State2', key: 'id' })
|
|
66
|
-
await sql.batchSet([
|
|
67
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
68
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
69
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
70
|
-
])
|
|
71
|
-
let reRenders = 0
|
|
72
|
-
const { result } = renderHook(() => {
|
|
73
|
-
reRenders++
|
|
74
|
-
const [minAge, setMinAge] = useState(20)
|
|
75
|
-
return [useSqliteValue(sql, { where: { age: { gt: minAge } }, sorBy: 'age' }, [minAge]), setMinAge] as const
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
await waitFor(() => {
|
|
79
|
-
expect(result.current[0][0].map((p) => p.name)).toEqual(['Alice', 'Bob', 'Carol'])
|
|
80
|
-
expect(reRenders).toBe(2)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
// // change minAge to 29
|
|
84
|
-
act(() => {
|
|
85
|
-
result.current[1](29)
|
|
86
|
-
})
|
|
87
|
-
await waitFor(() => {
|
|
88
|
-
expect(result.current[0][0].map((p) => p.name)).toEqual(['Alice', 'Carol'])
|
|
89
|
-
expect(reRenders).toBe(4)
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('should support like in where clause and update results', async () => {
|
|
94
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State3', key: 'id' })
|
|
95
|
-
await sql.batchSet([
|
|
96
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
97
|
-
{ id: '2', name: 'Alicia', age: 25 },
|
|
98
|
-
{ id: '3', name: 'Bob', age: 40 },
|
|
99
|
-
])
|
|
100
|
-
let reRenders = 0
|
|
101
|
-
const { result, rerender } = renderHook(
|
|
102
|
-
({ like }) => {
|
|
103
|
-
reRenders++
|
|
104
|
-
return useSqliteValue(sql, { where: { name: { like } } }, [like])
|
|
105
|
-
},
|
|
106
|
-
{ initialProps: { like: '%Ali%' } },
|
|
107
|
-
)
|
|
108
|
-
await waitFor(() => {
|
|
109
|
-
expect(result.current[0].map((p) => p.name)).toEqual(['Alice', 'Alicia'])
|
|
110
|
-
})
|
|
111
|
-
act(() => {
|
|
112
|
-
rerender({ like: '%Bob%' })
|
|
113
|
-
})
|
|
114
|
-
await waitFor(() => {
|
|
115
|
-
expect(result.current[0].map((p) => p.name)).toEqual(['Bob'])
|
|
116
|
-
})
|
|
117
|
-
expect(reRenders).toBeGreaterThanOrEqual(2)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('should update results when changing order and limit options', async () => {
|
|
121
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State4', key: 'id' })
|
|
122
|
-
await sql.batchSet([
|
|
123
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
124
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
125
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
126
|
-
])
|
|
127
|
-
const { result, rerender } = renderHook(
|
|
128
|
-
({ order, limit }) => useSqliteValue(sql, { sorBy: 'age', order, limit }, [order, limit]),
|
|
129
|
-
{ initialProps: { order: 'asc' as 'asc' | 'desc', limit: 2 } },
|
|
130
|
-
)
|
|
131
|
-
await waitFor(() => {
|
|
132
|
-
expect(result.current[0].map((p) => p.name)).toEqual(['Alice', 'Bob'])
|
|
133
|
-
})
|
|
134
|
-
act(() => {
|
|
135
|
-
rerender({ order: 'desc', limit: 2 })
|
|
136
|
-
})
|
|
137
|
-
await waitFor(() => {
|
|
138
|
-
expect(result.current[0].map((p) => p.name)).toEqual(['Alice', 'Bob'])
|
|
139
|
-
})
|
|
140
|
-
act(() => {
|
|
141
|
-
rerender({ order: 'desc', limit: 1 })
|
|
142
|
-
})
|
|
143
|
-
await waitFor(() => {
|
|
144
|
-
expect(result.current[0].map((p) => p.name)).toEqual(['Carol'])
|
|
145
|
-
})
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it('should support actions.next and actions.refresh', async () => {
|
|
149
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State5', key: 'id' })
|
|
150
|
-
await sql.batchSet([
|
|
151
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
152
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
153
|
-
])
|
|
154
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
155
|
-
// actions.next and actions.refresh should be functions
|
|
156
|
-
await waitFor(() => {
|
|
157
|
-
expect(typeof result.current[1].next).toBe('function')
|
|
158
|
-
expect(typeof result.current[1].reset).toBe('function')
|
|
159
|
-
expect(result.current[1].reset()).resolves.toBeUndefined()
|
|
160
|
-
expect(result.current[1].next()).resolves.toBeFalsy()
|
|
161
|
-
})
|
|
162
|
-
})
|
|
163
|
-
it('should handle thousands of records', async () => {
|
|
164
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State6', key: 'id' })
|
|
165
|
-
const people: Person[] = []
|
|
166
|
-
const ITEMS_COUNT = 1000
|
|
167
|
-
for (let index = 1; index <= ITEMS_COUNT; index++) {
|
|
168
|
-
people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
|
|
169
|
-
}
|
|
170
|
-
await sql.batchSet(people)
|
|
171
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
172
|
-
await waitFor(() => {
|
|
173
|
-
expect(result.current[0].length).toBe(DEFAULT_STEP_SIZE)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
// loop until we have all ITEMS_COUNT items
|
|
177
|
-
for (let index = 0; index < ITEMS_COUNT / DEFAULT_STEP_SIZE; index++) {
|
|
178
|
-
act(() => {
|
|
179
|
-
result.current[1].next()
|
|
180
|
-
})
|
|
181
|
-
await waitFor(() => {
|
|
182
|
-
expect(result.current[0].length).toBe(Math.min(DEFAULT_STEP_SIZE * (index + 2), ITEMS_COUNT))
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
act(() => {
|
|
187
|
-
result.current[1].reset()
|
|
188
|
-
})
|
|
189
|
-
await waitFor(() => {
|
|
190
|
-
expect(result.current[0].length).toBe(DEFAULT_STEP_SIZE)
|
|
191
|
-
})
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('should handle thousands of records with single update', async () => {
|
|
195
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State6', key: 'id' })
|
|
196
|
-
const people: Person[] = []
|
|
197
|
-
const ITEMS_COUNT = 10_000
|
|
198
|
-
const stepSize = 5000
|
|
199
|
-
for (let index = 1; index <= ITEMS_COUNT; index++) {
|
|
200
|
-
people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
|
|
201
|
-
}
|
|
202
|
-
await sql.batchSet(people)
|
|
203
|
-
let reRenders = 0
|
|
204
|
-
const { result } = renderHook(() => {
|
|
205
|
-
reRenders++
|
|
206
|
-
return useSqliteValue(sql, { stepSize }, [])
|
|
207
|
-
})
|
|
208
|
-
await waitFor(() => {
|
|
209
|
-
expect(reRenders).toBe(2)
|
|
210
|
-
expect(result.current[0].length).toBe(stepSize)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
act(() => {
|
|
214
|
-
for (let index = 0; index < ITEMS_COUNT / stepSize; index++) {
|
|
215
|
-
result.current[1].next()
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
await waitFor(() => {
|
|
220
|
-
expect(reRenders).toBe(4)
|
|
221
|
-
expect(result.current[0].length).toBe(ITEMS_COUNT)
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
act(() => {
|
|
225
|
-
result.current[1].reset()
|
|
226
|
-
})
|
|
227
|
-
await waitFor(() => {
|
|
228
|
-
expect(reRenders).toBe(5)
|
|
229
|
-
expect(result.current[0].length).toBe(stepSize)
|
|
230
|
-
})
|
|
231
|
-
})
|
|
232
|
-
it('should change ordering', async () => {
|
|
233
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State7', key: 'id', indexes: ['age'] })
|
|
234
|
-
const people: Person[] = []
|
|
235
|
-
for (let index = 1; index <= 100; index++) {
|
|
236
|
-
people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
|
|
237
|
-
}
|
|
238
|
-
await sql.batchSet(people)
|
|
239
|
-
const { result, rerender } = renderHook(({ order }) => useSqliteValue(sql, { sorBy: 'age', order }, [order]), {
|
|
240
|
-
initialProps: { order: 'asc' as 'asc' | 'desc' },
|
|
241
|
-
})
|
|
242
|
-
await waitFor(() => {
|
|
243
|
-
expect(result.current[0][0].age).toBe(21)
|
|
244
|
-
})
|
|
245
|
-
act(() => {
|
|
246
|
-
rerender({ order: 'desc' })
|
|
247
|
-
})
|
|
248
|
-
await waitFor(() => {
|
|
249
|
-
expect(result.current[0][0].age).toBe(69)
|
|
250
|
-
})
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it('should support selector in options', async () => {
|
|
254
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'State8', key: 'id' })
|
|
255
|
-
await sql.batchSet([
|
|
256
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
257
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
258
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
259
|
-
])
|
|
260
|
-
const { result } = renderHook(() =>
|
|
261
|
-
useSqliteValue(
|
|
262
|
-
sql,
|
|
263
|
-
{
|
|
264
|
-
sorBy: 'age',
|
|
265
|
-
select: (d) => d.name,
|
|
266
|
-
},
|
|
267
|
-
[],
|
|
268
|
-
),
|
|
269
|
-
)
|
|
270
|
-
await waitFor(() => {
|
|
271
|
-
expect(result.current[0]).toEqual(['Alice', 'Bob', 'Carol'])
|
|
272
|
-
})
|
|
273
|
-
})
|
|
274
|
-
})
|