muya 2.2.0 → 2.2.1

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{bunMemoryBackend as c}from"../table/bun-backend";import{createTable as r}from"../table/table";describe("table",()=>{let n=c(),e;beforeEach(async()=>{n=c(),e=await r({backend:n,tableName:"TestTable",key:"name"})}),it("should set and get items",async()=>{const a=await e.set({name:"Alice",age:30,city:"Paris"});expect(a.key).toBe("Alice"),expect(a.op).toBe("insert");const i=await e.get("Alice");expect(i).toEqual({name:"Alice",age:30,city:"Paris"});const t=await e.set({name:"Alice",age:31,city:"Paris"});expect(t.key).toBe("Alice"),expect(t.op).toBe("update");const s=await e.get("Alice");expect(s).toEqual({name:"Alice",age:31,city:"Paris"})}),it("should count items and count with where",async()=>{await e.set({name:"Alice",age:30,city:"Paris"}),await e.set({name:"Bob",age:25,city:"London"}),expect(await e.count()).toBe(2),expect(await e.count({where:{city:"Paris"}})).toBe(1)}),it("should search with ordering, limit and offset",async()=>{const a=[{name:"Alice",age:30,city:"Paris"},{name:"Bob",age:25,city:"London"},{name:"Carol",age:35,city:"Berlin"}];for(const o of a)await e.set(o);const i=[];for await(const o of e.search({sorBy:"age",order:"asc"}))i.push(o);expect(i.map(o=>o.name)).toEqual(["Bob","Alice","Carol"]);const t=[];for await(const o of e.search({sorBy:"age",order:"asc",limit:2}))t.push(o);expect(t.map(o=>o.name)).toEqual(["Bob","Alice"]);const s=[];for await(const o of e.search({sorBy:"age",order:"asc",offset:1,limit:2}))s.push(o);expect(s.map(o=>o.name)).toEqual(["Alice","Carol"])}),it("should deleteBy where clause",async()=>{await e.set({name:"Dave",age:40,city:"NY"}),await e.set({name:"Eve",age:45,city:"NY"}),await e.set({name:"Frank",age:50,city:"LA"}),expect(await e.count()).toBe(3),await e.deleteBy({city:"NY"}),expect(await e.count()).toBe(1),expect(await e.get("Frank")).toEqual({name:"Frank",age:50,city:"LA"}),expect(await e.get("Dave")).toBeUndefined()}),it("should use selector in get and search",async()=>{await e.set({name:"Gary",age:60,city:"SF"});const a=await e.get("Gary",({age:t})=>t);expect(a).toBe(60);const i=[];for await(const t of e.search({select:({city:s})=>s}))i.push(t);expect(i).toEqual(["SF"])}),it("should delete items by key",async()=>{await e.set({name:"Helen",age:28,city:"Rome"}),expect(await e.get("Helen")).toBeDefined(),await e.delete("Helen"),expect(await e.get("Helen")).toBeUndefined()}),it("should test search with 1000 items",async()=>{const a=[];for(let t=0;t<1e3;t++)a.push({name:`Person${t}`,age:Math.floor(Math.random()*100),city:"City"+t%10});for(const t of a)await e.set(t);const i=[];for await(const t of e.search({sorBy:"age",order:"asc",limit:100}))i.push(t);expect(i.length).toBe(100)}),it("should handle operations on an empty table",async()=>{expect(await e.count()).toBe(0),expect(await e.get("NonExistent")).toBeUndefined();const a=[];for await(const i of e.search({sorBy:"age",order:"asc"}))a.push(i);expect(a.length).toBe(0)}),it("should handle duplicate keys gracefully",async()=>{await e.set({name:"Alice",age:30,city:"Paris"}),await e.set({name:"Alice",age:35,city:"Berlin"});const a=await e.get("Alice");expect(a).toEqual({name:"Alice",age:35,city:"Berlin"})}),it("should handle edge cases in selectors",async()=>{await e.set({name:"Charlie",age:40,city:"NY"});const a=await e.get("Charlie",()=>null);expect(a).toBeNull();const i=await e.get("Charlie",()=>{});expect(i).toBeUndefined()})});
1
+ import{bunMemoryBackend as c}from"../table/bun-backend";import{createTable as r}from"../table/table";describe("table",()=>{let n=c(),e;beforeEach(async()=>{n=c(),e=await r({backend:n,tableName:"TestTable",key:"name"})}),it("should set and get items",async()=>{const a=await e.set({name:"Alice",age:30,city:"Paris"});expect(a.key).toBe("Alice"),expect(a.op).toBe("insert");const i=await e.get("Alice");expect(i).toEqual({name:"Alice",age:30,city:"Paris"});const t=await e.set({name:"Alice",age:31,city:"Paris"});expect(t.key).toBe("Alice"),expect(t.op).toBe("update");const s=await e.get("Alice");expect(s).toEqual({name:"Alice",age:31,city:"Paris"})}),it("should count items and count with where",async()=>{await e.set({name:"Alice",age:30,city:"Paris"}),await e.set({name:"Bob",age:25,city:"London"}),expect(await e.count()).toBe(2),expect(await e.count({where:{city:"Paris"}})).toBe(1)}),it("should search with ordering, limit and offset",async()=>{const a=[{name:"Alice",age:30,city:"Paris"},{name:"Bob",age:25,city:"London"},{name:"Carol",age:35,city:"Berlin"}];for(const o of a)await e.set(o);const i=[];for await(const o of e.search({sortBy:"age",order:"asc"}))i.push(o);expect(i.map(o=>o.name)).toEqual(["Bob","Alice","Carol"]);const t=[];for await(const o of e.search({sortBy:"age",order:"asc",limit:2}))t.push(o);expect(t.map(o=>o.name)).toEqual(["Bob","Alice"]);const s=[];for await(const o of e.search({sortBy:"age",order:"asc",offset:1,limit:2}))s.push(o);expect(s.map(o=>o.name)).toEqual(["Alice","Carol"])}),it("should deleteBy where clause",async()=>{await e.set({name:"Dave",age:40,city:"NY"}),await e.set({name:"Eve",age:45,city:"NY"}),await e.set({name:"Frank",age:50,city:"LA"}),expect(await e.count()).toBe(3),await e.deleteBy({city:"NY"}),expect(await e.count()).toBe(1),expect(await e.get("Frank")).toEqual({name:"Frank",age:50,city:"LA"}),expect(await e.get("Dave")).toBeUndefined()}),it("should use selector in get and search",async()=>{await e.set({name:"Gary",age:60,city:"SF"});const a=await e.get("Gary",({age:t})=>t);expect(a).toBe(60);const i=[];for await(const t of e.search({select:({city:s})=>s}))i.push(t);expect(i).toEqual(["SF"])}),it("should delete items by key",async()=>{await e.set({name:"Helen",age:28,city:"Rome"}),expect(await e.get("Helen")).toBeDefined(),await e.delete("Helen"),expect(await e.get("Helen")).toBeUndefined()}),it("should test search with 1000 items",async()=>{const a=[];for(let t=0;t<1e3;t++)a.push({name:`Person${t}`,age:Math.floor(Math.random()*100),city:"City"+t%10});for(const t of a)await e.set(t);const i=[];for await(const t of e.search({sortBy:"age",order:"asc",limit:100}))i.push(t);expect(i.length).toBe(100)}),it("should handle operations on an empty table",async()=>{expect(await e.count()).toBe(0),expect(await e.get("NonExistent")).toBeUndefined();const a=[];for await(const i of e.search({sortBy:"age",order:"asc"}))a.push(i);expect(a.length).toBe(0)}),it("should handle duplicate keys gracefully",async()=>{await e.set({name:"Alice",age:30,city:"Paris"}),await e.set({name:"Alice",age:35,city:"Berlin"});const a=await e.get("Alice");expect(a).toEqual({name:"Alice",age:35,city:"Berlin"})}),it("should handle edge cases in selectors",async()=>{await e.set({name:"Charlie",age:40,city:"NY"});const a=await e.get("Charlie",()=>null);expect(a).toBeNull();const i=await e.get("Charlie",()=>{});expect(i).toBeUndefined()})});
@@ -1 +1 @@
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(["Bob","Alice","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(["Bob","Alice"])}),s(()=>{n({order:"desc",limit:2})}),await o(()=>{expect(t.current[0].map(r=>r.name)).toEqual(["Carol","Alice"])}),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(20)}),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(["Bob","Alice","Carol"])})})});
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,10 +1,10 @@
1
- import{getWhereQuery as S}from"./where";const p=500,L=100;async function N(k){const{backend:s,tableName:o,indexes:D,key:T}=k,d=T!==void 0;d?await s.execute(`
2
- CREATE TABLE IF NOT EXISTS ${o} (
1
+ import{getWhereQuery as S}from"./where";const p=500,g=100;async function x(A){const{backend:o,tableName:s,indexes:O,key:m,disablePragmaOptimization:R}=A,d=m!==void 0;R||(await o.execute("PRAGMA journal_mode=WAL;"),await o.execute("PRAGMA synchronous=NORMAL;"),await o.execute("PRAGMA temp_store=MEMORY;"),await o.execute("PRAGMA cache_size=-20000;")),d?await o.execute(`
2
+ CREATE TABLE IF NOT EXISTS ${s} (
3
3
  key TEXT PRIMARY KEY,
4
4
  data TEXT NOT NULL
5
5
  );
6
- `):await s.execute(`
7
- CREATE TABLE IF NOT EXISTS ${o} (
6
+ `):await o.execute(`
7
+ CREATE TABLE IF NOT EXISTS ${s} (
8
8
  data TEXT NOT NULL
9
9
  );
10
- `);for(const n of D??[]){const a=String(n);await s.execute(`CREATE INDEX IF NOT EXISTS idx_${o}_${a} ON ${o} (json_extract(data, '$.${a}'));`)}function O(n){return d?n[T]:void 0}async function $(n){return(await n.select("SELECT changes() AS c"))[0]?.c??0}const f={backend:s,async set(n,a){const e=a??s,r=JSON.stringify(n);if(d){const t=O(n);if(t==null)throw new Error(`Document is missing the configured key "${String(T)}". Provide it or create the table without "key".`);if(await e.execute(`UPDATE ${o} SET data = ? WHERE key = ?`,[r,t]),await $(e)===1)return{key:t,op:"update"};try{return await e.execute(`INSERT INTO ${o} (key, data) VALUES (?, ?)`,[t,r]),{key:t,op:"insert"}}catch{return await e.execute(`UPDATE ${o} SET data = ? WHERE key = ?`,[r,t]),{key:t,op:"update"}}}await e.execute(`INSERT INTO ${o} (data) VALUES (?)`,[r]);const c=(await e.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(n,a=(e,r)=>e){const e=d?"key = ?":"rowid = ?",r=await s.select(`SELECT rowid, data FROM ${o} WHERE ${e}`,[n]);if(r.length===0)return;const[i]=r,{data:c,rowid:u}=i,t=JSON.parse(c);return a(t,{rowid:u})},async delete(n){const a=d?"key = ?":"rowid = ?";if(await s.execute(`DELETE FROM ${o} WHERE ${a}`,[n]),((await s.select("SELECT changes() AS c"))[0]?.c??0)>0)return{key:n,op:"delete"}},async*search(n={}){const{sorBy:a,order:e="asc",limit:r,offset:i=0,where:c,select:u=(y,m)=>y,stepSize:t=L}=n;let E=`SELECT rowid, data FROM ${o}`;c&&(E+=" "+S(c));let l=0,h=i;for(;;){let y=E;a?y+=` ORDER BY json_extract(data, '$.${String(a)}') COLLATE NOCASE ${e.toUpperCase()}`:y+=d?` ORDER BY key COLLATE NOCASE ${e.toUpperCase()}`:` ORDER BY rowid ${e.toUpperCase()}`;const m=r?Math.min(t,r-l):t;y+=` LIMIT ${m} OFFSET ${h}`;const w=await s.select(y);if(w.length===0)break;for(const{rowid:b,data:R}of w){if(r&&l>=r)return;const A=JSON.parse(R);yield u(A,{rowId:b}),l++}if(w.length<m||r&&l>=r)break;h+=w.length}},async count(n={}){const{where:a}=n;let e=`SELECT COUNT(*) as count FROM ${o}`;return a&&(e+=" "+S(a)),(await s.select(e))[0]?.count??0},async deleteBy(n){const a=S(n),e=d?"key":"rowid",r=[];return await s.transaction(async i=>{const c=await i.select(`SELECT ${e} AS k, rowid FROM ${o} ${a}`);if(c.length===0)return;const u=c.map(t=>t.k);for(let t=0;t<u.length;t+=p){const E=u.slice(t,t+p),l=E.map(()=>"?").join(",");await i.execute(`DELETE FROM ${o} WHERE ${e} IN (${l})`,E)}for(const t of u)r.push({key:t,op:"delete"})}),r},async batchSet(n){const a=[];return await s.transaction(async e=>{for(const r of n){const i=await f.set(r,e);a.push(i)}}),a}};return f}export{L as DEFAULT_STEP_SIZE,N as createTable};
10
+ `);for(const n of O??[]){const r=String(n);await o.execute(`CREATE INDEX IF NOT EXISTS idx_${s}_${r} ON ${s} (json_extract(data, '$.${r}'));`)}function k(n){return d?n[m]:void 0}async function D(n){return(await n.select("SELECT changes() AS c"))[0]?.c??0}const f={backend:o,async set(n,r){const e=r??o,a=JSON.stringify(n);if(d){const t=k(n);if(t==null)throw new Error(`Document is missing the configured key "${String(m)}". Provide it or create the table without "key".`);if(await e.execute(`UPDATE ${s} SET data = ? WHERE key = ?`,[a,t]),await D(e)===1)return{key:t,op:"update"};try{return await e.execute(`INSERT INTO ${s} (key, data) VALUES (?, ?)`,[t,a]),{key:t,op:"insert"}}catch{return await e.execute(`UPDATE ${s} SET data = ? WHERE key = ?`,[a,t]),{key:t,op:"update"}}}await e.execute(`INSERT INTO ${s} (data) VALUES (?)`,[a]);const c=(await e.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(n,r=(e,a)=>e){const e=d?"key = ?":"rowid = ?",a=await o.select(`SELECT rowid, data FROM ${s} WHERE ${e}`,[n]);if(a.length===0)return;const[i]=a,{data:c,rowid:u}=i,t=JSON.parse(c);return r(t,{rowid:u})},async delete(n){const r=d?"key = ?":"rowid = ?";if(await o.execute(`DELETE FROM ${s} WHERE ${r}`,[n]),((await o.select("SELECT changes() AS c"))[0]?.c??0)>0)return{key:n,op:"delete"}},async*search(n={}){const{sortBy:r,order:e="asc",limit:a,offset:i=0,where:c,select:u=(y,T)=>y,stepSize:t=g}=n;let l=`SELECT rowid, data FROM ${s}`;c&&(l+=" "+S(c));let E=0,h=i;for(;;){let y=l;r?y+=` ORDER BY json_extract(data, '$.${String(r)}') COLLATE NOCASE ${e.toUpperCase()}`:y+=d?` ORDER BY key COLLATE NOCASE ${e.toUpperCase()}`:` ORDER BY rowid ${e.toUpperCase()}`;const T=a?Math.min(t,a-E):t;y+=` LIMIT ${T} OFFSET ${h}`;const w=await o.select(y);if(w.length===0)break;for(const{rowid:$,data:b}of w){if(a&&E>=a)return;const L=JSON.parse(b);yield u(L,{rowId:$}),E++}if(w.length<T||a&&E>=a)break;h+=w.length}},async count(n={}){const{where:r}=n;let e=`SELECT COUNT(*) as count FROM ${s}`;return r&&(e+=" "+S(r)),(await o.select(e))[0]?.count??0},async deleteBy(n){const r=S(n),e=d?"key":"rowid",a=[];return await o.transaction(async i=>{const c=await i.select(`SELECT ${e} AS k, rowid FROM ${s} ${r}`);if(c.length===0)return;const u=c.map(t=>t.k);for(let t=0;t<u.length;t+=p){const l=u.slice(t,t+p),E=l.map(()=>"?").join(",");await i.execute(`DELETE FROM ${s} WHERE ${e} IN (${E})`,l)}for(const t of u)a.push({key:t,op:"delete"})}),a},async batchSet(n){const r=[];return await o.transaction(async e=>{for(const a of n){const i=await f.set(a,e);r.push(i)}}),r}};return f}export{g as DEFAULT_STEP_SIZE,x as createTable};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muya",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "author": "samuel.gjabel@gmail.com",
5
5
  "repository": "https://github.com/samuelgjabel/muya",
6
6
  "main": "cjs/index.js",
@@ -4,7 +4,7 @@ import { longPromise } from './test-utils'
4
4
  import { isPromise } from '../utils/is'
5
5
 
6
6
  describe('create', () => {
7
- it('should get basic value states', async () => {
7
+ it('should get basic value states here', async () => {
8
8
  const state1 = create(1)
9
9
  const state2 = create(2)
10
10
  expect(state1.get()).toBe(1)
@@ -57,14 +57,14 @@ describe('table', () => {
57
57
  }
58
58
  // sort by age ascending
59
59
  const asc = [] as Person[]
60
- for await (const p of table.search({ sorBy: 'age', order: 'asc' })) asc.push(p)
60
+ for await (const p of table.search({ sortBy: 'age', order: 'asc' })) asc.push(p)
61
61
  expect(asc.map((p) => p.name)).toEqual(['Bob', 'Alice', 'Carol'])
62
62
  // limit and offset
63
63
  const limited = [] as Person[]
64
- for await (const p of table.search({ sorBy: 'age', order: 'asc', limit: 2 })) limited.push(p)
64
+ for await (const p of table.search({ sortBy: 'age', order: 'asc', limit: 2 })) limited.push(p)
65
65
  expect(limited.map((p) => p.name)).toEqual(['Bob', 'Alice'])
66
66
  const offsetted = [] as Person[]
67
- for await (const p of table.search({ sorBy: 'age', order: 'asc', offset: 1, limit: 2 })) offsetted.push(p)
67
+ for await (const p of table.search({ sortBy: 'age', order: 'asc', offset: 1, limit: 2 })) offsetted.push(p)
68
68
  expect(offsetted.map((p) => p.name)).toEqual(['Alice', 'Carol'])
69
69
  })
70
70
 
@@ -109,7 +109,7 @@ describe('table', () => {
109
109
  await table.set(p)
110
110
  }
111
111
  const results: Person[] = []
112
- for await (const person of table.search({ sorBy: 'age', order: 'asc', limit: 100 })) {
112
+ for await (const person of table.search({ sortBy: 'age', order: 'asc', limit: 100 })) {
113
113
  results.push(person)
114
114
  }
115
115
  expect(results.length).toBe(100)
@@ -119,7 +119,7 @@ describe('table', () => {
119
119
  expect(await table.count()).toBe(0)
120
120
  expect(await table.get('NonExistent')).toBeUndefined()
121
121
  const results: Person[] = []
122
- for await (const person of table.search({ sorBy: 'age', order: 'asc' })) {
122
+ for await (const person of table.search({ sortBy: 'age', order: 'asc' })) {
123
123
  results.push(person)
124
124
  }
125
125
  expect(results.length).toBe(0)
@@ -76,7 +76,7 @@ describe('use-sqlite-state', () => {
76
76
  })
77
77
 
78
78
  await waitFor(() => {
79
- expect(result.current[0][0].map((p) => p.name)).toEqual(['Bob', 'Alice', 'Carol'])
79
+ expect(result.current[0][0].map((p) => p.name)).toEqual(['Alice', 'Bob', 'Carol'])
80
80
  expect(reRenders).toBe(2)
81
81
  })
82
82
 
@@ -129,13 +129,13 @@ describe('use-sqlite-state', () => {
129
129
  { initialProps: { order: 'asc' as 'asc' | 'desc', limit: 2 } },
130
130
  )
131
131
  await waitFor(() => {
132
- expect(result.current[0].map((p) => p.name)).toEqual(['Bob', 'Alice'])
132
+ expect(result.current[0].map((p) => p.name)).toEqual(['Alice', 'Bob'])
133
133
  })
134
134
  act(() => {
135
135
  rerender({ order: 'desc', limit: 2 })
136
136
  })
137
137
  await waitFor(() => {
138
- expect(result.current[0].map((p) => p.name)).toEqual(['Carol', 'Alice'])
138
+ expect(result.current[0].map((p) => p.name)).toEqual(['Alice', 'Bob'])
139
139
  })
140
140
  act(() => {
141
141
  rerender({ order: 'desc', limit: 1 })
@@ -240,7 +240,7 @@ describe('use-sqlite-state', () => {
240
240
  initialProps: { order: 'asc' as 'asc' | 'desc' },
241
241
  })
242
242
  await waitFor(() => {
243
- expect(result.current[0][0].age).toBe(20)
243
+ expect(result.current[0][0].age).toBe(21)
244
244
  })
245
245
  act(() => {
246
246
  rerender({ order: 'desc' })
@@ -268,7 +268,7 @@ describe('use-sqlite-state', () => {
268
268
  ),
269
269
  )
270
270
  await waitFor(() => {
271
- expect(result.current[0]).toEqual(['Bob', 'Alice', 'Carol'])
271
+ expect(result.current[0]).toEqual(['Alice', 'Bob', 'Carol'])
272
272
  })
273
273
  })
274
274
  })
@@ -9,9 +9,18 @@ import { getWhereQuery, type Where } from './where'
9
9
  const DELETE_IN_CHUNK = 500 // keep well below SQLite's default 999 parameter limit
10
10
  export const DEFAULT_STEP_SIZE = 100
11
11
  export async function createTable<Document extends DocType>(options: DbOptions<Document>): Promise<Table<Document>> {
12
- const { backend, tableName, indexes, key } = options
12
+ const { backend, tableName, indexes, key, disablePragmaOptimization } = options
13
13
  const hasUserKey = key !== undefined
14
14
 
15
+ // --- Apply performance PRAGMAs unless explicitly disabled ---
16
+ // These significantly speed up write-heavy workloads on SQLite.
17
+ if (!disablePragmaOptimization) {
18
+ await backend.execute(`PRAGMA journal_mode=WAL;`)
19
+ await backend.execute(`PRAGMA synchronous=NORMAL;`)
20
+ await backend.execute(`PRAGMA temp_store=MEMORY;`)
21
+ await backend.execute(`PRAGMA cache_size=-20000;`)
22
+ }
23
+
15
24
  // Schema
16
25
  if (hasUserKey) {
17
26
  await backend.execute(`
@@ -112,7 +121,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
112
121
  // --- FIXED: include rowid in search ---
113
122
  async *search<Selected = Document>(options: SearchOptions<Document, Selected> = {}): AsyncIterableIterator<Selected> {
114
123
  const {
115
- sorBy,
124
+ sortBy,
116
125
  order = 'asc',
117
126
  limit,
118
127
  offset = 0,
@@ -129,8 +138,8 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
129
138
  while (true) {
130
139
  let query = baseQuery
131
140
 
132
- if (sorBy) {
133
- query += ` ORDER BY json_extract(data, '$.${String(sorBy)}') COLLATE NOCASE ${order.toUpperCase()}`
141
+ if (sortBy) {
142
+ query += ` ORDER BY json_extract(data, '$.${String(sortBy)}') COLLATE NOCASE ${order.toUpperCase()}`
134
143
  } else {
135
144
  query += hasUserKey ? ` ORDER BY key COLLATE NOCASE ${order.toUpperCase()}` : ` ORDER BY rowid ${order.toUpperCase()}`
136
145
  }
@@ -16,6 +16,7 @@ export interface DbOptions<Document extends DocType> {
16
16
  * Optional key. If omitted, the table uses implicit SQLite ROWID as the key.
17
17
  */
18
18
  readonly key?: keyof Document
19
+ readonly disablePragmaOptimization?: boolean
19
20
  }
20
21
 
21
22
  interface DbNotGeneric {
@@ -23,7 +24,7 @@ interface DbNotGeneric {
23
24
  }
24
25
 
25
26
  export interface SearchOptions<Document extends DocType, Selected = Document> {
26
- readonly sorBy?: keyof Document
27
+ readonly sortBy?: keyof Document
27
28
  readonly order?: 'asc' | 'desc'
28
29
  readonly limit?: number
29
30
  readonly offset?: number
@@ -14,12 +14,13 @@ export interface DbOptions<Document extends DocType> {
14
14
  * Optional key. If omitted, the table uses implicit SQLite ROWID as the key.
15
15
  */
16
16
  readonly key?: keyof Document;
17
+ readonly disablePragmaOptimization?: boolean;
17
18
  }
18
19
  interface DbNotGeneric {
19
20
  readonly backend: Backend;
20
21
  }
21
22
  export interface SearchOptions<Document extends DocType, Selected = Document> {
22
- readonly sorBy?: keyof Document;
23
+ readonly sortBy?: keyof Document;
23
24
  readonly order?: 'asc' | 'desc';
24
25
  readonly limit?: number;
25
26
  readonly offset?: number;