A tiny, reactive JavaScript library for structured state and tabular data.

Overview

A JavaScript library for structured state.

Using plain old JavaScript objects to manage data gets old very quickly. It's error-prone, tricky to track changes efficiently, and easy to mistakenly incur performance costs.

TinyBase is a smarter way to structure your application state:

Tiny by name, tiny by nature, TinyBase only costs 2.6kB - 5.7kB when compressed, and has zero dependencies. And of course it's well tested, fully documented, and open source. Other FAQs?


Get started

Try the demos

Read the docs


Set and get tables, rows, and cells.

Creating a Store requires just a simple call to the createStore function. Once you have one, you can easily set Table, Row, or Cell values by their Id. And of course you can easily get the values back out again.

Read more about setting and changing data in The Basics guide.

const store = createStore()
  .setTable('pets', {fido: {species: 'dog'}})
  .setCell('pets', 'fido', 'color', 'brown');

console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'brown'}

Register listeners at any granularity.

The magic starts to happen when you register listeners on a Store, Table, Row, or Cell. They get called when any part of that object changes. You can also use wildcards - useful when you don't know the Id of the objects that might change.

Read more about listeners in the Listening To Stores guide.

const listenerId = store.addTableListener('pets', () =>
  console.log('changed'),
);

store.setCell('pets', 'fido', 'sold', false);
// -> 'changed'

store.delListener(listenerId);

Call React hooks to bind to data.

If you're using React in your application, the optional ui-react module provides hooks to bind to the data in a Store.

More magic! The useCell hook in this example fetches the dog's color. But it also registers a listener on that cell that will fire and re-render the component whenever the value changes.

Basically you simply describe what data you want in your user interface and TinyBase will take care of the whole lifecycle of updating it for you.

Read more about the using hooks in the Using React Hooks guide.

const App1 = () => {
  const color = useCell('pets', 'fido', 'color', store);
  return <>Color: {color}</>;
};

const app = document.createElement('div');
ReactDOM.render(<App1 />, app);
console.log(app.innerHTML);
// -> 'Color: brown'

store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> 'Color: walnut'

Use components to make reactive apps.

The react module provides simple React components with bindings that make it easy to create a fully reactive user interface based on a Store.

In this example, the library's RowView component just needs a reference to the Store, the tableId, and the rowId in order to render the contents of that row. An optional cellComponent prop lets you override how you want each Cell rendered. Again, all the listeners and updates are taken care of for you.

The module also includes a context Provider that sets up default for an entire app to use, reducing the need to drill all your props down into your app's hierarchy.

Most of the demos showcase the use of these React hooks and components. Take a look at Todo App v1 (the basics) to see these user interface binding patterns in action.

Read more about the ui-react module in the Building UIs guides.

const MyCellView = (props) => (
  <>
    {props.cellId}: <CellView {...props} />
    <hr />
  </>
);

const App2 = () => (
  <RowView
    store={store}
    tableId="pets"
    rowId="fido"
    cellComponent={MyCellView}
  />
);

ReactDOM.render(<App2 />, app);
console.log(app.innerHTML);
// -> 'species: dog<hr>color: walnut<hr>sold: false<hr>'

store.setCell('pets', 'fido', 'sold', true);
console.log(app.innerHTML);
// -> 'species: dog<hr>color: walnut<hr>sold: true<hr>'

ReactDOM.unmountComponentAtNode(app);

Apply schemas to tables.

By default, a Row can contain any arbitrary Cell. But you can add a schema to a Store to ensure that the values are always what you expect. For example, you can limit their types, and provide defaults. You can also create mutating listeners that can programmatically enforce a schema.

In this example, we set a second Row without the sold Cell in it. The schema ensures it's present with default of false.

Read more about schemas in the Using Schemas guide.

store.setSchema({
  pets: {
    species: {type: 'string'},
    color: {type: 'string'},
    sold: {type: 'boolean', default: false},
  },
});

store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getRow('pets', 'felix'));
// -> {species: 'cat', sold: false}

store.delSchema();

Persist data to browser, file, or server.

