muya 2.4.0 → 2.4.2

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 O}from"./select-sql";import{createTable as R,DEFAULT_STEP_SIZE as M}from"./table/table";function K(g){const k=P();function m(e){return`state-${k}-search-${e}`}let h;async function o(){if(!h){const{backend:e,...n}=g,t=e instanceof Promise?await e:e;h=await R({backend:t,...n})}return h}const c=new Map,i=new Map,S=new Map;async function p(e,n){const t=S.get(e),{options:a={}}=n,{stepSize:s=M}=a;if(!t)return!1;const r=[];for(let l=0;l<s;l++){const u=await t.next();if(u.done){S.delete(e);break}n.keys.has(String(u.value.rowId))||(r.push(u.value.document),n.keys.add(String(u.value.rowId)))}return r.length===0||v(n.items,r)?!1:(n.items=[...n.items,...r],!0)}function b(e){const n=i.get(e);n&&n()}async function x(e){const n=await o(),t=c.get(e);if(!t)return;const{options:a}=t,s=n.search({...a,select:(r,{rowId:l})=>({document:r,rowId:l})});S.set(e,s),t.keys=new Set,t.items=[],await p(e,t)}async function y(e){await x(e),b(e)}function T(e){const{key:n,op:t}=e,a=new Set;for(const[s,{keys:r}]of c)switch(t){case"delete":case"update":{r.has(String(n))&&a.add(s);break}case"insert":{a.add(s);break}}return a}async function d(e){const n=new Set;for(const t of e){const a=T(t);for(const s of a)n.add(s)}for(const t of n){const a=m(t);f.schedule(a,{searchId:t})}}const w=new Set;function D(e,n){c.has(e)||(c.set(e,{items:[],options:n,keys:new Set}),n&&y(e));const t=c.get(e);return n&&(t.options=n),t}const I={clear(e){c.delete(e)},async set(e){const t=await(await o()).set(e);return await d([t]),t},async batchSet(e){const t=await(await o()).batchSet(e);return await d(t),t},async delete(e){const t=await(await o()).delete(e);return t&&await d([t]),t},async deleteBy(e){const t=await(await o()).deleteBy(e);return await d(t),t},async get(e,n){return(await o()).get(e,n)},async*search(e={}){const n=await o();for await(const t of n.search(e))yield t},async count(e){return await(await o()).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){const t=m(e),a=f.add(t,{onScheduleDone(){y(e)}});return w.add(a),i.has(e)||i.set(e,n),()=>{i.delete(e),a()}},getSnapshot(e){return D(e).items},refresh:y,destroy(){for(const e of w)e();c.clear(),i.clear()},async next(e){const n=c.get(e);if(n){const t=await p(e,n);return t&&b(e),t}return!1},select(e){return O(I,e)}};return I}export{K as createSqliteState};
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 O}from"./select-sql";import{createTable as R,DEFAULT_STEP_SIZE as M}from"./table/table";function A(k){const g=P();function m(e){return`state-${g}-search-${e}`}let h;async function o(){if(!h){const{backend:e,...n}=k,t=e instanceof Promise?await e:e;h=await R({backend:t,...n})}return h}const c=new Map,d=new Map,y=new Map;async function p(e,n){const t=y.get(e),{options:a={}}=n,{stepSize:s=M}=a;if(!t)return!1;const r=[];for(let u=0;u<s;u++){const i=await t.next();if(i.done){y.delete(e);break}n.keys.has(String(i.value.key))||(r.push(i.value.document),n.keys.add(String(i.value.key)))}return r.length===0||v(n.items,r)?!1:(n.items=[...n.items,...r],!0)}function b(e){const n=d.get(e);n&&n()}async function x(e){const n=await o(),t=c.get(e);if(!t)return;const{options:a}=t,s=n.search({...a,select:(r,{rowId:u,key:i})=>({document:r,rowId:u,key:i})});y.set(e,s),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[s,{keys:r}]of c)switch(t){case"delete":case"update":{r.has(String(n))&&a.add(s);break}case"insert":{a.add(s);break}}return a}async function l(e){const n=new Set;for(const t of e){const a=T(t);for(const s of a)n.add(s)}for(const t of n){const a=m(t);f.schedule(a,{searchId:t})}}const D=new Set;function w(e,n){c.has(e)||(c.set(e,{items:[],options:n,keys:new Set}),n&&S(e));const t=c.get(e);return n&&(t.options=n),t}const I={clear(e){c.delete(e)},async set(e){const t=await(await o()).set(e);return await l([t]),t},async batchSet(e){const t=await(await o()).batchSet(e);return await l(t),t},async delete(e){const t=await(await o()).delete(e);return t&&await l([t]),t},async deleteBy(e){const t=await(await o()).deleteBy(e);return await l(t),t},async get(e,n){return(await o()).get(e,n)},async*search(e={}){const n=await o();for await(const t of n.search(e))yield t},async count(e){return await(await o()).count(e)},updateSearchOptions(e,n){const t=w(e,n);t.options=n;const a=m(e);f.schedule(a,{searchId:e})},subscribe(e,n){const t=m(e),a=f.add(t,{onScheduleDone(){S(e)}});return D.add(a),d.has(e)||d.set(e,n),()=>{d.delete(e),a()}},getSnapshot(e){return w(e).items},refresh:S,destroy(){for(const e of D)e();c.clear(),d.clear()},async next(e){const n=c.get(e);if(n){const t=await p(e,n);return t&&b(e),t}return!1},select(e){return O(I,e)}};return I}export{A as createSqliteState};
@@ -1,4 +1,4 @@
1
- import{unicodeTokenizer as _}from"./tokenizer";import{getWhereQuery as h}from"./where";const D=500,C=100;function R(l){return"$."+l}function M(l,a){if(!(!l||!a))return a.split(".").reduce((t,f)=>{if(typeof t=="object"&&t!==null&&f in t)return t[f]},l)}async function z(l){const{backend:a,tableName:t,indexes:f,key:$,disablePragmaOptimization:I}=l,d=$!==void 0;I||(await a.execute("PRAGMA journal_mode=WAL;"),await a.execute("PRAGMA synchronous=NORMAL;"),await a.execute("PRAGMA temp_store=MEMORY;"),await a.execute("PRAGMA cache_size=-20000;")),d?await a.execute(`
1
+ import{unicodeTokenizer as C}from"./tokenizer";import{getWhereQuery as k}from"./where";const L=500,M=100;function R(l){return"$."+l}function U(l,a){if(!(!l||!a))return a.split(".").reduce((t,y)=>{if(typeof t=="object"&&t!==null&&y in t)return t[y]},l)}async function z(l){const{backend:a,tableName:t,indexes:y,key:$,disablePragmaOptimization:x}=l,u=$!==void 0;x||(await a.execute("PRAGMA journal_mode=WAL;"),await a.execute("PRAGMA synchronous=NORMAL;"),await a.execute("PRAGMA temp_store=MEMORY;"),await a.execute("PRAGMA cache_size=-20000;")),u?await a.execute(`
2
2
  CREATE TABLE IF NOT EXISTS ${t} (
3
3
  key TEXT PRIMARY KEY,
4
4
  data TEXT NOT NULL
@@ -7,8 +7,8 @@ import{unicodeTokenizer as _}from"./tokenizer";import{getWhereQuery as h}from"./
7
7
  CREATE TABLE IF NOT EXISTS ${t} (
8
8
  data TEXT NOT NULL
9
9
  );
10
- `);let E;const T=[],y={};for(const e of f??[])if(typeof e=="string"&&e.startsWith("fts:")){const n=e.slice(4),r=n.replaceAll(".","_");T.push(n),y[n]=r}else if(typeof e=="object"&&e.type==="fts"){const n=e.path,r=n.replaceAll(".","_");if(T.push(n),y[n]=r,e.tokenizer){if(!E)E=e.tokenizer;else if(E!==e.tokenizer)throw new Error(`Conflicting FTS tokenizers: already using "${E}", got "${e.tokenizer}"`)}}else{const n=String(e);await a.execute(`CREATE INDEX IF NOT EXISTS idx_${t}_${n.replaceAll(/\W/g,"_")}
11
- ON ${t} (json_extract(data, '${R(n)}'));`)}if(T.length>0){let e;typeof E=="object"?e=_(E):E===void 0?e='"unicode61", "remove_diacritics=1"':e=E;const n=T.map(o=>y[o]).join(", "),r=`
10
+ `);let d;const T=[],f={};for(const e of y??[])if(typeof e=="string"&&e.startsWith("fts:")){const n=e.slice(4),r=n.replaceAll(".","_");T.push(n),f[n]=r}else if(typeof e=="object"&&e.type==="fts"){const n=e.path,r=n.replaceAll(".","_");if(T.push(n),f[n]=r,e.tokenizer){if(!d)d=e.tokenizer;else if(d!==e.tokenizer)throw new Error(`Conflicting FTS tokenizers: already using "${d}", got "${e.tokenizer}"`)}}else{const n=String(e);await a.execute(`CREATE INDEX IF NOT EXISTS idx_${t}_${n.replaceAll(/\W/g,"_")}
11
+ ON ${t} (json_extract(data, '${R(n)}'));`)}if(T.length>0){let e;typeof d=="object"?e=C(d):d===void 0?e='"unicode61", "remove_diacritics=1"':e=d;const n=T.map(o=>f[o]).join(", "),r=`
12
12
  CREATE VIRTUAL TABLE IF NOT EXISTS ${t}_fts
13
13
  USING fts5(${n}, tokenize=${e});
14
14
  `;await a.execute(r),await a.execute(`
@@ -32,7 +32,7 @@ import{unicodeTokenizer as _}from"./tokenizer";import{getWhereQuery as h}from"./
32
32
  AFTER UPDATE ON ${t}
33
33
  BEGIN
34
34
  UPDATE ${t}_fts
35
- SET ${T.map(o=>`${y[o]}=json_extract(new.data, '${R(o)}')`).join(", ")}
35
+ SET ${T.map(o=>`${f[o]}=json_extract(new.data, '${R(o)}')`).join(", ")}
36
36
  WHERE rowid = old.rowid;
37
37
  END;
38
- `)}function N(e){if(d)return M(e,String($))}async function L(e){return(await e.select("SELECT changes() AS c"))[0]?.c??0}const O={backend:a,async set(e,n){const r=n??a,o=JSON.stringify(e);if(d){const s=N(e);if(s==null)throw new Error(`Document is missing the configured key "${String($)}".`);if(await r.execute(`UPDATE ${t} SET data = ? WHERE key = ?`,[o,s]),await L(r)===1)return{key:s,op:"update"};try{return await r.execute(`INSERT INTO ${t} (key, data) VALUES (?, ?)`,[s,o]),{key:s,op:"insert"}}catch{return await r.execute(`UPDATE ${t} SET data = ? WHERE key = ?`,[o,s]),{key:s,op:"update"}}}await r.execute(`INSERT INTO ${t} (data) VALUES (?)`,[o]);const u=(await r.select("SELECT last_insert_rowid() AS id"))[0]?.id;if(typeof u!="number")throw new Error("Failed to retrieve last_insert_rowid()");return{key:u,op:"insert"}},async get(e,n=r=>r){const r=d?"key = ?":"rowid = ?",o=await a.select(`SELECT rowid, data FROM ${t} WHERE ${r}`,[e]);if(o.length===0)return;const{data:c,rowid:u}=o[0],s=JSON.parse(c);return n(s,{rowId:u})},async delete(e){const n=d?"key = ?":"rowid = ?";if(await a.execute(`DELETE FROM ${t} WHERE ${n}`,[e]),((await a.select("SELECT changes() AS c"))[0]?.c??0)>0)return{key:e,op:"delete"}},async*search(e={}){const{sortBy:n,order:r="asc",limit:o,offset:c=0,where:u,select:s=w=>w,stepSize:i=C}=e,p=h(u,t),A=`SELECT rowid, data FROM ${t} ${p}`;let S=0,k=c;for(;;){let w=A;n?w+=` ORDER BY json_extract(data, '${R(String(n))}') COLLATE NOCASE ${r.toUpperCase()}`:w+=d?` ORDER BY key COLLATE NOCASE ${r.toUpperCase()}`:` ORDER BY rowid ${r.toUpperCase()}`;const g=o?Math.min(i,o-S):i;w+=` LIMIT ${g} OFFSET ${k}`;const m=await a.select(w);if(m.length===0)break;for(const{rowid:x,data:b}of m){if(o&&S>=o)return;const F=JSON.parse(b);yield s(F,{rowId:x}),S++}if(m.length<g||o&&S>=o)break;k+=m.length}},async count(e={}){const n=h(e.where,t),r=`SELECT COUNT(*) as count FROM ${t} ${n}`;return(await a.select(r))[0]?.count??0},async deleteBy(e){const n=h(e,t),r=d?"key":"rowid",o=[];return await a.transaction(async c=>{const u=await c.select(`SELECT ${r} AS k FROM ${t} ${n}`);if(u.length===0)return;const s=u.map(i=>i.k);for(let i=0;i<s.length;i+=D){const p=s.slice(i,i+D),A=p.map(()=>"?").join(",");await c.execute(`DELETE FROM ${t} WHERE ${r} IN (${A})`,p)}for(const i of s)o.push({key:i,op:"delete"})}),o},async clear(){await a.execute(`DELETE FROM ${t}`)},async batchSet(e){const n=[];return await a.transaction(async r=>{for(const o of e){const c=await O.set(o,r);n.push(c)}}),n}};return O}export{C as DEFAULT_STEP_SIZE,z as createTable,M as getByPath,R as toJsonPath};
38
+ `)}function A(e){if(u)return U(e,String($))}async function b(e){return(await e.select("SELECT changes() AS c"))[0]?.c??0}const g={backend:a,async set(e,n){const r=n??a,o=JSON.stringify(e);if(u){const s=A(e);if(s==null)throw new Error(`Document is missing the configured key "${String($)}".`);if(await r.execute(`UPDATE ${t} SET data = ? WHERE key = ?`,[o,s]),await b(r)===1)return{key:s,op:"update"};try{return await r.execute(`INSERT INTO ${t} (key, data) VALUES (?, ?)`,[s,o]),{key:s,op:"insert"}}catch{return await r.execute(`UPDATE ${t} SET data = ? WHERE key = ?`,[o,s]),{key:s,op:"update"}}}await r.execute(`INSERT INTO ${t} (data) VALUES (?)`,[o]);const c=(await r.select("SELECT last_insert_rowid() AS id"))[0]?.id;if(typeof c!="number")throw new Error("Failed to retrieve last_insert_rowid()");return{key:c,op:"insert"}},async get(e,n=r=>r){const r=u?"key = ?":"rowid = ?",o=await a.select(`SELECT rowid, data FROM ${t} WHERE ${r}`,[e]);if(o.length===0)return;const{data:E,rowid:c}=o[0],s=JSON.parse(E),i=u?A(s)??c:c;return n(s,{rowId:c,key:i})},async delete(e){const n=u?"key = ?":"rowid = ?";if(await a.execute(`DELETE FROM ${t} WHERE ${n}`,[e]),((await a.select("SELECT changes() AS c"))[0]?.c??0)>0)return{key:e,op:"delete"}},async*search(e={}){const{sortBy:n,order:r="asc",limit:o,offset:E=0,where:c,select:s=w=>w,stepSize:i=M}=e,p=k(c,t),h=`SELECT rowid, data FROM ${t} ${p}`;let S=0,D=E;for(;;){let w=h;n?w+=` ORDER BY json_extract(data, '${R(String(n))}') COLLATE NOCASE ${r.toUpperCase()}`:w+=u?` ORDER BY key COLLATE NOCASE ${r.toUpperCase()}`:` ORDER BY rowid ${r.toUpperCase()}`;const I=o?Math.min(i,o-S):i;w+=` LIMIT ${I} OFFSET ${D}`;const m=await a.select(w);if(m.length===0)break;for(const{rowid:O,data:F}of m){if(o&&S>=o)return;const N=JSON.parse(F),_=u?A(N)??O:O;yield s(N,{rowId:O,key:_}),S++}if(m.length<I||o&&S>=o)break;D+=m.length}},async count(e={}){const n=k(e.where,t),r=`SELECT COUNT(*) as count FROM ${t} ${n}`;return(await a.select(r))[0]?.count??0},async deleteBy(e){const n=k(e,t),r=u?"key":"rowid",o=[];return await a.transaction(async E=>{const c=await E.select(`SELECT ${r} AS k FROM ${t} ${n}`);if(c.length===0)return;const s=c.map(i=>i.k);for(let i=0;i<s.length;i+=L){const p=s.slice(i,i+L),h=p.map(()=>"?").join(",");await E.execute(`DELETE FROM ${t} WHERE ${r} IN (${h})`,p)}for(const i of s)o.push({key:i,op:"delete"})}),o},async clear(){await a.execute(`DELETE FROM ${t}`)},async batchSet(e){const n=[];return await a.transaction(async r=>{for(const o of e){const E=await g.set(o,r);n.push(E)}}),n}};return g}export{M as DEFAULT_STEP_SIZE,z as createTable,U as getByPath,R as toJsonPath};
@@ -1 +1 @@
1
- import{useCallback as r,useDebugValue as S,useEffect as p,useId as a,useLayoutEffect as f,useMemo as D}from"react";import{isError as y,isPromise as x}from"../utils/is";import{useSyncExternalStoreWithSelector as b}from"use-sync-external-store/shim/with-selector";function O(e,s={},u=[]){const{select:c}=s,t=a();f(()=>{e.updateSearchOptions(t,{...s,select:void 0})},u),p(()=>()=>{e.clear(t)},[]);const l=r(o=>c?o.map(c):o,[c]),d=r(o=>e.subscribe(t,o),[e,t]),i=r(()=>e.getSnapshot(t),[e,t]),n=b(d,i,i,l);if(S(n),x(n)||y(n))throw n;const m=D(()=>({next:()=>e.next(t),reset:()=>e.refresh(t)}),[t,e]);return[n,m]}export{O as useSqliteValue};
1
+ import{useCallback as l,useDebugValue as m,useEffect as a,useLayoutEffect as p,useMemo as f}from"react";import{isError as y,isPromise as D}from"../utils/is";import{useSyncExternalStoreWithSelector as h}from"use-sync-external-store/shim/with-selector";function x(n){const{limit:o,offset:i,order:c,sortBy:t,where:s,stepSize:u,select:r}=n;let e="";return o!==void 0&&(e+=`l${o}`),i!==void 0&&(e+=`o${i}`),c!==void 0&&(e+=`r${c}`),t!==void 0&&(e+=`s${t}`),s!==void 0&&(e+=`w${JSON.stringify(s)}`),u!==void 0&&(e+=`t${u}`),r!==void 0&&(e+=`f${r.toString()}`),e}function O(n,o={},i=[]){const{select:c}=o,t=f(()=>x({...o,select:void 0}),[o]);p(()=>{n.updateSearchOptions(t,{...o,select:void 0})},i),a(()=>()=>{n.clear(t)},[]);const s=l(d=>c?d.map(c):d,[c]),u=l(d=>n.subscribe(t,d),[n,t]),r=l(()=>n.getSnapshot(t),[n,t]),e=h(u,r,r,s);if(m(e),D(e)||y(e))throw e;const S=f(()=>({next:()=>n.next(t),reset:()=>n.refresh(t)}),[t,n]);return[e,S]}export{O as useSqliteValue};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muya",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "author": "samuel.gjabel@gmail.com",
5
5
  "repository": "https://github.com/samuelgjabel/muya",
