muya 2.1.3 → 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.
- package/esm/sqlite/__tests__/table.test.js +1 -1
- package/esm/sqlite/__tests__/use-sqlite.test.js +1 -1
- package/esm/sqlite/create-sqlite.js +1 -1
- package/esm/sqlite/index.js +1 -0
- package/esm/sqlite/table/index.js +1 -0
- package/esm/sqlite/table/table.js +5 -5
- package/package.json +1 -1
- package/src/__tests__/create.test.tsx +1 -1
- package/src/sqlite/__tests__/table.test.ts +5 -5
- package/src/sqlite/__tests__/use-sqlite.test.ts +5 -5
- package/src/sqlite/create-sqlite.ts +0 -4
- package/src/sqlite/index.ts +4 -0
- package/src/sqlite/table/index.ts +5 -0
- package/src/sqlite/table/table.ts +13 -4
- package/src/sqlite/table/table.types.ts +2 -1
- package/types/sqlite/create-sqlite.d.ts +0 -4
- package/types/sqlite/index.d.ts +4 -0
- package/types/sqlite/table/index.d.ts +5 -0
- package/types/sqlite/table/table.types.d.ts +2 -1
|
@@ -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({
|
|
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(["
|
|
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 +1 @@
|
|
|
1
|
-
import{createScheduler as
|
|
1
|
+
import{createScheduler as P}from"../scheduler";import{shallow as R}from"../utils/shallow";import{selectSql as M}from"./select-sql";import{createTable as O,DEFAULT_STEP_SIZE as v}from"./table/table";const f=P();let C=0;function E(){return C++}function N(g){const T=E();function l(e){return`state-${T}-search-${e}`}let m;async function o(){return m||(m=await O(g)),m}const c=new Map,i=new Map,h=new Map;async function p(e,n){const t=h.get(e),{options:a={}}=n,{stepSize:s=v}=a;if(!t)return!1;const r=[];for(let d=0;d<s;d++){const y=await t.next();if(y.done){h.delete(e);break}r.push(y.value.document),n.keys.add(String(y.value.rowId))}return r.length===0||R(n.items,r)?!1:(n.items=[...n.items,...r],!0)}function b(e){const n=i.get(e);n&&n()}async function k(e){const n=await o(),t=c.get(e);if(!t)return;const{options:a}=t,s=n.search({...a,select:(r,{rowId:d})=>({document:r,rowId:d})});h.set(e,s),t.keys=new Set,t.items=[],await p(e,t)}async function S(e){await k(e),b(e)}function x(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 u(e){const n=new Set;for(const t of e){const a=x(t);for(const s of a)n.add(s)}for(const t of n){const a=l(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&&S(e));const t=c.get(e);return n&&(t.options=n),t}const I={async set(e){const t=await(await o()).set(e);return await u([t]),t},async batchSet(e){const t=await(await o()).batchSet(e);return await u(t),t},async delete(e){const t=await(await o()).delete(e);return t&&await u([t]),t},async deleteBy(e){const t=await(await o()).deleteBy(e);return await u(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=l(e);f.schedule(a,{searchId:e})},subscribe(e,n){const t=l(e),a=f.add(t,{onScheduleDone(){S(e)}});return w.add(a),i.has(e)||i.set(e,n),()=>{i.delete(e),a(),c.delete(e)}},getSnapshot(e){return D(e).items},refresh:S,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 M(I,e)}};return I}export{N as createSqliteState};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./create-sqlite";export*from"./table";export*from"./select-sql";export*from"./use-sqlite";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./backend";export*from"./table.types";export*from"./where";export*from"./table";export*from"./map-deque";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{getWhereQuery as S}from"./where";const p=500,
|
|
2
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
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
|
|
7
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
6
|
+
`):await o.execute(`
|
|
7
|
+
CREATE TABLE IF NOT EXISTS ${s} (
|
|
8
8
|
data TEXT NOT NULL
|
|
9
9
|
);
|
|
10
|
-
`);for(const n of
|
|
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
|
@@ -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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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(['
|
|
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(['
|
|
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(['
|
|
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(
|
|
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(['
|
|
271
|
+
expect(result.current[0]).toEqual(['Alice', 'Bob', 'Carol'])
|
|
272
272
|
})
|
|
273
273
|
})
|
|
274
274
|
})
|
|
@@ -16,10 +16,6 @@ function getStateId() {
|
|
|
16
16
|
return stateId++
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export interface SqLiteActions {
|
|
20
|
-
readonly next: () => Promise<boolean>
|
|
21
|
-
readonly reset: () => Promise<void>
|
|
22
|
-
}
|
|
23
19
|
export interface SyncTable<Document extends DocType> {
|
|
24
20
|
// readonly registerSearch: <Selected = Document>(searchId: SearchId, options: SearchOptions<Document, Selected>) => () => void
|
|
25
21
|
readonly updateSearchOptions: <Selected = Document>(searchId: SearchId, options: SearchOptions<Document, Selected>) => void
|
|
@@ -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
|
-
|
|
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 (
|
|
133
|
-
query += ` ORDER BY json_extract(data, '$.${String(
|
|
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
|
|
27
|
+
readonly sortBy?: keyof Document
|
|
27
28
|
readonly order?: 'asc' | 'desc'
|
|
28
29
|
readonly limit?: number
|
|
29
30
|
readonly offset?: number
|
|
@@ -2,10 +2,6 @@ import { type CreateState } from './select-sql';
|
|
|
2
2
|
import type { DbOptions, DocType, Key, MutationResult, SearchOptions } from './table/table.types';
|
|
3
3
|
import type { Where } from './table/where';
|
|
4
4
|
type SearchId = string;
|
|
5
|
-
export interface SqLiteActions {
|
|
6
|
-
readonly next: () => Promise<boolean>;
|
|
7
|
-
readonly reset: () => Promise<void>;
|
|
8
|
-
}
|
|
9
5
|
export interface SyncTable<Document extends DocType> {
|
|
10
6
|
readonly updateSearchOptions: <Selected = Document>(searchId: SearchId, options: SearchOptions<Document, Selected>) => void;
|
|
11
7
|
readonly subscribe: (searchId: SearchId, listener: () => void) => () => void;
|
|
@@ -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
|
|
23
|
+
readonly sortBy?: keyof Document;
|
|
23
24
|
readonly order?: 'asc' | 'desc';
|
|
24
25
|
readonly limit?: number;
|
|
25
26
|
readonly offset?: number;
|