muya 2.4.93 → 2.4.95

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.
@@ -0,0 +1 @@
1
+ import{createTable as n}from"../table";import{bunMemoryBackend as c}from"../table/bun-backend";describe("where clauses",()=>{const a=c();it("should handle where where array of conditions",async()=>{const e=await n({backend:a,tableName:"TestTableNestedOptional",key:"id",indexes:["content"]});await e.set({id:"1",content:"The quick brown fox"}),await e.set({id:"2",content:"The jumps over the lazy dog"}),await e.set({id:"3"});const t=[];for await(const i of e.search({where:{content:{like:["The%"]}}}))t.push(i);expect(t.length).toBe(2),expect(t[0].id).toBe("1")}),it("should create nested index for optional nested fields",async()=>{const e=await n({backend:a,tableName:"TestTableNestedOptional",key:"id",indexes:["fts:info.content"]});await e.set({id:"1",info:{content:"The quick brown fox"}}),await e.set({id:"2",info:{content:"jumps over the lazy dog"}}),await e.set({id:"3"});const t=[];for await(const s of e.search({where:{info:{content:{like:["The%"]}}}}))t.push(s);expect(t.length).toBe(1),expect(t[0].id).toBe("1");const i=[];for await(const s of e.search({where:{OR:[{info:{content:{like:["The%"]}}},{info:{content:{like:["jumps%"]}}}]}}))i.push(s);expect(i.length).toBe(2);const o=[];for await(const s of e.search({where:{info:{content:"nonexistent"}}}))o.push(s);expect(o.length).toBe(0)}),it("should handle FTS queries",async()=>{const e=await n({backend:a,tableName:"TestTableFts",key:"id",indexes:["fts:content"]});await e.set({id:"1",content:"The quick brown fox"}),await e.set({id:"2",content:"Jumps over the lazy dog"}),await e.set({id:"3",content:"Another document"});const t=[];for await(const i of e.search({where:{content:{fts:"quick"}}}))t.push(i);expect(t.length).toBe(1),expect(t[0].id).toBe("1")}),it("should handle nested where conditions",async()=>{const e=await n({backend:a,tableName:"TestTableNested",key:"id",indexes:["info.content"]});await e.set({id:"1",info:{content:"Nested quick brown fox"}}),await e.set({id:"2",info:{content:"Nested jumps over the lazy dog"}});const t=[];for await(const i of e.search({where:{info:{content:{like:"Nested%"}}}}))t.push(i);expect(t.length).toBe(2)}),it("should handle complex operators",async()=>{const e=await n({backend:a,tableName:"TestTableComplex",key:"id",indexes:["value"]});await e.set({id:"1",value:10}),await e.set({id:"2",value:20}),await e.set({id:"3",value:30});const t=[];for await(const i of e.search({where:{value:{gt:15,lt:25}},sortBy:"value"}))t.push(i);expect(t.length).toBe(1),expect(t[0].id).toBe("2")}),it("should handle NOT conditions",async()=>{const e=await n({backend:a,tableName:"TestTableNot",key:"id",indexes:["value"]});await e.set({id:"1",value:"apple"}),await e.set({id:"2",value:"banana"}),await e.set({id:"3",value:"cherry"});const t=[];for await(const i of e.search({where:{NOT:{value:"banana"}}}))t.push(i);expect(t.length).toBe(2),expect(t.map(i=>i.value)).toEqual(["apple","cherry"])}),it("should handle AND conditions",async()=>{const e=await n({backend:a,tableName:"TestTableAnd",key:"id",indexes:["category","price"]});await e.set({id:"1",category:"fruit",price:10}),await e.set({id:"2",category:"fruit",price:20}),await e.set({id:"3",category:"vegetable",price:15});const t=[];for await(const i of e.search({where:{AND:[{category:"fruit"},{price:{lt:15}}]}}))t.push(i);expect(t.length).toBe(1),expect(t[0].id).toBe("1")}),it("should handle OR conditions",async()=>{const e=await n({backend:a,tableName:"TestTableOr",key:"id",indexes:["type"]});await e.set({id:"1",type:"A"}),await e.set({id:"2",type:"B"}),await e.set({id:"3",type:"C"});const t=[];for await(const i of e.search({where:{OR:[{type:"A"},{type:"C"}]}}))t.push(i);expect(t.length).toBe(2),expect(t.map(i=>i.type)).toEqual(["A","C"])}),it("should handle nested AND/OR/NOT conditions",async()=>{const e=await n({backend:a,tableName:"TestTableNestedLogic",key:"id",indexes:["category","price"]});await e.set({id:"1",category:"fruit",price:10}),await e.set({id:"2",category:"fruit",price:20}),await e.set({id:"3",category:"fruit",price:15});const t={AND:[{category:{is:"fruit"}},{OR:[{price:{lt:15}},{price:{is:15}}]}]},i=[];for await(const s of e.search({}))i.push(s);expect(i.length).toBe(3);const o=[];for await(const s of e.search({where:t}))o.push(s);expect(o.length).toBe(2)})});
@@ -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 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};
@@ -35,4 +35,4 @@ import{unicodeTokenizer as _}from"./tokenizer";import{getWhereQuery as g}from"./
35
35
  SET ${T.map(r=>`${f[r]}=json_extract(new.data, '${m(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 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};
@@ -1 +1 @@
1
- function a(n){return typeof n=="string"?`'${n.replaceAll("'","''")}'`:typeof n=="number"?n.toString():typeof n=="boolean"?n?"1":"0":`'${String(n).replaceAll("'","''")}'`}function A(n,r,t){const e=t?`${t}.`:"";return n==="KEY"?`"${e}key"`:typeof r=="string"?`CAST(json_extract(${e}data, '$.${n}') AS TEXT)`:typeof r=="number"?`CAST(json_extract(${e}data, '$.${n}') AS NUMERIC)`:typeof r=="boolean"?`CAST(json_extract(${e}data, '$.${n}') AS INTEGER)`:`json_extract(${e}data, '$.${n}')`}const N=new Set(["is","isNot","gt","gte","lt","lte","in","notIn","like","fts"]);function p(n,r=""){const t={};for(const[e,c]of Object.entries(n)){if(e==="AND"||e==="OR"||e==="NOT"){t[e]=c;continue}const $=r?`${r}.${e}`:e;c&&typeof c=="object"&&!Array.isArray(c)&&!Object.keys(c).some(i=>N.has(i))?Object.assign(t,p(c,$)):t[$]=c}return t}function l(n,r,t){if(!n||typeof n!="object")return"";if(n.AND){const i=Array.isArray(n.AND)?n.AND.map(u=>l(u,r,t)).filter(Boolean):[];return i.length>0?`(${i.join(" AND ")})`:""}if(n.OR){const i=Array.isArray(n.OR)?n.OR.map(u=>l(u,r,t)).filter(Boolean):[];return i.length>0?`(${i.join(" OR ")})`:""}if(n.NOT){const i=l(n.NOT,r,t);return i?`(NOT ${i})`:""}const e=p(n);let c="",$=!1;for(const[i,u]of Object.entries(e)){if(u==null)continue;let T;typeof u!="object"||Array.isArray(u)?T=Array.isArray(u)?{in:u}:{is:u}:T=u;for(const s of Object.keys(T)){const d=T[s];if(d==null)continue;const y=Array.isArray(d)?d:[d];if(y.length!==0){if(s==="fts"){if(!t)throw new Error("FTS requires tableName for JOIN reference");const o=y.map(f=>`EXISTS (SELECT 1 FROM ${t}_fts f WHERE f.rowid = ${r??t}.rowid AND ${t}_fts MATCH ${a(f)})`).join(" AND ");c+=($?" AND ":"")+o,$=!0;continue}if(s==="is"||s==="isNot"||s==="in"||s==="notIn"){const o=A(i,y[0],r),f=y.map(a).join(","),g=s==="is"?y.length>1?`${o} IN (${f})`:`${o} = ${a(y[0])}`:s==="isNot"?y.length>1?`${o} NOT IN (${f})`:`${o} <> ${a(y[0])}`:s==="in"?`${o} IN (${f})`:`${o} NOT IN (${f})`;c+=($?" AND ":"")+g,$=!0;continue}for(const o of y){const f=A(i,o,r),g=s==="gt"?`${f} > ${a(o)}`:s==="gte"?`${f} >= ${a(o)}`:s==="lt"?`${f} < ${a(o)}`:s==="lte"?`${f} <= ${a(o)}`:`${f} LIKE ${a(o)}`;c+=($?" AND ":"")+g,$=!0}}}}return $?`(${c})`:""}function k(n,r){if(!n)return"";const t=l(n,void 0,r);return t?`WHERE ${t}`:""}export{l as getWhere,k as getWhereQuery};
1
+ function $(n){return typeof n=="string"?`'${n.replaceAll("'","''")}'`:typeof n=="number"?n.toString():typeof n=="boolean"?n?"1":"0":`'${String(n).replaceAll("'","''")}'`}function A(n,r,t){const o=t?`${t}.`:"";return n==="KEY"?`"${o}key"`:typeof r=="string"?`CAST(json_extract(${o}data, '$.${n}') AS TEXT)`:typeof r=="number"?`CAST(json_extract(${o}data, '$.${n}') AS NUMERIC)`:typeof r=="boolean"?`CAST(json_extract(${o}data, '$.${n}') AS INTEGER)`:`json_extract(${o}data, '$.${n}')`}const p=new Set(["is","isNot","gt","gte","lt","lte","in","notIn","like","fts"]);function R(n,r=""){const t={};for(const[o,c]of Object.entries(n)){if(o==="AND"||o==="OR"||o==="NOT"){t[o]=c;continue}const a=r?`${r}.${o}`:o;c&&typeof c=="object"&&!Array.isArray(c)&&!Object.keys(c).some(i=>p.has(i))?Object.assign(t,R(c,a)):t[a]=c}return t}function T(n,r,t){if(!n||typeof n!="object")return"";if(n.AND){const i=Array.isArray(n.AND)?n.AND.map(u=>T(u,r,t)).filter(Boolean):[];return i.length>0?`(${i.join(" AND ")})`:""}if(n.OR){const i=Array.isArray(n.OR)?n.OR.map(u=>T(u,r,t)).filter(Boolean):[];return i.length>0?`(${i.join(" OR ")})`:""}if(n.NOT){const i=T(n.NOT,r,t);return i?`(NOT ${i})`:""}const o=R(n);let c="",a=!1;for(const[i,u]of Object.entries(o)){if(u==null)continue;let d;typeof u!="object"||Array.isArray(u)?d=Array.isArray(u)?{in:u}:{is:u}:d=u;for(const s of Object.keys(d)){const l=d[s];if(l==null)continue;const y=Array.isArray(l)?l:[l];if(y.length!==0){if(s==="fts"){if(!t)throw new Error("FTS requires tableName for JOIN reference");const e=y.map(f=>`EXISTS (SELECT 1 FROM ${t}_fts f WHERE f.rowid = ${r??t}.rowid AND ${t}_fts MATCH ${$(f)})`).join(" AND ");c+=(a?" AND ":"")+e,a=!0;continue}if(s==="is"||s==="isNot"||s==="in"||s==="notIn"){const e=A(i,y[0],r),f=y.map($).join(","),g=s==="is"?y.length>1?`${e} IN (${f})`:`${e} = ${$(y[0])}`:s==="isNot"?y.length>1?`${e} NOT IN (${f})`:`${e} <> ${$(y[0])}`:s==="in"?`${e} IN (${f})`:`${e} NOT IN (${f})`;c+=(a?" AND ":"")+g,a=!0;continue}for(const e of y){const f=A(i,e,r),g=s==="gt"?`${f} > ${$(e)}`:s==="gte"?`${f} >= ${$(e)}`:s==="lt"?`${f} < ${$(e)}`:s==="lte"?`${f} <= ${$(e)}`:`${f} LIKE ${$(e)}`;c+=(a?" AND ":"")+g,a=!0}}}}return a?`(${c})`:""}function k(n,r){if(!n)return"";const t=T(n,void 0,r);return t?`WHERE ${t}`:""}export{T as getWhere,k as getWhereQuery};
@@ -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.93",
3
+ "version": "2.4.95",
4
4
  "author": "samuel.gjabel@gmail.com",
5
5
  "repository": "https://github.com/samuelgjabel/muya",
6
6
  "main": "cjs/index.js",
@@ -0,0 +1,234 @@
1
+ import { createTable } from '../table'
2
+ import { bunMemoryBackend } from '../table/bun-backend'
3
+ import { type Where } from '../table/where'
4
+
5
+ describe('where clauses', () => {
6
+ const backend = bunMemoryBackend()
7
+
8
+ it('should handle where where array of conditions', async () => {
9
+ const tableNestedOptional = await createTable<{ id: string; content?: string }>({
10
+ backend,
11
+ tableName: 'TestTableNestedOptional',
12
+ key: 'id',
13
+ indexes: ['content'],
14
+ })
15
+
16
+ await tableNestedOptional.set({ id: '1', content: 'The quick brown fox' })
17
+ await tableNestedOptional.set({ id: '2', content: 'The jumps over the lazy dog' })
18
+ await tableNestedOptional.set({ id: '3' }) // No `content` field
19
+
20
+ const results: { id: string; content?: string }[] = []
21
+ for await (const doc of tableNestedOptional.search({
22
+ where: { content: { like: ['The%'] } },
23
+ })) {
24
+ results.push(doc)
25
+ }
26
+ expect(results.length).toBe(2)
27
+ expect(results[0].id).toBe('1')
28
+ })
29
+ it('should create nested index for optional nested fields', async () => {
30
+ type TestDoc = { id: string; info?: { content?: string } }
31
+ const tableNestedOptional = await createTable<TestDoc>({
32
+ backend,
33
+ tableName: 'TestTableNestedOptional',
34
+ key: 'id',
35
+ indexes: ['fts:info.content'],
36
+ })
37
+
38
+ await tableNestedOptional.set({ id: '1', info: { content: 'The quick brown fox' } })
39
+ await tableNestedOptional.set({ id: '2', info: { content: 'jumps over the lazy dog' } })
40
+ await tableNestedOptional.set({ id: '3' }) // No `info` field
41
+
42
+ const results: TestDoc[] = []
43
+ for await (const doc of tableNestedOptional.search({
44
+ where: { info: { content: { like: ['The%'] } } },
45
+ })) {
46
+ results.push(doc)
47
+ }
48
+ expect(results.length).toBe(1)
49
+ expect(results[0].id).toBe('1')
50
+
51
+ const results2: TestDoc[] = []
52
+ for await (const doc of tableNestedOptional.search({
53
+ where: { OR: [{ info: { content: { like: ['The%'] } } }, { info: { content: { like: ['jumps%'] } } }] },
54
+ })) {
55
+ results2.push(doc)
56
+ }
57
+ expect(results2.length).toBe(2)
58
+
59
+ const results3: TestDoc[] = []
60
+ for await (const doc of tableNestedOptional.search({
61
+ where: { info: { content: 'nonexistent' } },
62
+ })) {
63
+ results3.push(doc)
64
+ }
65
+ expect(results3.length).toBe(0)
66
+ })
67
+ it('should handle FTS queries', async () => {
68
+ const tableFts = await createTable<{ id: string; content: string }>({
69
+ backend,
70
+ tableName: 'TestTableFts',
71
+ key: 'id',
72
+ indexes: ['fts:content'],
73
+ })
74
+
75
+ await tableFts.set({ id: '1', content: 'The quick brown fox' })
76
+ await tableFts.set({ id: '2', content: 'Jumps over the lazy dog' })
77
+ await tableFts.set({ id: '3', content: 'Another document' })
78
+
79
+ const results: { id: string; content: string }[] = []
80
+ for await (const doc of tableFts.search({
81
+ where: { content: { fts: 'quick' } },
82
+ })) {
83
+ results.push(doc)
84
+ }
85
+ expect(results.length).toBe(1)
86
+ expect(results[0].id).toBe('1')
87
+ })
88
+
89
+ it('should handle nested where conditions', async () => {
90
+ const tableNested = await createTable<{ id: string; info: { content: string } }>({
91
+ backend,
92
+ tableName: 'TestTableNested',
93
+ key: 'id',
94
+ indexes: ['info.content'],
95
+ })
96
+
97
+ await tableNested.set({ id: '1', info: { content: 'Nested quick brown fox' } })
98
+ await tableNested.set({ id: '2', info: { content: 'Nested jumps over the lazy dog' } })
99
+
100
+ const results: { id: string; info: { content: string } }[] = []
101
+ for await (const doc of tableNested.search({
102
+ where: { info: { content: { like: 'Nested%' } } },
103
+ })) {
104
+ results.push(doc)
105
+ }
106
+ expect(results.length).toBe(2)
107
+ })
108
+
109
+ it('should handle complex operators', async () => {
110
+ const tableComplex = await createTable<{ id: string; value: number }>({
111
+ backend,
112
+ tableName: 'TestTableComplex',
113
+ key: 'id',
114
+ indexes: ['value'],
115
+ })
116
+
117
+ await tableComplex.set({ id: '1', value: 10 })
118
+ await tableComplex.set({ id: '2', value: 20 })
119
+ await tableComplex.set({ id: '3', value: 30 })
120
+
121
+ const results: { id: string; value: number }[] = []
122
+ for await (const doc of tableComplex.search({
123
+ where: { value: { gt: 15, lt: 25 } },
124
+ sortBy: 'value',
125
+ })) {
126
+ results.push(doc)
127
+ }
128
+ expect(results.length).toBe(1)
129
+ expect(results[0].id).toBe('2')
130
+ })
131
+
132
+ it('should handle NOT conditions', async () => {
133
+ const tableNot = await createTable<{ id: string; value: string }>({
134
+ backend,
135
+ tableName: 'TestTableNot',
136
+ key: 'id',
137
+ indexes: ['value'],
138
+ })
139
+
140
+ await tableNot.set({ id: '1', value: 'apple' })
141
+ await tableNot.set({ id: '2', value: 'banana' })
142
+ await tableNot.set({ id: '3', value: 'cherry' })
143
+
144
+ const results: { id: string; value: string }[] = []
145
+ for await (const doc of tableNot.search({
146
+ where: { NOT: { value: 'banana' } },
147
+ })) {
148
+ results.push(doc)
149
+ }
150
+ expect(results.length).toBe(2)
151
+ expect(results.map((doc) => doc.value)).toEqual(['apple', 'cherry'])
152
+ })
153
+
154
+ it('should handle AND conditions', async () => {
155
+ const tableAnd = await createTable<{ id: string; category: string; price: number }>({
156
+ backend,
157
+ tableName: 'TestTableAnd',
158
+ key: 'id',
159
+ indexes: ['category', 'price'],
160
+ })
161
+
162
+ await tableAnd.set({ id: '1', category: 'fruit', price: 10 })
163
+ await tableAnd.set({ id: '2', category: 'fruit', price: 20 })
164
+ await tableAnd.set({ id: '3', category: 'vegetable', price: 15 })
165
+
166
+ const results: { id: string; category: string; price: number }[] = []
167
+ for await (const doc of tableAnd.search({
168
+ where: { AND: [{ category: 'fruit' }, { price: { lt: 15 } }] },
169
+ })) {
170
+ results.push(doc)
171
+ }
172
+ expect(results.length).toBe(1)
173
+ expect(results[0].id).toBe('1')
174
+ })
175
+
176
+ it('should handle OR conditions', async () => {
177
+ const tableOr = await createTable<{ id: string; type: string }>({
178
+ backend,
179
+ tableName: 'TestTableOr',
180
+ key: 'id',
181
+ indexes: ['type'],
182
+ })
183
+
184
+ await tableOr.set({ id: '1', type: 'A' })
185
+ await tableOr.set({ id: '2', type: 'B' })
186
+ await tableOr.set({ id: '3', type: 'C' })
187
+
188
+ const results: { id: string; type: string }[] = []
189
+ for await (const doc of tableOr.search({
190
+ where: { OR: [{ type: 'A' }, { type: 'C' }] },
191
+ })) {
192
+ results.push(doc)
193
+ }
194
+ expect(results.length).toBe(2)
195
+ expect(results.map((doc) => doc.type)).toEqual(['A', 'C'])
196
+ })
197
+
198
+ it('should handle nested AND/OR/NOT conditions', async () => {
199
+ const tableNestedLogic = await createTable<{ id: string; category: string; price: number }>({
200
+ backend,
201
+ tableName: 'TestTableNestedLogic',
202
+ key: 'id',
203
+ indexes: ['category', 'price'],
204
+ })
205
+
206
+ await tableNestedLogic.set({ id: '1', category: 'fruit', price: 10 })
207
+ await tableNestedLogic.set({ id: '2', category: 'fruit', price: 20 })
208
+ await tableNestedLogic.set({ id: '3', category: 'fruit', price: 15 })
209
+
210
+ const whereClause: Where<{ category: string; price: number }> = {
211
+ AND: [
212
+ { category: { is: 'fruit' } },
213
+ {
214
+ OR: [{ price: { lt: 15 } }, { price: { is: 15 } }],
215
+ },
216
+ ],
217
+ }
218
+
219
+ const allData: { id: string; category: string; price: number }[] = []
220
+ for await (const doc of tableNestedLogic.search({})) {
221
+ allData.push(doc)
222
+ }
223
+ expect(allData.length).toBe(3)
224
+
225
+ const results: { id: string; category: string; price: number }[] = []
226
+ for await (const doc of tableNestedLogic.search({
227
+ where: whereClause,
228
+ })) {
229
+ results.push(doc)
230
+ }
231
+
232
+ expect(results.length).toBe(2)
233
+ })
234
+ })
@@ -9,21 +9,21 @@ 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
21
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>
22
22
 
23
- readonly delete: (key: Key) => Promise<MutationResult | undefined>
23
+ readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>
24
24
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>
25
25
  readonly count: (options?: { where?: Where<Document> }) => Promise<number>
26
- readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>
26
+ readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>
27
27
  readonly clear: () => Promise<void>
28
28
  }
29
29
 
@@ -53,8 +53,8 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
53
53
  if (!unknownItems) {
54
54
  return
55
55
  }
56
- const items = unknownItems as MutationItems[]
57
- const merged: MutationItems = {}
56
+ const items = unknownItems as MutationItems<Document>[]
57
+ const merged: MutationItems<Document> = {}
58
58
  for (const item of items) {
59
59
  if (item.removedAll) {
60
60
  merged.removedAll = true
@@ -76,11 +76,11 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
76
76
  * Notify all subscribers of changes
77
77
  * @param item The mutation items to notify subscribers about
78
78
  */
79
- function handleChanges(item: MutationItems) {
79
+ function handleChanges(item: MutationItems<Document>) {
80
80
  STATE_SCHEDULER.schedule(id, item)
81
81
  }
82
82
 
83
- const listeners = new Set<(mutation: MutationItems) => void>()
83
+ const listeners = new Set<(mutation: MutationItems<Document>) => void>()
84
84
 
85
85
  const state: SyncTable<Document> = {
86
86
  subscribe(listener) {
@@ -181,10 +181,10 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
181
181
 
182
182
  if (existing.length > 0) {
183
183
  await db.execute(`UPDATE ${tableName} SET data = ? WHERE key = ?`, [json, id])
184
- return { key: id, op: 'update' }
184
+ return { key: id, op: 'update', document }
185
185
  } else {
186
186
  await db.execute(`INSERT INTO ${tableName} (key, data) VALUES (?, ?)`, [id, json])
187
- return { key: id, op: 'insert' }
187
+ return { key: id, op: 'insert', document }
188
188
  }
189
189
  }
190
190
 
@@ -192,7 +192,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
192
192
  const rows = await db.select<Array<{ id: number }>>(`SELECT last_insert_rowid() AS id`)
193
193
  const rowid = rows[0]?.id
194
194
  if (typeof rowid !== 'number') throw new Error('Failed to retrieve last_insert_rowid()')
195
- return { key: rowid, op: 'insert' }
195
+ return { key: rowid, op: 'insert', document }
196
196
  },
197
197
 
198
198
  async get<Selected = Document>(
@@ -274,7 +274,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
274
274
  async deleteBy(where: Where<Document>) {
275
275
  const whereSql = getWhereQuery<Document>(where, tableName)
276
276
  const keyCol = hasUserKey ? 'key' : 'rowid'
277
- const results: MutationResult[] = []
277
+ const results: MutationResult<Document>[] = []
278
278
 
279
279
  await backend.transaction(async (tx) => {
280
280
  const rows = await tx.select<Array<{ k: Key }>>(`SELECT ${keyCol} AS k FROM ${tableName} ${whereSql}`)
@@ -287,7 +287,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
287
287
  await tx.execute(`DELETE FROM ${tableName} WHERE ${keyCol} IN (${placeholders})`, chunk as unknown[])
288
288
  }
289
289
 
290
- for (const k of allKeys) results.push({ key: k, op: 'delete' })
290
+ for (const k of allKeys) results.push({ key: k, op: 'delete', document: undefined })
291
291
  })
292
292
 
293
293
  return results
@@ -298,7 +298,7 @@ export async function createTable<Document extends DocType>(options: DbOptions<D
298
298
  },
299
299
 
300
300
  async batchSet(documents: Document[]) {
301
- const mutations: MutationResult[] = []
301
+ const mutations: MutationResult<Document>[] = []
302
302
  await backend.transaction(async (tx) => {
303
303
  for (const document of documents) {
304
304
  const m = await table.set(document, tx)
@@ -20,14 +20,16 @@ export type DotPrefix<T extends string> = T extends '' ? '' : `.${T}`
20
20
 
21
21
  type Previous = [never, 0, 1, 2, 3, 4, 5]
22
22
 
23
- export type DotPath<T, D extends number = 5> = [D] extends [never]
23
+ type DotPathRaw<T, D extends number = 5> = [D] extends [never]
24
24
  ? never
25
25
  : T extends object
26
26
  ? {
27
- [K in Extract<keyof T, string>]: T[K] extends object ? K | `${K}.${DotPath<T[K], Previous[D]>}` : K
27
+ [K in Extract<keyof T, string>]: T[K] extends object ? K | `${K}.${DotPathRaw<T[K], Previous[D]>}` : K
28
28
  }[Extract<keyof T, string>]
29
29
  : never
30
30
 
31
+ export type DotPath<T> = DotPathRaw<MakeAllFieldAsRequired<T>>
32
+
31
33
  // Built-in FTS5 tokenizers
32
34
  export type FtsTokenizer =
33
35
  | 'porter' // English stemming
@@ -65,19 +67,37 @@ interface DbNotGeneric {
65
67
  export type Key = string | number
66
68
 
67
69
  export type MutationOp = 'insert' | 'update' | 'delete'
68
- export interface MutationResult {
70
+
71
+ interface MutationResultBase<T> {
69
72
  key: Key
70
73
  op: MutationOp
74
+ document?: T
75
+ }
76
+ interface MutationResultDelete<T> extends MutationResultBase<T> {
77
+ key: Key
78
+ op: 'delete'
79
+ }
80
+
81
+ interface MutationResultUpdateInsert<T> extends MutationResultBase<T> {
82
+ key: Key
83
+ op: 'update' | 'insert'
84
+ document: T
71
85
  }
72
86
 
87
+ export type MutationResult<T> = MutationResultDelete<T> | MutationResultUpdateInsert<T>
88
+
73
89
  export interface Table<Document extends DocType> extends DbNotGeneric {
74
- readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult>
75
- readonly batchSet: (documents: Document[]) => Promise<MutationResult[]>
90
+ readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult<Document>>
91
+ readonly batchSet: (documents: Document[]) => Promise<MutationResult<Document>[]>
76
92
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>
77
93
 
78
- readonly delete: (key: Key) => Promise<MutationResult | undefined>
94
+ readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>
79
95
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>
80
96
  readonly count: (options?: { where?: Where<Document> }) => Promise<number>
81
- readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>
97
+ readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>
82
98
  readonly clear: () => Promise<void>
83
99
  }
100
+
101
+ export type MakeAllFieldAsRequired<T> = {
102
+ [K in keyof T]-?: T[K] extends object ? MakeAllFieldAsRequired<T[K]> : T[K]
103
+ }
@@ -4,6 +4,8 @@
4
4
  /* eslint-disable sonarjs/no-nested-conditional */
5
5
  /* eslint-disable sonarjs/cognitive-complexity */
6
6
 
7
+ import type { MakeAllFieldAsRequired } from './table.types'
8
+
7
9
  // -------------------------------------------------------------
8
10
  // Condition operators for each field
9
11
  // -------------------------------------------------------------
@@ -20,21 +22,22 @@ interface Condition<T> {
20
22
  readonly fts?: string | string[] // 🔥 NEW
21
23
  }
22
24
 
23
- // -------------------------------------------------------------
24
- // Where type: recursive object with operators or nested fields
25
- // -------------------------------------------------------------
26
- export type Where<T extends Record<string, unknown>> =
25
+ type WhereRaw<T extends Record<string, unknown>> =
27
26
  | {
28
27
  [K in keyof T]?: T[K] extends Record<string, unknown>
29
- ? Where<T[K]> // nested object
28
+ ? WhereRaw<T[K]> // nested object
30
29
  : Condition<T[K]> | T[K] | T[K][]
31
30
  }
32
31
  | {
33
- readonly AND?: Array<Where<T>>
34
- readonly OR?: Array<Where<T>>
35
- readonly NOT?: Where<T>
32
+ readonly AND?: Array<WhereRaw<T>>
33
+ readonly OR?: Array<WhereRaw<T>>
34
+ readonly NOT?: WhereRaw<T>
36
35
  }
37
36
 
37
+ // -------------------------------------------------------------
38
+ // Where type: recursive object with operators or nested fields
39
+ // -------------------------------------------------------------
40
+ export type Where<T extends Record<string, unknown>> = WhereRaw<MakeAllFieldAsRequired<T>>
38
41
  /**
39
42
  * Inline a value for SQL query, with proper escaping for strings
40
43
  * @param value The value to inline
@@ -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,21 @@ 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
15
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>;
16
- readonly delete: (key: Key) => Promise<MutationResult | undefined>;
16
+ readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>;
17
17
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>;
18
18
  readonly count: (options?: {
19
19
  where?: Where<Document>;
20
20
  }) => Promise<number>;
21
- readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>;
21
+ readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>;
22
22
  readonly clear: () => Promise<void>;
23
23
  }
24
24
  /**
@@ -15,9 +15,10 @@ export interface SqlSeachOptions<Document extends DocType> {
15
15
  }
16
16
  export type DotPrefix<T extends string> = T extends '' ? '' : `.${T}`;
17
17
  type Previous = [never, 0, 1, 2, 3, 4, 5];
18
- export type DotPath<T, D extends number = 5> = [D] extends [never] ? never : T extends object ? {
19
- [K in Extract<keyof T, string>]: T[K] extends object ? K | `${K}.${DotPath<T[K], Previous[D]>}` : K;
18
+ type DotPathRaw<T, D extends number = 5> = [D] extends [never] ? never : T extends object ? {
19
+ [K in Extract<keyof T, string>]: T[K] extends object ? K | `${K}.${DotPathRaw<T[K], Previous[D]>}` : K;
20
20
  }[Extract<keyof T, string>] : never;
21
+ export type DotPath<T> = DotPathRaw<MakeAllFieldAsRequired<T>>;
21
22
  export type FtsTokenizer = 'porter' | 'simple' | 'icu' | 'unicode61' | FtsTokenizerOptions;
22
23
  export interface FtsType<Document extends DocType> {
23
24
  readonly type: 'fts';
@@ -43,20 +44,34 @@ interface DbNotGeneric {
43
44
  }
44
45
  export type Key = string | number;
45
46
  export type MutationOp = 'insert' | 'update' | 'delete';
46
- export interface MutationResult {
47
+ interface MutationResultBase<T> {
47
48
  key: Key;
48
49
  op: MutationOp;
50
+ document?: T;
49
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>;
50
62
  export interface Table<Document extends DocType> extends DbNotGeneric {
51
- readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult>;
52
- readonly batchSet: (documents: Document[]) => Promise<MutationResult[]>;
63
+ readonly set: (document: Document, backendOverride?: Backend) => Promise<MutationResult<Document>>;
64
+ readonly batchSet: (documents: Document[]) => Promise<MutationResult<Document>[]>;
53
65
  readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>;
54
- readonly delete: (key: Key) => Promise<MutationResult | undefined>;
66
+ readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>;
55
67
  readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>;
56
68
  readonly count: (options?: {
57
69
  where?: Where<Document>;
58
70
  }) => Promise<number>;
59
- readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>;
71
+ readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>;
60
72
  readonly clear: () => Promise<void>;
61
73
  }
74
+ export type MakeAllFieldAsRequired<T> = {
75
+ [K in keyof T]-?: T[K] extends object ? MakeAllFieldAsRequired<T[K]> : T[K];
76
+ };
62
77
  export {};
@@ -1,3 +1,4 @@
1
+ import type { MakeAllFieldAsRequired } from './table.types';
1
2
  interface Condition<T> {
2
3
  readonly is?: T | T[];
3
4
  readonly isNot?: T | T[];
@@ -10,13 +11,14 @@ interface Condition<T> {
10
11
  readonly like?: T | T[];
11
12
  readonly fts?: string | string[];
12
13
  }
13
- export type Where<T extends Record<string, unknown>> = {
14
- [K in keyof T]?: T[K] extends Record<string, unknown> ? Where<T[K]> : Condition<T[K]> | T[K] | T[K][];
14
+ type WhereRaw<T extends Record<string, unknown>> = {
15
+ [K in keyof T]?: T[K] extends Record<string, unknown> ? WhereRaw<T[K]> : Condition<T[K]> | T[K] | T[K][];
15
16
  } | {
16
- readonly AND?: Array<Where<T>>;
17
- readonly OR?: Array<Where<T>>;
18
- readonly NOT?: Where<T>;
17
+ readonly AND?: Array<WhereRaw<T>>;
18
+ readonly OR?: Array<WhereRaw<T>>;
19
+ readonly NOT?: WhereRaw<T>;
19
20
  };
21
+ export type Where<T extends Record<string, unknown>> = WhereRaw<MakeAllFieldAsRequired<T>>;
20
22
  /**
21
23
  * Write SQL WHERE clause from a Where object
22
24
  * @param where The Where object defining the conditions