6
6
  "main": "cjs/index.js",
@@ -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: 'State3', key: 'id' })
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: 'State4', key: 'id' })
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: 'State5', key: 'id' })
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: 'State6', key: 'id' })
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: 'State6', key: 'id' })
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
@@ -324,4 +324,86 @@ describe('use-sqlite-state', () => {
324
324
  expect(result2.current[0].length).toBe(50)
325
325
  })
326
326
  })
327
+
328
+ it('should handle update of deep fields with deep id', async () => {
329
+ interface DeepItem {
330
+ person: {
331
+ id: string
332
+ name: string
333
+ age: number
334
+ }
335
+ }
336
+ const sql = createSqliteState<DeepItem>({ backend, tableName: 'State10', key: 'person.id' })
337
+ let reRenders = 0
338
+ const { result } = renderHook(() => {
339
+ reRenders++
340
+ return useSqliteValue(sql, { sortBy: 'person.age' }, [])
341
+ })
342
+
343
+ await waitFor(() => {
344
+ expect(reRenders).toBe(2)
345
+ expect(result.current[0].length).toBe(0)
346
+ })
347
+
348
+ act(() => {
349
+ sql.set({ person: { id: 'some_id', name: 'Alice', age: 30 } })
350
+ })
351
+ await waitFor(() => {
352
+ expect(reRenders).toBe(3)
353
+ expect(result.current[0]).toEqual([{ person: { id: 'some_id', name: 'Alice', age: 30 } }])
354
+ })
355
+
356
+ // update deep field
357
+ act(() => {
358
+ sql.set({ person: { id: 'some_id', name: 'Alice', age: 31 } })
359
+ })
360
+ await waitFor(() => {
361
+ // expect(reRenders).toBe(4)
362
+ expect(result.current[0]).toEqual([{ person: { id: 'some_id', name: 'Alice', age: 31 } }])
363
+ })
364
+
365
+ // update same field
366
+ act(() => {
367
+ sql.set({ person: { id: 'some_id', name: 'Alice', age: 31 } })
368
+ })
369
+ // should not re-render
370
+ await waitFor(() => {
371
+ expect(result.current[0]).toEqual([{ person: { id: 'some_id', name: 'Alice', age: 31 } }])
372
+ })
373
+
374
+ // add another item
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
+ })
327
409
  })