You can easily persist a Store between browser page reloads or sessions. You can also synchronize it with a web endpoint, or (if you're using TinyBase in an appropriate environment), load and save it to a file.

Read more about persisters in the Persisting Data guide.

const persister = createSessionPersister(store, 'demo');
await persister.save();

console.log(sessionStorage.getItem('demo'));
// -> '{"pets":{"fido":{"species":"dog","color":"walnut","sold":true},"felix":{"species":"cat","sold":false}}}'

persister.destroy();
sessionStorage.clear();

Define metrics and aggregations.

A Metrics object makes it easy to keep a running aggregation of Cell values in each Row of a Table. This is useful for counting rows, but also supports averages, ranges of values, or arbitrary aggregations.

In this example, we create a new table of the pet species, and keep a track of which is most expensive. When we add horses to our pet store, the listener detects that the highest price has changed.

Read more about Metrics in the Using Metrics guide.

store.setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition(
  'highestPrice', // metricId
  'species', //      tableId to aggregate
  'max', //          aggregation
  'price', //        cellId to aggregate
);

console.log(metrics.getMetric('highestPrice'));
// -> 5

metrics.addMetricListener('highestPrice', () =>
  console.log(metrics.getMetric('highestPrice')),
);
store.setCell('species', 'horse', 'price', 20);
// -> 20

metrics.destroy();

Create indexes for fast lookups.

An Indexes object makes it easy to look up all the Row objects that have a certain value in a Cell.

In this example, we create an index on the species Cell values. We can then get the the list of distinct Cell value present for that index (known as 'slices'), and the set of Row objects that match each value.

Indexes objects are reactive too. So you can set listeners on them just as you do for the data in the underlying Store.

Read more about Indexes in the Using Indexes guide.

const indexes = createIndexes(store);
indexes.setIndexDefinition(
  'bySpecies', // indexId
  'pets', //      tableId to index
  'species', //   cellId to index
);

console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido']

indexes.addSliceIdsListener('bySpecies', () =>
  console.log(indexes.getSliceIds('bySpecies')),
);
store.setRow('pets', 'lowly', {species: 'worm'});
// -> ['dog', 'cat', 'worm']

indexes.destroy();

Model relationships between tables.

A Relationships object lets you associate a Row in a local Table with the Id of a Row in a remote Table. You can also reference a table to itself to create linked lists.

In this example, the species Cell of the pets Table is used to create a relationship to the species Table, so that we can access the price of a given pet.

Like everything else, you can set listeners on Relationships too.

Read more about Relationships in the Using Relationships guide.

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies', // relationshipId
  'pets', //       local tableId to link from
  'species', //    remote tableId to link to
  'species', //    cellId containing remote key
);

console.log(
  store.getCell(
    relationships.getRemoteTableId('petSpecies'),
    relationships.getRemoteRowId('petSpecies', 'fido'),
    'price',
  ),
);
// -> 5

relationships.destroy();

Set checkpoints for an undo stack.

A Checkpoints object lets you set checkpoints on a Store. Move forward and backward through them to create undo and redo functions.

In this example, we set a checkpoint, then sell one of the pets. Later, the pet is brought back to the shop, and we go back to that checkpoint to revert the store to its previous state.

Read more about Checkpoints in the Using Checkpoints guide.

const checkpoints = createCheckpoints(store);
checkpoints.addCheckpoint('pre-sale');

store.setCell('pets', 'felix', 'sold', true);
console.log(store.getCell('pets', 'felix', 'sold'));
// -> true

checkpoints.goBackward();
console.log(store.getCell('pets', 'felix', 'sold'));
// -> false

Did we say tiny?

If you use the basic store module alone, you'll only add a gzipped 2.6kB to your app. You can incrementally add the other modules as you need more functionality, or get it all for 5.7kB. The ui-react adaptor is just another 2.6kB, and everything is fast.

Life's easy when you have zero dependencies.

Read more about how TinyBase is structured in the Architecture guide.

  .js.gz .js debug.js .d.ts
store 2.6kB 5.9kB 23.9kB 87.5kB
indexes 1.6kB 3.2kB 14.0kB 32.9kB
metrics 1.5kB 3.1kB 12.5kB 29.1kB
relationships 1.6kB 3.2kB 14.7kB 42.1kB
checkpoints 1.3kB 2.5kB 10.3kB 33.0kB
persisters 0.8kB 1.6kB 4.9kB 26.7kB
common 0.1kB 0.1kB 0.1kB 3.5kB
tinybase 5.7kB 13.4kB 55.6kB 0.3kB

Well tested and documented.

TinyBase has 100.0% test coverage, including the code throughout the documentation - even on this page! The guides, demos, and API examples are designed to make it as easy as possible to get up and running.

Read more about how TinyBase is tested in the Unit Testing guide.

  Total Tested Coverage
Lines 957 957 100.0%
Statements 1,047 1,047 100.0%
Functions 384 384 100.0%
Branches 341 341 100.0%
Tests 1,731
Assertions 8,036

Get started

Try the demos

Read the docs


Follow

About

