Git Product home page Git Product logo

tinybase's People

Contributors

alanjhughes avatar alitnk avatar alokmenghrajani avatar aretrace avatar arnaudhillen avatar bigab avatar bndkt avatar devjume avatar doubleinc avatar humphd avatar jamesgpearce avatar mutewinter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tinybase's Issues

tinybase.org crashes Brave on iOS

Describe the bug

As described

Your Example Website or App

No response

Steps to Reproduce the Bug or Issue

No response

Expected behavior

No response

Screenshots or Videos

No response

Platform

  • OS: [e.g. macOS, Windows, Linux]
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Version: [e.g. 91.1]

Additional context

No response

Ability to create Metrics on top of Indices

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.

A way to handle authentication & user context

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.

Consider a select('*') or equivalent

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

Tinybase fails to build with Browserify

Describe the bug

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.

Your Example Website or App

https://github.com/shaneosullivan/tinybaseRepro

Steps to Reproduce the Bug or Issue

1: Go to https://github.com/shaneosullivan/tinybaseRepro and check out the project
2: Run npm install
3: Run npm run repro

Expected behavior

It should successfully build the file output.js without error

Screenshots or Videos

No response

Platform

  • OS: macOS
  • Browser: Terminal
  • Version: 1.3.3

Additional context

No response

node.js getting started example does not work

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:

  1. npm install tinybase
  2. add following line to package.json: "type": "module"
  3. create index.js with following script:
import {createStore} from 'tinybase';
const store = createStore();
store.setCell('t1', 'r1', 'c1', 'Hello World');
console.log(store.getCell('t1', 'r1', 'c1'));
  1. node index.js

Trying the same with node index.mjs leads to the same result.

Documentation Navigation Challenges

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:

Sidebar is Hidden at Above Mobile Sizes

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.

Headings in Top Level Guide Pages Don't Look Clickable

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.

Finding the Next Guide

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.

IndexedDB persister

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?

Missing folders on first builds

$ 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)

[doc] [typo] Using Context doc page guides/building-uis/using-context/ uses too when it should use to

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:

  1. Go to 'guides/building-uis/using-context/
  2. Click on 'guides/building-uis/using-context/
  3. Scroll down to "Using Context", 3pm rd paragraph
  4. See typo

Expected behavior
Should stay " will bind to automatically"

Screenshots

Screenshot 2022-01-15 at 12 11 25

Importing tinybase/ui-react with React Native / Metro

Describe the bug

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.

Your Example Website or App

https://github.com/brentvatne/example-tinybase

Steps to Reproduce the Bug or Issue

  1. Clone the repository (it's a bare-bones React Native / Expo project with TypeScript installed)
  2. Open up App.tsx, notice the type error on the import
    image
  3. See in the commented out import above that we can resolve this type error by importing from tinybase/ui-react instead, but then when you run the app Metro will error.

Expected behavior

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.

Screenshots or Videos

No response

Platform

Additional context

Issue previously described in this comment: #28 (comment)

Unable to run createQueries while using api generate getStoreApi

Describe the bug

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.

Your Example Website or App

https://github.com/fdfontes/tinybase-rn-broken-queries-repro

Steps to Reproduce the Bug or Issue

  1. Run tinybase getStoreApi <schemaFile> <storeName> <outputDir> schema in this case is dbSchema.json
  2. Generates 4 files testStore-ui-react.d.ts, testStore-ui-react.tsx, testStore.d.ts, testStore.ts
  3. Use createQueries in App.js

Expected behavior

I expected to be able to set up queries.

Screenshots or Videos

Simulator Screen Shot - iPhone 14 Plus - 2023-08-01 at 09 10 36

Platform

  • OS: macOS
  • Using Expo + React Native

Additional context

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.

Append new cell to an existing row

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

TinyBase does not work in Microsoft Edge

Describe the bug

image

Your Example Website or App

Steps to Reproduce the Bug or Issue

Expected behavior

Screenshots or Videos

No response

Platform

Additional context

No response

Getters return empty on first render

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.

Allow null values in cells

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'}

Unexpected behavior with ids parsed on local storage.

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.

React native metro code target

Describe the bug

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

Your Example Website or App

No stackblitz for react-native

Steps to Reproduce the Bug or Issue

.

Expected behavior

.

Screenshots or Videos

No response

Platform

  • OS: [Windows]
  • Tinybase Version: [1.3.2]

Additional context

No response

Use a prisma schema with TinyBase so you can sync with a hosted postgres db

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

Add errors

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.

Ensure that automatically added row IDs always increment

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.

Tinybase not compatible with metro in react-native

Describe the bug

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.

Your Example Website or App

React native is not an option in stackblitz

Steps to Reproduce the Bug or Issue

  1. Initialize a react native app
  2. Install tinybase
  3. Import and interact with tinybase

Expected behavior

I would expect tinybase to work similar to how it does in a react or next app, with webpack.

Screenshots or Videos

No response

Platform

  • OS: [macOS, Windows, Linux]
  • Browser: [Chrome, Safari, Firefox]
  • Version: [e.g. 91.1]

Additional context

No response

Support an array of sliceId for setIndexDefinition?

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

Error using tinybase in electron.js app

Describe the bug

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

Your Example Website or App

https://github.com/uwemneku/electron-quick-start/blob/master/main.js

Steps to Reproduce the Bug or Issue

  1. Clone this repo
  2. Start the app npm start

Expected behavior

As a user, I expect the app to start without any issues

Screenshots or Videos

image
image

Platform

  • OS: [Windows]
  • Browser: [Chronium]

Additional context

No response

Web Dashboard / Admin UI?

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

Better types for `setValues` & `useStore`

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.

Prettier peer dependency causes install errors

Describe the bug

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

Your Example Website or App

No response

Steps to Reproduce the Bug or Issue

Create an Electron project using Svelte as your UI framework and add Tinybase.

Expected behavior

To not be forced to choose a linter version by my reactive store?

Screenshots or Videos

No response

Platform

  • OS: MacOS 13.5.1
  • Browser: n/a
  • Version: 4.1.1

Additional context

No response

Many-to-many support

Distant wishlist, but how could a cell point to multiple other cells?

Via @founderYonz

Ability to assign rowId when adding a new row

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.

  • Explicitly using the methods above
  • Maybe as part of the table schema definition we could add a factory function (eg generating a uuid) that could be used for ID generation?

Chained/nested joins

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?

Nested and related data?

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

Enable Github Sponsors for TinyBase

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.