@@ -77,7 +77,7 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
77
77
 
78
78
  interface NextResult {
79
79
  document: Document
80
- rowId: number
80
+ key: Key
81
81
  }
82
82
  // const emitter = createEmitter<Table<Document>>()
83
83
  const cachedData = new Map<SearchId, DataItems<Document>>()
@@ -104,9 +104,9 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
104
104
  break
105
105
  }
106
106
 
107
- if (!data.keys.has(String(result.value.rowId))) {
107
+ if (!data.keys.has(String(result.value.key))) {
108
108
  newItems.push(result.value.document)
109
- data.keys.add(String(result.value.rowId))
109
+ data.keys.add(String(result.value.key))
110
110
  }
111
111
  }
112
112
 
@@ -136,7 +136,7 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
136
136
  const data = cachedData.get(searchId)
137
137
  if (!data) return
138
138
  const { options: refreshOptions } = data
139
- const iterator = table.search({ ...refreshOptions, select: (document, { rowId }) => ({ document, rowId }) })
139
+ const iterator = table.search({ ...refreshOptions, select: (document, { rowId, key }) => ({ document, rowId, key }) })
140
140
  iterators.set(searchId, iterator)
141
141
  data.keys = new Set()
142
142
  data.items = []
@@ -209,7 +209,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
209
209
 