Building TinyBase was an interesting exercise in API design, minification, and documentation. It's not my day job, but I do intend to maintain it, so please provide feedback. I could not have done this without these great projects and friends, and I hope you enjoy using it!

Comments
  • Add errors

    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.

    enhancement discussion 
    opened by AHBruns 33
  • React native metro code target

    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

    enhancement please verify 
    opened by kastriotkastrati 11
  • Support an array of sliceId for setIndexDefinition?

    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

    enhancement please verify 
    opened by tienle 9
  • Tinybase not compatible with metro in react-native

    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

    bug 
    opened by kastriotkastrati 7
  • node.js getting started example does not work

    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 
    opened by d-bardiau 7
  • Unexpected behavior with ids parsed on local storage.

    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.

    opened by kastriotkastrati 4
  • Documentation Navigation Challenges

    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.

    site 
    opened by mutewinter 4
  • Tinybase fails to build with Browserify

    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

    opened by shaneosullivan 3
  • ​Fresh install with NPM and React 18.1 fails

    ​Fresh install with NPM and React 18.1 fails

    Describe the bug

    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]
    
    bug please verify 
    opened by jamesgpearce 2
  • Getters return empty on first render

    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.

    question 
    opened by kastriotkastrati 2
  • Consider providing value of entity in iterators

    Consider providing value of entity in iterators

    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

    enhancement 
    opened by jamesgpearce 1
  • Consider a select('*') or equivalent

    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

    enhancement 
    opened by jamesgpearce 0
  • tinybase.org logo does not render in Microsoft Edge

    tinybase.org logo does not render in Microsoft Edge

    Describe the bug

    Your Example Website or App

    Steps to Reproduce the Bug or Issue

    Expected behavior

    Screenshots or Videos

    No response

    Platform

    Additional context

    No response

    opened by jamesgpearce 0
