muya 2.4.94 → 2.4.96

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 l}from"../create";import{getId as m}from"../utils/id";import{createTable as d}from"./table/table";function D(u){let s;async function a(){if(!s){const{backend:e,...n}=u,t=e instanceof Promise?await e:e;s=await d({backend:t,...n})}return s}const i=m();l.add(i,{onScheduleDone(e){if(!e)return;const n=e,t={};for(const c of n)c.removedAll&&(t.removedAll=!0),c.mutations&&(t.mutations||(t.mutations=[]),t.mutations.push(...c.mutations));for(const c of r)c(t)}});function o(e){l.schedule(i,e)}const r=new Set;return{subscribe(e){return r.add(e),()=>r.delete(e)},async clear(){const e=await a();return o({removedAll:!0}),e.clear()},async set(e){const t=await(await a()).set(e);return o({mutations:[t]}),t},async batchSet(e){const t=await(await a()).batchSet(e);return o({mutations:t}),t},async delete(e){const t=await(await a()).delete(e);return t&&o({mutations:[t]}),t},async deleteBy(e){const t=await(await a()).deleteBy(e);return o({mutations:t}),t},async get(e,n){return(await a()).get(e,n)},async*search(e={}){const n=await a();for await(const t of n.search(e))yield t},async count(e){return await(await a()).count(e)}}}export{D as createSqliteState};
1
+ import{STATE_SCHEDULER as u}from"../create";import{getId as m}from"../utils/id";import{createTable as d}from"./table/table";function h(l){let s;async function o(){if(!s){const{backend:e,...n}=l,t=e instanceof Promise?await e:e;s=await d({backend:t,...n})}return s}const i=m();u.add(i,{onScheduleDone(e){if(!e)return;const n=e,t={};for(const c of n)c.removedAll&&(t.removedAll=!0),c.mutations&&(t.mutations||(t.mutations=[]),t.mutations.push(...c.mutations));for(const c of r)c(t)}});function a(e){u.schedule(i,e)}const r=new Set;return{subscribe(e){return r.add(e),()=>r.delete(e)},async clear(){const e=await o();return a({removedAll:!0}),e.clear()},async set(e){const t=await(await o()).set(e);return a({mutations:[t]}),t},async batchSet(e){const t=await(await o()).batchSet(e);return a({mutations:t}),t},async batchDelete(e){const t=await(await o()).batchDelete(e);return a({mutations:t}),t},async delete(e){const t=await(await o()).delete(e);return t&&a({mutations:[t]}),t},async deleteBy(e){const t=await(await o()).deleteBy(e);return a({mutations: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)}}}export{h as createSqliteState};
@@ -1,4 +1,4 @@
1
- import{unicodeTokenizer as _}from"./tokenizer";import{getWhereQuery as g}from"./where";const x=500,C=100;function m(l){return"$."+l}function M(l,s){if(!(!l||!s))return s.split(".").reduce((t,y)=>{if(typeof t=="object"&&t!==null&&y in t)return t[y]},l)}async function G(l){const{backend:s,tableName:t,indexes:y,key:$,disablePragmaOptimization:L}=l,u=$!==void 0;L||(await s.execute("PRAGMA journal_mode=WAL;"),await s.execute("PRAGMA synchronous=NORMAL;"),await s.execute("PRAGMA temp_store=MEMORY;"),await s.execute("PRAGMA cache_size=-20000;")),u?await s.execute(`
1
+ import{unicodeTokenizer as _}from"./tokenizer";import{getWhereQuery as g}from"./where";const x=500,C=100;function R(l){return"$."+l}function M(l,s){if(!(!l||!s))return s.split(".").reduce((t,w)=>{if(typeof t=="object"&&t!==null&&w in t)return t[w]},l)}async function G(l){const{backend:s,tableName:t,indexes:w,key:$,disablePragmaOptimization:L}=l,E=$!==void 0;L||(await s.execute("PRAGMA journal_mode=WAL;"),await s.execute("PRAGMA synchronous=NORMAL;"),await s.execute("PRAGMA temp_store=MEMORY;"),await s.execute("PRAGMA cache_size=-20000;")),E?await s.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 g}from"./
7
7
  CREATE TABLE IF NOT EXISTS ${t} (
8
8
  data TEXT NOT NULL
9
9
  );
10
- `);let d;const T=[],f={};for(const e of y??[])if(typeof e=="string"&&e.startsWith("fts:")){const n=e.slice(4),o=n.replaceAll(".","_");T.push(n),f[n]=o}else if(typeof e=="object"&&e.type==="fts"){const n=e.path,o=n.replaceAll(".","_");if(T.push(n),f[n]=o,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 s.execute(`CREATE INDEX IF NOT EXISTS idx_${t}_${n.replaceAll(/\W/g,"_")}
11
- ON ${t} (json_extract(data, '${m(n)}'));`)}if(T.length>0){let e;typeof d=="object"?e=_(d):d===void 0?e='"unicode61", "remove_diacritics=1"':e=d;const n=T.map(r=>f[r]).join(", "),o=`
10
+ `);let d;const T=[],f={};for(const e of w??[])if(typeof e=="string"&&e.startsWith("fts:")){const n=e.slice(4),o=n.replaceAll(".","_");T.push(n),f[n]=o}else if(typeof e=="object"&&e.type==="fts"){const n=e.path,o=n.replaceAll(".","_");if(T.push(n),f[n]=o,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 s.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=_(d):d===void 0?e='"unicode61", "remove_diacritics=1"':e=d;const n=T.map(r=>f[r]).join(", "),o=`
12
12
  CREATE VIRTUAL TABLE IF NOT EXISTS ${t}_fts
13
13
  USING fts5(${n}, tokenize=${e});
14
14
  `;await s.execute(o),await s.execute(`
@@ -18,7 +18,7 @@ import{unicodeTokenizer as _}from"./tokenizer";import{getWhereQuery as g}from"./
18
18
  INSERT INTO ${t}_fts(rowid, ${n})
19
19
  VALUES (
20
20
  new.rowid,
21
- ${T.map(r=>`json_extract(new.data, '${m(r)}')`).join(", ")}
21
+ ${T.map(r=>`json_extract(new.data, '${R(r)}')`).join(", ")}
22
22
  );
23
23
  END;
24
24
  `),await s.execute(`
@@ -32,7 +32,7 @@ import{unicodeTokenizer as _}from"./tokenizer";import{getWhereQuery as g}from"./
32
32
  AFTER UPDATE ON ${t}
33
33
  BEGIN
34
34
  UPDATE ${t}_fts
35
- SET ${T.map(r=>`${f[r]}=json_extract(new.data, '${m(r)}')`).join(", ")}
35
+ SET ${T.map(r=>`${f[r]}=json_extract(new.data, '${R(r)}')`).join(", ")}
36
36
  WHERE rowid = old.rowid;
37
37
  END;
38
- `)}function A(e){if(u)return M(e,String($))}const k={backend:s,async set(e,n){const o=n??s,r=JSON.stringify(e);if(u){const a=A(e);if(a==null)throw new Error(`Document is missing the configured key "${String($)}".`);return(await o.select(`SELECT key FROM ${t} WHERE key = ?`,[a])).length>0?(await o.execute(`UPDATE ${t} SET data = ? WHERE key = ?`,[r,a]),{key:a,op:"update"}):(await o.execute(`INSERT INTO ${t} (key, data) VALUES (?, ?)`,[a,r]),{key:a,op:"insert"})}await o.execute(`INSERT INTO ${t} (data) VALUES (?)`,[r]);const c=(await o.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=o=>o){const o=u?"key = ?":"rowid = ?",r=await s.select(`SELECT rowid, data FROM ${t} WHERE ${o}`,[e]);if(r.length===0)return;const{data:E,rowid:c}=r[0],a=JSON.parse(E),i=u?A(a)??c:c;return n(a,{rowId:c,key:i})},async delete(e){const n=u?"key = ?":"rowid = ?";if(await s.execute(`DELETE FROM ${t} WHERE ${n}`,[e]),((await s.select("SELECT changes() AS c"))[0]?.c??0)>0)return{key:e,op:"delete"}},async*search(e={}){const{sortBy:n,order:o="asc",limit:r,offset:E=0,where:c,select:a=w=>w,pageSize:i=C}=e,S=g(c,t),h=`SELECT rowid, data FROM ${t} ${S}`;let p=0,I=E;for(;;){let w=h;n?w+=` ORDER BY json_extract(data, '${m(String(n))}') COLLATE NOCASE ${o.toUpperCase()}`:w+=u?` ORDER BY key COLLATE NOCASE ${o.toUpperCase()}`:` ORDER BY rowid ${o.toUpperCase()}`;const N=r?Math.min(i,r-p):i;w+=` LIMIT ${N} OFFSET ${I}`;const R=await s.select(w);if(R.length===0)break;for(const{rowid:O,data:F}of R){if(r&&p>=r)return;const D=JSON.parse(F),b=u?A(D)??O:O;yield a(D,{rowId:O,key:b}),p++}if(R.length<N||r&&p>=r)break;I+=R.length}},async count(e={}){const n=g(e.where,t),o=`SELECT COUNT(*) as count FROM ${t} ${n}`;return(await s.select(o))[0]?.count??0},async deleteBy(e){const n=g(e,t),o=u?"key":"rowid",r=[];return await s.transaction(async E=>{const c=await E.select(`SELECT ${o} AS k FROM ${t} ${n}`);if(c.length===0)return;const a=c.map(i=>i.k);for(let i=0;i<a.length;i+=x){const S=a.slice(i,i+x),h=S.map(()=>"?").join(",");await E.execute(`DELETE FROM ${t} WHERE ${o} IN (${h})`,S)}for(const i of a)r.push({key:i,op:"delete"})}),r},async clear(){await s.execute(`DELETE FROM ${t}`)},async batchSet(e){const n=[];return await s.transaction(async o=>{for(const r of e){const E=await k.set(r,o);n.push(E)}}),n}};return k}export{C as DEFAULT_PAGE_SIZE,G as createTable,M as getByPath,m as toJsonPath};
38
+ `)}function h(e){if(E)return M(e,String($))}const A={backend:s,async set(e,n){const o=n??s,r=JSON.stringify(e);if(E){const a=h(e);if(a==null)throw new Error(`Document is missing the configured key "${String($)}".`);return(await o.select(`SELECT key FROM ${t} WHERE key = ?`,[a])).length>0?(await o.execute(`UPDATE ${t} SET data = ? WHERE key = ?`,[r,a]),{key:a,op:"update",document:e}):(await o.execute(`INSERT INTO ${t} (key, data) VALUES (?, ?)`,[a,r]),{key:a,op:"insert",document:e})}await o.execute(`INSERT INTO ${t} (data) VALUES (?)`,[r]);const u=(await o.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",document:e}},async get(e,n=o=>o){const o=E?"key = ?":"rowid = ?",r=await s.select(`SELECT rowid, data FROM ${t} WHERE ${o}`,[e]);if(r.length===0)return;const{data:c,rowid:u}=r[0],a=JSON.parse(c),i=E?h(a)??u:u;return n(a,{rowId:u,key:i})},async delete(e,n){const o=n??s,r=E?"key = ?":"rowid = ?";if(await o.execute(`DELETE FROM ${t} WHERE ${r}`,[e]),((await s.select("SELECT changes() AS c"))[0]?.c??0)>0)return{key:e,op:"delete"}},async*search(e={}){const{sortBy:n,order:o="asc",limit:r,offset:c=0,where:u,select:a=y=>y,pageSize:i=C}=e,p=g(u,t),k=`SELECT rowid, data FROM ${t} ${p}`;let S=0,D=c;for(;;){let y=k;n?y+=` ORDER BY json_extract(data, '${R(String(n))}') COLLATE NOCASE ${o.toUpperCase()}`:y+=E?` ORDER BY key COLLATE NOCASE ${o.toUpperCase()}`:` ORDER BY rowid ${o.toUpperCase()}`;const I=r?Math.min(i,r-S):i;y+=` LIMIT ${I} OFFSET ${D}`;const m=await s.select(y);if(m.length===0)break;for(const{rowid:O,data:b}of m){if(r&&S>=r)return;const N=JSON.parse(b),F=E?h(N)??O:O;yield a(N,{rowId:O,key:F}),S++}if(m.length<I||r&&S>=r)break;D+=m.length}},async count(e={}){const n=g(e.where,t),o=`SELECT COUNT(*) as count FROM ${t} ${n}`;return(await s.select(o))[0]?.count??0},async deleteBy(e){const n=g(e,t),o=E?"key":"rowid",r=[];return await s.transaction(async c=>{const u=await c.select(`SELECT ${o} AS k FROM ${t} ${n}`);if(u.length===0)return;const a=u.map(i=>i.k);for(let i=0;i<a.length;i+=x){const p=a.slice(i,i+x),k=p.map(()=>"?").join(",");await c.execute(`DELETE FROM ${t} WHERE ${o} IN (${k})`,p)}for(const i of a)r.push({key:i,op:"delete",document:void 0})}),r},async clear(){await s.execute(`DELETE FROM ${t}`)},async batchSet(e){const n=[];return await s.transaction(async o=>{for(const r of e){const c=await A.set(r,o);n.push(c)}}),n},async batchDelete(e){const n=[];return await s.transaction(async o=>{for(const r of e){const c=await A.delete(r,o);c&&n.push(c)}}),n}};return A}export{C as DEFAULT_PAGE_SIZE,G as createTable,M as getByPath,R as toJsonPath};
@@ -1 +1 @@
1
- import{useCallback as f,useLayoutEffect as P,useReducer as _,useRef as D}from"react";import{DEFAULT_PAGE_SIZE as C}from"./table";import{shallow as E}from"../utils/shallow";const T=1e4;function z(a,w={},I=[]){const{select:m,pageSize:q=C}=w,e=D(null),[,k]=_(c=>c+1,0),n=D(new Map),x=D(null),A=f(()=>{const{select:c,...i}=w;x.current=a.search({select:(u,l)=>({doc:u,meta:l}),...i})},[a,...I]),d=f(()=>{e.current=[],n.current.clear(),A()},[A]),y=f(async c=>{e.current===null&&(e.current=[]),c===!0&&d();const{current:i}=x;if(!i)return!0;let u=!1;for(let l=0;l<q;l++){const o=await i.next();if(o.done){x.current=null,u=!0;break}n.current.has(o.value.meta.key)||(e.current.push(m?m(o.value.doc):o.value.doc),n.current.set(o.value.meta.key,e.current.length-1))}return e.current=[...e.current],u},[]),p=f(async()=>{const c=await y(!1);return k(),c},[y]);P(()=>{const c=a.subscribe(async i=>{const{mutations:u,removedAll:l}=i;if(l&&d(),!u)return;const o=e.current?.length??0;let g=o,h=!1;const S=new Set;for(const s of u){const{key:r,op:b}=s;switch(b){case"insert":{g+=1;break}case"delete":{if(e.current&&e.current.length>0&&n.current.has(r)){const t=n.current.get(r);if(t===void 0)break;S.add(t),h=!0}break}case"update":{if(n.current.has(r)){const t=n.current.get(r);if(t!==void 0&&e.current){const v=await a.get(r,m),O=e.current[t];E(O,v)||(e.current[t]=v,e.current=[...e.current],h=!0)}}else{const t=await a.get(r,m);t&&(e.current=[...e.current??[],t],n.current.set(r,e.current.length-1),h=!0)}break}}}if(S.size>0&&e.current&&e.current.length>0){const s=new Map;e.current=e.current?.filter((b,t)=>!S.has(t));let r=0;for(const[b,t]of n.current)S.has(t)||(s.set(b,r),r++);n.current=s}const L=o!==g;if(L||h){if(L){await y(!0);let s=0;for(;(e.current?.length??0)<g&&s<T;)await y(!1),s++;s===T&&console.warn("Reached maximum iterations in fillNextPage loop. Possible duplicate or data issue.")}k()}});return()=>{c()}},[a]),P(()=>{d(),p()},I);const R=f(async()=>{d(),await p()},[p,d]);return[e.current,{nextPage:p,reset:R,keysIndex:n.current}]}export{z as useSqliteValue};
1
+ import{useCallback as y,useLayoutEffect as T,useReducer as C,useRef as w}from"react";import{DEFAULT_PAGE_SIZE as E}from"./table";import{shallow as K}from"../utils/shallow";const q=1e4;function F(d,k={},I=[]){const{select:f,pageSize:R=E}=k,e=w(null),[,A]=C(c=>c+1,0),t=w(new Map),g=w(null),L=y(()=>{const{select:c,...a}=k;g.current=d.search({select:(u,i)=>({doc:u,meta:i}),...a})},[d,...I]),m=y(()=>{e.current=[],t.current.clear(),L()},[L]),p=y(async c=>{e.current===null&&(e.current=[]),c===!0&&m();const{current:a}=g;if(!a)return!0;let u=!1;for(let i=0;i<R;i++){const o=await a.next();if(o.done){g.current=null,u=!0;break}t.current.has(o.value.meta.key)||(e.current.push(f?f(o.value.doc):o.value.doc),t.current.set(o.value.meta.key,e.current.length-1))}return e.current=[...e.current],u},[]),h=y(async()=>{const c=await p(!1);return A(),c},[p]);T(()=>{const c=d.subscribe(async a=>{const{mutations:u,removedAll:i}=a;if(i&&m(),!u)return;const o=e.current?.length??0;let D=o,S=!1;const b=new Set;for(const s of u){const{key:n,op:x,document:l}=s;switch(x){case"insert":{D+=1;break}case"delete":{if(e.current&&e.current.length>0&&t.current.has(n)){const r=t.current.get(n);if(r===void 0)break;b.add(r),S=!0}break}case"update":{if(t.current.has(n)){const r=t.current.get(n);if(r!==void 0&&e.current){const P=f?f(l):l,_=e.current[r];K(_,P)||(e.current[r]=P,e.current=[...e.current],S=!0)}}else{const r=await d.get(n,f);r&&(e.current=[...e.current??[],r],t.current.set(n,e.current.length-1),S=!0)}break}}}if(b.size>0&&e.current&&e.current.length>0){const s=new Map;e.current=e.current?.filter((x,l)=>!b.has(l));let n=0;for(const[x,l]of t.current)b.has(l)||(s.set(x,n),n++);t.current=s}const v=o!==D;if(v||S){if(v){await p(!0);let s=0;for(;(e.current?.length??0)<D&&s<q;)await p(!1),s++;s===q&&console.warn("Reached maximum iterations in fillNextPage loop. Possible duplicate or data issue.")}A()}});return()=>{c()}},[d]),T(()=>{m(),h()},I);const O=y(async()=>{m(),await h()},[h,m]);return[e.current,{nextPage:h,reset:O,keysIndex:t.current}]}export{F as useSqliteValue};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muya",
3
- "version": "2.4.94",
3
+ "version": "2.4.96",
4
4
  "author": "samuel.gjabel@gmail.com",
5
5
  "repository": "https://github.com/samuelgjabel/muya",
6
6
  "main": "cjs/index.js",
@@ -489,4 +489,35 @@ describe('use-sqlite-state', () => {
489
489
  expect(result.current[0]?.length).toBe(ITEMS_COUNT)
490
490
  })
491
491
  })
492
+ it("should test batch delete and its impact on the hook's results", async () => {
493
+ const sql = createSqliteState<Person>({ backend, tableName: 'BatchDeleteState', key: 'id' })
494
+ const people: Person[] = []
495
+ for (let index = 1; index <= 20; index++) {
496
+ people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
497
+ }
498
+ await sql.batchSet(people)
499
+
500
+ let reRenders = 0
501
+ const { result } = renderHook(() => {
502
+ reRenders++
503
+ return useSqliteValue(sql, {}, [])
504
+ })
505
+
506
+ await waitFor(() => {
507
+ expect(result.current[0]?.length).toBe(20)
508
+ expect(reRenders).toBe(2)
509
+ })
510
+
511
+ act(() => {
512
+ sql.batchDelete(['5', '10', '15'])
513
+ })
514
+
515
+ await waitFor(() => {
516
+ expect(result.current[0]?.length).toBe(17)
517
+ expect(result.current[0]?.find((p) => p.id === '5')).toBeUndefined()
518
+ expect(result.current[0]?.find((p) => p.id === '10')).toBeUndefined()
519
+ expect(result.current[0]?.find((p) => p.id === '15')).toBeUndefined()
520
+ expect(reRenders).toBe(3)
521
+ })
522
+ })
492
523
  })
@@ -9,21 +9,22 @@ export interface CreateSqliteOptions<Document extends DocType> extends Omit<DbOp
9
9
  readonly backend: Backend | Promise<Backend>
10
10
  }
11
11
 
12
- export interface MutationItems {
13
- mutations?: MutationResult[]
12
+ export interface MutationItems<Doc> {
13
+ mutations?: MutationResult<Doc>[]
14
14
  removedAll?: boolean
15
15
  }
16
16
 
17
17
  export interface SyncTable<Document extends DocType> {
18
- readonly subscribe: (listener: (mutation: MutationItems) => void) => () => void
19
- readonly set: (document: Document) => Promise<MutationResult>
20
- readonly batchSet: (documents: Document[]) => Promise<MutationResult[]>
18
+ readonly subscribe: (listener: (mutation: MutationItems<Document>) => void) => () => void
19
+ readonly set: (document: Document) => Promise<MutationResult<Document>>
20
+ readonly batchSet: (documents: Document[]) => Promise<MutationResult<Document>[]>
21
+ readonly batchDelete: (keys: Key[]) => Promise<MutationResult<Document>[]>
21
22
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>
22
23
 
23
- readonly delete: (key: Key) => Promise<MutationResult | undefined>
24
+ readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>
24
25
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>
25
26
  readonly count: (options?: { where?: Where<Document> }) => Promise<number>
26
- readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>
27
+ readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>
27
28
  readonly clear: () => Promise<void>
28
29
  }
29
30
 
@@ -53,8 +54,8 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
53
54
  if (!unknownItems) {
54
55
  return
55
56
  }
56
- const items = unknownItems as MutationItems[]
57
- const merged: MutationItems = {}
57
+ const items = unknownItems as MutationItems<Document>[]
58
+ const merged: MutationItems<Document> = {}
58
59
  for (const item of items) {
59
60
  if (item.removedAll) {
60
61
  merged.removedAll = true
@@ -76,11 +77,11 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
76
77
  * Notify all subscribers of changes
77
78
  * @param item The mutation items to notify subscribers about
78
79
  */
79
- function handleChanges(item: MutationItems) {
80
+ function handleChanges(item: MutationItems<Document>) {
80
81
  STATE_SCHEDULER.schedule(id, item)
81
82
  }
82
83
 
83
- const listeners = new Set<(mutation: MutationItems) => void>()
84
+ const listeners = new Set<(mutation: MutationItems<Document>) => void>()
84
85
 
85
86
  const state: SyncTable<Document> = {
86
87
  subscribe(listener) {
@@ -104,6 +105,12 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
104
105
  handleChanges({ mutations: changes })
105
106
  return changes
106
107
  },
108
+ async batchDelete(keys) {
109
+ const table = await getTable()
110
+ const changes = await table.batchDelete(keys)
111
+ handleChanges({ mutations: changes })
112
+ return changes
113
+ },
107
114
  async delete(key) {
108
115
  const table = await getTable()
109
116
  const changes = await table.delete(key)
@@ -3,6 +3,7 @@
3
3
  /* eslint-disable sonarjs/cognitive-complexity */
4
4
  /* eslint-disable @typescript-eslint/no-shadow */
5
5
  /* eslint-disable no-shadow */
6
+ import type { Backend } from './backend'
6
7
  import type { Table, DbOptions, DocType, Key, SearchOptions, MutationResult } from './table.types'
7
8
  import { unicodeTokenizer, type FtsTokenizerOptions } from './tokenizer'
8
9
  import type { Where } from './where'
@@ -181,10 +182,10 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
181
182
 
182
183
  if (existing.length > 0) {
183
184
  await db.execute(`UPDATE ${tableName} SET data = ? WHERE key = ?`, [json, id])
184
- return { key: id, op: 'update' }
185
+ return { key: id, op: 'update', document }
185
186
  } else {
186
187
  await db.execute(`INSERT INTO ${tableName} (key, data) VALUES (?, ?)`, [id, json])
187
- return { key: id, op: 'insert' }
188
+ return { key: id, op: 'insert', document }
188
189
  }
189
190
  }
190
191
 
@@ -192,7 +193,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
192
193
  const rows = await db.select<Array<{ id: number }>>(`SELECT last_insert_rowid() AS id`)
193
194
  const rowid = rows[0]?.id
194
195
  if (typeof rowid !== 'number') throw new Error('Failed to retrieve last_insert_rowid()')
195
- return { key: rowid, op: 'insert' }
196
+ return { key: rowid, op: 'insert', document }
196
197
  },
197
198
 
198
199
  async get<Selected = Document>(
@@ -211,9 +212,10 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
211
212
  return selector(document, { rowId: rowid, key: logicalKey }) as Selected
212
213
  },
213
214
 
214
- async delete(keyValue: Key) {
215
+ async delete(keyValue: Key, backendOverride?: Backend) {
216
+ const db = backendOverride ?? backend
215
217
  const whereKey = hasUserKey ? `key = ?` : `rowid = ?`
216
- await backend.execute(`DELETE FROM ${tableName} WHERE ${whereKey}`, [keyValue])
218
+ await db.execute(`DELETE FROM ${tableName} WHERE ${whereKey}`, [keyValue])
217
219
  const changed = await backend.select<Array<{ c: number }>>(`SELECT changes() AS c`)
218
220
  if ((changed[0]?.c ?? 0) > 0) return { key: keyValue, op: 'delete' }
219
221
  return
@@ -274,7 +276,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
274
276
  async deleteBy(where: Where<Document>) {
275
277
  const whereSql = getWhereQuery<Document>(where, tableName)
276
278
  const keyCol = hasUserKey ? 'key' : 'rowid'
277
- const results: MutationResult[] = []
279
+ const results: MutationResult<Document>[] = []
278
280
 
279
281
  await backend.transaction(async (tx) => {
280
282
  const rows = await tx.select<Array<{ k: Key }>>(`SELECT ${keyCol} AS k FROM ${tableName} ${whereSql}`)
@@ -287,7 +289,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
287
289
  await tx.execute(`DELETE FROM ${tableName} WHERE ${keyCol} IN (${placeholders})`, chunk as unknown[])
288
290
  }
289
291
 
290
- for (const k of allKeys) results.push({ key: k, op: 'delete' })
292
+ for (const k of allKeys) results.push({ key: k, op: 'delete', document: undefined })
291
293
  })
292
294
 
293
295
  return results
@@ -298,7 +300,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
298
300
  },
299
301
 
300
302
  async batchSet(documents: Document[]) {
301
- const mutations: MutationResult[] = []
303
+ const mutations: MutationResult<Document>[] = []
302
304
  await backend.transaction(async (tx) => {
303
305
  for (const document of documents) {
304
306
  const m = await table.set(document, tx)
@@ -307,6 +309,16 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
307
309
  })
308
310
  return mutations
309
311
  },
312
+ async batchDelete(keys: Key[]) {
313
+ const mutations: MutationResult<Document>[] = []
314
+ await backend.transaction(async (tx) => {
315
+ for (const key of keys) {
316
+ const m = await table.delete(key, tx)
317
+ if (m) mutations.push(m)
318
+ }
319
+ })
320
+ return mutations
321
+ },
310
322
  }
311
323
 
312
324
  return table
@@ -67,20 +67,35 @@ interface DbNotGeneric {
67
67
  export type Key = string | number
68
68
 
69
69
  export type MutationOp = 'insert' | 'update' | 'delete'
70
- export interface MutationResult {
70
+
71
+ interface MutationResultBase<T> {
71
72
  key: Key
72
73
  op: MutationOp
74
+ document?: T
75
+ }
76
+ interface MutationResultDelete<T> extends MutationResultBase<T> {
77
+ key: Key
78
+ op: 'delete'
73
79
  }
74
80
 
81
+ interface MutationResultUpdateInsert<T> extends MutationResultBase<T> {
82
+ key: Key
83
+ op: 'update' | 'insert'
84
+ document: T
85
+ }
86
+
87
+ export type MutationResult<T> = MutationResultDelete<T> | MutationResultUpdateInsert<T>
88
+
75
89
  export interface Table<Document extends DocType> extends DbNotGeneric {
76
- readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult>
77
- readonly batchSet: (documents: Document[]) => Promise<MutationResult[]>
90
+ readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult<Document>>
91
+ readonly batchSet: (documents: Document[]) => Promise<MutationResult<Document>[]>
92
+ readonly batchDelete: (keys: Key[]) => Promise<MutationResult<Document>[]>
78
93
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>
79
94
 
80
- readonly delete: (key: Key) => Promise<MutationResult | undefined>
95
+ readonly delete: (key: Key, backendOverride?: Backend) => Promise<MutationResult<Document> | undefined>
81
96
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>
82
97
  readonly count: (options?: { where?: Where<Document> }) => Promise<number>
83
- readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>
98
+ readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>
84
99
  readonly clear: () => Promise<void>
85
100
  }
86
101
 
@@ -114,7 +114,7 @@ export function useSqliteValue<Document extends DocType, Selected = Document>(
114
114
  let hasUpdate = false
115
115
  const removeIndexes = new Set<number>()
116
116
  for (const mutation of mutations) {
117
- const { key, op } = mutation
117
+ const { key, op, document } = mutation
118
118
  switch (op) {
119
119
  case 'insert': {
120
120
  newLength += 1
@@ -133,7 +133,7 @@ export function useSqliteValue<Document extends DocType, Selected = Document>(
133
133
  if (keysIndex.current.has(key)) {
134
134
  const index = keysIndex.current.get(key)
135
135
  if (index !== undefined && itemsRef.current) {
136
- const newItem = (await state.get(key, select)) as Selected
136
+ const newItem = select ? select(document as Document) : (document as unknown as Selected)
137
137
  const previousItem = itemsRef.current[index]
138
138
 
139
139
  // 🆕 Only update & rerender if shallow comparison fails
@@ -4,21 +4,22 @@ import type { Where } from './table/where';
4
4
  export interface CreateSqliteOptions<Document extends DocType> extends Omit<DbOptions<Document>, 'backend'> {
5
5
  readonly backend: Backend | Promise<Backend>;
6
6
  }
7
- export interface MutationItems {
8
- mutations?: MutationResult[];
7
+ export interface MutationItems<Doc> {
8
+ mutations?: MutationResult<Doc>[];
9
9
  removedAll?: boolean;
10
10
  }
11
11
  export interface SyncTable<Document extends DocType> {
12
- readonly subscribe: (listener: (mutation: MutationItems) => void) => () => void;
13
- readonly set: (document: Document) => Promise<MutationResult>;
14
- readonly batchSet: (documents: Document[]) => Promise<MutationResult[]>;
12
+ readonly subscribe: (listener: (mutation: MutationItems<Document>) => void) => () => void;
13
+ readonly set: (document: Document) => Promise<MutationResult<Document>>;
14
+ readonly batchSet: (documents: Document[]) => Promise<MutationResult<Document>[]>;
15
+ readonly batchDelete: (keys: Key[]) => Promise<MutationResult<Document>[]>;
15
16
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>;
16
- readonly delete: (key: Key) => Promise<MutationResult | undefined>;
17
+ readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>;
17
18
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>;
18
19
  readonly count: (options?: {
19
20
  where?: Where<Document>;
20
21
  }) => Promise<number>;
21
- readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>;
22
+ readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>;
22
23
  readonly clear: () => Promise<void>;
23
24
  }
24
25
  /**
@@ -44,20 +44,32 @@ interface DbNotGeneric {
44
44
  }
45
45
  export type Key = string | number;
46
46
  export type MutationOp = 'insert' | 'update' | 'delete';
47
- export interface MutationResult {
47
+ interface MutationResultBase<T> {
48
48
  key: Key;
49
49
  op: MutationOp;
50
+ document?: T;
50
51
  }
52
+ interface MutationResultDelete<T> extends MutationResultBase<T> {
53
+ key: Key;
54
+ op: 'delete';
55
+ }
56
+ interface MutationResultUpdateInsert<T> extends MutationResultBase<T> {
57
+ key: Key;
58
+ op: 'update' | 'insert';
59
+ document: T;
60
+ }
61
+ export type MutationResult<T> = MutationResultDelete<T> | MutationResultUpdateInsert<T>;
51
62
  export interface Table<Document extends DocType> extends DbNotGeneric {
52
- readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult>;
53
- readonly batchSet: (documents: Document[]) => Promise<MutationResult[]>;
63
+ readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult<Document>>;
64
+ readonly batchSet: (documents: Document[]) => Promise<MutationResult<Document>[]>;
65
+ readonly batchDelete: (keys: Key[]) => Promise<MutationResult<Document>[]>;
54
66
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>;
55
- readonly delete: (key: Key) => Promise<MutationResult | undefined>;
67
+ readonly delete: (key: Key, backendOverride?: Backend) => Promise<MutationResult<Document> | undefined>;
56
68
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>;
57
69
  readonly count: (options?: {
58
70
  where?: Where<Document>;
59
71
  }) => Promise<number>;
60
- readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>;
72
+ readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>;
61
73
  readonly clear: () => Promise<void>;
62
74
  }
63
75
  export type MakeAllFieldAsRequired<T> = {