muya 2.4.95 → 2.5.0

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 u}from"../create";import{getId as m}from"../utils/id";import{createTable as d}from"./table/table";function p(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 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{p 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 i=A(e);if(i==null)throw new Error(`Document is missing the configured key "${String($)}".`);return(await o.select(`SELECT key FROM ${t} WHERE key = ?`,[i])).length>0?(await o.execute(`UPDATE ${t} SET data = ? WHERE key = ?`,[r,i]),{key:i,op:"update",document:e}):(await o.execute(`INSERT INTO ${t} (key, data) VALUES (?, ?)`,[i,r]),{key:i,op:"insert",document:e})}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",document:e}},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],i=JSON.parse(E),a=u?A(i)??c:c;return n(i,{rowId:c,key:a})},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:i=w=>w,pageSize:a=C}=e,S=g(c,t),h=`SELECT rowid, data FROM ${t} ${S}`;let p=0,D=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 I=r?Math.min(a,r-p):a;w+=` LIMIT ${I} OFFSET ${D}`;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 N=JSON.parse(F),b=u?A(N)??O:O;yield i(N,{rowId:O,key:b}),p++}if(R.length<I||r&&p>=r)break;D+=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 i=c.map(a=>a.k);for(let a=0;a<i.length;a+=x){const S=i.slice(a,a+x),h=S.map(()=>"?").join(",");await E.execute(`DELETE FROM ${t} WHERE ${o} IN (${h})`,S)}for(const a of i)r.push({key:a,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 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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muya",
3
- "version": "2.4.95",
3
+ "version": "2.5.0",
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
  })
@@ -18,6 +18,7 @@ export interface SyncTable<Document extends DocType> {
18
18
  readonly subscribe: (listener: (mutation: MutationItems<Document>) => void) => () => void
19
19
  readonly set: (document: Document) => Promise<MutationResult<Document>>
20
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
24
  readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>
@@ -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'
@@ -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
@@ -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
@@ -89,9 +89,10 @@ export type MutationResult<T> = MutationResultDelete<T> | MutationResultUpdateIn
89
89
  export interface Table<Document extends DocType> extends DbNotGeneric {
90
90
  readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult<Document>>
91
91
  readonly batchSet: (documents: Document[]) => Promise<MutationResult<Document>[]>
92
+ readonly batchDelete: (keys: Key[]) => Promise<MutationResult<Document>[]>
92
93
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>
93
94
 
94
- readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>
95
+ readonly delete: (key: Key, backendOverride?: Backend) => Promise<MutationResult<Document> | undefined>
95
96
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>
96
97
  readonly count: (options?: { where?: Where<Document> }) => Promise<number>
97
98
  readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>
@@ -12,6 +12,7 @@ export interface SyncTable<Document extends DocType> {
12
12
  readonly subscribe: (listener: (mutation: MutationItems<Document>) => void) => () => void;
13
13
  readonly set: (document: Document) => Promise<MutationResult<Document>>;
14
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
17
  readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>;
17
18
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>;
@@ -62,8 +62,9 @@ export type MutationResult<T> = MutationResultDelete<T> | MutationResultUpdateIn
62
62
  export interface Table<Document extends DocType> extends DbNotGeneric {
63
63
  readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult<Document>>;
64
64
  readonly batchSet: (documents: Document[]) => Promise<MutationResult<Document>[]>;
65
+ readonly batchDelete: (keys: Key[]) => Promise<MutationResult<Document>[]>;
65
66
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>;
66
- readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>;
67
+ readonly delete: (key: Key, backendOverride?: Backend) => Promise<MutationResult<Document> | undefined>;
67
68
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>;
68
69
  readonly count: (options?: {
69
70
  where?: Where<Document>;