Releases(v3.0.0-beta.0)
  • v3.0.0-beta.0(Dec 27, 2022)

  • v2.2.6(Dec 14, 2022)

  • v2.2.5(Dec 13, 2022)

  • v2.2.4(Dec 13, 2022)

  • v2.2.3(Nov 26, 2022)

    https://tinybase.org/guides/releases/#v2-2

    This release includes a new tools module. These tools are not intended for production use, but are instead to be used as part of your engineering workflow to perform tasks like generating APIs from schemas, or schemas from data. For example:

    const store = createStore().setTable('pets', {
      fido: {species: 'dog'},
      felix: {species: 'cat'},
      cujo: {species: 'dog'},
    });
    
    const tools = createTools(store);
    const [dTs, ts] = tools.getStoreApi('shop');
    
    // -- shop.d.ts --
    /* Represents the 'pets' Table. */
    export type PetsTable = {[rowId: Id]: PetsRow};
    /* Represents a Row when getting the content of the 'pets' Table. */
    export type PetsRow = {species: string};
    //...
    
    // -- shop.ts --
    export const createShop: typeof createShopDecl = () => {
      //...
    };
    

    This release includes a new tinybase CLI tool which allows you to generate Typescript definition and implementation files direct from a schema file:

    npx tinybase getStoreApi schema.json shop api
    
        Definition: [...]/api/shop.d.ts
    Implementation: [...]/api/shop.ts
    

    Finally, the tools module also provides ways to track the overall size and structure of a Store for use while debugging.

    Source code(tar.gz)
    Source code(zip)
  • v2.1.1(Nov 7, 2022)

  • v2.1.0(Sep 24, 2022)

    This release allows you to create indexes where a single Row Id can exist in multiple slices. You can utilize this to build simple keyword searches, for example.

    Simply provide a custom getSliceIdOrIds function in the setIndexDefinition method that returns an array of Slice Ids, rather than a single Id:

    const store = createStore().setTable('pets', {
      fido: {species: 'dog'},
      felix: {species: 'cat'},
      rex: {species: 'dog'},
    });
    
    const indexes = createIndexes(store);
    indexes.setIndexDefinition('containsLetter', 'pets', (_, rowId) =>
      rowId.split(''),
    );
    
    console.log(indexes.getSliceIds('containsLetter'));
    // -> ['f', 'i', 'd', 'o', 'e', 'l', 'x', 'r']
    console.log(indexes.getSliceRowIds('containsLetter', 'i'));
    // -> ['fido', 'felix']
    console.log(indexes.getSliceRowIds('containsLetter', 'x'));
    // -> ['felix', 'rex']
    

    This functionality is showcased in the Word Frequencies demo if you would like to see it in action.

    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Sep 16, 2022)

    Announcing the next major version of TinyBase 2.0! This is an exciting release that evolves TinyBase towards becoming a reactive, relational data store, complete with querying, sorting, and pagination. Here are a few of the highlights...

    Query Engine

    The flagship feature of this release is the new queries module. This allows you to build expressive queries against your data with a SQL-adjacent API that we've cheekily called TinyQL. The query engine lets you select, join, filter, group, sort and paginate data. And of course, it's all reactive!

    The best way to see the power of this new engine is with the two new demos we've included this release:

    • The Car Analysis demo showcases the analytical query capabilities of TinyBase v2.0, grouping and sorting dimensional data for lightweight analytical usage, graphing, and tabular display. Try this demo here.
    • The Movie Database demo showcases the relational query capabilities of TinyBase v2.0, joining together information about movies, directors, and actors from across multiple source tables. Try this demo here.

    Sorting and Pagination

    To complement the query engine, you can now sort and paginate Row Ids. This makes it very easy to build grid-like user interfaces (also shown in the demos above). To achieve this, the Store now includes the getSortedRowIds method (and the addSortedRowIdsListener method for reactivity), and the Queries object includes the equivalent getResultSortedRowIds method and addResultSortedRowIdsListener method.

    These are also exposed in the optional ui-react module via the useSortedRowIds hook, the useResultSortedRowIds hook, the SortedTableView component and the ResultSortedTableView component, and so on.

    Queries in the ui-react module

    The v2.0 query functionality is fully supported by the ui-react module (to match support for Store, Metrics, Indexes, and Relationship objects). The useCreateQueries hook memoizes the creation of app- or component-wide Query objects; and the useResultTable hook, useResultRow hook, useResultCell hook (and so on) let you bind you component to the results of a query.

    This is, of course, supplemented with higher-level components: the ResultTableView component, the ResultRowView component, the ResultCellView component, and so on. See the Building A UI With Queries guide for more details.

    It's a big release!

    Thank you for all your support as we brought this important new release to life, and we hope you enjoy using it as much as we did building it. Please provide feedback via Github and Twitter!

    Source code(tar.gz)
    Source code(zip)
  • v1.3.6(Aug 9, 2022)

  • v1.3.5(Jun 12, 2022)

  • v1.3.4(May 16, 2022)

  • v1.3.3(May 7, 2022)

  • v1.3.2(Apr 15, 2022)

    Works around https://github.com/facebook/metro/issues/670 so that TinyBase can be used in React Native. See https://github.com/tinyplex/tinybase/issues/17#issuecomment-1099910224 for details.

    Source code(tar.gz)
    Source code(zip)
  • v1.3.1(Apr 14, 2022)

  • v1.3.0(Mar 19, 2022)

    Adds support for explicit transaction start and finish methods, as well as listeners for transactions finishing. This allows stores to couple their transactions together, which we need for the query engine.

    Source code(tar.gz)
    Source code(zip)
  • v1.2.3(Mar 19, 2022)

  • v1.2.2(Mar 8, 2022)

  • v1.2.1(Mar 8, 2022)

  • v1.2.0(Feb 15, 2022)

    v1.2.0 adds a way to revert transactions if they have not met certain conditions.

    When using the transaction method, you can provide an optional doRollback callback which should return true if you want to revert the whole transaction at its conclusion.

    The callback is provided with two objects, changedCells and invalidCells which list all the net changes and invalid attempts at changes that were made during the transaction. You will most likely use the contents of those objects to decide whether the transaction should be rolled back.

    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Feb 6, 2022)

    This release allows you to listen to invalid data being added to a Store, allowing you to gracefully handle errors, rather than them failing silently.

    There is a new listener type InvalidCellListener and a method addInvalidCellListener in the Store interface.

    These allow you to keep track of failed attempts to update the Store with invalid Cell data. These listeners can also be mutators, allowing you to address any failed writes programmatically.

    For more information, please see the addInvalidCellListener documentation. In particular, this explains how this listener behaves for a Store with a Schema.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.5(Jan 21, 2022)

    Adds iterators to all modules for consistency:

    • Metrics.forEachMetric
    • Indexes.forEachIndex
    • Indexes.forEachSlice
    • Relationships.forEachRelationship
    • Checkpoints.forEachCheckpoint
    Source code(tar.gz)
    Source code(zip)
  • v1.0.4(Jan 19, 2022)

    Gives Metrics, Indexes, Relationships, and Checkpoints the same sort of hasX methods as Stores

    Also adds hasTables method to let you check if a Store is empty.

    See fe84502708ea20209bb29a450cc69956cb8849e5

    Source code(tar.gz)
    Source code(zip)
  • v1.0.3(Jan 17, 2022)

  • v1.0.2(Jan 17, 2022)

  • v1.0.1(Jan 17, 2022)

  • v1.0.0(Jan 14, 2022)

Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Card IDE (CIDEr) - Design game cards using HTML/Handlebars, CSS, and tabular data

