tinyplex / tinybase Goto Github PK
View Code? Open in Web Editor NEWThe reactive data store for local‑first apps.
Home Page: https://tinybase.org
License: MIT License
The reactive data store for local‑first apps.
Home Page: https://tinybase.org
License: MIT License
As described
No response
No response
No response
No response
No response
Is your feature request related to a problem? Please describe.
I'm working on a local app that conceptually has a list of items pertaining to a certain parent project. For simplicity, the schema could be:
{
project: {
id: string
},
item: {
id: string,
projectId: string,
isComplete: boolean
}
}
I'd like to be able to have a metric that allows me to calculate percentage calculation for each project based on its related items
Describe the solution you'd like
It seems like it would make sense to be able to define a metric definition that targets a specific index instead of the overall table.
Describe alternatives you've considered
We can definitely recreate the aggregate completion calculation using other TinyBase primitives but it feels like there's an opportunity for an elegant addition to the core API
Additional context
Add any other context or screenshots about the feature request here.
Is there any way or example on how to use authentication & user context with TinyBase?
For example in the Todo application. Let's say there are many users & todos in the remote stored database.
A user logins using direct access to the remote database. Then only sync Todo's associated with that user locally.
Without Select, queries don'f fetch anything. And since Select('*') is not supported there isn't a way to use getResultTable to return the row.
Think about whether this is for the root table or for every table referenced and how to deal with duplicate cell Ids
Via @founderYonz
When adding an import "tinybase"
or require("tinybase")
in a file that is then passed to Browserify for buliding, it fails with the error
Error: Can't walk dependency graph: Cannot find module 'tinybase'
Perhaps it is related to bundler requirements, but I build by app with many other packages and they all work just fine.
https://github.com/shaneosullivan/tinybaseRepro
1: Go to https://github.com/shaneosullivan/tinybaseRepro and check out the project
2: Run npm install
3: Run npm run repro
It should successfully build the file output.js
without error
No response
No response
Hi everyone,
if I follow the get started guide for node.js the following error gets thrown:
file:///Users/user/dev/projectname/node_modules/tinybase/lib/tinybase.js:1
import{promises as e,watch as t}from"fs";const s=e=>typeof e,n=s(""),o=s(!0),r=s(0),a=s(s),i=(e,t)=>e.includes(t),d=(e,t)=>e.every(((s,n)=>0==n||t(e[n-1],s)<=0)),l=(e,t)=>e.sort(t),c=(e,t)=>e.forEach(t),u=e=>f(e,((e,t)=>e+t),0),h=e=>e.length,g=e=>0==h(e),f=(e,t,s)=>e.reduce(t,s),L=e=>e.slice(1),p=e=>JSON.stringify(e,((e,t)=>y(t,Map)?f([...t],((e,[t,s])=>(e[t]=s,e)),{}):t)),w=JSON.parse,v=Math.max,I=Math.min,S=isFinite,y=(e,t)=>e instanceof t,R=e=>null==e,T=(e,t,s)=>R(e)?s?.():t(e),b=e=>e==n||e==o,C=e=>s(e)==a,m=()=>{},E=e=>e.size,k=(e,t)=>e?.has(t)??!1,M=e=>R(e)||0==E(e),A=e=>[...e?.values()??[]],x=e=>e.clear(),D=(e,t)=>e?.forEach(t),J=(e,t)=>e?.delete(t),F=e=>new Map(e),z=(e=F)=>[e(),e()],N=e=>[...e?.keys()??[]],O=(e,t)=>e?.get(t),j=(e,t)=>D(e,((e,s)=>t(s,e))),P=(e,t,s)=>R(s)?(J(e,t),e):e?.set(t,s),B=(e,t,s,n)=>(k(e,t)||(n?.(s),e.set(t,s)),O(e,t)),H=(e,t)=>{const s={},n=t??(e=>e);return D(e,((e,t)=>s[t]=n(e))),s},W=(e,t)=>{const s=F(),n=t??(e=>e);return D(e,((e,t)=>s.set(t,n(e)))),s},q=e=>new Set(e),G=(e,t)=>e?.add(t),K=(e,t,s)=>{const n=e.hasRow,o=F(),r=F(),a=F(),i=F(),d=F(),l=t=>T(O(d,t),(s=>{D(s,e.delListener),P(d,t)})),u=e=>{P(o,e),P(r,e),P(a,e),P(i,e),l(e)};return[()=>e,()=>N(o),e=>j(r,e),e=>k(r,e),e=>O(o,e),e=>O(r,e),(e,t)=>P(r,e,t),(u,h,g,f,L)=>{const p=F(),w=F();P(o,u,h),k(r,u)||(P(r,u,t()),P(a,u,F()),P(i,u,F()));const v=O(a,u),I=O(i,u),S=t=>{const o=s=>e.getCell(h,t,s),r=O(v,t),a=n(h,t)?s(f(o,t)):void 0;if(r!=a&&P(p,t,[r,a]),!R(L)){const e=O(I,t),s=n(h,t)?L(o,t):void 0;e!=s&&P(w,t,s)}},y=e=>{g((()=>{D(p,(([,e],t)=>P(v,t,e))),D(w,((e,t)=>P(I,t,e)))}),p,w,v,I,e),x(p),x(w)};j(v,S),e.hasTable(h)&&c(e.getRowIds(h),(e=>{k(v,e)||S(e)})),y(!0),l(u),P(d,u,q([e.addRowListener(h,null,((e,t,s)=>S(s))),e.addTableListener(h,(()=>y()))]))},u,()=>j(d,u)]},Q=(e,t)=>s(e)==n?t=>t(e):e??(()=>t??""),U=e=>{const t=new WeakMap;return s=>(t.has(s)||t.set(s,e(s)),t.get(s))},V=(e,t,s)=>h(s)<2?G(g(s)?e:B(e,s[0],q()),t):V(B(e,s[0],F()),t,L(s)),X=e=>{const t=(s,n,...o)=>T(s,(s=>g(o)?e(s,n):c([o[0],null],(e=>t(O(s,e),n,...L(o))))));return t},Y=e=>{let t,s=0;const n=[],o=F();return[(r,a,i=[])=>{t??=e();const d=n.pop()??""+s++;return P(o,d,[r,a,i]),V(a,d,i),d},(e,s=[],...n)=>X(D)(e,(e=>T(O(o,e),(([e])=>e(t,...s,...n)))),...s),e=>T(O(o,e),(([,t,s])=>(X(J)(t,e,...s),P(o,e),h(n)<1e3&&n.push(e),s)),(()=>[])),(e,s,n)=>T(O(o,e),(([e,,o])=>{const r=(...a)=>{const i=h(a);i==h(o)?e(t,...a,...n(a)):R(o[i])?c(s[i](...a),(e=>r(...a,e))):r(...a,o[i])};r()}))]},Z=Object,$=Z.keys,_=Z.isFrozen,ee=Z.freeze,te=(e,t)=>!R(((e,t)=>T(e,(e=>e[t])))(e,t)),se=(e,t)=>delete e[t],ne=(e,t)=>c(Z.entries(e),(([e,s])=>t(s,e))),oe=U((e=>{let t,s,n,o=100,r=F(),a=1;const d=q(),l=F(),[u,f,L]=Y((()=>W)),p=F(),w=F(),v=[],I=[],S=(t,s)=>{a=0,e.transaction((()=>D(O(p,s),((s,n)=>D(s,((s,o)=>D(s,((s,r)=>R(s[t])?e.delCell(n,o,r,!0):e.setCell(n,o,r,s[t]))))))))),a=1},y=e=>{P(p,e),P(w,e),f(l,[e])},b=(e,t)=>c(((e,t)=>e.splice(0,t))(e,t??h(e)),y),C=()=>b(v,h(v)-o),m=e.addCellListener(null,null,null,((e,s,o,i,d,l)=>{if(a){T(t,(()=>{v.push(t),C(),b(I),t=void 0,n=1}));const e=B(r,s,F()),a=B(e,o,F()),c=B(a,i,[void 0,void 0],(e=>e[0]=l));c[1]=d,c[0]===c[1]&&M(P(a,i))&&M(P(e,o))&&M(P(r,s))&&(t=v.pop(),n=1),J()}})),E=(e="")=>(R(t)&&(t=""+s++,P(p,t,r),N(t,e),r=F(),n=1),t),A=()=>{g(v)||(I.unshift(E()),S(0,t),t=v.pop(),n=1)},x=()=>{g(I)||(v.push(t),t=I.shift(),S(1,t),n=1)},J=()=>{n&&(f(d),n=0)},z=e=>{const t=E(e);return J(),t},N=(e,t)=>(H(e)&&O(w,e)!==t&&(P(w,e,t),f(l,[e])),W),H=e=>k(p,e),W={setSize:e=>(o=e,C(),W),addCheckpoint:z,setCheckpoint:N,getStore:()=>e,getCheckpointIds:()=>[[...v],t,[...I]],forEachCheckpoint:e=>j(w,e),hasCheckpoint:H,getCheckpoint:e=>O(w,e),goBackward:()=>(A(),J(),W),goForward:()=>(x(),J(),W),goTo:e=>{const s=i(v,e)?A:i(I,e)?x:null;for(;!R(s)&&e!=t;)s();return J(),W},addCheckpointIdsListener:e=>u(e,d),addCheckpointListener:(e,t)=>u(t,l,[e]),delListener:e=>(L(e),W),clear:()=>(b(v),b(I),R(t)||y(t),t=void 0,s=0,z(),W),destroy:()=>{e.delListener(m)},getListenerStats:()=>({})};return ee(W.clear())})),re=(e,t)=>e<t?-1:1,ae=U((e=>{const t=F(),s=F(),[n,o,r,a,i,c,u,h,g,f]=K(e,F,(e=>R(e)?"":e+"")),[L,p,w]=Y((()=>I)),v=(t,s,n)=>{const o=i(t);D(n,((t,n)=>s(n,(s=>D(t,(t=>s(t,(s=>e.forEachCell(o,t,s)))))))))},I={setIndexDefinition:(e,n,o,r,a,i=re)=>{const g=R(a)?void 0:([e],[t])=>a(e,t);return h(e,n,((n,o,a,h,f,L)=>{let w=0;const v=q(),I=q(),S=c(e);if(D(o,(([e,t],s)=>{R(e)||(G(v,e),T(O(S,e),(t=>{J(t,s),M(t)&&(P(S,e),w=1)}))),R(t)||(G(v,t),k(S,t)||(P(S,t,q()),w=1),G(O(S,t),s),R(r)||G(I,t))})),n(),M(f)||(L?j(S,(e=>G(I,e))):j(a,(e=>T(O(h,e),(e=>G(I,e))))),D(I,(e=>{const t=(t,s)=>i(O(f,t),O(f,s),e),s=[...O(S,e)];d(s,t)||(P(S,e,q(l(s,t))),G(v,e))}))),(w||L)&&!R(g)){const t=[...S];d(t,g)||(u(e,F(l(t,g))),w=1)}w&&p(t,[e]),D(v,(t=>p(s,[e,t])))}),Q(o),T(r,Q)),I},delIndexDefinition:e=>(g(e),I),getStore:n,getIndexIds:o,forEachIndex:e=>r(((t,s)=>e(t,(e=>v(t,e,s))))),forEachSlice:(e,t)=>v(e,t,c(e)),hasIndex:a,hasSlice:(e,t)=>k(c(e),t),getTableId:i,getSliceIds:e=>N(c(e)),getSliceRowIds:(e,t)=>A(O(c(e),t)),addSliceIdsListener:(e,s)=>L(s,t,[e]),addSliceRowIdsListener:(e,t,n)=>L(n,s,[e,t]),delListener:e=>(w(e),I),destroy:f,getListenerStats:()=>({})};return ee(I)})),ie=F([["avg",[(e,t)=>u(e)/t,(e,t,s)=>e+(t-e)/(s+1),(e,t,s)=>e+(e-t)/(s-1),(e,t,s,n)=>e+(t-s)/n]],["max",[e=>v(...e),(e,t)=>v(t,e),(e,t)=>t==e?void 0:e,(e,t,s)=>s==e?void 0:v(t,e)]],["min",[e=>I(...e),(e,t)=>I(t,e),(e,t)=>t==e?void 0:e,(e,t,s)=>s==e?void 0:I(t,e)]],["sum",[e=>u(e),(e,t)=>e+t,(e,t)=>e-t,(e,t,s)=>e-s+t]]]),de=U((e=>{const t=F(),[s,n,o,r,a,i,d,l,c,u]=K(e,m,(e=>isNaN(e)||R(e)||!0===e||!1===e||""===e?void 0:1*e)),[h,g,f]=Y((()=>L)),L={setMetricDefinition:(e,s,n,o,r,a,c)=>{const u=C(n)?[n,r,a,c]:O(ie,n)??O(ie,"sum");return l(e,s,((s,n,o,r,a,l)=>{let c=i(e),h=E(r);const[f,L,p,w]=u;l=l||R(c),D(n,(([e,t])=>{l||(c=R(e)?L?.(c,t,h++):R(t)?p?.(c,e,h--):w?.(c,t,e,h)),l=l||R(c)})),s(),M(r)?c=void 0:l&&(c=f(A(r),E(r))),S(c)||(c=void 0);const v=i(e);c!=v&&(d(e,c),g(t,[e],c,v))}),Q(o,1)),L},delMetricDefinition:e=>(c(e),L),getStore:s,getMetricIds:n,forEachMetric:o,hasMetric:r,getTableId:a,getMetric:i,addMetricListener:(e,s)=>h(s,t,[e]),delListener:e=>(f(e),L),destroy:u,getListenerStats:()=>({})};return ee(L)})),le=(e,t,s,n,o)=>{let r,a=0;const i={load:async s=>{if(2!=a){a=1;const n=await t();R(n)||""==n?e.setTables(s):e.setJson(n),a=0}return i},startAutoLoad:async e=>(i.stopAutoLoad(),await i.load(e),n(i.load),i),stopAutoLoad:()=>(o(),i),save:async()=>(1!=a&&(a=2,await s(e.getJson()),a=0),i),startAutoSave:async()=>(await i.stopAutoSave().save(),r=e.addTablesListener((()=>i.save())),i),stopAutoSave:()=>(T(r,e.delListener),i),getStore:()=>e,destroy:()=>i.stopAutoLoad().stopAutoSave(),getStats:()=>({})};return ee(i)},ce=globalThis.window,ue=(e,t,s)=>{let n;return le(e,(async()=>s.getItem(t)),(async e=>s.setItem(t,e)),(e=>{n=n=>{n.storageArea===s&&n.key===t&&e()},ce.addEventListener("storage",n)}),(()=>{ce.removeEventListener("storage",n),n=void 0}))},he=(e,t)=>ue(e,t,localStorage),ge=(e,t)=>ue(e,t,sessionStorage),fe=(s,n)=>{let o;return le(s,(async()=>{try{return await e.readFile(n,"utf8")}catch{}}),(async t=>{try{await e.writeFile(n,t,"utf8")}catch{}}),(e=>{o=t(n,e)}),(()=>{o?.close(),o=void 0}))},Le=e=>e.headers.get("ETag"),pe=(e,t,s,n)=>{let o,r;return le(e,(async()=>{const e=await fetch(t);return r=Le(e),e.text()}),(async e=>await fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:e})),(e=>{o=setInterval((async()=>{const s=await fetch(t,{method:"HEAD"}),n=Le(s);R(r)||R(n)||n==r||(r=n,e())}),1e3*n)}),(()=>{T(o,clearInterval),o=void 0}))},we=U((e=>{const t=F(),s=F(),n=F(),o=F(),[r,a,i,d,l,c,u,h,g,f]=K(e,(()=>[F(),F(),F(),F()]),(e=>R(e)?void 0:e+"")),[L,p,w]=Y((()=>y)),v=(e,t,s)=>T(c(e),(([n,,o])=>{if(!k(o,t)){const r=q();if(l(e)!=S(e))G(r,t);else{let e=t;for(;!R(e)&&!k(r,e);)G(r,e),e=O(n,e)}if(s)return r;P(o,t,r)}return O(o,t)})),I=(e,t)=>T(c(e),(([,,e])=>P(e,t))),S=e=>O(t,e),y={setRelationshipDefinition:(e,r,a,i)=>(P(t,e,a),h(e,r,((t,r)=>{const a=q(),i=q(),d=q(),[l,u]=c(e);D(r,(([t,s],n)=>{R(t)||(G(i,t),T(O(u,t),(e=>{J(e,n),M(e)&&P(u,t)}))),R(s)||(G(i,s),k(u,s)||P(u,s,q()),G(O(u,s),n)),G(a,n),P(l,n,s),j(O(o,e),(t=>{k(v(e,t),n)&&G(d,t)}))})),t(),D(a,(t=>p(s,[e,t]))),D(i,(t=>p(n,[e,t]))),D(d,(t=>{I(e,t),p(o,[e,t])}))}),Q(i)),y),delRelationshipDefinition:e=>(P(t,e),g(e),y),getStore:r,getRelationshipIds:a,forEachRelationship:t=>i((s=>t(s,(t=>e.forEachRow(l(s),t))))),hasRelationship:d,getLocalTableId:l,getRemoteTableId:S,getRemoteRowId:(e,t)=>O(c(e)?.[0],t),getLocalRowIds:(e,t)=>A(O(c(e)?.[1],t)),getLinkedRowIds:(e,t)=>R(c(e))?[t]:A(v(e,t,!0)),addRemoteRowIdListener:(e,t,n)=>L(n,s,[e,t]),addLocalRowIdsListener:(e,t,s)=>L(s,n,[e,t]),addLinkedRowIdsListener:(e,t,s)=>(v(e,t),L(s,o,[e,t])),delListener:e=>(I(...w(e)),y),destroy:f,getListenerStats:()=>({})};return ee(y)})),ve=(e,t,s,n=P)=>{const o=(r=N(e),a=e=>!te(t,e),r.filter(a));var r,a;return c($(t),(n=>s(e,n,t[n]))),c(o,(t=>n(e,t))),e},Ie=e=>{const t=s(e);return b(t)||t==r&&S(e)?t:void 0},Se=(e,t)=>!(R(e)||!(e=>y(e,Z)&&e.constructor==Z)(e)||_(e))&&(ne(e,((s,n)=>{t(s,n)||se(e,n)})),!(e=>g($(e)))(e)),ye=(e,t,s)=>P(e,t,O(e,t)==-s?void 0:s),Re=()=>{let e,t=0,s=0;const n=F(),o=F(),a=F(),d=F(),l=F(),u=F(),h=F(),g=z(q),f=z(q),L=z(),v=z(),I=z(),S=z(),y=z(),[m,E,A,J]=Y((()=>Me)),G=(t,s)=>(!e||k(l,s))&&Se(t,(e=>K(s,e))),K=(e,t,s)=>Se(s?t:U(t,e),((s,n)=>T(Q(e,n,s),(e=>(t[n]=e,!0)),(()=>!1)))),Q=(t,s,n)=>e?T(O(O(l,t),s),(e=>Ie(n)!=e.type?e.default:n)):R(Ie(n))?void 0:n,U=(e,t)=>(T(O(u,t),(t=>ne(t,((t,s)=>{te(e,s)||(e[s]=t)})))),e),V=e=>ve(l,e,((e,t,s)=>{const n={};ve(B(l,t,F()),s,((e,t,s)=>{P(e,t,s),T(s.default,(e=>n[t]=e))})),P(u,t,n)}),((e,t)=>{P(l,t),P(u,t)})),X=e=>ve(h,e,((e,t,s)=>Z(t,s)),((e,t)=>de(t))),Z=(e,t)=>ve(B(h,e,F(),(()=>ue(e,1))),t,((t,s,n)=>$(e,t,s,n)),((t,s)=>le(e,t,s))),$=(e,t,s,n,o)=>ve(B(t,s,F(),(()=>he(e,s,1))),n,((t,n,o)=>_(e,s,t,n,o)),((n,r)=>ce(e,t,s,n,r,o))),_=(e,t,s,n,o)=>{k(s,n)||ge(e,t,n,1);const r=O(s,n);o!==r&&(fe(e,t,n,r),P(s,n,o))},oe=(e,t,s)=>ke((()=>$(e,ie(e),t,s))),re=(e,t,s,n,o)=>T(O(t,s),(t=>_(e,s,t,n,o)),(()=>$(e,t,s,U({[n]:o},e)))),ae=e=>{const s=""+t++;return k(e,s)?ae(e):s},ie=e=>O(h,e)??Z(e,{}),de=e=>Z(e,{}),le=(e,t,s)=>$(e,t,s,{},!0),ce=(e,t,s,n,o,r)=>{const a=O(u,e)?.[o];if(!R(a)&&!r)return _(e,s,n,o,a);const i=t=>{fe(e,s,t,O(n,t)),ge(e,s,t,-1),P(n,t)};R(a)?i(o):j(n,i),M(n)&&(he(e,s,-1),M(P(t,s))&&(ue(e,-1),P(h,e)))},ue=(e,t)=>ye(n,e,t),he=(e,t,s)=>ye(B(o,e,F()),t,s),ge=(e,t,s,n)=>ye(B(B(a,e,F()),t,F()),s,n),fe=(e,t,s,n)=>B(B(B(d,e,F()),t,F()),s,n),Le=(e,t,s)=>{const n=O(O(d,e),t),o=Ce(e,t,s);return k(n,s)?[!0,O(n,s),o]:[!1,o,o]},pe=e=>{const t=M(S[e])&&M(v[e])&&M(f[e]),s=M(y[e])&&M(I[e])&&M(L[e])&&M(g[e]);if(t&&s)return;const r=e?[W(n),W(o,W),W(a,(e=>W(e,W))),W(d,(e=>W(e,W)))]:[n,o,a,d];if(t||(D(r[2],((t,s)=>D(t,((t,n)=>{M(t)||E(S[e],[s,n])})))),D(r[1],((t,s)=>{M(t)||E(v[e],[s])})),M(r[0])||E(f[e])),!s){let t;D(r[3],((s,n)=>{let o;D(s,((s,r)=>{let a;D(s,((s,i)=>{const d=Ce(n,r,i);d!==s&&(E(y[e],[n,r,i],d,s,Le),t=o=a=1)})),a&&E(I[e],[n,r],Le)})),o&&E(L[e],[n],Le)})),t&&E(g[e],[],Le)}},we=()=>H(h,(e=>H(e,H))),Re=()=>N(h),Te=e=>N(O(h,e)),be=(e,t)=>N(O(O(h,e),t)),Ce=(e,t,s)=>O(O(O(h,e),t),s),me=e=>((e=>Se(e,G))(e)&&ke((()=>X(e))),Me),Ee=()=>(ke((()=>X({}))),Me),ke=e=>{if(-1==s)return;s++;const t=e();return s--,0==s&&(s=1,pe(1),s=-1,pe(0),s=0,c([d,n,o,a],x)),t},Me={getTables:we,getTableIds:Re,getTable:e=>H(O(h,e),H),getRowIds:Te,getRow:(e,t)=>H(O(O(h,e),t)),getCellIds:be,getCell:Ce,hasTables:()=>!M(h),hasTable:e=>k(h,e),hasRow:(e,t)=>k(O(h,e),t),hasCell:(e,t,s)=>k(O(O(h,e),t),s),getJson:()=>p(h),getSchemaJson:()=>p(l),setTables:me,setTable:(e,t)=>(G(t,e)&&ke((()=>Z(e,t))),Me),setRow:(e,t,s)=>(K(e,s)&&oe(e,t,s),Me),addRow:(e,t)=>{let s;return K(e,t)&&(s=ae(O(h,e)),oe(e,s,t)),s},setPartialRow:(e,t,s)=>(K(e,s,1)&&ke((()=>{const n=ie(e);ne(s,((s,o)=>re(e,n,t,o,s)))})),Me),setCell:(e,t,s,n)=>(T(Q(e,s,C(n)?n(Ce(e,t,s)):n),(n=>ke((()=>re(e,ie(e),t,s,n))))),Me),setJson:e=>{try{"{}"===e?Ee():me(w(e))}catch{}return Me},setSchema:t=>{if((e=(e=>Se(e,(e=>Se(e,(e=>{if(!Se(e,((e,t)=>i(["type","default"],t))))return!1;const t=e.type;return!(!b(t)&&t!=r||(Ie(e.default)!=t&&se(e,"default"),0))})))))(t))&&(V(t),!M(h))){const e=we();Ee(),me(e)}return Me},delTables:Ee,delTable:e=>(k(h,e)&&ke((()=>de(e))),Me),delRow:(e,t)=>(T(O(h,e),(s=>{k(s,t)&&ke((()=>le(e,s,t)))})),Me),delCell:(e,t,s,n)=>(T(O(h,e),(o=>T(O(o,t),(r=>{k(r,s)&&ke((()=>ce(e,o,t,r,s,n)))})))),Me),delSchema:()=>(V({}),e=!1,Me),transaction:ke,forEachTable:e=>D(h,((t,s)=>e(s,(e=>D(t,((t,s)=>e(s,(e=>j(t,e))))))))),forEachRow:(e,t)=>D(O(h,e),((e,s)=>t(s,(t=>j(e,t))))),forEachCell:(e,t,s)=>j(O(O(h,e),t),s),addTablesListener:(e,t)=>m(e,g[t?1:0]),addTableIdsListener:(e,t)=>m(e,f[t?1:0]),addTableListener:(e,t,s)=>m(t,L[s?1:0],[e]),addRowIdsListener:(e,t,s)=>m(t,v[s?1:0],[e]),addRowListener:(e,t,s,n)=>m(s,I[n?1:0],[e,t]),addCellIdsListener:(e,t,s,n)=>m(s,S[n?1:0],[e,t]),addCellListener:(e,t,s,n,o)=>m(n,y[o?1:0],[e,t,s]),callListener:e=>(J(e,[Re,Te,be],(e=>R(e[2])?[]:[,,].fill(Ce(...e)))),Me),delListener:e=>(A(e),Me),getListenerStats:()=>({})};return ee(Me)};export{oe as createCheckpoints,le as createCustomPersister,fe as createFilePersister,ae as createIndexes,he as createLocalPersister,de as createMetrics,we as createRelationships,pe as createRemotePersister,ge as createSessionPersister,Re as createStore,re as defaultSorter};
SyntaxError: Unexpected token '??='
at Loader.moduleStrategy (internal/modules/esm/translators.js:145:18)
This is what I did:
import {createStore} from 'tinybase';
const store = createStore();
store.setCell('t1', 'r1', 'c1', 'Hello World');
console.log(store.getCell('t1', 'r1', 'c1'));
Trying the same with node index.mjs leads to the same result.
No response
No response
Hey, I've been reading through all of the TinyBase docs and loving what I'm seeing. I ran into a few navigation challenges and thought I'd share them (instead of creating a speculative PR that might not match the style you're going for).
I wanted to read TinyBase's docs sequentially to best understand it. However, I ran into a few issues:
On a portrait iPad, I had more than enough room to see the sidebar -- but it had been hidden by a breakpoint. It would still be useful to show the navigation until a smaller breakpoint, even if it requires the user to scroll for full visibility of the text.
Without underlines or some other distinguishing mark, headings like "Getting Started" on this page don't look clickable. Even more challenging when sidebar is hidden, now that the headings are the only way of navigating into a section.
Some pages lack links to the next guide, like the Creating A Store page. Others make it difficult to tell which word will continue to the next page, like the Using Indexes page.
Because of how the guides are structured, I'd love a clear previous and next guide button at the bottom of the page. I love that the guides are written assuming the user is reading them sequentially, it'd be great to be able to browse sequentially as well.
Really excited to give TinyBase a try! I feel like it's solving a problem that will become more and more common as apps grow larger and more complex. Also kudos for having so many demos at different levels of complexity.
Curious if someone's looked into this. Would complement tinybase really well!
Is your feature request related to a problem? Please describe.
localStorage is limited to 5MB in size.
Describe the solution you'd like
IndexedDB is a browser API for client-side storage of significant amounts of structured data. Since it's a database, I would think it could be a nice complement to TinyBase. Ideally, every change to TinyBase would be incrementally pushed to IndexedDB (maybe even in a Web Worker to unblock the main thread).
Do you know if anyone has explored this option? Are there any reasons this might not be a good idea?
$ npm run compileDocs v14.16.0 01/16/22 7:26AM
[email protected] compileDocs
gulp compileDocs
[07:26:49] Using gulpfile
[07:26:49] Starting 'compileDocs'...
[07:26:50] 'compileDocs' errored after 1.1 s
[07:26:50] Error: ENOENT: no such file or directory, scandir 'lib/umd'
at readdirSync (fs.js:1021:3)
Describe the bug
The following paragraph uses "too" when it should use "to". See bold too:
To help with this, the Provider component lets you specify a Store that all the hooks and components will bind too automatically. Simply provide the Store in the store prop, and it will be used by default. Notice how the store variable is not referenced in the child Pane component here, for example:
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Should stay " will bind to automatically"
Screenshots
eg, in https://tinybase.org/api/store/type-aliases/callback/rowcallback/ change from
(
rowId: Id,
forEachCell: (cellCallback: CellCallback) => void,
): void
to
(
rowId: Id,
forEachCell: (cellCallback: CellCallback) => void,
row: Row, // <- add this
): void
Via @founderYonz
Metro doesn't work with the package.json exports
field, so we already use the react-native
field in TinyBase to define the entry point for React Native. This is fine until you need to import tinybase/ui-react
, at which point Metro will fail to resolve it tinybase/ui-react
but TypeScript won't complain. If you import tinybase/lib/ui-react
to appease Metro, then TypeScript can't find the types.
One possible solution here is to use the typesVersions
field in package.json to point to the types for tinybase/lib/ui-react
. This works for now, without breaking anything, but it does lead to a divergence in the API and a quirk to document for React Native users.
https://github.com/brentvatne/example-tinybase
tinybase/ui-react
instead, but then when you run the app Metro will error.As a user, I expect to be able to import tinybase/ui-react
in a React Native environment and everything will work - but Metro fails to resolve it. Short of that being possible, I expect there to be another suggested import format that will be compatible with React Native and allow me to use ui-react
with Metro and TypeScript.
No response
Issue previously described in this comment: #28 (comment)
I used tinybase getStoreApi <schemaFile> <storeName> <outputDir>
which allowed me to set & access data. However, I've been unable to use queries. I get the following error:
TypeError: s is not a function (it is undefined)
(sometimes l is not a function
)
I have tried both createQueries
and useCreateQueries
. I noticed that createQueries
complains that store is not an instance of Store
even though I used the command line above.
https://github.com/fdfontes/tinybase-rn-broken-queries-repro
tinybase getStoreApi <schemaFile> <storeName> <outputDir>
schema in this case is dbSchema.json
testStore-ui-react.d.ts
, testStore-ui-react.tsx
, testStore.d.ts
, testStore.ts
createQueries
in App.js
I expected to be able to set up queries.
I tried to include createQueries
and useCreateQueries
in my UI files. The documentation isn't super clear on how queries work when using the auto generated UI. Perhaps I'm not understanding some element of the implementation.
Is your feature request related to a problem? Please describe.
The .setCell()
should update an existing row if it already exist and not replace it. Or, maybe a .appendRow()
method?
If i've defined a table like below for example:
import { createStore } from "tinybase";
const store = createStore();
store.setTable("books", {
1: {
title: "Book Title",
author: "John Doe",
},
});
And, i do:
store.setCell("books", 1, "price", "$15");
This should replace the entire row with the new cell, rather than update it.
Describe the solution you'd like
If i use the .setCell()
function for an existing row, it should update the row.
Describe alternatives you've considered
Or maybe a .appendRow()
function to append new cells to a row
As described
Via @founderYonz
Hello, thank you for your work, this is a really nice project and I am using it.
I want to report what seems to me as a bug. The getter methods return empty objects on first render instead of the expected behavior which is to return the storage content.
I am persisting to local storage and on the first render I would expect to get the saved data but I get an empty object.
This only happens with the store methods, because when I manually check the local storage I can access the data.
To visualize this, here is some simple code which could serve for reproducing the bug as well.
useEffect(() => {
const table = store.getTable('products');
const table2 = localStorage.getItem('products/store');
console.log(table); // returns empty { }
console.log(table2); // returns data
}, [])
It works just fine on subsequent renders though and table returns the data normally then.
Thank you.
I have a React Native app and was following Typing The ui-react Module section. When running the app i'm getting the following error:
Error: Unable to resolve module tinybase/ui-react/with-schemas
I tried importing from tinybase/lib/ui-react/with-schemas
instead but it didn't help.
Package versions:
"tinybase": "^4.0.1"
"react-native": "0.72.3"
When creating a new row it is impossible to store null
values in a cell. It will be just dropped.
In my app, I need to check specifically for nulls
store.setRow('files', 'test_id', {
name: 'Test file',
folder_id: null,
})
console.log(store.getRow('files', 'test_id')) // {name: 'Test file'}
With a schema it should be possible to generate domain-specific providers, hooks, and components, just like we do for store methods.
Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
Assume you set a table like this:
store.setTable('tableName', { parseInt('123'): { 'attr': 1, ...otherAttrs }})
This is wrong because rowId should not be a number but stick with me.
If later you set a cell in this table and rowId like.
store.setCell('tableName', parseInt('123'), 'attr', 2);
This will override the localStorage instance of this rowId
to only { 'attr': 2 }
. There's no other attributes that we initially set.
I'm assuming this happens because when we set the table with a rowId
of type number, internally this number is casted into a string.
When on setCell
we offer the same rowId
of type number with which we set the table, the rowId
is not a string anymore, thus the instance cannot be found and an empty object is returned, which we then populate with the value of setCell
. And ultimately, when the save operations happens, the rowId
of type number is again casted as string which then overrides the localStorage instance
Describe the solution you'd like
A clear and concise description of what you want to happen.
Just a warning would be nice whenever we're setting rowIds as number, or in a more consistent fashion with the error handling of tinybase until now, we could have it just silently fail the operation of setting a cell if the rowId provided is a number.
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context or screenshots about the feature request here.
As described
Via @founderYonz
Hello, so I'm configuring eas update in one of my apps in react-native where I'm encountering a problem.
[expo-cli] SyntaxError: node_modules\tinybase\lib\indexes.js: Unexpected token: operator (?) in file node_modules\tinybase\lib\indexes.js at 224:11
[expo-cli] Error: Unexpected token: operator (?) in file node_modules\tinybase\lib\indexes.js at 224:11
[expo-cli] at minifyCode (--path--\node_modules\metro-transform-worker\src\index.js:101:13)
[expo-cli] at transformJS (--path--\node_modules\metro-transform-worker\src\index.js:319:28)
[expo-cli] at transformJSWithBabel (--path--\node_modules\metro-transform-worker\src\index.js:410:16)
[expo-cli] at processTicksAndRejections (node:internal/process/task_queues:96:5)
[expo-cli] at async Object.transform (--path--\node_modules\metro-transform-worker\src\index.js:571:12)
It seems that the transpiling target for tinybase is too high for metro which makes metro fail when it encounters new syntax like optional chaining etc.
It would be nice if the target was set to something that includes these features.
I have been debugging the problem for a while and it appears to me that adding this would help:
https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining.
I patched tinybase from node_modules with yarn by importing things from "lib/debug" and literally replacing lib/debug/*.js with the babel ie9 transpiled versions of those files and it all works, but it would be nice to have this from the package as others will probably encounter this problem in the future too.
Thank you
No stackblitz for react-native
.
.
No response
No response
I'd like to have a single prisma schema that can be used with the inside setTablesSchema()
.
An example schema.primsa
file:
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgres"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
// --------------------------------------
model User {
id Int @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique
hashedPassword String?
role String @default("USER")
avatar Json?
tokens Token[]
sessions Session[]
todos Todo[]
}
model Session {
id Int @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
expiresAt DateTime?
handle String @unique
hashedSessionToken String?
antiCSRFToken String?
publicData String?
privateData String?
user User? @relation(fields: [userId], references: [id])
userId Int?
}
model Token {
id Int @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
hashedToken String
lastFour String?
type TokenType
expiresAt DateTime
sentTo String?
user User? @relation(fields: [userId], references: [id])
userId Int?
@@unique([hashedToken, type])
}
enum TokenType {
RESET_PASSWORD
INVITE_TOKEN
PUBLIC_KEY
SECRET_KEY
}
model Todo {
id String @id @default(cuid())
createdAt DateTime @default(now())
modifiedAt DateTime @default(now())
name String
slug String @unique
user User @relation(fields: [userId], references: [id])
userId Int
}
Notice the @unqiue
& @relation
helpers
First off, thank you for a great library. Using a client-side relational database is, in my opinion, the clear future for large frontend applications. However, after trying Tinybase out there is a key feature that I find myself really missing: errors.
Tinybase has cell types and default values. That is, I can enforce my data never has the wrong type, and I can substitute a default value when applicable, but I can't throw an error when my app tries to add invalid data to my store. Everything fails silently. This is fine in some circumstances, but is very much not in others.
I'd like a way to say, this cell is required. This should throw something akin to a Postgres NotNullConstraint error. This also goes for foreign key constraints, check constraints, etc.
In my opinion there should be no requirement to use these constraints nor even a schema, but they should be available.
For context, my current work around is to set a unique default value for fields which I require which is used no-where else in my application. I then use listeners to watch for this value and, if they find it throw an error. This is very brittle. I'd much prefer a built-in solution.
Is your feature request related to a problem? Please describe.
When adding rows with store.addRow()
or useRowCallback()
after removing rows in reverse order (based on sorted row IDs), the row IDs do not increment continuously. Instead, the first added row ID is one greater than the previous greatest ID, and subsequent row IDs decrease until they form a continuous sequence from 0 to the first incremented ID after row removals.
Describe the solution you'd like
Ensure that the row IDs generated by store.addRow()
and useRowCallback()
always increment based on the current greatest ID value.
Describe alternatives you've considered
As changing the behavior of store.addRow()
and useRowCallback()
could affect existing implementations, consider introducing a new method, store.appendRow()
, that generates auto-incrementing row IDs based on the current greatest ID value. This new method would provide a more intuitive and expected behavior without disrupting existing code that relies on the current behavior.
Hello, I'm trying to use tinybase for a project with react-native and I keep getting this error:
While trying to resolve module `tinybase` from file `---path---\App.tsx`, the package `--path--\node_modules\tinybase\package.json` was successfully found. However, this package itself specifies a `main` module field that could not be resolved (`--path---\node_modules\tinybase\index`. Indeed, none of these files exist.:.....
In my opinion, looks like metro has issues with the way the package is exported in package.json and cannot resolve the correct path.
React native is not an option in stackblitz
I would expect tinybase to work similar to how it does in a react or next app, with webpack.
No response
No response
Is your feature request related to a problem? Please describe.
We were trying to create indexes for events object by date. So that if we look for a specific date then it will return the collection of events that cover that given date.
Event { start: Date, end: Date, ...}
indexes.setIndexDefinition(
'eventsByDate',
'events',
(getCell) => ['2022-09-19', '2022-09-20'] // eg. return array of ISO date from this code: eachDayOfInterval(getCell('from'), getCell('to')).map((date) => formatISO(date))
);
indexes.getSliceRowIds('eventsByDate', '2022-09-19') // return events that cover this date
indexes.getSliceRowIds('eventsByDate', '2022-09-20') // return events that cover this date
Describe the solution you'd like
I think if the setIndexDefinition could support having sliceId array to index rows then it's more flexible.
Describe alternatives you've considered
We are not sure of the best way to solve this with Tinybase without manually indexing rows by listening to the row-adding event.
Additional context
N/A
I'm currently trying to use tinybase in an electon.js personal project but keep on getting an import error saying
Error [ERR_REQUIRE_ESM]: require() of ES Module ...\tinybase.js from .....\dataBase.js not supported. Instead change the require of tinybase.js in ...\dataBase.js to a dynamic import() which is available in all CommonJS modules.
dataBase.js is the file where I'm trying to use tinybase
https://github.com/uwemneku/electron-quick-start/blob/master/main.js
npm start
As a user, I expect the app to start without any issues
No response
Is your feature request related to a problem? Please describe.
When experimenting/debugging, I like having some sort of UI for my databases, such as https://github.com/sqlitebrowser/sqlitebrowser. For TinyBase, I think it'd make sense to have a simple JS web UI that users can interact with their data from.
Describe the solution you'd like
Something similar to deployd
's web dashboard, which can be used for basic CRUD operations.
Describe alternatives you've considered
N/A
Additional context
deployd
was a cool API builder that is no longer actively maintained. https://github.com/deployd/deployd
Currently, setValues
& setValue
will ignore updating the store if a property doesn't match the set schema. This is great.
But would be very helpful to also have some info on the type side when setting the values (or row/cell). Would also be nice to get the schema types from useStore
.
Appreciate your work on this. Ran into a weird issue, tho: The framework I'm using requires Prettier 3 for its internal eslint formatter but for some reason your package requires Prettier 2 as a peer dependency and therefore requires me to install packages with --legacy-peer-deps
enabled. If you're not providing a language server to provide live code formatting in this package, could you not require that as a peer dep?
Thanks
❯ npm i
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/prettier
npm ERR! dev prettier@"^3.0.3" from the root project
npm ERR! peer prettier@">= 3.0.0" from @electron-toolkit/[email protected]
npm ERR! node_modules/@electron-toolkit/eslint-config-prettier
npm ERR! dev @electron-toolkit/eslint-config-prettier@"^1.0.1" from the root project
npm ERR! 3 more (eslint-plugin-prettier, prettier-plugin-svelte, prettier-plugin-tailwindcss)
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peerOptional prettier@"^2.8.8" from [email protected]
npm ERR! node_modules/tinybase
npm ERR! dev tinybase@"^4.1.3" from the root project
npm ERR!
npm ERR! Conflicting peer dependency: [email protected]
npm ERR! node_modules/prettier
npm ERR! peerOptional prettier@"^2.8.8" from [email protected]
npm ERR! node_modules/tinybase
npm ERR! dev tinybase@"^4.1.3" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR!
npm ERR! For a full report see:
npm ERR! /Users/daniel/.npm/_logs/2023-08-31T21_11_14_966Z-eresolve-report.txt
No response
Create an Electron project using Svelte as your UI framework and add Tinybase.
To not be forced to choose a linter version by my reactive store?
No response
No response
Distant wishlist, but how could a cell point to multiple other cells?
Via @founderYonz
Forgive me if this is covered somewhere, but looking through the docs and playing around with examples using store.addRow()
or useRowCallback()
it doesn't seem like there is a way to provide our own ID and we must rely on receiving one that was assigned automatically by tinybase
Describe the solution you'd like
It would be great if there was an optional way to add your own ID for a row.
I want to create a query over multiple tables, let's say orders
, customers
, and countries
, to retrieve a list of orders along with customer information and the country information of each customer.
The orders
table has a foreign key customer_id
referencing the customers
table, and the customers
table has a foreign key country_id
referencing the countries
table.
In SQL, you can achieve this using nested LEFT JOINs like this:
SELECT
o.order_id,
c.customer_name,
co.country_name
FROM
orders o
LEFT JOIN
customers c ON o.customer_id = c.customer_id
LEFT JOIN
countries co ON c.country_id = co.country_id;
In Tinybase, it seems like I cannot replicate the second join condition.
If I do join("countries", "country_id")
, I don't get any results.
Perhaps the join condition could allow for something like join("countries", "customers.country_id")
?
Any thoughts?
Sorry to open this as an issue but I noticed you've previously said you don't get notifications for Discussions.
Has there been any further movement on supporting one->many queries? Or a pattern I should be following?
_Originally posted in #18
Via @founderYonz
Fresh install with NPM and React 18.1 fails because npm doesn't respect the package.json "optional: true" for peer Dependency.
tinyBaseExpoNB $ npm i tinybase
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
Sorry for the deluge of issues! We've been prototyping with TinyBase over the last couple weeks and have been blown away by how much you've achieved so rapidly.
I'm not sure if financial gain is helpful or motivating for you but I'd love to be able to make a monthly contribution to the continued development of this incredible project. I could only offer a smaller personal donation to start but if we end up moving forward with TinyBase at my $DAY_JOB I'd definitely push for some kind of larger sponsorship as well
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.