210
210
  async get<Selected = Document>(
211
211
  keyValue: Key,
212
- selector: (document: Document, meta: { rowId: number }) => Selected = (d) => d as unknown as Selected,
212
+ selector: (document: Document, meta: { rowId: number; key: Key }) => Selected = (d) => d as unknown as Selected,
213
213
  ) {
214
214
  const whereKey = hasUserKey ? `key = ?` : `rowid = ?`
215
215
  const result = await backend.select<Array<{ data: string; rowid: number }>>(
@@ -219,7 +219,8 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
219
219
  if (result.length === 0) return
220
220
  const { data, rowid } = result[0]
221
221
  const document = JSON.parse(data) as Document
222
- return selector(document, { rowId: rowid }) as Selected
222
+ const logicalKey = hasUserKey ? (getKeyFromDocument(document) ?? rowid) : rowid
223
+ return selector(document, { rowId: rowid, key: logicalKey }) as Selected
223
224
  },
224
225
 
225
226
  async delete(keyValue: Key) {
@@ -264,7 +265,9 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
264
265
  for (const { rowid, data } of results) {
265
266
  if (limit && yielded >= limit) return
266
267
  const document = JSON.parse(data) as Document
267
- yield select(document, { rowId: rowid }) as Selected
268
+ const logicalKey = hasUserKey ? (getKeyFromDocument(document) ?? rowid) : rowid
269
+ // Pass both rowId and logicalKey
270
+ yield select(document, { rowId: rowid, key: logicalKey }) as Selected
268
271
  yielded++
269
272
  }
270
273
 
@@ -47,7 +47,7 @@ export interface DbOptions<Document extends DocType> {
47
47
  }
48
48
 
49
49
  export interface SearchOptions<Document extends DocType, Selected = Document> extends SqlSeachOptions<Document> {
50
- readonly select?: (document: Document, meta: { rowId: number }) => Selected
50
+ readonly select?: (document: Document, meta: { rowId: number; key: Key }) => Selected
51
51
  }
52
52
 
53
53
  interface DbNotGeneric {
@@ -1,4 +1,4 @@
1
- import { useCallback, useDebugValue, useEffect, useId, useLayoutEffect, useMemo, type DependencyList } from 'react'
1
+ import { useCallback, useDebugValue, useEffect, useLayoutEffect, useMemo, type DependencyList } from 'react'
2
2
  import type { SyncTable } from './create-sqlite'
3
3
  import type { DocType } from './table/table.types'
4
4
  import { isError, isPromise } from '../utils/is'
@@ -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,16 @@ 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 id = useId()
67
+ const searchId = useMemo(() => generateCacheKey({ ...options, select: undefined }), [options])
36
68
 
37
69
  useLayoutEffect(() => {
38
- state.updateSearchOptions(id, { ...options, select: undefined })
70
+ state.updateSearchOptions(searchId, { ...options, select: undefined })
39
71
  // eslint-disable-next-line react-hooks/exhaustive-deps
40
72
  }, deps)
41
73
 
42
74
  useEffect(() => {
43
75
  return () => {
44
- state.clear(id)
76
+ state.clear(searchId)
45
77
  }
46
78
  // eslint-disable-next-line react-hooks/exhaustive-deps
47
79
  }, [])
@@ -56,14 +88,14 @@ export function useSqliteValue<Document extends DocType, Selected = Document>(
56
88
 
57
89
  const subscribe = useCallback(
58
90
  (onStorageChange: () => void) => {
59
- return state.subscribe(id, onStorageChange)
91
+ return state.subscribe(searchId, onStorageChange)
60
92
  },
61
- [state, id],
93
+ [state, searchId],
62
94
  )
63
95
 
64
96
  const getSnapshot = useCallback(() => {
65
- return state.getSnapshot(id)
66
- }, [state, id])
97
+ return state.getSnapshot(searchId)
98
+ }, [state, searchId])
67
99
 
68
100
  const value = useSyncExternalStoreWithSelector<Document[], Selected[]>(subscribe, getSnapshot, getSnapshot, selector)
69
101
 
@@ -77,9 +109,9 @@ export function useSqliteValue<Document extends DocType, Selected = Document>(
77
109
 
78
110
  const actions = useMemo((): SqLiteActions => {
79
111
  return {
80
- next: () => state.next(id),
81
- reset: () => state.refresh(id),
112
+ next: () => state.next(searchId),
113
+ reset: () => state.refresh(searchId),
82
114
  }
83
- }, [id, state])
115
+ }, [searchId, state])
84
116
  return [value as undefined extends Selected ? Document[] : Selected[], actions]
85
117
  }
@@ -28,6 +28,7 @@ export interface DbOptions<Document extends DocType> {
28
28
  export interface SearchOptions<Document extends DocType, Selected = Document> extends SqlSeachOptions<Document> {
29
29
  readonly select?: (document: Document, meta: {
30
30
  rowId: number;
31
+ key: Key;
31
32
  }) => Selected;
32
33
  }
33
34
  interface DbNotGeneric {
@@ -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
- })