CIDEr Card IDE (CIDEr) - Design game cards using HTML/Handlebars, CSS, and tabular data. Website: Start using Cider About CIDEr Cider was created to f

Oatear 37 Dec 10, 2022
Successor of the flowchart-fun syntax. Store tabular data and graph information in the same document.

graph-selector-syntax A syntax for storing graphs and tabular data in plain text View Examples Installation npm install graph-selector Usage import {

Tone Row 32 Dec 15, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
Lazy minting of ERC721 NFTs using EIP712 standard for typed, structured data. ✨

ERC721 - Signature minting Lazy minting of ERC721 NFTs using EIP712 standard for typed, structured data. ✨ How it works Lazy minting or Signature mint

Sunrit Jana 21 Oct 20, 2022
Stacks Voice is a reference project that builds on the SIP018 signed structured data standard to create an accountless internet forum.

Stacks Voice Stacks Voice is a reference project that builds on the SIP018 signed structured data standard to create an accountless internet forum. Th

Clarity Innovation Lab 4 Dec 21, 2022
A tiny JavaScript library to easily toggle the state of any HTML element in any contexts, and create UI components in no time.

A tiny JavaScript library to easily toggle the state of any HTML element in any contexts, and create UI components in no time. Dropdown, navigation bu

Matthieu Bué 277 Nov 25, 2022
API dot Open Sauced is NestJS and SupaBase powered OAS3 backend designed to remove client complexity and provide a structured graph of all @open-sauced integrations

?? Open Sauced Nest Supabase API ?? The path to your next Open Source contribution ?? Prerequisites In order to run the project we need the following

TED Vortex (Teodor-Eugen Duțulescu) 13 Dec 18, 2022
A tiny foundation that providing nested state-based routing for complex web application.

StateMan stateman: A tiny foundation that provides nested state-based routing for complex web applications. stateman is highly inspired by ui-router;

ZhengHaibo 390 Dec 20, 2022
Functional reactive UI library

reflex Reflex is a functional reactive UI library that is heavily inspired by (pretty much is a port of) elm and it's amazingly simple yet powerful ar

Mozilla 364 Oct 31, 2022
Inventory App - a SPA project developed with Angular using Reactive Forms and VMware's Clarity components.

Inventory App - a SPA (Single Page Application) project developed with Angular using Reactive Forms and VMware's Clarity components.

null 11 Oct 5, 2022
History.js gracefully supports the HTML5 History/State APIs (pushState, replaceState, onPopState) in all browsers. Including continued support for data, titles, replaceState

History.js gracefully supports the HTML5 History/State APIs (pushState, replaceState, onPopState) in all browsers. Including continued support for data, titles, replaceState. Supports jQuery, MooTools and Prototype.

Browser State 10.8k Dec 26, 2022
A library for updating your immutable state in JavaScript applications.

ionic-bond A library for updating immutable states in JavaScript applications. Introduction This library is a very lightweight replacement for immer,

null 10 Nov 15, 2022
Tiny JavaScript library (1kB) by CurrencyRate.today, providing simple way and advanced number, money and currency formatting and removes all formatting/cruft and returns the raw float value.

Zero dependency tiny JavaScript library (1kB bytes) by CurrencyRate.today, providing simple way and advanced number, money and currency formatting and removes all formatting/cruft and returns the raw float value.

Yurii De 11 Nov 8, 2022
Solid.js library adding a services layer for global shared state.

Solid Services Services are "global" objects useful for features that require shared state or persistent connections. Example uses of services might i

Maciej Kwaśniak 55 Dec 30, 2022
State based input library for the browser

Inp State based input library for the browser

null 2 Mar 23, 2022
A super tiny Javascript library to make DOM elements draggable and movable. ~500 bytes and no dependencies.

dragmove.js A super tiny Javascript library to make DOM elements draggable and movable. Has touch screen support. Zero dependencies and 500 bytes Gzip

Kailash Nadh 814 Dec 29, 2022
A tiny, plugin extendable JavaScript utility library with a JQuery-like syntax.

Tiny Friggin' Utility Zapper What is it? A tiny ~7kb extendable JavaScript utility library with a JQuery like syntax for getting work done fast! If yo

Bret 4 Jun 25, 2022