API
store
The store
module is the core of the TinyBase project and contains the types, interfaces, and functions to work with Store
objects.
The main entry point to this module is the createStore
function, which returns a new Store
. From there, you can set and get data, register listeners, and use other modules to build an entire app around the state and tabular data within.
Since
v1.0.0
Interfaces
There is one interface, Store
, within the store
module.
Store
A Store
is the main location for keeping both tabular data and keyed values.
Create a Store
easily with the createStore
function. From there, you can set and get data, add listeners for when the data changes, set schemas, and so on.
A Store
has two facets. It can contain keyed Values
, and independently, it can contain tabular Tables
data. These two facets have similar APIs but can be used entirely independently: you can use only tables, only keyed Values
, or both tables and keyed Values
- all in a single Store
.
Keyed values
The keyed value support is best thought of as a flat JavaScript object. The Store
contains a number of Value
objects, each with a unique ID, and which is a string, boolean, or number.
{ // Store
"value1": "one", // Value (string)
"value2": true, // Value (boolean)
"value3": 3, // Value (number)
...
}
In its default form, a Store
has no sense of a structured schema for the Values
. However, you can optionally specify a ValuesSchema
for a Store
, which then usefully constrains and defaults the Values
you can use.
Tabular data
The tabular data exists in a simple hierarchical structure:
- The
Store
contains a number ofTable
objects. - Each
Table
contains a number ofRow
objects. - Each
Row
contains a number ofCell
objects.
A Cell
is a string, boolean, or number value.
The members of each level of this hierarchy are identified with a unique Id
(which is a string). In other words you can naively think of a Store
as a three-level-deep JavaScript object, keyed with strings:
{ // Store
"table1": { // Table
"row1": { // Row
"cell1": "one", // Cell (string)
"cell2": true, // Cell (boolean)
"cell3": 3, // Cell (number)
...
},
...
},
...
}
Again, by default Store
has no sense of a structured schema. As long as they are unique within their own parent, the Id
keys can each be any string you want. However, as you can optionally specify a TablesSchema
for the tabular data in a Store
, which then usefully constrains the Table
and Cell
Ids
(and Cell
values) you can use.
Setting and getting data
Every part of the Store
can be accessed with getter methods. When you retrieve data from the Store
, you are receiving a copy - rather than a reference - of it. This means that manipulating the data in the Store
must be performed with the equivalent setter and deleter methods.
To benefit from the reactive behavior of the Store
, you can also subscribe to changes on any part of it with 'listeners'. Registering a listener returns a listener Id
(that you can use later to remove it with the delListener
method), and it will then be called every time there is a change within the part of the hierarchy you're listening to.
This table shows the main ways you can set, get, and listen to, different types of data in a Store
:
There are two extra methods to manipulate Row
objects. The addRow
method is like the setRow
method but automatically assigns it a new unique Id
. And the setPartialRow
method lets you update multiple Cell
values in a Row
without affecting the others. There is a similar setPartialValues
method to do the same for the Values
in a Store
.
You can listen to attempts to write invalid data to a Value
or Cell
with the addInvalidValueListener
method or addInvalidCellListener
method.
The transaction
method is used to wrap multiple changes to the Store
so that the relevant listeners only fire once.
The setJson
method and the getJson
method allow you to work with a JSON-encoded representation of the entire Store
, which is useful for persisting it.
Finally, the callListener
method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed. This is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store
in bulk.
Read more about setting and changing data in The Basics guides, and about listeners in the Listening to Stores guide.
Creating a schema
You can set a ValuesSchema
and a TablesSchema
with the setValuesSchema
method and setTablesSchema
method respectively. A TablesSchema
constrains the Table
Ids
the Store
can have, and the types of Cell
data in each Table
. Each Cell
requires its type to be specified, and can also take a default value for when it's not specified.
You can also get a serialization of the schemas out of the Store
with the getSchemaJson
method, and remove the schemas altogether with the delValuesSchema
method and delTablesSchema
method.
Read more about schemas in the Using Schemas guide.
Convenience methods
There are a few additional helper methods to make it easier to work with a Store
. There are methods for easily checking the existence of a Table
, Row
, or Cell
, and iterators that let you act on the children of a common parent:
Checking existence | Iterator | |
---|---|---|
Value | hasValue | forEachValue |
Table | hasTable | forEachTable |
Row | hasRow | forEachRow |
Cell | hasCell | forEachCell |
Since v4.3.23, you can add listeners for the change of existence of part of a Store
. For example, the addHasValueListener
method lets you listen for a Value
being added or removed.
Finally, the getListenerStats
method describes the current state of the Store
's listeners for debugging purposes.
Example
This example shows a very simple lifecycle of a Store
: from creation, to adding and getting some data, and then registering and removing a listener.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getCell('pets', 'fido', 'color'));
// -> 'brown'
const listenerId = store.addTableListener('pets', () => {
console.log('changed');
});
store.setCell('pets', 'fido', 'sold', false);
// -> 'changed'
store.delListener(listenerId);
See also
- The Basics guides
- Using Schemas guides
- Hello World demos
- Todo App demos
Since
v1.0.0
Methods
These are the methods within the Store
interface.
Getter methods
This is the collection of getter methods within the Store
interface. There are 29 getter methods in total.
getTables
The getTables
method returns a Tables
object containing the entire tabular data of the Store
.
getTables(): Tables
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store
itself.
Examples
This example retrieves the tabular data in a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example retrieves the Tables
of an empty Store
, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTables());
// -> {}
Since
v1.0.0
getTablesJson
The getTablesJson
method returns a string serialization of all of the Tables
in the Store
.
getTablesJson(): string
Examples
This example serializes the contents of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTablesJson());
// -> '{"pets":{"fido":{"species":"dog"}}}'
This example serializes the contents of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTablesJson());
// -> '{}'
Since
v3.0.0
getTablesSchemaJson
The getTablesSchemaJson
method returns a string serialization of the TablesSchema
of the Store
.
getTablesSchemaJson(): string
returns | string | A string serialization of the |
---|
If no TablesSchema
has been set on the Store
(or if it has been removed with the delTablesSchema
method), then it will return the serialization of an empty object, {}
.
Examples
This example serializes the TablesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean'},
},
});
console.log(store.getTablesSchemaJson());
// -> '{"pets":{"species":{"type":"string"},"sold":{"type":"boolean"}}}'
This example serializes the TablesSchema
of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTablesSchemaJson());
// -> '{}'
Since
v3.0.0
hasTables
The hasTables
method returns a boolean indicating whether any Table
objects exist in the Store
.
hasTables(): boolean
returns | boolean | Whether any |
---|
Example
This example shows simple existence checks.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.hasTables());
// -> false
store.setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTables());
// -> true
Since
v1.0.0
hasTablesSchema
The hasTablesSchema
method returns a boolean indicating whether the Store
currently has a TablesSchema
applied to it.
hasTablesSchema(): boolean
returns | boolean | Whether the |
---|
Example
This example sets a TablesSchema
and checks that it is present.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
price: {type: 'number'},
},
});
console.log(store.hasTablesSchema());
// -> true
store.delTablesSchema();
console.log(store.hasTablesSchema());
// -> false
Since
v4.1.1
getTableIds
The getTableIds
method returns the Ids
of every Table
in the Store
.
getTableIds(): Ids
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Table
Ids
in a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTableIds());
// -> ['pets', 'species']
This example retrieves the Table
Ids
of an empty Store
, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTableIds());
// -> []
Since
v1.0.0
getTable
The getTable
method returns an object containing the entire data of a single Table
in the Store
.
getTable(tableId: string): Table
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store
itself.
Examples
This example retrieves the data in a single Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTable('pets'));
// -> {fido: {species: 'dog'}}
This example retrieves a Table
that does not exist, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTable('employees'));
// -> {}
Since
v1.0.0
getTableCellIds
The getTableCellIds
method returns the Ids
of every Cell
used across the whole Table
.
getTableCellIds(tableId: string): Ids
Type | Description | |
---|---|---|
tableId | string | |
returns | Ids | An array of the |
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Cell
Ids
used across a whole Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', legs: 4},
cujo: {dangerous: true},
},
});
console.log(store.getTableCellIds('pets'));
// -> ['species', 'color', 'legs', 'dangerous']
This example retrieves the Cell
Ids
used across a Table
that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTableCellIds('species'));
// -> []
Since
v3.3.0
hasTable
The hasTable
method returns a boolean indicating whether a given Table
exists in the Store
.
hasTable(tableId: string): boolean
Type | Description | |
---|---|---|
tableId | string | |
returns | boolean |
Example
This example shows two simple Table
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTable('pets'));
// -> true
console.log(store.hasTable('employees'));
// -> false
Since
v1.0.0
hasTableCell
The hasTableCell
method returns a boolean indicating whether a given Cell
exists anywhere in a Table
, not just in a specific Row
.
hasTableCell(
tableId: string,
cellId: string,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
cellId | string | |
returns | boolean |
Example
This example shows two simple Cell
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {legs: 4}},
});
console.log(store.hasTableCell('pets', 'species'));
// -> true
console.log(store.hasTableCell('pets', 'legs'));
// -> true
console.log(store.hasTableCell('pets', 'color'));
// -> false
Since
v3.3.0
getRowIds
The getRowIds
method returns the Ids
of every Row
in a given Table
.
getRowIds(tableId: string): Ids
Type | Description | |
---|---|---|
tableId | string | |
returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Row
Ids
in a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowIds('pets'));
// -> ['fido', 'felix']
This example retrieves the Row
Ids
of a Table
that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowIds('employees'));
// -> []
Since
v1.0.0
getSortedRowIds
The getSortedRowIds
method returns the Ids
of every Row
in a given Table
, sorted according to the values in a specified Cell
.
getSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
returns | Ids |
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the Table
is large. For a performant approach to tracking the sorted Row
Ids
when they change, use the addSortedRowIdsListener
method.
If the Table
does not exist, an empty array is returned.
Examples
This example retrieves sorted Row
Ids
in a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species'));
// -> ['felix', 'fido']
This example retrieves sorted Row
Ids
in a Table
in reverse order.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets', 'species', true));
// -> ['cujo', 'fido', 'felix']
This example retrieves two pages of Row
Ids
in a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
console.log(store.getSortedRowIds('pets', 'price', false, 0, 2));
// -> ['lowly', 'mickey']
console.log(store.getSortedRowIds('pets', 'price', false, 2, 2));
// -> ['carnaby', 'tom']
This example retrieves Row
Ids
sorted by their own value, since the cellId
parameter is undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets'));
// -> ['cujo', 'felix', 'fido']
This example retrieves the sorted Row
Ids
of a Table
that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getSortedRowIds('employees'));
// -> []
Since
v2.0.0
getRow
The getRow
method returns an object containing the entire data of a single Row
in a given Table
.
getRow(
tableId: string,
rowId: string,
): Row
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store
itself.
Examples
This example retrieves the data in a single Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
This example retrieves a Row
that does not exist, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'felix'));
// -> {}
Since
v1.0.0
getRowCount
The getRowCount
method returns the count of the Row
objects in a given Table
.
getRowCount(tableId: string): number
Type | Description | |
---|---|---|
tableId | string | |
returns | number |
While this provides the same result as the length of Ids
array returned from the getRowIds
method, it is somewhat faster, and useful for efficient pagination.
Examples
This example retrieves the number of Row
objects in the Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowCount('pets'));
// -> 2
This example retrieves the Row
Ids
of a Table
that does not exist, returning zero.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowCount('employees'));
// -> 0
Since
v4.1.0
hasRow
The hasRow
method returns a boolean indicating whether a given Row
exists in the Store
.
hasRow(
tableId: string,
rowId: string,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | boolean |
Example
This example shows two simple Row
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasRow('pets', 'fido'));
// -> true
console.log(store.hasRow('pets', 'felix'));
// -> false
Since
v1.0.0
getCellIds
The getCellIds
method returns the Ids
of every Cell
in a given Row
in a given Table
.
getCellIds(
tableId: string,
rowId: string,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Cell
Ids
in a Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
},
});
console.log(store.getCellIds('pets', 'fido'));
// -> ['species', 'color']
This example retrieves the Cell
Ids
of a Row
that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCellIds('pets', 'felix'));
// -> []
Since
v1.0.0
getCell
The getCell
method returns the value of a single Cell
in a given Row
, in a given Table
.
getCell(
tableId: string,
rowId: string,
cellId: string,
): CellOrUndefined
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
returns | CellOrUndefined | The value of the |
Examples
This example retrieves a single Cell
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
console.log(store.getCell('pets', 'fido', 'species'));
// -> 'dog'
This example retrieves a Cell
that does not exist, returning undefined
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCell('pets', 'fido', 'color'));
// -> undefined
Since
v1.0.0
hasCell
The hasCell
method returns a boolean indicating whether a given Cell
exists in a given Row
in a given Table
.
hasCell(
tableId: string,
rowId: string,
cellId: string,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
returns | boolean | Whether a |
Example
This example shows two simple Cell
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasCell('pets', 'fido', 'species'));
// -> true
console.log(store.hasCell('pets', 'fido', 'color'));
// -> false
Since
v1.0.0
getValues
The getValues
method returns an object containing the entire set of keyed Values
in the Store
.
getValues(): Values
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store
itself.
Examples
This example retrieves the set of keyed Values
in the Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example retrieves Values
from a Store
that has none, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValues());
// -> {}
Since
v3.0.0
getValuesJson
The getValuesJson
method returns a string serialization of all of the keyed Values
in the Store
.
getValuesJson(): string
Examples
This example serializes the keyed value contents of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
console.log(store.getValuesJson());
// -> '{"open":true}'
This example serializes the contents of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValuesJson());
// -> '{}'
Since
v3.0.0
getValuesSchemaJson
The getValuesSchemaJson
method returns a string serialization of the ValuesSchema
of the Store
.
getValuesSchemaJson(): string
returns | string | A string serialization of the |
---|
If no ValuesSchema
has been set on the Store
(or if it has been removed with the delValuesSchema
method), then it will return the serialization of an empty object, {}
.
Examples
This example serializes the ValuesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
console.log(store.getValuesSchemaJson());
// -> '{"open":{"type":"boolean","default":false}}'
This example serializes the ValuesSchema
of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
hasValues
The hasValues
method returns a boolean indicating whether any Values
exist in the Store
.
hasValues(): boolean
returns | boolean | Whether any |
---|
Example
This example shows simple existence checks.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.hasValues());
// -> false
store.setValues({open: true});
console.log(store.hasValues());
// -> true
Since
v3.0.0
hasValuesSchema
The hasValuesSchema
method returns a boolean indicating whether the Store
currently has a ValuesSchema
applied to it.
hasValuesSchema(): boolean
returns | boolean | Whether the |
---|
Example
This example sets a ValuesSchema
and checks that it is present.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({open: {type: 'boolean'}});
console.log(store.hasValuesSchema());
// -> true
store.delValuesSchema();
console.log(store.hasValuesSchema());
// -> false
Since
v4.1.1
getValue
The getValue
method returns a single keyed Value
in the Store
.
getValue(valueId: string): ValueOrUndefined
Type | Description | |
---|---|---|
valueId | string | |
returns | ValueOrUndefined | The |
Examples
This example retrieves a single Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('employees'));
// -> 3
This example retrieves a Value
that does not exist, returning undefined
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('website'));
// -> undefined
Since
v3.0.0
getValueIds
The getValueIds
method returns the Ids
of every Value
in a Store
.
getValueIds(): Ids
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Value
Ids
in a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValueIds());
// -> ['open', 'employees']
This example retrieves the Value
Ids
of a Store
that has had none set, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValueIds());
// -> []
Since
v3.0.0
hasValue
The hasValue
method returns a boolean indicating whether a given Value
exists in the Store
.
hasValue(valueId: string): boolean
Type | Description | |
---|---|---|
valueId | string | |
returns | boolean |
Example
This example shows two simple Value
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
console.log(store.hasValue('open'));
// -> true
console.log(store.hasValue('employees'));
// -> false
Since
v3.0.0
getContent
The getContent
method returns a Tables
object and a Values
object in an array, representing the entire content of the Store
.
getContent(): Content
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned objects are not made to the Store
itself.
Examples
This example retrieves the content of a Store
.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true, employees: 3});
console.log(store.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true, employees: 3}]
This example retrieves the Tables
and Values
of an empty Store
, returning empty objects.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getContent());
// -> [{}, {}]
Since
v4.0.0
getJson
The getJson
method returns a string serialization of all the Store
content: both the Tables
and the keyed Values
.
getJson(): string
From v3.0 onwards, the serialization is of an array with two entries. The first is the Tables
object, the second the Values
. In previous versions (before the existence of the Values
data structure), it was a sole object of Tables
.
Examples
This example serializes the tabular and keyed value contents of a Store
.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true});
console.log(store.getJson());
// -> '[{"pets":{"fido":{"species":"dog"}}},{"open":true}]'
This example serializes the contents of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getJson());
// -> '[{},{}]'
Since
v1.0.0
getSchemaJson
The getSchemaJson
method returns a string serialization of both the TablesSchema
and ValuesSchema
of the Store
.
getSchemaJson(): string
returns | string | A string serialization of the |
---|
From v3.0 onwards, the serialization is of an array with two entries. The first is the TablesSchema
object, the second the ValuesSchema
. In previous versions (before the existence of the ValuesSchema
data structure), it was a sole object of TablesSchema
.
Examples
This example serializes the TablesSchema
and ValuesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore()
.setTablesSchema({
pets: {
price: {type: 'number'},
},
})
.setValuesSchema({
open: {type: 'boolean'},
});
console.log(store.getSchemaJson());
// -> '[{"pets":{"price":{"type":"number"}}},{"open":{"type":"boolean"}}]'
This example serializes the TablesSchema
and ValuesSchema
of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Since
v1.0.0
Setter methods
This is the collection of setter methods within the Store
interface. There are 17 setter methods in total.
setTables
The setTables
method takes an object and sets the entire tabular data of the Store
.
setTables(tables: Tables): this
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Tables
type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Tables
object is valid, any data that was already present in the Store
will be completely overwritten. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the tabular data of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example attempts to set the tabular data of an existing Store
with partly invalid, and then completely invalid, Tables
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTables({pets: {felix: {species: 'cat', bug: []}}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTables({meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
setTablesJson
The setTablesJson
method takes a string serialization of all of the Tables
in the Store
and attempts to update them to that.
setTablesJson(tablesJson: string): this
Type | Description | |
---|---|---|
tablesJson | string | |
returns | this | A reference to the |
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables
method (according to the Tables
type, and matching any TablesSchema
associated with the Store
).
Examples
This example sets the tabular contents of a Store
from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setTablesJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the tabular contents of a Store
from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setTablesJson('{"pets": {"fido": {');
console.log(store.getTables());
// -> {}
Since
v3.0.0
setTablesSchema
The setTablesSchema
method lets you specify the TablesSchema
of the tabular part of the Store
.
setTablesSchema(tablesSchema: TablesSchema): this
Type | Description | |
---|---|---|
tablesSchema | TablesSchema | The |
returns | this | A reference to the |
Note that this may result in a change to data in the Store
, as defaults are applied or as invalid Table
, Row
, or Cell
objects are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing TablesSchema
with the delTablesSchema
method.
Example
This example sets the TablesSchema
of a Store
after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Since
v3.0.0
setTable
The setTable
method takes an object and sets the entire data of a single Table
in the Store
.
setTable(
tableId: string,
table: Table,
): this
Type | Description | |
---|---|---|
tableId | string | |
table | Table | The data of a single |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Table
type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Table
object is valid, any data that was already present in the Store
for that Table
will be completely overwritten. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Table
.
import {createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
This example attempts to set the data of an existing Store
with partly invalid, and then completely invalid, Table
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTable('pets', {felix: {species: 'cat', bug: []}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTable('pets', {meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
setRow
The setRow
method takes an object and sets the entire data of a single Row
in the Store
.
setRow(
tableId: string,
rowId: string,
row: Row,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
row | Row | The data of a single |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Row
type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Row
object is valid, any data that was already present in the Store
for that Row
will be completely overwritten. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Row
.
import {createStore} from 'tinybase';
const store = createStore().setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the data of an existing Store
with partly invalid, and then completely invalid, Row
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
store.setRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
Since
v1.0.0
addRow
The addRow
method takes an object and creates a new Row
in the Store
, returning the unique Id
assigned to it.
addRow(
tableId: string,
row: Row,
reuseRowIds?: boolean,
): undefined | string
Type | Description | |
---|---|---|
tableId | string | |
row | Row | The data of a single |
reuseRowIds? | boolean | Whether |
returns | undefined | string | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Row
type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Row
object is valid, a new Row
will be created. If the object is completely invalid, no change will be made to the Store
and the method will return undefined
.
You should not guarantee the form of the unique Id
that is generated when a Row
is added to the Table
. However it is likely to be a string representation of an increasing integer.
The reuseRowIds
parameter defaults to true
, which means that if you delete a Row
and then add another, the Id
will be re-used - unless you delete the entire Table
, in which case all Row
Ids
will reset. Otherwise, if you specify reuseRowIds
to be false
, then the Id
will be a monotonically increasing string representation of an increasing integer, regardless of any you may have previously deleted.
Examples
This example adds a single Row
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.addRow('pets', {species: 'dog'}));
// -> '0'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}}}
This example attempts to add Rows to an existing Store
with partly invalid, and then completely invalid, Row
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {'0': {species: 'dog'}}});
console.log(store.addRow('pets', {species: 'cat', bug: []}));
// -> '1'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
console.log(store.addRow('pets', 42));
// -> undefined
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
Since
v1.0.0
setPartialRow
The setPartialRow
method takes an object and sets partial data of a single Row
in the Store
, leaving other Cell
values unaffected.
setPartialRow(
tableId: string,
rowId: string,
partialRow: Row,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
partialRow | Row | The partial data of a single |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Row
type, or because, when combined with the current Row
data, it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Row
object is valid, it will be merged with the data that was already present in the Store
. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the data of a single Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.setPartialRow('pets', 'fido', {color: 'walnut', visits: 1});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'walnut', visits: 1}}}
This example attempts to set some of the data of an existing Store
with partly invalid, and then completely invalid, Row
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setPartialRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
store.setPartialRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
Since
v1.0.0
setCell
The setCell
method sets the value of a single Cell
in the Store
.
setCell(
tableId: string,
rowId: string,
cellId: string,
cell: Cell | MapCell,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
cell | Cell | MapCell | The value of the |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
If the Cell
value is invalid (either because of its type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
As well as string, number, or boolean Cell
types, this method can also take a MapCell
function that takes the current Cell
value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the value of a single Cell
.
import {createStore} from 'tinybase';
const store = createStore().setCell('pets', 'fido', 'species', 'dog');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example sets the data of a single Cell
by mapping the existing value.
import {createStore} from 'tinybase';
const increment = (cell) => cell + 1;
const store = createStore().setTables({pets: {fido: {visits: 1}}});
store.setCell('pets', 'fido', 'visits', increment);
console.log(store.getCell('pets', 'fido', 'visits'));
// -> 2
This example attempts to set the data of an existing Store
with an invalid Cell
value.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setCell('pets', 'fido', 'bug', []);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
Since
v1.0.0
setPartialValues
The setPartialValues
method takes an object and sets its Values
in the Store
, but leaving existing Values
unaffected.
setPartialValues(partialValues: Values): this
This method will cause listeners to be called for any Values
or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Values
type, or because, when combined with the current Values
data, it does not match a ValuesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Values
object is valid, it will be merged with the data that was already present in the Store
. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the keyed value data in a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set some of the data of an existing Store
with partly invalid, and then completely invalid, Values
objects.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setPartialValues(42);
console.log(store.getValues());
// -> {open: true, employees: 3}
Since
v3.0.0
setValues
The setValues
method takes an object and sets all the Values
in the Store
.
setValues(values: Values): this
This method will cause listeners to be called for any Value
or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Values
type, or because it does not match a ValuesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Values
object is valid, any data that was already present in the Store
for that Values
will be completely overwritten. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the Values
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store
with partly invalid, and then completely invalid, Values
objects.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {employees: 3}
store.setValues(42);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
setValuesJson
The setValuesJson
method takes a string serialization of all of the Values
in the Store
and attempts to update them to those values.
setValuesJson(valuesJson: string): this
Type | Description | |
---|---|---|
valuesJson | string | |
returns | this | A reference to the |
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setValues
method (according to the Values
type, and matching any ValuesSchema
associated with the Store
).
Examples
This example sets the keyed value contents of a Store
from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('{"open": true}');
console.log(store.getValues());
// -> {open: true}
This example attempts to set the keyed value contents of a Store
from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('{"open": false');
console.log(store.getValues());
// -> {}
Since
v3.0.0
setValuesSchema
The setValuesSchema
method lets you specify the ValuesSchema
of the keyed Values
part of the Store
.
setValuesSchema(valuesSchema: ValuesSchema): this
Type | Description | |
---|---|---|
valuesSchema | ValuesSchema | The |
returns | this | A reference to the |
Note that this may result in a change to data in the Store
, as defaults are applied or as invalid Values
are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing ValuesSchema
with the delValuesSchema
method.
Example
This example sets the ValuesSchema
of a Store
after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
store.setValue('open', 'maybe');
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
setValue
The setValue
method sets a single keyed Value
in the Store
.
setValue(
valueId: string,
value: Value | MapValue,
): this
This method will cause listeners to be called for any Value
, or Id
changes resulting from it.
If the Value
is invalid (either because of its type, or because it does not match a ValuesSchema
associated with the Store
), will be ignored silently.
As well as string, number, or boolean Value
types, this method can also take a MapValue
function that takes the current Value
as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets a single Value
.
import {createStore} from 'tinybase';
const store = createStore().setValue('open', true);
console.log(store.getValues());
// -> {open: true}
This example sets the data of a single Value
by mapping the existing Value
.
import {createStore} from 'tinybase';
const increment = (value) => value + 1;
const store = createStore().setValues({employees: 3});
store.setValue('employees', increment);
console.log(store.getValue('employees'));
// -> 4
This example attempts to set an invalid Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({employees: 3});
store.setValue('bug', []);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
applyChanges
The applyChanges
method applies a set of Changes
to the Store
.
applyChanges(changes: Changes): this
This method will take a Changes
object (which is available at the end of a transaction) and apply it to a Store
. The most likely need to do this is to take the changes made during the transaction of one Store
, and apply it to the content of another Store
- such as when persisting and synchronizing data.
Any part of the provided Changes
object are invalid (either because of its type, or because it does not match the schemas associated with the Store
) will be ignored silently.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Prior to v5.0, this method was named setTransactionChanges
.
Example
This example applies a Changes
object that sets a Cell
and removes a Value
.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.applyChanges([{pets: {fido: {color: 'black'}}}, {open: null}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
console.log(store.getValues());
// -> {}
Since
v5.0.0
setContent
The setContent
method takes an array of two objects and sets the entire data of the Store
.
setContent(content: Content): this
Type | Description | |
---|---|---|
content | Content | An array containing the tabular and keyed-value data of the |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, Value
, or Id
changes resulting from it.
Any part of the provided objects that are invalid (either according to the Tables
or Values
type, or because it does not match a TablesSchema
or ValuesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Tables
object or Values
object is valid, any data that was already present in that part of the Store
will be completely overwritten. If either object is completely invalid, no change will be made to the corresponding part of the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store
with partly invalid, and then completely invalid objects.
import {createStore} from 'tinybase';
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
store.setContent([{pets: {felix: {species: 'cat', bug: []}}}, '']);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setContent([{meaning: 42}]);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v4.0.0
setJson
The setJson
method takes a string serialization of all of the Tables
and Values
in the Store
and attempts to update them to those values.
setJson(tablesAndValuesJson: string): this
Type | Description | |
---|---|---|
tablesAndValuesJson | string | A string serialization of all of the |
returns | this | A reference to the |
From v3.0 onwards, the serialization should be of an array with two entries. The first is the Tables
object, the second the Values
. In previous versions (before the existence of the Values
data structure), it was a sole object of Tables
. For backwards compatibility, if a serialization of a single object is provided, it will be treated as the Tables
type.
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables
method (according to the Tables
type, and matching any TablesSchema
associated with the Store
), and the setValues
method (according to the Values
type, and matching any ValuesSchema
associated with the Store
).
Examples
This example sets the tabular and keyed value contents of a Store
from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setJson('[{"pets": {"fido": {"species": "dog"}}}, {"open": true}]');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true}
This example sets the tabular contents of a Store
from a legacy single-object serialization (compatible with v2.x and earlier).
import {createStore} from 'tinybase';
const store = createStore();
store.setJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {}
This example attempts to set both the tabular and keyed value contents of a Store
from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('[{"pets": {"fido": {"species": "do');
console.log(store.getTables());
// -> {}
console.log(store.getValues());
// -> {}
Since
v1.0.0
setSchema
The setSchema
method lets you specify the TablesSchema
and ValuesSchema
of the Store
.
setSchema(
tablesSchema: TablesSchema,
valuesSchema?: ValuesSchema,
): this
Type | Description | |
---|---|---|
tablesSchema | TablesSchema | The |
valuesSchema? | ValuesSchema | The |
returns | this | A reference to the |
Note that this may result in a change to data in the Store
, as defaults are applied or as invalid Table
, Row
, Cell
, or Value
objects are removed. These changes will fire any listeners to that data, as expected.
From v3.0 onwards, this method takes two arguments. The first is the TablesSchema
object, the second the ValuesSchema
. In previous versions (before the existence of the ValuesSchema
data structure), only the first was present. For backwards compatibility the new second parameter is optional.
Examples
This example sets the TablesSchema
and ValuesSchema
of a Store
after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setSchema(
{
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
},
{open: {type: 'boolean', default: false}},
);
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
store.setValue('open', 'maybe');
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
console.log(store.getValues());
// -> {open: false}
This example sets just the TablesSchema
of a Store
after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Since
v1.0.0
Listener methods
This is the collection of listener methods within the Store
interface. There are 27 listener methods in total.
addHasTablesListener
The addHasTablesListener
method registers a listener function with the Store
that will be called when Tables
as a whole are added to or removed from the Store
.
addHasTablesListener(
listener: HasTablesListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | HasTablesListener<Store> | The function that will be called whenever |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasTablesListener
function, and will be called with a reference to the Store
. It is also given a flag to indicate whether Tables
now exist (having not done previously), or do not (having done so previously).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to Tables
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTablesListener((store, hasTables) => {
console.log('Tables ' + (hasTables ? 'added' : 'removed'));
});
store.delTables();
// -> 'Tables removed'
store.setTables({species: {dog: {price: 5}}});
// -> 'Tables added'
store.delListener(listenerId);
This example registers a listener that responds to Tables
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addHasTablesListener(
(store, hasTables) => store.setValue('hasTables', hasTables),
true,
);
store.setTables({species: {dog: {price: 5}}});
console.log(store.getValues());
// -> {hasTables: true}
store.delListener(listenerId);
Since
v4.4.0
addTablesListener
The addTablesListener
method registers a listener function with the Store
that will be called whenever data in the Store
changes.
addTablesListener(
listener: TablesListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | TablesListener<Store> | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a TablesListener
function, and will be called with a reference to the Store
and a GetCellChange
function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the whole Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener((store, getCellChange) => {
console.log('Tables changed');
console.log(getCellChange('pets', 'fido', 'color'));
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to the whole Store
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(
(store) => store.setCell('meta', 'update', 'store', true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
Since
v1.0.0
addTableIdsListener
The addTableIdsListener
method registers a listener function with the Store
that will be called whenever the Table
Ids
in the Store
change.
addTableIdsListener(
listener: TableIdsListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | TableIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a TableIdsListener
function, and will be called with a reference to the Store
.
By default, such a listener is only called when a Table
is added or removed. To listen to all changes in the Store
, use the addTablesListener
method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Table
Ids
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener((store) => {
console.log('Table Ids changed');
console.log(store.getTableIds());
});
store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'
// -> ['pets', 'species']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Table
Ids
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener(
(store) => store.setCell('meta', 'update', 'store', true),
true, // mutator
);
store.setTable('species', {dog: {price: 5}});
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
Since
v1.0.0
addHasTableCellListener
The addHasTableCellListener
method registers a listener function with the Store
that will be called when a Cell
is added to or removed from anywhere in a Table
as a whole.
addHasTableCellListener(
tableId: IdOrNull,
cellId: IdOrNull,
listener: HasTableCellListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
cellId | IdOrNull | |
listener | HasTableCellListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasTableCellListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, and the Id
of the Table
Cell
that changed. It is also given a flag to indicate whether the Cell
now exists anywhere in the Table
(having not done previously), or does not (having done so previously).
You can either listen to a single Table
Cell
being added or removed (by specifying the Table
Id
and Cell
Id
, as the method's first two parameters) or changes to any Table
Cell
(by providing null
wildcards).
Both, either, or neither of the tableId
and cellId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Cell
being added to or removed from the Table
as a whole.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
'pets',
'color',
(store, tableId, cellId, hasTableCell) => {
console.log(
'color cell in pets table ' + (hasTableCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in pets table removed'
store.setRow('pets', 'felix', {species: 'cat', color: 'brown'});
// -> 'color cell in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Cell
being added to or removed from the Table
as a whole.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
null,
null,
(store, tableId, cellId, hasTableCell) => {
console.log(
`${cellId} cell in ${tableId} table ` +
(hasTableCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Cell
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
'pets',
'color',
(store, tableId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${cellId}`, true),
true,
);
store.delRow('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets_color: true}}
store.delListener(listenerId);
Since
v4.4.0
addHasTableListener
The addHasTableListener
method registers a listener function with the Store
that will be called when a Table
is added to or removed from the Store
.
addHasTableListener(
tableId: IdOrNull,
listener: HasTableListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | HasTableListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasTableListener
function, and will be called with a reference to the Store
and the Id
of the Table
that changed. It is also given a flag to indicate whether the Table
now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Table
being added or removed (by specifying the Table
Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Table
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
'pets',
(store, tableId, hasTable) => {
console.log('pets table ' + (hasTable ? 'added' : 'removed'));
},
);
store.delTable('pets');
// -> 'pets table removed'
store.setTable('pets', {fido: {species: 'dog', color: 'brown'}});
// -> 'pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Table
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
null,
(store, tableId, hasTable) => {
console.log(`${tableId} table ` + (hasTable ? 'added' : 'removed'));
},
);
store.delTable('pets');
// -> 'pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Table
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true,
);
store.delTable('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v4.4.0
addTableCellIdsListener
The addTableCellIdsListener
method registers a listener function with the Store
that will be called whenever the Cell
Ids
that appear anywhere in a Table
change.
addTableCellIdsListener(
tableId: IdOrNull,
listener: TableCellIdsListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | TableCellIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a TableCellIdsListener
function, and will be called with a reference to the Store
and the Id
of the Table
that changed.
By default, such a listener is only called when a Cell
Id
is added or removed from the whole of the Table
. To listen to all changes in the Table
, use the addTableListener
method.
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell
Ids
that appear anywhere in a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener('pets', (store) => {
console.log('Cell Ids in pets table changed');
console.log(store.getTableCellIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell
Ids
that appear anywhere in any Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
species: {dog: {price: 5}},
});
const listenerId = store.addTableCellIdsListener(
null,
(store, tableId) => {
console.log(`Cell Ids in ${tableId} table changed`);
console.log(store.getTableCellIds(tableId));
},
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.setRow('species', 'cat', {price: 4, friendly: true});
// -> 'Cell Ids in species table changed'
// -> ['price', 'friendly']
store.delListener(listenerId);
This example registers a listener that responds to the Cell
Ids
that appear anywhere in a Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addTableListener
The addTableListener
method registers a listener function with the Store
that will be called whenever data in a Table
changes.
addTableListener(
tableId: IdOrNull,
listener: TableListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | TableListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a TableListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, and a GetCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId, getCellChange) => {
console.log('pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(null, (store, tableId) => {
console.log(`${tableId} table changed`);
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addRowIdsListener
The addRowIdsListener
method registers a listener function with the Store
that will be called whenever the Row
Ids
in a Table
change.
addRowIdsListener(
tableId: IdOrNull,
listener: RowIdsListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a RowIdsListener
function, and will be called with a reference to the Store
and the Id
of the Table
that changed.
By default, such a listener is only called when a Row
is added or removed. To listen to all changes in the Table
, use the addTableListener
method.
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Row
Ids
of a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener('pets', (store) => {
console.log('Row Ids for pets table changed');
console.log(store.getRowIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row
Ids
of any Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids for ${tableId} table changed`);
console.log(store.getRowIds(tableId));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.setRow('species', 'dog', {price: 5});
// -> 'Row Ids for species table changed'
// -> ['dog']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row
Ids
of a specific Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addSortedRowIdsListener
The addSortedRowIdsListener
method registers a listener function with the Store
that will be called whenever sorted (and optionally, paginated) Row
Ids
in a Table
change.
addSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener<Store> | The function that will be called whenever the sorted |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a SortedRowIdsListener
function, and will be called with a reference to the Store
, the Id
of the Table
whose Row
Ids
sorting changed, the Cell
Id
being used to sort them, whether descending or not, and the offset and limit of the number of Ids
returned, for pagination purposes. It also receives the sorted array of Ids
itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Such a listener is called when a Row
is added or removed, but also when a value in the specified Cell
(somewhere in the Table
) has changed enough to change the sorting of the Row
Ids
.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified Table
, sorted by a single specified Cell
.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'cujo']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'fido', {species: 'dog'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['felix', 'fido', 'cujo']
store.delListener(listenerId);
This example registers a listener that responds to any change to a paginated section of the sorted Row
Ids
of a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'price',
false,
0,
3,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`First three sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
console.log(store.getSortedRowIds('pets', 'price', false, 0, 3));
// -> ['lowly', 'mickey', 'carnaby']
store.setCell('pets', 'carnaby', 'price', 4.5);
// -> 'First three sorted Row Ids for pets table changed'
// -> ['lowly', 'mickey', 'tom']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific Table
. The Row
Ids
are sorted by their own value, since the cellId
parameter is explicitly undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', undefined, false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
undefined,
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['cujo', 'felix', 'fido']
store.delListener(listenerId);
This example registers a listener that responds to a change in the sorting of the rows of a specific Table
, even though the set of Ids
themselves has not changed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setCell('pets', 'felix', 'species', 'tiger');
// -> 'Sorted Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId) => store.setCell('meta', 'sorted', tableId, true),
true, // mutator
);
store.setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTable('meta'));
// -> {sorted: {pets: true}}
store.delListener(listenerId);
Since
v2.0.0
addHasRowListener
The addHasRowListener
method registers a listener function with the Store
that will be called when a Row
is added to or removed from the Store
.
addHasRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: HasRowListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | HasRowListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasRowListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, and the Id
of the Row
that changed. It is also given a flag to indicate whether the Row
now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Row
being added or removed (by specifying the Table
Id
and Row
Id
, as the method's first two parameters) or changes to any Row
(by providing null
wildcards).
Both, either, or neither of the tableId
and rowId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Row
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
'pets',
'fido',
(store, tableId, rowId, hasRow) => {
console.log(
'fido row in pets table ' + (hasRow ? 'added' : 'removed'),
);
},
);
store.delRow('pets', 'fido');
// -> 'fido row in pets table removed'
store.setRow('pets', 'fido', {species: 'dog', color: 'brown'});
// -> 'fido row in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Row
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
null,
null,
(store, tableId, rowId, hasRow) => {
console.log(
`${rowId} row in ${tableId} table ` + (hasRow ? 'added' : 'removed'),
);
},
);
store.delRow('pets', 'fido');
// -> 'fido row in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Row
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delRow('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v4.4.0
addRowCountListener
The addRowCountListener
method registers a listener function with the Store
that will be called whenever the count of Row
objects in a Table
change.
addRowCountListener(
tableId: IdOrNull,
listener: RowCountListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | RowCountListener<Store> | The function that will be called whenever the number of |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a RowCountListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, and the number of Row
objects in the Table
.
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a change in the number of Row
objects in a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, _tableId, count) => {
console.log('Row count for pets table changed to ' + count);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row
objects of any Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
null,
(store, tableId, count) => {
console.log(`Row count for ${tableId} table changed to ${count}`);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.setRow('species', 'dog', {price: 5});
// -> 'Row count for species table changed to 1'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row
objects of a specific Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, tableId, count) =>
store.setCell('meta', 'update', tableId, count),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: 2}}
store.delListener(listenerId);
Since
v4.1.0
addRowListener
The addRowListener
method registers a listener function with the Store
that will be called whenever data in a Row
changes.
addRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: RowListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a RowListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, the Id
of the Row
that changed, and a GetCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single Row
(by specifying the Table
Id
and Row
Id
as the method's first two parameters) or changes to any Row
(by providing null
wildcards).
Both, either, or neither of the tableId
and rowId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId, getCellChange) => {
console.log('fido row in pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
null,
null,
(store, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Row
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v1.0.0
addCellIdsListener
The addCellIdsListener
method registers a listener function with the Store
that will be called whenever the Cell
Ids
in a Row
change.
addCellIdsListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: CellIdsListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a CellIdsListener
function, and will be called with a reference to the Store
, the Id
of the Table
, and the Id
of the Row
that changed.
By default, such a listener is only called when a Cell
is added or removed. To listen to all changes in the Row
, use the addRowListener
method.
You can either listen to a single Row
(by specifying the Table
Id
and Row
Id
as the method's first two parameters) or changes to any Row
(by providing a null
wildcard).
Both, either, or neither of the tableId
and rowId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell
Ids
of a specific Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener('pets', 'fido', (store) => {
console.log('Cell Ids for fido row in pets table changed');
console.log(store.getCellIds('pets', 'fido'));
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell
Ids
of any Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
null,
null,
(store, tableId, rowId) => {
console.log(`Cell Ids for ${rowId} row in ${tableId} table changed`);
console.log(store.getCellIds(tableId, rowId));
},
);
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.setCell('species', 'dog', 'price', 5);
// -> 'Cell Ids for dog row in species table changed'
// -> ['price']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell
Ids
of a specific Row
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true, // mutator
);
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v1.0.0
addCellListener
The addCellListener
method registers a listener function with the Store
that will be called whenever data in a Cell
changes.
addCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: CellListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a CellListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, the Id
of the Row
that changed, the Id
of the Cell
that changed, the new Cell
value, the old Cell
value, and a GetCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single Cell
(by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or changes to any Cell
(by providing null
wildcards).
All, some, or none of the tableId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific Row
in a specific Table
, any Cell
in any Row
in any Table
, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Cell
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log('color cell in fido row in pets table changed');
console.log([oldCell, newCell]);
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Cell
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
Since
v1.0.0
addHasCellListener
The addHasCellListener
method registers a listener function with the Store
that will be called when a Cell
is added to or removed from the Store
.
addHasCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: HasCellListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | HasCellListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasCellListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, the Id
of the Row
that changed, and the Id
of the Cell
that changed. It is also given a flag to indicate whether the Cell
now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Cell
being added or removed (by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or changes to any Cell
(by providing null
wildcards).
All, some, or none of the tableId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific Row
in a specific Table
, any Cell
in any Row
in any Table
, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Cell
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, hasCell) => {
console.log(
'color cell in fido row in pets table ' +
(hasCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in fido row in pets table removed'
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Cell
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
null,
null,
null,
(store, tableId, rowId, cellId, hasCell) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table ` +
(hasCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in fido row in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Cell
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
Since
v4.4.0
addInvalidCellListener
The addInvalidCellListener
method registers a listener function with the Store
that will be called whenever invalid data was attempted to be written to a Cell
.
addInvalidCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: InvalidCellListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | InvalidCellListener<Store> | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is an InvalidCellListener
function, and will be called with a reference to the Store
, the Id
of the Table
, the Id
of the Row
, and the Id
of Cell
that was being attempted to be changed. It is also given the invalid value of the Cell
, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell
within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Cell
(by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or invalid attempts to change any Cell
(by providing null
wildcards).
All, some, or none of the tableId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific Row
in a specific Table
, any Cell
in any Row
in any Table
, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a TablesSchema
is present. The listener will be called:
- if a
Table
is being updated that is not specified in theTablesSchema
, - if a
Cell
is of the wrong type specified in theTablesSchema
, - if a
Cell
is omitted and is not defaulted in theTablesSchema
, - or if an empty
Row
is provided and there are noCell
defaults in theTablesSchema
.
The listener will not be called if a Cell
that is defaulted in the TablesSchema
is not provided, as long as all of the Cells that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the TablesSchema
example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Cell
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) => {
console.log('Invalid color cell in fido row in pets table');
console.log(invalidCells);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
// -> [{r: '96', g: '4B', b: '00'}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell
- in a Store
without a TablesSchema
. Note also how it then responds to cases where empty or invalid Row
objects, or Table
objects, or Tables
objects are provided.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
store.setTable('sales', {fido: {date: new Date()}});
// -> 'Invalid date cell in fido row in sales table'
store.setRow('pets', 'felix', {});
// -> 'Invalid undefined cell in felix row in pets table'
store.setRow('filter', 'name', /[a-z]?/);
// -> 'Invalid undefined cell in name row in filter table'
store.setRow('sales', '2021', {forecast: undefined});
// -> 'Invalid forecast cell in 2021 row in sales table'
store.addRow('filter', /[0-9]?/);
// -> 'Invalid undefined cell in undefined row in filter table'
store.setTable('raw', {});
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTable('raw', ['row1', 'row2']);
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTables(['table1', 'table2']);
// -> 'Invalid undefined cell in undefined row in undefined table'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell
- in a Store
with a TablesSchema
. Note how it responds to cases where missing parameters are provided for optional, and defaulted Cell
values in a Row
.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string', default: 'unknown'},
},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setRow('sales', 'fido', {price: 5});
// -> 'Invalid price cell in fido row in sales table'
// The listener is called, because the sales Table is not in the schema
store.setRow('pets', 'felix', {species: true});
// -> 'Invalid species cell in felix row in pets table'
// The listener is called, because species is invalid...
console.log(store.getRow('pets', 'felix'));
// -> {color: 'unknown'}
// ...even though a Row was set with the default value
store.setRow('pets', 'fido', {color: 'brown'});
// -> 'Invalid species cell in fido row in pets table'
// The listener is called, because species is missing and not defaulted...
console.log(store.getRow('pets', 'fido'));
// -> {color: 'brown'}
// ...even though a Row was set
store.setRow('pets', 'rex', {species: 'dog'});
console.log(store.getRow('pets', 'rex'));
// -> {species: 'dog', color: 'unknown'}
// The listener is not called, because color is defaulted
store.delTables().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string'},
},
});
store.setRow('pets', 'cujo', {});
// -> 'Invalid species cell in cujo row in pets table'
// -> 'Invalid color cell in cujo row in pets table'
// -> 'Invalid undefined cell in cujo row in pets table'
// The listener is called multiple times, because neither Cell is defaulted
// and the Row as a whole is empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) =>
store.setCell(
'meta',
'invalid_updates',
`${tableId}_${rowId}_${cellId}`,
JSON.stringify(invalidCells[0]),
),
true,
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
console.log(store.getRow('meta', 'invalid_updates'));
// -> {'pets_fido_color': '{"r":"96","g":"4B","b":"00"}'}
store.delListener(listenerId);
Since
v1.1.0
addHasValuesListener
The addHasValuesListener
method registers a listener function with the Store
that will be called when Values
as a whole are added to or removed from the Store
.
addHasValuesListener(
listener: HasValuesListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | HasValuesListener<Store> | The function that will be called whenever |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasValuesListener
function, and will be called with a reference to the Store
. It is also given a flag to indicate whether Values
now exist (having not done previously), or do not (having done so previously).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to Values
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValuesListener((store, hasValues) => {
console.log('Values ' + (hasValues ? 'added' : 'removed'));
});
store.delValues();
// -> 'Values removed'
store.setValue('employees', 4);
// -> 'Values added'
store.delListener(listenerId);
This example registers a listener that responds to Values
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addHasValuesListener(
(store, hasValues) => store.setValue('hasValues', hasValues),
true,
);
store.setValue('employees', 4);
console.log(store.getValues());
// -> {employees: 4, hasValues: true}
store.delListener(listenerId);
Since
v4.4.0
addValuesListener
The addValuesListener
method registers a listener function with the Store
that will be called whenever the Values
change.
addValuesListener(
listener: ValuesListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | ValuesListener<Store> | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a ValuesListener
function, and will be called with a reference to the Store
and a GetValueChange
function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the Store
's Values
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener((store, getValueChange) => {
console.log('values changed');
console.log(getValueChange('employees'));
});
store.setValue('employees', 4);
// -> 'values changed'
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to the Store
's Values
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener(
(store) => store.setValue('updated', true),
true,
);
store.setValue('employees', 4);
console.log(store.getValues());
// -> {open: true, employees: 4, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addHasValueListener
The addHasValueListener
method registers a listener function with the Store
that will be called when a Value
is added to or removed from the Store
.
addHasValueListener(
valueId: IdOrNull,
listener: HasValueListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
valueId | IdOrNull | |
listener | HasValueListener<Store> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasValueListener
function, and will be called with a reference to the Store
and the Id
of Value
that changed. It is also given a flag to indicate whether the Value
now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Value
being added or removed (by specifying the Value
Id
) or any Value
being added or removed (by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Value
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
'employees',
(store, valueId, hasValue) => {
console.log('employee value ' + (hasValue ? 'added' : 'removed'));
},
);
store.delValue('employees');
// -> 'employee value removed'
store.setValue('employees', 4);
// -> 'employee value added'
store.delListener(listenerId);
This example registers a listener that responds to any Value
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
null,
(store, valueId, hasValue) => {
console.log(valueId + ' value ' + (hasValue ? 'added' : 'removed'));
},
);
store.delValue('employees');
// -> 'employees value removed'
store.setValue('website', 'https://pets.com');
// -> 'website value added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Value
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
'employees',
(store) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v4.4.0
addInvalidValueListener
The addInvalidValueListener
method registers a listener function with the Store
that will be called whenever invalid data was attempted to be written to a Value
.
addInvalidValueListener(
valueId: IdOrNull,
listener: InvalidValueListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
valueId | IdOrNull | |
listener | InvalidValueListener<Store> | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is an InvalidValueListener
function, and will be called with a reference to the Store
and the Id
of Value
that was being attempted to be changed. It is also given the invalid value of the Value
, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Value
within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Value
(by specifying the Value
Id
as the method's first parameter) or invalid attempts to change any Value
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a ValuesSchema
is present. The listener will be called:
- if a
Value
is being updated that is not specified in theValuesSchema
, - if a
Value
is of the wrong type specified in theValuesSchema
, - or if a
Value
is omitted when using setValues that is not defaulted in theValuesSchema
.
The listener will not be called if a Value
that is defaulted in the ValuesSchema
is not provided, as long as all of the Values
that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the ValuesSchema
example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) => {
console.log('Invalid open value');
console.log(invalidValues);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
// -> [{yes: true}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value
- in a Store
without a ValuesSchema
. Note also how it then responds to cases where an empty Values
object is provided.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
store.setValue('employees', ['alice', 'bob']);
// -> 'Invalid employees value'
store.setValues('pets', 'felix', {});
// -> 'Invalid undefined value'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value
- in a Store
with a ValuesSchema
. Note how it responds to cases where missing parameters are provided for optional, and defaulted Values
.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
console.log(store.getValues());
// -> {open: false}
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('website', true);
// -> 'Invalid website value'
// The listener is called, because the website Value is not in the schema
store.setValue('open', 'yes');
// -> 'Invalid open value'
// The listener is called, because 'open' is invalid...
console.log(store.getValues());
// -> {open: false}
// ...even though it is still present with the default value
store.setValues({open: true});
// -> 'Invalid employees value'
// The listener is called because employees is missing and not defaulted...
console.log(store.getValues());
// -> {open: true}
// ...even though the Values were set
store.setValues({employees: 3});
console.log(store.getValues());
// -> {open: false, employees: 3}
// The listener is not called, because 'open' is defaulted
store.setValuesSchema({
open: {type: 'boolean'},
employees: {type: 'number'},
});
store.setValues({});
// -> 'Invalid open value'
// -> 'Invalid employees value'
// -> 'Invalid undefined value'
// The listener is called multiple times, because neither Value is
// defaulted and the Values as a whole were empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) =>
store.setValue('invalid_updates', JSON.stringify(invalidValues[0])),
true,
);
store.setValue('open', {yes: true});
console.log(store.getValue('invalid_updates'));
// -> '{"yes":true}'
store.delListener(listenerId);
Since
v3.0.0
addValueIdsListener
The addValueIdsListener
method registers a listener function with the Store
that will be called whenever the Value
Ids
in a Store
change.
addValueIdsListener(
listener: ValueIdsListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | ValueIdsListener<Store> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a ValueIdsListener
function, and will be called with a reference to the Store
.
By default, such a listener is only called when a Value
is added or removed. To listen to all changes in the Values
, use the addValuesListener
method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Value
Ids
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener((store) => {
console.log('Value Ids changed');
console.log(store.getValueIds());
});
store.setValue('employees', 3);
// -> 'Value Ids changed'
// -> ['open', 'employees']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Value
Ids
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener(
(store) => store.setValue('updated', true),
true, // mutator
);
store.setValue('employees', 3);
console.log(store.getValues());
// -> {open: true, employees: 3, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addValueListener
The addValueListener
method registers a listener function with the Store
that will be called whenever data in a Value
changes.
addValueListener(
valueId: IdOrNull,
listener: ValueListener<Store>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
valueId | IdOrNull | |
listener | ValueListener<Store> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a ValueListener
function, and will be called with a reference to the Store
, the Id
of the Value
that changed, the new Value
value, the old Value
, and a GetValueChange
function in case you need to inspect any changes that occurred.
You can either listen to a single Value
(by specifying the Value
Id
) or changes to any Value
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store, valueId, newValue, oldValue, getValueChange) => {
console.log('employee value changed');
console.log([oldValue, newValue]);
console.log(getValueChange('employees'));
},
);
store.setValue('employees', 4);
// -> 'employee value changed'
// -> [3, 4]
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(null, (store, valueId) => {
console.log(`${valueId} value changed`);
});
store.setValue('employees', 4);
// -> 'employees value changed'
store.setValue('open', false);
// -> 'open value changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addDidFinishTransactionListener
The addDidFinishTransactionListener
method registers a listener function with the Store
that will be called just after other non-mutating listeners are called at the end of the transaction.
addDidFinishTransactionListener(listener: TransactionListener<Store>): string
Type | Description | |
---|---|---|
listener | TransactionListener<Store> | The function that will be called after the end of a transaction. |
returns | string | A unique |
This is useful if you need to know that a set of listeners have just been called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener
will receive a reference to the Store
and two booleans to indicate whether Cell
or Value
data has been touched during the transaction. The two flags is intended as a hint about whether non-mutating listeners might have been called at the end of the transaction.
Here, 'touched' means that Cell
or Value
data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched
and valuesTouched
in the listener will be false
because all changes have been reverted.
Note that a TransactionListener
added to the Store
with this method cannot mutate the Store
itself, and attempts to do so will fail silently.
Example
This example registers a listener that is called at the end of the transaction, just after its listeners have been called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched
and valuesTouched
parameters in the listener work.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addDidFinishTransactionListener((store) => {
const [cellsTouched, valuesTouched] = store.getTransactionLog() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
});
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Tables changed'
// -> 'Cells/Values touched: true/false'
store.transaction(() => store.setValue('employees', 4));
// -> 'Values changed'
// -> 'Cells/Values touched: false/true'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: false/false'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
addStartTransactionListener
The addStartTransactionListener
method registers a listener function with the Store
that will be called at the start of a transaction.
addStartTransactionListener(listener: TransactionListener<Store>): string
Type | Description | |
---|---|---|
listener | TransactionListener<Store> | The function that will be called at the start of a transaction. |
returns | string | A unique |
The provided TransactionListener
will receive a reference to the Store
and two booleans to indicate whether Cell
or Value
data has been touched during the transaction. Since this is called at the start, they will both be false
!
Note that a TransactionListener
added to the Store
with this method can mutate the Store
, and its changes will be treated as part of the transaction that is starting.
Example
This example registers a listener that is called at start end of the transaction, just before its listeners will be called.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addStartTransactionListener(() => {
console.log('Transaction started');
});
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Transaction started'
store.callListener(listenerId);
// -> 'Transaction started'
store.delListener(listenerId);
Since
v3.2.0
addWillFinishTransactionListener
The addWillFinishTransactionListener
method registers a listener function with the Store
that will be called just before other non-mutating listeners are called at the end of the transaction.
addWillFinishTransactionListener(listener: TransactionListener<Store>): string
Type | Description | |
---|---|---|
listener | TransactionListener<Store> | The function that will be called before the end of a transaction. |
returns | string | A unique |
This is useful if you need to know that a set of listeners are about to be called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener
will receive a reference to the Store
and two booleans to indicate whether Cell
or Value
data has been touched during the transaction. The two flags are intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell
or Value
data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched
and valuesTouched
in the listener will be false
because all changes have been reverted.
Note that a TransactionListener
added to the Store
with this method can mutate the Store
itself, and its changes will be treated as part of the transaction that is starting (and may fire non-mutating listeners after this).
Example
This example registers a listener that is called at the end of the transaction, just before its listeners will be called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched
and valuesTouched
parameters in the listener work.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addWillFinishTransactionListener((store) => {
const [cellsTouched, valuesTouched] = store.getTransactionLog() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
});
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Cells/Values touched: true/false'
// -> 'Tables changed'
store.transaction(() => store.setValue('employees', 4));
// -> 'Cells/Values touched: false/true'
// -> 'Values changed'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: false/false'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
callListener
The callListener
method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed.
callListener(listenerId: string): this
This is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store
in bulk.
Examples
This example registers a listener that ensures a Cell
has one of list of a valid values. After that list changes, the listener is called to apply the condition to the existing data.
import {createStore} from 'tinybase';
const validColors = ['walnut', 'brown', 'black'];
const store = createStore();
const listenerId = store.addCellListener(
'pets',
null,
'color',
(store, tableId, rowId, cellId, color) => {
if (!validColors.includes(color)) {
store.setCell(tableId, rowId, cellId, validColors[0]);
}
},
true,
);
store.setRow('pets', 'fido', {species: 'dog', color: 'honey'});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'walnut'}
validColors.shift();
console.log(validColors);
// -> ['brown', 'black']
store.callListener(listenerId);
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'brown'}
store.delListener(listenerId);
This example registers a listener to Row
Id
changes. It is explicitly called and fires for two Tables
in the Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids listener called for ${tableId} table`);
});
store.callListener(listenerId);
// -> 'Row Ids listener called for pets table'
// -> 'Row Ids listener called for species table'
store.delListener(listenerId);
This example registers a listener Value
changes. It is explicitly called and fires for two Values
in the Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
null,
(store, valueId, value) => {
console.log(`Value listener called for ${valueId} value, ${value}`);
},
);
store.callListener(listenerId);
// -> 'Value listener called for open value, true'
// -> 'Value listener called for employees value, 3'
store.delListener(listenerId);
This example registers listeners for the end of transactions, and for invalid Cells. They are explicitly called, meaninglessly. The former receives empty arguments. The latter is not called at all.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addWillFinishTransactionListener(
(store, cellsTouched, valuesTouched) => {
console.log(`Transaction finish: ${cellsTouched}/${valuesTouched}`);
},
);
store.callListener(listenerId);
// -> 'Transaction finish: undefined/undefined'
store.delListener(listenerId);
const listenerId2 = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log('Invalid cell', tableId, rowId, cellId);
},
);
store.callListener(listenerId2);
// -> undefined
store.delListener(listenerId2);
Since
v1.0.0
delListener
The delListener
method removes a listener that was previously added to the Store
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Store
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(() => {
console.log('Tables changed');
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
store.delListener(listenerId);
store.setCell('pets', 'fido', 'color', 'honey');
// -> undefined
// The listener is not called.
Since
v1.0.0
Iterator methods
This is the collection of iterator methods within the Store
interface. There are 5 iterator methods in total.
forEachTable
The forEachTable
method takes a function that it will then call for each Table
in the Store
.
forEachTable(tableCallback: TableCallback): void
Type | Description | |
---|---|---|
tableCallback | TableCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Table
structure of the Store
in a functional style. The tableCallback
parameter is a TableCallback
function that will be called with the Id
of each Table
, and with a function that can then be used to iterate over each Row
of the Table
, should you wish.
Example
This example iterates over each Table
in a Store
, and lists each Row
Id
within them.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.forEachTable((tableId, forEachRow) => {
console.log(tableId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'pets'
// -> '- fido'
// -> 'species'
// -> '- dog'
Since
v1.0.0
forEachTableCell
The forEachTableCell
method takes a function that it will then call for each Cell
used across the whole Table
.
forEachTableCell(
tableId: string,
tableCellCallback: TableCellCallback,
): void
Type | Description | |
---|---|---|
tableId | string | |
tableCellCallback | TableCellCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Cell
structure of the Table
in a functional style. The tableCellCallback
parameter is a TableCellCallback
function that will be called with the Id
of each Cell
and the count of Rows in the Table
in which it appears.
Example
This example iterates over each Cell
Id
used across the whole Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat', legs: 4}},
});
store.forEachTableCell('pets', (cellId, count) => {
console.log(`${cellId}: ${count}`);
});
// -> 'species: 2'
// -> 'legs: 1'
Since
v3.3.0
forEachRow
The forEachRow
method takes a function that it will then call for each Row
in a specified Table
.
forEachRow(
tableId: string,
rowCallback: RowCallback,
): void
Type | Description | |
---|---|---|
tableId | string | |
rowCallback | RowCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Row
structure of the Table
in a functional style. The rowCallback
parameter is a RowCallback
function that will be called with the Id
of each Row
, and with a function that can then be used to iterate over each Cell
of the Row
, should you wish.
Example
This example iterates over each Row
in a Table
, and lists each Cell
Id
within them.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {color: 'black'},
},
});
store.forEachRow('pets', (rowId, forEachCell) => {
console.log(rowId);
forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- species'
// -> 'felix'
// -> '- color'
Since
v1.0.0
forEachCell
The forEachCell
method takes a function that it will then call for each Cell
in a specified Row
.
forEachCell(
tableId: string,
rowId: string,
cellCallback: CellCallback,
): void
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellCallback | CellCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Cell
structure of the Row
in a functional style. The cellCallback
parameter is a CellCallback
function that will be called with the Id
and value of each Cell
.
Example
This example iterates over each Cell
in a Row
, and lists its value.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.forEachCell('pets', 'fido', (cellId, cell) => {
console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
Since
v1.0.0
forEachValue
The forEachValue
method takes a function that it will then call for each Value
in a Store
.
forEachValue(valueCallback: ValueCallback): void
Type | Description | |
---|---|---|
valueCallback | ValueCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Value
structure of the Store
in a functional style. The valueCallback
parameter is a ValueCallback
function that will be called with the Id
and value of each Value
.
Example
This example iterates over each Value
in a Store
, and lists its value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.forEachValue((valueId, value) => {
console.log(`${valueId}: ${value}`);
});
// -> 'open: true'
// -> 'employees: 3'
Since
v3.0.0
Transaction methods
This is the collection of transaction methods within the Store
interface. There are 5 transaction methods in total.
finishTransaction
The finishTransaction
method allows you to explicitly finish a transaction that has made multiple mutations to the Store
, triggering all calls to the relevant listeners.
finishTransaction(doRollback?: DoRollback): this
Type | Description | |
---|---|---|
doRollback? | DoRollback | An optional callback that should return |
returns | this | A reference to the |
Transactions are useful for making bulk changes to the data in a Store
, and when you don't want listeners to be called as you make each change. Changes
are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction
method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction
methods for you. See that method for several transaction examples.
Use this finishTransaction
method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. There must have been a corresponding startTransaction
method that this completes, of course, otherwise this function has no effect.
The optional parameter, doRollback
is a DoRollback
callback that you can use to rollback the transaction if it did not complete to your satisfaction. It is called with getTransactionChanges
and getTransactionLog
parameters, which inform you of the net changes that have been made during the transaction, at different levels of detail. See the DoRollback
documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row
listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
This example makes multiple changes to the Store
, including some attempts to update a Cell
with invalid values. The doRollback
callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob'])
.finishTransaction(() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
});
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.3.0
getTransactionChanges
The getTransactionChanges
method returns the net meaningful changes that have been made to a Store
during a transaction.
getTransactionChanges(): Changes
This is useful for deciding whether to rollback a transaction, for example. The returned object is only meaningful if the method is called when the Store
is in a transaction - such as in a TransactionListener
.
Example
This example makes changes to the Store
. At the end of the transaction, detail about what changed is enumerated.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setValue('open', false)
.finishTransaction(() => {
const [changedCells, changedValues] = store.getTransactionChanges();
console.log(changedCells);
console.log(changedValues);
});
// -> {pets: {fido: {color: 'black'}}}
// -> {open: false}
Since
v5.0.0
getTransactionLog
The getTransactionLog
method returns the changes that were made to a Store
during a transaction in more detail, including invalid changes, and what previous values were.
getTransactionLog(): TransactionLog
returns | TransactionLog | A |
---|
This is useful for deciding whether to rollback a transaction, for example. The returned object is only meaningful if the method is called when the Store
is in a transaction - such as in a TransactionListener
.
Example
This example makes changes to the Store
. At the end of the transaction, detail about what changed is enumerated.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob'])
.finishTransaction(() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
});
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
Since
v5.0.0
startTransaction
The startTransaction
method allows you to explicitly start a transaction that will make multiple mutations to the Store
, buffering all calls to the relevant listeners until it completes when you call the finishTransaction
method.
startTransaction(): this
returns | this | A reference to the |
---|
Transactions are useful for making bulk changes to the data in a Store
, and when you don't want listeners to be called as you make each change. Changes
are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction
method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction
methods for you. See that method for several transaction examples.
Use this startTransaction
method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. You must remember to also call the finishTransaction
method explicitly when it is done, of course.
Example
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row
listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
Since
v1.3.0
transaction
The transaction
method takes a function that makes multiple mutations to the Store
, buffering all calls to the relevant listeners until it completes.
transaction<Return>(
actions: () => Return,
doRollback?: DoRollback,
): Return
Type | Description | |
---|---|---|
actions | () => Return | The function to be executed as a transaction. |
doRollback? | DoRollback | An optional callback that should return |
returns | Return | Whatever value the provided transaction function returns. |
This method is useful for making bulk changes to the data in a Store
, and when you don't want listeners to be called as you make each change. Changes
are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
If multiple changes are made to a piece of Store
data throughout the transaction, a relevant listener will only be called with the final value (assuming it is different to the value at the start of the transaction), regardless of the changes that happened in between. For example, if a Cell
had a value 'a'
and then, within a transaction, it was changed to 'b'
and then 'c'
, any CellListener
registered for that cell would be called once as if there had been a single change from 'a'
to 'c'
.
Transactions can be nested. Relevant listeners will be called only when the outermost one completes.
The second, optional parameter, doRollback
is a DoRollback
callback that you can use to rollback the transaction if it did not complete to your satisfaction. See the DoRollback
documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction. In the second case, the Row
listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true),
);
// -> 'Fido changed'
This example makes multiple changes to one Cell
. The Cell
listener is called once - and with the final value - only if there is a net overall change.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell) => console.log(newCell),
);
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> 'walnut'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> undefined
// No net change during the transaction, so the listener is not called.
This example makes multiple changes to the Store
, including some attempts to update a Cell
and Value
with invalid values. The doRollback
callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.transaction(
() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob']),
() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
},
);
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.0.0
Deleter methods
This is the collection of deleter methods within the Store
interface. There are 9 deleter methods in total.
delTables
The delTables
method lets you remove all of the data in a Store
.
delTables(): this
returns | this | A reference to the |
---|
Example
This example removes the data of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.delTables();
console.log(store.getTables());
// -> {}
Since
v1.0.0
delTablesSchema
The delTablesSchema
method lets you remove the TablesSchema
of the Store
.
delTablesSchema(): this
returns | this | A reference to the |
---|
Example
This example removes the TablesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {species: {type: 'string'}},
});
store.delTablesSchema();
console.log(store.getTablesSchemaJson());
// -> '{}'
Since
v1.0.0
delTable
The delTable
method lets you remove a single Table
from the Store
.
delTable(tableId: string): this
Example
This example removes a Table
from a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.delTable('pets');
console.log(store.getTables());
// -> {species: {dog: {price: 5}}}
Since
v1.0.0
delRow
The delRow
method lets you remove a single Row
from a Table
.
delRow(
tableId: string,
rowId: string,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | this | A reference to the |
If this is the last Row
in its Table
, then that Table
will be removed.
Example
This example removes a Row
from a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat'}},
});
store.delRow('pets', 'fido');
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
delCell
The delCell
method lets you remove a single Cell
from a Row
.
delCell(
tableId: string,
rowId: string,
cellId: string,
forceDel?: boolean,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
forceDel? | boolean | An optional flag to indicate that the whole |
returns | this | A reference to the |
When there is no TablesSchema
applied to the Store
, then if this is the last Cell
in its Row
, then that Row
will be removed. If, in turn, that is the last Row
in its Table
, then that Table
will be removed.
If there is a TablesSchema
applied to the Store
and it specifies a default value for this Cell
, then deletion will result in it being set back to its default value. To override this, use the forceDel
parameter, as described below.
The forceDel
parameter is an optional flag that is only relevant if a TablesSchema
provides a default value for this Cell
. Under such circumstances, deleting a Cell
value will normally restore it to the default value. If this flag is set to true
, the complete removal of the Cell
is instead guaranteed. But since doing do so would result in an invalid Row
(according to the TablesSchema
), in fact the whole Row
is deleted to retain the integrity of the Table
. Therefore, this flag should be used with caution.
Examples
This example removes a Cell
from a Row
without a TablesSchema
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', sold: true}},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example removes a Cell
from a Row
with a TablesSchema
that defaults its value.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
This example removes a Cell
from a Row
with a TablesSchema
that defaults its value, but uses the forceDel
parameter to override it.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}, felix: {species: 'cat'}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold', true);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat', sold: false}}}
Since
v1.0.0
delValues
The delValues
method lets you remove all the Values
from a Store
.
delValues(): this
returns | this | A reference to the |
---|
If there is a ValuesSchema
applied to the Store
and it specifies a default value for any Value
Id
, then deletion will result in it being set back to its default value.
Examples
This example removes all Values
from a Store
without a ValuesSchema
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.delValues();
console.log(store.getValues());
// -> {}
This example removes all Values
from a Store
with a ValuesSchema
that defaults one of its values.
import {createStore} from 'tinybase';
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValues();
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
delValuesSchema
The delValuesSchema
method lets you remove the ValuesSchema
of the Store
.
delValuesSchema(): this
returns | this | A reference to the |
---|
Example
This example removes the ValuesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delValuesSchema();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
delValue
The delValue
method lets you remove a single Value
from a Store
.
delValue(valueId: string): this
If there is a ValuesSchema
applied to the Store
and it specifies a default value for this Value
Id
, then deletion will result in it being set back to its default value.
Examples
This example removes a Value
from a Store
without a ValuesSchema
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.delValue('employees');
console.log(store.getValues());
// -> {open: true}
This example removes a Value
from a Store
with a ValuesSchema
that defaults its value.
import {createStore} from 'tinybase';
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValue('open');
console.log(store.getValues());
// -> {open: false, employees: 3}
Since
v3.0.0
delSchema
The delSchema
method lets you remove both the TablesSchema
and ValuesSchema
of the Store
.
delSchema(): this
returns | this | A reference to the |
---|
Prior to v3.0, this method removed the TablesSchema
only.
Example
This example removes the TablesSchema
and ValuesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore()
.setTablesSchema({
pets: {species: {type: 'string'}},
})
.setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delSchema();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Since
v3.0.0
Development methods
This is the collection of development methods within the Store
interface. There is only one method, getListenerStats
.
getListenerStats
The getListenerStats
method provides a set of statistics about the listeners registered with the Store
, and is used for debugging purposes.
getListenerStats(): StoreListenerStats
returns | StoreListenerStats | A |
---|
The StoreListenerStats
object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a small and simple Store
.
import {createStore} from 'tinybase';
const store = createStore();
store.addTablesListener(() => console.log('Tables changed'));
store.addRowIdsListener(() => console.log('Row Ids changed'));
const listenerStats = store.getListenerStats();
console.log(listenerStats.rowIds);
// -> 1
console.log(listenerStats.tables);
// -> 1
Since
v1.0.0
Properties
There is one property, isMergeable
, within the Store
interface.
isMergeable
This will always be false for a Store
, and true for a MergeableStore
.
Since
v5.0.0
Functions
There is one function, createStore
, within the store
module.
createStore
The createStore
function creates a Store
, and is the main entry point into the store
module.
createStore(): Store
Since (or perhaps because) it is the most important function in the whole module, it is trivially simple.
Examples
This example creates a Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTables());
// -> {}
This example creates a Store
with some initial data:
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example creates a Store
with some initial data and a TablesSchema
:
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
See also
The Basics guides
Since
v1.0.0
Type Aliases
These are the type aliases within the store
module.
Listener type aliases
This is the collection of listener type aliases within the store
module. There are 28 listener type aliases in total.
TablesListener
The TablesListener
type describes a function that is used to listen to changes to the tabular part of the Store
.
(
store: Store,
getCellChange: GetCellChange | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | This has no return value. |
A TablesListener
is provided when using the addTablesListener
method. See that method for specific examples.
When called, a TablesListener
is given a reference to the Store
and a GetCellChange
function that can be used to query Cell
values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener
method rather than due to a real change in the Store
), the GetCellChange
function will not be present.
Since
v1.0.0
HasTablesListener
The HasTablesListener
type describes a function that is used to listen to Tables
as a whole being added to or removed from the Store
.
(
store: Store,
hasTables: boolean,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
hasTables | boolean | Whether |
returns | void | This has no return value. |
A HasTablesListener
is provided when using the addHasTablesListener
method. See that method for specific examples.
When called, a HasTablesListener
is given a reference to the Store
. It is also given a flag to indicate whether Tables
now exist (having not done previously), or do not (having done so previously).
Since
v4.4.0
TableIdsListener
The TableIdsListener
type describes a function that is used to listen to changes to the Table
Ids
in the Store
.
(
store: Store,
getIdChanges: GetIdChanges | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
returns | void | This has no return value. |
A TableIdsListener
is provided when using the addTableIdsListener
method. See that method for specific examples.
When called, a TableIdsListener
is given a reference to the Store
.
Since v3.3, the listener is also passed a GetIdChanges
function that can be used to query which Ids
changed during the transaction.
Since
v1.0.0
TableCellIdsListener
The TableCellIdsListener
type describes a function that is used to listen to changes to the Cell
Ids
that appear anywhere in a Table
.
(
store: Store,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
returns | void | This has no return value. |
A TableCellIdsListener
is provided when using the addTableCellIdsListener
method. See that method for specific examples.
When called, a TableCellIdsListener
is given a reference to the Store
, the Id
of the Table
whose Cell
Ids
changed, and a GetIdChanges
function that can be used to query which Ids
changed during the transaction.
Since
v3.3.0
TableListener
The TableListener
type describes a function that is used to listen to changes to a Table
.
(
store: Store,
tableId: Id,
getCellChange: GetCellChange | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | This has no return value. |
A TableListener
is provided when using the addTableListener
method. See that method for specific examples.
When called, a TableListener
is given a reference to the Store
, the Id
of the Table
that changed, and a GetCellChange
function that can be used to query Cell
values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener
method rather than due to a real change in the Store
), the GetCellChange
function will not be present.
Since
v1.0.0
HasTableCellListener
The HasTableCellListener
type describes a function that is used to listen to a Cell
being added to or removed from a Table
as a whole.
(
store: Store,
tableId: Id,
cellId: Id,
hasTableCell: boolean,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
cellId | Id | |
hasTableCell | boolean | |
returns | void | This has no return value. |
A HasTableCellListener
is provided when using the addHasTableCellListener
method. See that method for specific examples.
When called, a HasTableCellListener
is given a reference to the Store
, the Id
of the Table
that changed, and the Id
of the Cell
that changed. It is also given a flag to indicate whether the Cell
now exists anywhere in the Table
(having not done previously), or does not (having done so previously).
Since
v4.4.0
HasTableListener
The HasTableListener
type describes a function that is used to listen to a Table
being added to or removed from the Store
.
(
store: Store,
tableId: Id,
hasTable: boolean,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
hasTable | boolean | Whether the |
returns | void | This has no return value. |
A HasTableListener
is provided when using the addHasTableListener
method. See that method for specific examples.
When called, a HasTableListener
is given a reference to the Store
, and the Id
of the Table
that changed. It is also given a flag to indicate whether the Table
now exists (having not done previously), or does not (having done so previously).
Since
v4.4.0
RowIdsListener
The RowIdsListener
type describes a function that is used to listen to changes to the Row
Ids
in a Table
.
(
store: Store,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
returns | void | This has no return value. |
A RowIdsListener
is provided when using the addRowIdsListener
method. See that method for specific examples.
When called, a RowIdsListener
is given a reference to the Store
, and the Id
of the Table
whose Row
Ids
changed.
Since v3.3, the listener is also passed a GetIdChanges
function that can be used to query which Ids
changed during the transaction.
Since
v1.0.0
SortedRowIdsListener
The SortedRowIdsListener
type describes a function that is used to listen to changes to sorted Row
Ids
in a Table
.
(
store: Store,
tableId: Id,
cellId: Id | undefined,
descending: boolean,
offset: number,
limit: number | undefined,
sortedRowIds: Ids,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
cellId | Id | undefined | |
descending | boolean | Whether the sorting was in descending order. |
offset | number | |
limit | number | undefined | |
sortedRowIds | Ids | |
returns | void | This has no return value. |
A SortedRowIdsListener
is provided when using the addSortedRowIdsListener
method. See that method for specific examples.
When called, a SortedRowIdsListener
is given a reference to the Store
, the Id
of the Table
whose Row
Ids
sorting changed, the Cell
Id
being used to sort them, whether descending or not, and the offset and limit of the number of Ids
returned, for pagination purposes. It also receives the sorted array of Ids
itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Since
v2.0.0
HasRowListener
The HasRowListener
type describes a function that is used to listen to a Row
being added to or removed from the Store
.
(
store: Store,
tableId: Id,
rowId: Id,
hasRow: boolean,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
hasRow | boolean | Whether the |
returns | void | This has no return value. |
A HasRowListener
is provided when using the addHasRowListener
method. See that method for specific examples.
When called, a HasRowListener
is given a reference to the Store
, the Id
of the Table
that changed, and the Id
of the Row
that changed. It is also given a flag to indicate whether the Row
now exists (having not done previously), or does not (having done so previously).
Since
v4.4.0
RowCountListener
The RowCountListener
type describes a function that is used to listen to changes to the number of Row
objects in a Table
.
(
store: Store,
tableId: Id,
count: number,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
count | number | |
returns | void | This has no return value. |
A RowCountListener
is provided when using the addRowCountListener
method. See that method for specific examples.
When called, a RowCountListener
is given a reference to the Store
, the Id
of the Table
whose Row
Ids
changed, and the number of Row
objects in the Table
.
Since
v4.1.0
RowListener
The RowListener
type describes a function that is used to listen to changes to a Row
.
(
store: Store,
tableId: Id,
rowId: Id,
getCellChange: GetCellChange | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | This has no return value. |
A RowListener
is provided when using the addRowListener
method. See that method for specific examples.
When called, a RowListener
is given a reference to the Store
, the Id
of the Table
that changed, the Id
of the Row
that changed, and a GetCellChange
function that can be used to query Cell
values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener
method rather than due to a real change in the Store
), the GetCellChange
function will not be present.
Since
v1.0.0
CellIdsListener
The CellIdsListener
type describes a function that is used to listen to changes to the Cell
Ids
in a Row
.
(
store: Store,
tableId: Id,
rowId: Id,
getIdChanges: GetIdChanges | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
returns | void | This has no return value. |
A CellIdsListener
is provided when using the addCellIdsListener
method. See that method for specific examples.
When called, a CellIdsListener
is given a reference to the Store
, the Id
of the Table
that changed, and the Id
of the Row
whose Cell
Ids
changed.
Since v3.3, the listener is also passed a GetIdChanges
function that can be used to query which Ids
changed during the transaction.
Since
v1.0.0
CellChange
The CellChange
type describes a Cell
's changes during a transaction.
[changed: boolean, oldCell: CellOrUndefined, newCell: CellOrUndefined]
This is returned by the GetCellChange
function that is provided to every listener when called. This array contains the previous value of a Cell
before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v1.0.0
CellListener
The CellListener
type describes a function that is used to listen to changes to a Cell
.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
newCell: Cell,
oldCell: Cell,
getCellChange: GetCellChange | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
newCell | Cell | The new value of the |
oldCell | Cell | The old value of the |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | This has no return value. |
A CellListener
is provided when using the addCellListener
method. See that method for specific examples.
When called, a CellListener
is given a reference to the Store
, the Id
of the Table
that changed, the Id
of the Row
that changed, and the Id
of Cell
that changed. It is also given the new value of the Cell
, the old value of the Cell
, and a GetCellChange
function that can be used to query Cell
values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener
method rather than due to a real change in the Store
), the GetCellChange
function will not be present and the new and old values of the Cell
will be the same.
Since
v1.0.0
GetCellChange
The GetCellChange
type describes a function that returns information about any Cell
's changes during a transaction.
(
tableId: Id,
rowId: Id,
cellId: Id,
): CellChange
Type | Description | |
---|---|---|
tableId | Id | |
rowId | Id | |
cellId | Id | |
returns | CellChange |
A GetCellChange
function is provided to every listener when called due the Store
changing. The listener can then fetch the previous value of a Cell
before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v1.0.0
HasCellListener
The HasCellListener
type describes a function that is used to listen to a Cell
being added to or removed from the Store
.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
hasCell: boolean,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
hasCell | boolean | Whether the |
returns | void | This has no return value. |
A HasCellListener
is provided when using the addHasCellListener
method. See that method for specific examples.
When called, a HasCellListener
is given a reference to the Store
, the Id
of the Table
that changed, the Id
of the Row
that changed, and the Id
of Cell
that changed. It is also given a flag to indicate whether the Cell
now exists (having not done previously), or does not (having done so previously).
Since
v4.4.0
InvalidCellListener
The InvalidCellListener
type describes a function that is used to listen to attempts to set invalid data to a Cell
.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
invalidCells: any[],
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
invalidCells | any[] | An array of the values of the |
returns | void | This has no return value. |
An InvalidCellListener
is provided when using the addInvalidCellListener
method. See that method for specific examples.
When called, an InvalidCellListener
is given a reference to the Store
, the Id
of the Table
, the Id
of the Row
, and the Id
of Cell
that was being attempted to be changed. It is also given the invalid value of the Cell
, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell
within a single transaction, this is an array containing each attempt, chronologically.
Since
v1.1.0
ValuesListener
The ValuesListener
type describes a function that is used to listen to changes to all the Values
in a Store
.
(
store: Store,
getValueChange: GetValueChange | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
getValueChange | GetValueChange | undefined | A function that returns information about any |
returns | void | This has no return value. |
A ValuesListener
is provided when using the addValuesListener
method. See that method for specific examples.
When called, a ValuesListener
is given a reference to the Store
and a GetValueChange
function that can be used to query Values
before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener
method rather than due to a real change in the Store
), the GetValueChange
function will not be present.
Since
v1.0.0
HasValuesListener
The HasValuesListener
type describes a function that is used to listen to Values
as a whole being added to or removed from the Store
.
(
store: Store,
hasValues: boolean,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
hasValues | boolean | Whether |
returns | void | This has no return value. |
A HasValuesListener
is provided when using the addHasValuesListener
method. See that method for specific examples.
When called, a HasValuesListener
is given a reference to the Store
. It is also given a flag to indicate whether Values
now exist (having not done previously), or do not (having done so previously).
Since
v4.4.0
GetValueChange
The GetValueChange
type describes a function that returns information about any Value
's changes during a transaction.
(valueId: Id): ValueChange
Type | Description | |
---|---|---|
valueId | Id | |
returns | ValueChange |
A GetValueChange
function is provided to every listener when called due the Store
changing. The listener can then fetch the previous value of a Value
before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v1.0.0
HasValueListener
The HasValueListener
type describes a function that is used to listen to a Value
being added to or removed from the Store
.
(
store: Store,
valueId: Id,
hasValue: boolean,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
valueId | Id | |
hasValue | boolean | Whether the |
returns | void | This has no return value. |
A HasValueListener
is provided when using the addHasValueListener
method. See that method for specific examples.
When called, a HasValueListener
is given a reference to the Store
and the Id
of Value
that changed. It is also given a flag to indicate whether the Value
now exists (having not done previously), or does not (having done so previously).
Since
v4.4.0
InvalidValueListener
The InvalidValueListener
type describes a function that is used to listen to attempts to set invalid data to a Value
.
(
store: Store,
valueId: Id,
invalidValues: any[],
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
valueId | Id | |
invalidValues | any[] | An array of the |
returns | void | This has no return value. |
An InvalidValueListener
is provided when using the addInvalidValueListener
method. See that method for specific examples.
When called, an InvalidValueListener
is given a reference to the Store
and the Id
of Value
that was being attempted to be changed. It is also given the invalid value of the Value
, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Value
within a single transaction, this is an array containing each attempt, chronologically.
Since
v3.0.0
ValueChange
The ValueChange
type describes a Value
's changes during a transaction.
[changed: boolean, oldValue: ValueOrUndefined, newValue: ValueOrUndefined]
This is returned by the GetValueChange
function that is provided to every listener when called. This array contains the previous value of a Value
before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v1.0.0
ValueIdsListener
The ValueIdsListener
type describes a function that is used to listen to changes to the Value
Ids
in a Store
.
(
store: Store,
getIdChanges: GetIdChanges | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
returns | void | This has no return value. |
A ValueIdsListener
is provided when using the addValueIdsListener
method. See that method for specific examples.
When called, a ValueIdsListener
is given a reference to the Store
.
Since v3.3, the listener is also passed a GetIdChanges
function that can be used to query which Ids
changed during the transaction.
Since
v1.0.0
ValueListener
The ValueListener
type describes a function that is used to listen to changes to a Value
.
(
store: Store,
valueId: Id,
newValue: Value,
oldValue: Value,
getValueChange: GetValueChange | undefined,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
valueId | Id | |
newValue | Value | The new value of the |
oldValue | Value | The old value of the |
getValueChange | GetValueChange | undefined | A function that returns information about any |
returns | void | This has no return value. |
A ValueListener
is provided when using the addValueListener
method. See that method for specific examples.
When called, a ValueListener
is given a reference to the Store
and the Id
of Value
that changed. It is also given the new value of the Value
, the old value of the Value
, and a GetValueChange
function that can be used to query Values
before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener
method rather than due to a real change in the Store
), the GetValueChange
function will not be present and the new and old values of the Value
will be the same.
Since
v3.0.0
GetIdChanges
The GetIdChanges
type describes a function that returns information about the changes to a set of Ids
during a transaction.
(): {[id: Id]: 1 | -1}
returns | {[id: Id]: 1 | -1} |
---|
A GetIdChanges
function is provided to every listener when called due Ids
in the Store
changing. It returns an object where each key is an Id
that changed. The listener can then easily identify which Ids
have been added (those with the value 1
), and which have been removed (those with the value -1
).
Since
v3.3.0
TransactionListener
The TransactionListener
type describes a function that is used to listen to the completion of a transaction for the Store
.
(store: Store): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
returns | void | This has no return value. |
A TransactionListener
is provided when using the addWillFinishTransactionListener and addDidFinishTransactionListener
methods. See those methods for specific examples.
Since v5.0, this listener is called with no arguments other than the Store
. You can use the getTransactionChanges
method and getTransactionLog
method of the Store
directly to get information about the changes made within the transaction.
Since
v1.0.0
Callback type aliases
This is the collection of callback type aliases within the store
module. There are 9 callback type aliases in total.
TableCallback
The TableCallback
type describes a function that takes a Table
's Id
and a callback to loop over each Row
within it.
(
tableId: Id,
forEachRow: (rowCallback: RowCallback) => void,
): void
Type | Description | |
---|---|---|
tableId | Id | |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the |
returns | void | This has no return value. |
A TableCallback
is provided when using the forEachTable
method, so that you can do something based on every Table
in the Store
. See that method for specific examples.
Since
v1.0.0
TableCellCallback
The TableCellCallback
type describes a function that takes a Cell
's Id
and the count of times it appears across a whole Table
.
(
cellId: Id,
count: number,
): void
Type | Description | |
---|---|---|
cellId | Id | |
count | number | |
returns | void | This has no return value. |
A TableCellCallback
is provided when using the forEachTableCell
method, so that you can do something based on every Cell
used across a Table
. See that method for specific examples.
Since
v1.0.0
RowCallback
The RowCallback
type describes a function that takes a Row
's Id
and a callback to loop over each Cell
within it.
(
rowId: Id,
forEachCell: (cellCallback: CellCallback) => void,
): void
Type | Description | |
---|---|---|
rowId | Id | |
forEachCell | (cellCallback: CellCallback) => void | |
returns | void | This has no return value. |
A RowCallback
is provided when using the forEachRow
method, so that you can do something based on every Row
in a Table
. See that method for specific examples.
Since
v1.0.0
CellCallback
The CellCallback
type describes a function that takes a Cell
's Id
and its value.
(
cellId: Id,
cell: Cell,
): void
A CellCallback
is provided when using the forEachCell
method, so that you can do something based on every Cell
in a Row
. See that method for specific examples.
Since
v1.0.0
GetCell
The GetCell
type describes a function that takes a Id
and returns the Cell
value for a particular Row
.
(cellId: Id): CellOrUndefined
Type | Description | |
---|---|---|
cellId | Id | |
returns | CellOrUndefined |
A GetCell
can be provided when setting definitions, as in the setMetricDefinition
method of a Metrics
object, or the setIndexDefinition
method of an Indexes
object. See those methods for specific examples.
Since
v1.0.0
MapCell
The MapCell
type describes a function that takes an existing Cell
value and returns another.
(cell: CellOrUndefined): Cell
Type | Description | |
---|---|---|
cell | CellOrUndefined | The current value of the |
returns | Cell |
A MapCell
can be provided in the setCell
method to map an existing value to a new one, such as when incrementing a number. See that method for specific examples.
Since
v1.0.0
MapValue
The MapValue
type describes a function that takes an existing Value
and returns another.
(value: ValueOrUndefined): Value
Type | Description | |
---|---|---|
value | ValueOrUndefined | |
returns | Value |
A MapValue
can be provided in the setValue
method to map an existing Value
to a new one, such as when incrementing a number. See that method for specific examples.
Since
v3.0.0
ValueCallback
The ValueCallback
type describes a function that takes a Value
's Id
and its actual value.
(
valueId: Id,
value: Value,
): void
A ValueCallback
is provided when using the forEachValue
method, so that you can do something based on every Value
in a Store
. See that method for specific examples.
Since
v3.0.0
DoRollback
The DoRollback
type describes a function that you can use to rollback the transaction if it did not complete to your satisfaction.
(store: Store): boolean
A DoRollback
can be provided when calling the transaction
method or the finishTransaction
method. See those methods for specific examples.
Since v5.0, this function is called with the Store
as a single argument. You can use the getTransactionChanges
method and getTransactionLog
method of the Store
directly to decide whether to do the rollback.
Since
v1.0.0
Schema type aliases
This is the collection of schema type aliases within the store
module. There are 10 schema type aliases in total.
TablesSchema
The TablesSchema
type describes the tabular structure of a Store
in terms of valid Table
Ids
and the types of Cell
that can exist within them.
{[tableId: Id]: {[cellId: Id]: CellSchema}}
A TablesSchema
comprises a JavaScript object describing each Table
, in turn a nested JavaScript object containing information about each Cell
and its CellSchema
. It is provided to the setTablesSchema
method.
Example
When applied to a Store
, this TablesSchema
only allows one Table
called pets
, in which each Row
may contain a string species
Cell
, and is guaranteed to contain a boolean sold
Cell
that defaults to false
.
import type {TablesSchema} from 'tinybase';
export const tableSchema: TablesSchema = {
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
};
Since
v1.0.0
NoTablesSchema
The NoTablesSchema
type is a TablesSchema
-like type for when one has not been provided.
{[tableId: Id]: {[cellId: Id]: {type: "any"}}}
Since
v1.0.0
OptionalTablesSchema
The OptionalTablesSchema
type is used by generic types that can optionally take a TablesSchema
.
TablesSchema | NoTablesSchema
Since
v1.0.0
CellSchema
The CellSchema
type describes what values are allowed for each Cell
in a Table
.
{
type: "string";
default?: string;
} | {
type: "number";
default?: number;
} | {
type: "boolean";
default?: boolean;
}
A CellSchema
specifies the type of the Cell
(string
, boolean
, or number
), and what the default value can be when an explicit value is not specified.
If a default value is provided (and its type is correct), you can be certain that that Cell
will always be present in a Row
.
If the default value is not provided (or its type is incorrect), the Cell
may be missing from the Row
, but when present you can be guaranteed it is of the correct type.
Example
When applied to a Store
, this CellSchema
ensures a boolean Cell
is always present, and defaults it to false
.
import type {CellSchema} from 'tinybase';
export const requiredBoolean: CellSchema = {
type: 'boolean',
default: false,
};
Since
v1.0.0
ValuesSchema
The ValuesSchema
type describes the keyed Values
that can be set in a Store
and their types.
{[valueId: Id]: ValueSchema}
A ValuesSchema
comprises a JavaScript object describing each Value
and its ValueSchema
. It is provided to the setValuesSchema
method.
Example
When applied to a Store
, this ValuesSchema
only allows one boolean Value
called open
, that defaults to false
.
import type {ValuesSchema} from 'tinybase';
export const valuesSchema: ValuesSchema = {
open: {type: 'boolean', default: false},
};
Since
v3.0.0
NoValuesSchema
The NoValuesSchema
type is a ValuesSchema
-like type for when one has not been provided.
{[valueId: Id]: {type: "any"}}
Since
v1.0.0
OptionalValuesSchema
The OptionalValuesSchema
type is used by generic types that can optionally take a ValuesSchema
.
ValuesSchema | NoValuesSchema
Since
v1.0.0
ValueSchema
The ValueSchema
type describes what values are allowed for keyed Values
in a Store
.
{
type: "string";
default?: string;
} | {
type: "number";
default?: number;
} | {
type: "boolean";
default?: boolean;
}
A ValueSchema
specifies the type of the Value
(string
, boolean
, or number
), and what the default value can be when an explicit value is not specified.
If a default value is provided (and its type is correct), you can be certain that the Value
will always be present in a Store
.
If the default value is not provided (or its type is incorrect), the Value
may not be present in the Store
, but when present you can be guaranteed it is of the correct type.
Example
When applied to a Store
, this ValueSchema
ensures a boolean Value
is always present, and defaults it to false
.
import type {ValueSchema} from 'tinybase';
export const requiredBoolean: ValueSchema = {
type: 'boolean',
default: false,
};
Since
v3.0.0
NoSchemas
The NoSchemas
type is used as a default by generic types that can optionally take either or both of a TablesSchema
and ValuesSchema
.
[NoTablesSchema, NoValuesSchema]
Since
v1.0.0
OptionalSchemas
The OptionalSchemas
type is used by generic types that can optionally take either or both of a TablesSchema
and ValuesSchema
.
[OptionalTablesSchema, OptionalValuesSchema]
Since
v1.0.0
Store type aliases
This is the collection of store type aliases within the store
module. There are 9 store type aliases in total.
Tables
The Tables
type is the data structure representing all of the data in a Store
.
{[tableId: Id]: Table}
A Tables
object is used when setting all of the tables together with the setTables
method, and when getting them back out again with the getTables
method. A Tables
object is a regular JavaScript object containing individual Table
objects, keyed by their Id
.
Example
import type {Tables} from 'tinybase';
export const tables: Tables = {
pets: {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
},
species: {
dog: {price: 5},
cat: {price: 4},
},
};
Since
v1.0.0
Table
The Table
type is the data structure representing the data in a single table.
{[rowId: Id]: Row}
A Table
is used when setting a table with the setTable
method, and when getting it back out again with the getTable
method. A Table
object is a regular JavaScript object containing individual Row
objects, keyed by their Id
.
Example
import type {Table} from 'tinybase';
export const table: Table = {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
};
Since
v1.0.0
Row
The Row
type is the data structure representing the data in a single row.
{[cellId: Id]: Cell}
A Row
is used when setting a row with the setRow
method, and when getting it back out again with the getRow
method. A Row
object is a regular JavaScript object containing individual Cell
objects, keyed by their Id
.
Example
import type {Row} from 'tinybase';
export const row: Row = {species: 'dog', color: 'brown'};
Since
v1.0.0
Cell
The Cell
type is the data structure representing the data in a single cell.
string | number | boolean
A Cell
is used when setting a cell with the setCell
method, and when getting it back out again with the getCell
method. A Cell
is a JavaScript string, number, or boolean.
Example
import type {Cell} from 'tinybase';
export const cell: Cell = 'dog';
Since
v1.0.0
CellOrUndefined
The CellOrUndefined
type is a data structure representing the data in a single cell, or the value undefined
.
Cell | undefined
This is used when describing a Cell
that is present or that is not present, such as when it has been deleted, or when describing a previous state where the Cell
value has since been added.
Since
v1.0.0
Values
The Values
type is the data structure representing all the keyed values in a Store
.
{[valueId: Id]: Value}
A Values
object is used when setting values with the setValues
method, and when getting them back out again with the getValues
method. A Values
object is a regular JavaScript object containing individual Value
objects, keyed by their Id
.
Example
import type {Values} from 'tinybase';
export const values: Values = {open: true, employees: 4};
Since
v3.0.0
Value
The Value
type is the data structure representing the data in a single keyed value.
string | number | boolean
A Value
is used when setting a value with the setValue
method, and when getting it back out again with the getValue
method. A Value
is a JavaScript string, number, or boolean.
Example
import type {Value} from 'tinybase';
export const value: Value = 'dog';
Since
v3.0.0
ValueOrUndefined
The ValueOrUndefined
type is a data structure representing the data in a single value, or the value undefined
.
Value | undefined
This is used when describing a Value
that is present or that is not present, such as when it has been deleted, or when describing a previous state where the Value
has since been added.
Since
v3.0.0
Content
The Content
type describes both the Tables
and Values
in a Store
.
[Tables, Values]
It is an array of two objects, representing tabular and keyed value content.
Example
The following is a valid Content
array:
[
{
"pets": {
"fido": {
"sold": false,
"price": 4,
},
"felix": {
"sold": true,
"price": 5,
},
},
},
{
open: true,
employees: 3,
},
]
Since
v5.0.0
Transaction type aliases
This is the collection of transaction type aliases within the store
module. There are 13 transaction type aliases in total.
ChangedTableIds
The ChangedTableIds
type describes the Table
Ids
that were added or removed during a transaction.
{[tableId: Id]: IdAddedOrRemoved}
It is available to the DoRollback
function and to a TransactionListener
function via the TransactionLog
object.
It is a simple object that has Table
Id
as key, and an IdAddedOrRemoved
number indicating whether the Table
Id
was added (1) or removed (-1).
Note that there will be no entry if the content of the Table
itself changed. For that you should consult the ChangedRowIds
, ChangedCellIds
, or ChangedCells
types.
Since
v4.0.0
ChangedRowIds
The ChangedRowIds
type describes the Row
Ids
that were added or removed during a transaction.
{[tableId: Id]: {[rowId: Id]: IdAddedOrRemoved}}
It is available to the DoRollback
function and to a TransactionListener
function via the TransactionLog
object.
It is a nested object that has Table
Id
as a top-level key, and then Row
Id
as an inner key. The values of the inner objects are IdAddedOrRemoved
numbers indicating whether the Row
Id
was added (1) or removed (-1) to the given Table
.
Note that there will be no entry if the content of the Row
itself changed. For that you should consult the ChangedCellIds
or ChangedCells
types.
Since
v4.0.0
ChangedCellIds
The ChangedCellIds
type describes the Cell
Ids
that were added or removed during a transaction.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: IdAddedOrRemoved}}}
It is available to the DoRollback
function and to a TransactionListener
function via the TransactionLog
object.
It is a nested object that has Table
Id
as a top-level key, and Row
Id
, and then CellId as inner keys. The values of the inner objects are IdAddedOrRemoved
numbers indicating whether the Cell
Id
was added (1) or removed (-1) to the given Row
.
Note that there will be no entry if the content of the Cell
itself changed. For that you should consult the ChangedCells
type.
Since
v4.0.0
ChangedCell
The ChangedCell
type describes a Cell
that has been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
[oldCell: CellOrUndefined, newCell: CellOrUndefined]
It provides both the old and new Cell
values in a two-part array. These are describing the state of the changed Cell
in the Store
at the start of the transaction, and by the end of the transaction.
Hence, an undefined
value for the first item in the array means that the Cell
was added during the transaction. An undefined
value for the second item in the array means that the Cell
was removed during the transaction. An array with two different Cell
values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined
values), even if, during the transaction, a Cell
was changed to a different value and then changed back.
Since
v1.2.0
ChangedCells
The ChangedCells
type describes the Cell
values that have been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: ChangedCell}}}
A ChangedCells
object is provided to the doRollback
callback when using the transaction
method and the finishTransaction
method. See those methods for specific examples.
This type is a nested structure of Table
, Row
, and Cell
objects, much like the Tables
object, but one which provides both the old and new Cell
values in a two-part array. These are describing the state of each changed Cell
in Store
at the start of the transaction, and by the end of the transaction.
Hence, an undefined
value for the first item in the array means that the Cell
was added during the transaction. An undefined
value for the second item in the array means that the Cell
was removed during the transaction. An array with two different Cell
values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined
values), even if, during the transaction, a Cell
was changed to a different value and then changed back.
Since
v1.2.0
InvalidCells
The InvalidCells
type describes the invalid Cell
values that have been attempted during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: any[]}}}
An InvalidCells
object is provided to the doRollback
callback when using the transaction
method and the finishTransaction
method. See those methods for specific examples.
This type is a nested structure of Table
, Row
, and Cell
objects, much like the Tables
object, but one for which Cell
values are listed in array (much like the InvalidCellListener
type) so that multiple failed attempts to change a Cell
during the transaction are described.
Since
v1.2.0
ChangedValues
The ChangedValues
type describes the Values
that have been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[valueId: Id]: ChangedValue}
A ChangedValues
object is provided to the doRollback
callback when using the transaction
method and the finishTransaction
method. See those methods for specific examples.
This type is an object containing the old and new Values
in two-part arrays. These are describing the state of each changed Value
in Store
at the start of the transaction, and by the end of the transaction.
Hence, an undefined
value for the first item in the array means that the Value
was added during the transaction. An undefined
value for the second item in the array means that the Value
was removed during the transaction. An array with two different Values
indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined
values), even if, during the transaction, a Value
was changed to a different value and then changed back.
Since
v3.0.0
InvalidValues
The InvalidValues
type describes the invalid Values
that have been attempted during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[valueId: Id]: any[]}
An InvalidValues
object is provided to the doRollback
callback when using the transaction
method and the finishTransaction
method. See those methods for specific examples.
This type is an object containing each invalid Value
's attempt listed in array (much like the InvalidValueListener
type) so that multiple failed attempts to change a Value
during the transaction are described.
Since
v3.0.0
ChangedValue
The ChangedValue
type describes a Value
that has been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
[oldValue: ValueOrUndefined, newValue: ValueOrUndefined]
It provides both the the old and new Values
in a two-part array. These describe the state of the changed Value
in the Store
at the start of the transaction, and by the end of the transaction.
Hence, an undefined
value for the first item in the array means that the Value
was added during the transaction. An undefined
value for the second item in the array means that the Value
was removed during the transaction. An array with two different Values
indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined
values), even if, during the transaction, a Value
was changed to a different value and then changed back.
Since
v3.0.0
ChangedValueIds
The ChangedValueIds
type describes the Value
Ids
that were added or removed during a transaction.
{[valueId: Id]: IdAddedOrRemoved}
It is available to the DoRollback
function and to a TransactionListener
function via the TransactionLog
object.
It is a simple object that has Value
Id
as key, and an IdAddedOrRemoved
number indicating whether the Value
Id
was added (1) or removed (-1).
Note that there will be no entry if the content of the Value
itself changed. For that you should consult the ChangedValues
type.
Since
v4.0.0
Changes
The Changes
type describes the net meaningful changes that were made to a Store
during a transaction.
[changedTables: {[tableId: Id]: {[rowId: Id]: {[cellId: Id]: CellOrUndefined} | undefined} | undefined}, changedValues: {[valueId: Id]: ValueOrUndefined}, isChanges: 1]
This contains mostly equivalent information to a TransactionLog
, but in a form that can be more efficiently parsed and serialized (for example in the case of synchronization between systems).
It is an array of two objects, representing tabular and keyed value changes. If the first item is an empty object, it means no tabular changes were made. If the second item is an empty object, it means no keyed value changes were made.
If not empty, the first object has an entry for each Table
in a Store
that has had a change within it. If the entry is null, it means that whole Table
was deleted. Otherwise, the entry will be an object with an entry for each Row
in that Table
that had a change within it. In turn, if that entry is null, it means the Row
was deleted. Otherwise, the entry will be an object with an entry for each Cell
in that Row
that had a change within it. If the entry is null, the Cell
was deleted, otherwise it will contain the new value the Cell
was changed to during the transaction.
If not empty, the second object has an entry for each Value
in a Store
that has had a change. If the entry is null, the Value
was deleted, otherwise it will contain the new Value
it was changed to during the transaction.
A third, required, item in the array is the digit 1
, so that instances of Content
and Changes
types can be disambiguated.
Example
The following is a valid Changes
array that conveys the following:
[
{ // changes to tabular data in the Store
"pets": { // this Table was changed
"fido": null, // this Row was deleted
"felix": { // this Row was changed
"sold": true, // this Cell was changed
"price": null, // this Cell was deleted
},
},
"pendingSales": null, // this Table was deleted
},
{}, // no changes to keyed value data in the Store
1, // indicates that this is a Changes array
]
Since
v4.0.0
IdAddedOrRemoved
The IdAddedOrRemoved
type describes a change made to an Id
in either the tabular of keyed-value part of the Store
.
1 | -1
This type is used in other types like ChangedTableIds
, ChangedRowIds
, ChangedCellIds
, and ChangedValueIds
.
It is a simple number: a 1 indicates that a given Id
was added to the Store
during a transaction, and a -1 indicates that it was removed.
Since
v4.0.0
TransactionLog
The TransactionLog
type describes the changes that were made to a Store
during a transaction in detail.
[cellsTouched: boolean, valuesTouched: boolean, changedCells: ChangedCells, invalidCells: InvalidCells, changedValues: ChangedValues, invalidValues: InvalidValues, changedTableIds: ChangedTableIds, changedRowIds: ChangedRowIds, changedCellIds: ChangedCellIds, changedValueIds: ChangedValueIds]
This contains equivalent information to a Changes
object, but also information about what the previous state of the Store
was. The changedCells and changedValues entries contain information about all changes to those parts of the Store
, with their before and after values, for example.
cellsTouched
and valuesTouched
indicate whether Cell
or Value
data has been touched during the transaction. The two flags are intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell
or Value
data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched
and valuesTouched
in the listener will be false
because all changes have been reverted.
In v5.0, this type changed from an object to an array, but still contains the same values.
See the documentation for the types of the inner objects for other details.
Since
v4.0.0
Development type aliases
This is the collection of development type aliases within the store
module. There is only one type alias, StoreListenerStats
.
StoreListenerStats
The StoreListenerStats
type describes the number of listeners registered with the Store
, and can be used for debugging purposes.
{
hasTables: number;
tables: number;
tableIds: number;
hasTable: number;
table: number;
tableCellIds: number;
hasTableCell: number;
rowCount: number;
rowIds: number;
sortedRowIds: number;
hasRow: number;
row: number;
cellIds: number;
hasCell: number;
cell: number;
invalidCell: number;
hasValues: number;
values: number;
valueIds: number;
hasValue: number;
value: number;
invalidValue: number;
transaction: number;
}
Type | Description | |
---|---|---|
hasTables | number | The number of |
tables | number | The number of |
tableIds | number | The number of |
hasTable | number | The number of |
table | number | The number of |
tableCellIds | number | The number of |
hasTableCell | number | The number of |
rowCount | number | The number of |
rowIds | number | The number of |
sortedRowIds | number | The number of |
hasRow | number | The number of |
row | number | The number of |
cellIds | number | The number of |
hasCell | number | The number of |
cell | number | The number of |
invalidCell | number | The number of |
hasValues | number | The number of |
values | number | The number of |
valueIds | number | The number of |
hasValue | number | The number of |
value | number | The number of |
invalidValue | number | The number of |
transaction | number | The number of |
The StoreListenerStats
object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners. A StoreListenerStats
object is returned from the getListenerStats
method.
Since
v1.0.0
mergeable-store
The mergeable-store
module contains the types, interfaces, and functions to work with MergeableStore
objects, which provide merge and synchronization functionality.
The main entry point to this module is the createMergeableStore
function, which returns a new MergeableStore
, a subtype of Store
that can be merged with another with deterministic results.
Please be aware that a lot of the types and methods exposed by this module are used internally within TinyBase itself (in particular the Synchronizer
framework). They're documented here, but mostly for interest, and it is generally assumed that they won't be called directly by applications.
As an application developer, it's more likely that you will continue to use the main Store
methods for reading, writing, and listening to data, and rely on Synchronizer
instances to keep the data in step with other places.
Since
v5.0.0
Interfaces
There is one interface, MergeableStore
, within the mergeable-store
module.
MergeableStore
The MergeableStore
type represents a Store
that carries with it sufficient metadata to be able to be merged with another MergeableStore
with deterministic results.
This is the key data type used when you need TinyBase data to be cleanly synchronized or merged with data elsewhere on the system, or on another system. It acts as a Conflict-Free Replicated Data Type (CRDT) which allows deterministic disambiguation of how changes to different instances should be merged.
Please be aware that a lot of the methods exposed by this interface are used internally within TinyBase itself (in particular the Synchronizer
framework). They're documented here, but mostly for interest, and it is generally assumed that they won't be called directly by applications.
As an application developer, it's more likely that you will continue to use the main Store
methods for reading, writing, and listening to data, and rely on Synchronizer
instances to keep the data in step with other places.
One possible exceptions is the merge
method, which can be used to simply merge two co-located MergeableStore
instances together.
Example
This example shows very simple usage of the MergeableStore
: whereby two are created, updated with different data, and then merged with one another.
import {createMergeableStore} from 'tinybase';
const localStore1 = createMergeableStore();
const localStore2 = createMergeableStore();
localStore1.setCell('pets', 'fido', 'color', 'brown');
localStore2.setCell('pets', 'felix', 'color', 'black');
localStore1.merge(localStore2);
console.log(localStore1.getContent());
// -> [{pets: {felix: {color: 'black'}, fido: {color: 'brown'}}}, {}]
console.log(localStore2.getContent());
// -> [{pets: {felix: {color: 'black'}, fido: {color: 'brown'}}}, {}]
Since
v5.0.0
Methods
These are the methods within the MergeableStore
interface.
Getter methods
This is the collection of getter methods within the MergeableStore
interface. There are 30 getter methods in total.
getTables
The getTables
method returns a Tables
object containing the entire tabular data of the Store
.
getTables(): Tables
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store
itself.
Examples
This example retrieves the tabular data in a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example retrieves the Tables
of an empty Store
, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTables());
// -> {}
Since
v1.0.0
getTablesJson
The getTablesJson
method returns a string serialization of all of the Tables
in the Store
.
getTablesJson(): string
Examples
This example serializes the contents of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTablesJson());
// -> '{"pets":{"fido":{"species":"dog"}}}'
This example serializes the contents of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTablesJson());
// -> '{}'
Since
v3.0.0
getTablesSchemaJson
The getTablesSchemaJson
method returns a string serialization of the TablesSchema
of the Store
.
getTablesSchemaJson(): string
returns | string | A string serialization of the |
---|
If no TablesSchema
has been set on the Store
(or if it has been removed with the delTablesSchema
method), then it will return the serialization of an empty object, {}
.
Examples
This example serializes the TablesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean'},
},
});
console.log(store.getTablesSchemaJson());
// -> '{"pets":{"species":{"type":"string"},"sold":{"type":"boolean"}}}'
This example serializes the TablesSchema
of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTablesSchemaJson());
// -> '{}'
Since
v3.0.0
hasTables
The hasTables
method returns a boolean indicating whether any Table
objects exist in the Store
.
hasTables(): boolean
returns | boolean | Whether any |
---|
Example
This example shows simple existence checks.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.hasTables());
// -> false
store.setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTables());
// -> true
Since
v1.0.0
hasTablesSchema
The hasTablesSchema
method returns a boolean indicating whether the Store
currently has a TablesSchema
applied to it.
hasTablesSchema(): boolean
returns | boolean | Whether the |
---|
Example
This example sets a TablesSchema
and checks that it is present.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
price: {type: 'number'},
},
});
console.log(store.hasTablesSchema());
// -> true
store.delTablesSchema();
console.log(store.hasTablesSchema());
// -> false
Since
v4.1.1
getTableIds
The getTableIds
method returns the Ids
of every Table
in the Store
.
getTableIds(): Ids
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Table
Ids
in a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTableIds());
// -> ['pets', 'species']
This example retrieves the Table
Ids
of an empty Store
, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getTableIds());
// -> []
Since
v1.0.0
getTable
The getTable
method returns an object containing the entire data of a single Table
in the Store
.
getTable(tableId: string): Table
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store
itself.
Examples
This example retrieves the data in a single Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTable('pets'));
// -> {fido: {species: 'dog'}}
This example retrieves a Table
that does not exist, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTable('employees'));
// -> {}
Since
v1.0.0
getTableCellIds
The getTableCellIds
method returns the Ids
of every Cell
used across the whole Table
.
getTableCellIds(tableId: string): Ids
Type | Description | |
---|---|---|
tableId | string | |
returns | Ids | An array of the |
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Cell
Ids
used across a whole Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', legs: 4},
cujo: {dangerous: true},
},
});
console.log(store.getTableCellIds('pets'));
// -> ['species', 'color', 'legs', 'dangerous']
This example retrieves the Cell
Ids
used across a Table
that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTableCellIds('species'));
// -> []
Since
v3.3.0
hasTable
The hasTable
method returns a boolean indicating whether a given Table
exists in the Store
.
hasTable(tableId: string): boolean
Type | Description | |
---|---|---|
tableId | string | |
returns | boolean |
Example
This example shows two simple Table
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTable('pets'));
// -> true
console.log(store.hasTable('employees'));
// -> false
Since
v1.0.0
hasTableCell
The hasTableCell
method returns a boolean indicating whether a given Cell
exists anywhere in a Table
, not just in a specific Row
.
hasTableCell(
tableId: string,
cellId: string,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
cellId | string | |
returns | boolean |
Example
This example shows two simple Cell
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {legs: 4}},
});
console.log(store.hasTableCell('pets', 'species'));
// -> true
console.log(store.hasTableCell('pets', 'legs'));
// -> true
console.log(store.hasTableCell('pets', 'color'));
// -> false
Since
v3.3.0
getRowIds
The getRowIds
method returns the Ids
of every Row
in a given Table
.
getRowIds(tableId: string): Ids
Type | Description | |
---|---|---|
tableId | string | |
returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Row
Ids
in a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowIds('pets'));
// -> ['fido', 'felix']
This example retrieves the Row
Ids
of a Table
that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowIds('employees'));
// -> []
Since
v1.0.0
getSortedRowIds
The getSortedRowIds
method returns the Ids
of every Row
in a given Table
, sorted according to the values in a specified Cell
.
getSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
returns | Ids |
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the Table
is large. For a performant approach to tracking the sorted Row
Ids
when they change, use the addSortedRowIdsListener
method.
If the Table
does not exist, an empty array is returned.
Examples
This example retrieves sorted Row
Ids
in a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species'));
// -> ['felix', 'fido']
This example retrieves sorted Row
Ids
in a Table
in reverse order.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets', 'species', true));
// -> ['cujo', 'fido', 'felix']
This example retrieves two pages of Row
Ids
in a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
console.log(store.getSortedRowIds('pets', 'price', false, 0, 2));
// -> ['lowly', 'mickey']
console.log(store.getSortedRowIds('pets', 'price', false, 2, 2));
// -> ['carnaby', 'tom']
This example retrieves Row
Ids
sorted by their own value, since the cellId
parameter is undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets'));
// -> ['cujo', 'felix', 'fido']
This example retrieves the sorted Row
Ids
of a Table
that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getSortedRowIds('employees'));
// -> []
Since
v2.0.0
getRow
The getRow
method returns an object containing the entire data of a single Row
in a given Table
.
getRow(
tableId: string,
rowId: string,
): Row
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store
itself.
Examples
This example retrieves the data in a single Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
This example retrieves a Row
that does not exist, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'felix'));
// -> {}
Since
v1.0.0
getRowCount
The getRowCount
method returns the count of the Row
objects in a given Table
.
getRowCount(tableId: string): number
Type | Description | |
---|---|---|
tableId | string | |
returns | number |
While this provides the same result as the length of Ids
array returned from the getRowIds
method, it is somewhat faster, and useful for efficient pagination.
Examples
This example retrieves the number of Row
objects in the Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowCount('pets'));
// -> 2
This example retrieves the Row
Ids
of a Table
that does not exist, returning zero.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowCount('employees'));
// -> 0
Since
v4.1.0
hasRow
The hasRow
method returns a boolean indicating whether a given Row
exists in the Store
.
hasRow(
tableId: string,
rowId: string,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | boolean |
Example
This example shows two simple Row
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasRow('pets', 'fido'));
// -> true
console.log(store.hasRow('pets', 'felix'));
// -> false
Since
v1.0.0
getCellIds
The getCellIds
method returns the Ids
of every Cell
in a given Row
in a given Table
.
getCellIds(
tableId: string,
rowId: string,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Cell
Ids
in a Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
},
});
console.log(store.getCellIds('pets', 'fido'));
// -> ['species', 'color']
This example retrieves the Cell
Ids
of a Row
that does not exist, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCellIds('pets', 'felix'));
// -> []
Since
v1.0.0
getCell
The getCell
method returns the value of a single Cell
in a given Row
, in a given Table
.
getCell(
tableId: string,
rowId: string,
cellId: string,
): CellOrUndefined
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
returns | CellOrUndefined | The value of the |
Examples
This example retrieves a single Cell
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
console.log(store.getCell('pets', 'fido', 'species'));
// -> 'dog'
This example retrieves a Cell
that does not exist, returning undefined
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCell('pets', 'fido', 'color'));
// -> undefined
Since
v1.0.0
hasCell
The hasCell
method returns a boolean indicating whether a given Cell
exists in a given Row
in a given Table
.
hasCell(
tableId: string,
rowId: string,
cellId: string,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
returns | boolean | Whether a |
Example
This example shows two simple Cell
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasCell('pets', 'fido', 'species'));
// -> true
console.log(store.hasCell('pets', 'fido', 'color'));
// -> false
Since
v1.0.0
getValues
The getValues
method returns an object containing the entire set of keyed Values
in the Store
.
getValues(): Values
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store
itself.
Examples
This example retrieves the set of keyed Values
in the Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example retrieves Values
from a Store
that has none, returning an empty object.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValues());
// -> {}
Since
v3.0.0
getValuesJson
The getValuesJson
method returns a string serialization of all of the keyed Values
in the Store
.
getValuesJson(): string
Examples
This example serializes the keyed value contents of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
console.log(store.getValuesJson());
// -> '{"open":true}'
This example serializes the contents of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValuesJson());
// -> '{}'
Since
v3.0.0
getValuesSchemaJson
The getValuesSchemaJson
method returns a string serialization of the ValuesSchema
of the Store
.
getValuesSchemaJson(): string
returns | string | A string serialization of the |
---|
If no ValuesSchema
has been set on the Store
(or if it has been removed with the delValuesSchema
method), then it will return the serialization of an empty object, {}
.
Examples
This example serializes the ValuesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
console.log(store.getValuesSchemaJson());
// -> '{"open":{"type":"boolean","default":false}}'
This example serializes the ValuesSchema
of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
hasValues
The hasValues
method returns a boolean indicating whether any Values
exist in the Store
.
hasValues(): boolean
returns | boolean | Whether any |
---|
Example
This example shows simple existence checks.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.hasValues());
// -> false
store.setValues({open: true});
console.log(store.hasValues());
// -> true
Since
v3.0.0
hasValuesSchema
The hasValuesSchema
method returns a boolean indicating whether the Store
currently has a ValuesSchema
applied to it.
hasValuesSchema(): boolean
returns | boolean | Whether the |
---|
Example
This example sets a ValuesSchema
and checks that it is present.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({open: {type: 'boolean'}});
console.log(store.hasValuesSchema());
// -> true
store.delValuesSchema();
console.log(store.hasValuesSchema());
// -> false
Since
v4.1.1
getValue
The getValue
method returns a single keyed Value
in the Store
.
getValue(valueId: string): ValueOrUndefined
Type | Description | |
---|---|---|
valueId | string | |
returns | ValueOrUndefined | The |
Examples
This example retrieves a single Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('employees'));
// -> 3
This example retrieves a Value
that does not exist, returning undefined
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('website'));
// -> undefined
Since
v3.0.0
getValueIds
The getValueIds
method returns the Ids
of every Value
in a Store
.
getValueIds(): Ids
Note that this returns a copy of, rather than a reference, to the list of Ids
, so changes made to the list are not made to the Store
itself.
Examples
This example retrieves the Value
Ids
in a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValueIds());
// -> ['open', 'employees']
This example retrieves the Value
Ids
of a Store
that has had none set, returning an empty array.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getValueIds());
// -> []
Since
v3.0.0
hasValue
The hasValue
method returns a boolean indicating whether a given Value
exists in the Store
.
hasValue(valueId: string): boolean
Type | Description | |
---|---|---|
valueId | string | |
returns | boolean |
Example
This example shows two simple Value
existence checks.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
console.log(store.hasValue('open'));
// -> true
console.log(store.hasValue('employees'));
// -> false
Since
v3.0.0
getContent
The getContent
method returns a Tables
object and a Values
object in an array, representing the entire content of the Store
.
getContent(): Content
Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned objects are not made to the Store
itself.
Examples
This example retrieves the content of a Store
.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true, employees: 3});
console.log(store.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true, employees: 3}]
This example retrieves the Tables
and Values
of an empty Store
, returning empty objects.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getContent());
// -> [{}, {}]
Since
v4.0.0
getJson
The getJson
method returns a string serialization of all the Store
content: both the Tables
and the keyed Values
.
getJson(): string
From v3.0 onwards, the serialization is of an array with two entries. The first is the Tables
object, the second the Values
. In previous versions (before the existence of the Values
data structure), it was a sole object of Tables
.
Examples
This example serializes the tabular and keyed value contents of a Store
.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true});
console.log(store.getJson());
// -> '[{"pets":{"fido":{"species":"dog"}}},{"open":true}]'
This example serializes the contents of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getJson());
// -> '[{},{}]'
Since
v1.0.0
getMergeableContent
The getMergeableContent
method returns the full content of a MergeableStore
, together with the metadata required to make it mergeable with another.
getMergeableContent(): MergeableContent
returns | MergeableContent | A |
---|
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore
, sets some data, and then accesses the content and metadata required to make it mergeable.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getMergeableContent());
// ->
[
[
{
pets: [
{
fido: [
{color: ['brown', 'Nn1JUF-----FnHIC', 923684530]},
'',
851131566,
],
},
'',
518810247,
],
},
'',
784336119,
],
[{}, '', 0],
];
Since
v5.0.0
getSchemaJson
The getSchemaJson
method returns a string serialization of both the TablesSchema
and ValuesSchema
of the Store
.
getSchemaJson(): string
returns | string | A string serialization of the |
---|
From v3.0 onwards, the serialization is of an array with two entries. The first is the TablesSchema
object, the second the ValuesSchema
. In previous versions (before the existence of the ValuesSchema
data structure), it was a sole object of TablesSchema
.
Examples
This example serializes the TablesSchema
and ValuesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore()
.setTablesSchema({
pets: {
price: {type: 'number'},
},
})
.setValuesSchema({
open: {type: 'boolean'},
});
console.log(store.getSchemaJson());
// -> '[{"pets":{"price":{"type":"number"}}},{"open":{"type":"boolean"}}]'
This example serializes the TablesSchema
and ValuesSchema
of an empty Store
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Since
v1.0.0
Setter methods
This is the collection of setter methods within the MergeableStore
interface. There are 21 setter methods in total.
setTables
The setTables
method takes an object and sets the entire tabular data of the Store
.
setTables(tables: Tables): this
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Tables
type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Tables
object is valid, any data that was already present in the Store
will be completely overwritten. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the tabular data of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example attempts to set the tabular data of an existing Store
with partly invalid, and then completely invalid, Tables
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTables({pets: {felix: {species: 'cat', bug: []}}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTables({meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
setTablesJson
The setTablesJson
method takes a string serialization of all of the Tables
in the Store
and attempts to update them to that.
setTablesJson(tablesJson: string): this
Type | Description | |
---|---|---|
tablesJson | string | |
returns | this | A reference to the |
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables
method (according to the Tables
type, and matching any TablesSchema
associated with the Store
).
Examples
This example sets the tabular contents of a Store
from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setTablesJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the tabular contents of a Store
from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setTablesJson('{"pets": {"fido": {');
console.log(store.getTables());
// -> {}
Since
v3.0.0
setTablesSchema
The setTablesSchema
method lets you specify the TablesSchema
of the tabular part of the Store
.
setTablesSchema(tablesSchema: TablesSchema): this
Type | Description | |
---|---|---|
tablesSchema | TablesSchema | The |
returns | this | A reference to the |
Note that this may result in a change to data in the Store
, as defaults are applied or as invalid Table
, Row
, or Cell
objects are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing TablesSchema
with the delTablesSchema
method.
Example
This example sets the TablesSchema
of a Store
after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Since
v3.0.0
setTable
The setTable
method takes an object and sets the entire data of a single Table
in the Store
.
setTable(
tableId: string,
table: Table,
): this
Type | Description | |
---|---|---|
tableId | string | |
table | Table | The data of a single |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Table
type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Table
object is valid, any data that was already present in the Store
for that Table
will be completely overwritten. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Table
.
import {createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
This example attempts to set the data of an existing Store
with partly invalid, and then completely invalid, Table
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTable('pets', {felix: {species: 'cat', bug: []}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTable('pets', {meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
setRow
The setRow
method takes an object and sets the entire data of a single Row
in the Store
.
setRow(
tableId: string,
rowId: string,
row: Row,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
row | Row | The data of a single |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Row
type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Row
object is valid, any data that was already present in the Store
for that Row
will be completely overwritten. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Row
.
import {createStore} from 'tinybase';
const store = createStore().setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the data of an existing Store
with partly invalid, and then completely invalid, Row
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
store.setRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
Since
v1.0.0
addRow
The addRow
method takes an object and creates a new Row
in the Store
, returning the unique Id
assigned to it.
addRow(
tableId: string,
row: Row,
reuseRowIds?: boolean,
): undefined | string
Type | Description | |
---|---|---|
tableId | string | |
row | Row | The data of a single |
reuseRowIds? | boolean | Whether |
returns | undefined | string | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Row
type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Row
object is valid, a new Row
will be created. If the object is completely invalid, no change will be made to the Store
and the method will return undefined
.
You should not guarantee the form of the unique Id
that is generated when a Row
is added to the Table
. However it is likely to be a string representation of an increasing integer.
The reuseRowIds
parameter defaults to true
, which means that if you delete a Row
and then add another, the Id
will be re-used - unless you delete the entire Table
, in which case all Row
Ids
will reset. Otherwise, if you specify reuseRowIds
to be false
, then the Id
will be a monotonically increasing string representation of an increasing integer, regardless of any you may have previously deleted.
Examples
This example adds a single Row
.
import {createStore} from 'tinybase';
const store = createStore();
console.log(store.addRow('pets', {species: 'dog'}));
// -> '0'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}}}
This example attempts to add Rows to an existing Store
with partly invalid, and then completely invalid, Row
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {'0': {species: 'dog'}}});
console.log(store.addRow('pets', {species: 'cat', bug: []}));
// -> '1'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
console.log(store.addRow('pets', 42));
// -> undefined
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
Since
v1.0.0
setPartialRow
The setPartialRow
method takes an object and sets partial data of a single Row
in the Store
, leaving other Cell
values unaffected.
setPartialRow(
tableId: string,
rowId: string,
partialRow: Row,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
partialRow | Row | The partial data of a single |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Row
type, or because, when combined with the current Row
data, it does not match a TablesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Row
object is valid, it will be merged with the data that was already present in the Store
. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the data of a single Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.setPartialRow('pets', 'fido', {color: 'walnut', visits: 1});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'walnut', visits: 1}}}
This example attempts to set some of the data of an existing Store
with partly invalid, and then completely invalid, Row
objects.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setPartialRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
store.setPartialRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
Since
v1.0.0
setCell
The setCell
method sets the value of a single Cell
in the Store
.
setCell(
tableId: string,
rowId: string,
cellId: string,
cell: Cell | MapCell,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
cell | Cell | MapCell | The value of the |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, or Id
changes resulting from it.
If the Cell
value is invalid (either because of its type, or because it does not match a TablesSchema
associated with the Store
), will be ignored silently.
As well as string, number, or boolean Cell
types, this method can also take a MapCell
function that takes the current Cell
value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the value of a single Cell
.
import {createStore} from 'tinybase';
const store = createStore().setCell('pets', 'fido', 'species', 'dog');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example sets the data of a single Cell
by mapping the existing value.
import {createStore} from 'tinybase';
const increment = (cell) => cell + 1;
const store = createStore().setTables({pets: {fido: {visits: 1}}});
store.setCell('pets', 'fido', 'visits', increment);
console.log(store.getCell('pets', 'fido', 'visits'));
// -> 2
This example attempts to set the data of an existing Store
with an invalid Cell
value.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setCell('pets', 'fido', 'bug', []);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
Since
v1.0.0
setPartialValues
The setPartialValues
method takes an object and sets its Values
in the Store
, but leaving existing Values
unaffected.
setPartialValues(partialValues: Values): this
This method will cause listeners to be called for any Values
or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Values
type, or because, when combined with the current Values
data, it does not match a ValuesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Values
object is valid, it will be merged with the data that was already present in the Store
. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the keyed value data in a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set some of the data of an existing Store
with partly invalid, and then completely invalid, Values
objects.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setPartialValues(42);
console.log(store.getValues());
// -> {open: true, employees: 3}
Since
v3.0.0
setValues
The setValues
method takes an object and sets all the Values
in the Store
.
setValues(values: Values): this
This method will cause listeners to be called for any Value
or Id
changes resulting from it.
Any part of the provided object that is invalid (either according to the Values
type, or because it does not match a ValuesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Values
object is valid, any data that was already present in the Store
for that Values
will be completely overwritten. If the object is completely invalid, no change will be made to the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the Values
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store
with partly invalid, and then completely invalid, Values
objects.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
store.setValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {employees: 3}
store.setValues(42);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
setValuesJson
The setValuesJson
method takes a string serialization of all of the Values
in the Store
and attempts to update them to those values.
setValuesJson(valuesJson: string): this
Type | Description | |
---|---|---|
valuesJson | string | |
returns | this | A reference to the |
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setValues
method (according to the Values
type, and matching any ValuesSchema
associated with the Store
).
Examples
This example sets the keyed value contents of a Store
from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('{"open": true}');
console.log(store.getValues());
// -> {open: true}
This example attempts to set the keyed value contents of a Store
from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('{"open": false');
console.log(store.getValues());
// -> {}
Since
v3.0.0
setValuesSchema
The setValuesSchema
method lets you specify the ValuesSchema
of the keyed Values
part of the Store
.
setValuesSchema(valuesSchema: ValuesSchema): this
Type | Description | |
---|---|---|
valuesSchema | ValuesSchema | The |
returns | this | A reference to the |
Note that this may result in a change to data in the Store
, as defaults are applied or as invalid Values
are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing ValuesSchema
with the delValuesSchema
method.
Example
This example sets the ValuesSchema
of a Store
after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
store.setValue('open', 'maybe');
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
setValue
The setValue
method sets a single keyed Value
in the Store
.
setValue(
valueId: string,
value: Value | MapValue,
): this
This method will cause listeners to be called for any Value
, or Id
changes resulting from it.
If the Value
is invalid (either because of its type, or because it does not match a ValuesSchema
associated with the Store
), will be ignored silently.
As well as string, number, or boolean Value
types, this method can also take a MapValue
function that takes the current Value
as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets a single Value
.
import {createStore} from 'tinybase';
const store = createStore().setValue('open', true);
console.log(store.getValues());
// -> {open: true}
This example sets the data of a single Value
by mapping the existing Value
.
import {createStore} from 'tinybase';
const increment = (value) => value + 1;
const store = createStore().setValues({employees: 3});
store.setValue('employees', increment);
console.log(store.getValue('employees'));
// -> 4
This example attempts to set an invalid Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({employees: 3});
store.setValue('bug', []);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
applyChanges
The applyChanges
method applies a set of Changes
to the Store
.
applyChanges(changes: Changes): this
This method will take a Changes
object (which is available at the end of a transaction) and apply it to a Store
. The most likely need to do this is to take the changes made during the transaction of one Store
, and apply it to the content of another Store
- such as when persisting and synchronizing data.
Any part of the provided Changes
object are invalid (either because of its type, or because it does not match the schemas associated with the Store
) will be ignored silently.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Prior to v5.0, this method was named setTransactionChanges
.
Example
This example applies a Changes
object that sets a Cell
and removes a Value
.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.applyChanges([{pets: {fido: {color: 'black'}}}, {open: null}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
console.log(store.getValues());
// -> {}
Since
v5.0.0
applyMergeableChanges
The applyMergeableChanges
method applies a set of mergeable changes or content to the MergeableStore
.
applyMergeableChanges(mergeableChanges: MergeableContent | MergeableChanges): MergeableStore
Type | Description | |
---|---|---|
mergeableChanges | MergeableContent | MergeableChanges | The |
returns | MergeableStore | A reference to the |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example applies a MergeableChanges
object that sets a Cell
and removes a Value
.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1')
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.applyMergeableChanges([
[{pets: [{fido: [{color: ['black', 'Nn1JUF----2FnHIC']}]}]}],
[{open: [null, 'Nn1JUF----3FnHIC']}],
1,
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
console.log(store.getValues());
// -> {}
Since
v5.0.0
merge
The merge
method is a convenience method that applies the mergeable content from two MergeableStores to each other in order to bring them to the same state.
merge(mergeableStore: MergeableStore): MergeableStore
Type | Description | |
---|---|---|
mergeableStore | MergeableStore | A reference to the other |
returns | MergeableStore | A reference to this |
This method is symmetrical: applying store1
to store2
will have exactly the same effect as applying store2
to store1
.
Example
This example merges two MergeableStore
objects together. Note how the final part of the timestamps on each Cell
give you a clue that the data comes from changes made to different MergeableStore
objects.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {species: 'dog', color: 'brown'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {felix: {species: 'cat', color: 'tan'}}});
store1.merge(store2);
console.log(store1.getContent());
// ->
[
{
pets: {
felix: {color: 'tan', species: 'cat'},
fido: {color: 'brown', species: 'dog'},
},
},
{},
];
console.log(store2.getContent());
// ->
[
{
pets: {
felix: {color: 'tan', species: 'cat'},
fido: {color: 'brown', species: 'dog'},
},
},
{},
];
console.log(store2.getMergeableContent());
// ->
[
[
{
pets: [
{
felix: [
{
color: ['tan', 'Nn1JUF----0CnH-J', 2576658292],
species: ['cat', 'Nn1JUF-----CnH-J', 3409607562],
},
'',
4146239216,
],
fido: [
{
color: ['brown', 'Nn1JUF----0FnHIC', 1240535355],
species: ['dog', 'Nn1JUF-----FnHIC', 290599168],
},
'',
3989065420,
],
},
'',
4155188296,
],
},
'',
972931118,
],
[{}, '', 0],
];
Since
v5.0.0
setContent
The setContent
method takes an array of two objects and sets the entire data of the Store
.
setContent(content: Content): this
Type | Description | |
---|---|---|
content | Content | An array containing the tabular and keyed-value data of the |
returns | this | A reference to the |
This method will cause listeners to be called for any Table
, Row
, Cell
, Value
, or Id
changes resulting from it.
Any part of the provided objects that are invalid (either according to the Tables
or Values
type, or because it does not match a TablesSchema
or ValuesSchema
associated with the Store
), will be ignored silently.
Assuming that at least some of the provided Tables
object or Values
object is valid, any data that was already present in that part of the Store
will be completely overwritten. If either object is completely invalid, no change will be made to the corresponding part of the Store
.
The method returns a reference to the Store
so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store
with partly invalid, and then completely invalid objects.
import {createStore} from 'tinybase';
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
store.setContent([{pets: {felix: {species: 'cat', bug: []}}}, '']);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setContent([{meaning: 42}]);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v4.0.0
setDefaultContent
The setDefaultContent
method sets initial content of a MergeableStore
.
setDefaultContent(content: Content): MergeableStore
Type | Description | |
---|---|---|
content | Content | An array containing the tabular and keyed-value data to be set. |
returns | MergeableStore | A reference to the |
This differs from the setMergeableContent
method in that all of the metadata is initialized with a empty HLC timestamp - meaning that any changes applied to it will 'win', yet ensuring that at least default, initial data exists.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a new MergeableStore
with default data, and demonstrates that it is overwritten with another MergeableStore
's data on merge, even if the other MergeableStore
was provisioned earlier.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setValues({employees: 3});
const store2 = createMergeableStore('store2');
store2.setDefaultContent([{}, {employees: 4}]);
console.log(store2.getMergeableContent());
// -> [[{}, "", 0], [{"employees": [4, "", 2414055963]}, "", 3035768673]]
store2.merge(store1);
console.log(store2.getContent());
// -> [{}, {employees: 3}]
Since
v5.0.0
setJson
The setJson
method takes a string serialization of all of the Tables
and Values
in the Store
and attempts to update them to those values.
setJson(tablesAndValuesJson: string): this
Type | Description | |
---|---|---|
tablesAndValuesJson | string | A string serialization of all of the |
returns | this | A reference to the |
From v3.0 onwards, the serialization should be of an array with two entries. The first is the Tables
object, the second the Values
. In previous versions (before the existence of the Values
data structure), it was a sole object of Tables
. For backwards compatibility, if a serialization of a single object is provided, it will be treated as the Tables
type.
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables
method (according to the Tables
type, and matching any TablesSchema
associated with the Store
), and the setValues
method (according to the Values
type, and matching any ValuesSchema
associated with the Store
).
Examples
This example sets the tabular and keyed value contents of a Store
from a serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setJson('[{"pets": {"fido": {"species": "dog"}}}, {"open": true}]');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true}
This example sets the tabular contents of a Store
from a legacy single-object serialization (compatible with v2.x and earlier).
import {createStore} from 'tinybase';
const store = createStore();
store.setJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {}
This example attempts to set both the tabular and keyed value contents of a Store
from an invalid serialization.
import {createStore} from 'tinybase';
const store = createStore();
store.setValuesJson('[{"pets": {"fido": {"species": "do');
console.log(store.getTables());
// -> {}
console.log(store.getValues());
// -> {}
Since
v1.0.0
setMergeableContent
The setMergeableContent
method sets the full content of a MergeableStore
, together with the metadata required to make it mergeable with another.
setMergeableContent(mergeableContent: MergeableContent): MergeableStore
Type | Description | |
---|---|---|
mergeableContent | MergeableContent | The full content and metadata of a |
returns | MergeableStore | A reference to the |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a new MergeableStore
and initializes it with the content and metadata from another.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setValues({employees: 3});
console.log(store1.getMergeableContent());
// ->
[
[{}, '', 0],
[{employees: [3, 'Nn1JUF-----FnHIC', 1940815977]}, '', 1260895905],
];
const store2 = createMergeableStore('store2');
store2.setMergeableContent(store1.getMergeableContent());
console.log(store2.getMergeableContent());
// ->
[
[{}, '', 0],
[{employees: [3, 'Nn1JUF-----FnHIC', 1940815977]}, '', 1260895905],
];
Since
v5.0.0
setSchema
The setSchema
method lets you specify the TablesSchema
and ValuesSchema
of the Store
.
setSchema(
tablesSchema: TablesSchema,
valuesSchema?: ValuesSchema,
): this
Type | Description | |
---|---|---|
tablesSchema | TablesSchema | The |
valuesSchema? | ValuesSchema | The |
returns | this | A reference to the |
Note that this may result in a change to data in the Store
, as defaults are applied or as invalid Table
, Row
, Cell
, or Value
objects are removed. These changes will fire any listeners to that data, as expected.
From v3.0 onwards, this method takes two arguments. The first is the TablesSchema
object, the second the ValuesSchema
. In previous versions (before the existence of the ValuesSchema
data structure), only the first was present. For backwards compatibility the new second parameter is optional.
Examples
This example sets the TablesSchema
and ValuesSchema
of a Store
after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setSchema(
{
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
},
{open: {type: 'boolean', default: false}},
);
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
store.setValue('open', 'maybe');
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
console.log(store.getValues());
// -> {open: false}
This example sets just the TablesSchema
of a Store
after it has been created.
import {createStore} from 'tinybase';
const store = createStore().setSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Since
v1.0.0
Listener methods
This is the collection of listener methods within the MergeableStore
interface. There are 27 listener methods in total.
addHasTablesListener
The addHasTablesListener
method registers a listener function with the Store
that will be called when Tables
as a whole are added to or removed from the Store
.
addHasTablesListener(
listener: HasTablesListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | HasTablesListener<MergeableStore> | The function that will be called whenever |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasTablesListener
function, and will be called with a reference to the Store
. It is also given a flag to indicate whether Tables
now exist (having not done previously), or do not (having done so previously).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to Tables
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTablesListener((store, hasTables) => {
console.log('Tables ' + (hasTables ? 'added' : 'removed'));
});
store.delTables();
// -> 'Tables removed'
store.setTables({species: {dog: {price: 5}}});
// -> 'Tables added'
store.delListener(listenerId);
This example registers a listener that responds to Tables
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addHasTablesListener(
(store, hasTables) => store.setValue('hasTables', hasTables),
true,
);
store.setTables({species: {dog: {price: 5}}});
console.log(store.getValues());
// -> {hasTables: true}
store.delListener(listenerId);
Since
v4.4.0
addTablesListener
The addTablesListener
method registers a listener function with the Store
that will be called whenever data in the Store
changes.
addTablesListener(
listener: TablesListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | TablesListener<MergeableStore> | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a TablesListener
function, and will be called with a reference to the Store
and a GetCellChange
function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the whole Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener((store, getCellChange) => {
console.log('Tables changed');
console.log(getCellChange('pets', 'fido', 'color'));
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to the whole Store
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(
(store) => store.setCell('meta', 'update', 'store', true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
Since
v1.0.0
addTableIdsListener
The addTableIdsListener
method registers a listener function with the Store
that will be called whenever the Table
Ids
in the Store
change.
addTableIdsListener(
listener: TableIdsListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | TableIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a TableIdsListener
function, and will be called with a reference to the Store
.
By default, such a listener is only called when a Table
is added or removed. To listen to all changes in the Store
, use the addTablesListener
method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Table
Ids
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener((store) => {
console.log('Table Ids changed');
console.log(store.getTableIds());
});
store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'
// -> ['pets', 'species']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Table
Ids
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener(
(store) => store.setCell('meta', 'update', 'store', true),
true, // mutator
);
store.setTable('species', {dog: {price: 5}});
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
Since
v1.0.0
addHasTableCellListener
The addHasTableCellListener
method registers a listener function with the Store
that will be called when a Cell
is added to or removed from anywhere in a Table
as a whole.
addHasTableCellListener(
tableId: IdOrNull,
cellId: IdOrNull,
listener: HasTableCellListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
cellId | IdOrNull | |
listener | HasTableCellListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasTableCellListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, and the Id
of the Table
Cell
that changed. It is also given a flag to indicate whether the Cell
now exists anywhere in the Table
(having not done previously), or does not (having done so previously).
You can either listen to a single Table
Cell
being added or removed (by specifying the Table
Id
and Cell
Id
, as the method's first two parameters) or changes to any Table
Cell
(by providing null
wildcards).
Both, either, or neither of the tableId
and cellId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Cell
being added to or removed from the Table
as a whole.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
'pets',
'color',
(store, tableId, cellId, hasTableCell) => {
console.log(
'color cell in pets table ' + (hasTableCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in pets table removed'
store.setRow('pets', 'felix', {species: 'cat', color: 'brown'});
// -> 'color cell in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Cell
being added to or removed from the Table
as a whole.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
null,
null,
(store, tableId, cellId, hasTableCell) => {
console.log(
`${cellId} cell in ${tableId} table ` +
(hasTableCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Cell
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableCellListener(
'pets',
'color',
(store, tableId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${cellId}`, true),
true,
);
store.delRow('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets_color: true}}
store.delListener(listenerId);
Since
v4.4.0
addHasTableListener
The addHasTableListener
method registers a listener function with the Store
that will be called when a Table
is added to or removed from the Store
.
addHasTableListener(
tableId: IdOrNull,
listener: HasTableListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | HasTableListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasTableListener
function, and will be called with a reference to the Store
and the Id
of the Table
that changed. It is also given a flag to indicate whether the Table
now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Table
being added or removed (by specifying the Table
Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Table
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
'pets',
(store, tableId, hasTable) => {
console.log('pets table ' + (hasTable ? 'added' : 'removed'));
},
);
store.delTable('pets');
// -> 'pets table removed'
store.setTable('pets', {fido: {species: 'dog', color: 'brown'}});
// -> 'pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Table
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
null,
(store, tableId, hasTable) => {
console.log(`${tableId} table ` + (hasTable ? 'added' : 'removed'));
},
);
store.delTable('pets');
// -> 'pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Table
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasTableListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true,
);
store.delTable('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v4.4.0
addTableCellIdsListener
The addTableCellIdsListener
method registers a listener function with the Store
that will be called whenever the Cell
Ids
that appear anywhere in a Table
change.
addTableCellIdsListener(
tableId: IdOrNull,
listener: TableCellIdsListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | TableCellIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a TableCellIdsListener
function, and will be called with a reference to the Store
and the Id
of the Table
that changed.
By default, such a listener is only called when a Cell
Id
is added or removed from the whole of the Table
. To listen to all changes in the Table
, use the addTableListener
method.
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell
Ids
that appear anywhere in a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener('pets', (store) => {
console.log('Cell Ids in pets table changed');
console.log(store.getTableCellIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell
Ids
that appear anywhere in any Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
species: {dog: {price: 5}},
});
const listenerId = store.addTableCellIdsListener(
null,
(store, tableId) => {
console.log(`Cell Ids in ${tableId} table changed`);
console.log(store.getTableCellIds(tableId));
},
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.setRow('species', 'cat', {price: 4, friendly: true});
// -> 'Cell Ids in species table changed'
// -> ['price', 'friendly']
store.delListener(listenerId);
This example registers a listener that responds to the Cell
Ids
that appear anywhere in a Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addTableListener
The addTableListener
method registers a listener function with the Store
that will be called whenever data in a Table
changes.
addTableListener(
tableId: IdOrNull,
listener: TableListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | TableListener<MergeableStore> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a TableListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, and a GetCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId, getCellChange) => {
console.log('pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(null, (store, tableId) => {
console.log(`${tableId} table changed`);
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addRowIdsListener
The addRowIdsListener
method registers a listener function with the Store
that will be called whenever the Row
Ids
in a Table
change.
addRowIdsListener(
tableId: IdOrNull,
listener: RowIdsListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a RowIdsListener
function, and will be called with a reference to the Store
and the Id
of the Table
that changed.
By default, such a listener is only called when a Row
is added or removed. To listen to all changes in the Table
, use the addTableListener
method.
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Row
Ids
of a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener('pets', (store) => {
console.log('Row Ids for pets table changed');
console.log(store.getRowIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row
Ids
of any Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids for ${tableId} table changed`);
console.log(store.getRowIds(tableId));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.setRow('species', 'dog', {price: 5});
// -> 'Row Ids for species table changed'
// -> ['dog']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row
Ids
of a specific Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
Since
v1.0.0
addSortedRowIdsListener
The addSortedRowIdsListener
method registers a listener function with the Store
that will be called whenever sorted (and optionally, paginated) Row
Ids
in a Table
change.
addSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener<MergeableStore> | The function that will be called whenever the sorted |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a SortedRowIdsListener
function, and will be called with a reference to the Store
, the Id
of the Table
whose Row
Ids
sorting changed, the Cell
Id
being used to sort them, whether descending or not, and the offset and limit of the number of Ids
returned, for pagination purposes. It also receives the sorted array of Ids
itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Such a listener is called when a Row
is added or removed, but also when a value in the specified Cell
(somewhere in the Table
) has changed enough to change the sorting of the Row
Ids
.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified Table
, sorted by a single specified Cell
.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'cujo']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'fido', {species: 'dog'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['felix', 'fido', 'cujo']
store.delListener(listenerId);
This example registers a listener that responds to any change to a paginated section of the sorted Row
Ids
of a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'price',
false,
0,
3,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`First three sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
console.log(store.getSortedRowIds('pets', 'price', false, 0, 3));
// -> ['lowly', 'mickey', 'carnaby']
store.setCell('pets', 'carnaby', 'price', 4.5);
// -> 'First three sorted Row Ids for pets table changed'
// -> ['lowly', 'mickey', 'tom']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific Table
. The Row
Ids
are sorted by their own value, since the cellId
parameter is explicitly undefined.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', undefined, false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
undefined,
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['cujo', 'felix', 'fido']
store.delListener(listenerId);
This example registers a listener that responds to a change in the sorting of the rows of a specific Table
, even though the set of Ids
themselves has not changed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setCell('pets', 'felix', 'species', 'tiger');
// -> 'Sorted Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId) => store.setCell('meta', 'sorted', tableId, true),
true, // mutator
);
store.setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTable('meta'));
// -> {sorted: {pets: true}}
store.delListener(listenerId);
Since
v2.0.0
addHasRowListener
The addHasRowListener
method registers a listener function with the Store
that will be called when a Row
is added to or removed from the Store
.
addHasRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: HasRowListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | HasRowListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasRowListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, and the Id
of the Row
that changed. It is also given a flag to indicate whether the Row
now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Row
being added or removed (by specifying the Table
Id
and Row
Id
, as the method's first two parameters) or changes to any Row
(by providing null
wildcards).
Both, either, or neither of the tableId
and rowId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Row
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
'pets',
'fido',
(store, tableId, rowId, hasRow) => {
console.log(
'fido row in pets table ' + (hasRow ? 'added' : 'removed'),
);
},
);
store.delRow('pets', 'fido');
// -> 'fido row in pets table removed'
store.setRow('pets', 'fido', {species: 'dog', color: 'brown'});
// -> 'fido row in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Row
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
null,
null,
(store, tableId, rowId, hasRow) => {
console.log(
`${rowId} row in ${tableId} table ` + (hasRow ? 'added' : 'removed'),
);
},
);
store.delRow('pets', 'fido');
// -> 'fido row in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Row
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delRow('pets', 'fido');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v4.4.0
addRowCountListener
The addRowCountListener
method registers a listener function with the Store
that will be called whenever the count of Row
objects in a Table
change.
addRowCountListener(
tableId: IdOrNull,
listener: RowCountListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | RowCountListener<MergeableStore> | The function that will be called whenever the number of |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a RowCountListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, and the number of Row
objects in the Table
.
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a change in the number of Row
objects in a specific Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, _tableId, count) => {
console.log('Row count for pets table changed to ' + count);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row
objects of any Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
null,
(store, tableId, count) => {
console.log(`Row count for ${tableId} table changed to ${count}`);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.setRow('species', 'dog', {price: 5});
// -> 'Row count for species table changed to 1'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row
objects of a specific Table
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, tableId, count) =>
store.setCell('meta', 'update', tableId, count),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: 2}}
store.delListener(listenerId);
Since
v4.1.0
addRowListener
The addRowListener
method registers a listener function with the Store
that will be called whenever data in a Row
changes.
addRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: RowListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener<MergeableStore> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a RowListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, the Id
of the Row
that changed, and a GetCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single Row
(by specifying the Table
Id
and Row
Id
as the method's first two parameters) or changes to any Row
(by providing null
wildcards).
Both, either, or neither of the tableId
and rowId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId, getCellChange) => {
console.log('fido row in pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
null,
null,
(store, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Row
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v1.0.0
addCellIdsListener
The addCellIdsListener
method registers a listener function with the Store
that will be called whenever the Cell
Ids
in a Row
change.
addCellIdsListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: CellIdsListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a CellIdsListener
function, and will be called with a reference to the Store
, the Id
of the Table
, and the Id
of the Row
that changed.
By default, such a listener is only called when a Cell
is added or removed. To listen to all changes in the Row
, use the addRowListener
method.
You can either listen to a single Row
(by specifying the Table
Id
and Row
Id
as the method's first two parameters) or changes to any Row
(by providing a null
wildcard).
Both, either, or neither of the tableId
and rowId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell
Ids
of a specific Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener('pets', 'fido', (store) => {
console.log('Cell Ids for fido row in pets table changed');
console.log(store.getCellIds('pets', 'fido'));
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell
Ids
of any Row
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
null,
null,
(store, tableId, rowId) => {
console.log(`Cell Ids for ${rowId} row in ${tableId} table changed`);
console.log(store.getCellIds(tableId, rowId));
},
);
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.setCell('species', 'dog', 'price', 5);
// -> 'Cell Ids for dog row in species table changed'
// -> ['price']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell
Ids
of a specific Row
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true, // mutator
);
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
Since
v1.0.0
addCellListener
The addCellListener
method registers a listener function with the Store
that will be called whenever data in a Cell
changes.
addCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: CellListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener<MergeableStore> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a CellListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, the Id
of the Row
that changed, the Id
of the Cell
that changed, the new Cell
value, the old Cell
value, and a GetCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single Cell
(by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or changes to any Cell
(by providing null
wildcards).
All, some, or none of the tableId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific Row
in a specific Table
, any Cell
in any Row
in any Table
, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Cell
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log('color cell in fido row in pets table changed');
console.log([oldCell, newCell]);
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Cell
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
Since
v1.0.0
addHasCellListener
The addHasCellListener
method registers a listener function with the Store
that will be called when a Cell
is added to or removed from the Store
.
addHasCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: HasCellListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | HasCellListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasCellListener
function, and will be called with a reference to the Store
, the Id
of the Table
that changed, the Id
of the Row
that changed, and the Id
of the Cell
that changed. It is also given a flag to indicate whether the Cell
now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Cell
being added or removed (by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or changes to any Cell
(by providing null
wildcards).
All, some, or none of the tableId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific Row
in a specific Table
, any Cell
in any Row
in any Table
, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Cell
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, hasCell) => {
console.log(
'color cell in fido row in pets table ' +
(hasCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in fido row in pets table removed'
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table added'
store.delListener(listenerId);
This example registers a listener that responds to any Cell
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
null,
null,
null,
(store, tableId, rowId, cellId, hasCell) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table ` +
(hasCell ? 'added' : 'removed'),
);
},
);
store.delCell('pets', 'fido', 'color');
// -> 'color cell in fido row in pets table removed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Cell
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addHasCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
Since
v4.4.0
addInvalidCellListener
The addInvalidCellListener
method registers a listener function with the Store
that will be called whenever invalid data was attempted to be written to a Cell
.
addInvalidCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: InvalidCellListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | InvalidCellListener<MergeableStore> | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is an InvalidCellListener
function, and will be called with a reference to the Store
, the Id
of the Table
, the Id
of the Row
, and the Id
of Cell
that was being attempted to be changed. It is also given the invalid value of the Cell
, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell
within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Cell
(by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or invalid attempts to change any Cell
(by providing null
wildcards).
All, some, or none of the tableId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific Row
in a specific Table
, any Cell
in any Row
in any Table
, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a TablesSchema
is present. The listener will be called:
- if a
Table
is being updated that is not specified in theTablesSchema
, - if a
Cell
is of the wrong type specified in theTablesSchema
, - if a
Cell
is omitted and is not defaulted in theTablesSchema
, - or if an empty
Row
is provided and there are noCell
defaults in theTablesSchema
.
The listener will not be called if a Cell
that is defaulted in the TablesSchema
is not provided, as long as all of the Cells that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the TablesSchema
example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Cell
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) => {
console.log('Invalid color cell in fido row in pets table');
console.log(invalidCells);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
// -> [{r: '96', g: '4B', b: '00'}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell
- in a Store
without a TablesSchema
. Note also how it then responds to cases where empty or invalid Row
objects, or Table
objects, or Tables
objects are provided.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
store.setTable('sales', {fido: {date: new Date()}});
// -> 'Invalid date cell in fido row in sales table'
store.setRow('pets', 'felix', {});
// -> 'Invalid undefined cell in felix row in pets table'
store.setRow('filter', 'name', /[a-z]?/);
// -> 'Invalid undefined cell in name row in filter table'
store.setRow('sales', '2021', {forecast: undefined});
// -> 'Invalid forecast cell in 2021 row in sales table'
store.addRow('filter', /[0-9]?/);
// -> 'Invalid undefined cell in undefined row in filter table'
store.setTable('raw', {});
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTable('raw', ['row1', 'row2']);
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTables(['table1', 'table2']);
// -> 'Invalid undefined cell in undefined row in undefined table'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell
- in a Store
with a TablesSchema
. Note how it responds to cases where missing parameters are provided for optional, and defaulted Cell
values in a Row
.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string', default: 'unknown'},
},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setRow('sales', 'fido', {price: 5});
// -> 'Invalid price cell in fido row in sales table'
// The listener is called, because the sales Table is not in the schema
store.setRow('pets', 'felix', {species: true});
// -> 'Invalid species cell in felix row in pets table'
// The listener is called, because species is invalid...
console.log(store.getRow('pets', 'felix'));
// -> {color: 'unknown'}
// ...even though a Row was set with the default value
store.setRow('pets', 'fido', {color: 'brown'});
// -> 'Invalid species cell in fido row in pets table'
// The listener is called, because species is missing and not defaulted...
console.log(store.getRow('pets', 'fido'));
// -> {color: 'brown'}
// ...even though a Row was set
store.setRow('pets', 'rex', {species: 'dog'});
console.log(store.getRow('pets', 'rex'));
// -> {species: 'dog', color: 'unknown'}
// The listener is not called, because color is defaulted
store.delTables().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string'},
},
});
store.setRow('pets', 'cujo', {});
// -> 'Invalid species cell in cujo row in pets table'
// -> 'Invalid color cell in cujo row in pets table'
// -> 'Invalid undefined cell in cujo row in pets table'
// The listener is called multiple times, because neither Cell is defaulted
// and the Row as a whole is empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) =>
store.setCell(
'meta',
'invalid_updates',
`${tableId}_${rowId}_${cellId}`,
JSON.stringify(invalidCells[0]),
),
true,
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
console.log(store.getRow('meta', 'invalid_updates'));
// -> {'pets_fido_color': '{"r":"96","g":"4B","b":"00"}'}
store.delListener(listenerId);
Since
v1.1.0
addHasValuesListener
The addHasValuesListener
method registers a listener function with the Store
that will be called when Values
as a whole are added to or removed from the Store
.
addHasValuesListener(
listener: HasValuesListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | HasValuesListener<MergeableStore> | The function that will be called whenever |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasValuesListener
function, and will be called with a reference to the Store
. It is also given a flag to indicate whether Values
now exist (having not done previously), or do not (having done so previously).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to Values
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValuesListener((store, hasValues) => {
console.log('Values ' + (hasValues ? 'added' : 'removed'));
});
store.delValues();
// -> 'Values removed'
store.setValue('employees', 4);
// -> 'Values added'
store.delListener(listenerId);
This example registers a listener that responds to Values
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addHasValuesListener(
(store, hasValues) => store.setValue('hasValues', hasValues),
true,
);
store.setValue('employees', 4);
console.log(store.getValues());
// -> {employees: 4, hasValues: true}
store.delListener(listenerId);
Since
v4.4.0
addValuesListener
The addValuesListener
method registers a listener function with the Store
that will be called whenever the Values
change.
addValuesListener(
listener: ValuesListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | ValuesListener<MergeableStore> | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a ValuesListener
function, and will be called with a reference to the Store
and a GetValueChange
function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the Store
's Values
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener((store, getValueChange) => {
console.log('values changed');
console.log(getValueChange('employees'));
});
store.setValue('employees', 4);
// -> 'values changed'
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to the Store
's Values
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener(
(store) => store.setValue('updated', true),
true,
);
store.setValue('employees', 4);
console.log(store.getValues());
// -> {open: true, employees: 4, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addHasValueListener
The addHasValueListener
method registers a listener function with the Store
that will be called when a Value
is added to or removed from the Store
.
addHasValueListener(
valueId: IdOrNull,
listener: HasValueListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
valueId | IdOrNull | |
listener | HasValueListener<MergeableStore> | The function that will be called whenever the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a HasValueListener
function, and will be called with a reference to the Store
and the Id
of Value
that changed. It is also given a flag to indicate whether the Value
now exists (having not done previously), or does not (having done so previously).
You can either listen to a single Value
being added or removed (by specifying the Value
Id
) or any Value
being added or removed (by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a specific Value
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
'employees',
(store, valueId, hasValue) => {
console.log('employee value ' + (hasValue ? 'added' : 'removed'));
},
);
store.delValue('employees');
// -> 'employee value removed'
store.setValue('employees', 4);
// -> 'employee value added'
store.delListener(listenerId);
This example registers a listener that responds to any Value
being added or removed.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
null,
(store, valueId, hasValue) => {
console.log(valueId + ' value ' + (hasValue ? 'added' : 'removed'));
},
);
store.delValue('employees');
// -> 'employees value removed'
store.setValue('website', 'https://pets.com');
// -> 'website value added'
store.delListener(listenerId);
This example registers a listener that responds to a specific Value
being added or removed, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addHasValueListener(
'employees',
(store) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v4.4.0
addInvalidValueListener
The addInvalidValueListener
method registers a listener function with the Store
that will be called whenever invalid data was attempted to be written to a Value
.
addInvalidValueListener(
valueId: IdOrNull,
listener: InvalidValueListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
valueId | IdOrNull | |
listener | InvalidValueListener<MergeableStore> | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is an InvalidValueListener
function, and will be called with a reference to the Store
and the Id
of Value
that was being attempted to be changed. It is also given the invalid value of the Value
, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Value
within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Value
(by specifying the Value
Id
as the method's first parameter) or invalid attempts to change any Value
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a ValuesSchema
is present. The listener will be called:
- if a
Value
is being updated that is not specified in theValuesSchema
, - if a
Value
is of the wrong type specified in theValuesSchema
, - or if a
Value
is omitted when using setValues that is not defaulted in theValuesSchema
.
The listener will not be called if a Value
that is defaulted in the ValuesSchema
is not provided, as long as all of the Values
that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the ValuesSchema
example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) => {
console.log('Invalid open value');
console.log(invalidValues);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
// -> [{yes: true}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value
- in a Store
without a ValuesSchema
. Note also how it then responds to cases where an empty Values
object is provided.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
store.setValue('employees', ['alice', 'bob']);
// -> 'Invalid employees value'
store.setValues('pets', 'felix', {});
// -> 'Invalid undefined value'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value
- in a Store
with a ValuesSchema
. Note how it responds to cases where missing parameters are provided for optional, and defaulted Values
.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
console.log(store.getValues());
// -> {open: false}
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('website', true);
// -> 'Invalid website value'
// The listener is called, because the website Value is not in the schema
store.setValue('open', 'yes');
// -> 'Invalid open value'
// The listener is called, because 'open' is invalid...
console.log(store.getValues());
// -> {open: false}
// ...even though it is still present with the default value
store.setValues({open: true});
// -> 'Invalid employees value'
// The listener is called because employees is missing and not defaulted...
console.log(store.getValues());
// -> {open: true}
// ...even though the Values were set
store.setValues({employees: 3});
console.log(store.getValues());
// -> {open: false, employees: 3}
// The listener is not called, because 'open' is defaulted
store.setValuesSchema({
open: {type: 'boolean'},
employees: {type: 'number'},
});
store.setValues({});
// -> 'Invalid open value'
// -> 'Invalid employees value'
// -> 'Invalid undefined value'
// The listener is called multiple times, because neither Value is
// defaulted and the Values as a whole were empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) =>
store.setValue('invalid_updates', JSON.stringify(invalidValues[0])),
true,
);
store.setValue('open', {yes: true});
console.log(store.getValue('invalid_updates'));
// -> '{"yes":true}'
store.delListener(listenerId);
Since
v3.0.0
addValueIdsListener
The addValueIdsListener
method registers a listener function with the Store
that will be called whenever the Value
Ids
in a Store
change.
addValueIdsListener(
listener: ValueIdsListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
listener | ValueIdsListener<MergeableStore> | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a ValueIdsListener
function, and will be called with a reference to the Store
.
By default, such a listener is only called when a Value
is added or removed. To listen to all changes in the Values
, use the addValuesListener
method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Value
Ids
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener((store) => {
console.log('Value Ids changed');
console.log(store.getValueIds());
});
store.setValue('employees', 3);
// -> 'Value Ids changed'
// -> ['open', 'employees']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Value
Ids
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener(
(store) => store.setValue('updated', true),
true, // mutator
);
store.setValue('employees', 3);
console.log(store.getValues());
// -> {open: true, employees: 3, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addValueListener
The addValueListener
method registers a listener function with the Store
that will be called whenever data in a Value
changes.
addValueListener(
valueId: IdOrNull,
listener: ValueListener<MergeableStore>,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
valueId | IdOrNull | |
listener | ValueListener<MergeableStore> | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | A unique |
The provided listener is a ValueListener
function, and will be called with a reference to the Store
, the Id
of the Value
that changed, the new Value
value, the old Value
, and a GetValueChange
function in case you need to inspect any changes that occurred.
You can either listen to a single Value
(by specifying the Value
Id
) or changes to any Value
(by providing a null
wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store, valueId, newValue, oldValue, getValueChange) => {
console.log('employee value changed');
console.log([oldValue, newValue]);
console.log(getValueChange('employees'));
},
);
store.setValue('employees', 4);
// -> 'employee value changed'
// -> [3, 4]
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Value
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(null, (store, valueId) => {
console.log(`${valueId} value changed`);
});
store.setValue('employees', 4);
// -> 'employees value changed'
store.setValue('open', false);
// -> 'open value changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value
, and which also mutates the Store
itself.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addDidFinishTransactionListener
The addDidFinishTransactionListener
method registers a listener function with the Store
that will be called just after other non-mutating listeners are called at the end of the transaction.
addDidFinishTransactionListener(listener: TransactionListener<MergeableStore>): string
Type | Description | |
---|---|---|
listener | TransactionListener<MergeableStore> | The function that will be called after the end of a transaction. |
returns | string | A unique |
This is useful if you need to know that a set of listeners have just been called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener
will receive a reference to the Store
and two booleans to indicate whether Cell
or Value
data has been touched during the transaction. The two flags is intended as a hint about whether non-mutating listeners might have been called at the end of the transaction.
Here, 'touched' means that Cell
or Value
data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched
and valuesTouched
in the listener will be false
because all changes have been reverted.
Note that a TransactionListener
added to the Store
with this method cannot mutate the Store
itself, and attempts to do so will fail silently.
Example
This example registers a listener that is called at the end of the transaction, just after its listeners have been called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched
and valuesTouched
parameters in the listener work.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addDidFinishTransactionListener((store) => {
const [cellsTouched, valuesTouched] = store.getTransactionLog() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
});
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Tables changed'
// -> 'Cells/Values touched: true/false'
store.transaction(() => store.setValue('employees', 4));
// -> 'Values changed'
// -> 'Cells/Values touched: false/true'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: false/false'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
addStartTransactionListener
The addStartTransactionListener
method registers a listener function with the Store
that will be called at the start of a transaction.
addStartTransactionListener(listener: TransactionListener<MergeableStore>): string
Type | Description | |
---|---|---|
listener | TransactionListener<MergeableStore> | The function that will be called at the start of a transaction. |
returns | string | A unique |
The provided TransactionListener
will receive a reference to the Store
and two booleans to indicate whether Cell
or Value
data has been touched during the transaction. Since this is called at the start, they will both be false
!
Note that a TransactionListener
added to the Store
with this method can mutate the Store
, and its changes will be treated as part of the transaction that is starting.
Example
This example registers a listener that is called at start end of the transaction, just before its listeners will be called.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addStartTransactionListener(() => {
console.log('Transaction started');
});
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Transaction started'
store.callListener(listenerId);
// -> 'Transaction started'
store.delListener(listenerId);
Since
v3.2.0
addWillFinishTransactionListener
The addWillFinishTransactionListener
method registers a listener function with the Store
that will be called just before other non-mutating listeners are called at the end of the transaction.
addWillFinishTransactionListener(listener: TransactionListener<MergeableStore>): string
Type | Description | |
---|---|---|
listener | TransactionListener<MergeableStore> | The function that will be called before the end of a transaction. |
returns | string | A unique |
This is useful if you need to know that a set of listeners are about to be called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener
will receive a reference to the Store
and two booleans to indicate whether Cell
or Value
data has been touched during the transaction. The two flags are intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell
or Value
data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched
and valuesTouched
in the listener will be false
because all changes have been reverted.
Note that a TransactionListener
added to the Store
with this method can mutate the Store
itself, and its changes will be treated as part of the transaction that is starting (and may fire non-mutating listeners after this).
Example
This example registers a listener that is called at the end of the transaction, just before its listeners will be called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched
and valuesTouched
parameters in the listener work.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addWillFinishTransactionListener((store) => {
const [cellsTouched, valuesTouched] = store.getTransactionLog() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
});
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Cells/Values touched: true/false'
// -> 'Tables changed'
store.transaction(() => store.setValue('employees', 4));
// -> 'Cells/Values touched: false/true'
// -> 'Values changed'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: false/false'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
callListener
The callListener
method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed.
callListener(listenerId: string): this
This is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store
in bulk.
Examples
This example registers a listener that ensures a Cell
has one of list of a valid values. After that list changes, the listener is called to apply the condition to the existing data.
import {createStore} from 'tinybase';
const validColors = ['walnut', 'brown', 'black'];
const store = createStore();
const listenerId = store.addCellListener(
'pets',
null,
'color',
(store, tableId, rowId, cellId, color) => {
if (!validColors.includes(color)) {
store.setCell(tableId, rowId, cellId, validColors[0]);
}
},
true,
);
store.setRow('pets', 'fido', {species: 'dog', color: 'honey'});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'walnut'}
validColors.shift();
console.log(validColors);
// -> ['brown', 'black']
store.callListener(listenerId);
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'brown'}
store.delListener(listenerId);
This example registers a listener to Row
Id
changes. It is explicitly called and fires for two Tables
in the Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids listener called for ${tableId} table`);
});
store.callListener(listenerId);
// -> 'Row Ids listener called for pets table'
// -> 'Row Ids listener called for species table'
store.delListener(listenerId);
This example registers a listener Value
changes. It is explicitly called and fires for two Values
in the Store
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
null,
(store, valueId, value) => {
console.log(`Value listener called for ${valueId} value, ${value}`);
},
);
store.callListener(listenerId);
// -> 'Value listener called for open value, true'
// -> 'Value listener called for employees value, 3'
store.delListener(listenerId);
This example registers listeners for the end of transactions, and for invalid Cells. They are explicitly called, meaninglessly. The former receives empty arguments. The latter is not called at all.
import {createStore} from 'tinybase';
const store = createStore();
const listenerId = store.addWillFinishTransactionListener(
(store, cellsTouched, valuesTouched) => {
console.log(`Transaction finish: ${cellsTouched}/${valuesTouched}`);
},
);
store.callListener(listenerId);
// -> 'Transaction finish: undefined/undefined'
store.delListener(listenerId);
const listenerId2 = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log('Invalid cell', tableId, rowId, cellId);
},
);
store.callListener(listenerId2);
// -> undefined
store.delListener(listenerId2);
Since
v1.0.0
delListener
The delListener
method removes a listener that was previously added to the Store
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Store
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(() => {
console.log('Tables changed');
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
store.delListener(listenerId);
store.setCell('pets', 'fido', 'color', 'honey');
// -> undefined
// The listener is not called.
Since
v1.0.0
Iterator methods
This is the collection of iterator methods within the MergeableStore
interface. There are 5 iterator methods in total.
forEachTable
The forEachTable
method takes a function that it will then call for each Table
in the Store
.
forEachTable(tableCallback: TableCallback): void
Type | Description | |
---|---|---|
tableCallback | TableCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Table
structure of the Store
in a functional style. The tableCallback
parameter is a TableCallback
function that will be called with the Id
of each Table
, and with a function that can then be used to iterate over each Row
of the Table
, should you wish.
Example
This example iterates over each Table
in a Store
, and lists each Row
Id
within them.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.forEachTable((tableId, forEachRow) => {
console.log(tableId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'pets'
// -> '- fido'
// -> 'species'
// -> '- dog'
Since
v1.0.0
forEachTableCell
The forEachTableCell
method takes a function that it will then call for each Cell
used across the whole Table
.
forEachTableCell(
tableId: string,
tableCellCallback: TableCellCallback,
): void
Type | Description | |
---|---|---|
tableId | string | |
tableCellCallback | TableCellCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Cell
structure of the Table
in a functional style. The tableCellCallback
parameter is a TableCellCallback
function that will be called with the Id
of each Cell
and the count of Rows in the Table
in which it appears.
Example
This example iterates over each Cell
Id
used across the whole Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat', legs: 4}},
});
store.forEachTableCell('pets', (cellId, count) => {
console.log(`${cellId}: ${count}`);
});
// -> 'species: 2'
// -> 'legs: 1'
Since
v3.3.0
forEachRow
The forEachRow
method takes a function that it will then call for each Row
in a specified Table
.
forEachRow(
tableId: string,
rowCallback: RowCallback,
): void
Type | Description | |
---|---|---|
tableId | string | |
rowCallback | RowCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Row
structure of the Table
in a functional style. The rowCallback
parameter is a RowCallback
function that will be called with the Id
of each Row
, and with a function that can then be used to iterate over each Cell
of the Row
, should you wish.
Example
This example iterates over each Row
in a Table
, and lists each Cell
Id
within them.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {color: 'black'},
},
});
store.forEachRow('pets', (rowId, forEachCell) => {
console.log(rowId);
forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- species'
// -> 'felix'
// -> '- color'
Since
v1.0.0
forEachCell
The forEachCell
method takes a function that it will then call for each Cell
in a specified Row
.
forEachCell(
tableId: string,
rowId: string,
cellCallback: CellCallback,
): void
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellCallback | CellCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Cell
structure of the Row
in a functional style. The cellCallback
parameter is a CellCallback
function that will be called with the Id
and value of each Cell
.
Example
This example iterates over each Cell
in a Row
, and lists its value.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.forEachCell('pets', 'fido', (cellId, cell) => {
console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
Since
v1.0.0
forEachValue
The forEachValue
method takes a function that it will then call for each Value
in a Store
.
forEachValue(valueCallback: ValueCallback): void
Type | Description | |
---|---|---|
valueCallback | ValueCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Value
structure of the Store
in a functional style. The valueCallback
parameter is a ValueCallback
function that will be called with the Id
and value of each Value
.
Example
This example iterates over each Value
in a Store
, and lists its value.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.forEachValue((valueId, value) => {
console.log(`${valueId}: ${value}`);
});
// -> 'open: true'
// -> 'employees: 3'
Since
v3.0.0
Syncing methods
This is the collection of syncing methods within the MergeableStore
interface. There are 9 syncing methods in total.
getMergeableTableDiff
The getMergeableTableDiff
method returns information about new and differing Table
objects of a MergeableStore
relative to another.
getMergeableTableDiff(otherTableHashes: TableHashes): [newTables: [thing: {[tableId: Id]: TableStamp<Hashed>}, time?: string], differingTableHashes: TableHashes]
Type | Description | |
---|---|---|
otherTableHashes | TableHashes | The |
returns | [newTables: [thing: {[tableId: Id]: TableStamp<Hashed>}, time?: string], differingTableHashes: TableHashes] | A pair of objects describing the new and differing |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates two MergeableStores, sets some differing data, and then identifies the differences in the Table
objects of one versus the other. Once they have been merged, the differences are empty.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown'}}});
const store2 = createMergeableStore('store2');
store2.setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(
store2.getMergeableTableDiff(store1.getMergeableTableHashes()),
);
// ->
[
[{species: [{dog: [{price: [5, 'Nn1JUF----0CnH-J']}]}]}],
{pets: 1212600658},
];
store1.merge(store2);
console.log(
store2.getMergeableTableDiff(store1.getMergeableTableHashes()),
);
// -> [[{}], {}]
Since
v5.0.0
getMergeableTableHashes
The getMergeableTableHashes
method returns hashes for the Table
objects in a MergeableStore
.
getMergeableTableHashes(): TableHashes
returns | TableHashes | A |
---|
If two Table
Ids
have different hashes, that indicates that the content within them is different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore
, sets some data, and then accesses the Table
hashes.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getMergeableTableHashes());
// -> {pets: 518810247}
store.setCell('species', 'dog', 'price', 5);
console.log(store.getMergeableTableHashes());
// -> {pets: 518810247, species: 2324343240}
Since
v5.0.0
getMergeableRowDiff
The getMergeableRowDiff
method returns information about new and differing Row
objects of a MergeableStore
relative to another.
getMergeableRowDiff(otherTableRowHashes: RowHashes): [newRows: [thing: {[tableId: Id]: TableStamp<Hashed>}, time?: string], differingRowHashes: RowHashes]
Type | Description | |
---|---|---|
otherTableRowHashes | RowHashes | The |
returns | [newRows: [thing: {[tableId: Id]: TableStamp<Hashed>}, time?: string], differingRowHashes: RowHashes] | A pair of objects describing the new and differing |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates two MergeableStores, sets some differing data, and then identifies the differences in the Row
objects of one versus the other. Once they have been merged, the differences are empty.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {fido: {color: 'black'}, felix: {color: 'tan'}}});
console.log(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(store1.getMergeableTableHashes())[1],
),
),
);
// ->
[
[{pets: [{felix: [{color: ['tan', 'Nn1JUF----0CnH-J']}]}]}],
{pets: {fido: 1038491054}},
];
store1.merge(store2);
console.log(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(store1.getMergeableTableHashes())[1],
),
),
);
// -> [[{}], {}]
Since
v5.0.0
getMergeableRowHashes
The getMergeableRowHashes
method returns hashes for Row
objects in a MergeableStore
.
getMergeableRowHashes(otherTableHashes: TableHashes): RowHashes
Type | Description | |
---|---|---|
otherTableHashes | TableHashes | The |
returns | RowHashes | A |
If two Row
Ids
have different hashes, that indicates that the content within them is different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore
, sets some data, and then accesses the Row
hashes for the differing Table
Ids
.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown'}, felix: {color: 'tan'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {fido: {color: 'black'}, felix: {color: 'tan'}}});
console.log(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(store1.getMergeableTableHashes())[1],
),
);
// -> {pets: {felix: 1683761402, fido: 851131566}}
Since
v5.0.0
getMergeableCellDiff
The getMergeableCellDiff
method returns information about new and differing Cell
objects of a MergeableStore
relative to another.
getMergeableCellDiff(otherTableRowCellHashes: CellHashes): [thing: {[tableId: Id]: TableStamp<Hashed>}, time?: string]
Type | Description | |
---|---|---|
otherTableRowCellHashes | CellHashes | The |
returns | [thing: {[tableId: Id]: TableStamp<Hashed>}, time?: string] | The new and differing |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates two MergeableStores, sets some differing data, and then identifies the differences in the Cell
objects of one versus the other. Once they have been merged, the differences are empty.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {fido: {color: 'black', species: 'dog'}}});
console.log(
store2.getMergeableCellDiff(
store1.getMergeableCellHashes(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(
store1.getMergeableTableHashes(),
)[1],
),
)[1],
),
),
);
// ->
[
{
pets: [
{
fido: [
{
color: ['black', 'Nn1JUF-----CnH-J'],
species: ['dog', 'Nn1JUF----0CnH-J'],
},
],
},
],
},
];
store1.merge(store2);
console.log(
store2.getMergeableCellDiff(
store1.getMergeableCellHashes(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(
store1.getMergeableTableHashes(),
)[1],
),
)[1],
),
),
);
// -> [{}]
Since
v5.0.0
getMergeableCellHashes
The getMergeableCellHashes
method returns hashes for Cell
objects in a MergeableStore
.
getMergeableCellHashes(otherTableRowHashes: RowHashes): CellHashes
Type | Description | |
---|---|---|
otherTableRowHashes | RowHashes | The |
returns | CellHashes | A |
If two Cell
Ids
have different hashes, that indicates that the content within them is different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore
, sets some data, and then accesses the Cell
hashes for the differing Table
Ids
.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setTables({pets: {fido: {color: 'brown', species: 'dog'}}});
const store2 = createMergeableStore('store2');
store2.setTables({pets: {fido: {color: 'black', species: 'dog'}}});
console.log(
store1.getMergeableCellHashes(
store2.getMergeableRowDiff(
store1.getMergeableRowHashes(
store2.getMergeableTableDiff(store1.getMergeableTableHashes())[1],
),
)[1],
),
);
// -> {pets: {fido: {color: 923684530, species: 227729753}}}
Since
v5.0.0
getMergeableValueDiff
The getMergeableValueDiff
method returns information about new and differing Value
objects of a MergeableStore
relative to another.
getMergeableValueDiff(otherValueHashes: ValueHashes): [thing: {[valueId: Id]: ValueStamp<Hashed>}, time?: string]
Type | Description | |
---|---|---|
otherValueHashes | ValueHashes | The |
returns | [thing: {[valueId: Id]: ValueStamp<Hashed>}, time?: string] | The new and differing |
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates two MergeableStores, sets some differing data, and then identifies the differences in the Value
objects of one versus the other. Once they have been merged, the differences are empty.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setValues({employees: 3});
const store2 = createMergeableStore('store2');
store2.setValues({employees: 4, open: true});
console.log(
store2.getMergeableValueDiff(store1.getMergeableValueHashes()),
);
// ->
[
{
employees: [4, 'Nn1JUF-----CnH-J'],
open: [true, 'Nn1JUF----0CnH-J'],
},
];
store1.merge(store2);
console.log(
store2.getMergeableValueDiff(store1.getMergeableValueHashes()),
);
// -> [{}]
Since
v5.0.0
getMergeableValueHashes
The getMergeableValueHashes
method returns hashes for the Value
objects in a MergeableStore
.
getMergeableValueHashes(): ValueHashes
returns | ValueHashes | A |
---|
If two Value
Ids
have different hashes, that indicates that the content within them is different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore
, sets some data, and then accesses the Value
hashes.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setValue('employees', 3);
console.log(store.getMergeableValueHashes());
// -> {employees: 1940815977}
store.setValue('open', true);
console.log(store.getMergeableValueHashes());
// -> {employees: 1940815977, open: 3860530645}
Since
v5.0.0
getMergeableContentHashes
The getMergeableContentHashes
method returns hashes for the full content of a MergeableStore
.
getMergeableContentHashes(): ContentHashes
returns | ContentHashes | A |
---|
If two MergeableStore
instances have different hashes, that indicates that the mergeable Tables
or Values
within them are different and should be synchronized.
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example creates a MergeableStore
, sets some data, and then accesses the content hashes.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getMergeableContentHashes());
// -> [784336119, 0]
store.setValue('open', true);
console.log(store.getMergeableContentHashes());
// -> [784336119, 2829789038]
Since
v5.0.0
Transaction methods
This is the collection of transaction methods within the MergeableStore
interface. There are 6 transaction methods in total.
finishTransaction
The finishTransaction
method allows you to explicitly finish a transaction that has made multiple mutations to the Store
, triggering all calls to the relevant listeners.
finishTransaction(doRollback?: DoRollback): this
Type | Description | |
---|---|---|
doRollback? | DoRollback | An optional callback that should return |
returns | this | A reference to the |
Transactions are useful for making bulk changes to the data in a Store
, and when you don't want listeners to be called as you make each change. Changes
are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction
method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction
methods for you. See that method for several transaction examples.
Use this finishTransaction
method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. There must have been a corresponding startTransaction
method that this completes, of course, otherwise this function has no effect.
The optional parameter, doRollback
is a DoRollback
callback that you can use to rollback the transaction if it did not complete to your satisfaction. It is called with getTransactionChanges
and getTransactionLog
parameters, which inform you of the net changes that have been made during the transaction, at different levels of detail. See the DoRollback
documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row
listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
This example makes multiple changes to the Store
, including some attempts to update a Cell
with invalid values. The doRollback
callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob'])
.finishTransaction(() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
});
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.3.0
getTransactionChanges
The getTransactionChanges
method returns the net meaningful changes that have been made to a Store
during a transaction.
getTransactionChanges(): Changes
This is useful for deciding whether to rollback a transaction, for example. The returned object is only meaningful if the method is called when the Store
is in a transaction - such as in a TransactionListener
.
Example
This example makes changes to the Store
. At the end of the transaction, detail about what changed is enumerated.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setValue('open', false)
.finishTransaction(() => {
const [changedCells, changedValues] = store.getTransactionChanges();
console.log(changedCells);
console.log(changedValues);
});
// -> {pets: {fido: {color: 'black'}}}
// -> {open: false}
Since
v5.0.0
getTransactionLog
The getTransactionLog
method returns the changes that were made to a Store
during a transaction in more detail, including invalid changes, and what previous values were.
getTransactionLog(): TransactionLog
returns | TransactionLog | A |
---|
This is useful for deciding whether to rollback a transaction, for example. The returned object is only meaningful if the method is called when the Store
is in a transaction - such as in a TransactionListener
.
Example
This example makes changes to the Store
. At the end of the transaction, detail about what changed is enumerated.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob'])
.finishTransaction(() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
});
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
Since
v5.0.0
getTransactionMergeableChanges
The getTransactionMergeableChanges
method returns the net meaningful changes that have been made to a MergeableStore
during a transaction.
getTransactionMergeableChanges(): MergeableChanges
returns | MergeableChanges | A |
---|
The method is generally intended to be used internally within TinyBase itself and the return type is assumed to be opaque to applications that use it.
Example
This example makes changes to the MergeableStore
. At the end of the transaction, detail about what changed is enumerated.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
store.setTables({pets: {fido: {species: 'dog', color: 'brown'}}});
store.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setValue('open', false)
.finishTransaction(() => {
console.log(store.getTransactionMergeableChanges());
});
// ->
[
[{pets: [{fido: [{color: ['black', 'Nn1JUF----2FnHIC']}]}]}],
[{open: [false, 'Nn1JUF----3FnHIC']}],
1,
];
Since
v5.0.0
startTransaction
The startTransaction
method allows you to explicitly start a transaction that will make multiple mutations to the Store
, buffering all calls to the relevant listeners until it completes when you call the finishTransaction
method.
startTransaction(): this
returns | this | A reference to the |
---|
Transactions are useful for making bulk changes to the data in a Store
, and when you don't want listeners to be called as you make each change. Changes
are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction
method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction
methods for you. See that method for several transaction examples.
Use this startTransaction
method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. You must remember to also call the finishTransaction
method explicitly when it is done, of course.
Example
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row
listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
Since
v1.3.0
transaction
The transaction
method takes a function that makes multiple mutations to the Store
, buffering all calls to the relevant listeners until it completes.
transaction<Return>(
actions: () => Return,
doRollback?: DoRollback,
): Return
Type | Description | |
---|---|---|
actions | () => Return | The function to be executed as a transaction. |
doRollback? | DoRollback | An optional callback that should return |
returns | Return | Whatever value the provided transaction function returns. |
This method is useful for making bulk changes to the data in a Store
, and when you don't want listeners to be called as you make each change. Changes
are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
If multiple changes are made to a piece of Store
data throughout the transaction, a relevant listener will only be called with the final value (assuming it is different to the value at the start of the transaction), regardless of the changes that happened in between. For example, if a Cell
had a value 'a'
and then, within a transaction, it was changed to 'b'
and then 'c'
, any CellListener
registered for that cell would be called once as if there had been a single change from 'a'
to 'c'
.
Transactions can be nested. Relevant listeners will be called only when the outermost one completes.
The second, optional parameter, doRollback
is a DoRollback
callback that you can use to rollback the transaction if it did not complete to your satisfaction. See the DoRollback
documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction. In the second case, the Row
listener is only called once.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true),
);
// -> 'Fido changed'
This example makes multiple changes to one Cell
. The Cell
listener is called once - and with the final value - only if there is a net overall change.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell) => console.log(newCell),
);
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> 'walnut'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> undefined
// No net change during the transaction, so the listener is not called.
This example makes multiple changes to the Store
, including some attempts to update a Cell
and Value
with invalid values. The doRollback
callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.transaction(
() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob']),
() => {
const [, , changedCells, invalidCells, changedValues, invalidValues] =
store.getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
},
);
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.0.0
Deleter methods
This is the collection of deleter methods within the MergeableStore
interface. There are 9 deleter methods in total.
delTables
The delTables
method lets you remove all of the data in a Store
.
delTables(): this
returns | this | A reference to the |
---|
Example
This example removes the data of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.delTables();
console.log(store.getTables());
// -> {}
Since
v1.0.0
delTablesSchema
The delTablesSchema
method lets you remove the TablesSchema
of the Store
.
delTablesSchema(): this
returns | this | A reference to the |
---|
Example
This example removes the TablesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTablesSchema({
pets: {species: {type: 'string'}},
});
store.delTablesSchema();
console.log(store.getTablesSchemaJson());
// -> '{}'
Since
v1.0.0
delTable
The delTable
method lets you remove a single Table
from the Store
.
delTable(tableId: string): this
Example
This example removes a Table
from a Store
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.delTable('pets');
console.log(store.getTables());
// -> {species: {dog: {price: 5}}}
Since
v1.0.0
delRow
The delRow
method lets you remove a single Row
from a Table
.
delRow(
tableId: string,
rowId: string,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | this | A reference to the |
If this is the last Row
in its Table
, then that Table
will be removed.
Example
This example removes a Row
from a Table
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat'}},
});
store.delRow('pets', 'fido');
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v1.0.0
delCell
The delCell
method lets you remove a single Cell
from a Row
.
delCell(
tableId: string,
rowId: string,
cellId: string,
forceDel?: boolean,
): this
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
forceDel? | boolean | An optional flag to indicate that the whole |
returns | this | A reference to the |
When there is no TablesSchema
applied to the Store
, then if this is the last Cell
in its Row
, then that Row
will be removed. If, in turn, that is the last Row
in its Table
, then that Table
will be removed.
If there is a TablesSchema
applied to the Store
and it specifies a default value for this Cell
, then deletion will result in it being set back to its default value. To override this, use the forceDel
parameter, as described below.
The forceDel
parameter is an optional flag that is only relevant if a TablesSchema
provides a default value for this Cell
. Under such circumstances, deleting a Cell
value will normally restore it to the default value. If this flag is set to true
, the complete removal of the Cell
is instead guaranteed. But since doing do so would result in an invalid Row
(according to the TablesSchema
), in fact the whole Row
is deleted to retain the integrity of the Table
. Therefore, this flag should be used with caution.
Examples
This example removes a Cell
from a Row
without a TablesSchema
.
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {fido: {species: 'dog', sold: true}},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example removes a Cell
from a Row
with a TablesSchema
that defaults its value.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
This example removes a Cell
from a Row
with a TablesSchema
that defaults its value, but uses the forceDel
parameter to override it.
import {createStore} from 'tinybase';
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}, felix: {species: 'cat'}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold', true);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat', sold: false}}}
Since
v1.0.0
delValues
The delValues
method lets you remove all the Values
from a Store
.
delValues(): this
returns | this | A reference to the |
---|
If there is a ValuesSchema
applied to the Store
and it specifies a default value for any Value
Id
, then deletion will result in it being set back to its default value.
Examples
This example removes all Values
from a Store
without a ValuesSchema
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.delValues();
console.log(store.getValues());
// -> {}
This example removes all Values
from a Store
with a ValuesSchema
that defaults one of its values.
import {createStore} from 'tinybase';
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValues();
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
delValuesSchema
The delValuesSchema
method lets you remove the ValuesSchema
of the Store
.
delValuesSchema(): this
returns | this | A reference to the |
---|
Example
This example removes the ValuesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore().setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delValuesSchema();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
delValue
The delValue
method lets you remove a single Value
from a Store
.
delValue(valueId: string): this
If there is a ValuesSchema
applied to the Store
and it specifies a default value for this Value
Id
, then deletion will result in it being set back to its default value.
Examples
This example removes a Value
from a Store
without a ValuesSchema
.
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
store.delValue('employees');
console.log(store.getValues());
// -> {open: true}
This example removes a Value
from a Store
with a ValuesSchema
that defaults its value.
import {createStore} from 'tinybase';
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValue('open');
console.log(store.getValues());
// -> {open: false, employees: 3}
Since
v3.0.0
delSchema
The delSchema
method lets you remove both the TablesSchema
and ValuesSchema
of the Store
.
delSchema(): this
returns | this | A reference to the |
---|
Prior to v3.0, this method removed the TablesSchema
only.
Example
This example removes the TablesSchema
and ValuesSchema
of a Store
.
import {createStore} from 'tinybase';
const store = createStore()
.setTablesSchema({
pets: {species: {type: 'string'}},
})
.setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delSchema();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Since
v3.0.0
Development methods
This is the collection of development methods within the MergeableStore
interface. There is only one method, getListenerStats
.
getListenerStats
The getListenerStats
method provides a set of statistics about the listeners registered with the Store
, and is used for debugging purposes.
getListenerStats(): StoreListenerStats
returns | StoreListenerStats | A |
---|
The StoreListenerStats
object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a small and simple Store
.
import {createStore} from 'tinybase';
const store = createStore();
store.addTablesListener(() => console.log('Tables changed'));
store.addRowIdsListener(() => console.log('Row Ids changed'));
const listenerStats = store.getListenerStats();
console.log(listenerStats.rowIds);
// -> 1
console.log(listenerStats.tables);
// -> 1
Since
v1.0.0
Properties
There is one property, isMergeable
, within the MergeableStore
interface.
isMergeable
This will always be false for a Store
, and true for a MergeableStore
.
Since
v5.0.0
Functions
There is one function, createMergeableStore
, within the mergeable-store
module.
createMergeableStore
The createMergeableStore
function creates a MergeableStore
, and is the main entry point into the mergeable-store
module.
createMergeableStore(uniqueId?: string): MergeableStore
Type | Description | |
---|---|---|
uniqueId? | string | |
returns | MergeableStore | A reference to the new |
There is one optional parameter which is a uniqueId for the MergeableStore
. This is used to distinguish conflicting changes made in the same millisecond by two different MergeableStore
objects as its hash is added to the end of the HLC timestamps. Generally this can be omitted unless you have a need for deterministic HLCs, such as in a testing scenario. Otherwise, TinyBase will assign a unique Id
to the Store
at the time of creation.
Examples
This example creates a MergeableStore
.
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1');
console.log(store.getContent());
// -> [{}, {}]
console.log(store.getMergeableContent());
// -> [[{}, '', 0], [{}, '', 0]]
This example creates a MergeableStore
with some initial data:
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1').setTables({
pets: {fido: {species: 'dog'}},
});
console.log(store.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {}]
console.log(store.getMergeableContent());
// ->
[
[
{
pets: [
{
fido: [
{species: ['dog', 'Nn1JUF-----FnHIC', 290599168]},
'',
2682656941,
],
},
'',
2102515304,
],
},
'',
3506229770,
],
[{}, '', 0],
];
This example creates a MergeableStore
with some initial data and a TablesSchema
:
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore('store1')
.setTables({pets: {fido: {species: 'dog'}}})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
console.log(store.getContent());
// -> [{pets: {fido: {sold: false, species: 'dog'}}}, {}]
console.log(store.getMergeableContent());
// ->
[
[
{
pets: [
{
fido: [
{
sold: [false, 'Nn1JUF----2FnHIC', 2603026204],
species: ['dog', 'Nn1JUF----1FnHIC', 2817056260],
},
'',
2859424112,
],
},
'',
1640515891,
],
},
'',
2077041985,
],
[{}, '', 0],
];
Since
v5.0.0
Type Aliases
These are the type aliases within the mergeable-store
module.
Mergeable type aliases
This is the collection of mergeable type aliases within the mergeable-store
module. There are only two mergeable type aliases, MergeableChanges
and MergeableContent
.
MergeableChanges
The MergeableChanges
type represents changes to the content of a MergeableStore
and the metadata about that content) required to merge it with another.
[mergeableTables: TablesStamp, mergeableValues: ValuesStamp, isChanges: 1]
It is simply an array of two Stamp
types, one for changes to the MergeableStore
's Tables
and one for changes to its Values
. A final 1
is used to distinguish it from a full MergeableContent
object.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
MergeableContent
The MergeableContent
type represents the content of a MergeableStore
and the metadata about that content) required to merge it with another.
[mergeableTables: TablesStamp<true>, mergeableValues: ValuesStamp<true>]
It is simply an array of two Stamp
types, one for the MergeableStore
's Tables
and one for its Values
.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
Stamps type aliases
This is the collection of stamps type aliases within the mergeable-store
module. There are 9 stamps type aliases in total.
TablesStamp
The TablesStamp
type is used as metadata to decide how to merge two different sets of Tables
together.
Stamp<{[tableId: Id]: TableStamp<Hashed>}, Hashed>
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
TableStamp
The TableStamp
type is used as metadata to decide how to merge two different Table
objects together.
Stamp<{[rowId: Id]: RowStamp<Hashed>}, Hashed>
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
RowStamp
The RowStamp
type is used as metadata to decide how to merge two different Row
objects together.
Stamp<{[cellId: Id]: CellStamp<Hashed>}, Hashed>
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
CellStamp
The CellStamp
type is used as metadata to decide how to merge two different Cell
objects together.
Stamp<CellOrUndefined, Hashed>
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
ValuesStamp
The ValuesStamp
type is used as metadata to decide how to merge two different sets of Values
together.
Stamp<{[valueId: Id]: ValueStamp<Hashed>}, Hashed>
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
ValueStamp
The ValueStamp
type is used as metadata to decide how to merge two different Value
objects together.
Stamp<ValueOrUndefined, Hashed>
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
Hash
The Hash
type is used within the mergeable-store
module to quickly compare the content of two objects.
number
This is simply an alias for a JavaScript number
.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
Stamp
The Stamp
type is used as metadata to decide how to merge two different MergeableStore
objects together.
Hashed extends true ? [thing: Thing, time: Time, hash: Hash] : [thing: Thing, time?: Time]
It describes a combination of a value (or object), a Time
, and optionally a Hash
, all in an array.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
Time
The Time
type is used within the mergeable-store
module to store the value of a hybrid logical clock (HLC).
string
It is simply an alias for a JavaScript string
, but it comprises three HLC parts: a logical timestamp, a sequence counter, and a client Id
. It is designed to be string-sortable and unique across all of the systems involved in synchronizing a MergeableStore
.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
Syncing type aliases
This is the collection of syncing type aliases within the mergeable-store
module. There are 5 syncing type aliases in total.
TableHashes
The TableHashes
type is used to quickly compare the content of two sets of Table
objects.
{[tableId: Id]: Hash}
It is simply an object of Hash
types, one for each Table
Id
in the MergeableStore
.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
RowHashes
The RowHashes
type is used to quickly compare the content of two sets of Row
objects.
{[tableId: Id]: {[rowId: Id]: Hash}}
It is simply a nested object of Hash
types, one for each Row
Id
, for each TableId, in the MergeableStore
.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
CellHashes
The CellHashes
type is used to quickly compare the content of two sets of Cell
objects.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: Hash}}}
It is simply a nested object of Hash
types, one for each Cell
Id
, for each Row
Id
, for each TableId, in the MergeableStore
.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
ValueHashes
The ValueHashes
type is used to quickly compare the content of two sets of Value
objects.
{[valueId: Id]: Hash}
It is simply an object of Hash
types, one for each Value
Id
in the MergeableStore
.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
ContentHashes
The ContentHashes
type is used to quickly compare the content of two MergeableStore
objects.
[tablesHash: Hash, valuesHash: Hash]
It is simply an array of two Hash
types, one for the MergeableStore
's Tables
and one for its Values
.
This type is mostly utilized internally within TinyBase itself and is generally assumed to be opaque to applications that use it.
Since
v5.0.0
metrics
The metrics
module of the TinyBase project provides the ability to create and track metrics and aggregates of the data in Store
objects.
The main entry point to this module is the createMetrics
function, which returns a new Metrics
object. From there, you can create new Metric
definitions, access the values of those Metrics
directly, and register listeners for when they change.
Since
v1.0.0
Interfaces
There is one interface, Metrics
, within the metrics
module.
Metrics
A Metrics
object lets you define, query, and listen to, aggregations of Cell
values within a Table
in a Store
.
This is useful for counting the number of Row
objects in a Table
, averaging Cell
values, or efficiently performing any arbitrary aggregations.
Create a Metrics
object easily with the createMetrics
function. From there, you can add new Metric
definitions (with the setMetricDefinition
method), query their values (with the getMetric
method), and add listeners for when they change (with the addMetricListener
method).
This module provides a number of predefined and self-explanatory aggregations ('sum', 'avg', 'min', and 'max'), and defaults to counting Row
objects when using the setMetricDefinition
method. However, far more complex aggregations can be configured with custom functions.
Example
This example shows a very simple lifecycle of a Metrics
object: from creation, to adding a definition, getting a Metric
, and then registering and removing a listener for it.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().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
const listenerId = metrics.addMetricListener('highestPrice', () => {
console.log(metrics.getMetric('highestPrice'));
});
store.setCell('species', 'horse', 'price', 20);
// -> 20
metrics.delListener(listenerId);
metrics.destroy();
See also
- Using Metrics guides
- Rolling Dice demos
- Country demo
- Todo App demos
Since
v1.0.0
Getter methods
This is the collection of getter methods within the Metrics
interface. There are 5 getter methods in total.
getStore
The getStore
method returns a reference to the underlying Store
that is backing this Metrics
object.
getStore(): Store
Example
This example creates a Metrics
object against a newly-created Store
and then gets its reference in order to update its data.
import {createMetrics, createStore} from 'tinybase';
const metrics = createMetrics(createStore());
metrics.setMetricDefinition('speciesCount', 'species');
metrics.getStore().setCell('species', 'dog', 'price', 5);
console.log(metrics.getMetric('speciesCount'));
// -> 1
Since
v1.0.0
getTableId
The getTableId
method returns the Id
of the underlying Table
that is backing a Metric
.
getTableId(metricId: string): undefined | string
Type | Description | |
---|---|---|
metricId | string | |
returns | undefined | string |
If the Metric
Id
is invalid, the method returns undefined
.
Example
This example creates a Metrics
object, a single Metric
definition, and then queries it (and a non-existent definition) to get the underlying Table
Id
.
import {createMetrics, createStore} from 'tinybase';
const metrics = createMetrics(createStore());
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getTableId('speciesCount'));
// -> 'species'
console.log(metrics.getTableId('petsCount'));
// -> undefined
Since
v1.0.0
getMetric
The getMetric
method gets the current value of a Metric
.
getMetric(metricId: string): undefined | number
Type | Description | |
---|---|---|
metricId | string | |
returns | undefined | number | The numeric value of the |
If the identified Metric
does not exist (or if the definition references a Table
or Cell
value that does not exist) then undefined
is returned.
Example
This example creates a Store
, creates a Metrics
object, and defines a simple Metric
to average the price values in the Table
. It then uses getMetric to access its value (and also the value of a Metric
that has not been defined).
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
console.log(metrics.getMetric('highestPrice'));
// -> 5
console.log(metrics.getMetric('lowestPrice'));
// -> undefined
Since
v1.0.0
getMetricIds
The getMetricIds
method returns an array of the Metric
Ids
registered with this Metrics
object.
getMetricIds(): Ids
Example
This example creates a Metrics
object with two definitions, and then gets the Ids
of the definitions.
import {createMetrics, createStore} from 'tinybase';
const metrics = createMetrics(createStore())
.setMetricDefinition('speciesCount', 'species')
.setMetricDefinition('petsCount', 'pets');
console.log(metrics.getMetricIds());
// -> ['speciesCount', 'petsCount']
Since
v1.0.0
hasMetric
The hasMetric
method returns a boolean indicating whether a given Metric
exists in the Metrics
object, and has a value.
hasMetric(metricId: string): boolean
Type | Description | |
---|---|---|
metricId | string | |
returns | boolean |
Example
This example shows two simple Metric
existence checks.
import {createMetrics, createStore} from 'tinybase';
const store = createStore();
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
console.log(metrics.hasMetric('lowestPrice'));
// -> false
console.log(metrics.hasMetric('highestPrice'));
// -> false
store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
console.log(metrics.hasMetric('highestPrice'));
// -> true
Since
v1.0.0
Listener methods
This is the collection of listener methods within the Metrics
interface. There are only three listener methods, addMetricIdsListener
, addMetricListener
, and delListener
.
addMetricIdsListener
The addMetricIdsListener
method registers a listener function with the Metrics
object that will be called whenever a Metric
definition is added or removed.
addMetricIdsListener(listener: MetricIdsListener): string
Type | Description | |
---|---|---|
listener | MetricIdsListener | The function that will be called whenever a |
returns | string |
The provided listener is a MetricIdsListener
function, and will be called with a reference to the Metrics
object.
Example
This example creates a Store
, a Metrics
object, and then registers a listener that responds to the addition and the removal of a Metric
definition.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
const listenerId = metrics.addMetricIdsListener((metrics) => {
console.log(metrics.getMetricIds());
});
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
// -> ['highestPrice']
metrics.delMetricDefinition('highestPrice');
// -> []
metrics.delListener(listenerId);
Since
v4.1.0
addMetricListener
The addMetricListener
method registers a listener function with the Metrics
object that will be called whenever the value of a specified Metric
changes.
addMetricListener(
metricId: IdOrNull,
listener: MetricListener,
): string
Type | Description | |
---|---|---|
metricId | IdOrNull | |
listener | MetricListener | The function that will be called whenever the |
returns | string | A unique |
You can either listen to a single Metric
(by specifying the Metric
Id
as the method's first parameter), or changes to any Metric
(by providing a null
wildcard).
The provided listener is a MetricListener
function, and will be called with a reference to the Metrics
object, the Id
of the Metric
that changed, the new Metric
value, and the old Metric
value.
Examples
This example creates a Store
, a Metrics
object, and then registers a listener that responds to any changes to a specific Metric
.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const listenerId = metrics.addMetricListener(
'highestPrice',
(metrics, _metricId, newMetric, oldMetric) => {
console.log('highestPrice metric changed');
console.log([oldMetric, newMetric]);
},
);
store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
// -> [5, 20]
metrics.delListener(listenerId);
This example creates a Store
, a Metrics
object, and then registers a listener that responds to any changes to any Metric
.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store)
.setMetricDefinition('highestPrice', 'species', 'max', 'price')
.setMetricDefinition('speciesCount', 'species');
const listenerId = metrics.addMetricListener(
null,
(metrics, metricId, newMetric, oldMetric) => {
console.log(`${metricId} metric changed`);
console.log([oldMetric, newMetric]);
},
);
store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
// -> [5, 20]
// -> 'speciesCount metric changed'
// -> [3, 4]
metrics.delListener(listenerId);
Since
v1.0.0
delListener
The delListener
method removes a listener that was previously added to the Metrics
object.
delListener(listenerId: string): Metrics
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Metrics | A reference to the |
Use the Id
returned by the addMetricListener
method. Note that the Metrics
object may re-use this Id
for future listeners added to it.
Example
This example creates a Store
, a Metrics
object, registers a listener, and then removes it.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const listenerId = metrics.addMetricListener('highestPrice', () => {
console.log('highestPrice metric changed');
});
store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
metrics.delListener(listenerId);
store.setCell('species', 'giraffe', 'price', 50);
// -> undefined
// The listener is not called.
Since
v1.0.0
Configuration methods
This is the collection of configuration methods within the Metrics
interface. There are only two configuration methods, delMetricDefinition
and setMetricDefinition
.
delMetricDefinition
The delMetricDefinition
method removes an existing Metric
definition.
delMetricDefinition(metricId: string): Metrics
Example
This example creates a Store
, creates a Metrics
object, defines a simple Metric
, and then removes it.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getMetricIds());
// -> ['speciesCount']
metrics.delMetricDefinition('speciesCount');
console.log(metrics.getMetricIds());
// -> []
Since
v1.0.0
setMetricDefinition
The setMetricDefinition
method lets you set the definition of a Metric
.
setMetricDefinition(
metricId: string,
tableId: string,
aggregate?: MetricAggregate | "sum" | "avg" | "min" | "max",
getNumber?: string | (getCell: GetCell, rowId: string) => number,
aggregateAdd?: MetricAggregateAdd,
aggregateRemove?: MetricAggregateRemove,
aggregateReplace?: MetricAggregateReplace,
): Metrics
Type | Description | |
---|---|---|
metricId | string | |
tableId | string | |
aggregate? | MetricAggregate | "sum" | "avg" | "min" | "max" | Either a string representing one of a set of common aggregation techniques ('sum', 'avg', 'min', or 'max'), or a function that aggregates numeric values from each |
getNumber? | string | (getCell: GetCell, rowId: string) => number | Either the |
aggregateAdd? | MetricAggregateAdd | A function that can be used to optimize a custom |
aggregateRemove? | MetricAggregateRemove | A function that can be used to optimize a custom |
aggregateReplace? | MetricAggregateReplace | A function that can be used to optimize a custom |
returns | Metrics | A reference to the |
Every Metric
definition is identified by a unique Id
, and if you re-use an existing Id
with this method, the previous definition is overwritten.
A Metric
is an aggregation of numeric values produced from each Row
within a single Table
. Therefore the definition must specify the Table
(by its Id
) to be aggregated.
Without the third aggregate
parameter, the Metric
will simply be a count of the number of Row
objects in the Table
. But often you will specify a more interesting aggregate - such as the four predefined aggregates, 'sum', 'avg', 'min', and 'max' - or a custom function that produces your own aggregation of an array of numbers.
The fourth getNumber
parameter specifies which Cell
in each Row
contains the numerical values to be used in the aggregation. Alternatively, a custom function can be provided that produces your own numeric value from the local Row
as a whole.
The final three parameters, aggregateAdd
, aggregateRemove
, aggregateReplace
need only be provided when you are using your own custom aggregate
function. These give you the opportunity to reduce your custom function's algorithmic complexity by providing shortcuts that can nudge an aggregation result when a single value is added, removed, or replaced in the input values.
Examples
This example creates a Store
, creates a Metrics
object, and defines a simple Metric
to count the Row
objects in the Table
.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getMetric('speciesCount'));
// -> 3
This example creates a Store
, creates a Metrics
object, and defines a standard Metric
to get the highest value of each price
Cell
in the Row
objects in the Table
.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
console.log(metrics.getMetric('highestPrice'));
// -> 5
This example creates a Store
, creates a Metrics
object, and defines a custom Metric
to get the lowest value of each price
Cell
, greater than 2.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'lowestPriceOver2',
'species',
(numbers) => Math.min(...numbers.filter((number) => number > 2)),
'price',
);
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 4
This example also creates a Store
, creates a Metrics
object, and defines a custom Metric
to get the lowest value of each price
Cell
, greater than 2. However, it also reduces algorithmic complexity with two shortcut functions.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'lowestPriceOver2',
'species',
(numbers) => Math.min(...numbers.filter((number) => number > 2)),
'price',
(metric, add) => (add > 2 ? Math.min(metric, add) : metric),
(metric, remove) => (remove == metric ? undefined : metric),
(metric, add, remove) =>
remove == metric
? undefined
: add > 2
? Math.min(metric, add)
: metric,
);
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 4
store.setRow('species', 'fish', {price: 3});
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 3
This example creates a Store
, creates a Metrics
object, and defines a custom Metric
to get the average value of a discounted price.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5, discount: 0.3},
cat: {price: 4, discount: 0.2},
worm: {price: 1, discount: 0.2},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'averageDiscountedPrice',
'species',
'avg',
(getCell) => getCell('price') * (1 - getCell('discount')),
);
console.log(metrics.getMetric('averageDiscountedPrice'));
// -> 2.5
Since
v1.0.0
Iterator methods
This is the collection of iterator methods within the Metrics
interface. There is only one method, forEachMetric
.
forEachMetric
The forEachMetric
method takes a function that it will then call for each Metric
in the Metrics
object.
forEachMetric(metricCallback: MetricCallback): void
Type | Description | |
---|---|---|
metricCallback | MetricCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over all the Metrics
in a functional style. The metricCallback
parameter is a MetricCallback
function that will be called with the Id
of each Metric
and its value.
Example
This example iterates over each Metric
in a Metrics
object.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store)
.setMetricDefinition('highestPrice', 'species', 'max', 'price')
.setMetricDefinition('lowestPrice', 'species', 'min', 'price');
metrics.forEachMetric((metricId, metric) => {
console.log([metricId, metric]);
});
// -> ['highestPrice', 5]
// -> ['lowestPrice', 1]
Since
v1.0.0
Lifecycle methods
This is the collection of lifecycle methods within the Metrics
interface. There is only one method, destroy
.
destroy
The destroy
method should be called when this Metrics
object is no longer used.
destroy(): void
This guarantees that all of the listeners that the object registered with the underlying Store
are removed and it can be correctly garbage collected.
Example
This example creates a Store
, adds a Metrics
object with a definition (that registers a RowListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createMetrics, createStore} from 'tinybase';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(store.getListenerStats().row);
// -> 1
metrics.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v1.0.0
Development methods
This is the collection of development methods within the Metrics
interface. There is only one method, getListenerStats
.
getListenerStats
The getListenerStats
method provides a set of statistics about the listeners registered with the Metrics
object, and is used for debugging purposes.
getListenerStats(): MetricsListenerStats
returns | MetricsListenerStats | A |
---|
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Metrics
object.
import {createMetrics, createStore} from 'tinybase';
const store = createStore();
const metrics = createMetrics(store);
metrics.addMetricListener(null, () => console.log('Metric changed'));
console.log(metrics.getListenerStats());
// -> {metric: 1}
Since
v1.0.0
Functions
There is one function, createMetrics
, within the metrics
module.
createMetrics
The createMetrics
function creates a Metrics
object, and is the main entry point into the metrics
module.
createMetrics(store: Store): Metrics
A given Store
can only have one Metrics
object associated with it. If you call this function twice on the same Store
, your second call will return a reference to the Metrics
object created by the first.
Examples
This example creates a Metrics
object.
import {createMetrics, createStore} from 'tinybase';
const store = createStore();
const metrics = createMetrics(store);
console.log(metrics.getMetricIds());
// -> []
This example creates a Metrics
object, and calls the method a second time for the same Store
to return the same object.
import {createMetrics, createStore} from 'tinybase';
const store = createStore();
const metrics1 = createMetrics(store);
const metrics2 = createMetrics(store);
console.log(metrics1 === metrics2);
// -> true
Since
v1.0.0
Type Aliases
These are the type aliases within the metrics
module.
Listener type aliases
This is the collection of listener type aliases within the metrics
module. There are only two listener type aliases, MetricIdsListener
and MetricListener
.
MetricIdsListener
The MetricIdsListener
type describes a function that is used to listen to Metric
definitions being added or removed.
(metrics: Metrics): void
Type | Description | |
---|---|---|
metrics | Metrics | A reference to the |
returns | void | This has no return value. |
A MetricIdsListener
is provided when using the addMetricIdsListener
method. See that method for specific examples.
When called, a MetricIdsListener
is given a reference to the Metrics
object.
Since
v1.0.0
MetricListener
The MetricListener
type describes a function that is used to listen to changes to a Metric
.
(
metrics: Metrics,
metricId: Id,
newMetric: Metric | undefined,
oldMetric: Metric | undefined,
): void
Type | Description | |
---|---|---|
metrics | Metrics | A reference to the |
metricId | Id | |
newMetric | Metric | undefined | The new value of the |
oldMetric | Metric | undefined | The old value of the |
returns | void | This has no return value. |
A MetricListener
is provided when using the addMetricListener
method. See that method for specific examples.
When called, a MetricListener
is given a reference to the Metrics
object, the Id
of the Metric
that changed, and the new and old values of the Metric
.
If this is the first time that a Metric
has had a value (such as when a table has gained its first row), the old value will be undefined
. If a Metric
now no longer has a value, the new value will be undefined
.
Since
v1.0.0
Aggregators type aliases
This is the collection of aggregators type aliases within the metrics
module. There are 4 aggregators type aliases in total.
MetricAggregate
The MetricAggregate
type describes a custom function that takes an array of numbers and returns an aggregate that is used as a Metric
.
(
numbers: number[],
length: number,
): Metric
Type | Description | |
---|---|---|
numbers | number[] | The array of numbers in the |
length | number | The length of the array of numbers in the |
returns | Metric |
There are a number of common predefined aggregators, such as for counting, summing, and averaging values. This type is instead used for when you wish to use a more complex aggregation of your own devising. See the setMetricDefinition
method for more examples.
Since
v1.0.0
MetricAggregateAdd
The MetricAggregateAdd
type describes a function that can be used to optimize a custom MetricAggregate
by providing a shortcut for when a single value is added to the input values.
(
metric: Metric,
add: number,
length: number,
): Metric | undefined
Type | Description | |
---|---|---|
metric | Metric | The current value of the |
add | number | The number being added to the |
length | number | The length of the array of numbers in the |
returns | Metric | undefined |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when adding a new number to a series, the new sum of the series is the new value added to the previous sum.
If it is not possible to shortcut the aggregation based on just one value being added, return undefined
and the Metric
will be completely recalculated.
When possible, if you are providing a custom MetricAggregate
, seek an implementation of an MetricAggregateAdd
function that can reduce the complexity cost of growing the input data set. See the setMetricDefinition
method for more examples.
Since
v1.0.0
MetricAggregateRemove
The MetricAggregateRemove
type describes a function that can be used to optimize a custom MetricAggregate
by providing a shortcut for when a single value is removed from the input values.
(
metric: Metric,
remove: number,
length: number,
): Metric | undefined
Type | Description | |
---|---|---|
metric | Metric | The current value of the |
remove | number | The number being removed from the |
length | number | The length of the array of numbers in the |
returns | Metric | undefined |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when removing a number from a series, the new sum of the series is the new value subtracted from the previous sum.
If it is not possible to shortcut the aggregation based on just one value being removed, return undefined
and the Metric
will be completely recalculated. One example might be if you were taking the minimum of the values, and the previous minimum is being removed. The whole of the rest of the list will need to be re-scanned to find a new minimum.
When possible, if you are providing a custom MetricAggregate
, seek an implementation of an MetricAggregateRemove
function that can reduce the complexity cost of shrinking the input data set. See the setMetricDefinition
method for more examples.
Since
v1.0.0
MetricAggregateReplace
The MetricAggregateReplace
type describes a function that can be used to optimize a custom MetricAggregate
by providing a shortcut for when a single value in the input values is replaced with another.
(
metric: Metric,
add: number,
remove: number,
length: number,
): Metric | undefined
Type | Description | |
---|---|---|
metric | Metric | The current value of the |
add | number | The number being added to the |
remove | number | The number being removed from the |
length | number | The length of the array of numbers in the |
returns | Metric | undefined |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when replacing a number in a series, the new sum of the series is the previous sum, plus the new value, minus the old value.
If it is not possible to shortcut the aggregation based on just one value changing, return undefined
and the Metric
will be completely recalculated.
When possible, if you are providing a custom MetricAggregate
, seek an implementation of an MetricAggregateReplace
function that can reduce the complexity cost of changing the input data set in place. See the setMetricDefinition
method for more examples.
Since
v1.0.0
Callback type aliases
This is the collection of callback type aliases within the metrics
module. There is only one type alias, MetricCallback
.
MetricCallback
The MetricCallback
type describes a function that takes a Metric
's Id
and a callback to loop over each Row
within it.
(
metricId: Id,
metric?: Metric,
): void
A MetricCallback
is provided when using the forEachMetric
method, so that you can do something based on every Metric
in the Metrics
object. See that method for specific examples.
Since
v1.0.0
Metric type aliases
This is the collection of metric type aliases within the metrics
module. There is only one type alias, Metric
.
Metric
The Metric
type is simply an alias, but represents a number formed by aggregating multiple other numbers together.
number
Since
v1.0.0
Development type aliases
This is the collection of development type aliases within the metrics
module. There is only one type alias, MetricsListenerStats
.
MetricsListenerStats
The MetricsListenerStats
type describes the number of listeners registered with the Metrics
object, and can be used for debugging purposes.
{metric: number}
Type | Description | |
---|---|---|
metric | number | The number of |
A MetricsListenerStats
object is returned from the getListenerStats
method.
Since
v1.0.0
indexes
The indexes
module of the TinyBase project provides the ability to create and track indexes of the data in Store
objects.
The main entry point to this module is the createIndexes
function, which returns a new Indexes
object. From there, you can create new Index
definitions, access the contents of those Indexes
directly, and register listeners for when they change.
Since
v1.0.0
Interfaces
There is one interface, Indexes
, within the indexes
module.
Indexes
An Indexes
object lets you look up all the Row
objects in a Table
that have a certain Cell
value.
This is useful for creating filtered views of a Table
, or simple search functionality.
Create an Indexes
object easily with the createIndexes
function. From there, you can add new Index
definitions (with the setIndexDefinition
method), query their contents (with the getSliceIds
method and getSliceRowIds
method), and add listeners for when they change (with the addSliceIdsListener
method and addSliceRowIdsListener
method).
This module defaults to indexing Row
objects by one of their Cell
values. However, far more complex indexes can be configured with a custom function.
Example
This example shows a very simple lifecycle of an Indexes
object: from creation, to adding a definition, getting its contents, and then registering and removing a listener for it.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
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', 'cujo']
const listenerId = indexes.addSliceIdsListener('bySpecies', () => {
console.log(indexes.getSliceIds('bySpecies'));
});
store.setRow('pets', 'lowly', {species: 'worm'});
// -> ['dog', 'cat', 'worm']
indexes.delListener(listenerId);
indexes.destroy();
See also
- Using Indexes guides
- Rolling Dice demos
- Country demo
- Todo App demos
- Word Frequencies demo
Since
v1.0.0
Getter methods
This is the collection of getter methods within the Indexes
interface. There are 7 getter methods in total.
getStore
The getStore
method returns a reference to the underlying Store
that is backing this Indexes
object.
getStore(): Store
Example
This example creates an Indexes
object against a newly-created Store
and then gets its reference in order to update its data.
import {createIndexes, createStore} from 'tinybase';
const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
indexes.getStore().setCell('pets', 'fido', 'species', 'dog');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog']
Since
v1.0.0
getTableId
The getTableId
method returns the Id
of the underlying Table
that is backing an Index
.
getTableId(indexId: string): undefined | string
Type | Description | |
---|---|---|
indexId | string | |
returns | undefined | string |
If the Index
Id
is invalid, the method returns undefined
.
Example
This example creates an Indexes
object, a single Index
definition, and then queries it (and a non-existent definition) to get the underlying Table
Id
.
import {createIndexes, createStore} from 'tinybase';
const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getTableId('bySpecies'));
// -> 'pets'
console.log(indexes.getTableId('byColor'));
// -> undefined
Since
v1.0.0
getSliceRowIds
The getSliceRowIds
method gets the list of Row
Ids
in a given Slice
, within a given Index
.
getSliceRowIds(
indexId: string,
sliceId: string,
): Ids
Type | Description | |
---|---|---|
indexId | string | |
sliceId | string | |
returns | Ids |
If the identified Index
or Slice
do not exist (or if the definition references a Table
that does not exist) then an empty array is returned.
Example
This example creates a Store
, creates an Indexes
object, and defines a simple Index
. It then uses getSliceRowIds to see the Row
Ids
in the Slice
(and also the Row
Ids
in Slices that do not exist).
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']
console.log(indexes.getSliceRowIds('bySpecies', 'worm'));
// -> []
console.log(indexes.getSliceRowIds('byColor', 'brown'));
// -> []
Since
v1.0.0
getIndexIds
The getIndexIds
method returns an array of the Index
Ids
registered with this Indexes
object.
getIndexIds(): Ids
Example
This example creates an Indexes
object with two definitions, and then gets the Ids
of the definitions.
import {createIndexes, createStore} from 'tinybase';
const indexes = createIndexes(createStore())
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
console.log(indexes.getIndexIds());
// -> ['bySpecies', 'byColor']
Since
v1.0.0
getSliceIds
The getSliceIds
method gets the list of Slice
Ids
in an Index
.
getSliceIds(indexId: string): Ids
Type | Description | |
---|---|---|
indexId | string | |
returns | Ids |
If the identified Index
does not exist (or if the definition references a Table
that does not exist) then an empty array is returned.
Example
This example creates a Store
, creates an Indexes
object, and defines a simple Index
. It then uses getSliceIds to see the available Slice
Ids
in the Index
(and also the Slice
Ids
in an Index
that has not been defined).
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceIds('byColor'));
// -> []
Since
v1.0.0
hasIndex
The hasIndex
method returns a boolean indicating whether a given Index
exists in the Indexes
object.
hasIndex(indexId: string): boolean
Type | Description | |
---|---|---|
indexId | string | |
returns | boolean |
Example
This example shows two simple Index
existence checks.
import {createIndexes, createStore} from 'tinybase';
const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.hasIndex('bySpecies'));
// -> true
console.log(indexes.hasIndex('byColor'));
// -> false
Since
v1.0.0
hasSlice
The hasSlice
method returns a boolean indicating whether a given Slice
exists in the Indexes
object.
hasSlice(
indexId: string,
sliceId: string,
): boolean
Type | Description | |
---|---|---|
indexId | string | |
sliceId | string | |
returns | boolean |
Example
This example shows two simple Index
existence checks.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.hasSlice('bySpecies', 'dog'));
// -> true
console.log(indexes.hasSlice('bySpecies', 'worm'));
// -> false
Since
v1.0.0
Listener methods
This is the collection of listener methods within the Indexes
interface. There are 4 listener methods in total.
addSliceRowIdsListener
The addSliceRowIdsListener
method registers a listener function with the Indexes
object that will be called whenever the Row
Ids
in a Slice
change.
addSliceRowIdsListener(
indexId: IdOrNull,
sliceId: IdOrNull,
listener: SliceRowIdsListener,
): string
Type | Description | |
---|---|---|
indexId | IdOrNull | |
sliceId | IdOrNull | |
listener | SliceRowIdsListener | The function that will be called whenever the |
returns | string | A unique |
You can either listen to a single Slice
(by specifying the Index
Id
and Slice
Id
as the method's first two parameters), or changes to any Slice
(by providing null
wildcards).
Both, either, or neither of the indexId
and sliceId
parameters can be wildcarded with null
. You can listen to a specific Slice
in a specific Index
, any Slice
in a specific Index
, a specific Slice
in any Index
, or any Slice
in any Index
.
The provided listener is a SliceRowIdsListener
function, and will be called with a reference to the Indexes
object, the Id
of the Index
, and the Id
of the Slice
that changed.
Examples
This example creates a Store
, an Indexes
object, and then registers a listener that responds to any changes to a specific Slice
.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const listenerId = indexes.addSliceRowIdsListener(
'bySpecies',
'dog',
(indexes) => {
console.log('Row Ids for dog slice in bySpecies index changed');
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Row Ids for dog slice in bySpecies index changed'
// -> ['fido', 'cujo', 'toto']
indexes.delListener(listenerId);
This example creates a Store
, an Indexes
object, and then registers a listener that responds to any changes to any Slice
.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const indexes = createIndexes(store)
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
const listenerId = indexes.addSliceRowIdsListener(
null,
null,
(indexes, indexId, sliceId) => {
console.log(
`Row Ids for ${sliceId} slice in ${indexId} index changed`,
);
console.log(indexes.getSliceRowIds(indexId, sliceId));
},
);
store.setRow('pets', 'toto', {species: 'dog', color: 'brown'});
// -> 'Row Ids for dog slice in bySpecies index changed'
// -> ['fido', 'cujo', 'toto']
// -> 'Row Ids for brown slice in byColor index changed'
// -> ['fido', 'toto']
indexes.delListener(listenerId);
Since
v1.0.0
addIndexIdsListener
The addIndexIdsListener
method registers a listener function with the Indexes
object that will be called whenever an Index
definition is added or removed.
addIndexIdsListener(listener: IndexIdsListener): string
Type | Description | |
---|---|---|
listener | IndexIdsListener | The function that will be called whenever an |
returns | string |
The provided listener is an IndexIdsListener
function, and will be called with a reference to the Indexes
object.
Example
This example creates a Store
, an Indexes
object, and then registers a listener that responds to the addition and the removal of an Index
definition.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
const listenerId = indexes.addIndexIdsListener((indexes) => {
console.log(indexes.getIndexIds());
});
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
// -> ['bySpecies']
indexes.delIndexDefinition('bySpecies');
// -> []
indexes.delListener(listenerId);
Since
v4.1.0
addSliceIdsListener
The addSliceIdsListener
method registers a listener function with the Indexes
object that will be called whenever the Slice
Ids
in an Index
change.
addSliceIdsListener(
indexId: IdOrNull,
listener: SliceIdsListener,
): string
Type | Description | |
---|---|---|
indexId | IdOrNull | |
listener | SliceIdsListener | The function that will be called whenever the |
returns | string | A unique |
You can either listen to a single Index
(by specifying the Index
Id
as the method's first parameter), or changes to any Index
(by providing a null
wildcard).
The provided listener is a SliceIdsListener
function, and will be called with a reference to the Indexes
object, and the Id
of the Index
that changed.
Examples
This example creates a Store
, an Indexes
object, and then registers a listener that responds to any changes to a specific Index
.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const listenerId = indexes.addSliceIdsListener('bySpecies', (indexes) => {
console.log('Slice Ids for bySpecies index changed');
console.log(indexes.getSliceIds('bySpecies'));
});
store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids for bySpecies index changed'
// -> ['dog', 'cat', 'worm']
indexes.delListener(listenerId);
This example creates a Store
, an Indexes
object, and then registers a listener that responds to any changes to any Index
.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
});
const indexes = createIndexes(store)
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
const listenerId = indexes.addSliceIdsListener(
null,
(indexes, indexId) => {
console.log(`Slice Ids for ${indexId} index changed`);
console.log(indexes.getSliceIds(indexId));
},
);
store.setRow('pets', 'lowly', {species: 'worm', color: 'pink'});
// -> 'Slice Ids for bySpecies index changed'
// -> ['dog', 'cat', 'worm']
// -> 'Slice Ids for byColor index changed'
// -> ['brown', 'black', 'pink']
indexes.delListener(listenerId);
Since
v1.0.0
delListener
The delListener
method removes a listener that was previously added to the Indexes
object.
delListener(listenerId: string): Indexes
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Indexes | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Indexes
object may re-use this Id
for future listeners added to it.
Example
This example creates a Store
, an Indexes
object, registers a listener, and then removes it.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const listenerId = indexes.addSliceIdsListener('bySpecies', () => {
console.log('Slice Ids for bySpecies index changed');
});
store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids for bySpecies index changed'
indexes.delListener(listenerId);
store.setRow('pets', 'toto', {species: 'dog'});
// -> undefined
// The listener is not called.
Since
v1.0.0
Configuration methods
This is the collection of configuration methods within the Indexes
interface. There are only two configuration methods, delIndexDefinition
and setIndexDefinition
.
delIndexDefinition
The delIndexDefinition
method removes an existing Index
definition.
delIndexDefinition(indexId: string): Indexes
Example
This example creates a Store
, creates an Indexes
object, defines a simple Index
, and then removes it.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getIndexIds());
// -> ['bySpecies']
indexes.delIndexDefinition('bySpecies');
console.log(indexes.getIndexIds());
// -> []
Since
v1.0.0
setIndexDefinition
The setIndexDefinition
method lets you set the definition of an Index
.
setIndexDefinition(
indexId: string,
tableId: string,
getSliceIdOrIds?: string | (getCell: GetCell, rowId: string) => string | Ids,
getSortKey?: string | (getCell: GetCell, rowId: string) => SortKey,
sliceIdSorter?: (sliceId1: string, sliceId2: string) => number,
rowIdSorter?: (sortKey1: SortKey, sortKey2: SortKey, sliceId: string) => number,
): Indexes
Type | Description | |
---|---|---|
indexId | string | |
tableId | string | |
getSliceIdOrIds? | string | (getCell: GetCell, rowId: string) => string | Ids | Either the |
getSortKey? | string | (getCell: GetCell, rowId: string) => SortKey | Either the |
sliceIdSorter? | (sliceId1: string, sliceId2: string) => number | A function that takes two |
rowIdSorter? | (sortKey1: SortKey, sortKey2: SortKey, sliceId: string) => number | A function that takes two |
returns | Indexes | A reference to the |
Every Index
definition is identified by a unique Id
, and if you re-use an existing Id
with this method, the previous definition is overwritten.
An Index
is a keyed map of Slice
objects, each of which is a list of Row
Ids
from a given Table
. Therefore the definition must specify the Table
(by its Id
) to be indexed.
The Ids
in a Slice
represent Row
objects from a Table
that all have a derived string value in common, as described by this method. Those values are used as the key for each Slice
in the overall Index
object.
Without the third getSliceIdOrIds
parameter, the Index
will simply have a single Slice
, keyed by an empty string. But more often you will specify a Cell
value containing the Slice
Id
that the Row
should belong to. Alternatively, a custom function can be provided that produces your own Slice
Id
from the local Row
as a whole. Since v2.1, the custom function can return an array of Slice
Ids
, each of which the Row
will then belong to.
The fourth getSortKey
parameter specifies a Cell
Id
to get a value (or a function that processes a whole Row
to get a value) that is used to sort the Row
Ids
within each Slice
in the Index
.
The fifth parameter, sliceIdSorter
, lets you specify a way to sort the Slice
Ids
when you access the Index
, which may be useful if you are trying to create an alphabetic Index
of Row
entries. If not specified, the order of the Slice
Ids
will match the order of Row
insertion.
The final parameter, rowIdSorter
, lets you specify a way to sort the Row
Ids
within each Slice
, based on the getSortKey
parameter. This may be useful if you are trying to keep Rows in a determined order relative to each other in the Index
. If omitted, the Row
Ids
are sorted alphabetically, based on the getSortKey
parameter.
The two 'sorter' parameters, sliceIdSorter
and rowIdSorter
, are functions that take two values and return a positive or negative number for when they are in the wrong or right order, respectively. This is exactly the same as the 'compareFunction' that is used in the standard JavaScript array sort
method, with the addition that rowIdSorter
also takes the Slice
Id
parameter, in case you want to sort Row
Ids
differently in each Slice
. You can use the convenient defaultSorter
function to default this to be alphanumeric.
Examples
This example creates a Store
, creates an Indexes
object, and defines a simple Index
based on the values in the species
Cell
.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']
This example creates a Store
, creates an Indexes
object, and defines an Index
based on the first letter of the pets' names.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('byFirst', 'pets', (_, rowId) => rowId[0]);
console.log(indexes.getSliceIds('byFirst'));
// -> ['f', 'c']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['fido', 'felix']
This example creates a Store
, creates an Indexes
object, and defines an Index
based on each of the letters present in the pets' names.
import {createIndexes, createStore} from 'tinybase';
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 example creates a Store
, creates an Indexes
object, and defines an Index
based on the first letter of the pets' names. The Slice
Ids
(and Row
Ids
within them) are alphabetically sorted.
import {createIndexes, createStore, defaultSorter} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition(
'byFirst', // indexId
'pets', // tableId
(_, rowId) => rowId[0], // each Row's sliceId
(_, rowId) => rowId, // each Row's sort key
defaultSorter, // sort Slice Ids
defaultSorter, // sort Row Ids
);
console.log(indexes.getSliceIds('byFirst'));
// -> ['c', 'f']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['felix', 'fido']
Since
v1.0.0
Iterator methods
This is the collection of iterator methods within the Indexes
interface. There are only two iterator methods, forEachIndex
and forEachSlice
.
forEachIndex
The forEachIndex
method takes a function that it will then call for each Index
in a specified Indexes
object.
forEachIndex(indexCallback: IndexCallback): void
Type | Description | |
---|---|---|
indexCallback | IndexCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the structure of the Indexes
object in a functional style. The indexCallback
parameter is a IndexCallback
function that will be called with the Id
of each Index
, and with a function that can then be used to iterate over each Slice
of the Index
, should you wish.
Example
This example iterates over each Index
in an Indexes
object, and lists each Slice
Id
within them.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const indexes = createIndexes(store)
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
indexes.forEachIndex((indexId, forEachSlice) => {
console.log(indexId);
forEachSlice((sliceId) => console.log(`- ${sliceId}`));
});
// -> 'bySpecies'
// -> '- dog'
// -> '- cat'
// -> 'byColor'
// -> '- brown'
// -> '- black'
Since
v1.0.0
forEachSlice
The forEachSlice
method takes a function that it will then call for each Slice
in a specified Index
.
forEachSlice(
indexId: string,
sliceCallback: SliceCallback,
): void
Type | Description | |
---|---|---|
indexId | string | |
sliceCallback | SliceCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the Slice
structure of the Index
in a functional style. The rowCallback
parameter is a RowCallback
function that will be called with the Id
and value of each Row
in the Slice
.
Example
This example iterates over each Row
in a Slice
, and lists its Id
.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
indexes.forEachSlice('bySpecies', (sliceId, forEachRow) => {
console.log(sliceId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'dog'
// -> '- fido'
// -> '- cujo'
// -> 'cat'
// -> '- felix'
Since
v1.0.0
Lifecycle methods
This is the collection of lifecycle methods within the Indexes
interface. There is only one method, destroy
.
destroy
The destroy
method should be called when this Indexes
object is no longer used.
destroy(): void
This guarantees that all of the listeners that the object registered with the underlying Store
are removed and it can be correctly garbage collected.
Example
This example creates a Store
, adds an Indexes
object with a definition (that registers a RowListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createIndexes, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(store.getListenerStats().row);
// -> 1
indexes.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v1.0.0
Development methods
This is the collection of development methods within the Indexes
interface. There is only one method, getListenerStats
.
getListenerStats
The getListenerStats
method provides a set of statistics about the listeners registered with the Indexes
object, and is used for debugging purposes.
getListenerStats(): IndexesListenerStats
returns | IndexesListenerStats | A |
---|
The IndexesListenerStats
object contains a breakdown of the different types of listener.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of an Indexes
object.
import {createIndexes, createStore} from 'tinybase';
const store = createStore();
const indexes = createIndexes(store);
indexes.addSliceIdsListener(null, () => {
console.log('Slice Ids changed');
});
indexes.addSliceRowIdsListener(null, null, () => {
console.log('Slice Row Ids changed');
});
console.log(indexes.getListenerStats());
// -> {sliceIds: 1, sliceRowIds: 1}
Since
v1.0.0
Functions
There is one function, createIndexes
, within the indexes
module.
createIndexes
The createIndexes
function creates an Indexes
object, and is the main entry point into the indexes
module.
createIndexes(store: Store): Indexes
A given Store
can only have one Indexes
object associated with it. If you call this function twice on the same Store
, your second call will return a reference to the Indexes
object created by the first.
Examples
This example creates an Indexes
object.
import {createIndexes, createStore} from 'tinybase';
const store = createStore();
const indexes = createIndexes(store);
console.log(indexes.getIndexIds());
// -> []
This example creates an Indexes
object, and calls the method a second time for the same Store
to return the same object.
import {createIndexes, createStore} from 'tinybase';
const store = createStore();
const indexes1 = createIndexes(store);
const indexes2 = createIndexes(store);
console.log(indexes1 === indexes2);
// -> true
Since
v1.0.0
Type Aliases
These are the type aliases within the indexes
module.
Listener type aliases
This is the collection of listener type aliases within the indexes
module. There are only three listener type aliases, SliceRowIdsListener
, IndexIdsListener
, and SliceIdsListener
.
SliceRowIdsListener
The SliceRowIdsListener
type describes a function that is used to listen to changes to the Row
Ids
in a Slice
.
(
indexes: Indexes,
indexId: Id,
sliceId: Id,
): void
Type | Description | |
---|---|---|
indexes | Indexes | A reference to the |
indexId | Id | |
sliceId | Id | |
returns | void | This has no return value. |
A SliceRowIdsListener
is provided when using the addSliceRowIdsListener
method. See that method for specific examples.
When called, a SliceRowIdsListener
is given a reference to the Indexes
object, the Id
of the Index
that changed, and the Id
of the Slice
whose Row
Ids
changed.
Since
v1.0.0
IndexIdsListener
The IndexIdsListener
type describes a function that is used to listen to Index
definitions being added or removed.
(indexes: Indexes): void
Type | Description | |
---|---|---|
indexes | Indexes | A reference to the |
returns | void | This has no return value. |
A IndexIdsListener
is provided when using the addIndexIdsListener
method. See that method for specific examples.
When called, an IndexIdsListener
is given a reference to the Indexes
object.
Since
v1.0.0
SliceIdsListener
The SliceIdsListener
type describes a function that is used to listen to changes to the Slice
Ids
in an Index
.
(
indexes: Indexes,
indexId: Id,
): void
Type | Description | |
---|---|---|
indexes | Indexes | A reference to the |
indexId | Id | |
returns | void | This has no return value. |
A SliceIdsListener
is provided when using the addSliceIdsListener
method. See that method for specific examples.
When called, a SliceIdsListener
is given a reference to the Indexes
object, and the Id
of the Index
that changed.
Since
v1.0.0
Callback type aliases
This is the collection of callback type aliases within the indexes
module. There are only two callback type aliases, IndexCallback
and SliceCallback
.
IndexCallback
The IndexCallback
type describes a function that takes an Index
's Id
and a callback to loop over each Slice
within it.
(
indexId: Id,
forEachSlice: (sliceCallback: SliceCallback) => void,
): void
Type | Description | |
---|---|---|
indexId | Id | |
forEachSlice | (sliceCallback: SliceCallback) => void | |
returns | void | This has no return value. |
A IndexCallback
is provided when using the forEachIndex
method, so that you can do something based on every Index
in the Indexes
object. See that method for specific examples.
Since
v1.0.0
SliceCallback
The SliceCallback
type describes a function that takes a Slice
's Id
and a callback to loop over each Row
within it.
(
sliceId: Id,
forEachRow: (rowCallback: RowCallback) => void,
): void
Type | Description | |
---|---|---|
sliceId | Id | |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the |
returns | void | This has no return value. |
A SliceCallback
is provided when using the forEachSlice
method, so that you can do something based on every Slice
in an Index
. See that method for specific examples.
Since
v1.0.0
Concept type aliases
This is the collection of concept type aliases within the indexes
module. There are only two concept type aliases, Index
and Slice
.
Index
The Index
type represents the concept of a map of Slice
objects, keyed by Id
.
{[sliceId: Id]: Slice}
The Ids
in a Slice
represent Row
objects from a Table
that all have a derived string value in common, as described by the setIndexDefinition
method. Those values are used as the key for each Slice
in the overall Index
object.
Note that the Index
type is not actually used in the API, and you instead enumerate and access its structure with the getSliceIds
method and getSliceRowIds
method.
Since
v1.0.0
Slice
The Slice
type represents the concept of a set of Row
objects that comprise part of an Index
.
Ids
The Ids
in a Slice
represent Row
objects from a Table
that all have a derived string value in common, as described by the setIndexDefinition
method.
Note that the Slice
type is not actually used in the API, and you instead get Row
Ids
directly with the getSliceRowIds
method.
Since
v1.0.0
Development type aliases
This is the collection of development type aliases within the indexes
module. There is only one type alias, IndexesListenerStats
.
IndexesListenerStats
The IndexesListenerStats
type describes the number of listeners registered with the Indexes
object, and can be used for debugging purposes.
{
sliceIds: number;
sliceRowIds: number;
}
Type | Description | |
---|---|---|
sliceIds | number | The number of SlideIdsListener functions registered with the |
sliceRowIds | number | The number of |
A IndexesListenerStats
object is returned from the getListenerStats
method.
Since
v1.0.0
relationships
The relationships
module of the TinyBase project provides the ability to create and track relationships between the data in Store
objects.
The main entry point to this module is the createRelationships
function, which returns a new Relationships
object. From there, you can create new Relationship
definitions, access the associations within those Relationships
directly, and register listeners for when they change.
Since
v1.0.0
Interfaces
There is one interface, Relationships
, within the relationships
module.
Relationships
A Relationships
object lets you associate a Row
in a one Table
with the Id
of a Row
in another Table
.
This is useful for creating parent-child relationships between the data in different Table
objects, but it can also be used to model a linked list of Row
objects in the same Table
.
Create a Relationships
object easily with the createRelationships
function. From there, you can add new Relationship
definitions (with the setRelationshipDefinition
method), query their contents (with the getRemoteRowId
method, the getLocalRowIds
method, and the getLinkedRowIds
method), and add listeners for when they change (with the addRemoteRowIdListener
method, the addLocalRowIdsListener
method, and the addLinkedRowIdsListener
method).
This module defaults to creating relationships between Row
objects by using one of their Cell
values. However, far more complex relationships can be configured with a custom function.
Example
This example shows a very simple lifecycle of a Relationships
object: from creation, to adding definitions (both local/remote table and linked list), getting their contents, and then registering and removing listeners for them.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
// A local/remote table relationship:
relationships.setRelationshipDefinition(
'petSpecies', // relationshipId
'pets', // localTableId to link from
'species', // remoteTableId to link to
'species', // cellId containing remote key
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
// A linked list relationship:
relationships.setRelationshipDefinition(
'petSequence', // relationshipId
'pets', // localTableId to link from
'pets', // the same remoteTableId to link within
'next', // cellId containing link key
);
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
const listenerId1 = relationships.addLocalRowIdsListener(
'petSpecies',
'dog',
() => {
console.log('petSpecies relationship (to dog) changed');
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
},
);
const listenerId2 = relationships.addLinkedRowIdsListener(
'petSequence',
'fido',
() => {
console.log('petSequence linked list (from fido) changed');
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'cujo', 'toto']
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'petSequence linked list (from fido) changed'
// -> ['fido', 'felix', 'cujo', 'toto']
relationships.delListener(listenerId1);
relationships.delListener(listenerId2);
relationships.destroy();
See also
- Using Relationships guide
- Drawing demo
Since
v1.0.0
Getter methods
This is the collection of getter methods within the Relationships
interface. There are 8 getter methods in total.
getStore
The getStore
method returns a reference to the underlying Store
that is backing this Relationships
object.
getStore(): Store
Example
This example creates a Relationships
object against a newly-created Store
and then gets its reference in order to update its data.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
relationships.getStore().setCell('pets', 'fido', 'species', 'dog');
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
Since
v1.0.0
getLocalTableId
The getLocalTableId
method returns the Id
of the underlying local Table
that is used in the Relationship
.
getLocalTableId(relationshipId: string): undefined | string
Type | Description | |
---|---|---|
relationshipId | string | The |
returns | undefined | string | The |
If the Relationship
Id
is invalid, the method returns undefined
.
Example
This example creates a Relationship
object, a single Relationship
definition, and then queries it (and a non-existent definition) to get the underlying local Table
Id
.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getLocalTableId('petSpecies'));
// -> 'pets'
console.log(relationships.getLocalTableId('petColor'));
// -> undefined
Since
v1.0.0
getRemoteTableId
The getRemoteTableId
method returns the Id
of the underlying remote Table
that is used in the Relationship
.
getRemoteTableId(relationshipId: string): undefined | string
Type | Description | |
---|---|---|
relationshipId | string | The |
returns | undefined | string | The |
If the Relationship
Id
is invalid, the method returns undefined
.
Example
This example creates a Relationship
object, a single Relationship
definition, and then queries it (and a non-existent definition) to get the underlying remote Table
Id
.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getRemoteTableId('petSpecies'));
// -> 'species'
console.log(relationships.getRemoteTableId('petColor'));
// -> undefined
Since
v1.0.0
getLinkedRowIds
The getLinkedRowIds
method gets the linked Row
Ids
for a given Row
in a linked list Relationship
.
getLinkedRowIds(
relationshipId: string,
firstRowId: string,
): Ids
Type | Description | |
---|---|---|
relationshipId | string | The |
firstRowId | string | The |
returns | Ids | The linked |
A linked list Relationship
is one that has the same Table
specified as both local Table
Id
and remote Table
Id
, allowing you to create a sequence of Row
objects within that one Table
.
If the identified Relationship
or Row
does not exist (or if the definition references a Table
that does not exist) then an array containing just the first Row
Id
is returned.
Example
This example creates a Store
, creates a Relationships
object, and defines a simple linked list Relationship
. It then uses getLinkedRowIds to see the linked Row
Ids
in the Relationship
(and also the linked Row
Ids
for a Row
that does not exist, and for a Relationship
that has not been defined).
import {createRelationships, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'felix'));
// -> ['felix', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'toto'));
// -> ['toto']
console.log(relationships.getLinkedRowIds('petFriendships', 'fido'));
// -> ['fido']
Since
v1.0.0
getLocalRowIds
The getLocalRowIds
method gets the local Row
Ids
for a given remote Row
in a Relationship
.
getLocalRowIds(
relationshipId: string,
remoteRowId: string,
): Ids
Type | Description | |
---|---|---|
relationshipId | string | The |
remoteRowId | string | The |
returns | Ids | The local |
If the identified Relationship
or Row
does not exist (or if the definition references a Table
that does not exist) then an empty array is returned.
Example
This example creates a Store
, creates a Relationships
object, and defines a simple Relationship
. It then uses getLocalRowIds to see the local Row
Ids
in the Relationship
(and also the local Row
Ids
for a remote Row
that does not exist, and for a Relationship
that has not been defined).
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
console.log(relationships.getLocalRowIds('petSpecies', 'worm'));
// -> []
console.log(relationships.getLocalRowIds('petColor', 'brown'));
// -> []
Since
v1.0.0
getRemoteRowId
The getRemoteRowId
method gets the remote Row
Id
for a given local Row
in a Relationship
.
getRemoteRowId(
relationshipId: string,
localRowId: string,
): undefined | string
Type | Description | |
---|---|---|
relationshipId | string | The |
localRowId | string | The |
returns | undefined | string | The remote |
If the identified Relationship
or Row
does not exist (or if the definition references a Table
that does not exist) then undefined
is returned.
Example
This example creates a Store
, creates a Relationships
object, and defines a simple Relationship
. It then uses getRemoteRowId to see the remote Row
Id
in the Relationship
(and also the remote Row
Ids
for a local Row
that does not exist, and for a Relationship
that has not been defined).
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getRemoteRowId('petSpecies', 'toto'));
// -> undefined
console.log(relationships.getRemoteRowId('petColor', 'fido'));
// -> undefined
Since
v1.0.0
getRelationshipIds
The getRelationshipIds
method returns an array of the Relationship
Ids
registered with this Relationships
object.
getRelationshipIds(): Ids
Example
This example creates a Relationships
object with two definitions, and then gets the Ids
of the definitions.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(createStore())
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
console.log(relationships.getRelationshipIds());
// -> ['petSpecies', 'petSequence']
Since
v1.0.0
hasRelationship
The hasRelationship
method returns a boolean indicating whether a given Relationship
exists in the Relationships
object.
hasRelationship(relationshipId: string): boolean
Type | Description | |
---|---|---|
relationshipId | string | The |
returns | boolean | Whether a |
Example
This example shows two simple Relationship
existence checks.
import {createRelationships, createStore} from 'tinybase';
const relationships = createRelationships(
createStore(),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
console.log(relationships.hasRelationship('petSpecies'));
// -> true
console.log(relationships.hasRelationship('petColor'));
// -> false
Since
v1.0.0
Listener methods
This is the collection of listener methods within the Relationships
interface. There are 5 listener methods in total.
addLinkedRowIdsListener
The addLinkedRowIdsListener
method registers a listener function with the Relationships
object that will be called whenever the linked Row
Ids
in a linked list Relationship
change.
addLinkedRowIdsListener(
relationshipId: string,
firstRowId: string,
listener: LinkedRowIdsListener,
): string
Type | Description | |
---|---|---|
relationshipId | string | The |
firstRowId | string | |
listener | LinkedRowIdsListener | The function that will be called whenever the linked |
returns | string | A unique |
A linked list Relationship
is one that has the same Table
specified as both local Table
Id
and remote Table
Id
, allowing you to create a sequence of Row
objects within that one Table
.
You listen to changes to a linked list starting from a single first Row
by specifying the Relationship
Id
and local Row
Id
as the method's first two parameters.
Unlike other listener registration methods, you cannot provide null
wildcards for the first two parameters of the addLinkedRowIdsListener
method. This prevents the prohibitive expense of tracking all the possible linked lists (and partial linked lists within them) in a Store
.
The provided listener is a LinkedRowIdsListener
function, and will be called with a reference to the Relationships
object, the Id
of the Relationship
, and the Id
of the first Row
that had its linked list change.
Example
This example creates a Store
, a Relationships
object, and then registers a listener that responds to any changes to a specific first Row
's linked Row
objects.
import {createRelationships, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const listenerId = relationships.addLinkedRowIdsListener(
'petSequence',
'fido',
(relationships) => {
console.log('petSequence linked list (from fido) changed');
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'petSequence linked list (from fido) changed'
// -> ['fido', 'felix', 'cujo', 'toto']
relationships.delListener(listenerId);
Since
v1.0.0
addLocalRowIdsListener
The addLocalRowIdsListener
method registers a listener function with the Relationships
object that will be called whenever the local Row
Ids
in a Relationship
change.
addLocalRowIdsListener(
relationshipId: IdOrNull,
remoteRowId: IdOrNull,
listener: LocalRowIdsListener,
): string
Type | Description | |
---|---|---|
relationshipId | IdOrNull | The |
remoteRowId | IdOrNull | The |
listener | LocalRowIdsListener | The function that will be called whenever the local |
returns | string | A unique |
You can either listen to a single local Row
(by specifying the Relationship
Id
and local Row
Id
as the method's first two parameters), or changes to any local Row
(by providing a null
wildcards).
Both, either, or neither of the relationshipId
and remoteRowId
parameters can be wildcarded with null
. You can listen to a specific remote Row
in a specific Relationship
, any remote Row
in a specific Relationship
, a specific remote Row
in any Relationship
, or any remote Row
in any Relationship
.
The provided listener is a LocalRowIdsListener
function, and will be called with a reference to the Relationships
object, the Id
of the Relationship
, and the Id
of the remote Row
that had its local Row
objects change.
Examples
This example creates a Store
, a Relationships
object, and then registers a listener that responds to any changes to a specific remote Row
's local Row
objects.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const listenerId = relationships.addLocalRowIdsListener(
'petSpecies',
'dog',
(relationships) => {
console.log('petSpecies relationship (to dog) changed');
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'cujo', 'toto']
relationships.delListener(listenerId);
This example creates a Store
, a Relationships
object, and then registers a listener that responds to any changes to any remote Row
's local Row
objects.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
toto: {species: 'dog', color: 'grey'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
})
.setTable('color', {
brown: {discount: 0.1},
black: {discount: 0},
grey: {discount: 0.2},
});
const relationships = createRelationships(store)
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petColor', 'pets', 'color', 'color');
const listenerId = relationships.addLocalRowIdsListener(
null,
null,
(relationships, relationshipId, remoteRowId) => {
console.log(
`${relationshipId} relationship (to ${remoteRowId}) changed`,
);
console.log(relationships.getLocalRowIds(relationshipId, remoteRowId));
},
);
store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'toto']
// -> 'petSpecies relationship (to wolf) changed'
// -> ['cujo']
// -> 'petColor relationship (to brown) changed'
// -> ['fido']
// -> 'petColor relationship (to grey) changed'
// -> ['toto', 'cujo']
relationships.delListener(listenerId);
Since
v1.0.0
addRemoteRowIdListener
The addRemoteRowIdListener
method registers a listener function with the Relationships
object that will be called whenever a remote Row
Id
in a Relationship
changes.
addRemoteRowIdListener(
relationshipId: IdOrNull,
localRowId: IdOrNull,
listener: RemoteRowIdListener,
): string
Type | Description | |
---|---|---|
relationshipId | IdOrNull | The |
localRowId | IdOrNull | The |
listener | RemoteRowIdListener | The function that will be called whenever the remote |
returns | string | A unique |
You can either listen to a single local Row
(by specifying the Relationship
Id
and local Row
Id
as the method's first two parameters), or changes to any local Row
(by providing a null
wildcards).
Both, either, or neither of the relationshipId
and localRowId
parameters can be wildcarded with null
. You can listen to a specific local Row
in a specific Relationship
, any local Row
in a specific Relationship
, a specific local Row
in any Relationship
, or any local Row
in any Relationship
.
The provided listener is a RemoteRowIdListener
function, and will be called with a reference to the Relationships
object, the Id
of the Relationship
, and the Id
of the local Row
that had its remote Row
change.
Examples
This example creates a Store
, a Relationships
object, and then registers a listener that responds to any changes to a specific local Row
's remote Row
.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const listenerId = relationships.addRemoteRowIdListener(
'petSpecies',
'cujo',
(relationships) => {
console.log('petSpecies relationship (from cujo) changed');
console.log(relationships.getRemoteRowId('petSpecies', 'cujo'));
},
);
store.setCell('pets', 'cujo', 'species', 'wolf');
// -> 'petSpecies relationship (from cujo) changed'
// -> 'wolf'
relationships.delListener(listenerId);
This example creates a Store
, a Relationships
object, and then registers a listener that responds to any changes to any local Row
's remote Row
. It also illustrates how you can use the getStore
method and the getRemoteRowId
method to resolve the remote Row
as a whole.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
})
.setTable('color', {
brown: {discount: 0.1},
black: {discount: 0},
grey: {discount: 0.2},
});
const relationships = createRelationships(store)
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petColor', 'pets', 'color', 'color');
const listenerId = relationships.addRemoteRowIdListener(
null,
null,
(relationships, relationshipId, localRowId) => {
console.log(
`${relationshipId} relationship (from ${localRowId}) changed`,
);
console.log(relationships.getRemoteRowId(relationshipId, localRowId));
console.log(
relationships
.getStore()
.getRow(
relationships.getRemoteTableId(relationshipId),
relationships.getRemoteRowId(relationshipId, localRowId),
),
);
},
);
store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'});
// -> 'petSpecies relationship (from cujo) changed'
// -> 'wolf'
// -> {price: 10}
// -> 'petColor relationship (from cujo) changed'
// -> 'grey'
// -> {discount: 0.2}
relationships.delListener(listenerId);
Since
v1.0.0
addRelationshipIdsListener
The addRelationshipIdsListener
method registers a listener function with the Relationships
object that will be called whenever a Relationship
definition is added or removed.
addRelationshipIdsListener(listener: RelationshipIdsListener): string
Type | Description | |
---|---|---|
listener | RelationshipIdsListener | The function that will be called whenever a |
returns | string |
The provided listener is a RelationshipIdsListener
function, and will be called with a reference to the Relationships
object.
Example
This example creates a Store
, a Relationships
object, and then registers a listener that responds to the addition and the removal of a Relationship
definition.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
const listenerId = relationships.addRelationshipIdsListener(
(relationships) => {
console.log(relationships.getRelationshipIds());
},
);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
// -> ['petSpecies']
relationships.delRelationshipDefinition('petSpecies');
// -> []
relationships.delListener(listenerId);
Since
v4.1.0
delListener
The delListener
method removes a listener that was previously added to the Relationships
object.
delListener(listenerId: string): Relationships
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Relationships | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Relationships
object may re-use this Id
for future listeners added to it.
Example
This example creates a Store
, a Relationships
object, registers a listener, and then removes it.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const listenerId = relationships.addLocalRowIdsListener(
'petSpecies',
'dog',
() => {
console.log('petSpecies relationship (to dog) changed');
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
relationships.delListener(listenerId);
store.setRow('pets', 'toto', {species: 'dog'});
// -> undefined
// The listener is not called.
Since
v1.0.0
Configuration methods
This is the collection of configuration methods within the Relationships
interface. There are only two configuration methods, delRelationshipDefinition
and setRelationshipDefinition
.
delRelationshipDefinition
The delRelationshipDefinition
method removes an existing Relationship
definition.
delRelationshipDefinition(relationshipId: string): Relationships
Type | Description | |
---|---|---|
relationshipId | string | The |
returns | Relationships | A reference to the |
Example
This example creates a Store
, creates a Relationships
object, defines a simple Relationship
, and then removes it.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getRelationshipIds());
// -> ['petSpecies']
relationships.delRelationshipDefinition('petSpecies');
console.log(relationships.getRelationshipIds());
// -> []
Since
v1.0.0
setRelationshipDefinition
The setRelationshipDefinition
method lets you set the definition of a Relationship
.
setRelationshipDefinition(
relationshipId: string,
localTableId: string,
remoteTableId: string,
getRemoteRowId: string | (getCell: GetCell, localRowId: string) => string,
): Relationships
Type | Description | |
---|---|---|
relationshipId | string | The |
localTableId | string | The |
remoteTableId | string | The |
getRemoteRowId | string | (getCell: GetCell, localRowId: string) => string | Either the |
returns | Relationships | A reference to the |
Every Relationship
definition is identified by a unique Id
, and if you re-use an existing Id
with this method, the previous definition is overwritten.
An Relationship
is based on connections between Row
objects, often in two different Table
objects. Therefore the definition requires the localTableId
parameter to specify the 'local' Table
to create the Relationship
from, and the remoteTableId
parameter to specify the 'remote' Table
to create Relationship
to.
A linked list Relationship
is one that has the same Table
specified as both local Table
Id
and remote Table
Id
, allowing you to create a sequence of Row
objects within that one Table
.
A local Row
is related to a remote Row
by specifying which of its (local) Cell
values contains the (remote) Row
Id
, using the getRemoteRowId
parameter. Alternatively, a custom function can be provided that produces your own remote Row
Id
from the local Row
as a whole.
Examples
This example creates a Store
, creates a Relationships
object, and defines a simple Relationship
based on the values in the species
Cell
of the pets
Table
that relates a Row
to another in the species
Table
.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies', // relationshipId
'pets', // localTableId to link from
'species', // remoteTableId to link to
'species', // cellId containing remote key
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
This example creates a Store
, creates a Relationships
object, and defines a linked list Relationship
based on the values in the next
Cell
of the pets
Table
that relates a Row
to another in the same Table
.
import {createRelationships, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence', // relationshipId
'pets', // localTableId to link from
'pets', // the same remoteTableId to link within
'next', // cellId containing link key
);
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
Since
v1.0.0
Iterator methods
This is the collection of iterator methods within the Relationships
interface. There is only one method, forEachRelationship
.
forEachRelationship
The forEachRelationship
method takes a function that it will then call for each Relationship
in a specified Relationships
object.
forEachRelationship(relationshipCallback: RelationshipCallback): void
Type | Description | |
---|---|---|
relationshipCallback | RelationshipCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over the structure of the Relationships
object in a functional style. The relationshipCallback
parameter is a RelationshipCallback
function that will be called with the Id
of each Relationship
, and with a function that can then be used to iterate over each local Row
involved in the Relationship
.
Example
This example iterates over each Relationship
in a Relationships
object, and lists each Row
Id
within them.
import {createRelationships, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store)
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
relationships.forEachRelationship((relationshipId, forEachRow) => {
console.log(relationshipId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'petSpecies'
// -> '- fido'
// -> '- felix'
// -> '- cujo'
// -> 'petSequence'
// -> '- fido'
// -> '- felix'
// -> '- cujo'
Since
v1.0.0
Lifecycle methods
This is the collection of lifecycle methods within the Relationships
interface. There is only one method, destroy
.
destroy
The destroy
method should be called when this Relationships
object is no longer used.
destroy(): void
This guarantees that all of the listeners that the object registered with the underlying Store
are removed and it can be correctly garbage collected.
Example
This example creates a Store
, adds a Relationships
object with a definition (that registers a RowListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createRelationships, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(store.getListenerStats().row);
// -> 1
relationships.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v1.0.0
Development methods
This is the collection of development methods within the Relationships
interface. There is only one method, getListenerStats
.
getListenerStats
The getListenerStats
method provides a set of statistics about the listeners registered with the Relationships
object, and is used for debugging purposes.
getListenerStats(): RelationshipsListenerStats
returns | RelationshipsListenerStats | A |
---|
The RelationshipsListenerStats
object contains a breakdown of the different types of listener.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Relationships
object.
import {createRelationships, createStore} from 'tinybase';
const store = createStore();
const relationships = createRelationships(store);
relationships.addRemoteRowIdListener(null, null, () => {
console.log('Remote Row Id changed');
});
relationships.addLocalRowIdsListener(null, null, () => {
console.log('Local Row Id changed');
});
const listenerStats = relationships.getListenerStats();
console.log(listenerStats.remoteRowId);
// -> 1
console.log(listenerStats.localRowIds);
// -> 1
Since
v1.0.0
Functions
There is one function, createRelationships
, within the relationships
module.
createRelationships
The createRelationships
function creates a Relationships
object, and is the main entry point into the relationships
module.
createRelationships(store: Store): Relationships
Type | Description | |
---|---|---|
store | Store | The |
returns | Relationships | A reference to the new |
A given Store
can only have one Relationships
object associated with it. If you call this function twice on the same Store
, your second call will return a reference to the Relationships
object created by the first.
Examples
This example creates a Relationships
object.
import {createRelationships, createStore} from 'tinybase';
const store = createStore();
const relationships = createRelationships(store);
console.log(relationships.getRelationshipIds());
// -> []
This example creates a Relationships
object, and calls the method a second time for the same Store
to return the same object.
import {createRelationships, createStore} from 'tinybase';
const store = createStore();
const relationships1 = createRelationships(store);
const relationships2 = createRelationships(store);
console.log(relationships1 === relationships2);
// -> true
Since
v1.0.0
Type Aliases
These are the type aliases within the relationships
module.
Listener type aliases
This is the collection of listener type aliases within the relationships
module. There are 4 listener type aliases in total.
LinkedRowIdsListener
The LinkedRowIdsListener
type describes a function that is used to listen to changes to the local Row
Id
ends of a Relationship
.
(
relationships: Relationships,
relationshipId: Id,
firstRowId: Id,
): void
Type | Description | |
---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
firstRowId | Id | The |
returns | void | This has no return value. |
A LinkedRowIdsListener
is provided when using the addLinkedRowIdsListener
method. See that method for specific examples.
When called, a LinkedRowIdsListener
is given a reference to the Relationships
object, the Id
of the Relationship
that changed, and the Id
of the first Row
of the the linked list whose members changed.
Since
v1.0.0
LocalRowIdsListener
The LocalRowIdsListener
type describes a function that is used to listen to changes to the local Row
Id
ends of a Relationship
.
(
relationships: Relationships,
relationshipId: Id,
remoteRowId: Id,
): void
Type | Description | |
---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
remoteRowId | Id | |
returns | void | This has no return value. |
A LocalRowIdsListener
is provided when using the addLocalRowIdsListener
method. See that method for specific examples.
When called, a LocalRowIdsListener
is given a reference to the Relationships
object, the Id
of the Relationship
that changed, and the Id
of the remote Row
whose local Row
Ids
changed.
Since
v1.0.0
RemoteRowIdListener
The RemoteRowIdListener
type describes a function that is used to listen to changes to the remote Row
Id
end of a Relationship
.
(
relationships: Relationships,
relationshipId: Id,
localRowId: Id,
): void
Type | Description | |
---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
localRowId | Id | |
returns | void | This has no return value. |
A RemoteRowIdListener
is provided when using the addRemoteRowIdListener
method. See that method for specific examples.
When called, a RemoteRowIdListener
is given a reference to the Relationships
object, the Id
of the Relationship
that changed, and the Id
of the local Row
whose remote Row
Id
changed.
Since
v1.0.0
RelationshipIdsListener
The RelationshipIdsListener
type describes a function that is used to listen to Relationship
definitions being added or removed.
(relationships: Relationships): void
Type | Description | |
---|---|---|
relationships | Relationships | A reference to the |
returns | void | This has no return value. |
A RelationshipIdsListener
is provided when using the addRelationshipIdsListener
method. See that method for specific examples.
When called, a RelationshipIdsListener
is given a reference to the Relationships
object.
Since
v1.0.0
Callback type aliases
This is the collection of callback type aliases within the relationships
module. There is only one type alias, RelationshipCallback
.
RelationshipCallback
The RelationshipCallback
type describes a function that takes a Relationship
's Id
and a callback to loop over each local Row
within it.
(
relationshipId: Id,
forEachRow: (rowCallback: RowCallback) => void,
): void
Type | Description | |
---|---|---|
relationshipId | Id | The |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the local |
returns | void | This has no return value. |
A RelationshipCallback
is provided when using the forEachRelationship
method, so that you can do something based on every Relationship
in the Relationships
object. See that method for specific examples.
Since
v1.0.0
Concept type aliases
This is the collection of concept type aliases within the relationships
module. There is only one type alias, Relationship
.
Relationship
The Relationship
type represents the concept of a map that connects one Row
object to another, often in another Table
.
{
remoteRowId: {[localRowId: Id]: Id};
localRowIds: {[remoteRowId: Id]: Ids};
linkedRowIds: {[firstRowId: Id]: Ids};
}
Type | Description | |
---|---|---|
remoteRowId | {[localRowId: Id]: Id} | |
localRowIds | {[remoteRowId: Id]: Ids} | |
linkedRowIds | {[firstRowId: Id]: Ids} |
The Relationship
has a one-to-many nature. One local Row
Id
is linked to one remote Row
Id
(in the remote Table
), as described by the setRelationshipDefinition
method - and one remote Row
Id
may map back to multiple local Row
Ids
(in the local Table
).
A Relationship
where the local Table
is the same as the remote Table
can be used to model a 'linked list', where Row
A references Row
B, Row
B references Row
C, and so on.
Note that the Relationship
type is not actually used in the API, and you instead enumerate and access its structure with the getRemoteRowId
method, the getLocalRowIds
method, and the getLinkedRowIds
method.
Since
v1.0.0
Development type aliases
This is the collection of development type aliases within the relationships
module. There is only one type alias, RelationshipsListenerStats
.
RelationshipsListenerStats
The RelationshipsListenerStats
type describes the number of listeners registered with the Relationships
object, and can be used for debugging purposes.
{
remoteRowId: number;
localRowIds: number;
linkedRowIds: number;
}
Type | Description | |
---|---|---|
remoteRowId | number | The number of |
localRowIds | number | The number of |
linkedRowIds | number | The number of LinkedRowId functions registered with the |
A RelationshipsListenerStats
object is returned from the getListenerStats
method.
Since
v1.0.0
queries
The queries
module of the TinyBase project provides the ability to create and track queries of the data in Store
objects.
The main entry point to using the queries
module is the createQueries
function, which returns a new Queries
object. That object in turn has methods that let you create new query definitions, access their results directly, and register listeners for when those results change.
Since
v2.0.0
Interfaces
There is one interface, Queries
, within the queries
module.
Queries
A Queries
object lets you create and track queries of the data in Store
objects.
This is useful for creating a reactive view of data that is stored in physical tables: selecting columns, joining tables together, filtering rows, aggregating data, sorting it, and so on.
This provides a generalized query concept for Store
data. If you just want to create and track metrics, indexes, or relationships between rows, you may prefer to use the dedicated Metrics
, Indexes
, and Relationships
objects, which have simpler APIs.
Create a Queries
object easily with the createQueries
function. From there, you can add new query definitions (with the setQueryDefinition
method), query the results (with the getResultTable
method, the getResultRow
method, the getResultCell
method, and so on), and add listeners for when they change (with the addResultTableListener
method, the addResultRowListener
method, the addResultCellListener
method, and so on).
Example
This example shows a very simple lifecycle of a Queries
object: from creation, to adding definitions, getting their contents, and then registering and removing listeners for them.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown', ownerId: '1'},
felix: {species: 'cat', color: 'black', ownerId: '2'},
cujo: {species: 'dog', color: 'black', ownerId: '3'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
// A filtered table query:
queries.setQueryDefinition('blackPets', 'pets', ({select, where}) => {
select('species');
where('color', 'black');
});
console.log(queries.getResultTable('blackPets'));
// -> {felix: {species: 'cat'}, cujo: {species: 'dog'}}
// A joined table query:
queries.setQueryDefinition('petOwners', 'pets', ({select, join}) => {
select('owners', 'name').as('owner');
join('owners', 'ownerId');
});
console.log(queries.getResultTable('petOwners'));
// -> {fido: {owner: 'Alice'}, felix: {owner: 'Bob'}, cujo: {owner: 'Carol'}}
// A grouped query:
queries.setQueryDefinition(
'colorPrice',
'pets',
({select, join, group}) => {
select('color');
select('species', 'price');
join('species', 'species');
group('price', 'avg');
},
);
console.log(queries.getResultTable('colorPrice'));
// -> {"1": {color: 'black', price: 4.5}, "0": {color: 'brown', price: 5}}
console.log(queries.getResultSortedRowIds('colorPrice', 'price', true));
// -> ["0", "1"]
const listenerId = queries.addResultTableListener('colorPrice', () => {
console.log('Average prices per color changed');
console.log(queries.getResultTable('colorPrice'));
console.log(queries.getResultSortedRowIds('colorPrice', 'price', true));
});
store.setRow('pets', 'lowly', {species: 'worm', color: 'brown'});
// -> 'Average prices per color changed'
// -> {"0": {color: 'brown', price: 3}, "1": {color: 'black', price: 4.5}}
// -> ["1", "0"]
queries.delListener(listenerId);
queries.destroy();
See also
- Using Queries guides
- Car Analysis demo
- Movie Database demo
Since
v2.0.0
Getter methods
This is the collection of getter methods within the Queries
interface. There are 4 getter methods in total.
getStore
The getStore
method returns a reference to the underlying Store
that is backing this Queries
object.
getStore(): Store
Example
This example creates a Queries
object against a newly-created Store
and then gets its reference in order to update its data.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore());
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
queries
.getStore()
.setRow('pets', 'fido', {species: 'dog', color: 'brown'});
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}}
Since
v2.0.0
getTableId
The getTableId
method returns the Id
of the underlying Table
that is backing a query.
getTableId(queryId: string): undefined | string
Type | Description | |
---|---|---|
queryId | string | The |
returns | undefined | string |
If the query Id
is invalid, the method returns undefined
.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the underlying Table
Id
.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore()).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getTableId('dogColors'));
// -> 'pets'
console.log(queries.getTableId('catColors'));
// -> undefined
Since
v2.0.0
getQueryIds
The getQueryIds
method returns an array of the query Ids
registered with this Queries
object.
getQueryIds(): Ids
Example
This example creates a Queries
object with two definitions, and then gets the Ids
of the definitions.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore())
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
console.log(queries.getQueryIds());
// -> ['dogColors', 'catColors']
Since
v2.0.0
hasQuery
The hasQuery
method returns a boolean indicating whether a given query exists in the Queries
object.
hasQuery(queryId: string): boolean
Type | Description | |
---|---|---|
queryId | string | |
returns | boolean | Whether a query with that |
Example
This example shows two simple query existence checks.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore()).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasQuery('dogColors'));
// -> true
console.log(queries.hasQuery('catColors'));
// -> false
Since
v2.0.0
Result methods
This is the collection of result methods within the Queries
interface. There are 11 result methods in total.
getResultTable
The getResultTable
method returns an object containing the entire data of the ResultTable
of the given query.
getResultTable(queryId: string): ResultTable
Type | Description | |
---|---|---|
queryId | string | The |
returns | ResultTable | An object containing the entire data of the |
This has the same behavior as a Store
's getTable
method. For example, if the query Id
is invalid, the method returns an empty object. Similarly, it returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the query results themselves.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}, cujo: {color: 'black'}}
console.log(queries.getResultTable('catColors'));
// -> {}
Since
v2.0.0
getResultTableCellIds
The getResultTableCellIds
method returns the Ids
of every ResultCell
used across the ResultTable
of the given query.
getResultTableCellIds(queryId: string): Ids
Type | Description | |
---|---|---|
queryId | string | The |
returns | Ids | An array of the |
This has the same behavior as a Store
's getTableCellIds
method. For example, if the query Id
is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids
, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultCell
Ids
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black', legs: 4},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
},
);
console.log(queries.getResultTableCellIds('dogColors'));
// -> ['color', 'legs']
console.log(queries.getResultTableCellIds('catColors'));
// -> []
Since
v4.1.0
hasResultTable
The hasResultTable
method returns a boolean indicating whether a given ResultTable
exists.
hasResultTable(queryId: string): boolean
Type | Description | |
---|---|---|
queryId | string | The |
returns | boolean | Whether a |
Example
This example shows two simple ResultTable
existence checks.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultTable('dogColors'));
// -> true
console.log(queries.hasResultTable('catColors'));
// -> false
Since
v2.0.0
getResultRowIds
The getResultRowIds
method returns the Ids
of every ResultRow
in the ResultTable
of the given query.
getResultRowIds(queryId: string): Ids
Type | Description | |
---|---|---|
queryId | string | The |
returns | Ids | An array of the |
This has the same behavior as a Store
's getRowIds
method. For example, if the query Id
is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids
, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultRow
Ids
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRowIds('dogColors'));
// -> ['fido', 'cujo']
console.log(queries.getResultRowIds('catColors'));
// -> []
Since
v2.0.0
getResultSortedRowIds
The getResultSortedRowIds
method returns the Ids
of every ResultRow
in the ResultTable
of the given query, sorted according to the values in a specified ResultCell
.
getResultSortedRowIds(
queryId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
returns | Ids | An array of the sorted |
This has the same behavior as a Store
's getSortedRowIds
method. For example, if the query Id
is invalid, the method returns an empty array. Similarly, the sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available ResultRow
Ids
if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the ResultTable
is large. For a performant approach to tracking the sorted ResultRow
Ids
when they change, use the addResultSortedRowIdsListener
method.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultRow
Ids
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultSortedRowIds('dogColors', 'color'));
// -> ['cujo', 'fido']
console.log(queries.getResultSortedRowIds('catColors', 'color'));
// -> []
Since
v2.0.0
getResultRow
The getResultRow
method returns an object containing the entire data of a single ResultRow
in the ResultTable
of the given query.
getResultRow(
queryId: string,
rowId: string,
): ResultRow
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | The |
returns | ResultRow | An object containing the entire data of the |
This has the same behavior as a Store
's getRow
method. For example, if the query or ResultRow
Id
is invalid, the method returns an empty object. Similarly, it returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the query results themselves.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent ResultRow
Id
) to get the ResultRow
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRow('dogColors', 'fido'));
// -> {color: 'brown'}
console.log(queries.getResultRow('dogColors', 'felix'));
// -> {}
Since
v2.0.0
getResultRowCount
The getResultRowCount
method returns the count of the ResultRow
objects in the ResultTable
of the given query.
getResultRowCount(queryId: string): number
Type | Description | |
---|---|---|
queryId | string | The |
returns | number | The number of |
This has the same behavior as a Store
's getRowCount
method. For example, if the query Id
is invalid, the method returns zero.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultRow
count.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRowCount('dogColors'));
// -> 2
console.log(queries.getResultRowCount('catColors'));
// -> 0
Since
v4.1.0
hasResultRow
The hasResultRow
method returns a boolean indicating whether a given ResultRow
exists.
hasResultRow(
queryId: string,
rowId: string,
): boolean
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
returns | boolean |
Example
This example shows two simple ResultRow
existence checks.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultRow('dogColors', 'fido'));
// -> true
console.log(queries.hasResultRow('dogColors', 'felix'));
// -> false
Since
v2.0.0
getResultCellIds
The getResultCellIds
method returns the Ids
of every ResultCell
in a given ResultRow
, in the ResultTable
of the given query.
getResultCellIds(
queryId: string,
rowId: string,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | The |
returns | Ids | An array of the |
This has the same behavior as a Store
's getCellIds
method. For example, if the query Id
or ResultRow
Id
is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids
, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent ResultRow
Id
) to get the ResultCell
Ids
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultCellIds('dogColors', 'fido'));
// -> ['color']
console.log(queries.getResultCellIds('dogColors', 'felix'));
// -> []
Since
v2.0.0
getResultCell
The getResultCell
method returns the value of a single ResultCell
in a given ResultRow
, in the ResultTable
of the given query.
getResultCell(
queryId: string,
rowId: string,
cellId: string,
): ResultCellOrUndefined
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | The |
cellId | string | The |
returns | ResultCellOrUndefined | The value of the |
This has the same behavior as a Store
's getCell
method. For example, if the query, or ResultRow
, or ResultCell
Id
is invalid, the method returns undefined
.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent ResultCell
Id
) to get the ResultCell
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultCell('dogColors', 'fido', 'color'));
// -> 'brown'
console.log(queries.getResultCell('dogColors', 'fido', 'species'));
// -> undefined
Since
v2.0.0
hasResultCell
The hasResultCell
method returns a boolean indicating whether a given ResultCell
exists.
hasResultCell(
queryId: string,
rowId: string,
cellId: string,
): boolean
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
cellId | string | The |
returns | boolean | Whether a |
Example
This example shows two simple ResultRow
existence checks.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultCell('dogColors', 'fido', 'color'));
// -> true
console.log(queries.hasResultCell('dogColors', 'fido', 'species'));
// -> false
Since
v2.0.0
Listener methods
This is the collection of listener methods within the Queries
interface. There are 10 listener methods in total.
addResultTableCellIdsListener
The addResultTableCellIdsListener
method registers a listener function with the Queries
object that will be called whenever the Cell
Ids
that appear anywhere in a ResultTable
change.
addResultTableCellIdsListener(
queryId: IdOrNull,
listener: ResultTableCellIdsListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultTableCellIdsListener | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a ResultTableCellIdsListener
function, and will be called with a reference to the Queries
object and the Id
of the ResultTable
that changed (which is also the query Id
).
By default, such a listener is only called when a Cell
Id
is added to, or removed from, the ResultTable
. To listen to all changes in the ResultTable
, use the addResultTableListener
method.
You can either listen to a single ResultTable
(by specifying a query Id
as the method's first parameter) or changes to any ResultTable
(by providing a null
wildcard).
Examples
This example registers a listener that responds to any change to the Cell
Ids
of a specific ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColorsAndLegs',
'pets',
({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
},
);
const listenerId = queries.addResultTableCellIdsListener(
'dogColorsAndLegs',
(queries) => {
console.log(`Cell Ids for dogColorsAndLegs result table changed`);
console.log(queries.getResultTableCellIds('dogColorsAndLegs'));
},
);
store.setCell('pets', 'cujo', 'legs', 4);
// -> 'Cell Ids for dogColorsAndLegs result table changed'
// -> ['color', 'legs']
store.delListener(listenerId);
This example registers a listener that responds to any change to the ResultCell
Ids
of any ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black', legs: 4},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
})
.setQueryDefinition('catColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'cat');
});
const listenerId = queries.addResultTableCellIdsListener(
null,
(queries, tableId) => {
console.log(`Cell Ids for ${tableId} result table changed`);
},
);
store.setCell('pets', 'cujo', 'legs', 4);
// -> 'Cell Ids for dogColorsAndLegs result table changed'
store.delCell('pets', 'felix', 'legs');
// -> 'Cell Ids for catColorsAndLegs result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultTableListener
The addResultTableListener
method registers a listener function with the Queries
object that will be called whenever data in a ResultTable
changes.
addResultTableListener(
queryId: IdOrNull,
listener: ResultTableListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultTableListener | The function that will be called whenever data in the matching |
returns | string | A unique |
The provided listener is a ResultTableListener
function, and will be called with a reference to the Queries
object, the Id
of the ResultTable
that changed (which is also the query Id
), and a GetResultCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single ResultTable
(by specifying a query Id
as the method's first parameter) or changes to any ResultTable
(by providing a null
wildcard).
Examples
This example registers a listener that responds to any changes to a specific ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultTableListener(
'dogColors',
(queries, tableId, getCellChange) => {
console.log('dogColors result table changed');
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'dogColors result table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultTableListener(
null,
(queries, tableId) => {
console.log(`${tableId} result table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'dogColors result table changed'
store.setCell('pets', 'felix', 'color', 'tortoiseshell');
// -> 'catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultRowIdsListener
The addResultRowIdsListener
method registers a listener function with the Queries
object that will be called whenever the ResultRow
Ids
in a ResultTable
change.
addResultRowIdsListener(
queryId: IdOrNull,
listener: ResultRowIdsListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultRowIdsListener | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a ResultRowIdsListener
function, and will be called with a reference to the Queries
object and the Id
of the ResultTable
that changed (which is also the query Id
).
By default, such a listener is only called when a ResultRow
is added to, or removed from, the ResultTable
. To listen to all changes in the ResultTable
, use the addResultTableListener
method.
You can either listen to a single ResultTable
(by specifying a query Id
as the method's first parameter) or changes to any ResultTable
(by providing a null
wildcard).
Examples
This example registers a listener that responds to any change to the ResultRow
Ids
of a specific ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowIdsListener(
'dogColors',
(queries) => {
console.log(`Row Ids for dogColors result table changed`);
console.log(queries.getResultRowIds('dogColors'));
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row Ids for dogColors result table changed'
// -> ['fido', 'cujo', 'rex']
store.delListener(listenerId);
This example registers a listener that responds to any change to the ResultRow
Ids
of any ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowIdsListener(
null,
(queries, tableId) => {
console.log(`Row Ids for ${tableId} result table changed`);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row Ids for dogColors result table changed'
store.setRow('pets', 'tom', {species: 'cat', color: 'gray'});
// -> 'Row Ids for catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultSortedRowIdsListener
The addResultSortedRowIdsListener
method registers a listener function with the Queries
object that will be called whenever sorted (and optionally, paginated) ResultRow
Ids
in a ResultTable
change.
addResultSortedRowIdsListener(
queryId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: ResultSortedRowIdsListener,
): string
Type | Description | |
---|---|---|
queryId | string | The |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | ResultSortedRowIdsListener | The function that will be called whenever the sorted |
returns | string | A unique |
The provided listener is a ResultSortedRowIdsListener
function, and will be called with a reference to the Queries
object, the Id
of the ResultTable
whose ResultRow
Ids
sorting changed (which is also the query Id
), the ResultCell
Id
being used to sort them, whether descending or not, and the offset and limit of the number of Ids
returned, for pagination purposes. It also receives the sorted array of Ids
itself, so that you can use them in the listener without the additional cost of an explicit call to the getResultSortedRowIds
method.
Such a listener is called when a ResultRow
is added or removed, but also when a value in the specified ResultCell
(somewhere in the ResultTable
) has changed enough to change the sorting of the ResultRow
Ids
.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified ResultTable
, sorted by a single specified ResultCell
.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available ResultRow
Ids
if not specified.
Examples
This example registers a listener that responds to any change to the sorted ResultRow
Ids
of a specific ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultSortedRowIdsListener(
'dogColors',
'color',
false,
0,
undefined,
(queries, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted row Ids for dogColors result table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getResultSortedRowIds again
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Sorted row Ids for dogColors result table changed'
// -> ['cujo', 'fido', 'rex']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted ResultRow
Ids
of a specific ResultTable
. The ResultRow
Ids
are sorted by their own value, since the cellId
parameter is explicitly undefined.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultSortedRowIds('dogColors', undefined));
// -> ['cujo', 'fido']
const listenerId = queries.addResultSortedRowIdsListener(
'dogColors',
undefined,
false,
0,
undefined,
(queries, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted row Ids for dogColors result table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Sorted row Ids for dogColors result table changed'
// -> ['cujo', 'fido', 'rex']
store.delListener(listenerId);
Since
v2.0.0
addResultRowCountListener
The addResultRowCountListener
method registers a listener function with the Queries
object that will be called whenever the count of ResultRow
objects in a ResultTable
changes.
addResultRowCountListener(
queryId: IdOrNull,
listener: ResultRowCountListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultRowCountListener | The function that will be called whenever the number of |
returns | string | A unique |
The provided listener is a ResultRowCountListener
function, and will be called with a reference to the Queries
object, the Id
of the ResultTable
that changed (which is also the query Id
), and the number of ResultRow
objects in th ResultTable
.
You can either listen to a single ResultTable
(by specifying a query Id
as the method's first parameter) or changes to any ResultTable
(by providing a null
wildcard).
Examples
This example registers a listener that responds to a change in the number of ResultRow
objects in a specific ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowCountListener(
'dogColors',
(queries, tableId, count) => {
console.log(
'Row count for dogColors result table changed to ' + count,
);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row count for dogColors result table changed to 3'
store.delListener(listenerId);
This example registers a listener that responds to a change in the number of ResultRow
objects any ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowCountListener(
null,
(queries, tableId, count) => {
console.log(
`Row count for ${tableId} result table changed to ${count}`,
);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row count for dogColors result table changed to 3'
store.setRow('pets', 'tom', {species: 'cat', color: 'gray'});
// -> 'Row count for catColors result table changed to 2'
store.delListener(listenerId);
Since
v4.1.0
addResultRowListener
The addResultRowListener
method registers a listener function with the Queries
object that will be called whenever data in a ResultRow
changes.
addResultRowListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultRowListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultRowListener | The function that will be called whenever data in the matching |
returns | string | A unique |
The provided listener is a ResultRowListener
function, and will be called with a reference to the Queries
object, the Id
of the ResultTable
that changed (which is also the query Id
), and a GetResultCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single ResultRow
(by specifying a query Id
and ResultRow
Id
as the method's first two parameters) or changes to any ResultRow
(by providing null
wildcards).
Both, either, or neither of the queryId
and rowId
parameters can be wildcarded with null
. You can listen to a specific ResultRow
in a specific query, any ResultRow
in a specific query, a specific ResultRow
in any query, or any ResultRow
in any query.
Examples
This example registers a listener that responds to any changes to a specific ResultRow
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowListener(
'dogColors',
'fido',
(queries, tableId, rowId, getCellChange) => {
console.log('fido row in dogColors result table changed');
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in dogColors result table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any ResultRow
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowListener(
null,
null,
(queries, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} result table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'color', 'tortoiseshell');
// -> 'felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultCellIdsListener
The addResultCellIdsListener
method registers a listener function with the Queries
object that will be called whenever the ResultCell
Ids
in a ResultRow
change.
addResultCellIdsListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultCellIdsListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultCellIdsListener | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a ResultCellIdsListener
function, and will be called with a reference to the Queries
object, the Id
of the ResultTable
(which is also the query Id
), and the Id
of the ResultRow
that changed.
Such a listener is only called when a ResultCell
is added to, or removed from, the ResultRow
. To listen to all changes in the ResultRow
, use the addResultRowListener
method.
You can either listen to a single ResultRow
(by specifying the query Id
and ResultRow
Id
as the method's first two parameters) or changes to any ResultRow
(by providing null
wildcards).
Both, either, or neither of the queryId
and rowId
parameters can be wildcarded with null
. You can listen to a specific ResultRow
in a specific query, any ResultRow
in a specific query, a specific ResultRow
in any query, or any ResultRow
in any query.
Examples
This example registers a listener that responds to any change to the ResultCell
Ids
of a specific ResultRow
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
select('price');
where('species', 'dog');
},
);
const listenerId = queries.addResultCellIdsListener(
'dogColors',
'fido',
(queries) => {
console.log(`Cell Ids for fido row in dogColors result table changed`);
console.log(queries.getResultCellIds('dogColors', 'fido'));
},
);
store.setCell('pets', 'fido', 'price', 5);
// -> 'Cell Ids for fido row in dogColors result table changed'
// -> ['color', 'price']
store.delListener(listenerId);
This example registers a listener that responds to any change to the ResultCell
Ids
of any ResultRow
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
select('price');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
select('purrs');
where('species', 'cat');
});
const listenerId = queries.addResultCellIdsListener(
null,
null,
(queries, tableId, rowId) => {
console.log(
`Cell Ids for ${rowId} row in ${tableId} result table changed`,
);
},
);
store.setCell('pets', 'fido', 'price', 5);
// -> 'Cell Ids for fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'purrs', true);
// -> 'Cell Ids for felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultCellListener
The addResultCellListener
method registers a listener function with the Queries
object that will be called whenever data in a ResultCell
changes.
addResultCellListener(
queryId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: ResultCellListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
cellId | IdOrNull | The |
listener | ResultCellListener | The function that will be called whenever data in the matching |
returns | string | A unique |
The provided listener is a ResultCellListener
function, and will be called with a reference to the Queries
object, the Id
of the ResultTable
that changed (which is also the query Id
), the Id
of the ResultRow
that changed, the Id
of the ResultCell
that changed, the new ResultCell
value, the old ResultCell
value, and a GetResultCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single ResultRow
(by specifying a query Id
, ResultRow
Id
, and ResultCell
Id
as the method's first three parameters) or changes to any ResultCell
(by providing null
wildcards).
All, some, or none of the queryId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific ResultCell
in a specific ResultRow
in a specific query, any ResultCell
in any ResultRow
in any query, for example - or every other combination of wildcards.
Examples
This example registers a listener that responds to any changes to a specific ResultCell
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultCellListener(
'dogColors',
'fido',
'color',
(queries, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log(
'color cell in fido row in dogColors result table changed',
);
console.log([oldCell, newCell]);
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in dogColors result table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any ResultCell
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown', price: 5},
felix: {species: 'cat', color: 'black', price: 4},
cujo: {species: 'dog', color: 'black', price: 5},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
select('price');
where('species', 'cat');
});
const listenerId = queries.addResultCellListener(
null,
null,
null,
(queries, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} result table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'price', 3);
// -> 'price cell in felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addQueryIdsListener
The addQueryIdsListener
method registers a listener function with the Queries
object that will be called whenever an Query definition is added or removed.
addQueryIdsListener(listener: QueryIdsListener): string
Type | Description | |
---|---|---|
listener | QueryIdsListener | The function that will be called whenever a Query definition is added or removed. |
returns | string |
The provided listener is a QueryIdsListener
function, and will be called with a reference to the Queries
object.
Example
This example creates a Store
, a Queries
object, and then registers a listener that responds to the addition and the removal of a Query definition.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
const listenerId = queries.addQueryIdsListener((queries) => {
console.log(queries.getQueryIds());
});
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
// -> ['dogColors']
queries.delQueryDefinition('dogColors');
// -> []
queries.delListener(listenerId);
Since
v4.1.0
delListener
The delListener
method removes a listener that was previously added to the Queries
object.
delListener(listenerId: string): Queries
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Queries | A reference to the |
Use the Id
returned by the addMetricListener
method. Note that the Queries
object may re-use this Id
for future listeners added to it.
Example
This example creates a Store
, a Queries
object, registers a listener, and then removes it.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store).setQueryDefinition(
'species',
'pets',
({select}) => {
select('species');
},
);
const listenerId = queries.addResultTableListener('species', () =>
console.log('species result changed'),
);
store.setCell('pets', 'ed', 'species', 'horse');
// -> 'species result changed'
queries.delListener(listenerId);
store.setCell('pets', 'molly', 'species', 'cow');
// -> undefined
// The listener is not called.
Since
v2.0.0
Configuration methods
This is the collection of configuration methods within the Queries
interface. There are only two configuration methods, delQueryDefinition
and setQueryDefinition
.
delQueryDefinition
The delQueryDefinition
method removes an existing query definition.
delQueryDefinition(queryId: string): Queries
Type | Description | |
---|---|---|
queryId | string | The |
returns | Queries | A reference to the |
Example
This example creates a Store
, creates a Queries
object, defines a simple query, and then removes it.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
console.log(queries.getQueryIds());
// -> ['dogColors']
queries.delQueryDefinition('dogColors');
console.log(queries.getQueryIds());
// -> []
Since
v2.0.0
setQueryDefinition
The setQueryDefinition
method lets you set the definition of a query.
setQueryDefinition(
queryId: string,
tableId: string,
query: (keywords: {
select: Select;
join: Join;
where: Where;
group: Group;
having: Having;
}) => void,
): Queries
Type | Description | |
---|---|---|
queryId | string | The |
tableId | string | |
query | (keywords: { select: Select; join: Join; where: Where; group: Group; having: Having; }) => void | A callback which can take a |
returns | Queries | A reference to the |
Every query definition is identified by a unique Id
, and if you re-use an existing Id
with this method, the previous definition is overwritten.
A query provides a tabular result formed from each Row
within a root Table
. The definition must specify this Table
(by its Id
) to be aggregated. Other Tables
can be joined to that using Join
clauses.
The third query
parameter is a callback that you provide to define the query. That callback is provided with a keywords
object that contains the functions you use to define the query, like select
, join
, and so on. You can see how that is used in the simple example below. The following five clause types are supported:
- The
Select
type describes a function that lets you specify aCell
or calculated value for including into the query's result. - The
Join
type describes a function that lets you specify aCell
or calculated value to join the main queryTable
to others, byRow
Id
. - The
Where
type describes a function that lets you specify conditions to filter results, based on the underlying Cells of the main or joinedTables
. - The
Group
type describes a function that lets you specify that the values of aCell
in multiple ResultRows should be aggregated together. - The
Having
type describes a function that lets you specify conditions to filter results, based on the grouped Cells resulting from aGroup
clause.
Full documentation and examples are provided in the sections for each of those clause types.
Additionally, you can use the getResultSortedRowIds
method and addResultSortedRowIdsListener
method to sort and paginate the results.
Example
This example creates a Store
, creates a Queries
object, and defines a simple query to select just one column from the Table
, for each Row
where the species
Cell
matches as certain value.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}, cujo: {color: 'black'}}
Since
v2.0.0
Iterator methods
This is the collection of iterator methods within the Queries
interface. There are 4 iterator methods in total.
forEachResultTable
The forEachResultTable
method takes a function that it will then call for each ResultTable
in the Queries
object.
forEachResultTable(tableCallback: ResultTableCallback): void
Type | Description | |
---|---|---|
tableCallback | ResultTableCallback | The function that should be called for every query's |
returns | void | This has no return value. |
This method is useful for iterating over all the ResultTables of the queries in a functional style. The tableCallback
parameter is a ResultTableCallback
function that will be called with the Id
of each ResultTable
, and with a function that can then be used to iterate over each ResultRow
of the ResultTable
, should you wish.
Example
This example iterates over each query's ResultTable
in a Queries
object.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
queries.forEachResultTable((queryId, forEachRow) => {
console.log(queryId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'dogColors'
// -> '- fido'
// -> '- cujo'
// -> 'catColors'
// -> '- felix'
Since
v2.0.0
forEachResultRow
The forEachResultRow
method takes a function that it will then call for each ResultRow
in the ResultTable
of a query.
forEachResultRow(
queryId: string,
rowCallback: ResultRowCallback,
): void
Type | Description | |
---|---|---|
queryId | string | The |
rowCallback | ResultRowCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over each ResultRow
of the ResultTable
of the query in a functional style. The rowCallback
parameter is a ResultRowCallback
function that will be called with the Id
of each ResultRow
, and with a function that can then be used to iterate over each ResultCell
of the ResultRow
, should you wish.
Example
This example iterates over each ResultRow
in a query's ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
queries.forEachResultRow('dogColors', (rowId, forEachCell) => {
console.log(rowId);
forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- color'
// -> 'cujo'
// -> '- color'
Since
v2.0.0
forEachResultCell
The forEachResultCell
method takes a function that it will then call for each ResultCell
in the ResultRow
of a query.
forEachResultCell(
queryId: string,
rowId: string,
cellCallback: ResultCellCallback,
): void
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | The |
cellCallback | ResultCellCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over each ResultCell
of the ResultRow
of the query in a functional style. The cellCallback
parameter is a ResultCellCallback
function that will be called with the Id
and value of each ResultCell
.
Example
This example iterates over each ResultCell
in a query's ResultRow
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
where('species', 'dog');
},
);
queries.forEachResultCell('dogColors', 'fido', (cellId, cell) => {
console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
Since
v2.0.0
forEachQuery
The forEachQuery
method takes a function that it will then call for each Query in the Queries
object.
forEachQuery(queryCallback: QueryCallback): void
Type | Description | |
---|---|---|
queryCallback | QueryCallback | The function that should be called for every query. |
returns | void | This has no return value. |
This method is useful for iterating over all the queries in a functional style. The queryCallback
parameter is a QueryCallback
function that will be called with the Id
of each query.
Example
This example iterates over each query in a Queries
object.
import {createQueries, createStore} from 'tinybase';
const queries = createQueries(createStore())
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
queries.forEachQuery((queryId) => {
console.log(queryId);
});
// -> 'dogColors'
// -> 'catColors'
Since
v2.0.0
Lifecycle methods
This is the collection of lifecycle methods within the Queries
interface. There is only one method, destroy
.
destroy
The destroy
method should be called when this Queries
object is no longer used.
destroy(): void
This guarantees that all of the listeners that the object registered with the underlying Store
are removed and it can be correctly garbage collected.
Example
This example creates a Store
, adds a Queries
object with a definition (that registers a RowListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
queries.setQueryDefinition('species', 'species', ({select}) => {
select('species');
});
console.log(store.getListenerStats().row);
// -> 1
queries.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v2.0.0
Development methods
This is the collection of development methods within the Queries
interface. There is only one method, getListenerStats
.
getListenerStats
The getListenerStats
method provides a set of statistics about the listeners registered with the Queries
object, and is used for debugging purposes.
getListenerStats(): QueriesListenerStats
returns | QueriesListenerStats | A |
---|
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Queries
object.
import {createQueries, createStore} from 'tinybase';
const store = createStore();
const queries = createQueries(store);
queries.addResultTableListener(null, () => console.log('Result changed'));
console.log(queries.getListenerStats().table);
// -> 1
console.log(queries.getListenerStats().row);
// -> 0
Since
v2.0.0
Functions
There is one function, createQueries
, within the queries
module.
createQueries
The createQueries
function creates a Queries
object, and is the main entry point into the queries
module.
createQueries(store: Store): Queries
Type | Description | |
---|---|---|
store | Store | The |
returns | Queries | A reference to the new |
A given Store
can only have one Queries
object associated with it. If you call this function twice on the same Store
, your second call will return a reference to the Queries
object created by the first.
Examples
This example creates a Queries
object.
import {createQueries, createStore} from 'tinybase';
const store = createStore();
const queries = createQueries(store);
console.log(queries.getQueryIds());
// -> []
This example creates a Queries
object, and calls the method a second time for the same Store
to return the same object.
import {createQueries, createStore} from 'tinybase';
const store = createStore();
const queries1 = createQueries(store);
const queries2 = createQueries(store);
console.log(queries1 === queries2);
// -> true
Since
v2.0.0
Type Aliases
These are the type aliases within the queries
module.
Definition type aliases
This is the collection of definition type aliases within the queries
module. There are 8 definition type aliases in total.
Group
The Group
type describes a function that lets you specify that the values of a Cell
in multiple ResultRows should be aggregated together.
(
selectedCellId: Id,
aggregate: "count" | "sum" | "avg" | "min" | "max" | Aggregate,
aggregateAdd?: AggregateAdd,
aggregateRemove?: AggregateRemove,
aggregateReplace?: AggregateReplace,
): GroupedAs
Type | Description | |
---|---|---|
selectedCellId | Id | The |
aggregate | "count" | "sum" | "avg" | "min" | "max" | Aggregate | Either a string representing one of a set of common aggregation techniques ('count', 'sum', 'avg', 'min', or 'max'), or a function that aggregates |
aggregateAdd? | AggregateAdd | A function that can be used to optimize a custom |
aggregateRemove? | AggregateRemove | A function that can be used to optimize a custom |
aggregateReplace? | AggregateReplace | A function that can be used to optimize a custom |
returns | GroupedAs |
The Group
function is provided to the third query
parameter of the setQueryDefinition
method. When called, it should refer to a Cell
Id
(or aliased Id
) specified in one of the Select
functions, and indicate how the values should be aggregated.
This is applied after any joins or where-based filtering.
If you provide a Group
for every Select
, the result will be a single Row
with every Cell
having been aggregated. If you provide a Group
for only one, or some, of the Select
clauses, the others will be automatically used as dimensional values (analogous to the 'group bysemantics in SQL), within which the aggregations of
Group` Cells will be performed.
You can join the same underlying Cell
multiple times, but in that case you will need to use the 'as' function to distinguish them from each other.
The second parameter can be one of five predefined aggregates - 'count', 'sum', 'avg', 'min', and 'max' - or a custom function that produces your own aggregation of an array of Cell
values.
The final three parameters, aggregateAdd
, aggregateRemove
, aggregateReplace
need only be provided when you are using your own custom aggregate
function. These give you the opportunity to reduce your custom function's algorithmic complexity by providing shortcuts that can nudge an aggregation result when a single value is added, removed, or replaced in the input values.
Examples
This example shows a query that calculates the average of all the values in a single selected Cell
from a joined Table
.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
lowly: {species: 'worm'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('species', 'price');
// from pets
join('species', 'species');
group('price', 'avg').as('avgPrice');
});
console.log(queries.getResultTable('query'));
// -> {0: {avgPrice: 3.75}}
// 2 dogs at 5, 1 cat at 4, 1 worm at 1: a total of 15 for 4 pets
This example shows a query that calculates the average of a two Cell
values, aggregated by the two other dimensional 'group by' Cells.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', owner: 'alice'},
felix: {species: 'cat', owner: 'bob'},
cujo: {species: 'dog', owner: 'bob'},
lowly: {species: 'worm', owner: 'alice'},
carnaby: {species: 'parrot', owner: 'bob'},
polly: {species: 'parrot', owner: 'alice'},
})
.setTable('species', {
dog: {price: 5, legs: 4},
cat: {price: 4, legs: 4},
parrot: {price: 3, legs: 2},
worm: {price: 1, legs: 0},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('pets', 'owner'); // group by
select('species', 'price'); // grouped
// from pets
join('species', 'species');
group(
'price',
(cells) => Math.min(...cells.filter((cell) => cell > 2)),
(current, add) => (add > 2 ? Math.min(current, add) : current),
(current, remove) => (remove == current ? undefined : current),
(current, add, remove) =>
remove == current
? undefined
: add > 2
? Math.min(current, add)
: current,
).as('lowestPriceOver2');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {owner: 'alice', lowestPriceOver2: 3}}
// -> {1: {owner: 'bob', lowestPriceOver2: 3}}
// Both have a parrot at 3. Alice's worm at 1 is excluded from aggregation.
Since
v2.0.0
GroupedAs
The GroupedAs
type describes an object returned from calling a Group
function so that the grouped Cell
Id
can be optionally aliased.
{as: (groupedCellId: Id) => void}
Type | Description | |
---|---|---|
as | (groupedCellId: Id) => void | A function that lets you specify an alias for the grouped |
Note that if two Group
clauses are both aliased to the same name (or if you create two groups of the same underlying Cell
, both without aliases), only the latter of two will be used in the query.
Example
This example shows a query that groups the same underlying Cell
twice, for different purposes. Both groups are aliased with the 'as' function to disambiguate them.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'dog', minPrice: 4, maxPrice: 5}}
// -> {1: {species: 'cat', minPrice: 3, maxPrice: 4}}
Since
v2.0.0
Having
The Having
type describes a function that lets you specify conditions to filter results, based on the grouped Cells resulting from a Group
clause.
Calling this function with two parameters is used to include only those Rows for which a specified Cell
in the query's root Table
has a specified value.
(
selectedOrGroupedCellId: string,
equals: Cell,
): void
Type | Description | |
---|---|---|
selectedOrGroupedCellId | string | |
equals | Cell | The value that the |
returns | void | This has no return value. |
Since
v2.0.0
Calling this function with one callback parameter is used to include only those Rows which meet a calculated boolean condition.
(condition: (getSelectedOrGroupedCell: GetCell) => boolean): void
Type | Description | |
---|---|---|
condition | (getSelectedOrGroupedCell: GetCell) => boolean | A callback that takes a |
returns | void | This has no return value. |
Since
v2.0.0
The Having
function is provided to the third query
parameter of the setQueryDefinition
method.
A Having
condition has to be true for a Row
to be included in the results. Each Having
class is additive, as though combined with a logical 'and'. If you wish to create an 'or' expression, use the single parameter version of the type that allows arbitrary programmatic conditions.
The Where
keyword differs from the Having
keyword in that the former describes conditions that should be met by underlying Cell
values (whether selected or not), and the latter describes conditions based on calculated and aggregated values - after Group
clauses have been applied.
Whilst it is technically possible to use a Having
clause even if the results have not been grouped with a Group
clause, you should expect it to be less performant than using a Where
clause, due to that being applied earlier in the query process.
Examples
This example shows a query that filters the results from a grouped Table
by comparing a Cell
from it with a value.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
carnaby: {species: 'parrot', price: 3},
polly: {species: 'parrot', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group, having}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
having('minPrice', 3);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'cat', minPrice: 3, maxPrice: 4}}
// -> {1: {species: 'parrot', minPrice: 3, maxPrice: 3}}
This example shows a query that filters the results from a grouped Table
with a condition that is calculated from Cell
values.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
carnaby: {species: 'parrot', price: 3},
polly: {species: 'parrot', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group, having}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
having(
(getSelectedOrGroupedCell) =>
getSelectedOrGroupedCell('minPrice') !=
getSelectedOrGroupedCell('maxPrice'),
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'dog', minPrice: 4, maxPrice: 5}}
// -> {1: {species: 'cat', minPrice: 3, maxPrice: 4}}
// Parrots are filtered out because they have zero range in price.
Since
v2.0.0
Join
The Join
type describes a function that lets you specify a Cell
or calculated value to join the main query Table
to other Tables
, by their Row
Id
.
Calling this function with two Id
parameters will indicate that the join to a Row
in an adjacent Table
is made by finding its Id
in a Cell
of the query's root Table
.
(
joinedTableId: string,
on: string,
): JoinedAs
Type | Description | |
---|---|---|
joinedTableId | string | |
on | string | The |
returns | JoinedAs | A |
Since
v2.0.0
Calling this function with two parameters (where the second is a function) will indicate that the join to a Row
in an adjacent Table
is made by calculating its Id
from the Cells and the Row
Id
of the query's root Table
.
(
joinedTableId: string,
on: (getCell: GetCell, rowId: string) => undefined | string,
): JoinedAs
Type | Description | |
---|---|---|
joinedTableId | string | |
on | (getCell: GetCell, rowId: string) => undefined | string | A callback that takes a |
returns | JoinedAs | A |
Since
v2.0.0
Calling this function with three Id
parameters will indicate that the join to a Row
in distant Table
is made by finding its Id
in a Cell
of an intermediately joined Table
.
(
joinedTableId: string,
fromIntermediateJoinedTableId: string,
on: string,
): JoinedAs
Type | Description | |
---|---|---|
joinedTableId | string | |
fromIntermediateJoinedTableId | string | The |
on | string | The |
returns | JoinedAs | A |
Since
v2.0.0
Calling this function with three parameters (where the third is a function) will indicate that the join to a Row
in distant Table
is made by calculating its Id
from the Cells and the Row
Id
of an intermediately joined Table
.
(
joinedTableId: string,
fromIntermediateJoinedTableId: string,
on: (getIntermediateJoinedCell: GetCell, intermediateJoinedRowId: string) => undefined | string,
): JoinedAs
Type | Description | |
---|---|---|
joinedTableId | string | |
fromIntermediateJoinedTableId | string | The |
on | (getIntermediateJoinedCell: GetCell, intermediateJoinedRowId: string) => undefined | string | A callback that takes a |
returns | JoinedAs | A |
Since
v2.0.0
The Join
function is provided to the third query
parameter of the setQueryDefinition
method.
You can join zero, one, or many Tables
. You can join the same underlying Table
multiple times, but in that case you will need to use the 'as' function to distinguish them from each other.
By default, each join is made from the main query Table
to the joined table, but it is also possible to connect via an intermediate join Table
to a more distant join Table
.
Because a Join
clause is used to identify which unique Row
Id
of the joined Table
will be joined to each Row
of the root Table
, queries follow the 'left join' semantics you may be familiar with from SQL. This means that an unfiltered query will only ever return the same number of Rows as the main Table
being queried, and indeed the resulting table (assuming it has not been aggregated) will even preserve the root Table
's original Row
Ids
.
Examples
This example shows a query that joins a single Table
by using an Id
present in the main query Table
.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species');
select('owners', 'name');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', name: 'Alice'}}
// -> {felix: {species: 'cat', name: 'Bob'}}
// -> {cujo: {species: 'dog', name: 'Carol'}}
This example shows a query that joins the same underlying Table
twice, and aliases them (and the selected Cell
Ids
). Note the left-join semantics: Felix the cat was bought, but the seller was unknown. The record still exists in the ResultTable
.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', buyerId: '1', sellerId: '2'},
felix: {species: 'cat', buyerId: '2'},
cujo: {species: 'dog', buyerId: '3', sellerId: '1'},
})
.setTable('humans', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('buyers', 'name').as('buyer');
select('sellers', 'name').as('seller');
// from pets
join('humans', 'buyerId').as('buyers');
join('humans', 'sellerId').as('sellers');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {buyer: 'Alice', seller: 'Bob'}}
// -> {felix: {buyer: 'Bob'}}
// -> {cujo: {buyer: 'Carol', seller: 'Alice'}}
This example shows a query that calculates the Id
of the joined Table
based from multiple values in the root Table
rather than a single Cell
.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('colorSpecies', {
'brown-dog': {price: 6},
'black-dog': {price: 5},
'brown-cat': {price: 4},
'black-cat': {price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('colorSpecies', 'price');
// from pets
join(
'colorSpecies',
(getCell) => `${getCell('color')}-${getCell('species')}`,
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {price: 6}}
// -> {felix: {price: 3}}
// -> {cujo: {price: 5}}
This example shows a query that joins two Tables
, one through the intermediate other.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
})
.setTable('states', {
CA: {name: 'California'},
WA: {name: 'Washington'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select(
(getTableCell) =>
`${getTableCell('species')} in ${getTableCell('states', 'name')}`,
).as('description');
// from pets
join('owners', 'ownerId');
join('states', 'owners', 'state');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {description: 'dog in California'}}
// -> {felix: {description: 'cat in California'}}
// -> {cujo: {description: 'dog in Washington'}}
Since
v2.0.0
JoinedAs
The JoinedAs
type describes an object returned from calling a Join
function so that the joined Table
Id
can be optionally aliased.
{as: (joinedTableId: Id) => void}
Type | Description | |
---|---|---|
as | (joinedTableId: Id) => void | A function that lets you specify an alias for the joined |
Note that if two Join
clauses are both aliased to the same name (or if you create two joins to the same underlying Table
, both without aliases), only the latter of two will be used in the query.
For the purposes of clarity, it's recommended to use an alias that does not collide with a real underlying Table
(whether included in the query or not).
Example
This example shows a query that joins the same underlying Table
twice, for different purposes. Both joins are aliased with the 'as' function to disambiguate them. Note that the selected Cells are also aliased.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', buyerId: '1', sellerId: '2'},
felix: {species: 'cat', buyerId: '2'},
cujo: {species: 'dog', buyerId: '3', sellerId: '1'},
})
.setTable('humans', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('buyers', 'name').as('buyer');
select('sellers', 'name').as('seller');
// from pets
join('humans', 'buyerId').as('buyers');
join('humans', 'sellerId').as('sellers');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {buyer: 'Alice', seller: 'Bob'}}
// -> {felix: {buyer: 'Bob'}}
// -> {cujo: {buyer: 'Carol', seller: 'Alice'}}
Since
v2.0.0
Select
The Select
type describes a function that lets you specify a Cell
or calculated value for including into the query's result.
Calling this function with one Id
parameter will indicate that the query should select the value of the specified Cell
from the query's root Table
.
(cellId: string): SelectedAs
Type | Description | |
---|---|---|
cellId | string | |
returns | SelectedAs | A |
Since
v2.0.0
Calling this function with two parameters will indicate that the query should select the value of the specified Cell
from a Table
that has been joined in the query.
(
joinedTableId: string,
joinedCellId: string,
): SelectedAs
Type | Description | |
---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
returns | SelectedAs | A |
Since
v2.0.0
Calling this function with one callback parameter will indicate that the query should select a calculated value, based on one or more Cell
values in the root Table
or a joined Table
, or on the root Table
's Row
Id
.
(getCell: (getTableCell: GetTableCell, rowId: string) => ResultCellOrUndefined): SelectedAs
Type | Description | |
---|---|---|
getCell | (getTableCell: GetTableCell, rowId: string) => ResultCellOrUndefined | A callback that takes a |
returns | SelectedAs | A |
Since
v2.0.0
The Select
function is provided to the third query
parameter of the setQueryDefinition
method. A query definition must call the Select
function at least once, otherwise it will be meaningless and return no data.
Examples
This example shows a query that selects two Cells from the main query Table
.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown', legs: 4},
felix: {species: 'cat', color: 'black', legs: 4},
cujo: {species: 'dog', color: 'black', legs: 4},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select}) => {
select('species');
select('color');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', color: 'brown'}}
// -> {felix: {species: 'cat', color: 'black'}}
// -> {cujo: {species: 'dog', color: 'black'}}
This example shows a query that selects two Cells, one from a joined Table
.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species');
select('owners', 'name');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', name: 'Alice'}}
// -> {felix: {species: 'cat', name: 'Bob'}}
// -> {cujo: {species: 'dog', name: 'Carol'}}
This example shows a query that calculates a value from two underlying Cells.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select(
(getTableCell) =>
`${getTableCell('species')} for ${getTableCell('owners', 'name')}`,
).as('description');
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {description: 'dog for Alice'}}
// -> {felix: {description: 'cat for Bob'}}
// -> {cujo: {description: 'dog for Carol'}}
Since
v2.0.0
SelectedAs
The SelectedAs
type describes an object returned from calling a Select
function so that the selected Cell
Id
can be optionally aliased.
{as: (selectedCellId: Id) => void}
Type | Description | |
---|---|---|
as | (selectedCellId: Id) => void |
If you are using a callback in the Select
cause, it is highly recommended to use the 'as' function, since otherwise a machine-generated column name will be used.
Note that if two Select
clauses are both aliased to the same name (or if two columns with the same underlying name are selected, both without aliases), only the latter of two will be used in the query.
Example
This example shows a query that selects two Cells, one from a joined Table
. Both are aliased with the 'as' function:
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species').as('petSpecies');
select('owners', 'name').as('ownerName');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {petSpecies: 'dog', ownerName: 'Alice'}}
// -> {felix: {petSpecies: 'cat', ownerName: 'Bob'}}
// -> {cujo: {petSpecies: 'dog', ownerName: 'Carol'}}
Since
v2.0.0
Where
The Where
type describes a function that lets you specify conditions to filter results, based on the underlying Cells of the root or joined Tables
.
Calling this function with two parameters is used to include only those Rows for which a specified Cell
in the query's root Table
has a specified value.
(
cellId: string,
equals: Cell,
): void
Type | Description | |
---|---|---|
cellId | string | |
equals | Cell | The value that the |
returns | void | This has no return value. |
Since
v2.0.0
Calling this function with three parameters is used to include only those Rows for which a specified Cell
in a joined Table
has a specified value.
(
joinedTableId: string,
joinedCellId: string,
equals: Cell,
): void
Type | Description | |
---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
equals | Cell | The value that the |
returns | void | This has no return value. |
Since
v2.0.0
Calling this function with one callback parameter is used to include only those Rows which meet a calculated boolean condition, based on values in the main and (optionally) joined Tables
.
(condition: (getTableCell: GetTableCell) => boolean): void
Type | Description | |
---|---|---|
condition | (getTableCell: GetTableCell) => boolean | A callback that takes a |
returns | void | This has no return value. |
Since
v2.0.0
The Where
function is provided to the third query
parameter of the setQueryDefinition
method.
If you do not specify a Where
clause, you should expect every non-empty Row
of the root Table
to appear in the query's results.
A Where
condition has to be true for a Row
to be included in the results. Each Where
class is additive, as though combined with a logical 'and'. If you wish to create an 'or' expression, use the single parameter version of the type that allows arbitrary programmatic conditions.
The Where
keyword differs from the Having
keyword in that the former describes conditions that should be met by underlying Cell
values (whether selected or not), and the latter describes conditions based on calculated and aggregated values - after Group
clauses have been applied.
Examples
This example shows a query that filters the results from a single Table
by comparing an underlying Cell
from it with a value.
import {createQueries, createStore} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, where}) => {
select('species');
where('species', 'dog');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog'}}
// -> {cujo: {species: 'dog'}}
This example shows a query that filters the results of a query by comparing an underlying Cell
from a joined Table
with a value. Note that the joined table has also been aliased, and so its alias is used in the Where
clause.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, where}) => {
select('species');
// from pets
join('owners', 'ownerId').as('petOwners');
where('petOwners', 'state', 'CA');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog'}}
// -> {felix: {species: 'cat'}}
This example shows a query that filters the results of a query with a condition that is calculated from underlying Cell
values from the main and joined Table
. Note that the joined table has also been aliased, and so its alias is used in the Where
clause.
import {createQueries, createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, where}) => {
select('species');
select('petOwners', 'state');
// from pets
join('owners', 'ownerId').as('petOwners');
where(
(getTableCell) =>
getTableCell('pets', 'species') === 'cat' ||
getTableCell('petOwners', 'state') === 'WA',
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {felix: {species: 'cat', state: 'CA'}}
// -> {cujo: {species: 'dog', state: 'WA'}}
Since
v2.0.0
Result type aliases
This is the collection of result type aliases within the queries
module. There are 4 result type aliases in total.
ResultTable
The ResultTable
type is the data structure representing the results of a query.
{[rowId: Id]: ResultRow}
A ResultTable
is typically accessed with the getResultTable
method or addResultTableListener
method. It is similar to the Table
type in the store
module, but without schema-specific typing, and is a regular JavaScript object containing individual ResultRow
objects, keyed by their Id
.
Example
import type {ResultTable} from 'tinybase';
export const resultTable: ResultTable = {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
};
Since
v2.0.0
ResultRow
The ResultRow
type is the data structure representing a single row in the results of a query.
{[cellId: Id]: ResultCell}
A ResultRow
is typically accessed with the getResultRow
method or addResultRowListener
method. It is similar to the Row
type in the store
module, but without schema-specific typing, and is a regular JavaScript object containing individual ResultCell
objects, keyed by their Id
.
Example
import type {ResultRow} from 'tinybase';
export const resultRow: ResultRow = {species: 'dog', color: 'brown'};
Since
v2.0.0
ResultCell
The ResultCell
type is the data structure representing a single cell in the results of a query.
string | number | boolean
A ResultCell
is typically accessed with the getResultCell
method or addResultCellListener
method. It is similar to the Cell
type in the store
module, but without schema-specific typing, and is a JavaScript string, number, or boolean.
Example
import type {ResultCell} from 'tinybase';
export const resultCell: ResultCell = 'dog';
Since
v2.0.0
ResultCellOrUndefined
The ResultCellOrUndefined
type is the data structure representing a single cell in the results of a query, or the value undefined
.
ResultCell | undefined
Since
v2.0.0
Listener type aliases
This is the collection of listener type aliases within the queries
module. There are 11 listener type aliases in total.
ResultTableCellIdsListener
The ResultTableCellIdsListener
type describes a function that is used to listen to changes to the Cell
Ids
that appear anywhere in a query's ResultTable
.
(
queries: Queries,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getIdChanges | GetIdChanges | undefined | |
returns | void | This has no return value. |
A ResultTableCellIdsListener
is provided when using the addResultTableCellIdsListener
method. See that method for specific examples.
When called, a ResultTableCellIdsListener
is given a reference to the Queries
object, and the Id
of the ResultTable
whose Cell
Ids
changed (which is the same as the query Id
).
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v4.1.0
ResultTableListener
The ResultTableListener
type describes a function that is used to listen to changes to a query's ResultTable
.
(
queries: Queries,
tableId: Id,
getCellChange: GetResultCellChange,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getCellChange | GetResultCellChange | A function that returns information about any |
returns | void | This has no return value. |
A ResultTableListener
is provided when using the addResultTableListener
method. See that method for specific examples.
When called, a ResultTableListener
is given a reference to the Queries
object, the Id
of the ResultTable
that changed (which is the same as the query Id
), and a GetResultCellChange
function that can be used to query ResultCell
values before and after the change.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultRowIdsListener
The ResultRowIdsListener
type describes a function that is used to listen to changes to the ResultRow
Ids
in a query's ResultTable
.
(
queries: Queries,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getIdChanges | GetIdChanges | undefined | |
returns | void | This has no return value. |
A ResultRowIdsListener
is provided when using the addResultRowIdsListener
method. See that method for specific examples.
When called, a ResultRowIdsListener
is given a reference to the Queries
object, and the Id
of the ResultTable
whose ResultRow
Ids
changed (which is the same as the query Id
).
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultSortedRowIdsListener
The ResultSortedRowIdsListener
type describes a function that is used to listen to changes to the sorted ResultRow
Ids
in a query's ResultTable
.
(
queries: Queries,
tableId: Id,
cellId: Id | undefined,
descending: boolean,
offset: number,
limit: number | undefined,
sortedRowIds: Ids,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
cellId | Id | undefined | The |
descending | boolean | Whether the sorting was in descending order. |
offset | number | |
limit | number | undefined | |
sortedRowIds | Ids | |
returns | void | This has no return value. |
A ResultSortedRowIdsListener
is provided when using the addResultSortedRowIdsListener
method. See that method for specific examples.
When called, a ResultSortedRowIdsListener
is given a reference to the Queries
object, the Id
of the ResultTable
whose ResultRow
Ids
changed (which is the same as the query Id
), the ResultCell
Id
being used to sort them, whether descending or not, and the offset and limit of the number of Ids
returned, for pagination purposes. It also receives the sorted array of Ids
itself, so that you can use them in the listener without the additional cost of an explicit call to getResultSortedRowIds.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultRowCountListener
The ResultRowCountListener
type describes a function that is used to listen to changes to the number of ResultRow
objects in a query's ResultTable
.
(
queries: Queries,
tableId: Id,
count: number,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
count | number | The number of |
returns | void | This has no return value. |
A ResultRowCountListener
is provided when using the addResultRowCountListener
method. See that method for specific examples.
When called, a ResultRowCountListener
is given a reference to the Queries
object, the Id
of the ResultTable
whose ResultRow
Ids
changed (which is the same as the query Id
), and the count of ResultRow
objects in the ResultTable
.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v4.1.0
ResultRowListener
The ResultRowListener
type describes a function that is used to listen to changes to a ResultRow
in a query's ResultTable
.
(
queries: Queries,
tableId: Id,
rowId: Id,
getCellChange: GetResultCellChange,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
getCellChange | GetResultCellChange | A function that returns information about any |
returns | void | This has no return value. |
A ResultRowListener
is provided when using the addResultRowListener
method. See that method for specific examples.
When called, a ResultRowListener
is given a reference to the Queries
object, the Id
of the ResultTable
that changed (which is the same as the query Id
), the Id
of the ResultRow
that changed, and a GetResultCellChange
function that can be used to query ResultCell
values before and after the change.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultCellIdsListener
The ResultCellIdsListener
type describes a function that is used to listen to changes to the ResultCell
Ids
in a ResultRow
in a query's ResultTable
.
(
queries: Queries,
tableId: Id,
rowId: Id,
getIdChanges: GetIdChanges | undefined,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
getIdChanges | GetIdChanges | undefined | |
returns | void | This has no return value. |
A ResultCellIdsListener
is provided when using the addResultCellIdsListener
method. See that method for specific examples.
When called, a ResultCellIdsListener
is given a reference to the Queries
object, the Id
of the ResultTable
that changed (which is the same as the query Id
), and the Id
of the ResultRow
whose ResultCell
Ids
changed.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
GetResultCellChange
The GetResultCellChange
type describes a function that returns information about any ResultCell
's changes during a transaction.
(
tableId: Id,
rowId: Id,
cellId: Id,
): ResultCellChange
Type | Description | |
---|---|---|
tableId | Id | The |
rowId | Id | |
cellId | Id | The |
returns | ResultCellChange |
A GetResultCellChange
function is provided to every listener when called due the Store
changing. The listener can then fetch the previous value of a ResultCell
before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v2.0.0
ResultCellChange
The ResultCellChange
type describes a ResultCell
's changes during a transaction.
[changed: boolean, oldCell: ResultCellOrUndefined, newCell: ResultCellOrUndefined]
This is returned by the GetResultCellChange
function that is provided to every listener when called. This array contains the previous value of a ResultCell
before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
Since
v2.0.0
ResultCellListener
The ResultCellListener
type describes a function that is used to listen to changes to a ResultCell
in a query's ResultTable
.
(
queries: Queries,
tableId: Id,
rowId: Id,
cellId: Id,
newCell: ResultCell,
oldCell: ResultCell,
getCellChange: GetResultCellChange,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
cellId | Id | The |
newCell | ResultCell | The new value of the |
oldCell | ResultCell | The old value of the |
getCellChange | GetResultCellChange | A function that returns information about any |
returns | void | This has no return value. |
A ResultCellListener
is provided when using the addResultCellListener
method. See that method for specific examples.
When called, a ResultCellListener
is given a reference to the Queries
object, the Id
of the ResultTable
that changed (which is the same as the query Id
), the Id
of the ResultRow
that changed, and the Id
of ResultCell
that changed. It is also given the new value of the ResultCell
, the old value of the ResultCell
, and a GetResultCellChange
function that can be used to query ResultCell
values before and after the change.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
QueryIdsListener
The QueryIdsListener
type describes a function that is used to listen to Query definitions being added or removed.
(queries: Queries): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
returns | void | This has no return value. |
A QueryIdsListener
is provided when using the addQueryIdsListener
method. See that method for specific examples.
When called, a QueryIdsListener
is given a reference to the Queries
object.
Since
v2.0.0
Aggregators type aliases
This is the collection of aggregators type aliases within the queries
module. There are 4 aggregators type aliases in total.
Aggregate
The Aggregate
type describes a custom function that takes an array of Cell
values and returns an aggregate of them.
(
cells: Cell[],
length: number,
): ResultCell
Type | Description | |
---|---|---|
cells | Cell[] | The array of |
length | number | The length of the array of |
returns | ResultCell |
There are a number of common predefined aggregators, such as for counting, summing, and averaging values. This type is instead used for when you wish to use a more complex aggregation of your own devising.
Since
v2.0.0
AggregateAdd
The AggregateAdd
type describes a function that can be used to optimize a custom Aggregate
by providing a shortcut for when a single value is added to the input values.
(
current: Cell,
add: Cell,
length: number,
): ResultCellOrUndefined
Type | Description | |
---|---|---|
current | Cell | The current value of the aggregation. |
add | Cell | The |
length | number | The length of the array of |
returns | ResultCellOrUndefined |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when adding a new number to a series, the new sum of the series is the new value added to the previous sum.
If it is not possible to shortcut the aggregation based on just one value being added, return undefined
and the aggregation will be completely recalculated.
When possible, if you are providing a custom Aggregate
, seek an implementation of an AggregateAdd
function that can reduce the complexity cost of growing the input data set.
Since
v2.0.0
AggregateRemove
The AggregateRemove
type describes a function that can be used to optimize a custom Aggregate
by providing a shortcut for when a single value is removed from the input values.
(
current: Cell,
remove: Cell,
length: number,
): ResultCellOrUndefined
Type | Description | |
---|---|---|
current | Cell | The current value of the aggregation. |
remove | Cell | The |
length | number | The length of the array of |
returns | ResultCellOrUndefined |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when removing a number from a series, the new sum of the series is the new value subtracted from the previous sum.
If it is not possible to shortcut the aggregation based on just one value being removed, return undefined
and the aggregation will be completely recalculated. One example might be if you were taking the minimum of the values, and the previous minimum is being removed. The whole of the rest of the list will need to be re-scanned to find a new minimum.
When possible, if you are providing a custom Aggregate
, seek an implementation of an AggregateRemove
function that can reduce the complexity cost of shrinking the input data set.
Since
v2.0.0
AggregateReplace
The AggregateReplace
type describes a function that can be used to optimize a custom Aggregate
by providing a shortcut for when a single value in the input values is replaced with another.
(
current: Cell,
add: Cell,
remove: Cell,
length: number,
): ResultCellOrUndefined
Type | Description | |
---|---|---|
current | Cell | The current value of the aggregation. |
add | Cell | The |
remove | Cell | The |
length | number | The length of the array of |
returns | ResultCellOrUndefined |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when replacing a number in a series, the new sum of the series is the previous sum, plus the new value, minus the old value.
If it is not possible to shortcut the aggregation based on just one value changing, return undefined
and the aggregation will be completely recalculated.
When possible, if you are providing a custom Aggregate
, seek an implementation of an AggregateReplace
function that can reduce the complexity cost of changing the input data set in place.
Since
v2.0.0
Callback type aliases
This is the collection of callback type aliases within the queries
module. There are 5 callback type aliases in total.
GetTableCell
The GetTableCell
type describes a function that takes a Id
and returns the Cell
value for a particular Row
, optionally in a joined Table
.
When called with one parameter, this function will return the value of the specified Cell
from the query's root Table
for the Row
being selected or filtered.
(cellId: string): CellOrUndefined
Type | Description | |
---|---|---|
cellId | string | |
returns | CellOrUndefined | A |
Since
v2.0.0
When called with two parameters, this function will return the value of the specified Cell
from a Table
that has been joined in the query, for the Row
being selected or filtered.
(
joinedTableId: string,
joinedCellId: string,
): CellOrUndefined
Type | Description | |
---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
returns | CellOrUndefined | A |
Since
v2.0.0
A GetTableCell
can be provided when setting query definitions, specifically in the Select
and Where
clauses when you want to create or filter on calculated values. See those methods for specific examples.
Since
v2.0.0
ResultTableCallback
The ResultTableCallback
type describes a function that takes a ResultTable
's Id
and a callback to loop over each ResultRow
within it.
(
tableId: Id,
forEachRow: (rowCallback: ResultRowCallback) => void,
): void
Type | Description | |
---|---|---|
tableId | Id | The |
forEachRow | (rowCallback: ResultRowCallback) => void | A function that will let you iterate over the |
returns | void | This has no return value. |
A ResultTableCallback
is provided when using the forEachResultTable
method, so that you can do something based on every ResultTable
in the Queries
object. See that method for specific examples.
Since
v2.0.0
ResultRowCallback
The ResultRowCallback
type describes a function that takes a ResultRow
's Id
and a callback to loop over each ResultCell
within it.
(
rowId: Id,
forEachCell: (cellCallback: ResultCellCallback) => void,
): void
Type | Description | |
---|---|---|
rowId | Id | |
forEachCell | (cellCallback: ResultCellCallback) => void | |
returns | void | This has no return value. |
A ResultRowCallback
is provided when using the forEachResultRow
method, so that you can do something based on every ResultRow
in a ResultTable
. See that method for specific examples.
Since
v2.0.0
ResultCellCallback
The ResultCellCallback
type describes a function that takes a ResultCell
's Id
and its value.
(
cellId: Id,
cell: ResultCell,
): void
Type | Description | |
---|---|---|
cellId | Id | The |
cell | ResultCell | The value of the |
returns | void | This has no return value. |
A ResultCellCallback
is provided when using the forEachResultCell
method, so that you can do something based on every ResultCell
in a ResultRow
. See that method for specific examples.
Since
v2.0.0
QueryCallback
The QueryCallback
type describes a function that takes a query's Id
.
(queryId: Id): void
Type | Description | |
---|---|---|
queryId | Id | The |
returns | void | This has no return value. |
A QueryCallback
is provided when using the forEachQuery
method, so that you can do something based on every query in the Queries
object. See that method for specific examples.
Since
v2.0.0
Development type aliases
This is the collection of development type aliases within the queries
module. There is only one type alias, QueriesListenerStats
.
QueriesListenerStats
The QueriesListenerStats
type describes the number of listeners registered with the Queries
object, and can be used for debugging purposes.
{
table: number;
tableCellIds: number;
rowCount: number;
rowIds: number;
sortedRowIds: number;
row: number;
cellIds: number;
cell: number;
}
Type | Description | |
---|---|---|
table | number | The number of |
tableCellIds | number | The number of |
rowCount | number | The number of |
rowIds | number | The number of |
sortedRowIds | number | The number of |
row | number | The number of |
cellIds | number | The number of |
cell | number | The number of |
A QueriesListenerStats
object is returned from the getListenerStats
method.
Since
v2.0.0
checkpoints
The checkpoints
module of the TinyBase project provides the ability to create and track checkpoints made to the data in Store
objects.
The main entry point to this module is the createCheckpoints
function, which returns a new Checkpoints
object. From there, you can create new checkpoints, go forwards or backwards to others, and register listeners for when the list of checkpoints change.
Since
v1.0.0
Interfaces
There is one interface, Checkpoints
, within the checkpoints
module.
Checkpoints
A Checkpoints
object lets you set checkpoints on a Store
, and move forward and backward through them to create undo and redo functionality.
Create a Checkpoints
object easily with the createCheckpoints
function. From there, you can set checkpoints (with the addCheckpoint
method), query the checkpoints available (with the getCheckpointIds
method), move forward and backward through them (with the goBackward
method, goForward
method, and goTo
method), and add listeners for when the list checkpoints changes (with the addCheckpointIdsListener
method).
Checkpoints
work for both changes to tabular data and to keyed value data.
Every checkpoint can be given a label which can be used to describe the actions that changed the Store
before this checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.
Example
This example shows a simple lifecycle of a Checkpoints
object: from creation, to adding a checkpoint, getting the list of available checkpoints, and then registering and removing a listener for them.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore()
.setTables({pets: {fido: {sold: false}}})
.setValue('open', true);
const checkpoints = createCheckpoints(store);
checkpoints.setSize(200);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
store.setValue('open', false);
checkpoints.addCheckpoint('closed');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '2', []]
checkpoints.goBackward();
console.log(store.getValue('open'));
// -> true
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['2']]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log(checkpoints.getCheckpointIds());
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> [['0'], undefined, []]
checkpoints.addCheckpoint();
// -> [['0'], '3', []]
// Previous redo of checkpoints '1' and '2' are now not possible.
checkpoints.delListener(listenerId);
See also
- Using Checkpoints guide
- Todo App demos
- Drawing demo
Since
v1.0.0
Getter methods
This is the collection of getter methods within the Checkpoints
interface. There are 4 getter methods in total.
getStore
The getStore
method returns a reference to the underlying Store
that is backing this Checkpoints
object.
getStore(): Store
Example
This example creates a Checkpoints
object against a newly-created Store
and then gets its reference in order to update its data and set a checkpoint.
import {createCheckpoints, createStore} from 'tinybase';
const checkpoints = createCheckpoints(createStore());
checkpoints.getStore().setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
Since
v1.0.0
getCheckpoint
The getCheckpoint
method fetches the label for a checkpoint, if it had been provided at the time of the addCheckpoint
method or set subsequently with the setCheckpoint
method.
getCheckpoint(checkpointId: string): undefined | string
Type | Description | |
---|---|---|
checkpointId | string | The |
returns | undefined | string | A string label for the requested checkpoint, an empty string if it was never set, or |
If the checkpoint has had no label provided, this method will return an empty string.
Examples
This example creates a Store
, adds a Checkpoints
object, and sets a checkpoint with a label, before retrieving it again.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
console.log(checkpoints.addCheckpoint('sale'));
// -> '1'
console.log(checkpoints.getCheckpoint('1'));
// -> 'sale'
This example creates a Store
, adds a Checkpoints
object, and sets a checkpoint without a label, setting it subsequently. A non-existent checkpoint return an undefined
label.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpoint('1'));
// -> ''
checkpoints.setCheckpoint('1', 'sold');
console.log(checkpoints.getCheckpoint('1'));
// -> 'sold'
console.log(checkpoints.getCheckpoint('2'));
// -> undefined
Since
v1.0.0
getCheckpointIds
The getCheckpointIds
method returns an array of the checkpoint Ids
being managed by this Checkpoints
object.
getCheckpointIds(): CheckpointIds
returns | CheckpointIds | A |
---|
The returned CheckpointIds
array contains 'backward' checkpoint Ids
, the current checkpoint Id
(if present), and the 'forward' checkpointIds. Together, these are sufficient to understand the state of the Checkpoints
object and what movement is possible backward or forward through the checkpoint stack.
Example
This example creates a Store
, adds a Checkpoints
object, and then gets the Ids
of the checkpoints as it sets them and moves around the stack.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
checkpoints.goForward();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
Since
v1.0.0
hasCheckpoint
The hasCheckpoint
method returns a boolean indicating whether a given Checkpoint exists in the Checkpoints
object.
hasCheckpoint(checkpointId: string): boolean
Type | Description | |
---|---|---|
checkpointId | string | The |
returns | boolean | Whether a Checkpoint with that |
Example
This example shows two simple Checkpoint existence checks.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.hasCheckpoint('0'));
// -> true
console.log(checkpoints.hasCheckpoint('1'));
// -> false
Since
v1.0.0
Setter methods
This is the collection of setter methods within the Checkpoints
interface. There are only two setter methods, addCheckpoint
and setCheckpoint
.
addCheckpoint
The addCheckpoint
method records a checkpoint of the Store
into the Checkpoints
object that can be reverted to in the future.
addCheckpoint(label?: string): string
Type | Description | |
---|---|---|
label? | string | An optional label to describe the actions leading up to this checkpoint. |
returns | string | The |
If no changes have been made to the Store
since the last time a checkpoint was made, this method will have no effect.
The optional label
parameter can be used to describe the actions that changed the Store
before this checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.
Example
This example creates a Store
, adds a Checkpoints
object, and adds two checkpoints, one with a label.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'species', 'dog');
const checkpointId1 = checkpoints.addCheckpoint();
console.log(checkpointId1);
// -> '1'
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
console.log(checkpoints.getCheckpoint('2'));
// -> 'sale'
Since
v1.0.0
setCheckpoint
The setCheckpoint
method updates the label for a checkpoint in the Checkpoints
object after it has been created.
setCheckpoint(
checkpointId: string,
label: string,
): Checkpoints
Type | Description | |
---|---|---|
checkpointId | string | The |
label | string | A label to describe the actions leading up to this checkpoint or left undefined if you want to clear the current label. |
returns | Checkpoints | A reference to the |
The label
parameter can be used to describe the actions that changed the Store
before the given checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.
Generally you will provide the label
parameter when the addCheckpoint
method is called. Use this setCheckpoint
method only when you need to change the label at a later point.
You cannot add a label to a checkpoint that does not yet exist.
Example
This example creates a Store
, adds a Checkpoints
object, and sets two checkpoints, one with a label, which are both then re-labelled.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint();
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpoint('1'));
// -> ''
console.log(checkpoints.getCheckpoint('2'));
// -> 'sale'
checkpoints.setCheckpoint('1', 'identified');
checkpoints.setCheckpoint('2', '');
console.log(checkpoints.getCheckpoint('1'));
// -> 'identified'
console.log(checkpoints.getCheckpoint('2'));
// -> ''
checkpoints.setCheckpoint('3', 'unknown');
console.log(checkpoints.getCheckpoint('3'));
// -> undefined
Since
v1.0.0
Listener methods
This is the collection of listener methods within the Checkpoints
interface. There are only three listener methods, addCheckpointIdsListener
, addCheckpointListener
, and delListener
.
addCheckpointIdsListener
The addCheckpointIdsListener
method registers a listener function with the Checkpoints
object that will be called whenever its set of checkpoints changes.
addCheckpointIdsListener(listener: CheckpointIdsListener): string
Type | Description | |
---|---|---|
listener | CheckpointIdsListener | The function that will be called whenever the checkpoints change. |
returns | string | A unique |
The provided listener is a CheckpointIdsListener
function, and will be called with a reference to the Checkpoints
object.
Example
This example creates a Store
, a Checkpoints
object, and then registers a listener that responds to any changes to the checkpoints.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('Checkpoint Ids changed');
console.log(checkpoints.getCheckpointIds());
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> 'Checkpoint Ids changed'
// -> [['0'], undefined, []]
checkpoints.addCheckpoint();
// -> 'Checkpoint Ids changed'
// -> [['0'], '1', []]
checkpoints.goBackward();
// -> 'Checkpoint Ids changed'
// -> [[], '0', ['1']]
checkpoints.goForward();
// -> 'Checkpoint Ids changed'
// -> [['0'], '1', []]
checkpoints.delListener(listenerId);
Since
v1.0.0
addCheckpointListener
The addCheckpointListener
method registers a listener function with the Checkpoints
object that will be called whenever the label of a checkpoint changes.
addCheckpointListener(
checkpointId: IdOrNull,
listener: CheckpointListener,
): string
Type | Description | |
---|---|---|
checkpointId | IdOrNull | The |
listener | CheckpointListener | The function that will be called whenever the checkpoint label changes. |
returns | string | A unique |
You can either listen to a single checkpoint label (by specifying the checkpoint Id
as the method's first parameter), or changes to any checkpoint label (by providing a null
wildcard).
The provided listener is a CheckpointListener
function, and will be called with a reference to the Checkpoints
object, and the Id
of the checkpoint whose label changed.
Example
This example creates a Store
, a Checkpoints
object, and then registers a listener that responds to any changes to a specific checkpoint label, including when the checkpoint no longer exists.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointListener('1', () => {
console.log('Checkpoint 1 label changed');
console.log(checkpoints.getCheckpoint('1'));
});
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
// -> 'Checkpoint 1 label changed'
// -> 'sale'
checkpoints.setCheckpoint('1', 'sold');
// -> 'Checkpoint 1 label changed'
// -> 'sold'
checkpoints.setCheckpoint('1', 'sold');
// The listener is not called when the label does not change.
checkpoints.goTo('0');
store.setCell('pets', 'fido', 'sold', false);
// -> 'Checkpoint 1 label changed'
// -> undefined
// The checkpoint no longer exists.
checkpoints.delListener(listenerId);
Since
v1.0.0
delListener
The delListener
method removes a listener that was previously added to the Checkpoints
object.
delListener(listenerId: string): Checkpoints
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Checkpoints | A reference to the |
Use the Id
returned by the addCheckpointIdsListener
method. Note that the Checkpoints
object may re-use this Id
for future listeners added to it.
Example
This example creates a Store
, a Checkpoints
object, registers a listener, and then removes it.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('checkpoints changed');
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
checkpoints.delListener(listenerId);
store.setCell('pets', 'fido', 'sold', 'true');
// -> undefined
// The listener is not called.
Since
v1.0.0
Configuration methods
This is the collection of configuration methods within the Checkpoints
interface. There is only one method, setSize
.
setSize
The setSize
method lets you specify how many checkpoints the Checkpoints
object will store.
setSize(size: number): Checkpoints
Type | Description | |
---|---|---|
size | number | The number of checkpoints that this |
returns | Checkpoints | A reference to the |
If you set more checkpoints than this size, the oldest checkpoints will be pruned to make room for more recent ones.
The default size for a newly-created Checkpoints
object is 100.
Example
This example creates a Store
, adds a Checkpoints
object, reduces the size of the Checkpoints
object dramatically and then creates more than that number of checkpoints to demonstrate the oldest being pruned.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {views: 0}}});
const checkpoints = createCheckpoints(store);
checkpoints.setSize(2);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'views', 1);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
store.setCell('pets', 'fido', 'views', 2);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
store.setCell('pets', 'fido', 'views', 3);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['1', '2'], '3', []]
Since
v1.0.0
Iterator methods
This is the collection of iterator methods within the Checkpoints
interface. There is only one method, forEachCheckpoint
.
forEachCheckpoint
The forEachCheckpoint
method takes a function that it will then call for each Checkpoint in a specified Checkpoints
object.
forEachCheckpoint(checkpointCallback: CheckpointCallback): void
Type | Description | |
---|---|---|
checkpointCallback | CheckpointCallback | The function that should be called for every Checkpoint. |
returns | void | This has no return value. |
This method is useful for iterating over the structure of the Checkpoints
object in a functional style. The checkpointCallback
parameter is a CheckpointCallback
function that will be called with the Id
of each Checkpoint.
Example
This example iterates over each Checkpoint in a Checkpoints
object.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.forEachCheckpoint((checkpointId, label) => {
console.log(`${checkpointId}:${label}`);
});
// -> '0:'
// -> '1:sale'
Since
v1.0.0
Lifecycle methods
This is the collection of lifecycle methods within the Checkpoints
interface. There are only three lifecycle methods, clear
, clearForward
, and destroy
.
clear
The clear
method resets this Checkpoints
object to its initial state, removing all the checkpoints it has been managing.
clear(): Checkpoints
returns | Checkpoints | A reference to the |
---|
Obviously this method should be used with caution as it destroys the ability to undo or redo recent changes to the Store
(though of course the Store
itself is not reset by this method).
This method can be useful when a Store
is being loaded via a Persister
asynchronously after the Checkpoints
object has been attached, and you don't want users to be able to undo the initial load of the data. In this case you could call the clear
method immediately after the initial load so that that is the baseline from which all subsequent changes are tracked.
Example
This example creates a Store
, a Checkpoints
object, adds a listener, makes a change and then clears the checkpoints.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('checkpoints changed');
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
store.setCell('pets', 'fido', 'sold', true);
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
console.log(store.getTables());
// -> {pets: {fido: {sold: true, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
checkpoints.clear();
// -> 'checkpoints changed'
console.log(store.getTables());
// -> {pets: {fido: {sold: true, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
checkpoints.delListener(listenerId);
Since
v1.0.0
clearForward
The clearForward
method resets just the 'redo' checkpoints it has been managing.
clearForward(): Checkpoints
returns | Checkpoints | A reference to the |
---|
Obviously this method should be used with caution as it destroys the ability to redo recent changes to the Store
(though of course the Store
itself is not reset by this method).
This method can be useful when you want to prohibit a user from redoing changes they have undone. The 'backward' redo stack, and current checkpoint are not affected.
Example
This example creates a Store
, a Checkpoints
object, adds a listener, makes a change and then clears the forward checkpoints.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('checkpoints changed');
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
store.setCell('pets', 'fido', 'sold', true);
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
checkpoints.goBackward();
// -> 'checkpoints changed'
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown', sold: false}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]
checkpoints.clearForward();
// -> 'checkpoints changed'
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.delListener(listenerId);
Since
v4.5.3
destroy
The destroy
method should be called when this Checkpoints
object is no longer used.
destroy(): void
This guarantees that all of the listeners that the object registered with the underlying Store
are removed and it can be correctly garbage collected.
Example
This example creates a Store
, adds a Checkpoints
object (that registers a CellListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(store.getListenerStats().cell);
// -> 1
checkpoints.destroy();
console.log(store.getListenerStats().cell);
// -> 0
Since
v1.0.0
Movement methods
This is the collection of movement methods within the Checkpoints
interface. There are only three movement methods, goBackward
, goForward
, and goTo
.
goBackward
The goBackward
method moves the state of the underlying Store
back to the previous checkpoint, effectively performing an 'undo' on the Store
data.
goBackward(): Checkpoints
returns | Checkpoints | A reference to the |
---|
If there is no previous checkpoint to return to, this method has no effect.
Example
This example creates a Store
, a Checkpoints
object, makes a change and then goes backward to the state of the Store
before the change.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
Since
v1.0.0
goForward
The goForward
method moves the state of the underlying Store
forwards to a future checkpoint, effectively performing an 'redo' on the Store
data.
goForward(): Checkpoints
returns | Checkpoints | A reference to the |
---|
If there is no future checkpoint to return to, this method has no effect.
Note that if you have previously used the goBackward
method to undo changes, the forwards 'redo' stack will only exist while you do not make changes to the Store
. In general the goForward
method is expected to be used to redo changes that were just undone.
Examples
This example creates a Store
, a Checkpoints
object, makes a change and then goes backward to the state of the Store
before the change. It then goes forward again to restore the state with the changes.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
checkpoints.goForward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> true
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
This example creates a Store
, a Checkpoints
object, makes a change and then goes backward to the state of the Store
before the change. It makes a new change, the redo stack disappears, and then the attempt to forward again has no effect.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
store.setCell('pets', 'fido', 'color', 'brown');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], undefined, []]
checkpoints.goForward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [['0'], undefined, []]
// The original change cannot be redone.
Since
v1.0.0
goTo
The goTo
method moves the state of the underlying Store
backwards or forwards to a specified checkpoint.
goTo(checkpointId: string): Checkpoints
Type | Description | |
---|---|---|
checkpointId | string | The |
returns | Checkpoints | A reference to the |
If there is no checkpoint with the Id
specified, this method has no effect.
Example
This example creates a Store
, a Checkpoints
object, makes two changes and then goes directly to the state of the Store
before the two changes. It then goes forward again one change, also using the goTo
method. Finally it tries to go to a checkpoint that does not exist.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'color', 'brown');
checkpoints.addCheckpoint('identification');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
checkpoints.goTo('0');
console.log(store.getTables());
// -> {pets: {fido: {sold: false}}}
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1', '2']]
checkpoints.goTo('1');
console.log(store.getTables());
// -> {pets: {fido: {sold: false, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]
checkpoints.goTo('3');
console.log(store.getTables());
// -> {pets: {fido: {sold: false, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]
Since
v1.0.0
Development methods
This is the collection of development methods within the Checkpoints
interface. There is only one method, getListenerStats
.
getListenerStats
The getListenerStats
method provides a set of statistics about the listeners registered with the Checkpoints
object, and is used for debugging purposes.
getListenerStats(): CheckpointsListenerStats
returns | CheckpointsListenerStats | A |
---|
The CheckpointsListenerStats
object contains a breakdown of the different types of listener.
The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Checkpoints
object.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore();
const checkpoints = createCheckpoints(store);
checkpoints.addCheckpointIdsListener(() => {
console.log('Checkpoint Ids changed');
});
checkpoints.addCheckpointListener(null, () => {
console.log('Checkpoint label changed');
});
console.log(checkpoints.getListenerStats());
// -> {checkpointIds: 1, checkpoint: 1}
Since
v1.0.0
Functions
There is one function, createCheckpoints
, within the checkpoints
module.
createCheckpoints
The createCheckpoints
function creates a Checkpoints
object, and is the main entry point into the checkpoints
module.
createCheckpoints(store: Store): Checkpoints
Type | Description | |
---|---|---|
store | Store | The |
returns | Checkpoints | A reference to the new |
A given Store
can only have one Checkpoints
object associated with it. If you call this function twice on the same Store
, your second call will return a reference to the Checkpoints
object created by the first.
Examples
This example creates a Checkpoints
object.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore();
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
This example creates a Checkpoints
object, and calls the method a second time for the same Store
to return the same object.
import {createCheckpoints, createStore} from 'tinybase';
const store = createStore();
const checkpoints1 = createCheckpoints(store);
const checkpoints2 = createCheckpoints(store);
console.log(checkpoints1 === checkpoints2);
// -> true
Since
v1.0.0
Type Aliases
These are the type aliases within the checkpoints
module.
Listener type aliases
This is the collection of listener type aliases within the checkpoints
module. There are only two listener type aliases, CheckpointIdsListener
and CheckpointListener
.
CheckpointIdsListener
The CheckpointIdsListener
type describes a function that is used to listen to changes to the checkpoint Ids
in a Checkpoints
object.
(checkpoints: Checkpoints): void
Type | Description | |
---|---|---|
checkpoints | Checkpoints | A reference to the |
returns | void | This has no return value. |
A CheckpointIdsListener
is provided when using the addCheckpointIdsListener
method. See that method for specific examples.
When called, a CheckpointIdsListener
is given a reference to the Checkpoints
object.
Since
v1.0.0
CheckpointListener
The CheckpointListener
type describes a function that is used to listen to changes to a checkpoint's label in a Checkpoints
object.
(
checkpoints: Checkpoints,
checkpointId: Id,
): void
Type | Description | |
---|---|---|
checkpoints | Checkpoints | A reference to the |
checkpointId | Id | The |
returns | void | This has no return value. |
A CheckpointListener
is provided when using the addCheckpointListener
method. See that method for specific examples.
When called, a CheckpointListener
is given a reference to the Checkpoints
object, and the Id
of the checkpoint whose label changed.
Since
v1.0.0
Callback type aliases
This is the collection of callback type aliases within the checkpoints
module. There is only one type alias, CheckpointCallback
.
CheckpointCallback
The CheckpointCallback
type describes a function that takes a Checkpoint's Id
.
(
checkpointId: Id,
label?: string,
): void
Type | Description | |
---|---|---|
checkpointId | Id | The |
label? | string | |
returns | void | This has no return value. |
A CheckpointCallback
is provided when using the forEachCheckpoint
method, so that you can do something based on every Checkpoint in the Checkpoints
object. See that method for specific examples.
Since
v1.0.0
Identity type aliases
This is the collection of identity type aliases within the checkpoints
module. There is only one type alias, CheckpointIds
.
CheckpointIds
The CheckpointIds
type is a representation of the list of checkpoint Ids
stored in a Checkpoints
object.
[Ids, Id | undefined, Ids]
There are three parts to a CheckpointsIds array:
- The 'backward' checkpoint
Ids
that can be rolled backward to (in other words, the checkpoints in the undo stack for thisStore
). They are in chronological order with the oldest checkpoint at the start of the array. - The current checkpoint
Id
of theStore
's state, orundefined
if the current state has not been checkpointed. - The 'forward' checkpoint
Ids
that can be rolled forward to (in other words, the checkpoints in the redo stack for thisStore
). They are in chronological order with the newest checkpoint at the end of the array.
Since
v1.0.0
Development type aliases
This is the collection of development type aliases within the checkpoints
module. There is only one type alias, CheckpointsListenerStats
.
CheckpointsListenerStats
The CheckpointsListenerStats
type describes the number of listeners registered with the Checkpoints
object, and can be used for debugging purposes.
{
checkpointIds: number;
checkpoint: number;
}
Type | Description | |
---|---|---|
checkpointIds | number | The number of |
checkpoint | number | The number of |
A CheckpointsListenerStats
object is returned from the getListenerStats
method.
Since
v1.0.0
common
The common
module of the TinyBase project provides a small collection of common types used across other modules.
Since
v1.0.0
Functions
These are the functions within the common
module.
defaultSorter
The defaultSorter
function is provided as a convenience to sort keys alphanumerically, and can be provided to the sliceIdSorter
and rowIdSorter
parameters of the setIndexDefinition
method in the indexes
module, for example.
defaultSorter(
sortKey1: SortKey,
sortKey2: SortKey,
): number
Type | Description | |
---|---|---|
sortKey1 | SortKey | The first item of the pair to compare. |
sortKey2 | SortKey | The second item of the pair to compare. |
returns | number | A number indicating how to sort the pair. |
Examples
This example creates an Indexes
object.
import {createIndexes, createStore} from 'tinybase';
const store = createStore();
const indexes = createIndexes(store);
console.log(indexes.getIndexIds());
// -> []
This example creates a Store
, creates an Indexes
object, and defines an Index
based on the first letter of the pets' names. The Slice
Ids
(and Row
Ids
within them) are alphabetically sorted using the defaultSorter
function.
import {createIndexes, createStore, defaultSorter} from 'tinybase';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition(
'byFirst', // indexId
'pets', // tableId
(_, rowId) => rowId[0], // each Row's Slice Id
(_, rowId) => rowId, // each Row's sort key
defaultSorter, // sort Slice Ids
defaultSorter, // sort Row Ids by sort key
);
console.log(indexes.getSliceIds('byFirst'));
// -> ['c', 'f']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['felix', 'fido']
Since
v1.0.0
getUniqueId
The getUniqueId
function returns a unique string of a given length.
getUniqueId(length?: number): Id
Type | Description | |
---|---|---|
length? | number | The desired length of the unique |
returns | Id | A unique |
This is used internally by TinyBase for the synchronizer protocol and for unique MergeableStore
identifiers. But it is useful enough for it to be publicly exposed for purposes such as identifying shared collaboration rooms, or creating other Ids
that need to be unique.
The string may contain numbers, lower or upper case letters, or the '-' or '_' characters. This makes them URL-safe, and means they can be identified with a regex like /[-_0-9A-Za-z]+/
.
This function prefers to use the crypto
module to generate random numbers, but where that is not available (such as in React Native), a Math.random
implementation is used. Whilst that may not be cryptographically sound, it should suffice for most TinyBase-related purposes.
Example
This example creates two 8 character long Ids
and compares them.
import {getUniqueId} from 'tinybase';
const id1 = getUniqueId(8);
const id2 = getUniqueId(8);
console.log(id1.length);
// -> 8
console.log(id2.length);
// -> 8
console.log(id1 == id2);
// -> false
Since
v5.0.0
Type Aliases
These are the type aliases within the common
module.
Callback type aliases
This is the collection of callback type aliases within the common
module. There are only two callback type aliases, Callback
and ParameterizedCallback
.
Callback
The Callback
type represents a function that is used as a callback and which does not take a parameter.
(): void
Since
v1.0.0
ParameterizedCallback
The ParameterizedCallback
type represents a generic function that will take an optional parameter - such as the handler of a DOM event.
(parameter?: Parameter): void
Type | Description | |
---|---|---|
parameter? | Parameter | |
returns | void | This has no return value. |
Since
v1.0.0
General type aliases
This is the collection of general type aliases within the common
module. There is only one type alias, Json
.
Json
The Json
type is a simple alias for a string, but is used to indicate that the string should be considered to be a JSON serialization of an object.
string
Since
v1.0.0
Identity type aliases
This is the collection of identity type aliases within the common
module. There are only three identity type aliases, Id
, IdOrNull
, and Ids
.
Id
The Id
type is a simple alias for a string, but is used to indicate that the string should be considered to be the key of an object (such as a Row
Id
string used in a Table
).
string
Since
v1.0.0
IdOrNull
The Id
type is a simple alias for the union of a string or null
value, where the string should be considered to be the key of an objects (such as a Row
Id
string used in a Table
), and typically null
indicates a wildcard - such as when used in the Store
addRowListener
method.
Id | null
Since
v1.0.0
Ids
The Ids
type is a simple alias for an array of strings, but is used to indicate that the strings should be considered to be the keys of objects (such as the Row
Id
strings used in a Table
).
Id[]
Since
v1.0.0
Parameter type aliases
This is the collection of parameter type aliases within the common
module. There is only one type alias, SortKey
.
SortKey
The SortKey
type represents a value that can be used by a sort function.
string | number | boolean
Since
v1.0.0
persisters
The persisters
module of the TinyBase project provides a simple framework for saving and loading Store
and MergeableStore
data, to and from different destinations, or underlying storage types.
Many entry points are provided (in separately installed modules), each of which returns different types of Persister
that can load and save a Store
. Between them, these allow you to store your TinyBase data locally, remotely, to SQLite and PostgreSQL databases, and across synchronization boundaries with CRDT frameworks.
Persister | Storage | Store | MergeableStore |
---|---|---|---|
SessionPersister | Browser session storage | Yes | Yes |
LocalPersister | Browser local storage | Yes | Yes |
FilePersister | Local file (where possible) | Yes | Yes |
IndexedDbPersister | Browser IndexedDB | Yes | No |
RemotePersister | Remote server | Yes | No |
Sqlite3Persister | SQLite in Node, via sqlite3 | Yes | Yes* |
SqliteWasmPersister | SQLite in a browser, via sqlite-wasm | Yes | Yes* |
ExpoSqlitePersister | SQLite in React Native, via expo-sqlite | Yes | Yes* |
PostgresPersister | PostgreSQL, via postgres | Yes | Yes* |
PglitePersister | PostgreSQL, via PGlite | Yes | Yes* |
CrSqliteWasmPersister | SQLite CRDTs, via cr-sqlite-wasm | Yes | No |
ElectricSqlPersister | Electric SQL, via electric-sql | Yes | No |
LibSqlPersister | LibSQL for Turso, via libsql-client | Yes | No |
PowerSyncPersister | PowerSync, via powersync-sdk | Yes | No |
YjsPersister | Yjs CRDTs, via yjs | Yes | No |
AutomergePersister | Automerge CRDTs, via automerge-repo | Yes | No |
PartyKitPersister | PartyKit, via the persister-partykit-server module | Yes | No |
(*) Note that SQLite- and PostgreSQL-based Persisters can currently only persist MergeableStore
data when used with the JSON-based DpcJson
mode, and not when using the DpcTabular
mode.
Since persistence requirements can be different for every app, the createCustomPersister
function in this module can also be used to easily create a fully customized way to save and load Store
data.
Similarly, the createCustomSqlitePersister
function and createCustomPostgreSqlPersister
function can be used to build Persister
objects against SQLite and PostgreSQL SDKs (or forks) that are not already included with TinyBase.
See also
- Persistence guides
- Countries demo
- Todo App demos
- Drawing demo
Since
v1.0.0
Interfaces
There is one interface, Persister
, within the persisters
module.
Persister
A Persister
object lets you save and load Store
data to and from different locations, or underlying storage types.
This is useful for preserving Store
or MergeableStore
data between browser sessions or reloads, saving or loading browser state to or from a server, or saving Store
data to disk in a environment with filesystem access.
Creating a Persister
depends on the choice of underlying storage where the data is to be stored. Options include the createSessionPersister
function, the createLocalPersister
function, the createRemotePersister
function, and the createFilePersister
function, as just simple examples. The createCustomPersister
function can also be used to easily create a fully customized way to save and load Store
data.
Using the values of the Persists
enum, the generic parameter to the Persister
indicates whether it can handle a regular Store
, a MergeableStore
, or either. Consult the table in the overall persisters
module documentation to see current support for each. The different levels of support are also described for each of the types of Persister
themselves.
A Persister
lets you explicit save or load data, with the save
method and the load
method respectively. These methods are both asynchronous (since the underlying data storage may also be) and return promises. As a result you should use the await
keyword to call them in a way that guarantees subsequent execution order.
When you don't want to deal with explicit persistence operations, a Persister
object also provides automatic saving and loading. Automatic saving listens for changes to the Store
and persists the data immediately. Automatic loading listens (or polls) for changes to the persisted data and reflects those changes in the Store
.
You can start automatic saving or loading with the startAutoSave
method and startAutoLoad
method. Both are asynchronous since they will do an immediate save and load before starting to listen for subsequent changes. You can stop the behavior with the stopAutoSave
method and stopAutoLoad
method (which are synchronous).
You may often want to have both automatic saving and loading of a Store
so that changes are constantly synchronized (allowing basic state preservation between browser tabs, for example). The framework has some basic provisions to prevent race conditions - for example it will not attempt to save data if it is currently loading it and vice-versa - and will sequentially schedule
methods that could cause race conditions.
That said, be aware that you should always comprehensively test your persistence strategy to understand the opportunity for data loss (in the case of trying to save data to a server under poor network conditions, for example).
To help debug such issues, since v4.0.4, the create methods for all Persister
objects take an optional onIgnoredError
argument. This is a handler for the errors that the Persister
would otherwise ignore when trying to save or load data (such as when handling corrupted stored data). It's recommended you use this for debugging persistence issues, but only in a development environment. Database-based Persister
objects also take an optional onSqlCommand
argument for logging commands and queries made to the underlying database.
Examples
This example creates a Store
, persists it to the browser's session storage as a JSON string, changes the persisted data, updates the Store
from it, and finally destroys the Persister
again.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load();
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
This example creates a Store
, and automatically saves and loads it to the browser's session storage as a JSON string. Changes
to the Store
data, or the persisted data (implicitly firing a StorageEvent), are reflected accordingly.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"felix":{"species":"cat"}}},{}]'
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Getter methods
This is the collection of getter methods within the Persister
interface. There is only one method, getStore
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): PersistedStore<Persist>
returns | PersistedStore<Persist> | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
Listener methods
This is the collection of listener methods within the Persister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<Persist>): string
Type | Description | |
---|---|---|
listener | StatusListener<Persist> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the Persister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<Persister<Persist>>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<Persister<Persist>> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the Persister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<Persister<Persist>>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<Persister<Persist>> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<Persister<Persist>>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<Persister<Persist>> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the Persister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<Persister<Persist>>
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<Persister<Persist>>
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the Persister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Enumerations
These are the enumerations within the persisters
module.
Lifecycle enumerations
This is the collection of lifecycle enumerations within the persisters
module. There is only one enumeration, Status
.
Status
The Status
enum is used to indicate whether a Persister
is idle, or loading or saving data.
{
Idle: 0;
Loading: 1;
Saving: 2;
}
Value | Description | |
---|---|---|
Idle | 0 | Indicates that the |
Loading | 1 | Indicates that the |
Saving | 2 | Indicates that the |
The enum is intended to be used to understand the status of the Persister
in conjunction with the getStatus and addStatusListener
methods.
Note that a Persister
cannot be loading and saving data at the same time.
Since
v5.3.0
Mergeable enumerations
This is the collection of mergeable enumerations within the persisters
module. There is only one enumeration, Persists
.
Persists
The Persists
enum is used to indicate whether a Persister
can support a regular Store
, a MergeableStore
, or both.
{
StoreOnly: 1;
MergeableStoreOnly: 2;
StoreOrMergeableStore: 3;
}
Value | Description | |
---|---|---|
StoreOnly | 1 | Indicates that only a regular |
MergeableStoreOnly | 2 | Indicates that only a |
StoreOrMergeableStore | 3 | Indicates that either a regular |
The enum is intended to be used by the author of a Persister
to indicate which types of store can be persisted. If you discover type errors when trying to instantiate a Persister
, it is most likely that you are passing in an unsupported type of store.
See the createCustomPersister method for an example of this enum being used.
Since
v5.0.0
Functions
These are the functions within the persisters
module.
createCustomPersister
The createCustomPersister
function creates a Persister
object that you can configure to persist the Store
in any way you wish.
createCustomPersister<ListenerHandle, Persist>(
store: PersistedStore<Persist>,
getPersisted: () => Promise<undefined | PersistedContent<Persist>>,
setPersisted: (getContent: () => PersistedContent<Persist>, changes?: PersistedChanges<Persist>) => Promise<void>,
addPersisterListener: (listener: PersisterListener<Persist>) => ListenerHandle | Promise<ListenerHandle>,
delPersisterListener: (listenerHandle: ListenerHandle) => void,
onIgnoredError?: (error: any) => void,
persist?: Persist,
): Persister<Persist>
Type | Description | |
---|---|---|
store | PersistedStore<Persist> | The |
getPersisted | () => Promise<undefined | PersistedContent<Persist>> | An asynchronous function which will fetch content from the persistence layer (or |
setPersisted | (getContent: () => PersistedContent<Persist>, changes?: PersistedChanges<Persist>) => Promise<void> | An asynchronous function which will send content to the persistence layer. Since v4.0, it receives functions for getting the |
addPersisterListener | (listener: PersisterListener<Persist>) => ListenerHandle | Promise<ListenerHandle> | A function that will register a |
delPersisterListener | (listenerHandle: ListenerHandle) => void | A function that will unregister the listener from the underlying changes to the persistence layer. It receives whatever was returned from your |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
persist? | Persist | Since v5.0, an optional integer from the |
returns | Persister<Persist> | A reference to the new |
This is only used when developing custom Persisters, and most TinyBase users will not need to be particularly aware of it.
As well as providing a reference to the Store
to persist, you must provide functions that handle how to fetch, write, and listen to, the persistence layer.
The other creation functions (such as the createSessionPersister
function and createFilePersister
function, for example) all use this function under the covers. See those implementations for ideas on how to implement your own Persister
types.
This API changed in v4.0. Any custom persisters created on previous versions should be upgraded. Most notably, the setPersisted
function parameter is provided with a getContent
function to get the content from the Store
itself, rather than being passed pre-serialized JSON. It also receives information about the changes made during a transaction. The getPersisted
function must return the content (or nothing) rather than JSON. startListeningToPersisted
has been renamed addPersisterListener
, and stopListeningToPersisted
has been renamed delPersisterListener
.
Examples
This example creates a custom Persister
object and persists a Store
to a local string called persistedJson
and which would automatically load by polling for changes every second. It implicitly supports only a regular Store
.
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
let persistedJson;
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return JSON.parse(persistedJson);
},
async (getContent) => {
// setPersisted
persistedJson = JSON.stringify(getContent());
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
await persister.save();
console.log(persistedJson);
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persistedJson = '[{"pets":{"fido":{"species":"dog","color":"brown"}}},{}]';
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
persister.destroy();
This example demonstrates a Persister
creation function which returns a Persister
. This can persists a store to a local string called persistedJson
and which would automatically load by polling for changes every second. It emits warnings to the console and explicitly supports either a Store
or a MergeableStore
.
import {Persists, createCustomPersister} from 'tinybase/persisters';
import {createMergeableStore, createStore} from 'tinybase';
let persistedJson;
const createJsonPersister = (storeOrMergeableStore) =>
createCustomPersister(
storeOrMergeableStore,
async () => {
// getPersisted
return JSON.parse(persistedJson);
},
async (getContent) => {
// setPersisted
persistedJson = JSON.stringify(getContent());
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
console.warn,
Persists.StoreOrMergeableStore,
);
const store = createStore();
store.setTables({pets: {fido: {species: 'dog'}}});
const storePersister = createJsonPersister(store);
await storePersister.save();
console.log(persistedJson);
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
storePersister.destroy();
const mergeableStore = createMergeableStore('mergeableStore1');
mergeableStore.setTables({pets: {fido: {species: 'dog'}}});
const mergeableStorePersister = createJsonPersister(mergeableStore);
await mergeableStorePersister.save();
console.log(JSON.parse(persistedJson));
// ->
[
[
{
pets: [
{
fido: [
{species: ['dog', 'Nn1JUF-----Zjl0M', 4176151067]},
'',
2722999044,
],
},
'',
3367164653,
],
},
'',
30627183,
],
[{}, '', 0],
];
mergeableStorePersister.destroy();
Since
v1.0.0
createCustomPostgreSqlPersister
The createCustomSqlitePersister
function creates a Persister
object that you can configure to persist the Store
to a PostgreSQL database.
createCustomPostgreSqlPersister<ListenerHandle, Persist>(
store: PersistedStore<Persist>,
configOrStoreTableName: undefined | string | DatabasePersisterConfig,
executeCommand: DatabaseExecuteCommand,
addChangeListener: (channel: string, listener: DatabaseChangeListener) => Promise<ListenerHandle>,
delChangeListener: (listenerHandle: ListenerHandle) => void,
onSqlCommand: undefined | (sql: string, params?: any[]) => void,
onIgnoredError: undefined | (error: any) => void,
destroy: () => void,
persist: Persist,
thing: any,
getThing?: string,
): Persister<Persist>
Type | Description | |
---|---|---|
store | PersistedStore<Persist> | The |
configOrStoreTableName | undefined | string | DatabasePersisterConfig | A |
executeCommand | DatabaseExecuteCommand | A function that will execute a command against the database. |
addChangeListener | (channel: string, listener: DatabaseChangeListener) => Promise<ListenerHandle> | A function that will register a listener for changes to the database. |
delChangeListener | (listenerHandle: ListenerHandle) => void | A function that will unregister the listener for changes to the database. |
onSqlCommand | undefined | (sql: string, params?: any[]) => void | A function that will be called for each SQL command executed against the database. |
onIgnoredError | undefined | (error: any) => void | A function that will be called for errors that are ignored by the |
destroy | () => void | A function that will be called to perform any extra clean up on the |
persist | Persist | An integer from the |
thing | any | A reference to the database or connection that can be returned with a method, by default called |
getThing? | string | An optional string that will be used to get the reference to the database or connection from the |
returns | Persister<Persist> | A reference to the new |
This is only used when developing custom database-oriented Persisters, and most TinyBase users will not need to be particularly aware of it.
All of the TinyBase PostgreSQL-oriented Persister
functions use this function under the covers, and so you may wish to look at those implementations for ideas on how to build your own Persister
type, and as functional examples. Examine the implementation of the createPostgresPersister
function as a good starting point, for example.
Since
v5.2.0
createCustomSqlitePersister
The createCustomSqlitePersister
function creates a Persister
object that you can configure to persist the Store
to a SQLite database.
createCustomSqlitePersister<ListenerHandle, Persist>(
store: PersistedStore<Persist>,
configOrStoreTableName: undefined | string | DatabasePersisterConfig,
executeCommand: DatabaseExecuteCommand,
addChangeListener: (listener: DatabaseChangeListener) => ListenerHandle,
delChangeListener: (listenerHandle: ListenerHandle) => void,
onSqlCommand: undefined | (sql: string, params?: any[]) => void,
onIgnoredError: undefined | (error: any) => void,
destroy: () => void,
persist: Persist,
thing: any,
getThing?: string,
): Persister<Persist>
Type | Description | |
---|---|---|
store | PersistedStore<Persist> | The |
configOrStoreTableName | undefined | string | DatabasePersisterConfig | A |
executeCommand | DatabaseExecuteCommand | A function that will execute a command against the database. |
addChangeListener | (listener: DatabaseChangeListener) => ListenerHandle | A function that will register a listener for changes to the database. |
delChangeListener | (listenerHandle: ListenerHandle) => void | A function that will unregister the listener for changes to the database. |
onSqlCommand | undefined | (sql: string, params?: any[]) => void | A function that will be called for each SQL command executed against the database. |
onIgnoredError | undefined | (error: any) => void | A function that will be called for errors that are ignored by the |
destroy | () => void | A function that will be called to perform any extra clean up on the |
persist | Persist | An integer from the |
thing | any | A reference to the database or connection that can be returned with a method, by default called |
getThing? | string | An optional string that will be used to get the reference to the database or connection from the |
returns | Persister<Persist> | A reference to the new SQLite-oriented |
This is only used when developing custom database-oriented Persisters, and most TinyBase users will not need to be particularly aware of it.
All of the TinyBase SQLite-oriented Persister
functions use this function under the covers, and so you may wish to look at those implementations for ideas on how to build your own Persister
type, and as functional examples. Examine the implementation of the createSqlite3Persister
function as a good starting point, for example.
Since
v5.2.0
Type Aliases
These are the type aliases within the persisters
module.
Listener type aliases
This is the collection of listener type aliases within the persisters
module. There is only one type alias, StatusListener
.
StatusListener
The StatusListener
type describes a function that is used to listen to changes to the loading and saving status of the Persister
.
(
persister: Persister<Persist>,
status: Status,
): void
Type | Description | |
---|---|---|
persister | Persister<Persist> | A reference to the |
status | Status | The new loading or saving |
returns | void | This has no return value. |
A StatusListener
is provided when using the addStatusListener
method. See that method for specific examples.
When called, a StatusListener
is given a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Since
v5.3.0
Configuration type aliases
This is the collection of configuration type aliases within the persisters
module. There are 6 configuration type aliases in total.
DatabasePersisterConfig
The DatabasePersisterConfig
type describes the configuration of a database-oriented Persister
, such as those for SQLite and PostgreSQL.
DpcJson | DpcTabular
There are two modes for persisting a Store
with a database:
- A JSON serialization of the whole
Store
, which is stored in a single row of a table (normally calledtinybase
) within the database. This is configured by providing aDpcJson
object. - A tabular mapping of
Table
Ids
to database table names (and vice-versa).Values
are stored in a separate special table (normally calledtinybase_values
). This is configured by providing aDpcTabular
object.
Please see the DpcJson
and DpcTabular
type documentation for more detail on each. If not specified otherwise, JSON serialization will be used for persistence.
Changes
made to the database (outside of this Persister
) are picked up immediately if they are made via the same connection or library that it is using. If the database is being changed by another client, the Persister
needs to poll for changes. Hence both configuration types also contain an autoLoadIntervalSeconds
property which indicates how often it should do that. This defaults to 1 second.
Note that all the nested types within this type have a 'Dpc' prefix, short for 'DatabasePersisterConfig
'.
Examples
When applied to a database Persister
, this DatabasePersisterConfig
will load and save a JSON serialization from and to a table called my_tinybase
, polling the database every 2 seconds. See DpcJson
for more details on these settings.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'json',
storeTableName: 'my_tinybase',
autoLoadIntervalSeconds: 2,
};
When applied to a database Persister
, this DatabasePersisterConfig
will load and save tabular data from and to tables specified in the load
and save
mappings. See DpcTabular
for more details on these settings.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {petsInDb: 'pets', speciesInDb: 'species'},
save: {pets: 'petsInDb', species: 'speciesInDb'},
},
};
Since
v4.0.0
DpcJson
The DpcJson
type describes the configuration of a database-oriented Persister
operating in serialized JSON mode.
{
mode: "json";
storeTableName?: string;
storeIdColumnName?: string;
storeColumnName?: string;
autoLoadIntervalSeconds?: number;
}
Type | Description | |
---|---|---|
mode | "json" | The mode to be used for persisting the |
storeTableName? | string | An optional string which indicates the name of a table in the database which will be used to serialize the |
storeIdColumnName? | string | The optional name of the column in the database table that will be used as the |
storeColumnName? | string | The optional name of the column in the database table that will be used for the JSON of the |
autoLoadIntervalSeconds? | number | How often the |
One setting is the storeTableName
property, which indicates the name of a table in the database which will be used to serialize the Store
content into. It defaults to tinybase
.
That table in the database will be given two columns: a primary key column called _id
, and one called store
. (These column names can be changed using the rowIdColumnName
and storeColumnName
settings). The Persister
will place a single row in this table with _
in the _id
column, and the JSON serialization in the store
column, something like the following.
> SELECT * FROM tinybase;
+-----+-----------------------------------------------------+
| _id | store |
+-----+-----------------------------------------------------+
| _ | [{"pets":{"fido":{"species":"dog"}}},{"open":true}] |
+-----+-----------------------------------------------------+
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig
type.
Example
When applied to a database Persister
, this DatabasePersisterConfig
will load and save a JSON serialization from and to a table called tinybase_json
.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'json',
storeTableName: 'tinybase_json',
};
Since
v4.0.0
DpcTabular
The DpcTabular
type describes the configuration of a database-oriented Persister
that is operating in tabular mapping mode.
{
mode: "tabular";
tables?: {
load?: DpcTabularLoad;
save?: DpcTabularSave;
};
values?: DpcTabularValues;
autoLoadIntervalSeconds?: number;
}
Type | Description | |
---|---|---|
mode | "tabular" | The mode to be used for persisting the |
tables? | { load?: DpcTabularLoad; save?: DpcTabularSave; } | The settings for how the |
values? | DpcTabularValues | The settings for how the |
autoLoadIntervalSeconds? | number | How often the |
This configuration can only be used when the Persister
is persisting a regular Store
. For those database-oriented Persister
types that support MergeableStore
data, you will need to use JSON-serialization, es described in the DpcJson
section.
It is important to note that both the tabular mapping in ('save') and out ('load') of an underlying database are disabled by default. This is to ensure that if you pass in an existing populated database you don't run the immediate risk of corrupting or losing all your data.
This configuration therefore takes a tables
property object (with child load
and save
property objects) and a values
property object. These indicate how you want to load and save Tables
and Values
respectively. At least one of these two properties are required for the Persister
to do anything!
Note that if you are planning to both load from and save to a database, it is important to make sure that the load and save table mappings are symmetrical. For example, consider the following.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {petsInDb: 'pets', speciesInDb: 'species'},
save: {pets: 'petsInDb', species: 'speciesInDb'},
},
};
See the documentation for the DpcTabularLoad
, DpcTabularSave
, and DpcTabularValues
types for more details on how to configure the tabular mapping mode.
Columns in SQLite database have no type, and so in this mode, the table can contain strings and numbers for Cells and Values
, just as TinyBase does. Booleans, unfortunately, are stored as 0 or 1 in SQLite, and cannot be distinguished from numbers.
In PostgreSQL databases, all Cell
and Value
columns are expected to be typed as text
, and the strings, booleans, and numbers are all JSON-encoded by the Persister
.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig
type.
Example
When applied to a database Persister
, this DatabasePersisterConfig
will load and save Tables
data from and to tables specified in the load
and save
mappings, and Values
data from and to a table called my_tinybase_values
.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {petsInDb: 'pets', speciesInDb: 'species'},
save: {pets: 'petsInDb', species: 'speciesInDb'},
},
values: {
load: true,
save: true,
tableName: 'my_tinybase_values',
},
};
Since
v4.0.0
DpcTabularLoad
The DpcTabularLoad
type describes the configuration for loading Tables
in a database-oriented Persister
that is operating in tabular mode.
{[tableName: string]: {
tableId: Id;
rowIdColumnName?: string;
} | Id}
It is an object where each key is a name of a database table, and the value is a child configuration object for how that table should be loaded into the Store
. The properties of the child configuration object are:
Type | Description | |
---|---|---|
tableId | Id | The Id of the Store Table into which data from this database table should be loaded. |
rowIdColumnName? | string | The optional name of the column in the database table that will be used as the Row Ids in the Store Table , defaulting to '_id'. |
As a shortcut, if you do not need to specify a custom rowIdColumnName
, you can simply provide the Id
of the Store
Table
instead of the whole object.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig
type.
Example
When applied to a database Persister
, this DatabasePersisterConfig
will load the data of two database tables (called 'petsInDb' and 'speciesInDb') into two Store
Tables
(called 'pets' and 'species'). One has a column for the Row
Id
called 'id' and the other defaults it to '_id'.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {
petsInDb: {tableId: 'pets', rowIdColumnName: 'id'},
speciesInDb: 'species',
},
},
};
Imagine database tables that look like this:
> SELECT * FROM petsInDb;
+-------+---------+-------+
| id | species | color |
+-------+---------+-------+
| fido | dog | brown |
| felix | cat | black |
+-------+---------+-------+
> SELECT * FROM speciesInDb;
+------+-------+
| _id | price |
+------+-------+
| dog | 5 |
| cat | 4 |
+------+-------+
With the configuration above, this will load into a Store
with Tables
that look like this:
{
"pets": {
"fido": {"species": "dog", "color": "brown"},
"felix": {"species": "cat", "color": "black"},
},
"species": {
"dog": {"price": 5},
"cat": {"price": 4},
},
}
The example above represents what happens with a SQLite Persister
. In PostgreSQL databases, all Cell
and Value
columns are expected to be typed as text
, and the strings, booleans, and numbers would be JSON-encoded if you queried them.
Since
v4.0.0
DpcTabularSave
The DpcTabularSave
type describes the configuration for saving Tables
in a database-oriented Persister
that is operating in tabular mode.
{[tableId: Id]: {
tableName: string;
rowIdColumnName?: string;
deleteEmptyColumns?: boolean;
deleteEmptyTable?: boolean;
} | string}
It is an object where each key is an Id
of a Store
Table
, and the value is a child configuration object for how that Table
should be saved out to the database. The properties of the child configuration object are:
Type | Description | |
---|---|---|
tableName | string | The name of the database table out to which the Store Table should be saved. |
rowIdColumnName? | string | The optional name of the column in the database table that will be used to save the Row Ids from the Store Table , defaulting to '_id'. |
deleteEmptyColumns? | boolean | Whether columns in the database table will be removed if they are empty in the Store Table , defaulting to false. |
deleteEmptyTable? | boolean | Whether tables in the database will be removed if the Store Table is empty, defaulting to false. |
As a shortcut, if you do not need to specify a custom rowIdColumnName
, or enable the deleteEmptyColumns
or deleteEmptyTable
settings, you can simply provide the name of the database table instead of the whole object.
deleteEmptyColumns
and deleteEmptyTable
only have a guaranteed effect when an explicit call is made to the Persister
's save
method. Columns and tables will not necessarily be removed when the Persister
is incrementally 'autoSaving', due to performance reasons. If you want to be sure that your database table matches a TinyBase Table
without any extraneous columns, simply call the save
method at an idle moment.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig
type.
Example
When applied to a database Persister
, this DatabasePersisterConfig
will save the data of two Store
Tables
(called 'pets' and 'species') into two database tables (called 'petsInDb' and 'speciesInDb'). One has a column for the Row
Id
called 'id' and will delete columns and the whole table if empty, the other defaults to '_id' and will not delete columns or the whole table if empty.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
save: {
pets: {
tableName: 'petsInDb',
deleteEmptyColumns: true,
deleteEmptyTable: true,
},
species: 'speciesInDb',
},
},
};
Imagine a Store
with Tables
that look like this:
{
"pets": {
"fido": {"species": "dog", "color": "brown"},
"felix": {"species": "cat", "color": "black"},
},
"species": {
"dog": {"price": 5},
"cat": {"price": 4},
},
}
With the configuration above, this will save out to a database with tables that look like this:
> SELECT * FROM petsInDb;
+-------+---------+-------+
| id | species | color |
+-------+---------+-------+
| fido | dog | brown |
| felix | cat | black |
+-------+---------+-------+
> SELECT * FROM speciesInDb;
+------+-------+
| _id | price |
+------+-------+
| dog | 5 |
| cat | 4 |
+------+-------+
The example above represents what happens with a SQLite Persister
. In PostgreSQL databases, all Cell
and Value
columns are expected to be typed as text
, and the strings, booleans, and numbers would be JSON-encoded if you queried them.
Since
v4.0.0
DpcTabularValues
The DpcTabularValues
type describes the configuration for handling Values
in a database-oriented Persister
that is operating in tabular mode.
{
load?: boolean;
save?: boolean;
tableName?: string;
}
Type | Description | |
---|---|---|
load? | boolean | |
save? | boolean | |
tableName? | string | The optional name of the database table from and to which the |
Note that both loading and saving of Values
from and to the database are disabled by default.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig
type.
Example
When applied to a database Persister
, this DatabasePersisterConfig
will load and save the data of a Store
's Values
into a database table called 'my_tinybase_values'.
import type {DatabasePersisterConfig} from 'tinybase';
export const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
values: {
load: true,
save: true,
tableName: 'my_tinybase_values',
},
};
Since
v4.0.0
Creation type aliases
This is the collection of creation type aliases within the persisters
module. There are only three creation type aliases, DatabaseChangeListener
, DatabaseExecuteCommand
, and PersisterListener
.
DatabaseChangeListener
The DatabaseChangeListener
type describes a function that is used to listen for changes to the data in a database.
(tableName: string): void
Type | Description | |
---|---|---|
tableName | string | The name of the table that has changed. |
returns | void | This has no return value. |
This is only used when developing custom database-oriented Persisters, and most TinyBase users will not need to be particularly aware of it.
This function should be called with the name of a relevant table that has changed, possible through the use of events, triggers, or notifications, dependent on the specific database implementation.
Since
v5.2.0
DatabaseExecuteCommand
The DatabaseExecuteCommand
type describes a function that is used to execute commands against a database.
(
sql: string,
params?: any[],
): Promise<{[field: string]: any}[]>
Type | Description | |
---|---|---|
sql | string | The SQL string to execute, which may include positional parameter placeholders. |
params? | any[] | An array of parameters to pass to the SQL command. |
returns | Promise<{[field: string]: any}[]> |
This is only used when developing custom database-oriented Persisters, and most TinyBase users will not need to be particularly aware of it.
It is modelled around the common pattern of database SDKs being able to execute commands with parameters, and have those (probably asynchronous) command executions return an array of objects, where each object represents a row.
Since
v5.2.0
PersisterListener
A PersisterListener
is a generic representation of the callback that lets a Persister
inform the store that a change has happened to the underlying data.
(
content?: PersistedContent<Persist>,
changes?: PersistedChanges<Persist>,
): void
Type | Description | |
---|---|---|
content? | PersistedContent<Persist> | If provided, this is a |
changes? | PersistedChanges<Persist> | If provided, this is a |
returns | void | This has no return value. |
Using the values of the Persists
enum, the generic parameter indicates whether the Persister
is handling content and changes from a regular Store
, a MergeableStore
, or either.
If the listener is called with the changes
parameter, it will be used to make an incremental change to the Store
. If not, but the content
parameter is available, that will be used to make a wholesale change to the Store
. If neither are present, the content will be loaded using the Persister
's load
method. Prior to v5.0, these parameters were callbacks and the overall type was non-generic.
Since
v4.0.0
Mergeable type aliases
This is the collection of mergeable type aliases within the persisters
module. There are 4 mergeable type aliases in total.
PersistedStore
The PersistedStore
type is a generic representation of the type of store being handled by a Persister
.
Persist extends Persists.StoreOrMergeableStore ? Store | MergeableStore : Persist extends Persists.MergeableStoreOnly ? MergeableStore : Store
Using the values of the Persists
enum, the generic parameter indicates whether the Persister
is handling a regular Store
, a MergeableStore
, or either.
If the generic parameter is unspecified, the StoreOnly enum value is used, meaning that PersistedStore
is equivalent to a regular Store
.
Since
v5.0.0
AnyPersister
The AnyPersister
type is a convenient alias for any type of Persister
that can persist Store
or MergeableStore
objects.
Persister<Persists>
Since
v5.3.0
PersistedChanges
The PersistedChanges
type is a generic representation of changes made to the type of store being handled by a Persister
.
Persist extends Persists.StoreOrMergeableStore ? Changes | MergeableChanges : Persist extends Persists.MergeableStoreOnly ? MergeableChanges : Changes
Using the values of the Persists
enum, the generic parameter indicates whether the Persister
is handling changes for a regular Store
(the Changes
type), a MergeableStore
(the MergeableChanges
type), or either (the union of the two).
Since
v5.0.0
PersistedContent
The PersistedContent
type is a generic representation of the content in the type of store being handled by a Persister
.
Persist extends Persists.StoreOrMergeableStore ? Content | MergeableContent : Persist extends Persists.MergeableStoreOnly ? MergeableContent : Content
Using the values of the Persists
enum, the generic parameter indicates whether the Persister
is handling content from a regular Store
(the Content
type), a MergeableStore
(the MergeableContent
type), or either (the union of the two).
If the generic parameter is unspecified, the StoreOnly enum value is used, meaning that PersistedContent
is equivalent to the Content
type.
Since
v5.0.0
Development type aliases
This is the collection of development type aliases within the persisters
module. There is only one type alias, PersisterStats
.
PersisterStats
The PersisterStats
type describes the number of times a Persister
object has loaded or saved data.
{
loads: number;
saves: number;
}
Type | Description | |
---|---|---|
loads | number | The number of times data has been loaded. |
saves | number | The number of times data has been saved. |
A PersisterStats
object is returned from the getStats
method.
Since
v1.0.0
persister-automerge
The persister-automerge
module of the TinyBase project provides a way to save and load Store
data to and from an Automerge document.
A single entry point, the createAutomergePersister
function, is provided, which returns a new Persister
object that can bind a Store
to a provided Automerge document handle (and in turn, its document).
See also
Since
v4.0.0
Interfaces
There is one interface, AutomergePersister
, within the persister-automerge
module.
AutomergePersister
The AutomergePersister
interface represents a Persister
that lets you save and load Store
data to and from an Automerge document.
You should use the createAutomergePersister
function to create an AutomergePersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getDocHandle
method for accessing the Automerge document handler the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the AutomergePersister
interface. There are only two getter methods, getStore
and getDocHandle
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store
returns | Store | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDocHandle
The getDocHandle
method returns the Automerge document handler the Store
is being persisted to.
getDocHandle(): DocHandle<any>
returns | DocHandle<any> | The Automerge document handler. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the Automerge document handler back out again.
import {Repo} from '@automerge/automerge-repo';
import {createAutomergePersister} from 'tinybase/persisters/persister-automerge';
import {createStore} from 'tinybase';
const docHandler = new Repo({network: []}).create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createAutomergePersister(store, docHandler);
console.log(persister.getDocHandle() == docHandler);
// -> true
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the AutomergePersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the AutomergePersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<AutomergePersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<AutomergePersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the AutomergePersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<AutomergePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<AutomergePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<AutomergePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<AutomergePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the AutomergePersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<AutomergePersister>
returns | Promise<AutomergePersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<AutomergePersister>
returns | Promise<AutomergePersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the AutomergePersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createAutomergePersister
, within the persister-automerge
module.
createAutomergePersister
The createAutomergePersister
function creates an AutomergePersister
object that can persist the Store
to an Automerge document.
createAutomergePersister(
store: Store,
docHandle: DocHandle<any>,
docMapName?: string,
onIgnoredError?: (error: any) => void,
): AutomergePersister
Type | Description | |
---|---|---|
store | Store | The |
docHandle | DocHandle<any> | The Automerge document handler to persist the |
docMapName? | string | The name of the map used inside the Automerge document to sync the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | AutomergePersister | A reference to the new |
An AutomergePersister
only supports regular Store
objects, and cannot be used to persist the metadata of a MergeableStore
.
As well as providing a reference to the Store
to persist, you must provide the Automerge document handler to persist it with.
Examples
This example creates a AutomergePersister
object and persists the Store
to an Automerge document.
import {Repo} from '@automerge/automerge-repo';
import {createAutomergePersister} from 'tinybase/persisters/persister-automerge';
import {createStore} from 'tinybase';
const docHandler = new Repo({network: []}).create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createAutomergePersister(store, docHandler);
await persister.save();
// Store will be saved to the document.
console.log(await docHandler.doc());
// -> {tinybase: {t: {pets: {fido: {species: 'dog'}}}, v: {}}}
persister.destroy();
This more complex example uses Automerge networking to keep two Store
objects (each with their own Persister
objects and Automerge documents) in sync with each other using a network.
import {BroadcastChannelNetworkAdapter} from '@automerge/automerge-repo-network-broadcastchannel';
import {Repo} from '@automerge/automerge-repo';
import {createAutomergePersister} from 'tinybase/persisters/persister-automerge';
import {createStore} from 'tinybase';
// Bind the first Store to a network-enabled automerge-repo
const repo1 = new Repo({
network: [new BroadcastChannelNetworkAdapter()],
});
const docHandler1 = repo1.create();
await docHandler1.doc();
const store1 = createStore();
const persister1 = createAutomergePersister(store1, docHandler1);
await persister1.startAutoLoad();
await persister1.startAutoSave();
// Bind the second Store to a different network-enabled automerge-repo
const repo2 = new Repo({
network: [new BroadcastChannelNetworkAdapter()],
});
const docHandler2 = repo2.find(docHandler1.documentId);
await docHandler2.doc();
const store2 = createStore();
const persister2 = createAutomergePersister(store2, docHandler2);
await persister2.startAutoLoad();
await persister2.startAutoSave();
// A function that waits briefly and then for the documents to synchronize
// with each other, merely for the purposes of sequentiality in this example.
const syncDocsWait = async () => {
await new Promise((resolve) => setTimeout(() => resolve(0), 100));
await docHandler1.doc();
await docHandler2.doc();
};
// Wait for the documents to synchronize in their initial state.
await syncDocsWait();
// Make a change to each of the two Stores.
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setValues({open: true});
// Wait for the documents to synchronize in their new state.
await syncDocsWait();
// Ensure the Stores are in sync.
console.log(store1.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
console.log(store2.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
persister1.destroy();
persister2.destroy();
Since
v4.0.0
persister-browser
The persister-browser
module of the TinyBase project lets you save and load Store
data to and from browser storage.
Two entry points are provided, each of which returns a new Persister
object that can load and save a Store
:
- The
createSessionPersister
function returns aPersister
that uses the browser's session storage. - The
createLocalPersister
function returns aPersister
that uses the browser's local storage.
See also
Persistence guides
Since
v1.0.0
Interfaces
These are the interfaces within the persister-browser
module.
LocalPersister
The LocalPersister
interface represents a Persister
that lets you save and load Store
data to and from the browser's local storage.
It is a minor extension to the Persister
interface and simply provides an extra getStorageName
method for accessing the unique key of the storage location the Store
is being persisted to.
You should use the createLocalPersister
function to create a LocalPersister
object.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the LocalPersister
interface. There are only two getter methods, getStore
and getStorageName
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store | MergeableStore
returns | Store | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getStorageName
The getStorageName
method returns the unique key of the storage location the Store
is being persisted to.
getStorageName(): string
returns | string | The unique key of the storage location. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the unique key of the storage location back out again.
import {createLocalPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLocalPersister(store, 'pets');
console.log(persister.getStorageName());
// -> 'pets'
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the LocalPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the LocalPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<LocalPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<LocalPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the LocalPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<LocalPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<LocalPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<LocalPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<LocalPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the LocalPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<LocalPersister>
returns | Promise<LocalPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<LocalPersister>
returns | Promise<LocalPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the LocalPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
SessionPersister
The SessionPersister
interface represents a Persister
that lets you save and load Store
data to and from the browser's session storage.
You should use the createSessionPersister
function to create a SessionPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getStorageName
method for accessing the unique key of the storage location the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the SessionPersister
interface. There are only two getter methods, getStore
and getStorageName
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store | MergeableStore
returns | Store | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getStorageName
The getStorageName
method returns the unique key of the storage location the Store
is being persisted to.
getStorageName(): string
returns | string | The unique key of the storage location. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the unique key of the storage location back out again.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
console.log(persister.getStorageName());
// -> 'pets'
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the SessionPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the SessionPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<SessionPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<SessionPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the SessionPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<SessionPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<SessionPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<SessionPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<SessionPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the SessionPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<SessionPersister>
returns | Promise<SessionPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<SessionPersister>
returns | Promise<SessionPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the SessionPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
These are the functions within the persister-browser
module.
createLocalPersister
The createLocalPersister
function creates a LocalPersister
object that can persist the Store
to the browser's local storage.
createLocalPersister(
store: Store | MergeableStore,
storageName: string,
onIgnoredError?: (error: any) => void,
): LocalPersister
Type | Description | |
---|---|---|
store | Store | MergeableStore | The |
storageName | string | The unique key to identify the storage location. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | LocalPersister | A reference to the new |
A LocalPersister
supports both regular Store
and MergeableStore
objects.
As well as providing a reference to the Store
to persist, you must provide a storageName
parameter which is unique to your application. This is the key that the browser uses to identify the storage location.
Example
This example creates a LocalPersister
object and persists the Store
to the browser's local storage.
import {createLocalPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLocalPersister(store, 'pets');
await persister.save();
console.log(localStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
localStorage.clear();
Since
v1.0.0
createSessionPersister
The createSessionPersister
function creates a SessionPersister
object that can persist the Store
to the browser's session storage.
createSessionPersister(
store: Store | MergeableStore,
storageName: string,
onIgnoredError?: (error: any) => void,
): SessionPersister
Type | Description | |
---|---|---|
store | Store | MergeableStore | The |
storageName | string | The unique key to identify the storage location. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | SessionPersister | A reference to the new |
A SessionPersister
supports both regular Store
and MergeableStore
objects.
As well as providing a reference to the Store
to persist, you must provide a storageName
parameter which is unique to your application. This is the key that the browser uses to identify the storage location.
Example
This example creates a SessionPersister
object and persists the Store
to the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
persister-cr-sqlite-wasm
The persister-cr-sqlite-wasm
module of the TinyBase project lets you save and load Store
data to and from a local CR-SQLite database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.0.0
Interfaces
There is one interface, CrSqliteWasmPersister
, within the persister-cr-sqlite-wasm
module.
CrSqliteWasmPersister
The CrSqliteWasmPersister
interface represents a Persister
that lets you save and load Store
data to and from a local CR-SQLite database.
You should use the createCrSqliteWasmPersister
function to create a CrSqliteWasmPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getDb
method for accessing a reference to the database instance the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the CrSqliteWasmPersister
interface. There are only two getter methods, getStore
and getDb
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store
returns | Store | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb
method returns a reference to the database instance the Store
is being persisted to.
getDb(): DB
returns | DB | A reference to the database instance. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the database instance back out again.
import {createCrSqliteWasmPersister} from 'tinybase/persisters/persister-cr-sqlite-wasm';
import {createStore} from 'tinybase';
import initWasm from '@vlcn.io/crsqlite-wasm';
const crSqlite3 = await initWasm();
const db = await crSqlite3.open();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createCrSqliteWasmPersister(store, db, 'my_tinybase');
console.log(persister.getDb() == db);
// -> true
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the CrSqliteWasmPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the CrSqliteWasmPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<CrSqliteWasmPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<CrSqliteWasmPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the CrSqliteWasmPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<CrSqliteWasmPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<CrSqliteWasmPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the CrSqliteWasmPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<CrSqliteWasmPersister>
returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<CrSqliteWasmPersister>
returns | Promise<CrSqliteWasmPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the CrSqliteWasmPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createCrSqliteWasmPersister
, within the persister-cr-sqlite-wasm
module.
createCrSqliteWasmPersister
The createCrSqliteWasmPersister
function creates a CrSqliteWasmPersister
object that can persist the Store
to a local CR-SQLite database.
createCrSqliteWasmPersister(
store: Store,
db: DB,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): CrSqliteWasmPersister
Type | Description | |
---|---|---|
store | Store | The |
db | DB | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | CrSqliteWasmPersister | A reference to the new |
A CrSqliteWasmPersister
only supports regular Store
objects, and cannot be used to persist the metadata of a MergeableStore
.
As well as providing a reference to the Store
to persist, you must provide a db
parameter which identifies the database instance.
A database Persister
uses one of two modes: either a JSON serialization of the whole Store
stored in a single row of a table (the default), or a tabular mapping of Table
Ids
to database table names and vice-versa).
The third argument is a DatabasePersisterConfig
object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName
property of the JSON serialization.
See the documentation for the DpcJson
and DpcTabular
types for more information on how both of those modes can be configured.
Examples
This example creates a CrSqliteWasmPersister
object and persists the Store
to a local CR-SQLite database as a JSON serialization into the my_tinybase
table. It makes a change to the database directly and then reloads it back into the Store
.
import {createCrSqliteWasmPersister} from 'tinybase/persisters/persister-cr-sqlite-wasm';
import {createStore} from 'tinybase';
import initWasm from '@vlcn.io/crsqlite-wasm';
const crSqlite3 = await initWasm();
const db = await crSqlite3.open();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createCrSqliteWasmPersister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(await db.execO('SELECT * FROM my_tinybase;'));
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await db.exec(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a CrSqliteWasmPersister
object and persists the Store
to a local SQLite database with tabular mapping.
import {createCrSqliteWasmPersister} from 'tinybase/persisters/persister-cr-sqlite-wasm';
import {createStore} from 'tinybase';
import initWasm from '@vlcn.io/crsqlite-wasm';
const crSqlite3 = await initWasm();
const db = await crSqlite3.open();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createCrSqliteWasmPersister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(await db.execO('SELECT * FROM pets;'));
// -> [{_id: 'fido', species: 'dog'}]
await db.exec(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.0.0
persister-electric-sql
The persister-electric-sql
module of the TinyBase project lets you save and load Store
data to and from a local ElectricSQL database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.6.0
Interfaces
There is one interface, ElectricSqlPersister
, within the persister-electric-sql
module.
ElectricSqlPersister
The ElectricSqlPersister
interface represents a Persister
that lets you save and load Store
data to and from a local ElectricSQL database.
You should use the createElectricSqlPersister
function to create an ElectricSqlPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getElectricClient
method for accessing a reference to the Electric client the Store
is being persisted to.
Since
v4.6.0
Getter methods
This is the collection of getter methods within the ElectricSqlPersister
interface. There are only two getter methods, getStore
and getElectricClient
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store
returns | Store | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getElectricClient
The getElectricClient
method returns a reference to the Electric client the Store
is being persisted to.
getElectricClient(): ElectricClient<any>
returns | ElectricClient<any> | A reference to the Electric client. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the Electric client back out again.
import {ElectricDatabase, electrify} from 'electric-sql/wa-sqlite';
import {createElectricSqlPersister} from 'tinybase/persisters/persister-electric-sql';
import {createStore} from 'tinybase';
import {schema} from './generated/client';
const electricClient = await electrify(
await ElectricDatabase.init('electric.db', ''),
schema,
{url: import.meta.env.ELECTRIC_SERVICE},
);
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createElectricSqlPersister(
store,
electricClient,
'my_tinybase',
);
console.log(persister.getElectricClient() == electricClient);
// -> true
persister.destroy();
Since
v4.6.0
Listener methods
This is the collection of listener methods within the ElectricSqlPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the ElectricSqlPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<ElectricSqlPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<ElectricSqlPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the ElectricSqlPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<ElectricSqlPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<ElectricSqlPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the ElectricSqlPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<ElectricSqlPersister>
returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<ElectricSqlPersister>
returns | Promise<ElectricSqlPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the ElectricSqlPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createElectricSqlPersister
, within the persister-electric-sql
module.
createElectricSqlPersister
The createElectricSqlPersister
function creates an ElectricSqlPersister
object that can persist a Store
to a local ElectricSQL database.
createElectricSqlPersister(
store: Store,
electricClient: ElectricClient<any>,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): ElectricSqlPersister
Type | Description | |
---|---|---|
store | Store | The |
electricClient | ElectricClient<any> | The Electric client that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | ElectricSqlPersister | A reference to the new |
An ElectricSqlPersister
only supports regular Store
objects, and cannot be used to persist the metadata of a MergeableStore
.
As well as providing a reference to the Store
to persist, you must provide a electricClient
parameter which identifies the Electric client.
A database Persister
uses one of two modes: either a JSON serialization of the whole Store
stored in a single row of a table (the default), or a tabular mapping of Table
Ids
to database table names and vice-versa).
The third argument is a DatabasePersisterConfig
object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName
property of the JSON serialization.
See the documentation for the DpcJson
and DpcTabular
types for more information on how both of those modes can be configured.
Examples
This example creates a ElectricSqlPersister
object and persists the Store
to a local ElectricSql database as a JSON serialization into the my_tinybase
table. It makes a change to the database directly and then reloads it back into the Store
.
import {ElectricDatabase, electrify} from 'electric-sql/wa-sqlite';
import {createElectricSqlPersister} from 'tinybase/persisters/persister-electric-sql';
import {createStore} from 'tinybase';
import {schema} from './generated/client';
const electricClient = await electrify(
await ElectricDatabase.init('electric.db', ''),
schema,
{url: import.meta.env.ELECTRIC_SERVICE},
);
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createElectricSqlPersister(
store,
electricClient,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log(
await electricClient.db.raw({sql: 'SELECT * FROM my_tinybase;'}),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await electricClient.db.raw({
sql:
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
});
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a ElectricSqlPersister
object and persists the Store
to a local ElectricSql database with tabular mapping.
import {ElectricDatabase, electrify} from 'electric-sql/wa-sqlite';
import {createElectricSqlPersister} from 'tinybase/persisters/persister-electric-sql';
import {createStore} from 'tinybase';
import {schema} from './generated/client';
const electricClient = await electrify(
await ElectricDatabase.init('electric.db', ''),
schema,
{url: import.meta.env.ELECTRIC_SERVICE},
);
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createElectricSqlPersister(store, electricClient, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(await electricClient.db.raw({sql: 'SELECT * FROM pets;'}));
// -> [{_id: 'fido', species: 'dog'}]
await electricClient.db.raw({
sql: `INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
});
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.6.0
persister-expo-sqlite
The persister-expo-sqlite
module of the TinyBase project lets you save and load Store
data to and from a Expo-SQLite database (in an appropriate React Native environment).
As of TinyBase v5.0, this module provides a Persister
for the modern version of Expo's SQLite library, designated 'next' as of November 2023 and default as v14.0 by June 2024.
Note that TinyBase support for the legacy version of Expo-SQLite is no longer available.
See also
Database Persistence guide
Since
v4.5.0
Interfaces
There is one interface, ExpoSqlitePersister
, within the persister-expo-sqlite
module.
ExpoSqlitePersister
The ExpoSqlitePersister
interface represents a Persister
that lets you save and load Store
data to and from a Expo-SQLite database.
You should use the createExpoSqlitePersister
function to create an ExpoSqlitePersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getDb
method for accessing a reference to the database instance the Store
is being persisted to.
Since
v4.5.0
Getter methods
This is the collection of getter methods within the ExpoSqlitePersister
interface. There are only two getter methods, getStore
and getDb
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store | MergeableStore
returns | Store | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb
method returns a reference to the database instance the Store
is being persisted to.
getDb(): SQLiteDatabase
returns | SQLiteDatabase | A reference to the database instance. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the database instance back out again.
import {createExpoSqlitePersister} from 'tinybase/persisters/persister-expo-sqlite';
import {createStore} from 'tinybase';
import {openDatabaseSync} from 'expo-sqlite';
const db = openDatabaseSync('my.db');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createExpoSqlitePersister(store, db, 'my_tinybase');
console.log(persister.getDb() == db);
// -> true
persister.destroy();
Since
v4.5.0
Listener methods
This is the collection of listener methods within the ExpoSqlitePersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the ExpoSqlitePersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<ExpoSqlitePersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<ExpoSqlitePersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the ExpoSqlitePersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<ExpoSqlitePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<ExpoSqlitePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the ExpoSqlitePersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<ExpoSqlitePersister>
returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<ExpoSqlitePersister>
returns | Promise<ExpoSqlitePersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the ExpoSqlitePersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createExpoSqlitePersister
, within the persister-expo-sqlite
module.
createExpoSqlitePersister
The createExpoSqlitePersister
function creates an ExpoSqlitePersister
object that can persist the Store
to a local Expo-SQLite database.
createExpoSqlitePersister(
store: Store | MergeableStore,
db: SQLiteDatabase,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): ExpoSqlitePersister
Type | Description | |
---|---|---|
store | Store | MergeableStore | The |
db | SQLiteDatabase | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | ExpoSqlitePersister | A reference to the new |
An ExpoSqlitePersister
supports regular Store
objects, and can also be used to persist the metadata of a MergeableStore
when using the JSON serialization mode, as described below.
Note that this Persister
is currently experimental as Expo themselves iterate on the underlying database library API.
As well as providing a reference to the Store
to persist, you must provide a db
parameter which identifies the database instance.
A database Persister
uses one of two modes: either a JSON serialization of the whole Store
stored in a single row of a table (the default), or a tabular mapping of Table
Ids
to database table names and vice-versa).
The third argument is a DatabasePersisterConfig
object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName
property of the JSON serialization.
See the documentation for the DpcJson
and DpcTabular
types for more information on how both of those modes can be configured.
Examples
This example creates a ExpoSqlitePersister
object and persists the Store
to a local SQLite database as a JSON serialization into the my_tinybase
table. It makes a change to the database directly and then reloads it back into the Store
.
import {createExpoSqlitePersister} from 'tinybase/persisters/persister-expo-sqlite';
import {createStore} from 'tinybase';
import {openDatabaseSync} from 'expo-sqlite';
const db = openDatabaseSync('my.db');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createExpoSqlitePersister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(
await new Promise((resolve) =>
db.allAsync('SELECT * FROM my_tinybase;').then(resolve),
),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await new Promise((resolve) =>
db
.allAsync(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
)
.then(resolve),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a ExpoSqlitePersister
object and persists the Store
to a local SQLite database with tabular mapping.
import {createExpoSqlitePersister} from 'tinybase/persisters/persister-expo-sqlite';
import {createStore} from 'tinybase';
import {openDatabaseSync} from 'expo-sqlite';
const db = openDatabaseSync('my.db');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createExpoSqlitePersister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(
await new Promise((resolve) =>
db.allAsync('SELECT * FROM pets;').then(resolve),
),
);
// -> [{_id: 'fido', species: 'dog'}]
await new Promise((resolve) =>
db
.allAsync(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`)
.then(resolve),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.5.0
persister-file
The persister-file
module of the TinyBase project lets you save and load Store
data to and from a local file system (in an appropriate environment).
See also
Persistence guides
Since
v1.0.0
Interfaces
There is one interface, FilePersister
, within the persister-file
module.
FilePersister
The FilePersister
interface represents a Persister
that lets you save and load Store
data to and from a local file system.
You should use the createFilePersister
function to create a FilePersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getFilePath
method for accessing the location of the local file the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the FilePersister
interface. There are only two getter methods, getStore
and getFilePath
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store | MergeableStore
returns | Store | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getFilePath
The getFilePath
method returns the location of the local file the Store
is being persisted to.
getFilePath(): string
returns | string | The location of the local file. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the file location back out again.
import {createFilePersister} from 'tinybase/persisters/persister-file';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createFilePersister(store, '/app/persisted.json');
console.log(persister.getFilePath());
// -> '/app/persisted.json'
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the FilePersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the FilePersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<FilePersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<FilePersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the FilePersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<FilePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<FilePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<FilePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<FilePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the FilePersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<FilePersister>
returns | Promise<FilePersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<FilePersister>
returns | Promise<FilePersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the FilePersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createFilePersister
, within the persister-file
module.
createFilePersister
The createFilePersister
function creates a FilePersister
object that can persist the Store
to a local file.
createFilePersister(
store: Store | MergeableStore,
filePath: string,
onIgnoredError?: (error: any) => void,
): FilePersister
Type | Description | |
---|---|---|
store | Store | MergeableStore | The |
filePath | string | The location of the local file to persist the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | FilePersister | A reference to the new |
A FilePersister
supports both regular Store
and MergeableStore
objects.
As well as providing a reference to the Store
to persist, you must provide a filePath
parameter which identifies the file to persist it to.
Example
This example creates a FilePersister
object and persists the Store
to a local file.
import {createFilePersister} from 'tinybase/persisters/persister-file';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createFilePersister(store, '/app/persisted.json');
await persister.save();
// Store JSON will be saved to the file.
await persister.load();
// Store JSON will be loaded from the file.
persister.destroy();
Since
v1.0.0
persister-indexed-db
The persister-indexed-db
module of the TinyBase project lets you save and load Store
data to and from browser IndexedDB storage.
See also
Persistence guides
Since
v4.2.0
Interfaces
There is one interface, IndexedDbPersister
, within the persister-indexed-db
module.
IndexedDbPersister
The IndexedDbPersister
interface represents a Persister
that lets you save and load Store
data to and from browser IndexedDB storage.
You should use the createIndexedDbPersister
function to create an IndexedDbPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getDbName
method for accessing the unique key of the IndexedDB the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the IndexedDbPersister
interface. There are only two getter methods, getStore
and getDbName
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store
returns | Store | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDbName
The getDbName
method returns the unique key of the IndexedDB the Store
is being persisted to.
getDbName(): string
returns | string | The unique key of the IndexedDB. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the unique key of the IndexedDB back out again.
import {createIndexedDbPersister} from 'tinybase/persisters/persister-indexed-db';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createIndexedDbPersister(store, 'petStore');
console.log(persister.getDbName());
// -> 'petStore'
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the IndexedDbPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the IndexedDbPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<IndexedDbPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<IndexedDbPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the IndexedDbPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<IndexedDbPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<IndexedDbPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the IndexedDbPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<IndexedDbPersister>
returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<IndexedDbPersister>
returns | Promise<IndexedDbPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the IndexedDbPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createIndexedDbPersister
, within the persister-indexed-db
module.
createIndexedDbPersister
The createIndexedDbPersister
function creates an IndexedDbPersister
object that can persist a Store
to the browser's IndexedDB storage.
createIndexedDbPersister(
store: Store,
dbName: string,
autoLoadIntervalSeconds?: number,
onIgnoredError?: (error: any) => void,
): IndexedDbPersister
Type | Description | |
---|---|---|
store | Store | The |
dbName | string | The unique key to identify the IndexedDB to use. |
autoLoadIntervalSeconds? | number | How often to poll the database when in 'autoLoad' mode, defaulting to 1. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | IndexedDbPersister | A reference to the new |
An IndexedDbPersister
only supports regular Store
objects, and cannot be used to persist the metadata of a MergeableStore
.
As well as providing a reference to the Store
to persist, you must provide a dbName
parameter which is unique to your application. This is the key used to identify which IndexedDB to use.
Within that database, this Persister
will create two object stores: one called 't', and one called 'v'. These will contain the Store
's tabular and key-value data respectively, using 'k' and 'v' to store the key and value of each entry, as shown in the example.
Note that it is not possible to reactively detect changes to a browser's IndexedDB. If you do choose to enable automatic loading for the Persister
(with the startAutoLoad
method), it needs to poll the database for changes. The autoLoadIntervalSeconds
method is used to indicate how often to do this.
Example
This example creates a IndexedDbPersister
object and persists the Store
to the browser's IndexedDB storage.
import {createIndexedDbPersister} from 'tinybase/persisters/persister-indexed-db';
import {createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}})
.setValues({open: true});
const persister = createIndexedDbPersister(store, 'petStore');
await persister.save();
// IndexedDB ->
// database petStore:
// objectStore t:
// object 0:
// k: "pets"
// v: {fido: {species: dog}}
// object 1:
// k: "species"
// v: {dog: {price: 5}}
// objectStore v:
// object 0:
// k: "open"
// v: true
persister.destroy();
Since
v4.2.0
persister-libsql
The persister-libsql
module of the TinyBase project lets you save and load Store
data to and from a local LibSQL database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.7.0
Interfaces
There is one interface, LibSqlPersister
, within the persister-libsql
module.
LibSqlPersister
The LibSqlPersister
interface represents a Persister
that lets you save and load Store
data to and from a local LibSQL database.
You should use the createLibSqlPersister
function to create a LibSqlPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getClient
method for accessing a reference to the database client the Store
is being persisted to.
Since
v4.7.0
Getter methods
This is the collection of getter methods within the LibSqlPersister
interface. There are only two getter methods, getStore
and getClient
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store
returns | Store | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getClient
The getClient
method returns a reference to the database client the Store
is being persisted to.
getClient(): Client
returns | Client | A reference to the database client. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the database client back out again.
import {createClient} from '@libsql/client';
import {createLibSqlPersister} from 'tinybase/persisters/persister-libsql';
import {createStore} from 'tinybase';
const client = createClient({url: 'file:my.db'});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLibSqlPersister(store, client, 'my_tinybase');
console.log(persister.getClient() == client);
// -> true
persister.destroy();
Since
v4.7.0
Listener methods
This is the collection of listener methods within the LibSqlPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the LibSqlPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<LibSqlPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<LibSqlPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the LibSqlPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<LibSqlPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<LibSqlPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the LibSqlPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<LibSqlPersister>
returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<LibSqlPersister>
returns | Promise<LibSqlPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the LibSqlPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createLibSqlPersister
, within the persister-libsql
module.
createLibSqlPersister
The createLibSqlPersister
function creates a LibSqlPersister
object that can persist a Store
to a local LibSQL database.
createLibSqlPersister(
store: Store,
client: Client,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): LibSqlPersister
Type | Description | |
---|---|---|
store | Store | The |
client | Client | The database client that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | LibSqlPersister | A reference to the new |
A LibSqlPersister
only supports regular Store
objects, and cannot be used to persist the metadata of a MergeableStore
.
As well as providing a reference to the Store
to persist, you must provide a client
parameter which identifies the database client.
A database Persister
uses one of two modes: either a JSON serialization of the whole Store
stored in a single row of a table (the default), or a tabular mapping of Table
Ids
to database table names and vice-versa).
The third argument is a DatabasePersisterConfig
object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName
property of the JSON serialization.
See the documentation for the DpcJson
and DpcTabular
types for more information on how both of those modes can be configured.
Examples
This example creates a LibSqlPersister
object and persists the Store
to a local SQLite database as a JSON serialization into the my_tinybase
table. It makes a change to the database directly and then reloads it back into the Store
.
import {createClient} from '@libsql/client';
import {createLibSqlPersister} from 'tinybase/persisters/persister-libsql';
import {createStore} from 'tinybase';
const client = createClient({url: 'file:my.db'});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLibSqlPersister(store, client, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log((await client.execute('SELECT * FROM my_tinybase;')).rows);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await client.execute(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a LibSqlPersister
object and persists the Store
to a local SQLite database with tabular mapping.
import {createClient} from '@libsql/client';
import {createLibSqlPersister} from 'tinybase/persisters/persister-libsql';
import {createStore} from 'tinybase';
const client = createClient({url: 'file:my.db'});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLibSqlPersister(store, client, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log((await client.execute('SELECT * FROM pets;')).rows);
// -> [{_id: 'fido', species: 'dog'}]
await client.execute(
`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.7.0
persister-partykit-client
The persister-partykit-client
module of the TinyBase project contains the client portion of the PartyKit integration.
It contains a Persister
which, when run in a PartyKit client environment, lets you save and load Store
data from the client to the durable PartyKit cloud storage of a server (that is using the complementary persister-partykit-server
module).
This enables synchronization of the same Store
across multiple clients in a PartyKit party room.
Note that both the client and server parts of this Persister
are currently experimental as PartyKit is new and still maturing.
See also
Persistence guides
Since
v4.3.0
Interfaces
There is one interface, PartyKitPersister
, within the persister-partykit-client
module.
PartyKitPersister
The PartyKitPersister
interface represents a Persister
that lets you save and load Store
data from the client to the durable PartyKit cloud storage of a server.
You should use the createPartyKitPersister
function to create a PartyKitPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getConnection
method for accessing the PartySocket the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the PartyKitPersister
interface. There are only two getter methods, getStore
and getConnection
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store
returns | Store | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getConnection
The getConnection
method returns the PartySocket the Store
is being persisted to.
getConnection(): PartySocket
returns | PartySocket | The PartySocket. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the PartySocket back out again.
import {PartySocket} from 'partysocket';
import {createPartyKitPersister} from 'tinybase/persisters/persister-partykit-client';
import {createStore} from 'tinybase';
const partySocket = new PartySocket({
host: '127.0.0.1:1999',
room: 'my_room',
});
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createPartyKitPersister(store, partySocket);
console.log(persister.getConnection() == partySocket);
// -> true
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the PartyKitPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the PartyKitPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<PartyKitPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<PartyKitPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the PartyKitPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<PartyKitPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<PartyKitPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the PartyKitPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<PartyKitPersister>
returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<PartyKitPersister>
returns | Promise<PartyKitPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the PartyKitPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createPartyKitPersister
, within the persister-partykit-client
module.
createPartyKitPersister
The createPartyKitPersister
function creates a PartyKitPersister
object that can persist the Store
to durable PartyKit storage, enabling synchronization of the same Store
across multiple clients.
createPartyKitPersister(
store: Store,
connection: PartySocket,
configOrStoreProtocol?: PartyKitPersisterConfig | "http" | "https",
onIgnoredError?: (error: any) => void,
): PartyKitPersister
Type | Description | |
---|---|---|
store | Store | The |
connection | PartySocket | The PartySocket to use for participating in the PartyKit room. |
configOrStoreProtocol? | PartyKitPersisterConfig | "http" | "https" | The |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | PartyKitPersister | A reference to the new |
A PartyKitPersister
only supports regular Store
objects, and cannot be used to persist the metadata of a MergeableStore
.
As well as providing a reference to the Store
to persist, you must provide a connection
parameter which is a PartyKit PartySocket that you have already instantiated with details of the host and room.
All suitably-equipped TinyBase clients connecting to that room will get to share synchronized Store
state.
The server room's Store
is considered the source of truth. If it is a newly-created room, then calling the save
method on this Persister
will initiate it. If, however, there is already a Store
present on the server, the save
method will fail gracefully.
In general, you are strongly recommended to use the auto-save and auto-load functionality to stay in sync incrementally with the server, as per the example below. This pattern will handle newly-created servers and newly-created clients - and the synchronization involved in joining rooms, leaving them, or temporarily going offline.
See the PartyKit client socket API documentation for more details.
Example
This example creates a PartyKitPersister
object and persists the Store
to the browser's IndexedDB storage.
import {PartySocket} from 'partysocket';
import {createPartyKitPersister} from 'tinybase/persisters/persister-partykit-client';
import {createStore} from 'tinybase';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}})
.setValues({open: true});
const partySocket = new PartySocket({
host: '127.0.0.1:1999',
room: 'my_room',
});
const persister = createPartyKitPersister(store, partySocket);
await persister.startAutoLoad();
await persister.startAutoSave();
// Store will now be synchronized with the room.
persister.destroy();
Since
v4.3.0
Type Aliases
There is one type alias, PartyKitPersisterConfig
, within the persister-partykit-client
module.
PartyKitPersisterConfig
The PartyKitPersisterConfig
type describes the configuration of a PartyKit Persister
on the client side.
{
storeProtocol?: "http" | "https";
storePath?: string;
messagePrefix?: string;
}
Type | Description | |
---|---|---|
storeProtocol? | "http" | "https" | The HTTP protocol to use (in addition to the websocket channel). This defaults to 'https' but you may wish to use 'http' for local PartyKit development. |
storePath? | string | The path used to set and get the whole |
messagePrefix? | string | The prefix at the beginning of the web socket messages sent between the client and the server when synchronizing the |
The defaults (if used on both the server and client) will work fine, but if you are building more complex PartyKit apps and you need to configure path names, for example, then this is the thing to use.
Example
When applied to a PartyKit Persister
, this PartyKitPersisterConfig
will load and save a JSON serialization from and to an end point in your room called /my_tinybase
, and use HTTP (rather than the default HTTPS) as the protocol.
Note that this would require you to also add the matching storePath setting to the TinyBasePartyKitServerConfig
on the server side.
export const partyKitPersisterConfig = {
storeProtocol: 'http',
storePath: '/my_tinybase',
};
Since
v4.3.9
persister-partykit-server
The persister-partykit-server
module of the TinyBase project contains the server portion of the PartyKit integration.
It contains a class which, when run in a PartyKit server environment, lets you save and load Store
data from a client (that is using the complementary persister-partykit-client
module) to durable PartyKit cloud storage.
This enables synchronization of the same Store
across multiple clients in a PartyKit party room.
Note that both the client and server parts of this Persister
are currently experimental as PartyKit is new and still maturing.
See also
Persistence guides
Since
v4.3.0
Classes
There is one class, TinyBasePartyKitServer
, within the persister-partykit-server
module.
TinyBasePartyKitServer
This extends the PartyKit Server class, which provides a selection of methods you are expected to implement. The TinyBasePartyKitServer implements only two of them, the onMessage
method and the onRequest
method, as well as the constructor.
If you wish to use TinyBasePartyKitServer as a general PartyKit server, you can implement other methods. But you must remember to call the super implementations of those methods to ensure the TinyBase synchronization stays supported in addition to your own custom functionality. The same applies to the constructor if you choose to implement that.
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
// This is your PartyKit server entry point.
export class MyServer extends TinyBasePartyKitServer {
constructor(party) {
super(party);
// custom constructor code
}
async onStart() {
// no need to call super.onStart()
console.log('Server started');
}
async onMessage(message, connection) {
await super.onMessage(message, connection);
// custom onMessage code
}
async onRequest(request) {
// custom onRequest code, else:
return await super.onRequest(request);
}
}
See the PartyKit server API documentation for more details.
Since
v4.3.0
Constructors
There is one constructor, constructor
, within the TinyBasePartyKitServer
class.
constructor
Methods
These are the methods within the TinyBasePartyKitServer
class.
Connection methods
This is the collection of connection methods within the TinyBasePartyKitServer
class. There are only two connection methods, onMessage
and onRequest
.
onMessage
The onMessage
method is called when the server receives a message from a client.
onMessage(
message: string,
connection: Connection,
): Promise<void>
Type | Description | |
---|---|---|
message | string | |
connection | Connection | |
returns | Promise<void> |
If you choose to implement additional functionality in this method, you must remember to call the super implementation to ensure the TinyBase synchronization stays supported.
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
async onMessage(message, connection) {
await super.onMessage(message, connection);
// custom onMessage code
}
}
See the PartyKit server API documentation for more details.
Since
v4.3.0
onRequest
The onRequest
method is called when a HTTP request is made to the party URL.
onRequest(request: Request): Promise<Response>
Type | Description | |
---|---|---|
request | Request | |
returns | Promise<Response> |
If you choose to implement additional functionality in this method, you must remember to call the super implementation to ensure the TinyBase synchronization stays supported.
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
async onRequest(request) {
// custom onRequest code, else:
return await super.onRequest(request);
}
}
See the PartyKit server API documentation for more details.
Since
v4.3.0
Sanitization methods
This is the collection of sanitization methods within the TinyBasePartyKitServer
class. There are 8 sanitization methods in total.
canDelTable
The canDelTable
method lets you allow or disallow deletions of a Table
stored on the server, as sent from a client.
canDelTable(
tableId: string,
connection: Connection,
): Promise<boolean>
Type | Description | |
---|---|---|
tableId | string | |
connection | Connection | |
returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table
Id
that the client is trying to delete. The connection
parameter will be the web socket connection of that client. You can, for instance, use this to distinguish between different users.
Return false
from this method to disallow this Table
from being deleted on the server, or true
to allow it. The default implementation returns true
to allow deletion.
Example
The following implementation will strip out any attempts by the client to delete the 'user' Table
:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canDelTable(tableId) {
return tableId != 'user';
}
}
Since
v4.3.12
canSetTable
The canSetTable
method lets you allow or disallow any changes to a Table
stored on the server, as sent from a client.
canSetTable(
tableId: string,
initialSave: boolean,
requestOrConnection: Request | Connection,
): Promise<boolean>
Type | Description | |
---|---|---|
tableId | string | |
initialSave | boolean | |
requestOrConnection | Request | Connection | |
returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table
Id
that the client is trying to change. The initialSave
parameter distinguishes between the first bulk save of the Store
to the PartyKit room over HTTP (true
), and subsequent incremental updates over a web sockets (false
).
The requestOrConnection
parameter will either be the HTTP(S) request or the web socket connection, in those two cases respectively. You can, for instance, use this to distinguish between different users.
Since v4.3.13, the final parameter is the Cell
previously stored on the server, if any. Use this to distinguish between the addition of a new Cell
(in which case it will be undefined) and the updating of an existing one.
Return false
from this method to disallow changes to this Table
on the server, or true
to allow them (subject to subsequent canSetRow
method, canDelRow
method, canSetCell
method, and canSetCell
method checks). The default implementation returns true
to allow all changes.
Example
The following implementation will strip out any attempts by the client to update any 'user' tabular data after the initial save:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canSetTable(tableId, initialSave) {
return initialSave || tableId != 'user';
}
}
Since
v4.3.12
canDelRow
The canDelRow
method lets you allow or disallow deletions of a Row
stored on the server, as sent from a client.
canDelRow(
tableId: string,
rowId: string,
connection: Connection,
): Promise<boolean>
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
connection | Connection | |
returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table
Id
and Row
Id
that the client is trying to delete. The connection
parameter will be the web socket connection of that client. You can, for instance, use this to distinguish between different users.
Return false
from this method to disallow this Row
from being deleted on the server, or true
to allow it. The default implementation returns true
to allow deletion.
Example
The following implementation will strip out any attempts by the client to delete the 'me' Row
of the 'user' Table
:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canDelRow(tableId, rowId) {
return tableId != 'user' || rowId != 'me';
}
}
Since
v4.3.12
canSetRow
The canSetRow
method lets you allow or disallow any changes to a Row
stored on the server, as sent from a client.
canSetRow(
tableId: string,
rowId: string,
initialSave: boolean,
requestOrConnection: Request | Connection,
): Promise<boolean>
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
initialSave | boolean | |
requestOrConnection | Request | Connection | |
returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table
Id
and Row
Id
that the client is trying to change. The initialSave
parameter distinguishes between the first bulk save of the Store
to the PartyKit room over HTTP (true
), and subsequent incremental updates over a web sockets (false
).
The final requestOrConnection
parameter will either be the HTTP(S) request or the web socket connection, in those two cases respectively. You can, for instance, use this to distinguish between different users.
Return false
from this method to disallow changes to this Row
on the server, or true
to allow them (subject to subsequent canSetCell
method and canSetCell
method checks). The default implementation returns true
to allow all changes.
Example
The following implementation will strip out any attempts by the client to update the 'me' Row
of the 'user' Table
after the initial save:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canSetRow(tableId, rowId, initialSave) {
return initialSave || tableId != 'user' || rowId != 'me';
}
}
Since
v4.3.12
canDelCell
The canDelCell
method lets you allow or disallow deletions of a Cell
stored on the server, as sent from a client.
canDelCell(
tableId: string,
rowId: string,
cellId: string,
connection: Connection,
): Promise<boolean>
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
connection | Connection | |
returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table
Id
, Row
Id
, and Cell
Id
that the client is trying to delete. The connection
parameter will be the web socket connection of that client. You can, for instance, use this to distinguish between different users.
Return false
from this method to disallow this Cell
from being deleted on the server, or true
to allow it. The default implementation returns true
to allow deletion.
Example
The following implementation will strip out any attempts by the client to delete the 'name' Cell
of the 'me' Row
of the 'user' Table
:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canDelCell(tableId, rowId, cellId) {
return tableId != 'user' || rowId != 'me' || cellId != 'name';
}
}
Since
v4.3.12
canSetCell
The canSetCell
method lets you allow or disallow any changes to a Cell
stored on the server, as sent from a client.
canSetCell(
tableId: string,
rowId: string,
cellId: string,
cell: Cell,
initialSave: boolean,
requestOrConnection: Request | Connection,
oldCell: CellOrUndefined,
): Promise<boolean>
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
cell | Cell | |
initialSave | boolean | |
requestOrConnection | Request | Connection | |
oldCell | CellOrUndefined | |
returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Table
Id
, Row
Id
, and Cell
Id
that the client is trying to change - as well as the Cell
value itself. The initialSave
parameter distinguishes between the first bulk save of the Store
to the PartyKit room over HTTP (true
), and subsequent incremental updates over a web sockets (false
).
The final requestOrConnection
parameter will either be the HTTP(S) request or the web socket connection, in those two cases respectively. You can, for instance, use this to distinguish between different users.
Return false
from this method to disallow changes to this Cell
on the server, or true
to allow them. The default implementation returns true
to allow all changes.
Example
The following implementation will strip out any attempts by the client to update the 'name' Cell
of the 'me' Row
of the 'user' Table
after the initial save:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canSetCell(tableId, rowId, cellId, cell, initialSave) {
return (
initialSave || tableId != 'user' || rowId != 'me' || cellId != 'name'
);
}
}
Since
v4.3.12
canDelValue
The canDelValue
method lets you allow or disallow deletions of a Value
stored on the server, as sent from a client.
canDelValue(
valueId: string,
connection: Connection,
): Promise<boolean>
Type | Description | |
---|---|---|
valueId | string | |
connection | Connection | |
returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Value
Id
that the client is trying to delete. The connection
parameter will be the web socket connection of that client. You can, for instance, use this to distinguish between different users.
Return false
from this method to disallow this Value
from being deleted on the server, or true
to allow it. The default implementation returns true
to allow deletion.
Example
The following implementation will strip out any attempts by the client to delete the 'userId' Value
:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canDelValue(valueId) {
return valueId != 'userId';
}
}
Since
v4.3.12
canSetValue
The canSetValue
method lets you allow or disallow any changes to a Value
stored on the server, as sent from a client.
canSetValue(
valueId: string,
value: Value,
initialSave: boolean,
requestOrConnection: Request | Connection,
oldValue: ValueOrUndefined,
): Promise<boolean>
Type | Description | |
---|---|---|
valueId | string | |
value | Value | |
initialSave | boolean | |
requestOrConnection | Request | Connection | |
oldValue | ValueOrUndefined | |
returns | Promise<boolean> |
This is one of the functions use to sanitize the data that is being sent from a client. Perhaps you might want to make sure the server-stored data adheres to a particular schema, or you might want to make certain data read-only. Remember that you cannot trust the client to only send data that the server considers valid or safe.
This method is passed the Value
Id
that the client is trying to change - as well as the Value
itself. The initialSave
parameter distinguishes between the first bulk save of the Store
to the PartyKit room over HTTP (true
), and subsequent incremental updates over a web sockets (false
).
The requestOrConnection
parameter will either be the HTTP(S) request or the web socket connection, in those two cases respectively. You can, for instance, use this to distinguish between different users.
Since v4.3.13, the final parameter is the Value
previously stored on the server, if any. Use this to distinguish between the addition of a new Value
(in which case it will be undefined) and the updating of an existing one.
Return false
from this method to disallow changes to this Value
on the server, or true
to allow them. The default implementation returns true
to allow all changes.
Example
The following implementation will strip out any attempts by the client to update the 'userId' Value
after the initial save:
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
canSetValue(valueId, value, initialSave) {
return initialSave || valueId != 'userId';
}
}
Since
v4.3.12
Properties
There is one property, config
, within the TinyBasePartyKitServer
class.
config
See the documentation for that type for more details.
Since
v4.3.9
Functions
These are the functions within the persister-partykit-server
module.
Connection functions
This is the collection of connection functions within the persister-partykit-server
module. There is only one function, broadcastChanges
.
broadcastChanges
The broadcastChanges
function allows you to broadcast Store
changes to all the client connections of a TinyBasePartyKitServer.
broadcastChanges(
server: TinyBasePartyKitServer,
changes: Changes,
without?: string[],
): Promise<void>
Type | Description | |
---|---|---|
server | TinyBasePartyKitServer | A reference to the TinyBasePartyKitServer object. |
changes | Changes | The |
without? | string[] | An optional array of client connection |
returns | Promise<void> |
This is intended for specialist applications that require the ability to update clients of a TinyBasePartyKitServer in their own custom ways.
The function is asynchronous, so you should use the await
keyword or handle its completion as a promise.
Since
v4.5.1
Storage functions
This is the collection of storage functions within the persister-partykit-server
module. There are only two storage functions, hasStoreInStorage
and loadStoreFromStorage
.
hasStoreInStorage
The hasStoreInStorage
function returns a boolean indicating whether durable PartyKit storage contains a serialized Store
.
hasStoreInStorage(
storage: Storage,
storagePrefix?: string,
): Promise<boolean>
Type | Description | |
---|---|---|
storage | Storage | A reference to the storage object, as would normally be accessible from the |
storagePrefix? | string | An optional prefix used before all the keys in the server's durable storage, to match the equivalent property in the server's |
returns | Promise<boolean> | A promised boolean indicating whether a |
This is intended for specialist applications that require the ability to inspect or load a TinyBase Store
from a server's storage outside of the normal context of a TinyBasePartyKitServer.
The function is asynchronous, so you should use the await
keyword or handle the result as a promise.
Since
v4.4.1
loadStoreFromStorage
The loadStoreFromStorage
function returns the content of a Store
from durable PartyKit storage.
loadStoreFromStorage(
storage: Storage,
storagePrefix?: string,
): Promise<Content>
Type | Description | |
---|---|---|
storage | Storage | A reference to the storage object, as would normally be accessible from the |
storagePrefix? | string | An optional prefix used before all the keys in the server's durable storage, to match the equivalent property in the server's |
returns | Promise<Content> |
This is intended for specialist applications that require the ability to inspect or load a TinyBase Store
from a server's storage outside of the normal context of a TinyBasePartyKitServer.
The function is asynchronous, so you should use the await
keyword or handle the result as a promise.
Since
v4.4.1
Type Aliases
There is one type alias, TinyBasePartyKitServerConfig
, within the persister-partykit-server
module.
TinyBasePartyKitServerConfig
The TinyBasePartyKitServerConfig
type describes the configuration of a PartyKit Persister
on the server side.
{
storePath?: string;
messagePrefix?: string;
storagePrefix?: string;
responseHeaders?: HeadersInit;
}
Type | Description | |
---|---|---|
storePath? | string | The path used to set and get the whole |
messagePrefix? | string | The prefix at the beginning of the web socket messages between the client and the server when synchronizing the |
storagePrefix? | string | The prefix used before all the keys in the server's durable storage. Use this in case you are worried about the |
responseHeaders? | HeadersInit | An object containing the extra HTTP(S) headers returned to the client from this server. This defaults to the following three headers to allow CORS. |
The defaults (if used on both the server and client) will work fine, but if you are building more complex PartyKit apps and you need to configure path names, for example, then this is the type to use.
Example
When set as the config in a TinyBasePartyKitServer, this TinyBasePartyKitServerConfig
will expect clients to load and save their JSON serialization from and to an end point in the room called /my_tinybase
. Note that this would require you to also add the matching storePath setting to the PartyKitPersisterConfig
on the client side.
It will also store the data in the durable storage with a prefix of 'tinybase_' in case you are worried about colliding with other data stored in the room.
import {TinyBasePartyKitServer} from 'tinybase/persisters/persister-partykit-server';
export class MyServer extends TinyBasePartyKitServer {
readonly config = {
storePath: '/my_tinybase',
storagePrefix: 'tinybase_',
};
}
Since
v4.3.9
persister-pglite
The persister-pglite
module of the TinyBase project lets you save and load Store
data to and from a PGlite database (in an appropriate environment).
See also
Database Persistence guide
Since
5.2.0
Interfaces
There is one interface, PglitePersister
, within the persister-pglite
module.
PglitePersister
The PglitePersister
interface represents a Persister
that lets you save and load Store
data to and from a local PGlite database.
You should use the createPglitePersister
function to create a PglitePersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getPglite
method for accessing a reference to the database connection the Store
is being persisted to.
Since
5.2.0
Getter methods
This is the collection of getter methods within the PglitePersister
interface. There are only two getter methods, getStore
and getPglite
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store | MergeableStore
returns | Store | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getPglite
The getPglite
method returns a reference to the database connection the Store
is being persisted to.
getPglite(): PGlite
returns | PGlite | A reference to the database connection. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the database connection back out again.
import {PGlite} from '@electric-sql/pglite';
import {createPglitePersister} from 'tinybase/persisters/persister-pglite';
import {createStore} from 'tinybase';
const pglite = await PGlite.create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPglitePersister(
store,
pglite,
'my_tinybase',
);
console.log(persister.getPglite() == pglite);
// -> true
persister.destroy();
await pglite.close();
Since
5.2.0
Listener methods
This is the collection of listener methods within the PglitePersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the PglitePersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<PglitePersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<PglitePersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the PglitePersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<PglitePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<PglitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<PglitePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<PglitePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the PglitePersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<PglitePersister>
returns | Promise<PglitePersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<PglitePersister>
returns | Promise<PglitePersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the PglitePersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createPglitePersister
, within the persister-pglite
module.
createPglitePersister
The createPglitePersister
function creates a PglitePersister
object that can persist the Store
to a local PGlite database.
createPglitePersister(
store: Store | MergeableStore,
pglite: PGlite,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Promise<PglitePersister>
Type | Description | |
---|---|---|
store | Store | MergeableStore | The |
pglite | PGlite | The database connection that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | Promise<PglitePersister> | A reference to the new |
A PglitePersister
supports regular Store
objects, and can also be used to persist the metadata of a MergeableStore
when using the JSON serialization mode, as described below.
As well as providing a reference to the Store
to persist, you must provide a pglite
parameter which identifies the database connection.
A database Persister
uses one of two modes: either a JSON serialization of the whole Store
stored in a single row of a table (the default), or a tabular mapping of Table
Ids
to database table names and vice-versa).
The third argument is a DatabasePersisterConfig
object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName
property of the JSON serialization.
See the documentation for the DpcJson
and DpcTabular
types for more information on how both of those modes can be configured.
This method is asynchronous because it will await the creation of dedicated new connections to the database. You will need to await
a call to this function or handle the return type natively as a Promise.
Examples
This example creates a PglitePersister
object and persists the Store
to a local PGlite database as a JSON serialization into the my_tinybase
table. It makes a change to the database directly and then reloads it back into the Store
.
import {PGlite} from '@electric-sql/pglite';
import {createPglitePersister} from 'tinybase/persisters/persister-pglite';
import {createStore} from 'tinybase';
const pglite = await PGlite.create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPglitePersister(
store,
pglite,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log((await pglite.query('SELECT * FROM my_tinybase;')).rows);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await pglite.query(`UPDATE my_tinybase SET store = $1 WHERE _id = '_';`, [
'[{"pets":{"felix":{"species":"cat"}}},{}]',
]);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
await pglite.close();
This example creates a PglitePersister
object and persists the Store
to a local PGlite database with tabular mapping.
import {PGlite} from '@electric-sql/pglite';
import {createPglitePersister} from 'tinybase/persisters/persister-pglite';
import {createStore} from 'tinybase';
const pglite = await PGlite.create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPglitePersister(store, pglite, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log((await pglite.query('SELECT * FROM pets;')).rows);
// -> [{_id: 'fido', species: '"dog"'}]
// Note that Cells and Values are JSON-encoded in PostgreSQL databases.
await pglite.query(
`INSERT INTO pets (_id, species) VALUES ('felix', '"cat"')`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
await pglite.query('DROP TABLE IF EXISTS pets');
await pglite.close();
Since
5.2.0
persister-postgres
The persister-postgres
module of the TinyBase project lets you save and load Store
data to and from a PostgreSQL database (in an appropriate environment).
See also
Database Persistence guide
Since
5.2.0
Interfaces
There is one interface, PostgresPersister
, within the persister-postgres
module.
PostgresPersister
The PostgresPersister
interface represents a Persister
that lets you save and load Store
data to and from a local PostgreSQL database.
You should use the createPostgresPersister
function to create a PostgresPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getSql
method for accessing a reference to the database connection the Store
is being persisted to.
Since
5.2.0
Getter methods
This is the collection of getter methods within the PostgresPersister
interface. There are only two getter methods, getStore
and getSql
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store | MergeableStore
returns | Store | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getSql
The getSql
method returns a reference to the database connection the Store
is being persisted to.
getSql(): Sql<{}>
returns | Sql<{}> | A reference to the database connection. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the database connection back out again.
import {createPostgresPersister} from 'tinybase/persisters/persister-postgres';
import {createStore} from 'tinybase';
import postgres from 'postgres';
const sql = postgres('postgres://localhost:5432/tinybase');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPostgresPersister(store, sql, 'my_tinybase');
console.log(persister.getSql() == sql);
// -> true
persister.destroy();
await sql.end();
Since
5.2.0
Listener methods
This is the collection of listener methods within the PostgresPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the PostgresPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<PostgresPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<PostgresPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the PostgresPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<PostgresPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<PostgresPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<PostgresPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<PostgresPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the PostgresPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<PostgresPersister>
returns | Promise<PostgresPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<PostgresPersister>
returns | Promise<PostgresPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the PostgresPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createPostgresPersister
, within the persister-postgres
module.
createPostgresPersister
The createPostgresPersister
function creates a PostgresPersister
object that can persist the Store
to a local PostgreSQL database.
createPostgresPersister(
store: Store | MergeableStore,
sql: Sql<{}>,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Promise<PostgresPersister>
Type | Description | |
---|---|---|
store | Store | MergeableStore | The |
sql | Sql<{}> | The database connection that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | Promise<PostgresPersister> | A reference to the new |
A PostgresPersister
supports regular Store
objects, and can also be used to persist the metadata of a MergeableStore
when using the JSON serialization mode, as described below.
As well as providing a reference to the Store
to persist, you must provide a sql
parameter which identifies the database connection.
A database Persister
uses one of two modes: either a JSON serialization of the whole Store
stored in a single row of a table (the default), or a tabular mapping of Table
Ids
to database table names and vice-versa).
The third argument is a DatabasePersisterConfig
object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName
property of the JSON serialization.
See the documentation for the DpcJson
and DpcTabular
types for more information on how both of those modes can be configured.
This method is asynchronous because it will await the creation of dedicated new connections to the database. You will need to await
a call to this function or handle the return type natively as a Promise.
Examples
This example creates a PostgresPersister
object and persists the Store
to a local PostgreSQL database as a JSON serialization into the my_tinybase
table. It makes a change to the database directly and then reloads it back into the Store
.
import {createPostgresPersister} from 'tinybase/persisters/persister-postgres';
import {createStore} from 'tinybase';
import postgres from 'postgres';
const sql = postgres('postgres://localhost:5432/tinybase');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPostgresPersister(store, sql, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(await sql`SELECT * FROM my_tinybase;`);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
const json = '[{"pets":{"felix":{"species":"cat"}}},{}]';
await sql`UPDATE my_tinybase SET store = ${json} WHERE _id = '_';`;
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
await sql.end();
This example creates a PostgresPersister
object and persists the Store
to a local PostgreSQL database with tabular mapping.
import {createPostgresPersister} from 'tinybase/persisters/persister-postgres';
import {createStore} from 'tinybase';
import postgres from 'postgres';
const sql = postgres('postgres://localhost:5432/tinybase');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = await createPostgresPersister(store, sql, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(await sql`SELECT * FROM pets;`);
// -> [{_id: 'fido', species: '"dog"'}]
// Note that Cells and Values are JSON-encoded in PostgreSQL databases.
await sql`INSERT INTO pets (_id, species) VALUES ('felix', '"cat"')`;
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
await sql`DROP TABLE IF EXISTS pets`;
await sql.end();
Since
5.2.0
persister-powersync
The persister-powersync
module of the TinyBase project lets you save and load Store
data to and from a local SQLite database that is automatically synced using the PowerSync service.
See also
Database Persistence guide
Since
v4.8.0
Interfaces
There is one interface, PowerSyncPersister
, within the persister-powersync
module.
PowerSyncPersister
The PowerSyncPersister
interface represents a Persister
that lets you save and load Store
data to and from a local SQLite database that is automatically synced using the PowerSync service.
You should use the createPowerSyncPersister
function to create a PowerSyncPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getPowerSync
method for accessing a reference to the PowerSync instance the Store
is being persisted to.
Since
v4.8.0
Getter methods
This is the collection of getter methods within the PowerSyncPersister
interface. There are only two getter methods, getStore
and getPowerSync
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store
returns | Store | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getPowerSync
The getPowerSync
method returns a reference to the PowerSync instance the Store
is being persisted to.
getPowerSync(): AbstractPowerSyncDatabase
returns | AbstractPowerSyncDatabase | A reference to the PowerSync instance. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the PowerSync instance back out again.
import {createPowerSyncPersister} from 'tinybase/persisters/persister-powersync';
import {createStore} from 'tinybase';
import {usePowerSync} from '@powersync/react';
const ps = usePowerSync();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createPowerSyncPersister(store, ps, 'my_tinybase');
console.log(persister.getPowerSync() == ps);
// -> true
persister.destroy();
Since
v4.8.0
Listener methods
This is the collection of listener methods within the PowerSyncPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the PowerSyncPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<PowerSyncPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<PowerSyncPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the PowerSyncPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<PowerSyncPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<PowerSyncPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the PowerSyncPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<PowerSyncPersister>
returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<PowerSyncPersister>
returns | Promise<PowerSyncPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the PowerSyncPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createPowerSyncPersister
, within the persister-powersync
module.
createPowerSyncPersister
The createPowerSyncPersister
function creates a PowerSyncPersister
object that can persist the Store
to a local SQLite database that is automatically synced using the PowerSync service.
createPowerSyncPersister(
store: Store,
powerSync: AbstractPowerSyncDatabase,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): PowerSyncPersister
Type | Description | |
---|---|---|
store | Store | The |
powerSync | AbstractPowerSyncDatabase | The PowerSync instance. |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | PowerSyncPersister | A reference to the new |
A PowerSyncPersister
only supports regular Store
objects, and cannot be used to persist the metadata of a MergeableStore
.
As well as providing a reference to the Store
to persist, you must provide a powerSync
parameter which identifies the PowerSync instance.
A database Persister
uses one of two modes: either a JSON serialization of the whole Store
stored in a single row of a table (the default), or a tabular mapping of Table
Ids
to database table names and vice-versa).
The third argument is a DatabasePersisterConfig
object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName
property of the JSON serialization.
See the documentation for the DpcJson
and DpcTabular
types for more information on how both of those modes can be configured.
Examples
This example creates a PowerSyncPersister
object and persists the Store
to a local PowerSync instance. It makes a change to the database directly and then reloads it back into the Store
.
import {createPowerSyncPersister} from 'tinybase/persisters/persister-powersync';
import {createStore} from 'tinybase';
import {usePowerSync} from '@powersync/react';
const ps = usePowerSync();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createPowerSyncPersister(store, ps, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(
await new Promise((resolve) =>
ps.execute('SELECT * FROM my_tinybase;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await new Promise((resolve) =>
ps.execute(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a PowerSyncPersister
object and persists the Store
to a local PowerSync instance with tabular mapping.
import {createPowerSyncPersister} from 'tinybase/persisters/persister-powersync';
import {createStore} from 'tinybase';
import {usePowerSync} from '@powersync/react';
const ps = usePowerSync();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createPowerSyncPersister(store, ps, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(
await new Promise((resolve) =>
ps.execute('SELECT * FROM pets;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: 'fido', species: 'dog'}]
await new Promise((resolve) =>
ps.execute(
`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.8.0
persister-remote
The persister-remote
module of the TinyBase project lets you save and load Store
data to and from a remote server.
See also
Persistence guides
Since
v1.0.0
Interfaces
There is one interface, RemotePersister
, within the persister-remote
module.
RemotePersister
The RemotePersister
interface represents a Persister
that lets you save and load Store
data to and from a remote server.
You should use the createRemotePersister
function to create a RemotePersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getUrls
method for accessing the URLs the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the RemotePersister
interface. There are only two getter methods, getStore
and getUrls
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store
returns | Store | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getUrls
The getUrls
method returns the URLs the Store
is being persisted to.
getUrls(): [string, string]
returns | [string, string] | The load and save URLs as a two-item array. |
---|
Example
This example creates a RemotePersister
object against a newly-created Store
and then gets the URLs back out again.
import {createRemotePersister} from 'tinybase/persisters/persister-remote';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createRemotePersister(
store,
'https://example.com/load',
'https://example.com/save',
5,
);
console.log(persister.getUrls());
// -> ['https://example.com/load', 'https://example.com/save']
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the RemotePersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the RemotePersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<RemotePersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<RemotePersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the RemotePersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<RemotePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<RemotePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<RemotePersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<RemotePersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the RemotePersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<RemotePersister>
returns | Promise<RemotePersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<RemotePersister>
returns | Promise<RemotePersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the RemotePersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createRemotePersister
, within the persister-remote
module.
createRemotePersister
The createRemotePersister
function creates a RemotePersister
object that can persist the Store
to a remote server.
createRemotePersister(
store: Store,
loadUrl: string,
saveUrl: string,
autoLoadIntervalSeconds?: number,
onIgnoredError?: (error: any) => void,
): RemotePersister
Type | Description | |
---|---|---|
store | Store | The |
loadUrl | string | The endpoint that supports a |
saveUrl | string | The endpoint that supports a |
autoLoadIntervalSeconds? | number | How often to poll the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | RemotePersister | A reference to the new |
A RemotePersister
only supports regular Store
objects, and cannot be used to persist the metadata of a MergeableStore
.
As well as providing a reference to the Store
to persist, you must provide loadUrl
and saveUrl
parameters. These identify the endpoints of the server that support the GET
method (to fetch the Store
JSON to load) and the POST
method (to send the Store
JSON to save) respectively.
For when you choose to enable automatic loading for the Persister
(with the startAutoLoad
method), it will poll the loadUrl for changes, using the ETag
HTTP header to identify if things have changed. The autoLoadIntervalSeconds
method is used to indicate how often to do this.
If you are implementing the server portion of this functionality yourself, remember to ensure that the ETag
header changes every time the server's Store
content does - otherwise changes will not be detected by the client.
Example
This example creates a RemotePersister
object and persists the Store
to a remote server.
import {createRemotePersister} from 'tinybase/persisters/persister-remote';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createRemotePersister(
store,
'https://example.com/load',
'https://example.com/save',
5,
);
await persister.save();
// Store JSON will be sent to server in a POST request.
await persister.load();
// Store JSON will be fetched from server with a GET request.
persister.destroy();
Since
v1.0.0
persister-sqlite-wasm
The persister-sqlite-wasm
module of the TinyBase project lets you save and load Store
data to and from a local SQLite database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.0.0
Interfaces
There is one interface, SqliteWasmPersister
, within the persister-sqlite-wasm
module.
SqliteWasmPersister
The SqliteWasmPersister
interface represents a Persister
that lets you save and load Store
data to and from a local SQLite database.
You should use the createSqliteWasmPersister
function to create a SqliteWasmPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getDb
method for accessing a reference to the database instance the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the SqliteWasmPersister
interface. There are only two getter methods, getStore
and getDb
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store | MergeableStore
returns | Store | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb
method returns a reference to the database instance the Store
is being persisted to.
getDb(): any
returns | any | A reference to the database instance. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the database instance back out again.
import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';
import {createStore} from 'tinybase';
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(
store,
sqlite3,
db,
'my_tinybase',
);
console.log(persister.getDb() == db);
// -> true
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the SqliteWasmPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the SqliteWasmPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<SqliteWasmPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<SqliteWasmPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the SqliteWasmPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<SqliteWasmPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<SqliteWasmPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the SqliteWasmPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<SqliteWasmPersister>
returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<SqliteWasmPersister>
returns | Promise<SqliteWasmPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the SqliteWasmPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createSqliteWasmPersister
, within the persister-sqlite-wasm
module.
createSqliteWasmPersister
The createSqliteWasmPersister
function creates a SqliteWasmPersister
object that can persist the Store
to a local SQLite database.
createSqliteWasmPersister(
store: Store | MergeableStore,
sqlite3: any,
db: any,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): SqliteWasmPersister
Type | Description | |
---|---|---|
store | Store | MergeableStore | The |
sqlite3 | any | The WASM module that was returned from |
db | any | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | SqliteWasmPersister | A reference to the new |
A SqliteWasmPersister
supports regular Store
objects, and can also be used to persist the metadata of a MergeableStore
when using the JSON serialization mode, as described below.
As well as providing a reference to the Store
to persist, you must provide sqlite3
and db
parameters which identify the WASM module and database instance respectively.
A database Persister
uses one of two modes: either a JSON serialization of the whole Store
stored in a single row of a table (the default), or a tabular mapping of Table
Ids
to database table names and vice-versa).
The fourth argument is a DatabasePersisterConfig
object that configures which of those modes to use, and settings for each. If the fourth argument is simply a string, it is used as the storeTableName
property of the JSON serialization.
See the documentation for the DpcJson
and DpcTabular
types for more information on how both of those modes can be configured.
Examples
This example creates a SqliteWasmPersister
object and persists the Store
to a local SQLite database as a JSON serialization into the my_tinybase
table. It makes a change to the database directly and then reloads it back into the Store
.
import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';
import {createStore} from 'tinybase';
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(
store,
sqlite3,
db,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log(db.exec('SELECT * FROM my_tinybase;', {rowMode: 'object'}));
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
db.exec(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a SqliteWasmPersister
object and persists the Store
to a local SQLite database with tabular mapping.
import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';
import {createStore} from 'tinybase';
import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(store, sqlite3, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(db.exec('SELECT * FROM pets;', {rowMode: 'object'}));
// -> [{_id: 'fido', species: 'dog'}]
db.exec(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.0.0
persister-sqlite3
The persister-sqlite3
module of the TinyBase project lets you save and load Store
data to and from a local SQLite database (in an appropriate environment).
See also
Database Persistence guide
Since
v4.0.0
Interfaces
There is one interface, Sqlite3Persister
, within the persister-sqlite3
module.
Sqlite3Persister
The Sqlite3Persister
interface represents a Persister
that lets you save and load Store
data to and from a local SQLite database.
You should use the createSqlite3Persister
function to create a Sqlite3Persister
object.
It is a minor extension to the Persister
interface and simply provides an extra getDb
method for accessing a reference to the database instance the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the Sqlite3Persister
interface. There are only two getter methods, getStore
and getDb
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store | MergeableStore
returns | Store | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getDb
The getDb
method returns a reference to the database instance the Store
is being persisted to.
getDb(): Database
returns | Database | A reference to the database instance. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the database instance back out again.
import {Database} from 'sqlite3';
import {createSqlite3Persister} from 'tinybase/persisters/persister-sqlite3';
import {createStore} from 'tinybase';
const db = new Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqlite3Persister(store, db, 'my_tinybase');
console.log(persister.getDb() == db);
// -> true
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the Sqlite3Persister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOrMergeableStore>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the Sqlite3Persister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<Sqlite3Persister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<Sqlite3Persister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the Sqlite3Persister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<Sqlite3Persister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<Sqlite3Persister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the Sqlite3Persister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<Sqlite3Persister>
returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<Sqlite3Persister>
returns | Promise<Sqlite3Persister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the Sqlite3Persister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createSqlite3Persister
, within the persister-sqlite3
module.
createSqlite3Persister
The createSqlite3Persister
function creates a Sqlite3Persister
object that can persist the Store
to a local SQLite database.
createSqlite3Persister(
store: Store | MergeableStore,
db: Database,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, params?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Sqlite3Persister
Type | Description | |
---|---|---|
store | Store | MergeableStore | The |
db | Database | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, params?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | Sqlite3Persister | A reference to the new |
A Sqlite3Persister
supports regular Store
objects, and can also be used to persist the metadata of a MergeableStore
when using the JSON serialization mode, as described below.
As well as providing a reference to the Store
to persist, you must provide a db
parameter which identifies the database instance.
A database Persister
uses one of two modes: either a JSON serialization of the whole Store
stored in a single row of a table (the default), or a tabular mapping of Table
Ids
to database table names and vice-versa).
The third argument is a DatabasePersisterConfig
object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName
property of the JSON serialization.
See the documentation for the DpcJson
and DpcTabular
types for more information on how both of those modes can be configured.
Examples
This example creates a Sqlite3Persister
object and persists the Store
to a local SQLite database as a JSON serialization into the my_tinybase
table. It makes a change to the database directly and then reloads it back into the Store
.
import {Database} from 'sqlite3';
import {createSqlite3Persister} from 'tinybase/persisters/persister-sqlite3';
import {createStore} from 'tinybase';
const db = new Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqlite3Persister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(
await new Promise((resolve) =>
db.all('SELECT * FROM my_tinybase;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await new Promise((resolve) =>
db.all(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a Sqlite3Persister
object and persists the Store
to a local SQLite database with tabular mapping.
import {Database} from 'sqlite3';
import {createSqlite3Persister} from 'tinybase/persisters/persister-sqlite3';
import {createStore} from 'tinybase';
const db = new Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqlite3Persister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(
await new Promise((resolve) =>
db.all('SELECT * FROM pets;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: 'fido', species: 'dog'}]
await new Promise((resolve) =>
db.all(
`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.0.0
persister-yjs
The persister-yjs
module of the TinyBase project provides a way to save and load Store
data to and from a Yjs document.
A single entry point, the createYjsPersister
function, is provided, which returns a new Persister
object that can bind a Store
to a provided Yjs document.
See also
Since
v4.0.0
Interfaces
There is one interface, YjsPersister
, within the persister-yjs
module.
YjsPersister
The YjsPersister
interface represents a Persister
that lets you save and load Store
data to and from a Yjs document.
You should use the createYjsPersister
function to create a YjsPersister
object.
It is a minor extension to the Persister
interface and simply provides an extra getYDoc
method for accessing the Yjs document the Store
is being persisted to.
Since
v4.3.14
Getter methods
This is the collection of getter methods within the YjsPersister
interface. There are only two getter methods, getStore
and getYDoc
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): Store
returns | Store | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getYDoc
The getYDoc
method returns the Yjs document the Store
is being persisted to.
getYDoc(): Doc
returns | Doc | The Yjs document. |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets the Yjs document back out again.
import {Doc} from 'yjs';
import {createStore} from 'tinybase';
import {createYjsPersister} from 'tinybase/persisters/persister-yjs';
const doc = new Doc();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createYjsPersister(store, doc);
console.log(persister.getYDoc() == doc);
// -> true
persister.destroy();
Since
v4.3.14
Listener methods
This is the collection of listener methods within the YjsPersister
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<StoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<StoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the YjsPersister
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<YjsPersister>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<YjsPersister> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the YjsPersister
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<YjsPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<YjsPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<YjsPersister>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<YjsPersister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the YjsPersister
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<YjsPersister>
returns | Promise<YjsPersister> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<YjsPersister>
returns | Promise<YjsPersister> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Development methods
This is the collection of development methods within the YjsPersister
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createYjsPersister
, within the persister-yjs
module.
createYjsPersister
The createYjsPersister
function creates a YjsPersister
object that can persist the Store
to a Yjs document.
createYjsPersister(
store: Store,
yDoc: Doc,
yMapName?: string,
onIgnoredError?: (error: any) => void,
): YjsPersister
Type | Description | |
---|---|---|
store | Store | The |
yDoc | Doc | The Yjs document to persist the |
yMapName? | string | The name of the Y.Map used inside the Yjs document to sync the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | YjsPersister | A reference to the new |
A YjsPersister
only supports regular Store
objects, and cannot be used to persist the metadata of a MergeableStore
.
As well as providing a reference to the Store
to persist, you must provide the Yjs document to persist it to.
Examples
This example creates a YjsPersister
object and persists the Store
to a Yjs document.
import {Doc} from 'yjs';
import {createStore} from 'tinybase';
import {createYjsPersister} from 'tinybase/persisters/persister-yjs';
const doc = new Doc();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createYjsPersister(store, doc);
await persister.save();
// Store will be saved to the document.
console.log(doc.toJSON());
// -> {tinybase: {t: {pets: {fido: {species: 'dog'}}}, v: {}}}
persister.destroy();
This more complex example uses Yjs updates to keep two Store
objects (each with their own YjsPersister
objects and Yjs documents) in sync with each other. We use the await
keyword extensively for the purpose of ensuring sequentiality in this example.
Typically, real-world synchronization would happen between two systems via a Yjs connection provider. Here, we synthesize that with the syncDocs
function.
import {Doc, applyUpdate, encodeStateAsUpdate} from 'yjs';
import {createStore} from 'tinybase';
import {createYjsPersister} from 'tinybase/persisters/persister-yjs';
const doc1 = new Doc();
const doc2 = new Doc();
// A function to manually synchronize documents with each other. Typically
// this would happen over the wire, via a Yjs connection provider.
const syncDocs = async () => {
applyUpdate(doc1, encodeStateAsUpdate(doc2));
applyUpdate(doc2, encodeStateAsUpdate(doc1));
};
// Bind a persisted Store to each document.
const store1 = createStore();
const persister1 = createYjsPersister(store1, doc1);
await persister1.startAutoLoad();
await persister1.startAutoSave();
const store2 = createStore();
const persister2 = createYjsPersister(store2, doc2);
await persister2.startAutoLoad();
await persister2.startAutoSave();
// Synchronize the documents in their initial state.
await syncDocs();
// Make a change to each of the two Stores.
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setValues({open: true});
// ...
// Synchronize the documents with each other again.
await syncDocs();
// Ensure the Stores are in sync.
console.log(store1.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
console.log(store2.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
persister1.destroy();
persister2.destroy();
Since
v4.0.0
synchronizers
The synchronizers
module of the TinyBase project lets you synchronize MergeableStore
data to and from other MergeableStore
instances.
See also
Synchronization guide
Since
v5.0.0
Interfaces
There is one interface, Synchronizer
, within the synchronizers
module.
Synchronizer
A Synchronizer
object lets you synchronize MergeableStore
data with another TinyBase client or system.
This is useful for sharing data between users, or between devices of a single user. This is especially valuable when there is the possibility that there has been a lack of immediate connectivity between clients and the synchronization requires some negotiation to orchestrate merging the MergeableStore
objects together.
Creating a Synchronizer
depends on the choice of underlying medium over which the synchronization will take place. Options include the createWsSynchronizer
function (for a Synchronizer
that will sync over web-sockets), and the createLocalSynchronizer
function (for a Synchronizer
that will sync two MergeableStore
objects in memory on one system). The createCustomSynchronizer
function can also be used to easily create a fully customized way to send and receive the messages of the synchronization protocol.
Note that, as an interface, it is an extension to the Persister
interface, since they share underlying implementations. Think of a Synchronizer
as 'persisting' your MergeableStore
to another client (and vice-versa).
Example
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
Getter methods
This is the collection of getter methods within the Synchronizer
interface. There is only one method, getStore
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): MergeableStore
returns | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
Listener methods
This is the collection of listener methods within the Synchronizer
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the Synchronizer
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<Synchronizer>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<Synchronizer> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the Synchronizer
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<Synchronizer>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<Synchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<Synchronizer>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<Synchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the Synchronizer
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<Synchronizer>
returns | Promise<Synchronizer> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<Synchronizer>
returns | Promise<Synchronizer> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Synchronization methods
This is the collection of synchronization methods within the Synchronizer
interface. There are only three synchronization methods, getSynchronizerStats
, startSync
, and stopSync
.
getSynchronizerStats
The getSynchronizerStats
method provides a set of statistics about the Synchronizer
, and is used for debugging purposes.
getSynchronizerStats(): SynchronizerStats
returns | SynchronizerStats | A |
---|
The SynchronizerStats
object contains a count of the number of times the Synchronizer
has sent and received messages.
The method is intended to be used during development to ensure your synchronization layer is acting as expected, for example.
Example
This example gets the send and receive statistics of two active Synchronizer
instances.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(synchronizer1.getSynchronizerStats());
// -> {receives: 4, sends: 5}
console.log(synchronizer2.getSynchronizerStats());
// -> {receives: 5, sends: 4}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
startSync
The startSync
method is used to start the process of synchronization between this instance and another matching Synchronizer
.
startSync(initialContent?: Content): Promise<Synchronizer>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<Synchronizer> | A Promise containing a reference to the |
The underlying implementation of a Synchronizer
is shared with the Persister
framework, and so this startSync
method is equivalent to starting both auto-loading (listening to sync messages from other active Synchronizer
instances) and auto-saving (sending sync messages to it).
This method is asynchronous so you should you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts synchronizing them with default content. The default content from the first Synchronizer
's startSync
method ends up populated in both MergeableStore
instances: by the time the second started, the first was available to synchronize with and its default was not needed.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync([{pets: {fido: {species: 'dog'}}}, {}]);
await synchronizer2.startSync([{pets: {felix: {species: 'cat'}}}, {}]);
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
stopSync
The stopSync
method is used to stop the process of synchronization between this instance and another matching Synchronizer
.
stopSync(): this
returns | this | A reference to the |
---|
The underlying implementation of a Synchronizer
is shared with the Persister
framework, and so this startSync
method is equivalent to stopping both auto-loading (listening to sync messages from other active Synchronizer
instances) and auto-saving (sending sync messages to them).
Example
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts - then stops - synchronizing them. Subsequent changes are not merged.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
await synchronizer1.startSync();
const store2 = createMergeableStore();
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.stopSync();
synchronizer2.stopSync();
store1.setCell('pets', 'fido', 'color', 'brown');
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
Development methods
This is the collection of development methods within the Synchronizer
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Enumerations
There is one enumeration, Message
, within the synchronizers
module.
Message
The Message
enum is used to indicate the type of the message being passed between Synchronizer
instances.
{
Response: 0;
GetContentHashes: 1;
ContentHashes: 2;
ContentDiff: 3;
GetTableDiff: 4;
GetRowDiff: 5;
GetCellDiff: 6;
GetValueDiff: 7;
}
Value | Description | |
---|---|---|
Response | 0 | A message that is a response to a previous request. |
GetContentHashes | 1 | A message that is a request to get |
ContentHashes | 2 | A message that contains |
ContentDiff | 3 | A message that contains a ContentDiff. |
GetTableDiff | 4 | A message that is a request to get a TableDiff from another |
GetRowDiff | 5 | A message that is a request to get a RowDiff from another |
GetCellDiff | 6 | A message that is a request to get a CellDiff from another |
GetValueDiff | 7 | A message that is a request to get a ValueDiff from another |
These message comprise the basic synchronization protocol for merging MergeableStore
instances across multiple systems.
The enum is generally intended to be used internally within TinyBase itself and opaque to applications that use synchronization.
Since
v5.0.0
Functions
There is one function, createCustomSynchronizer
, within the synchronizers
module.
createCustomSynchronizer
The createCustomSynchronizer
function creates a Synchronizer
object that can persist one MergeableStore
to another.
createCustomSynchronizer(
store: MergeableStore,
send: Send,
registerReceive: (receive: Receive) => void,
destroy: () => void,
requestTimeoutSeconds: number,
onSend?: Send,
onReceive?: Receive,
onIgnoredError?: (error: any) => void,
): Synchronizer
Type | Description | |
---|---|---|
store | MergeableStore | The |
send | Send | A |
registerReceive | (receive: Receive) => void | A callback (called once when the |
destroy | () => void | A function called when destroying the |
requestTimeoutSeconds | number | An number of seconds before a request sent from this |
onSend? | Send | An optional handler for the messages that this |
onReceive? | Receive | An optional handler for the messages that this |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | Synchronizer | A reference to the new |
As well as providing a reference to the MergeableStore
to synchronize, you must provide parameters which identify how to send and receive changes to and from this MergeableStore
and its peers. This is entirely dependent upon the medium of communication used.
You must also provide a callback for when the Synchronizer
is destroyed (which is a good place to clean up resources and stop communication listeners), and indicate how long the Synchronizer
will wait for responses to message requests before timing out.
A final set of optional handlers can be provided to help debug sends, receives, and errors respectively.
Example
This example creates a function for creating custom Synchronizer
objects via a very naive pair of message buses (which are first-in, first-out). Each Synchronizer
can write to the other's bus, and they each poll to read from their own. The example then uses these Synchronizer
instances to sync two MergeableStore
objects together
import {createMergeableStore, getUniqueId} from 'tinybase';
import {createCustomSynchronizer} from 'tinybase/synchronizers';
const bus1 = [];
const bus2 = [];
const createBusSynchronizer = (store, localBus, remoteBus) => {
let timer;
const clientId = getUniqueId();
return createCustomSynchronizer(
store,
(toClientId, requestId, message, body) => {
// send
remoteBus.push([clientId, requestId, message, body]);
},
(receive) => {
// registerReceive
timer = setInterval(() => {
if (localBus.length > 0) {
receive(...localBus.shift());
}
}, 1);
},
() => clearInterval(timer), // destroy
1,
);
};
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createBusSynchronizer(store1, bus1, bus2);
const synchronizer2 = createBusSynchronizer(store2, bus2, bus1);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
Type Aliases
These are the type aliases within the synchronizers
module.
Synchronization type aliases
This is the collection of synchronization type aliases within the synchronizers
module. There are only two synchronization type aliases, Receive
and Send
.
Receive
The Receive
type describes a function that knows how to handle the arrival of a message as part of the synchronization protocol.
(
fromClientId: Id,
requestId: IdOrNull,
message: Message,
body: any,
): void
Type | Description | |
---|---|---|
fromClientId | Id | The |
requestId | IdOrNull | The optional |
message | Message | A number that indicates the type of the message, according to the |
body | any | A message-specific payload. |
returns | void | This has no return value. |
When a message arrives (most likely from another system), the function will be called with parameters that indicate where the message came from, and its meaning and content.
Since
v5.0.0
Send
The Send
type describes a function that knows how to dispatch a message as part of the synchronization protocol.
(
toClientId: IdOrNull,
requestId: IdOrNull,
message: Message,
body: any,
): void
Type | Description | |
---|---|---|
toClientId | IdOrNull | The optional |
requestId | IdOrNull | The optional |
message | Message | A number that indicates the type of the message, according to the |
body | any | A message-specific payload. |
returns | void | This has no return value. |
Since
v5.0.0
Development type aliases
This is the collection of development type aliases within the synchronizers
module. There is only one type alias, SynchronizerStats
.
SynchronizerStats
The SynchronizerStats
type describes the number of times a Synchronizer
object has sent or received data.
{
sends: number;
receives: number;
}
Type | Description | |
---|---|---|
sends | number | The number of times messages have been sent. |
receives | number | The number of times messages has been received. |
A SynchronizerStats
object is returned from the getSynchronizerStats
method.
Since
v5.0.0
synchronizer-broadcast-channel
The synchronizer-broadcast-channel
module of the TinyBase project lets you synchronize MergeableStore
data to and from other MergeableStore
instances via a browser's BroadcastChannel API.
See also
Synchronization guide
Since
v5.0.0
Interfaces
There is one interface, BroadcastChannelSynchronizer
, within the synchronizer-broadcast-channel
module.
BroadcastChannelSynchronizer
The BroadcastChannelSynchronizer
interface represents a Synchronizer
that lets you synchronize MergeableStore
data to and from other MergeableStore
instances via a browser's BroadcastChannel API.
You should use the createBroadcastChannelSynchronizer
function to create a BroadcastChannelSynchronizer
object.
It is a minor extension to the Synchronizer
interface and simply provides an extra getChannelName
method for accessing the name of the channel being used.
Since
v5.0.0
Getter methods
This is the collection of getter methods within the BroadcastChannelSynchronizer
interface. There are only two getter methods, getStore
and getChannelName
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): MergeableStore
returns | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getChannelName
The getChannelName
method returns the name of the channel being used for synchronization.
getChannelName(): string
returns | string | The channel name. |
---|
Example
This example creates a BroadcastChannelSynchronizer
object for a newly-created MergeableStore
and then gets the channel name back out again.
import {createBroadcastChannelSynchronizer} from 'tinybase/synchronizers/synchronizer-broadcast-channel';
import {createMergeableStore} from 'tinybase';
const store = createMergeableStore();
const synchronizer = createBroadcastChannelSynchronizer(store, 'channelA');
console.log(synchronizer.getChannelName());
// -> 'channelA'
synchronizer.destroy();
Since
v5.0.0
Listener methods
This is the collection of listener methods within the BroadcastChannelSynchronizer
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the BroadcastChannelSynchronizer
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<BroadcastChannelSynchronizer>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<BroadcastChannelSynchronizer> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the BroadcastChannelSynchronizer
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<BroadcastChannelSynchronizer>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<BroadcastChannelSynchronizer>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the BroadcastChannelSynchronizer
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<BroadcastChannelSynchronizer>
returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<BroadcastChannelSynchronizer>
returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Synchronization methods
This is the collection of synchronization methods within the BroadcastChannelSynchronizer
interface. There are only three synchronization methods, getSynchronizerStats
, startSync
, and stopSync
.
getSynchronizerStats
The getSynchronizerStats
method provides a set of statistics about the Synchronizer
, and is used for debugging purposes.
getSynchronizerStats(): SynchronizerStats
returns | SynchronizerStats | A |
---|
The SynchronizerStats
object contains a count of the number of times the Synchronizer
has sent and received messages.
The method is intended to be used during development to ensure your synchronization layer is acting as expected, for example.
Example
This example gets the send and receive statistics of two active Synchronizer
instances.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(synchronizer1.getSynchronizerStats());
// -> {receives: 4, sends: 5}
console.log(synchronizer2.getSynchronizerStats());
// -> {receives: 5, sends: 4}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
startSync
The startSync
method is used to start the process of synchronization between this instance and another matching Synchronizer
.
startSync(initialContent?: Content): Promise<BroadcastChannelSynchronizer>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<BroadcastChannelSynchronizer> | A Promise containing a reference to the |
The underlying implementation of a Synchronizer
is shared with the Persister
framework, and so this startSync
method is equivalent to starting both auto-loading (listening to sync messages from other active Synchronizer
instances) and auto-saving (sending sync messages to it).
This method is asynchronous so you should you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts synchronizing them with default content. The default content from the first Synchronizer
's startSync
method ends up populated in both MergeableStore
instances: by the time the second started, the first was available to synchronize with and its default was not needed.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync([{pets: {fido: {species: 'dog'}}}, {}]);
await synchronizer2.startSync([{pets: {felix: {species: 'cat'}}}, {}]);
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
stopSync
The stopSync
method is used to stop the process of synchronization between this instance and another matching Synchronizer
.
stopSync(): this
returns | this | A reference to the |
---|
The underlying implementation of a Synchronizer
is shared with the Persister
framework, and so this startSync
method is equivalent to stopping both auto-loading (listening to sync messages from other active Synchronizer
instances) and auto-saving (sending sync messages to them).
Example
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts - then stops - synchronizing them. Subsequent changes are not merged.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
await synchronizer1.startSync();
const store2 = createMergeableStore();
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.stopSync();
synchronizer2.stopSync();
store1.setCell('pets', 'fido', 'color', 'brown');
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
Development methods
This is the collection of development methods within the BroadcastChannelSynchronizer
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createBroadcastChannelSynchronizer
, within the synchronizer-broadcast-channel
module.
createBroadcastChannelSynchronizer
The createBroadcastChannelSynchronizer
function creates a BroadcastChannelSynchronizer
object that can synchronize MergeableStore
data to and from other MergeableStore
instances via a browser's BroadcastChannel API.
createBroadcastChannelSynchronizer(
store: MergeableStore,
channelName: string,
onSend?: Send,
onReceive?: Receive,
onIgnoredError?: (error: any) => void,
): BroadcastChannelSynchronizer
Type | Description | |
---|---|---|
store | MergeableStore | The |
channelName | string | The name of the channel to use. |
onSend? | Send | An optional handler for the messages that this |
onReceive? | Receive | An optional handler for the messages that this |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | BroadcastChannelSynchronizer | A reference to the new |
As well as providing a reference to the MergeableStore
to persist, you must provide a channel name, used by all the browser tabs, workers, or contexts that need to synchronize together.
A final set of optional handlers can be provided to help debug sends, receives, and errors respectively.
Example
This example creates two BroadcastChannelSynchronizer
objects to synchronize one MergeableStore
to another.
import {createBroadcastChannelSynchronizer} from 'tinybase/synchronizers/synchronizer-broadcast-channel';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createBroadcastChannelSynchronizer(
store1,
'channelA',
);
const synchronizer2 = createBroadcastChannelSynchronizer(
store2,
'channelA',
);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
synchronizer-local
The synchronizer-local
module of the TinyBase project lets you synchronize MergeableStore
data to and from other MergeableStore
instances on the same local machine.
See also
Synchronization guide
Since
v5.0.0
Interfaces
There is one interface, LocalSynchronizer
, within the synchronizer-local
module.
LocalSynchronizer
The LocalSynchronizer
interface represents a Synchronizer
that lets you synchronize MergeableStore
data to and from other MergeableStore
instances on the same local machine.
You should use the createLocalSynchronizer
function to create a LocalSynchronizer
object.
Having
no specialized methods, it is a synonym for the Synchronizer
interface. This is also something of a showcase Synchronizer
, rather than something you would use in a production environment. If you do need to synchronize two in-memory MergeableStore
instances, you may prefer to use the merge function on either one of them instead of going to the effort of setting up this Synchronizer
.
Since
v5.0.0
Getter methods
This is the collection of getter methods within the LocalSynchronizer
interface. There is only one method, getStore
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): MergeableStore
returns | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
Listener methods
This is the collection of listener methods within the LocalSynchronizer
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the LocalSynchronizer
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<LocalSynchronizer>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<LocalSynchronizer> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the LocalSynchronizer
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<LocalSynchronizer>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<LocalSynchronizer>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the LocalSynchronizer
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<LocalSynchronizer>
returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<LocalSynchronizer>
returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Synchronization methods
This is the collection of synchronization methods within the LocalSynchronizer
interface. There are only three synchronization methods, getSynchronizerStats
, startSync
, and stopSync
.
getSynchronizerStats
The getSynchronizerStats
method provides a set of statistics about the Synchronizer
, and is used for debugging purposes.
getSynchronizerStats(): SynchronizerStats
returns | SynchronizerStats | A |
---|
The SynchronizerStats
object contains a count of the number of times the Synchronizer
has sent and received messages.
The method is intended to be used during development to ensure your synchronization layer is acting as expected, for example.
Example
This example gets the send and receive statistics of two active Synchronizer
instances.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(synchronizer1.getSynchronizerStats());
// -> {receives: 4, sends: 5}
console.log(synchronizer2.getSynchronizerStats());
// -> {receives: 5, sends: 4}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
startSync
The startSync
method is used to start the process of synchronization between this instance and another matching Synchronizer
.
startSync(initialContent?: Content): Promise<LocalSynchronizer>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<LocalSynchronizer> | A Promise containing a reference to the |
The underlying implementation of a Synchronizer
is shared with the Persister
framework, and so this startSync
method is equivalent to starting both auto-loading (listening to sync messages from other active Synchronizer
instances) and auto-saving (sending sync messages to it).
This method is asynchronous so you should you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts synchronizing them with default content. The default content from the first Synchronizer
's startSync
method ends up populated in both MergeableStore
instances: by the time the second started, the first was available to synchronize with and its default was not needed.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync([{pets: {fido: {species: 'dog'}}}, {}]);
await synchronizer2.startSync([{pets: {felix: {species: 'cat'}}}, {}]);
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
stopSync
The stopSync
method is used to stop the process of synchronization between this instance and another matching Synchronizer
.
stopSync(): this
returns | this | A reference to the |
---|
The underlying implementation of a Synchronizer
is shared with the Persister
framework, and so this startSync
method is equivalent to stopping both auto-loading (listening to sync messages from other active Synchronizer
instances) and auto-saving (sending sync messages to them).
Example
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts - then stops - synchronizing them. Subsequent changes are not merged.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
await synchronizer1.startSync();
const store2 = createMergeableStore();
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.stopSync();
synchronizer2.stopSync();
store1.setCell('pets', 'fido', 'color', 'brown');
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
Development methods
This is the collection of development methods within the LocalSynchronizer
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createLocalSynchronizer
, within the synchronizer-local
module.
createLocalSynchronizer
The createLocalSynchronizer
function creates a LocalSynchronizer
object that can synchronize MergeableStore
data to and from other MergeableStore
instances on the same local machine.
createLocalSynchronizer(
store: MergeableStore,
onSend?: Send,
onReceive?: Receive,
onIgnoredError?: (error: any) => void,
): LocalSynchronizer
Type | Description | |
---|---|---|
store | MergeableStore | The |
onSend? | Send | An optional handler for the messages that this |
onReceive? | Receive | An optional handler for the messages that this |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | LocalSynchronizer | A reference to the new |
This is something of a showcase Synchronizer
, rather than something you would use in a production environment. If you do need to synchronize two in-memory MergeableStore
instances, you may prefer to use the merge function on either one of them instead of going to the effort of setting up this Synchronizer
.
As well as providing a reference to the MergeableStore
to persist, a final set of optional handlers can be provided to help debug sends, receives, and errors respectively.
Example
This example creates two LocalSynchronizer
objects to synchronize one MergeableStore
to another.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
synchronizer-ws-client
The synchronizer-ws module of the TinyBase project lets you synchronize MergeableStore
data to and from other MergeableStore
instances via WebSockets facilitated by a server.
See also
- Synchronization guide
- Todo App v6 (collaboration) demo
Since
v5.0.0
Interfaces
There is one interface, WsSynchronizer
, within the synchronizer-ws-client
module.
WsSynchronizer
The WsSynchronizer
interface represents a Synchronizer
that lets you synchronize MergeableStore
data to and from other MergeableStore
instances via WebSockets facilitated by a server.
You should use the createWsSynchronizer
function to create a WsSynchronizer
object.
It is a minor extension to the Synchronizer
interface and simply provides an extra getWebSocket
method for accessing a reference to the WebSocket being used.
Since
v5.0.0
Getter methods
This is the collection of getter methods within the WsSynchronizer
interface. There are only two getter methods, getStore
and getWebSocket
.
getStore
The getStore
method returns a reference to the underlying Store
or MergeableStore
that is backing this Persister
object.
getStore(): MergeableStore
returns | MergeableStore | A reference to the |
---|
Example
This example creates a Persister
object against a newly-created Store
and then gets its reference in order to update its data.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
getWebSocket
The getWebSocket
method returns a reference to the WebSocket being used for synchronization.
getWebSocket(): WebSocketType
returns | WebSocketType | The WebSocket reference. |
---|
Example
This example creates a server and WsSynchronizer
object for a newly-created MergeableStore
and then gets the WebSocket reference back out again.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server = createWsServer(new WebSocketServer({port: 8046}));
const store = createMergeableStore();
const webSocket = new WebSocket('ws://localhost:8046');
const synchronizer = await createWsSynchronizer(store, webSocket);
console.log(synchronizer.getWebSocket() == webSocket);
// -> true
synchronizer.destroy();
server.destroy();
Since
v5.0.0
Listener methods
This is the collection of listener methods within the WsSynchronizer
interface. There are only two listener methods, addStatusListener
and delListener
.
addStatusListener
The addStatusListener
method registers a listener function with the Persister
that will be called whenever it starts or stops loading or saving.
addStatusListener(listener: StatusListener<MergeableStoreOnly>): string
Type | Description | |
---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the |
returns | string | A unique |
The provided listener is a StatusListener
function, and will be called with a reference to the Persister
and the new Status
: 0 means now idle, 1 means now loading, and 2 means now saving.
Example
This example registers a listener that responds to changes in the state of the Persister
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((persister, status) => {
console.log(
`${persister.getStorageName()} persister status changed to ${status}`,
);
});
await persister.load();
// -> 'pets persister status changed to 1'
// -> 'pets persister status changed to 0'
await persister.save();
// -> 'pets persister status changed to 2'
// -> 'pets persister status changed to 0'
persister.delListener(listenerId);
Since
v5.3.0
delListener
The delListener
method removes a listener that was previously added to the Persister
.
delListener(listenerId: string): this
Type | Description | |
---|---|---|
listenerId | string | The |
returns | this | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the Persister
may re-use this Id
for future listeners added to it.
Example
This example registers a listener and then removes it.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
const listenerId = persister.addStatusListener((_persister, status) => {
console.log(`Status changed to ${status}`);
});
await persister.load();
// -> `Status changed to 1`
// -> `Status changed to 0`
persister.delListener(listenerId);
await persister.load();
// -> undefined
// The listener is not called.
Since
v5.3.0
Lifecycle methods
This is the collection of lifecycle methods within the WsSynchronizer
interface. There are only three lifecycle methods, destroy
, getStatus
, and schedule
.
destroy
The destroy
method should be called when this Persister
object is no longer used.
destroy(): this
returns | this |
---|
This guarantees that all of the listeners that the object registered with the underlying Store
and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad
method and the stopAutoSave
method in succession.
Example
This example creates a Store
, associates a Persister
object with it (that registers a TablesListener
with the underlying Store
), and then destroys it again, removing the listener.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v1.0.0
getStatus
The getStatus
method lets you find out if the Persister
is currently in the process of loading or saving content.
getStatus(): Status
It can only be doing one or the other (or neither) at any given time. The Status
enum is returned, where 0 means idle, 1 means loading, and 2 means saving.
This method is only likely to be useful for Persister
implementations that have asynchronous load or save operations. The status for synchronous persister media (such as browser local or session storage) will switch back to idle before you are able to query it.
Example
This example creates a Persister
and queries its status.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.getStatus());
// -> 0
Since
v5.3.0
schedule
The schedule
method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: (() => Promise<any>)[]): Promise<WsSynchronizer<WebSocketType>>
Type | Description | |
---|---|---|
actions | (() => Promise<any>)[] | One or many functions which will be scheduled, and which can be asynchronous. |
returns | Promise<WsSynchronizer<WebSocketType>> | A reference to the |
For example, a database Persister
may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted
implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister
.
Example
This example creates a custom Persister
object against a newly-created Store
and then sequences two tasks in order to update its data on a hypothetical remote system.
import {
checkRemoteSystemIsReady,
getDataFromRemoteSystem,
sendDataToRemoteSystem,
} from 'custom-remote-handlers';
import {createCustomPersister} from 'tinybase/persisters';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the WsSynchronizer
interface. There are 4 load methods in total.
isAutoLoading
The isAutoLoading
method lets you find out if the Persister
is currently automatically loading its content.
isAutoLoading(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoLoading.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoLoading());
// -> false
await persister.startAutoLoad();
console.log(persister.isAutoLoading());
// -> true
await persister.stopAutoLoad();
console.log(persister.isAutoLoading());
// -> false
Since
v5.0.0
load
The load
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once.
load(initialContent?: Content): Promise<WsSynchronizer<WebSocketType>>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store
, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store
, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load
method is called, data has previously been persisted and instead, that is loaded.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
Since
v1.0.0
startAutoLoad
The startAutoLoad
method gets persisted data from storage, and loads it into the Store
with which the Persister
is associated, once, and then continuously.
startAutoLoad(initialContent?: Content): Promise<WsSynchronizer<WebSocketType>>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial content for the Store
will be if there is nothing currently persisted or if the load fails (for example when the Persister
is remote and the environment is offline). This allows you to fallback or instantiate a Store
whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store
.
This method is asynchronous because it starts by making a single call to the asynchronous load
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store
, and loads data into it from the browser's session storage, which at first is empty (so the initialTables
parameter is used). Subsequent changes to the underlying storage are then reflected in the Store
(in this case through detection of StorageEvents from session storage changes made in another browser tab).
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad([{pets: {fido: {species: 'dog'}}}, {}]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
stopAutoLoad
The stopAutoLoad
method stops the automatic loading of data from storage previously started with the startAutoLoad
method.
stopAutoLoad(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store
, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store
.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Save methods
This is the collection of save methods within the WsSynchronizer
interface. There are 4 save methods in total.
isAutoSaving
The isAutoSaving
method lets you find out if the Persister
is currently automatically saving its content.
isAutoSaving(): boolean
returns | boolean | A boolean indicating whether the |
---|
Example
This example creates a Persister
and queries whether it is autoSaving.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const persister = createSessionPersister(createStore(), 'pets');
console.log(persister.isAutoSaving());
// -> false
await persister.startAutoSave();
console.log(persister.isAutoSaving());
// -> true
await persister.stopAutoSave();
console.log(persister.isAutoSaving());
// -> false
Since
v5.0.0
save
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once.
save(): Promise<WsSynchronizer<WebSocketType>>
returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
---|
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
startAutoSave
The save
method takes data from the Store
with which the Persister
is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<WsSynchronizer<WebSocketType>>
returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
---|
This method first runs a single call to the save
method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store
, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save
method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await
calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
Since
v1.0.0
stopAutoSave
The stopAutoSave
method stops the automatic save of data to storage previously started with the startAutoSave
method.
stopAutoSave(): this
returns | this | A reference to the |
---|
If the Persister
is not currently set to automatically save, this method has no effect.
Example
This example creates a Store
with some data, and saves into the browser's session storage. Subsequent changes to the Store
are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Since
v1.0.0
Synchronization methods
This is the collection of synchronization methods within the WsSynchronizer
interface. There are only three synchronization methods, getSynchronizerStats
, startSync
, and stopSync
.
getSynchronizerStats
The getSynchronizerStats
method provides a set of statistics about the Synchronizer
, and is used for debugging purposes.
getSynchronizerStats(): SynchronizerStats
returns | SynchronizerStats | A |
---|
The SynchronizerStats
object contains a count of the number of times the Synchronizer
has sent and received messages.
The method is intended to be used during development to ensure your synchronization layer is acting as expected, for example.
Example
This example gets the send and receive statistics of two active Synchronizer
instances.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(synchronizer1.getSynchronizerStats());
// -> {receives: 4, sends: 5}
console.log(synchronizer2.getSynchronizerStats());
// -> {receives: 5, sends: 4}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
startSync
The startSync
method is used to start the process of synchronization between this instance and another matching Synchronizer
.
startSync(initialContent?: Content): Promise<WsSynchronizer<WebSocketType>>
Type | Description | |
---|---|---|
initialContent? | Content | An optional |
returns | Promise<WsSynchronizer<WebSocketType>> | A Promise containing a reference to the |
The underlying implementation of a Synchronizer
is shared with the Persister
framework, and so this startSync
method is equivalent to starting both auto-loading (listening to sync messages from other active Synchronizer
instances) and auto-saving (sending sync messages to it).
This method is asynchronous so you should you await
calls to this method or handle the return type natively as a Promise.
Examples
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts synchronizing them. A change in one becomes evident in the other.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
store2.setRow('pets', 'felix', {species: 'cat'});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts synchronizing them with default content. The default content from the first Synchronizer
's startSync
method ends up populated in both MergeableStore
instances: by the time the second started, the first was available to synchronize with and its default was not needed.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer1.startSync([{pets: {fido: {species: 'dog'}}}, {}]);
await synchronizer2.startSync([{pets: {felix: {species: 'cat'}}}, {}]);
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
stopSync
The stopSync
method is used to stop the process of synchronization between this instance and another matching Synchronizer
.
stopSync(): this
returns | this | A reference to the |
---|
The underlying implementation of a Synchronizer
is shared with the Persister
framework, and so this startSync
method is equivalent to stopping both auto-loading (listening to sync messages from other active Synchronizer
instances) and auto-saving (sending sync messages to them).
Example
This example creates two empty MergeableStore
objects, creates a LocalSynchronizer
for each, and starts - then stops - synchronizing them. Subsequent changes are not merged.
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore();
const synchronizer1 = createLocalSynchronizer(store1);
await synchronizer1.startSync();
const store2 = createMergeableStore();
const synchronizer2 = createLocalSynchronizer(store2);
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.stopSync();
synchronizer2.stopSync();
store1.setCell('pets', 'fido', 'color', 'brown');
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}}}
synchronizer1.destroy();
synchronizer2.destroy();
Since
v5.0.0
Development methods
This is the collection of development methods within the WsSynchronizer
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the Persister
, and is used for debugging purposes.
getStats(): PersisterStats
returns | PersisterStats | A |
---|
The PersisterStats
object contains a count of the number of times the Persister
has loaded and saved data.
The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister
object. Remember that the startAutoLoad
method invokes an explicit load when it starts, and the startAutoSave
method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store
and to the underlying storage.
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Since
v1.0.0
Functions
There is one function, createWsSynchronizer
, within the synchronizer-ws-client
module.
createWsSynchronizer
The createWsSynchronizer
function creates a WsSynchronizer
object that can synchronize MergeableStore
data to and from other MergeableStore
instances via WebSockets facilitated by a WsServer
.
createWsSynchronizer<WebSocketType>(
store: MergeableStore,
webSocket: WebSocketType,
requestTimeoutSeconds?: number,
onSend?: Send,
onReceive?: Receive,
onIgnoredError?: (error: any) => void,
): Promise<WsSynchronizer<WebSocketType>>
Type | Description | |
---|---|---|
store | MergeableStore | The |
webSocket | WebSocketType | The WebSocket to send synchronization messages over. |
requestTimeoutSeconds? | number | An optional time in seconds that the |
onSend? | Send | An optional handler for the messages that this |
onReceive? | Receive | An optional handler for the messages that this |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
returns | Promise<WsSynchronizer<WebSocketType>> | A reference to the new |
As well as providing a reference to the MergeableStore
to persist, you must provide a configured WebSocket to send synchronization messages over.
Instead of the raw browser implementation of WebSocket, you may prefer to use the Reconnecting WebSocket wrapper so that if a client goes offline, it can easily re-establish a connection when it comes back online. Its API is compatible with this Synchronizer
.
You can indicate how long the Synchronizer
will wait for responses to message requests before timing out. A final set of optional handlers can be provided to help debug sends, receives, and errors respectively.
This method is asynchronous because it will await the websocket's connection to the server. You will need to await
a call to this function or handle the return type natively as a Promise.
Example
This example creates two WsSynchronizer
objects to synchronize one MergeableStore
to another via a server.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server = createWsServer(new WebSocketServer({port: 8047}));
const store1 = createMergeableStore();
const store2 = createMergeableStore();
const synchronizer1 = await createWsSynchronizer(
store1,
new WebSocket('ws://localhost:8047'),
);
const synchronizer2 = await createWsSynchronizer(
store2,
new WebSocket('ws://localhost:8047'),
);
await synchronizer1.startSync();
await synchronizer2.startSync();
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(store1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(store2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
server.destroy();
Since
v5.0.0
Type Aliases
There is one type alias, WebSocketTypes
, within the synchronizer-ws-client
module.
WebSocketTypes
The WebSocketTypes
type represents the valid types of WebSocket that can be used with the WsSynchronizer
.
WebSocket | WsWebSocket
This includes the browser-native WebSocket type, as well as the WebSocket type from the well-known ws
package (such that the Synchronizer
can be used in a server environment).
Since
v5.0.0
synchronizer-ws-server
The synchronizer-ws-server
module of the TinyBase project lets you create a server that facilitates synchronization between clients.
See also
- Synchronization guide
- Todo App v6 (collaboration) demo
Since
v5.0.0
Interfaces
There is one interface, WsServer
, within the synchronizer-ws-server
module.
WsServer
The WsServer
interface represents an object that facilitates synchronization between clients that are using WsSynchronizer
instances.
You should use the createWsServer
function to create a WsServer
object.
Since
v5.0.0
Getter methods
This is the collection of getter methods within the WsServer
interface. There are 4 getter methods in total.
destroy
The destroy
method provides a way to clean up the server at the end of its use.
destroy(): void
This closes the underlying WebSocketServer that was provided when the WsServer
was created.
Example
This example creates a WsServer
and then destroys it again, closing the underlying WebSocketServer.
import {WebSocketServer} from 'ws';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
const webSocketServer = new WebSocketServer({port: 8047});
webSocketServer.on('close', () => {
console.log('WebSocketServer closed');
});
const server = createWsServer(webSocketServer);
server.destroy();
// ...
// -> 'WebSocketServer closed'
Since
v5.0.0
getClientIds
The getClientIds
method method returns the active clients that the WsServer
is handling for a given path.
getClientIds(pathId: string): Ids
Type | Description | |
---|---|---|
pathId | string | |
returns | Ids | An array of the clients connected to the given path. |
Example
This example creates a WsServer
, sets some clients up to connect to it, and then gets the number of clients on the given paths. (The client Ids
themselves are unique, based on the sec-websocket-key
header.)
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server = createWsServer(new WebSocketServer({port: 8047}));
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
const synchronizer3 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
console.log(server.getClientIds('roomA').length);
// -> 2
console.log(server.getClientIds('roomB').length);
// -> 1
synchronizer3.destroy();
// ...
console.log(server.getClientIds('roomB').length);
// -> 0
synchronizer1.destroy();
synchronizer2.destroy();
server.destroy();
Since
v5.0.0
getPathIds
The getPathIds
method returns the active paths that the WsServer
is handling.
getPathIds(): Ids
returns | Ids | An array of the paths that have clients connected to them. |
---|
These will be all the paths that have at least one active client connected to them.
Example
This example creates a WsServer
, sets some clients up to connect to it, and then enumerates the paths being used.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server = createWsServer(new WebSocketServer({port: 8047}));
console.log(server.getPathIds());
// -> []
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
const synchronizer3 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
console.log(server.getPathIds());
// -> ['roomA', 'roomB']
synchronizer3.destroy();
// ...
console.log(server.getPathIds());
// -> ['roomA']
synchronizer1.destroy();
synchronizer2.destroy();
server.destroy();
Since
v5.0.0
getWebSocketServer
The getWebSocketServer
method returns a reference to the WebSocketServer being used for this WsServer
.
getWebSocketServer(): WebSocketServer
returns | WebSocketServer | The WebSocketServer reference. |
---|
Example
This example creates a WsServer
and then gets the WebSocketServer reference back out again.
import {WebSocketServer} from 'ws';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
const webSocketServer = new WebSocketServer({port: 8047});
const server = createWsServer(webSocketServer);
console.log(server.getWebSocketServer() == webSocketServer);
// -> true
server.destroy();
Since
v5.0.0
Listener methods
This is the collection of listener methods within the WsServer
interface. There are only three listener methods, addClientIdsListener
, addPathIdsListener
, and delListener
.
addClientIdsListener
The addClientIdsListener
method registers a listener function with the WsServer
that will be called whenever there is a change in the clients connected to a path that a WsServer
is handling.
addClientIdsListener(
pathId: IdOrNull,
listener: ClientIdsListener,
): string
Type | Description | |
---|---|---|
pathId | IdOrNull | The path to listen to, or |
listener | ClientIdsListener | The function that will be called whenever the client |
returns | string | A unique |
The provided listener is a ClientIdsListener
function, and will be called with a reference to the WsServer
, the Id
of the path that the client joined or left, and a callback you can use to get information about the change.
You can either listen to a single path (by specifying its Id
as the method's first parameter) or changes to any path (by providing a null
wildcard).
Examples
This example creates a WsServer
, and listens to changes to the clients connecting to and disconnecting from a specific path.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server = createWsServer(new WebSocketServer({port: 8047}));
const listenerId = server.addClientIdsListener(
'roomA',
(server, pathId) => {
console.log(
`${server.getClientIds(pathId).length} client(s) in roomA`,
);
},
);
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
// -> '1 client(s) in roomA'
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
// The listener is not called.
synchronizer1.destroy();
// ...
// -> '0 client(s) in roomA'
synchronizer2.destroy();
server.delListener(listenerId);
server.destroy();
This example creates a WsServer
, and listens to changes to the clients connecting to and disconnecting from any path.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server = createWsServer(new WebSocketServer({port: 8047}));
const listenerId = server.addClientIdsListener(null, (server, pathId) => {
console.log(
`${server.getClientIds(pathId).length} client(s) in ${pathId}`,
);
});
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
// -> '1 client(s) in roomA'
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
// -> '1 client(s) in roomB'
synchronizer1.destroy();
// ...
// -> '0 client(s) in roomA'
synchronizer2.destroy();
// ...
// -> '0 client(s) in roomB'
server.delListener(listenerId);
server.destroy();
Since
v5.0.0
addPathIdsListener
The addPathIdsListener
method registers a listener function with the WsServer
that will be called whenever there is a change in the active paths that a WsServer
is handling.
addPathIdsListener(listener: PathIdsListener): string
Type | Description | |
---|---|---|
listener | PathIdsListener | The function that will be called whenever the path |
returns | string | A unique |
The provided listener is a PathIdsListener
function, and will be called with a reference to the WsServer
and a callback you can use to get information about the change.
Example
This example creates a WsServer
, and listens to changes to the active paths when clients connect to and disconnect from it.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server = createWsServer(new WebSocketServer({port: 8047}));
const listenerId = server.addPathIdsListener(
(server, pathId, addedOrRemoved) => {
console.log(pathId + (addedOrRemoved == 1 ? ' added' : ' removed'));
console.log(server.getPathIds());
},
);
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
// -> 'roomA added'
// -> ['roomA']
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomB'),
);
// -> 'roomB added'
// -> ['roomA', 'roomB']
synchronizer1.destroy();
// ...
// -> 'roomA removed'
// -> ['roomB']
synchronizer2.destroy();
// ...
// -> 'roomB removed'
// -> []
server.delListener(listenerId);
server.destroy();
Since
v5.0.0
delListener
The delListener
method removes a listener that was previously added to the WsServer
.
delListener(listenerId: string): WsServer
Type | Description | |
---|---|---|
listenerId | string | The |
returns | WsServer | A reference to the |
Use the Id
returned by whichever method was used to add the listener. Note that the WsServer
may re-use this Id
for future listeners added to it.
Example
This example registers a listener to a WsServer
and then removes it.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server = createWsServer(new WebSocketServer({port: 8047}));
const listenerId = server.addPathIdsListener(() => {
console.log('Paths changed');
});
const synchronizer = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047/roomA'),
);
// -> 'Paths changed'
server.delListener(listenerId);
synchronizer.destroy();
// -> undefined
// The listener is not called.
server.destroy();
Since
v5.0.0
Development methods
This is the collection of development methods within the WsServer
interface. There is only one method, getStats
.
getStats
The getStats
method provides a set of statistics about the WsServer
, and is used for debugging purposes.
getStats(): WsServerStats
returns | WsServerStats | A |
---|
The WsServerStats
object contains the number of paths and clients that are active on the WsServer
and is intended to be used during development.
Example
This example creates a WsServer
that facilitates some synchronization, demonstrating the statistics of the paths and clients handled as a result.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server = createWsServer(new WebSocketServer({port: 8047}));
const synchronizer1 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047'),
);
const synchronizer2 = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047'),
);
console.log(server.getStats());
// -> {paths: 1, clients: 2}
synchronizer1.destroy();
synchronizer2.destroy();
server.destroy();
Since
v5.0.0
Functions
There is one function, createWsServer
, within the synchronizer-ws-server
module.
createWsServer
The createWsServer
function creates a WsServer
that facilitates synchronization between clients that are using WsSynchronizer
instances.
createWsServer<PathPersister>(
webSocketServer: WebSocketServer,
createPersisterForPath?: (pathId: string) => undefined | PathPersister | [PathPersister, (store: MergeableStore) => void] | Promise<PathPersister> | Promise<[PathPersister, (store: MergeableStore) => void]>,
): WsServer
Type | Description | |
---|---|---|
webSocketServer | WebSocketServer | A WebSocketServer object from your server environment. |
createPersisterForPath? | (pathId: string) => undefined | PathPersister | [PathPersister, (store: MergeableStore) => void] | Promise<PathPersister> | Promise<[PathPersister, (store: MergeableStore) => void]> | An optional function that will create a |
returns | WsServer | A reference to the new |
This should be run in a server environment, and you must pass in a configured WebSocketServer object in order to create it.
If you want your server to persist data itself, you can use the optional second parameter of this function, which allows you to create a Persister
for a new path - whenever a new path is accessed by a client. This Persister
will only exist when there are active clients on that particular path. The creation callback can be asynchronous.
You are responsible for creating a MergeableStore
to pass to this Persister
, but starting and stopping its automatic saving and loading is taken care of by the WsServer
. As a result, the server MergeableStore
will be kept in sync with the clients on that path, and in turn with whatever persistence layer you have configured. See the example below.
It is not safe to add or manipulate data in the MergeableStore
during the createPersisterForPath
function, since changes will probably be overwritten when the Persister
starts. If you wish to modify data - or upgrade a schema, for example - you can have that function instead return an array containing the Persister
and a callback that takes the MergeableStore
. That callback will get called after the Persister
has started, and is an appropriate place to manipulate data in a way that will be transmitted to clients. Again, see the example below.
Examples
This example creates a WsServer
that synchronizes two clients on a shared path.
import {WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
// Server
const server = createWsServer(new WebSocketServer({port: 8047}));
// Client 1
const clientStore1 = createMergeableStore();
clientStore1.setCell('pets', 'fido', 'species', 'dog');
const synchronizer1 = await createWsSynchronizer(
clientStore1,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer1.startSync();
// ...
// Client 2
const clientStore2 = createMergeableStore();
clientStore2.setCell('pets', 'felix', 'species', 'cat');
const synchronizer2 = await createWsSynchronizer(
clientStore2,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer2.startSync();
// ...
console.log(clientStore1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(clientStore2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
server.destroy();
This longer example creates a WsServer
that persists a MergeableStore
to file that is synchronized with two clients on a shared path. Later, when a third client connects, it picks up the data the previous two were using.
import {WebSocketServer} from 'ws';
import {createFilePersister} from 'tinybase/persisters/persister-file';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {rmSync} from 'fs';
// Server
const server = createWsServer(
new WebSocketServer({port: 8047}),
(pathId) =>
createFilePersister(
createMergeableStore(),
pathId.replace(/[^a-zA-Z0-9]/g, '-') + '.json',
),
);
// Client 1
const clientStore1 = createMergeableStore();
clientStore1.setCell('pets', 'fido', 'species', 'dog');
const synchronizer1 = await createWsSynchronizer(
clientStore1,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer1.startSync();
// ...
// Client 2
const clientStore2 = createMergeableStore();
clientStore2.setCell('pets', 'felix', 'species', 'cat');
const synchronizer2 = await createWsSynchronizer(
clientStore2,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer2.startSync();
// ...
console.log(clientStore1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(clientStore2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
// ...
// Client 3 connects later
const clientStore3 = createMergeableStore();
const synchronizer3 = await createWsSynchronizer(
clientStore3,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer3.startSync();
// ...
console.log(clientStore3.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer3.destroy();
server.destroy();
// Remove file for the purposes of this demo.
rmSync('petShop.json');
This example creates a WsServer
that persists a MergeableStore
to file that is synchronized with two clients on a shared path, but also which updates its data once synchronization has started.
import {WebSocketServer} from 'ws';
import {createFilePersister} from 'tinybase/persisters/persister-file';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {rmSync} from 'fs';
// Server
const server = createWsServer(
new WebSocketServer({port: 8047}),
(pathId) => [
createFilePersister(
createMergeableStore(),
pathId.replace(/[^a-zA-Z0-9]/g, '-') + '.json',
),
(store) => store.setValue('pathId', pathId),
],
);
const clientStore = createMergeableStore();
clientStore.setCell('pets', 'fido', 'species', 'dog');
const synchronizer = await createWsSynchronizer(
clientStore,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer.startSync();
// ...
console.log(clientStore.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {"pathId": "petShop"}]
synchronizer.destroy();
server.destroy();
// Remove file for the purposes of this demo.
rmSync('petShop.json');
This example creates a WsServer
with a custom listener that displays information about the address of the client that connects to it.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
// On the server:
const webSocketServer = new WebSocketServer({port: 8047});
webSocketServer.on('connection', (_, request) => {
console.log('Client address: ' + request.socket.remoteAddress);
});
const server = createWsServer(webSocketServer);
// On a client:
const synchronizer = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047'),
);
// -> 'Client address: ::1'
synchronizer.destroy();
server.destroy();
Since
v5.0.0
Type Aliases
These are the type aliases within the synchronizer-ws-server
module.
Listener type aliases
This is the collection of listener type aliases within the synchronizer-ws-server
module. There are only two listener type aliases, ClientIdsListener
and PathIdsListener
.
ClientIdsListener
The ClientIdsListener
type describes a function that is used to listen to clients joining and leaving the active paths that a WsServer
is handling.
(
wsServer: WsServer,
pathId: Id,
clientId: Id,
addedOrRemoved: IdAddedOrRemoved,
): void
Type | Description | |
---|---|---|
wsServer | WsServer | A reference to the |
pathId | Id | The path that the client joined or left. |
clientId | Id | The |
addedOrRemoved | IdAddedOrRemoved | Whether the client was added ( |
returns | void | This has no return value. |
A WsServer
listens to any path, allowing an app to have the concept of distinct 'rooms' that only certain clients are participating in. As soon as a new client connects to a path, this listener will be called with the Id
of the path, the Id
of the new client, and an addedOrRemoved
value of 1
.
When the client disconnects from a path, it will be called again with the Id
of the path, the Id
of the leaving client, and an addedOrRemoved
value of -1
.
A ClientIdsListener
is provided when using the addClientIdsListener
method. See that method for specific examples.
Since
v5.0.3
PathIdsListener
The PathIdsListener
type describes a function that is used to listen to changes of active paths that a WsServer
is handling.
(
wsServer: WsServer,
pathId: Id,
addedOrRemoved: IdAddedOrRemoved,
): void
Type | Description | |
---|---|---|
wsServer | WsServer | A reference to the |
pathId | Id | The |
addedOrRemoved | IdAddedOrRemoved | Whether the path was added ( |
returns | void | This has no return value. |
A WsServer
listens to any path, allowing an app to have the concept of distinct 'rooms' that only certain clients are participating in. As soon as a single client connects to a new path, this listener will be called with the Id
of the new path and an addedOrRemoved
value of 1
.
When the final client disconnects from a path, it will be called again with the Id
of the deactivated path and an addedOrRemoved
value of -1
.
A PathIdsListener
is provided when using the addPathIdsListener
method. See that method for specific examples.
Since
v5.0.3
Development type aliases
This is the collection of development type aliases within the synchronizer-ws-server
module. There is only one type alias, WsServerStats
.
WsServerStats
The WsServerStats
type describes the number of paths and clients that are active on the WsServer
.
{
paths: number;
clients: number;
}
Type | Description | |
---|---|---|
paths | number | The number of paths currently being served by the |
clients | number | The number of clients currently being served by the |
A WsServerStats
object is returned from the getStats
method.
Since
v5.0.0
tools
The tools
module of the TinyBase project provides utilities for working with TinyBase during development.
This module is not intended to be directly used at runtime in a production environment.
Since
v2.2.0
Interfaces
There is one interface, Tools
, within the tools
module.
Tools
A Tools
object lets you run various utilities on, and get certain information about, Store
objects in development.
See also
Developer Tools guides
Since
v2.2.0
Getter methods
This is the collection of getter methods within the Tools
interface. There is only one method, getStore
.
getStore
The getStore
method returns a reference to the underlying Store
that is backing this Tools
object.
getStore(): Store
Example
This example creates a Tools
object against a newly-created Store
and then gets its reference in order to update its data.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const tools = createTools(createStore());
tools.getStore().setCell('species', 'dog', 'price', 5);
console.log(tools.getStoreStats().totalCells);
// -> 1
Since
v3.0.0
Modelling methods
This is the collection of modelling methods within the Tools
interface. There are 4 modelling methods in total.
getPrettyStoreApi
The getPrettyStoreApi
method attempts to return prettified code-generated .d.ts
and .ts(x)
files that describe the schema of a Store
and React bindings (since v3.1) in an ORM style.
getPrettyStoreApi(storeName: string): Promise<[string, string, string, string]>
Type | Description | |
---|---|---|
storeName | string | The name you want to provide to the generated |
returns | Promise<[string, string, string, string]> | A set of four strings representing the contents of the |
This is simply a wrapper around the getStoreApi
method that attempts to invoke the prettier
module (which it hopes you have installed) to format the generated code. If prettier
is not present, the output will resemble that of the underlying getStoreApi
method.
The method is asynchronous, so you should use the await
keyword or handle the results as a promise.
The method takes a single argument which represents the name you want the generated store object to have in code. You are expected to save the four files yourself, as the following:
[storeName].d.ts
as the main definition,[storeName].ts
as the main library,[storeName]-ui-react.d.ts
as theui-react
module definition,[storeName]-ui-react.tsx
as theui-react
module library.
Also you should save these alongside each other so that the .ts(x)
files can import types from the .d.ts
files.
See the documentation for the getStoreApi
method for details of the content of the generated files.
Examples
This example creates a Tools
object and generates code for a Store
that already has a TablesSchema
.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore().setTablesSchema({
pets: {
price: {type: 'number'},
},
});
const [dTs, ts, _uiReactDTs, _uiReactTsx] =
await createTools(store).getPrettyStoreApi('shop');
const dTsLines = dTs.split('\n');
console.log(dTsLines[15]);
// -> `export type Tables = {pets?: {[rowId: Id]: {price?: number}}};`
const tsLines = ts.split('\n');
console.log(tsLines[89]);
// -> ' hasPetsTable: (): boolean => store.hasTable(PETS),'
This example creates a Tools
object and generates code for a Store
that doesn't already have a TablesSchema
.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore().setTable('pets', {
fido: {price: 5},
felix: {price: 4},
});
const [dTs, ts, _uiReactDTs, _uiReactTsx] =
await createTools(store).getPrettyStoreApi('shop');
const dTsLines = dTs.split('\n');
console.log(dTsLines[15]);
// -> 'export type Tables = {pets?: {[rowId: Id]: {price: number}}};'
const tsLines = ts.split('\n');
console.log(tsLines[91]);
// -> ' hasPetsTable: (): boolean => store.hasTable(PETS),'
Since
v2.2.0
getStoreApi
The getStoreApi
method returns code-generated .d.ts
and .ts(x)
files that describe the schema of a Store
and React bindings (since v3.1) in an ORM style.
getStoreApi(storeName: string): [string, string, string, string]
Type | Description | |
---|---|---|
storeName | string | The name you want to provide to the generated |
returns | [string, string, string, string] | A set of four strings representing the contents of the |
If the Store
does not already have an explicit TablesSchema
or ValuesSchema
associated with it, the data in the Store
will be scanned to attempt to infer new schemas. The method returns four strings (which should be saved as files) though if no schema can be inferred, the strings will be empty.
The method takes a single argument which represents the name you want the generated store object to have in code. You are expected to save the four files yourself, as the following:
[storeName].d.ts
as the main definition,[storeName].ts
as the main library,[storeName]-ui-react.d.ts
as theui-react
module definition,[storeName]-ui-react.tsx
as theui-react
module library.
Also you should save these alongside each other so that the .ts(x)
files can import types from the .d.ts
files.
The .d.ts
and .ts(x)
files that are generated are designed to resemble the main TinyBase Store
and React binding files, but provide named types and methods that describe the domain of the schema in the Store
.
For example, from a Store
that has a pets
Table
, you will get methods like getPetsTable
, types like PetsRow
, and hooks and components that are more specific versions of the underlying getTable
method or the Row
type, and so on. For example:
Store type | Equivalent generated type |
---|---|
Table | [Table]Table |
Row | [Table]Row |
(Cell ) Id | [Table]CellId |
CellCallback | [Table]CellCallback |
... | ... |
Store method | Equivalent generated method |
---|---|
setTable | set[Table]Table |
hasRow | has[Table]Row |
getCell | get[Table][Cell]Cell |
... | ... |
Equivalent to the TinyBase createStore
function, a create[StoreName]
function will also be created. This acts as the main entry point to the generated implementation.
Each method is refined correctly to take, or return, the types specified by the schema. For example, if the pets
Table
has a numeric price
Cell
in the schema, the getPetsPriceCell
method will be typed to return a number.
The tables above include just a sample of the generated output. For the full set of methods and types generated by this method, inspect the output directly.
Examples
This example creates a Tools
object and generates code for a Store
that already has a TablesSchema
.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore().setTablesSchema({
pets: {
price: {type: 'number'},
},
});
const [dTs, ts, _uiReactDTs, _uiReactTsx] =
createTools(store).getStoreApi('shop');
const dTsLines = dTs.split('\n');
console.log(dTsLines[3]);
// -> `export type Tables = {'pets'?: {[rowId: Id]: {'price'?: number}}};`
const tsLines = ts.split('\n');
console.log(tsLines[39]);
// -> 'getPetsTable: (): PetsTable => store.getTable(PETS) as PetsTable,'
This example creates a Tools
object and generates code for a Store
that doesn't already have a TablesSchema
.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore().setTable('pets', {
fido: {price: 5},
felix: {price: 4},
});
const [dTs, ts, _uiReactDTs, _uiReactTsx] =
createTools(store).getStoreApi('shop');
const dTsLines = dTs.split('\n');
console.log(dTsLines[3]);
// -> `export type Tables = {'pets'?: {[rowId: Id]: {'price': number}}};`
const tsLines = ts.split('\n');
console.log(tsLines[41]);
// -> 'getPetsTable: (): PetsTable => store.getTable(PETS) as PetsTable,'
Since
v2.2.0
getStoreTablesSchema
The getStoreTablesSchema
method returns the TablesSchema
of the Store
as an object.
getStoreTablesSchema(): TablesSchema
returns | TablesSchema | A |
---|
If the Store
does not already have an explicit TablesSchema
associated with it, the data in the Store
will be scanned to attempt to infer a new TablesSchema
.
To be successful, this requires all the values of a given Cell
across a Table
object's Row
objects to have a consistent type. If a given Cell
Id
appears in every Row
, then a default
for that Cell
is specified in the TablesSchema
, based on the most common value found.
Examples
This example creates a Tools
object and gets the schema of a Store
that already has a TablesSchema
.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string'},
},
species: {
price: {type: 'number'},
},
});
const schema = createTools(store).getStoreTablesSchema();
console.log(schema.pets);
// -> {species: {type: 'string'}, color: {type: 'string'}}
This example creates a Tools
object and infers the schema of a Store
that doesn't already have a TablesSchema
.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('species', {
dog: {price: 5, barks: true},
cat: {price: 4, purrs: true},
});
const schema = createTools(store).getStoreTablesSchema();
console.log(schema.pets.species);
// -> {type: 'string', default: 'dog'}
console.log(schema.pets.color);
// -> {type: 'string', default: 'black'}
console.log(schema.species.price);
// -> {type: 'number', default: 5}
console.log(schema.species.barks);
// -> {type: 'boolean'}
console.log(schema.species.purrs);
// -> {type: 'boolean'}
Since
v3.0.0
getStoreValuesSchema
The getStoreValuesSchema
method returns the ValuesSchema
of the Store
as an object.
getStoreValuesSchema(): ValuesSchema
returns | ValuesSchema | A |
---|
If the Store
does not already have an explicit ValuesSchema
associated with it, the data in the Store
will be scanned to infer a new ValuesSchema
, based on the types of the Values
present. Note that, unlike the inference of Cell
values in the TablesSchema
, it is not able to determine whether a Value
should have a default or not.
Examples
This example creates a Tools
object and gets the schema of a Store
that already has a ValuesSchema
.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: true},
employees: {type: 'number'},
});
const schema = createTools(store).getStoreValuesSchema();
console.log(schema);
// -> {open: {type: 'boolean', default: true}, employees: {type: 'number'}}
This example creates a Tools
object and infers the schema of a Store
that doesn't already have a ValuesSchema
.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore().setValues({open: true, employees: 3});
const schema = createTools(store).getStoreValuesSchema();
console.log(schema);
// -> {open: {type: 'boolean'}, employees: {type: 'number'}}
Since
v3.0.0
Statistics methods
This is the collection of statistics methods within the Tools
interface. There is only one method, getStoreStats
.
getStoreStats
The getStoreStats
method provides a set of statistics about the Store
, and is used for debugging purposes.
getStoreStats(detail?: boolean): StoreStats
Type | Description | |
---|---|---|
detail? | boolean | An optional boolean that indicates more detailed stats about the inner structure of the |
returns | StoreStats | A |
Examples
This example creates a Tools
object and gets basic statistics about it.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
})
.setValues({open: true, employees: 3});
console.log(createTools(store).getStoreStats());
// -> {totalTables: 2, totalRows: 5, totalCells: 8, totalValues: 2, jsonLength: 212}
This example creates a Tools
object and gets detailed statistics about it.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const stats = createTools(store).getStoreStats(true);
console.log(stats.totalTables);
// -> 2
console.log(stats.totalRows);
// -> 5
console.log(stats.totalCells);
// -> 8
console.log(stats.detail.tables.pets.tableRows);
// -> 3
console.log(stats.detail.tables.pets.tableCells);
// -> 6
console.log(stats.detail.tables.pets.rows);
// -> {fido: {rowCells: 2}, felix: {rowCells: 2}, cujo: {rowCells: 2}}
Since
v2.2.0
Functions
There is one function, createTools
, within the tools
module.
createTools
The createTools
function creates a Tools
object, and is the main entry point into the tools
module.
createTools(store: Store): Tools
Type | Description | |
---|---|---|
store | Store | The |
returns | Tools | A reference to the new |
A given Store
can only have one Tools
object associated with it. If you call this function twice on the same Store
, your second call will return a reference to the Tools
object created by the first.
Examples
This example creates a Tools
object.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
})
.setValues({open: true, employees: 3});
console.log(createTools(store).getStoreStats());
// -> {totalTables: 2, totalRows: 5, totalCells: 8, totalValues: 2, jsonLength: 212}
This example creates a Tools
object, and calls the method a second time for the same Store
to return the same object.
import {createStore} from 'tinybase';
import {createTools} from 'tinybase/tools';
const store = createStore();
const tools1 = createTools(store);
const tools2 = createTools(store);
console.log(tools1 === tools2);
// -> true
Since
v2.2.0
Type Aliases
These are the type aliases within the tools
module.
StoreStats
The StoreStats
type describes a set of statistics about the Store
, and is used for debugging purposes.
{
totalTables: number;
totalRows: number;
totalCells: number;
totalValues: number;
jsonLength: number;
detail?: StoreStatsDetail;
}
Type | Description | |
---|---|---|
totalTables | number | |
totalRows | number | The number of |
totalCells | number | The number of |
totalValues | number | |
jsonLength | number | The string length of the |
detail? | StoreStatsDetail | Additional detailed statistics about the |
A StoreStats
object is returned from the getStoreStats
method.
Since
v2.2.0
StoreStatsDetail
The StoreStatsDetail
type describes a more detailed set of statistics about the Store
, and is used for debugging purposes.
{tables: {[tableId: Id]: StoreStatsTableDetail}}
Type | Description | |
---|---|---|
tables | {[tableId: Id]: StoreStatsTableDetail} |
A StoreStatsDetail
object is added to the StoreStats
object (returned from the getStoreStats
method) when the detail
flag is specified.
Since
v2.2.0
StoreStatsRowDetail
The StoreStatsRowDetail
type describes statistics about a single Row
in the Store
, and is used for debugging purposes.
{rowCells: number}
Since
v2.2.0
StoreStatsTableDetail
The StoreStatsTableDetail
type describes a detailed set of statistics about a single Table
in the Store
, and is used for debugging purposes.
{
tableRows: number;
tableCells: number;
rows: {[rowId: Id]: StoreStatsRowDetail};
}
Type | Description | |
---|---|---|
tableRows | number | |
tableCells | number | The number of |
rows | {[rowId: Id]: StoreStatsRowDetail} | Detail about the |
Since
v2.2.0
ui-react
The ui-react
module of the TinyBase project provides both hooks and components to make it easy to create reactive apps with Store
objects.
The hooks in this module provide access to the data and structures exposed by other modules in the project. As well as immediate access, they all register listeners such that components using those hooks are selectively re-rendered when data changes.
The components in this module provide a further abstraction over those hooks to ease the composition of user interfaces that use TinyBase.
See also
- Building UIs guides
- Building UIs With
Metrics
guide - Building UIs With
Indexes
guide - Building UIs With
Relationships
guide - Building UIs With
Queries
guide - Building UIs With
Checkpoints
guide - Countries demo
- Todo App demos
- Drawing demo
Since
v1.0.0
Functions
These are the functions within the ui-react
module.
Checkpoints hooks
This is the collection of checkpoints hooks within the ui-react
module. There are 14 checkpoints hooks in total.
useCheckpoint
The useCheckpoint
hook returns the label for a checkpoint, and registers a listener so that any changes to that result will cause a re-render.
useCheckpoint(
checkpointId: string,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): string | undefined
Type | Description | |
---|---|---|
checkpointId | string | The |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | string | undefined | A string label for the requested checkpoint, an empty string if it was never set, or |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Checkpoints
object or a set of Checkpoints
objects named by Id
. The useCheckpoint
hook lets you indicate which Checkpoints
object to get data for: omit the optional final parameter for the default context Checkpoints
object, provide an Id
for a named context Checkpoints
object, or provide a Checkpoints
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the label will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Checkpoints
object outside the application, which is used in the useCheckpoint
hook by reference. A change to the checkpoint label re-renders the component.
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useCheckpoint} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => <span>{useCheckpoint('1', checkpoints)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span></span>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<span>sale</span>'
This example creates a Provider context into which a default Checkpoints
object is provided. A component within it then uses the useCheckpoint
hook.
import {Provider, useCheckpoint} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCheckpoint('0')}</span>;
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
).setCheckpoint('0', 'initial');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>initial</span>'
This example creates a Provider context into which a default Checkpoints
object is provided. A component within it then uses the useCheckpoint
hook.
import {Provider, useCheckpoint} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpointsById={{petCheckpoints: checkpoints}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCheckpoint('0', 'petCheckpoints')}</span>;
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
).setCheckpoint('0', 'initial');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>initial</span>'
Since
v1.0.0
useCheckpointIds
The useCheckpointIds
hook returns an array of the checkpoint Ids
being managed by this Checkpoints
object, and registers a listener so that any changes to that result will cause a re-render.
useCheckpointIds(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): CheckpointIds
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | CheckpointIds | A |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Checkpoints
object or a set of Checkpoints
objects named by Id
. The useCheckpointIds
hook lets you indicate which Checkpoints
object to get data for: omit the optional parameter for the default context Checkpoints
object, provide an Id
for a named context Checkpoints
object, or provide a Checkpoints
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the checkpoint Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Checkpoints
object outside the application, which is used in the useCheckpointIds
hook by reference. A change to the checkpoint Ids
re-renders the component.
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useCheckpointIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<span>{JSON.stringify(useCheckpointIds(checkpoints))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
store.setCell('pets', 'fido', 'sold', true);
console.log(app.innerHTML);
// -> '<span>[["0"],null,[]]</span>'
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<span>[["0"],"1",[]]</span>'
This example creates a Provider context into which a default Checkpoints
object is provided. A component within it then uses the useCheckpointIds
hook.
import {Provider, useCheckpointIds} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useCheckpointIds())}</span>;
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
This example creates a Provider context into which a default Checkpoints
object is provided. A component within it then uses the useCheckpointIds
hook.
import {Provider, useCheckpointIds} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpointsById={{petCheckpoints: checkpoints}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useCheckpointIds('petCheckpoints'))}</span>
);
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
Since
v1.0.0
useCheckpointIdsListener
The useCheckpointIdsListener
hook registers a listener function with the Checkpoints
object that will be called whenever its set of checkpoints changes.
useCheckpointIdsListener(
listener: CheckpointIdsListener,
listenerDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): void
Type | Description | |
---|---|---|
listener | CheckpointIdsListener | The function that will be called whenever the checkpoints change. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCheckpointIds
hook).
Unlike the addCheckpointIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useCheckpointIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Checkpoints
object will be deleted.
Example
This example uses the useCheckpointIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useCheckpointIdsListener} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => {
useCheckpointIdsListener(() => console.log('Checkpoint Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(checkpoints.getListenerStats().checkpointIds);
// -> 1
store.setCell('pets', 'fido', 'sold', true);
// -> 'Checkpoint Ids changed'
checkpoints.addCheckpoint();
// -> 'Checkpoint Ids changed'
root.unmount();
console.log(checkpoints.getListenerStats().checkpointIds);
// -> 0
Since
v1.0.0
useCheckpointListener
The useCheckpointListener
hook registers a listener function with the Checkpoints
object that will be called whenever the label of a checkpoint changes.
useCheckpointListener(
checkpointId: IdOrNull,
listener: CheckpointListener,
listenerDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): void
Type | Description | |
---|---|---|
checkpointId | IdOrNull | The |
listener | CheckpointListener | The function that will be called whenever the checkpoint label changes. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCheckpoint
hook).
You can either listen to a single checkpoint label (by specifying the checkpoint Id
as the method's first parameter), or changes to any checkpoint label (by providing a null
wildcard).
Unlike the addCheckpointListener
method, which returns a listener Id
and requires you to remove it manually, the useCheckpointListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Checkpoints
object will be deleted.
Example
This example uses the useCheckpointListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useCheckpointListener} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => {
useCheckpointListener('0', () =>
console.log('Checkpoint label changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(checkpoints.getListenerStats().checkpoint);
// -> 1
checkpoints.setCheckpoint('0', 'initial');
// -> 'Checkpoint label changed'
root.unmount();
console.log(checkpoints.getListenerStats().checkpoint);
// -> 0
Since
v1.0.0
useCheckpoints
The useCheckpoints
hook is used to get a reference to a Checkpoints
object from within a Provider
component context.
useCheckpoints(id?: string): Checkpoints | undefined
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Checkpoints | undefined | A reference to the |
A Provider
component is used to wrap part of an application in a context. It can contain a default Checkpoints
object (or a set of Checkpoints
objects named by Id
) that can be easily accessed without having to be passed down as props through every component.
The useCheckpoints
hook lets you either get a reference to the default Checkpoints
object (when called without a parameter), or one of the Checkpoints
objects that are named by Id
(when called with an Id
parameter).
Examples
This example creates a Provider context into which a default Checkpoint object is provided. A component within it then uses the useCheckpoints
hook to get a reference to the Checkpoints
object again, without the need to have it passed as a prop.
import {Provider, useCheckpoints} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useCheckpoints().getListenerStats().checkpointIds}</span>
);
const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Checkpoints
object is provided, named by Id
. A component within it then uses the useCheckpoints
hook with that Id
to get a reference to the Checkpoints
object again, without the need to have it passed as a prop.
import {Provider, useCheckpoints} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpointsById={{petStore: checkpoints}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{useCheckpoints('petStore').getListenerStats().checkpointIds}
</span>
);
const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useCheckpointsIds
The useCheckpointsIds
hook is used to retrieve the Ids
of all the named Checkpoints
objects present in the current Provider
component context.
useCheckpointsIds(): Ids
Example
This example adds two named Checkpoints
objects to a Provider context and an inner component accesses their Ids
.
import {
Provider,
useCheckpointsIds,
useCreateCheckpoints,
useCreateStore,
} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store1 = useCreateStore(createStore);
const checkpoints1 = useCreateCheckpoints(store1, createCheckpoints);
const store2 = useCreateStore(createStore);
const checkpoints2 = useCreateCheckpoints(store2, createCheckpoints);
return (
<Provider checkpointsById={{checkpoints1, checkpoints2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useCheckpointsIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["checkpoints1","checkpoints2"]</span>'
Since
v4.1.0
useCheckpointsOrCheckpointsById
The useCheckpointsOrCheckpointsById
hook is used to get a reference to a Checkpoints
object from within a Provider
component context, or have it passed directly to this hook.
useCheckpointsOrCheckpointsById(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Checkpoints | undefined
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | Either an |
returns | Checkpoints | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Checkpoints
object and which might have been passed in explicitly to the component or is to be picked up from the context by Id
(a common pattern for Checkpoints
-based components).
This is unlikely to be used often. For most situations, you will want to use the useCheckpoints
hook.
Example
This example creates a Provider context into which a default Checkpoints
object is provided. A component within it then uses the useCheckpointsOrCheckpointsById
hook to get a reference to the Checkpoints
object again, without the need to have it passed as a prop. Note however, that unlike the useCheckpoints
hook example, this component would also work if you were to pass the Checkpoints
object directly into it, making it more portable.
import {
Provider,
useCheckpointsOrCheckpointsById,
} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = ({checkpoints}) => (
<span>
{JSON.stringify(
useCheckpointsOrCheckpointsById(checkpoints).getCheckpointIds(),
)}
</span>
);
const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
Since
v4.1.0
useCreateCheckpoints
The useCreateCheckpoints
hook is used to create a Checkpoints
object within a React application with convenient memoization.
useCreateCheckpoints(
store: undefined | Store,
create: (store: Store) => Checkpoints,
createDeps?: DependencyList,
): Checkpoints | undefined
Type | Description | |
---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Checkpoints | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Checkpoints | undefined | A reference to the |
It is possible to create a Checkpoints
object outside of the React app with the regular createCheckpoints
function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Checkpoints
object being created every time the app renders or re-renders, since v5.0 this hook performs the creation in an effect. As a result it will return undefined
on the brief first render (or if the Store
is not yet defined), which you should defend against.
If your create
function contains other dependencies, the changing of which should also cause the Checkpoints
object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Checkpoints
object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Checkpoints
object at the top level of a React application. Even though the App component is rendered twice, the Checkpoints
object creation only occurs once by default.
import {createCheckpoints, createStore} from 'tinybase';
import {useCreateCheckpoints, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store = useCreateStore(createStore);
const checkpoints = useCreateCheckpoints(store, (store) => {
console.log('Checkpoints created');
return createCheckpoints(store).setSize(10);
});
return <span>{JSON.stringify(checkpoints?.getCheckpointIds())}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Checkpoints created'
root.render(<App />);
// No second Checkpoints creation
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
This example creates a Checkpoints
object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateCheckpoints
hook takes the size prop as a dependency, and so the Checkpoints
object is created again on the second render.
import {createCheckpoints, createStore} from 'tinybase';
import {useCreateCheckpoints, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({size}) => {
const store = useCreateStore(createStore);
const checkpoints = useCreateCheckpoints(
store,
(store) => {
console.log(`Checkpoints created, size ${size}`);
return createCheckpoints(store).setSize(size);
},
[size],
);
return <span>{JSON.stringify(checkpoints?.getCheckpointIds())}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App size={20} />);
// -> 'Checkpoints created, size 20'
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
root.render(<App size={50} />);
// -> 'Checkpoints created, size 50'
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
Since
v1.0.0
useGoBackwardCallback
The useGoBackwardCallback
hook returns a callback that moves the state of the underlying Store
back to the previous checkpoint, effectively performing an 'undo' on the Store
data.
useGoBackwardCallback(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Callback
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will go backward to the previous checkpoint - such as when clicking an undo button.
If there is no previous checkpoint to return to, this callback has no effect.
Example
This example uses the useGoBackwardCallback
hook to create an event handler which goes backward in the checkpoint stack when the span
element is clicked.
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useGoBackwardCallback} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => (
<span id="span" onClick={useGoBackwardCallback(checkpoints)}>
Backward
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
const _span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
// User clicks the <span> element:
// -> _span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
Since
v1.0.0
useGoForwardCallback
The useGoForwardCallback
hook returns a callback that moves the state of the underlying Store
forwards to a future checkpoint, effectively performing an 'redo' on the Store
data.
useGoForwardCallback(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Callback
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will go forward to the next checkpoint - such as when clicking an redo button.
If there is no future checkpoint to return to, this callback has no effect.
Example
This example uses the useGoForwardCallback
hook to create an event handler which goes backward in the checkpoint stack when the span
element is clicked.
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useGoForwardCallback} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => (
<span id="span" onClick={useGoForwardCallback(checkpoints)}>
Forward
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
const _span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
checkpoints.goBackward();
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
// User clicks the <span> element:
// -> _span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
Since
v1.0.0
useGoToCallback
The useGoToCallback
hook returns a parameterized callback that can be used to move the state of the underlying Store
backwards or forwards to a specified checkpoint.
useGoToCallback<Parameter>(
getCheckpointId: (parameter: Parameter) => string,
getCheckpointIdDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
then?: (checkpoints: Checkpoints, checkpointId: string) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
getCheckpointId | (parameter: Parameter) => string | A function which returns an |
getCheckpointIdDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
then? | (checkpoints: Checkpoints, checkpointId: string) => void | A function which is called after the checkpoint is moved, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. This parameter defaults to an empty array. |
This hook is useful, for example, when creating an event handler that will move the checkpoint. In this case, the parameter will likely be the event, so that you can use data from it as the checkpoint Id
to move to.
The optional first parameter is a function which will produce the label that will then be used to name the checkpoint.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the checkpoint has been set.
The Checkpoints
object for which the callback will set the checkpoint (indicated by the hook's checkpointsOrCheckpointsId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useGoToCallback
hook to create an event handler which moves to a checkpoint when the span
element is clicked.
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useGoToCallback} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const handleClick = useGoToCallback(() => '0', [], checkpoints);
return (
<span id="span" onClick={handleClick}>
Goto 0
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const _span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
// User clicks the <span> element:
// -> _span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
Since
v1.0.0
useRedoInformation
The useRedoInformation
hook returns an UndoOrRedoInformation
array that indicates if and how you can move the state of the underlying Store
forwards to a future checkpoint.
useRedoInformation(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): UndoOrRedoInformation
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | UndoOrRedoInformation |
|
This hook is useful if you are building an redo button: the information contains whether a redo action is available (to enable the button), the callback to perform the redo action, the checkpoint Id
that will be redone, and its label, if available.
Example
This example uses the useUndoInformation
hook to create a redo button.
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useRedoInformation} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const [canRedo, handleRedo, _id, label] =
useRedoInformation(checkpoints);
return canRedo ? (
<span onClick={handleRedo}>Redo {label}</span>
) : (
<span>Nothing to redo</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>Nothing to redo</span>'
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint('color');
checkpoints.goTo('0');
console.log(app.innerHTML);
// -> '<span>Redo color</span>'
Since
v1.0.0
useSetCheckpointCallback
The useSetCheckpointCallback
hook returns a parameterized callback that can be used to record a checkpoint of a Store
into a Checkpoints
object that can be reverted to in the future.
useSetCheckpointCallback<Parameter>(
getCheckpoint?: (parameter: Parameter) => string,
getCheckpointDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
then?: (checkpointId: string, checkpoints: Checkpoints, label?: string) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
getCheckpoint? | (parameter: Parameter) => string | An optional function which returns a string that will be used to describe the actions leading up to this checkpoint, based on the parameter the callback will receive (and which is most likely a DOM event). |
getCheckpointDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
then? | (checkpointId: string, checkpoints: Checkpoints, label?: string) => void | A function which is called after the checkpoint is set, with the new checkpoint |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will set the checkpoint. In this case, the parameter will likely be the event, so that you can use data from it as the checkpoint label.
The optional first parameter is a function which will produce the label that will then be used to name the checkpoint.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the checkpoint has been set.
The Checkpoints
object for which the callback will set the checkpoint (indicated by the hook's checkpointsOrCheckpointsId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetCheckpointCallback
hook to create an event handler which sets a checkpoint when the span
element is clicked.
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useSetCheckpointCallback} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const handleClick = useSetCheckpointCallback(
(e) => `with #${e.target.id} button`,
[],
checkpoints,
(checkpointId, checkpoints, label) =>
console.log(`Checkpoint ${checkpointId} set, ${label}`),
);
return (
<span id="span" onClick={handleClick}>
Set
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const _span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
// User clicks the <span> element:
// -> _span MouseEvent('click', {bubbles: true})
// -> 'Checkpoint 1 set, with #span button'
Since
v1.0.0
useUndoInformation
The useUndoInformation
hook returns an UndoOrRedoInformation
array that indicates if and how you can move the state of the underlying Store
backward to the previous checkpoint.
useUndoInformation(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): UndoOrRedoInformation
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | UndoOrRedoInformation |
|
This hook is useful if you are building an undo button: the information contains whether an undo action is available (to enable the button), the callback to perform the undo action, the current checkpoint Id
that will be undone, and its label, if available.
Example
This example uses the useUndoInformation
hook to create an undo button.
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useUndoInformation} from 'tinybase/ui-react';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const [canUndo, handleUndo, _id, label] =
useUndoInformation(checkpoints);
return canUndo ? (
<span onClick={handleUndo}>Undo {label}</span>
) : (
<span>Nothing to undo</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>Nothing to undo</span>'
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint('color');
console.log(app.innerHTML);
// -> '<span>Undo color</span>'
Since
v1.0.0
Indexes hooks
This is the collection of indexes hooks within the ui-react
module. There are 9 indexes hooks in total.
useSliceRowIds
The useSliceRowIds
hook gets the list of Row
Ids
in a given Slice
, and registers a listener so that any changes to that result will cause a re-render.
useSliceRowIds(
indexId: string,
sliceId: string,
indexesOrIndexesId?: IndexesOrIndexesId,
): Ids
Type | Description | |
---|---|---|
indexId | string | |
sliceId | string | |
indexesOrIndexesId? | IndexesOrIndexesId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Indexes
object or a set of Indexes
objects named by Id
. The useSliceRowIds
hook lets you indicate which Indexes
object to get data for: omit the optional final parameter for the default context Indexes
object, provide an Id
for a named context Indexes
object, or provide an Indexes
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row
Ids
in the Slice
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates an Indexes
object outside the application, which is used in the useSliceRowIds
hook by reference. A change to the Row
Ids
in the Slice
re-renders the component.
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useSliceRowIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<span>
{JSON.stringify(useSliceRowIds('bySpecies', 'dog', indexes))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<span>["fido","cujo","toto"]</span>'
This example creates a Provider context into which a default Indexes
object is provided. A component within it then uses the useSliceRowIds
hook.
import {Provider, useSliceRowIds} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useSliceRowIds('bySpecies', 'dog'))}</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a default Indexes
object is provided. A component within it then uses the useSliceRowIds
hook.
import {Provider, useSliceRowIds} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexesById={{petIndexes: indexes}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useSliceRowIds('bySpecies', 'dog', 'petIndexes'))}
</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
Since
v1.0.0
useSliceRowIdsListener
The useSliceRowIdsListener
hook registers a listener function with the Indexes
object that will be called whenever the Row
Ids
in a Slice
change.
useSliceRowIdsListener(
indexId: IdOrNull,
sliceId: IdOrNull,
listener: SliceRowIdsListener,
listenerDeps?: DependencyList,
indexesOrIndexesId?: IndexesOrIndexesId,
): void
Type | Description | |
---|---|---|
indexId | IdOrNull | |
sliceId | IdOrNull | |
listener | SliceRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
indexesOrIndexesId? | IndexesOrIndexesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSliceRowIds
hook).
You can either listen to a single Slice
(by specifying the Index
Id
and Slice
Id
as the method's first two parameters), or changes to any Slice
(by providing null
wildcards).
Both, either, or neither of the indexId
and sliceId
parameters can be wildcarded with null
. You can listen to a specific Slice
in a specific Index
, any Slice
in a specific Index
, a specific Slice
in any Index
, or any Slice
in any Index
.
Unlike the addSliceRowIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useSliceRowIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Indexes
object will be deleted.
Example
This example uses the useSliceRowIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Indexes
object.
import {Provider, useSliceRowIdsListener} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => {
useSliceRowIdsListener('bySpecies', 'dog', () =>
console.log('Slice Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App indexes={indexes} />);
console.log(indexes.getListenerStats().sliceRowIds);
// -> 1
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Slice Row Ids changed'
root.unmount();
console.log(indexes.getListenerStats().sliceRowIds);
// -> 0
Since
v1.0.0
useCreateIndexes
The useCreateIndexes
hook is used to create an Indexes
object within a React application with convenient memoization.
useCreateIndexes(
store: undefined | Store,
create: (store: Store) => Indexes,
createDeps?: DependencyList,
): Indexes | undefined
Type | Description | |
---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Indexes | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Indexes | undefined | A reference to the |
It is possible to create an Indexes
object outside of the React app with the regular createIndexes
function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Indexes
object being created every time the app renders or re-renders, since v5.0 the this hook performs the creation in an effect. As a result it will return undefined
on the brief first render (or if the Store
is not yet defined), which you should defend against.
If your create
function contains other dependencies, the changing of which should also cause the Indexes
object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Indexes
object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates an Indexes
object at the top level of a React application. Even though the App component is rendered twice, the Indexes
object creation only occurs once by default.
import {createIndexes, createStore} from 'tinybase';
import {useCreateIndexes, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
);
const indexes = useCreateIndexes(store, (store) => {
console.log('Indexes created');
return createIndexes(store).setIndexDefinition(
'bySpecies',
'pets',
'species',
);
});
return <span>{JSON.stringify(indexes?.getSliceIds('bySpecies'))}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Indexes created'
root.render(<App />);
// No second Indexes creation
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
This example creates an Indexes
object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateIndexes
hook takes the cellToIndex prop as a dependency, and so the Indexes
object is created again on the second render.
import {createIndexes, createStore} from 'tinybase';
import {useCreateIndexes, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({cellToIndex}) => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
}),
);
const indexes = useCreateIndexes(
store,
(store) => {
console.log(`Index created for ${cellToIndex} cell`);
return createIndexes(store).setIndexDefinition(
'byCell',
'pets',
cellToIndex,
);
},
[cellToIndex],
);
return <span>{JSON.stringify(indexes?.getSliceIds('byCell'))}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App cellToIndex="species" />);
// -> 'Index created for species cell'
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
root.render(<App cellToIndex="color" />);
// -> 'Index created for color cell'
console.log(app.innerHTML);
// -> '<span>["brown","black"]</span>'
Since
v1.0.0
useIndexIds
The useIndexIds
hook gets an array of the Index
Ids
registered with an Indexes
object, and registers a listener so that any changes to that result will cause a re-render.
useIndexIds(indexesOrIndexesId?: IndexesOrIndexesId): Ids
Type | Description | |
---|---|---|
indexesOrIndexesId? | IndexesOrIndexesId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Indexes
object or a set of Indexes
objects named by Id
. The useIndexIds
hook lets you indicate which Indexes
object to get data for: omit the optional final parameter for the default context Indexes
object, provide an Id
for a named context Indexes
object, or provide an Indexes
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Index
Ids
in the Indexes
object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Indexes
object outside the application, which is used in the useIndexIds
hook by reference. A newly-registered Index
re-renders the component.
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useIndexIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
const App = () => <span>{JSON.stringify(useIndexIds(indexes))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(app.innerHTML);
// -> '<span>["bySpecies"]</span>'
Since
v4.1.0
useIndexes
The useIndexes
hook is used to get a reference to an Indexes
object from within a Provider
component context.
useIndexes(id?: string): Indexes | undefined
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Indexes | undefined | A reference to the |
A Provider
component is used to wrap part of an application in a context. It can contain a default Indexes
object (or a set of Indexes
objects named by Id
) that can be easily accessed without having to be passed down as props through every component.
The useIndexes
hook lets you either get a reference to the default Indexes
object (when called without a parameter), or one of the Indexes
objects that are named by Id
(when called with an Id
parameter).
Examples
This example creates a Provider context into which a default Indexes
object is provided. A component within it then uses the useIndexes
hook to get a reference to the Indexes
object again, without the need to have it passed as a prop.
import {Provider, useIndexes} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => <span>{useIndexes().getListenerStats().sliceIds}</span>;
const indexes = createIndexes(createStore());
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which an Indexes
object is provided, named by Id
. A component within it then uses the useIndexes
hook with that Id
to get a reference to the Indexes
object again, without the need to have it passed as a prop.
import {Provider, useIndexes} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexesById={{petStore: indexes}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useIndexes('petStore').getListenerStats().sliceIds}</span>
);
const indexes = createIndexes(createStore());
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useIndexesIds
The useIndexesIds
hook is used to retrieve the Ids
of all the named Indexes
objects present in the current Provider
component context.
useIndexesIds(): Ids
Example
This example adds two named Indexes
objects to a Provider context and an inner component accesses their Ids
.
import {
Provider,
useCreateIndexes,
useCreateStore,
useIndexesIds,
} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store1 = useCreateStore(createStore);
const indexes1 = useCreateIndexes(store1, createIndexes);
const store2 = useCreateStore(createStore);
const indexes2 = useCreateIndexes(store2, createIndexes);
return (
<Provider indexesById={{indexes1, indexes2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useIndexesIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["indexes1","indexes2"]</span>'
Since
v4.1.0
useIndexesOrIndexesById
The useIndexesOrIndexesById
hook is used to get a reference to an Indexes
object from within a Provider
component context, or have it passed directly to this hook.
useIndexesOrIndexesById(indexesOrIndexesId?: IndexesOrIndexesId): Indexes | undefined
Type | Description | |
---|---|---|
indexesOrIndexesId? | IndexesOrIndexesId | Either an |
returns | Indexes | undefined | A reference to the |
This is mostly of use when you are developing a component that needs an Indexes
object and which might have been passed in explicitly to the component or is to be picked up from the context by Id
(a common pattern for Indexes
-based components).
This hook is unlikely to be used often. For most situations, you will want to use the useIndexes
hook.
Example
This example creates a Provider context into which a default Indexes
object is provided. A component within it then uses the useIndexesOrIndexesById
hook to get a reference to the Indexes
object again, without the need to have it passed as a prop. Note however, that unlike the useIndexes
hook example, this component would also work if you were to pass the Indexes
object directly into it, making it more portable.
import {Provider, useIndexesOrIndexesById} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = ({indexes}) => (
<span>
{JSON.stringify(useIndexesOrIndexesById(indexes).getIndexIds())}
</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["bySpecies"]</span>'
Since
v4.1.0
useSliceIds
The useSliceIds
hook gets the list of Slice
Ids
in an Index
, and registers a listener so that any changes to that result will cause a re-render.
useSliceIds(
indexId: string,
indexesOrIndexesId?: IndexesOrIndexesId,
): Ids
Type | Description | |
---|---|---|
indexId | string | |
indexesOrIndexesId? | IndexesOrIndexesId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Indexes
object or a set of Indexes
objects named by Id
. The useSliceIds
hook lets you indicate which Indexes
object to get data for: omit the optional final parameter for the default context Indexes
object, provide an Id
for a named context Indexes
object, or provide a Indexes
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Slice
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates an Indexes
object outside the application, which is used in the useSliceIds
hook by reference. A change to the Slice
Ids
re-renders the component.
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useSliceIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<span>{JSON.stringify(useSliceIds('bySpecies', indexes))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
store.setRow('pets', 'lowly', {species: 'worm'});
console.log(app.innerHTML);
// -> '<span>["dog","cat","worm"]</span>'
This example creates a Provider context into which a default Indexes
object is provided. A component within it then uses the useSliceIds
hook.
import {Provider, useSliceIds} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useSliceIds('bySpecies'))}</span>;
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
This example creates a Provider context into which a default Indexes
object is provided. A component within it then uses the useSliceIds
hook.
import {Provider, useSliceIds} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexesById={{petIndexes: indexes}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useSliceIds('bySpecies', 'petIndexes'))}</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
Since
v1.0.0
useSliceIdsListener
The useSliceIdsListener
hook registers a listener function with the Indexes
object that will be called whenever the Slice
Ids
in an Index
change.
useSliceIdsListener(
indexId: IdOrNull,
listener: SliceIdsListener,
listenerDeps?: DependencyList,
indexesOrIndexesId?: IndexesOrIndexesId,
): void
Type | Description | |
---|---|---|
indexId | IdOrNull | |
listener | SliceIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
indexesOrIndexesId? | IndexesOrIndexesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSliceIds
hook).
You can either listen to a single Index
(by specifying the Index
Id
as the method's first parameter), or changes to any Index
(by providing a null
wildcard).
Unlike the addSliceIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useSliceIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Indexes
object will be deleted.
Example
This example uses the useSliceIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Indexes
object.
import {Provider, useSliceIdsListener} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => {
useSliceIdsListener('bySpecies', () => console.log('Slice Ids changed'));
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App indexes={indexes} />);
console.log(indexes.getListenerStats().sliceIds);
// -> 1
store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids changed'
root.unmount();
console.log(indexes.getListenerStats().sliceIds);
// -> 0
Since
v1.0.0
Metrics hooks
This is the collection of metrics hooks within the ui-react
module. There are 8 metrics hooks in total.
useCreateMetrics
The useCreateMetrics
hook is used to create a Metrics
object within a React application with convenient memoization.
useCreateMetrics(
store: undefined | Store,
create: (store: Store) => Metrics,
createDeps?: DependencyList,
): Metrics | undefined
Type | Description | |
---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Metrics | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Metrics | undefined | A reference to the |
It is possible to create a Metrics
object outside of the React app with the regular createMetrics
function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Metrics
object being created every time the app renders or re-renders, since v5.0 this hook performs the creation in an effect. As a result it will return undefined
on the brief first render (or if the Store
is not yet defined), which you should defend against.
If your create
function contains other dependencies, the changing of which should also cause the Metrics
object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Metrics
object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Metrics
object at the top level of a React application. Even though the App component is rendered twice, the Metrics
object creation only occurs once by default.
import {createMetrics, createStore} from 'tinybase';
import {useCreateMetrics, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('species', {dog: {price: 5}, cat: {price: 4}}),
);
const metrics = useCreateMetrics(store, (store) => {
console.log('Metrics created');
return createMetrics(store).setMetricDefinition(
'speciesCount',
'species',
);
});
return <span>{metrics?.getMetric('speciesCount')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Metrics created'
root.render(<App />);
// No second Metrics creation
console.log(app.innerHTML);
// -> '<span>2</span>'
This example creates a Metrics
object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateMetrics
hook takes the tableToCount prop as a dependency, and so the Metrics
object is created again on the second render.
import {createMetrics, createStore} from 'tinybase';
import {useCreateMetrics, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({tableToCount}) => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}, cat: {price: 4}}),
);
const metrics = useCreateMetrics(
store,
(store) => {
console.log(`Count created for ${tableToCount} table`);
return createMetrics(store).setMetricDefinition(
'tableCount',
tableToCount,
);
},
[tableToCount],
);
return <span>{metrics?.getMetric('tableCount')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App tableToCount="pets" />);
// -> 'Count created for pets table'
console.log(app.innerHTML);
// -> '<span>1</span>'
root.render(<App tableToCount="species" />);
// -> 'Count created for species table'
console.log(app.innerHTML);
// -> '<span>2</span>'
Since
v1.0.0
useMetric
The useMetric
hook gets the current value of a Metric
, and registers a listener so that any changes to that result will cause a re-render.
useMetric(
metricId: string,
metricsOrMetricsId?: MetricsOrMetricsId,
): number | undefined
Type | Description | |
---|---|---|
metricId | string | |
metricsOrMetricsId? | MetricsOrMetricsId | The |
returns | number | undefined | The numeric value of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Metrics
object or a set of Metrics
objects named by Id
. The useMetric
hook lets you indicate which Metrics
object to get data for: omit the optional final parameter for the default context Metrics
object, provide an Id
for a named context Metrics
object, or provide a Metrics
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Metric
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Metrics
object outside the application, which is used in the useMetric
hook by reference. A change to the Metric
re-renders the component.
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useMetric} from 'tinybase/ui-react';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const App = () => <span>{useMetric('highestPrice', metrics)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>5</span>'
store.setCell('species', 'horse', 'price', 20);
console.log(app.innerHTML);
// -> '<span>20</span>'
This example creates a Provider context into which a default Metrics
object is provided. A component within it then uses the useMetric
hook.
import {Provider, useMetric} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => <span>{useMetric('highestPrice')}</span>;
const metrics = createMetrics(
createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
}),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5</span>'
This example creates a Provider context into which a default Metrics
object is provided. A component within it then uses the useMetric
hook.
import {Provider, useMetric} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({metrics}) => (
<Provider metricsById={{petMetrics: metrics}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useMetric('highestPrice', 'petMetrics')}</span>;
const metrics = createMetrics(
createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
}),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5</span>'
Since
v1.0.0
useMetricIds
The useMetricIds
hook gets an array of the Metric
Ids
registered with a Metrics
object, and registers a listener so that any changes to that result will cause a re-render.
useMetricIds(metricsOrMetricsId?: MetricsOrMetricsId): Ids
Type | Description | |
---|---|---|
metricsOrMetricsId? | MetricsOrMetricsId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Metrics
object or a set of Metrics
objects named by Id
. The useMetricIds
hook lets you indicate which Metrics
object to get data for: omit the optional final parameter for the default context Metrics
object, provide an Id
for a named context Metrics
object, or provide a Metrics
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Metric
Ids
in the Metrics
object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Metrics
object outside the application, which is used in the useMetricIds
hook by reference. A newly-registered Metric
re-renders the component.
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useMetricIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const metrics = createMetrics(store);
const App = () => <span>{JSON.stringify(useMetricIds(metrics))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
const addMetricDefinition = () =>
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
addMetricDefinition();
console.log(app.innerHTML);
// -> '<span>["highestPrice"]</span>'
Since
v4.1.0
useMetricListener
The useMetricListener
hook registers a listener function with the Metrics
object that will be called whenever the value of a specified Metric
changes.
useMetricListener(
metricId: IdOrNull,
listener: MetricListener,
listenerDeps?: DependencyList,
metricsOrMetricsId?: MetricsOrMetricsId,
): void
Type | Description | |
---|---|---|
metricId | IdOrNull | |
listener | MetricListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
metricsOrMetricsId? | MetricsOrMetricsId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useMetric
hook).
You can either listen to a single Metric
(by specifying the Metric
Id
as the method's first parameter), or changes to any Metric
(by providing a null
wildcard).
Unlike the addMetricListener
method, which returns a listener Id
and requires you to remove it manually, the useMetricListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Metrics
object, will be deleted.
Example
This example uses the useMetricListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Metrics
object.
import {Provider, useMetricListener} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => {
useMetricListener('highestPrice', () => console.log('Metric changed'));
return <span>App</span>;
};
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App metrics={metrics} />);
console.log(metrics.getListenerStats().metric);
// -> 1
store.setCell('species', 'horse', 'price', 20);
// -> 'Metric changed'
root.unmount();
console.log(metrics.getListenerStats().metric);
// -> 0
Since
v1.0.0
useMetrics
The useMetrics
hook is used to get a reference to a Metrics
object from within a Provider
component context.
useMetrics(id?: string): Metrics | undefined
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Metrics | undefined | A reference to the |
A Provider
component is used to wrap part of an application in a context. It can contain a default Metrics
object (or a set of Metrics
objects named by Id
) that can be easily accessed without having to be passed down as props through every component.
The useMetrics
hook lets you either get a reference to the default Metrics
object (when called without a parameter), or one of the Metrics
objects that are named by Id
(when called with an Id
parameter).
Examples
This example creates a Provider context into which a default Metrics
object is provided. A component within it then uses the useMetrics
hook to get a reference to the Metrics
object again, without the need to have it passed as a prop.
import {Provider, useMetrics} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => <span>{useMetrics().getListenerStats().metric}</span>;
const metrics = createMetrics(createStore());
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Metrics
object is provided, named by Id
. A component within it then uses the useMetrics
hook with that Id
to get a reference to the Metrics
object again, without the need to have it passed as a prop.
import {Provider, useMetrics} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({metrics}) => (
<Provider metricsById={{petStore: metrics}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useMetrics('petStore').getListenerStats().metric}</span>
);
const metrics = createMetrics(createStore());
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useMetricsIds
The useMetricsIds
hook is used to retrieve the Ids
of all the named Metrics
objects present in the current Provider
component context.
useMetricsIds(): Ids
Example
This example adds two named Metrics
objects to a Provider context and an inner component accesses their Ids
.
import {
Provider,
useCreateMetrics,
useCreateStore,
useMetricsIds,
} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store1 = useCreateStore(createStore);
const metrics1 = useCreateMetrics(store1, createMetrics);
const store2 = useCreateStore(createStore);
const metrics2 = useCreateMetrics(store2, createMetrics);
return (
<Provider metricsById={{metrics1, metrics2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useMetricsIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["metrics1","metrics2"]</span>'
Since
v4.1.0
useMetricsOrMetricsById
The useMetricsOrMetricsById
hook is used to get a reference to a Metrics
object from within a Provider
component context, or have it passed directly to this hook.
useMetricsOrMetricsById(metricsOrMetricsId?: MetricsOrMetricsId): Metrics | undefined
Type | Description | |
---|---|---|
metricsOrMetricsId? | MetricsOrMetricsId | Either an |
returns | Metrics | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Metrics
object and which might have been passed in explicitly to the component or is to be picked up from the context by Id
(a common pattern for Metrics
-based components).
This hook is unlikely to be used often. For most situations, you will want to use the useMetrics
hook.
Example
This example creates a Provider context into which a default Metrics
object is provided. A component within it then uses the useMetricsOrMetricsById
hook to get a reference to the Metrics
object again, without the need to have it passed as a prop. Note however, that unlike the useMetrics
hook example, this component would also work if you were to pass the Metrics
object directly into it, making it more portable.
import {Provider, useMetricsOrMetricsById} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = ({metrics}) => (
<span>
{JSON.stringify(useMetricsOrMetricsById(metrics).getMetricIds())}
</span>
);
const metrics = createMetrics(
createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
}),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>["highestPrice"]</span>'
Since
v4.1.0
useProvideMetrics
The useProvideMetrics
hook is used to add a Metrics
object by Id
to a Provider
component, but imperatively from a component within it.
useProvideMetrics(
metricsId: string,
metrics: Metrics,
): void
Type | Description | |
---|---|---|
metricsId | string | The |
metrics | Metrics | The |
returns | void | This has no return value. |
Normally you will register a Metrics
object by Id
in a context by using the metricsById
prop of the top-level Provider
component. This hook, however, lets you dynamically add a new Metrics
object to the context, from within a descendent component. This is useful for applications where the set of Metrics
objects is not known at the time of the first render of the root Provider.
A Metrics
object added to the Provider context in this way will be available to other components within the context (using the useMetrics
hook and so on). If you use the same Id
as an existing Metrics
object registration, the new one will take priority over one provided by the metricsById
prop.
Note that other components that consume a Metrics
object registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe useMetrics('petMetrics')?
to do this.
Example
This example creates a Provider context. A child component registers a Metrics
object into it which is then consumable by a peer child component.
import {
Provider,
useCreateMetrics,
useCreateStore,
useMetrics,
useProvideMetrics,
} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => (
<Provider>
<RegisterMetrics />
<ConsumeMetrics />
</Provider>
);
const RegisterMetrics = () => {
const store = useCreateStore(() =>
createStore().setCell('pets', 'fido', 'color', 'brown'),
);
const metrics = useCreateMetrics(store, (store) =>
createMetrics(store).setMetricDefinition('petCount', 'pets', 'count'),
);
useProvideMetrics('petMetrics', metrics);
return null;
};
const ConsumeMetrics = () => (
<span>{useMetrics('petMetrics')?.getMetric('petCount')}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>1</span>'
Since
v5.3.0
Persister hooks
This is the collection of persister hooks within the ui-react
module. There are 6 persister hooks in total.
useCreatePersister
The useCreatePersister
hook is used to create a Persister
within a React application along with convenient memoization and callbacks.
useCreatePersister<Persist, PersisterOrUndefined>(
store: undefined | PersistedStore<Persist>,
create: (store: PersistedStore<Persist>) => PersisterOrUndefined | Promise<PersisterOrUndefined>,
createDeps?: DependencyList,
then?: (persister: Persister<Persist>) => Promise<void>,
thenDeps?: DependencyList,
destroy?: (persister: Persister<Persist>) => void,
destroyDeps?: DependencyList,
): PersisterOrUndefined
Type | Description | |
---|---|---|
store | undefined | PersistedStore<Persist> | A reference to the |
create | (store: PersistedStore<Persist>) => PersisterOrUndefined | Promise<PersisterOrUndefined> | A (possibly asynchronous) function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
then? | (persister: Persister<Persist>) => Promise<void> | An optional callback for performing asynchronous post-creation steps on the |
thenDeps? | DependencyList | An optional array of dependencies for the |
destroy? | (persister: Persister<Persist>) => void | An optional callback whenever the |
destroyDeps? | DependencyList | An optional array of dependencies for the |
returns | PersisterOrUndefined | A reference to the |
It is possible to create a Persister
outside of the React app with the regular createPersister function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Persister
being created every time the app renders or re-renders, since v5.0 the this hook performs the creation in an effect.
If your create
function (the second parameter to the hook) contains dependencies, the changing of which should cause the Persister
to be recreated, you can provide them in an array in the third parameter, just as you would for any React hook with dependencies. The Store
passed in as the first parameter of this hook is used as a dependency by default.
A second callback, called then
, can be provided as the fourth parameter. This is called after the creation, and, importantly, can be asynchronous, so that you can configure the Persister
with the startAutoLoad
method and startAutoSave
method, for example. If this callback contains dependencies, the changing of which should cause the Persister
to be reconfigured, you can provide them in an array in the fifth parameter. The Persister
itself is used as a dependency by default.
See the note below about possible future deprecation of the then
callback, however.
Since v4.3.0, the create
function can return undefined, meaning that you can enable or disable persistence conditionally within this hook. This is useful for applications which might turn on or off their cloud persistence or collaboration features. This hook can return undefined
if the Store
is not yet defined, which you should defend against.
Since v4.3.19, a destroy
function can be provided which will be called after an old Persister
is destroyed due to a change in the createDeps
dependencies that causes a new one to be created. Use this to clean up any underlying storage objects that you set up during the then
function, for example. If this callback itself contains additional dependencies, you can provide them in an array in the seventh parameter.
Since v5.2, the create
function can be asynchronous, which now makes it a suitable place to call the Persister
's startAutoLoad and startAutoSave
methods. At some major version in the future, the then
parameter will be removed, since that only really existed to perform such asynchronous initial tasks.
This hook ensures the Persister
object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Persister
at the top level of a React application. Even though the App component is rendered twice, the Persister
creation only occurs once by default.
import {
useCreatePersister,
useCreateStore,
useTables,
} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const App = () => {
const store = useCreateStore(createStore);
useCreatePersister(
store,
(store) => {
console.log('Persister created');
return createSessionPersister(store, 'pets');
},
[],
async (persister) => {
await persister.startAutoLoad();
await persister.startAutoSave();
},
);
return <span>{JSON.stringify(useTables(store))}</span>;
};
sessionStorage.setItem(
'pets',
'[{"pets":{"fido":{"species":"dog"}}}, {}]',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Persister created'
// ...
root.render(<App />);
// No second Persister creation
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"fido\":{\"species\":\"dog\"}}}</span>'
root.unmount();
This example creates a Persister
at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreatePersister
hook takes the sessionKey
prop as a dependency, and so the Persister
object is created again on the second render. The first is destroyed and the destroy
parameter is called for it.
import {
useCreatePersister,
useCreateStore,
useTables,
} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const App = ({sessionKey}) => {
const store = useCreateStore(createStore);
useCreatePersister(
store,
(store) => {
console.log(`Persister created for session key ${sessionKey}`);
return createSessionPersister(store, sessionKey);
},
[sessionKey],
async (persister) => {
await persister.startAutoLoad();
},
[],
(persister) =>
console.log(
`Persister destroyed for session key ${persister.getStorageName()}`,
),
);
return <span>{JSON.stringify(useTables(store))}</span>;
};
sessionStorage.setItem(
'fidoStore',
'[{"pets":{"fido":{"species":"dog"}}}, {}]',
);
sessionStorage.setItem(
'cujoStore',
'[{"pets":{"cujo":{"species":"dog"}}}, {}]',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App sessionKey="fidoStore" />);
// -> 'Persister created for session key fidoStore'
// ...
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"fido\":{\"species\":\"dog\"}}}</span>'
root.render(<App sessionKey="cujoStore" />);
// -> 'Persister created for session key cujoStore'
// -> 'Persister destroyed for session key fidoStore'
// ...
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"cujo\":{\"species\":\"dog\"}}}</span>'
root.unmount();
// -> 'Persister destroyed for session key cujoStore'
Since
v1.0.0
usePersister
The usePersister
hook is used to get a reference to a Persister
object from within a Provider
component context.
usePersister(id?: string): AnyPersister | undefined
Type | Description | |
---|---|---|
id? | string | An optional |
returns | AnyPersister | undefined | A reference to the |
A Provider
component is used to wrap part of an application in a context. It can contain a default Persister
object (or a set of Persister
objects named by Id
) that can be easily accessed without having to be passed down as props through every component.
The usePersister
hook lets you either get a reference to the default Persister
object (when called without a parameter), or one of the Persister
objects that are named by Id
(when called with an Id
parameter).
Examples
This example creates a Provider context into which a default Persister
object is provided. A component within it then uses the usePersister
hook to get a reference to the Persister
object again, without the need to have it passed as a prop.
import {Provider, usePersister} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const App = ({persister}) => (
<Provider persister={persister}>
<Pane />
</Provider>
);
const Pane = () => <span>{usePersister().getStatus()}</span>;
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Persister
object is provided, named by Id
. A component within it then uses the usePersister
hook with that Id
to get a reference to the Persister
object again, without the need to have it passed as a prop.
import {Provider, usePersister} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const App = ({persister}) => (
<Provider persistersById={{petPersister: persister}}>
<Pane />
</Provider>
);
const Pane = () => <span>{usePersister('petPersister').getStatus()}</span>;
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
usePersisterIds
The usePersisterIds
hook is used to retrieve the Ids
of all the named Persister
objects present in the current Provider
component context.
usePersisterIds(): Ids
Example
This example adds two named Persister
objects to a Provider context and an inner component accesses their Ids
.
import {
Provider,
useCreatePersister,
useCreateStore,
usePersisterIds,
} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const App = () => {
const store1 = useCreateStore(createStore);
const persister1 = useCreatePersister(store1, (store1) =>
createSessionPersister(store1, 'pets1'),
);
const store2 = useCreateStore(createStore);
const persister2 = useCreatePersister(store2, (store2) =>
createSessionPersister(store2, 'pets2'),
);
return (
<Provider persistersById={{persister1, persister2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(usePersisterIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
// ...
console.log(app.innerHTML);
// -> '<span>["persister1","persister2"]</span>'
Since
v5.3.0
usePersisterOrPersisterById
The usePersisterOrPersisterById
hook is used to get a reference to a Persister
object from within a Provider
component context, or have it passed directly to this hook.
usePersisterOrPersisterById(persisterOrPersisterId?: PersisterOrPersisterId): AnyPersister | undefined
Type | Description | |
---|---|---|
persisterOrPersisterId? | PersisterOrPersisterId | Either an |
returns | AnyPersister | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Persister
object and which might have been passed in explicitly to the component or is to be picked up from the context by Id
(a common pattern for Persister
-based components).
This is unlikely to be used often. For most situations, you will want to use the usePersister
hook.
Example
This example creates a Provider context into which a default Persister
object is provided. A component within it then uses the usePersisterOrPersisterById
hook to get a reference to the Persister
object again, without the need to have it passed as a prop. Note however, that unlike the usePersister
hook example, this component would also work if you were to pass the Persister
object directly into it, making it more portable.
import {Provider, usePersisterOrPersisterById} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const App = ({persister}) => (
<Provider persister={persister}>
<Pane />
</Provider>
);
const Pane = ({persister}) => (
<span>{usePersisterOrPersisterById(persister).getStatus()}</span>
);
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
usePersisterStatus
The usePersisterStatus
hook returns a the status of a Persister
, and registers a listener so that any changes to it will cause a re-render.
usePersisterStatus(persisterOrPersisterId?: PersisterOrPersisterId): Status
Type | Description | |
---|---|---|
persisterOrPersisterId? | PersisterOrPersisterId | The |
returns | Status | The status of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Persister
or a set of Persister
objects named by Id
. The usePersisterStatus
hook lets you indicate which Persister
to get data for: omit the optional parameter for the default context Persister
, provide an Id
for a named context Persister
, or provide a Persister
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Persister
status will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Persister
outside the application, which is used in the usePersisterStatus
hook by reference. A change to the status of the Persister
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
import {usePersisterStatus} from 'tinybase/ui-react';
const persister = createSessionPersister(createStore(), 'pets');
const App = () => <span>{usePersisterStatus(persister)}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a default Persister
is provided. A component within it then uses the usePersisterStatus
hook.
import {Provider, usePersisterStatus} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const App = ({persister}) => (
<Provider persister={persister}>
<Pane />
</Provider>
);
const Pane = () => <span>{usePersisterStatus()}</span>;
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
createRoot(app).render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Persister
is provided, named by Id
. A component within it then uses the usePersisterStatus
hook.
import {Provider, usePersisterStatus} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const App = ({persister}) => (
<Provider persistersById={{petPersister: persister}}>
<Pane />
</Provider>
);
const Pane = () => <span>{usePersisterStatus('petPersister')}</span>;
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
createRoot(app).render(<App persister={persister} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
usePersisterStatusListener
The usePersisterStatusListener
hook registers a listener function with the Persister
that will be called when its status changes.
usePersisterStatusListener(
listener: StatusListener<StoreOrMergeableStore>,
listenerDeps?: DependencyList,
persisterOrPersisterId?: PersisterOrPersisterId,
): void
Type | Description | |
---|---|---|
listener | StatusListener<StoreOrMergeableStore> | The function that will be called whenever the status of the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
persisterOrPersisterId? | PersisterOrPersisterId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the usePersisterStatus
hook).
Unlike the addStatusListener
method, which returns a listener Id
and requires you to remove it manually, the usePersisterStatusListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Persister
will be deleted.
Example
This example uses the usePersisterStatusListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Persister
.
import {Provider, usePersisterStatusListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createSessionPersister} from 'tinybase/persisters/persister-browser';
import {createStore} from 'tinybase';
const App = ({persister}) => (
<Provider persister={persister}>
<Pane />
</Provider>
);
const Pane = () => {
usePersisterStatusListener((persister, status) =>
console.log('Persister status changed: ' + status),
);
return <span>App</span>;
};
const persister = createSessionPersister(createStore(), 'pets');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App persister={persister} />);
persister.load();
// -> 'Persister status changed: 1'
// ...
// -> 'Persister status changed: 0'
persister.save();
// -> 'Persister status changed: 2'
// ...
// -> 'Persister status changed: 0'
Since
v5.3.0
Queries hooks
This is the collection of queries hooks within the ui-react
module. There are 21 queries hooks in total.
useResultTable
The useResultTable
hook returns an object containing the entire data of the ResultTable
of the given query, and registers a listener so that any changes to that result will cause a re-render.
useResultTable(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Table
Type | Description | |
---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Table | An object containing the entire data of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultTable
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the query result will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries
object outside the application, which is used in the useTable
hook by reference. A change to the data in the query re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useResultTable} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultTable('dogColors', queries))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut"},"cujo":{"color":"black"}}</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultTable
hook.
import {Provider, useResultTable} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTable('dogColors'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultTable
hook.
import {Provider, useResultTable} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTable('dogColors', 'petQueries'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
Since
v2.0.0
useResultTableCellIds
The useResultTableCellIds
hook returns the Ids
of every Cell
used across the whole ResultTable
of the given query, and registers a listener so that any changes to those Ids
will cause a re-render.
useResultTableCellIds(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Ids | An array of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultTableCellIds
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Cell
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries
object outside the application, which is used in the useResultTableCellIds
hook by reference. A change to the data in the query re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useResultTableCellIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColorsAndLegs',
'pets',
({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(useResultTableCellIds('dogColorsAndLegs', queries))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
store.setCell('pets', 'cujo', 'legs', 4);
console.log(app.innerHTML);
// -> '<span>["color","legs"]</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultTableCellIds
hook.
import {Provider, useResultTableCellIds} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTableCellIds('dogColorsAndLegs'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black', legs: 4},
}),
).setQueryDefinition('dogColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["color","legs"]</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultTableCellIds
hook.
import {Provider, useResultTableCellIds} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useResultTableCellIds('dogColorsAndLegs', 'petQueries'),
)}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black', legs: 4},
}),
).setQueryDefinition('dogColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["color","legs"]</span>'
Since
v4.1.0
useResultTableCellIdsListener
The useResultTableCellIdsListener
hook registers a listener function with a Queries
object that will be called whenever the Cell
Ids
that appear anywhere in a ResultTable
change.
useResultTableCellIdsListener(
queryId: IdOrNull,
listener: ResultTableCellIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultTableCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultTableCellIds
hook).
You can either listen to a single ResultTable
(by specifying a query Id
as the method's first parameter) or changes to any ResultTable
(by providing a null
wildcard).
Unlike the addResultTableCellIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useResultTableCellIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultTableCellIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
import {Provider, useResultTableCellIdsListener} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultTableCellIdsListener('petColorsAndLegs', () =>
console.log('Result Cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColorsAndLegs',
'pets',
({select}) => {
select('color');
select('legs');
},
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().tableCellIds);
// -> 1
store.setCell('pets', 'cujo', 'legs', 4);
// -> 'Result Cell Ids changed'
root.unmount();
console.log(queries.getListenerStats().tableCellIds);
// -> 0
Since
v4.1.0
useResultTableListener
The useResultTableListener
hook registers a listener function with a Queries
object that will be called whenever data in a ResultTable
changes.
useResultTableListener(
queryId: IdOrNull,
listener: ResultTableListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultTableListener | The function that will be called whenever data in the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultTable
hook).
You can either listen to a single ResultTable
(by specifying a query Id
as the method's first parameter) or changes to any ResultTable
(by providing a null
wildcard).
Unlike the addResultTableListener
method, which returns a listener Id
and requires you to remove it manually, the useResultTableListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultTableListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
import {Provider, useResultTableListener} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultTableListener('petColors', () =>
console.log('Result table changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().table);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result table changed'
root.unmount();
console.log(queries.getListenerStats().table);
// -> 0
Since
v2.0.0
useResultRowIds
The useResultRowIds
hook returns the Ids
of every Row
in the ResultTable
of the given query, and registers a listener so that any changes to those Ids
will cause a re-render.
useResultRowIds(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Ids | An array of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultRowIds
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Row
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries
object outside the application, which is used in the useResultRowIds
hook by reference. A change to the data in the query re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useResultRowIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultRowIds('dogColors', queries))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultRowIds
hook.
import {Provider, useResultRowIds} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRowIds('dogColors'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultRowIds
hook.
import {Provider, useResultRowIds} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRowIds('dogColors', 'petQueries'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
Since
v2.0.0
useResultRowIdsListener
The useResultRowIdsListener
hook registers a listener function with a Queries
object that will be called whenever the Row
Ids
in a ResultTable
change.
useResultRowIdsListener(
queryId: IdOrNull,
listener: ResultRowIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultRowIds
hook).
You can either listen to a single ResultTable
(by specifying a query Id
as the method's first parameter) or changes to any ResultTable
(by providing a null
wildcard).
Unlike the addResultRowIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useResultRowIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultRowIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
import {Provider, useResultRowIdsListener} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowIdsListener('petColors', () =>
console.log('Result Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().rowIds);
// -> 1
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Result Row Ids changed'
root.unmount();
console.log(queries.getListenerStats().rowIds);
// -> 0
Since
v2.0.0
useResultSortedRowIds
The useResultSortedRowIds
hook returns the sorted (and optionally, paginated) Ids
of every Row
in the ResultTable
of the given query, and registers a listener so that any changes to those Ids
will cause a re-render.
useResultSortedRowIds(
queryId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Ids | An array of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultSortedRowIds
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the sorted result Row
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries
object outside the application, which is used in the useResultSortedRowIds
hook by reference. A change to the data in the query re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useResultSortedRowIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(
useResultSortedRowIds(
'dogColors',
'color',
false,
0,
undefined,
queries,
),
)}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultSortedRowIds
hook.
import {Provider, useResultSortedRowIds} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultSortedRowIds('dogColors', 'color'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultSortedRowIds
hook.
import {Provider, useResultSortedRowIds} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useResultSortedRowIds(
'dogColors',
'color',
false,
0,
undefined,
'petQueries',
),
)}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
Since
v2.0.0
useResultSortedRowIdsListener
The useResultSortedRowIdsListener
hook registers a listener function with a Queries
object that will be called whenever the sorted (and optionally, paginated) Row
Ids
in a ResultTable
change.
useResultSortedRowIdsListener(
queryId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: ResultSortedRowIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | string | The |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | ResultSortedRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultSortedRowIds
hook).
Unlike the addResultSortedRowIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useResultSortedRowIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultSortedRowIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
import {Provider, useResultSortedRowIdsListener} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultSortedRowIdsListener(
'petColors',
'color',
false,
0,
undefined,
() => console.log('Sorted result Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().sortedRowIds);
// -> 1
store.setRow('pets', 'cujo', {color: 'tan'});
// -> 'Sorted result Row Ids changed'
root.unmount();
console.log(queries.getListenerStats().sortedRowIds);
// -> 0
Since
v2.0.0
useResultRow
The useResultRow
hook returns an object containing the data of a single Row
in the ResultTable
of the given query, and registers a listener so that any changes to that Row
will cause a re-render.
useResultRow(
queryId: string,
rowId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Row
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Row | An object containing the entire data of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultRow
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Row
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries
object outside the application, which is used in the useResultRow
hook by reference. A change to the data in the query re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useResultRow} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultRow('dogColors', 'fido', queries))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"color":"walnut"}</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultRow
hook.
import {Provider, useResultRow} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRow('dogColors', 'fido'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultRow
hook.
import {Provider, useResultRow} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultRow('dogColors', 'fido', 'petQueries'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
Since
v2.0.0
useResultRowCount
The useResultRowCount
hook returns the count of the Row
objects in the ResultTable
of the given query, and registers a listener so that any changes to that result will cause a re-render.
useResultRowCount(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): number
Type | Description | |
---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | number | The number of |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultRowCount
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the count of ResultRow
objects will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries
object outside the application, which is used in the useResultRowCount
hook by reference. A change to the data in the query re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useResultRowCount} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => <span>{useResultRowCount('dogColors', queries)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>2</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>1</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultRowCount
hook.
import {Provider, useResultRowCount} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => <span>{useResultRowCount('dogColors')}</span>;
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>2</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultRowCount
hook.
import {Provider, useResultRowCount} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultRowCount('dogColors', 'petQueries')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>2</span>'
Since
v4.1.0
useResultRowCountListener
The useResultRowCountListener
hook registers a listener function with a Queries
object that will be called whenever the count of ResultRow
objects in a ResultTable
changes.
useResultRowCountListener(
queryId: IdOrNull,
listener: ResultRowCountListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultRowCountListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultRowCount
hook).
You can either listen to a single ResultTable
(by specifying a query Id
as the method's first parameter) or changes to any ResultTable
(by providing a null
wildcard).
Unlike the addResultRowCountListener
method, which returns a listener Id
and requires you to remove it manually, the useResultRowCountListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultRowCountListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
import {Provider, useResultRowCountListener} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowCountListener('petColors', () =>
console.log('Result Row count changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().rowCount);
// -> 1
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Result Row count changed'
root.unmount();
console.log(queries.getListenerStats().rowCount);
// -> 0
Since
v4.1.0
useResultRowListener
The useResultRowListener
hook registers a listener function with a Queries
object that will be called whenever data in a result Row
changes.
useResultRowListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultRowListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultRowListener | The function that will be called whenever data in the matching result |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultRow
hook).
You can either listen to a single result Row
(by specifying a query Id
and Row
Id
as the method's first two parameters) or changes to any result Row
(by providing null
wildcards).
Both, either, or neither of the queryId
and rowId
parameters can be wildcarded with null
. You can listen to a specific result Row
in a specific query, any result Row
in a specific query, a specific result Row
in any query, or any result Row
in any query.
Unlike the addResultRowListener
method, which returns a listener Id
and requires you to remove it manually, the useResultRowListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultRowListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
import {Provider, useResultRowListener} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowListener('petColors', 'fido', () =>
console.log('Result row changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().row);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result row changed'
root.unmount();
console.log(queries.getListenerStats().row);
// -> 0
Since
v2.0.0
useResultCellIds
The useResultCellIds
hook returns the Ids
of every Cell
in a given Row
in the ResultTable
of the given query, and registers a listener so that any changes to those Ids
will cause a re-render.
useResultCellIds(
queryId: string,
rowId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Ids | An array of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultCellIds
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Cell
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries
object outside the application, which is used in the useResultCellIds
hook by reference. A change to the data in the query re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useResultCellIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(useResultCellIds('dogColors', 'fido', queries))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
store.setCell('pets', 'fido', 'legs', 4);
console.log(app.innerHTML);
// -> '<span>["species","color","legs"]</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultCellIds
hook.
import {Provider, useResultCellIds} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultCellIds('dogColors', 'fido'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultCellIds
hook.
import {Provider, useResultCellIds} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultCellIds('dogColors', 'fido', 'petQueries'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
Since
v2.0.0
useResultCellIdsListener
The useResultCellIdsListener
hook registers a listener function with a Queries
object that will be called whenever the Cell
Ids
in a result Row
change.
useResultCellIdsListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultCellIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultCellIds
hook).
Both, either, or neither of the queryId
and rowId
parameters can be wildcarded with null
. You can listen to a specific result Row
in a specific query, any result Row
in a specific query, a specific result Row
in any query, or any result Row
in any query.
Unlike the addResultCellIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useResultCellIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultCellIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
import {Provider, useResultCellIdsListener} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultCellIdsListener('petColors', 'fido', () =>
console.log('Result cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => {
select('color');
select('legs');
},
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().cellIds);
// -> 1
store.setCell('pets', 'fido', 'legs', 4);
// -> 'Result cell Ids changed'
root.unmount();
console.log(queries.getListenerStats().cellIds);
// -> 0
Since
v2.0.0
useResultCell
The useResultCell
hook returns the value of a single Cell
in a given Row
in the ResultTable
of the given query, and registers a listener so that any changes to that value will cause a re-render.
useResultCell(
queryId: string,
rowId: string,
cellId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Cell | undefined
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | The |
cellId | string | |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Cell | undefined | The value of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultCell
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Cell
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries
object outside the application, which is used in the useResultCell
hook by reference. A change to the data in the query re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useResultCell} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>{useResultCell('dogColors', 'fido', 'color', queries)}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultCell
hook.
import {Provider, useResultCell} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultCell('dogColors', 'fido', 'color')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultCell
hook.
import {Provider, useResultCell} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultCell('dogColors', 'fido', 'color', 'petQueries')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v2.0.0
useResultCellListener
The useResultCellListener
hook registers a listener function with a Queries
object that will be called whenever data in a Cell
changes.
useResultCellListener(
queryId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: ResultCellListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
cellId | IdOrNull | The |
listener | ResultCellListener | The function that will be called whenever data in the matching result |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultCell
hook).
You can either listen to a single Cell
(by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or changes to any Cell
(by providing null
wildcards).
All, some, or none of the queryId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific result Row
in a specific query, any Cell
in any result Row
in any query, for example - or every other combination of wildcards.
Unlike the addResultCellListener
method, which returns a listener Id
and requires you to remove it manually, the useResultCellListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultCellListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
import {Provider, useResultCellListener} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultCellListener('petColors', 'fido', 'color', () =>
console.log('Result cell changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().cell);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result cell changed'
root.unmount();
console.log(queries.getListenerStats().cell);
// -> 0
Since
v2.0.0
useCreateQueries
The useCreateQueries
hook is used to create a Queries
object within a React application with convenient memoization.
useCreateQueries(
store: undefined | Store,
create: (store: Store) => Queries,
createDeps?: DependencyList,
): Queries | undefined
Type | Description | |
---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Queries | An optional callback for performing post-creation steps on the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Queries | undefined | A reference to the |
It is possible to create a Queries
object outside of the React app with the regular createQueries
function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Queries
object being created every time the app renders or re-renders, since v5.0 this hook performs the creation in an effect. As a result it will return undefined
on the brief first render (or if the Store
is not yet defined), which you should defend against.
If your create
function contains other dependencies, the changing of which should also cause the Queries
object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Queries
object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Queries
object at the top level of a React application. Even though the App component is rendered twice, the Queries
object creation only occurs once by default.
import {createQueries, createStore} from 'tinybase';
import {useCreateQueries, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
);
const queries = useCreateQueries(store, (store) => {
console.log('Queries created');
return createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
});
return (
<span>{queries?.getResultCell('dogColors', 'fido', 'color')}</span>
);
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Queries created'
root.render(<App />);
// No second Queries creation
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Queries
object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateQueries
hook takes the resultCell
prop as a dependency, and so the Queries
object is created again on the second render.
import {createQueries, createStore} from 'tinybase';
import {useCreateQueries, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
);
const queries = useCreateQueries(store, (store) => {
console.log('Queries created');
return createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
});
return (
<span>{queries?.getResultCell('dogColors', 'fido', 'color')}</span>
);
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Queries created'
root.render(<App />);
// No second Queries creation
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v2.0.0
useQueries
The useQueries
hook is used to get a reference to a Queries
object from within a Provider
component context.
useQueries(id?: string): Queries | undefined
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Queries | undefined | A reference to the |
A Provider
component is used to wrap part of an application in a context. It can contain a default Queries
object (or a set of Queries
objects named by Id
) that can be easily accessed without having to be passed down as props through every component.
The useQueries
hook lets you either get a reference to the default Queries
object (when called without a parameter), or one of the Queries
objects that are named by Id
(when called with an Id
parameter).
Examples
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useQueries
hook to get a reference to the Queries
object again, without the need to have it passed as a prop.
import {Provider, useQueries} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => <span>{useQueries().getListenerStats().table}</span>;
const queries = createQueries(createStore());
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useQueries
hook with that Id
to get a reference to the Queries
object again, without the need to have it passed as a prop.
import {Provider, useQueries} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useQueries('petQueries').getListenerStats().table}</span>
);
const queries = createQueries(createStore());
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v2.0.0
useQueriesIds
The useQueriesIds
hook is used to retrieve the Ids
of all the named Queries
objects present in the current Provider
component context.
useQueriesIds(): Ids
Example
This example adds two named Queries
objects to a Provider context and an inner component accesses their Ids
.
import {
Provider,
useCreateQueries,
useCreateStore,
useQueriesIds,
} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store1 = useCreateStore(createStore);
const queries1 = useCreateQueries(store1, createQueries);
const store2 = useCreateStore(createStore);
const queries2 = useCreateQueries(store2, createQueries);
return (
<Provider queriesById={{queries1, queries2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useQueriesIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["queries1","queries2"]</span>'
Since
v4.1.0
useQueriesOrQueriesById
The useQueriesOrQueriesById
hook is used to get a reference to a Queries
object from within a Provider
component context, or have it passed directly to this hook.
useQueriesOrQueriesById(queriesOrQueriesId?: QueriesOrQueriesId): Queries | undefined
Type | Description | |
---|---|---|
queriesOrQueriesId? | QueriesOrQueriesId | Either an |
returns | Queries | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Queries
object and which might have been passed in explicitly to the component or is to be picked up from the context by Id
(a common pattern for Queries
-based components).
This is unlikely to be used often. For most situations, you will want to use the useQueries
hook.
Example
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useQueriesOrQueriesById
hook to get a reference to the Queries
object again, without the need to have it passed as a prop. Note however, that unlike the useQueries
hook example, this component would also work if you were to pass the Queries
object directly into it, making it more portable.
import {Provider, useQueriesOrQueriesById} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = ({queries}) => (
<span>
{JSON.stringify(useQueriesOrQueriesById(queries).getQueryIds())}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["dogColors"]</span>'
Since
v4.1.0
useQueryIds
The useQueryIds
hook gets an array of the Query Ids
registered with a Queries
object, and registers a listener so that any changes to that result will cause a re-render.
useQueryIds(queriesOrQueriesId?: QueriesOrQueriesId): Ids
Type | Description | |
---|---|---|
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useQueryIds
hook lets you indicate which Queries
object to get data for: omit the optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Query Ids
in the Queries
object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Queries
object outside the application, which is used in the useQueryIds
hook by reference. A newly-registered Relationship
re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useQueryIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
const App = () => <span>{JSON.stringify(useQueryIds(queries))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
const addQueryDefinition = () =>
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
addQueryDefinition();
console.log(app.innerHTML);
// -> '<span>["dogColors"]</span>'
Since
v4.1.0
Relationships hooks
This is the collection of relationships hooks within the ui-react
module. There are 11 relationships hooks in total.
useLinkedRowIds
The useLinkedRowIds
hook gets the linked Row
Ids
for a given Row
in a linked list Relationship
, and registers a listener so that any changes to that result will cause a re-render.
useLinkedRowIds(
relationshipId: string,
firstRowId: string,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Ids
Type | Description | |
---|---|---|
relationshipId | string | The |
firstRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | Ids | The linked |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Relationships
object or a set of Relationships
objects named by Id
. The useLinkedRowIds
hook lets you indicate which Relationships
object to get data for: omit the optional final parameter for the default context Relationships
object, provide an Id
for a named context Relationships
object, or provide a Relationships
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the linked Row
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Relationships
object outside the application, which is used in the useLinkedRowIds
hook by reference. A change to the linked Row
Ids
re-renders the component.
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useLinkedRowIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const App = () => (
<span>
{JSON.stringify(useLinkedRowIds('petSequence', 'fido', relationships))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo","toto"]</span>'
This example creates a Provider context into which a default Relationships
object is provided. A component within it then uses the useLinkedRowIds
hook.
import {Provider, useLinkedRowIds} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useLinkedRowIds('petSequence', 'fido'))}</span>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
This example creates a Provider context into which a default Relationships
object is provided. A component within it then uses the useLinkedRowIds
hook.
import {Provider, useLinkedRowIds} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationshipsById={{petRelationships: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useLinkedRowIds('petSequence', 'fido', 'petRelationships'),
)}
</span>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
Since
v1.0.0
useLinkedRowIdsListener
The useLinkedRowIdsListener
hook registers a listener function with the Relationships
object that will be called whenever the linked Row
Ids
in a Relationship
change.
useLinkedRowIdsListener(
relationshipId: string,
firstRowId: string,
listener: LinkedRowIdsListener,
listenerDeps?: DependencyList,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void
Type | Description | |
---|---|---|
relationshipId | string | The |
firstRowId | string | |
listener | LinkedRowIdsListener | The function that will be called whenever the linked |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useLinkedRowsId hook).
Unlike other listener registration methods, you cannot provide null
wildcards for the first two parameters of the useLinkedRowIdsListener method. This prevents the prohibitive expense of tracking all the possible linked lists (and partial linked lists within them) in a Store
.
Unlike the addLinkedRowsIdListener method, which returns a listener Id
and requires you to remove it manually, the useLinkedRowIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Indexes
object will be deleted.
Example
This example uses the useLinkedRowIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships
object.
import {Provider, useLinkedRowIdsListener} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => {
useLinkedRowIdsListener('petSequence', 'fido', () =>
console.log('Linked Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(relationships.getListenerStats().linkedRowIds);
// -> 1
store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'Linked Row Ids changed'
root.unmount();
console.log(relationships.getListenerStats().linkedRowIds);
// -> 0
Since
v1.0.0
useLocalRowIds
The useLocalRowIds
hook gets the local Row
Ids
for a given remote Row
in a Relationship
, and registers a listener so that any changes to that result will cause a re-render.
useLocalRowIds(
relationshipId: string,
remoteRowId: string,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Ids
Type | Description | |
---|---|---|
relationshipId | string | The |
remoteRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | Ids | The local |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Relationships
object or a set of Relationships
objects named by Id
. The useLocalRowIds
hook lets you indicate which Relationships
object to get data for: omit the optional final parameter for the default context Relationships
object, provide an Id
for a named context Relationships
object, or provide a Relationships
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the local Row
Id
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Relationships
object outside the application, which is used in the useLocalRowIds
hook by reference. A change to the local Row
Ids
re-renders the component.
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useLocalRowIds} from 'tinybase/ui-react';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<span>
{JSON.stringify(useLocalRowIds('petSpecies', 'dog', relationships))}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<span>["fido","cujo","toto"]</span>'
This example creates a Provider context into which a default Relationships
object is provided. A component within it then uses the useLocalRowIds
hook.
import {Provider, useLocalRowIds} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useLocalRowIds('petSpecies', 'dog'))}</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a default Relationships
object is provided. A component within it then uses the useLocalRowIds
hook.
import {Provider, useLocalRowIds} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationshipsById={{petRelationships: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useLocalRowIds('petSpecies', 'dog', 'petRelationships'),
)}
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
Since
v1.0.0
useLocalRowIdsListener
The useLocalRowIdsListener
hook registers a listener function with the Relationships
object that will be called whenever the local Row
Ids
in a Relationship
change.
useLocalRowIdsListener(
relationshipId: IdOrNull,
remoteRowId: IdOrNull,
listener: LocalRowIdsListener,
listenerDeps?: DependencyList,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void
Type | Description | |
---|---|---|
relationshipId | IdOrNull | The |
remoteRowId | IdOrNull | The |
listener | LocalRowIdsListener | The function that will be called whenever the local |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useLocalRowsId hook).
You can either listen to a single local Row
(by specifying the Relationship
Id
and local Row
Id
as the method's first two parameters), or changes to any local Row
(by providing a null
wildcards).
Both, either, or neither of the relationshipId
and remoteRowId
parameters can be wildcarded with null
. You can listen to a specific remote Row
in a specific Relationship
, any remote Row
in a specific Relationship
, a specific remote Row
in any Relationship
, or any remote Row
in any Relationship
.
Unlike the addLocalRowsIdListener method, which returns a listener Id
and requires you to remove it manually, the useLocalRowIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Indexes
object will be deleted.
Example
This example uses the useLocalRowIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships
object.
import {Provider, useLocalRowIdsListener} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => {
useLocalRowIdsListener('petSpecies', 'dog', () =>
console.log('Local Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(relationships.getListenerStats().localRowIds);
// -> 1
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Local Row Ids changed'
root.unmount();
console.log(relationships.getListenerStats().localRowIds);
// -> 0
Since
v1.0.0
useRemoteRowId
The useRemoteRowId
hook gets the remote Row
Id
for a given local Row
in a Relationship
, and registers a listener so that any changes to that result will cause a re-render.
useRemoteRowId(
relationshipId: string,
localRowId: string,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Id | undefined
Type | Description | |
---|---|---|
relationshipId | string | The |
localRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | Id | undefined | The remote |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Relationships
object or a set of Relationships
objects named by Id
. The useRemoteRowId
hook lets you indicate which Relationships
object to get data for: omit the optional final parameter for the default context Relationships
object, provide an Id
for a named context Relationships
object, or provide a Relationships
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the remote Row
Id
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Relationships
object outside the application, which is used in the useRemoteRowId
hook by reference. A change to the remote Row
Id
re-renders the component.
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useRemoteRowId} from 'tinybase/ui-react';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<span>{useRemoteRowId('petSpecies', 'cujo', relationships)}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>wolf</span>'
This example creates a Provider context into which a default Relationships
object is provided. A component within it then uses the useRemoteRowId
hook.
import {Provider, useRemoteRowId} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => <span>{useRemoteRowId('petSpecies', 'cujo')}</span>;
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates a Provider context into which a default Relationships
object is provided. A component within it then uses the useRemoteRowId
hook.
import {Provider, useRemoteRowId} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationshipsById={{petRelationships: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useRemoteRowId('petSpecies', 'cujo', 'petRelationships')}</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
Since
v1.0.0
useRemoteRowIdListener
The useRemoteRowIdListener
hook registers a listener function with the Relationships
object that will be called whenever a remote Row
Id
in a Relationship
changes.
useRemoteRowIdListener(
relationshipId: IdOrNull,
localRowId: IdOrNull,
listener: RemoteRowIdListener,
listenerDeps?: DependencyList,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void
Type | Description | |
---|---|---|
relationshipId | IdOrNull | The |
localRowId | IdOrNull | The |
listener | RemoteRowIdListener | The function that will be called whenever the remote |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRemoteRowId
hook).
You can either listen to a single local Row
(by specifying the Relationship
Id
and local Row
Id
as the method's first two parameters), or changes to any local Row
(by providing a null
wildcards).
Both, either, or neither of the relationshipId
and localRowId
parameters can be wildcarded with null
. You can listen to a specific local Row
in a specific Relationship
, any local Row
in a specific Relationship
, a specific local Row
in any Relationship
, or any local Row
in any Relationship
.
Unlike the addRemoteRowIdListener
method, which returns a listener Id
and requires you to remove it manually, the useRemoteRowIdListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Indexes
object will be deleted.
Example
This example uses the useRemoteRowIdListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships
object.
import {Provider, useRemoteRowIdListener} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => {
useRemoteRowIdListener('petSpecies', 'cujo', () =>
console.log('Remote Row Id changed'),
);
return <span>App</span>;
};
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(relationships.getListenerStats().remoteRowId);
// -> 1
store.setCell('pets', 'cujo', 'species', 'wolf');
// -> 'Remote Row Id changed'
root.unmount();
console.log(relationships.getListenerStats().remoteRowId);
// -> 0
Since
v1.0.0
useCreateRelationships
The useCreateRelationships
hook is used to create a Relationships
object within a React application with convenient memoization.
useCreateRelationships(
store: undefined | Store,
create: (store: Store) => Relationships,
createDeps?: DependencyList,
): Relationships | undefined
Type | Description | |
---|---|---|
store | undefined | Store | A reference to the |
create | (store: Store) => Relationships | An optional callback for performing post-creation steps on the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Relationships | undefined | A reference to the |
It is possible to create a Relationships
object outside of the React app with the regular createRelationships
function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Relationships
object being created every time the app renders or re-renders, since v5.0 this hook performs the creation in an effect. As a result it will return undefined
on the brief first render (or if the Store
is not yet defined), which you should defend against.
If your create
function contains other dependencies, the changing of which should also cause the Relationships
object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Relationships
object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Relationships
object at the top level of a React application. Even though the App component is rendered twice, the Relationships
object creation only occurs once by default.
import {createRelationships, createStore} from 'tinybase';
import {useCreateRelationships, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {dog: {price: 5}, cat: {price: 4}}),
);
const relationships = useCreateRelationships(store, (store) => {
console.log('Relationships created');
return createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
});
return (
<span>{relationships?.getRemoteRowId('petSpecies', 'fido')}</span>
);
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Relationships created'
root.render(<App />);
// No second Relationships creation
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates a Relationships
object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateRelationships
hook takes the remoteTableAndCellToLink
prop as a dependency, and so the Relationships
object is created again on the second render.
import {createRelationships, createStore} from 'tinybase';
import {useCreateRelationships, useCreateStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({remoteTableAndCellToLink}) => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
})
.setTable('species', {dog: {price: 5}, cat: {price: 4}})
.setTable('color', {brown: {discount: 0.1}, black: {discount: 0}}),
);
const relationships = useCreateRelationships(
store,
(store) => {
console.log(`Relationship created to ${remoteTableAndCellToLink}`);
return createRelationships(store).setRelationshipDefinition(
'cellLinked',
'pets',
remoteTableAndCellToLink,
remoteTableAndCellToLink,
);
},
[remoteTableAndCellToLink],
);
return (
<span>{relationships?.getRemoteRowId('cellLinked', 'fido')}</span>
);
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App remoteTableAndCellToLink="species" />);
// -> 'Relationship created to species'
console.log(app.innerHTML);
// -> '<span>dog</span>'
root.render(<App remoteTableAndCellToLink="color" />);
// -> 'Relationship created to color'
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v1.0.0
useRelationshipIds
The useRelationshipIds
hook gets an array of the Relationship
Ids
registered with a Relationships
object, and registers a listener so that any changes to that result will cause a re-render.
useRelationshipIds(relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId): Ids
Type | Description | |
---|---|---|
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | Ids | The |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Relationships
object or a set of Relationships
objects named by Id
. The useRelationshipIds
hook lets you indicate which Relationships
object to get data for: omit the optional final parameter for the default context Relationships
object, provide an Id
for a named context Relationships
object, or provide a Relationships
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Relationship
Ids
in the Relationships
object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Relationships
object outside the application, which is used in the useRelationshipIds
hook by reference. A newly-registered Relationship
re-renders the component.
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {useRelationshipIds} from 'tinybase/ui-react';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
const App = () => (
<span>{JSON.stringify(useRelationshipIds(relationships))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
const addRelationshipDefinition = () =>
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
addRelationshipDefinition();
console.log(app.innerHTML);
// -> '<span>["petSpecies"]</span>'
Since
v4.1.0
useRelationships
The useRelationships
hook is used to get a reference to a Relationships
object from within a Provider
component context.
useRelationships(id?: string): Relationships | undefined
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Relationships | undefined | A reference to the |
A Provider
component is used to wrap part of an application in a context. It can contain a default Relationships
object (or a set of Relationships
objects named by Id
) that can be easily accessed without having to be passed down as props through every component.
The useRelationships
hook lets you either get a reference to the default Relationships
object (when called without a parameter), or one of the Relationships
objects that are named by Id
(when called with an Id
parameter).
Examples
This example creates a Provider context into which a default Relationships
object is provided. A component within it then uses the useRelationships
hook to get a reference to the Relationships
object again, without the need to have it passed as a prop.
import {Provider, useRelationships} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useRelationships().getListenerStats().remoteRowId}</span>
);
const relationships = createRelationships(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Relationships
object is provided, named by Id
. A component within it then uses the useRelationships
hook with that Id
to get a reference to the Relationships
object again, without the need to have it passed as a prop.
import {Provider, useRelationships} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationshipsById={{petStore: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{useRelationships('petStore').getListenerStats().remoteRowId}
</span>
);
const relationships = createRelationships(createStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useRelationshipsIds
The useRelationshipsIds
hook is used to retrieve the Ids
of all the named Relationships
objects present in the current Provider
component context.
useRelationshipsIds(): Ids
Example
This example adds two named Relationships
objects to a Provider context and an inner component accesses their Ids
.
import {
Provider,
useCreateRelationships,
useCreateStore,
useRelationshipsIds,
} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = () => {
const store1 = useCreateStore(createStore);
const relationships1 = useCreateRelationships(
store1,
createRelationships,
);
const store2 = useCreateStore(createStore);
const relationships2 = useCreateRelationships(
store2,
createRelationships,
);
return (
<Provider relationshipsById={{relationships1, relationships2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useRelationshipsIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["relationships1","relationships2"]</span>'
Since
v4.1.0
useRelationshipsOrRelationshipsById
The useRelationshipsOrRelationshipsById
hook is used to get a reference to a Relationships
object from within a Provider
component context, or have it passed directly to this hook.
useRelationshipsOrRelationshipsById(relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId): Relationships | undefined
Type | Description | |
---|---|---|
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | Either an |
returns | Relationships | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Relationships
object and which might have been passed in explicitly to the component or is to be picked up from the context by Id
(a common pattern for Relationships
-based components).
This is unlikely to be used often. For most situations, you will want to use the useRelationships
hook.
Example
This example creates a Provider context into which a default Relationships
object is provided. A component within it then uses the useRelationshipsOrRelationshipsById
hook to get a reference to the Relationships
object again, without the need to have it passed as a prop. Note however, that unlike the useRelationships
hook example, this component would also work if you were to pass the Relationships
object directly into it, making it more portable.
import {
Provider,
useRelationshipsOrRelationshipsById,
} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = ({relationships}) => (
<span>
{JSON.stringify(
useRelationshipsOrRelationshipsById(
relationships,
).getRelationshipIds(),
)}
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["petSpecies"]</span>'
Since
v4.1.0
Store hooks
This is the collection of store hooks within the ui-react
module. There are 64 store hooks in total.
useCreateMergeableStore
The useCreateMergeableStore
hook.
useCreateMergeableStore(
create: () => MergeableStore,
createDeps?: DependencyList,
): MergeableStore
Type | Description | |
---|---|---|
create | () => MergeableStore | |
createDeps? | DependencyList | |
returns | MergeableStore |
Since
v1.0.0
useCreateStore
The useCreateStore
hook is used to create a Store
within a React application with convenient memoization.
useCreateStore(
create: () => Store,
createDeps?: DependencyList,
): Store
Type | Description | |
---|---|---|
create | () => Store | A function for performing the creation of the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Store | A reference to the |
It is possible to create a Store
outside of the React app with the regular createStore
function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Store
being created every time the app renders or re-renders, the useCreateStore
hook wraps the creation in a memoization.
The useCreateStore
hook is a very thin wrapper around the React useMemo
hook, defaulting to an empty array for its dependencies, so that by default, the creation only occurs once.
If your create
function contains other dependencies, the changing of which should cause the Store
to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
Examples
This example creates an empty Store
at the top level of a React application. Even though the App component is rendered twice, the Store
creation only occurs once by default.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCreateStore} from 'tinybase/ui-react';
const App = () => {
const store = useCreateStore(() => {
console.log('Store created');
return createStore().setTables({pets: {fido: {species: 'dog'}}});
});
return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Store created'
root.render(<App />);
// No second Store creation
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates an empty Store
at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateStore
hook takes the fidoSpecies
prop as a dependency, and so the Store
is created again on the second render.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCreateStore} from 'tinybase/ui-react';
const App = ({fidoSpecies}) => {
const store = useCreateStore(() => {
console.log(`Store created for fido as ${fidoSpecies}`);
return createStore().setTables({pets: {fido: {species: fidoSpecies}}});
}, [fidoSpecies]);
return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App fidoSpecies="dog" />);
// -> 'Store created for fido as dog'
console.log(app.innerHTML);
// -> '<span>dog</span>'
root.render(<App fidoSpecies="cat" />);
// -> 'Store created for fido as cat'
console.log(app.innerHTML);
// -> '<span>cat</span>'
Since
v1.0.0
useProvideStore
The useProvideStore
hook is used to add a Store
object by Id
to a Provider
component, but imperatively from a component within it.
useProvideStore(
storeId: string,
store: Store,
): void
Type | Description | |
---|---|---|
storeId | string | The |
store | Store | The |
returns | void | This has no return value. |
Normally you will register a Store
by Id
in a context by using the storesById
prop of the top-level Provider
component. This hook, however, lets you dynamically add a new Store
to the context, from within a descendent component. This is useful for applications where the set of Stores is not known at the time of the first render of the root Provider.
A Store
added to the Provider context in this way will be available to other components within the context (using the useStore
hook and so on). If you use the same Id
as an existing Store
registration, the new one will take priority over one provided by the storesById
prop.
Note that other components that consume a Store
registered like this should defend against it being undefined at first. On the first render, the other component will likely not yet have completed the registration. In the example below, we use the null-safe useStore('petStore')?
to do this.
Example
This example creates a Provider context. A child component registers a Store
into it which is then consumable by a peer child component.
import {
Provider,
useCreateStore,
useProvideStore,
useStore,
} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = () => (
<Provider>
<RegisterStore />
<ConsumeStore />
</Provider>
);
const RegisterStore = () => {
const store = useCreateStore(() =>
createStore().setCell('pets', 'fido', 'color', 'brown'),
);
useProvideStore('petStore', store);
return null;
};
const ConsumeStore = () => (
<span>{JSON.stringify(useStore('petStore')?.getTableIds())}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
Since
v4.4.2
useStore
The useStore
hook is used to get a reference to a Store
from within a Provider
component context.
useStore(id?: string): Store | undefined
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Store | undefined | A reference to the |
A Provider
component is used to wrap part of an application in a context. It can contain a default Store
(or a set of Store
objects named by Id
) that can be easily accessed without having to be passed down as props through every component.
The useStore
hook lets you either get a reference to the default Store
(when called without a parameter), or one of the Store
objects that are named by Id
(when called with an Id
parameter).
Examples
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useStore
hook to get a reference to the Store
again, without the need to have it passed as a prop.
import {Provider, useStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useStore().getListenerStats().tables}</span>;
const store = createStore();
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useStore
hook with that Id
to get a reference to the Store
again, without the need to have it passed as a prop.
import {Provider, useStore} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useStore('petStore').getListenerStats().tables}</span>
);
const store = createStore();
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v1.0.0
useStoreIds
The useStoreIds
hook is used to retrieve the Ids
of all the named Store
objects present in the current Provider
component context.
useStoreIds(): Ids
Example
This example adds two named Store
objects to a Provider context and an inner component accesses their Ids
.
import {Provider, useCreateStore, useStoreIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = () => {
const store1 = useCreateStore(createStore);
const store2 = useCreateStore(createStore);
return (
<Provider storesById={{store1, store2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useStoreIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["store1","store2"]</span>'
Since
v4.1.0
useStoreOrStoreById
The useStoreOrStoreById
hook is used to get a reference to a Store
object from within a Provider
component context, or have it passed directly to this hook.
useStoreOrStoreById(storeOrStoreId?: StoreOrStoreId): Store | undefined
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | Either an |
returns | Store | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Store
object and which might have been passed in explicitly to the component or is to be picked up from the context by Id
(a common pattern for Store
-based components).
This is unlikely to be used often. For most situations, you will want to use the useStore
hook.
Example
This example creates a Provider context into which a default Store
object is provided. A component within it then uses the useStoreOrStoreById
hook to get a reference to the Store
object again, without the need to have it passed as a prop. Note however, that unlike the useStore
hook example, this component would also work if you were to pass the Store
object directly into it, making it more portable.
import {Provider, useStoreOrStoreById} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = ({store}) => (
<span>{JSON.stringify(useStoreOrStoreById(store).getTableIds())}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
Since
v4.1.0
useDelTablesCallback
The useDelTablesCallback
hook returns a callback that can be used to remove all of the tabular data in a Store
.
useDelTablesCallback(
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store
.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the deletion to your application's undo stack.
The Store
to which the callback will make the deletion (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelTablesCallback
hook to create an event handler which deletes from the Store
when the span
element is clicked.
import {useDelTablesCallback, useTables} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelTablesCallback(store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v1.0.0
useHasTables
The useHasTables
hook returns a boolean indicating whether any Table
objects exist in the Store
, and registers a listener so that any changes to that result will cause a re-render.
useHasTables(storeOrStoreId?: StoreOrStoreId): boolean
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
returns | boolean | Whether any |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useHasTables
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Tables
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useHasTables
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasTables} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useHasTables(store))}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.delTable('pets');
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useHasTables
hook.
import {Provider, useHasTables} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasTables())}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useHasTables
hook.
import {Provider, useHasTables} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasTables('petStore'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v4.4.0
useHasTablesListener
The useHasTablesListener
hook registers a listener function with the Store
that will be called when Tables
as a whole are added to or removed from the Store
.
useHasTablesListener(
listener: HasTablesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
listener | HasTablesListener | The function that will be called whenever |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasTables
hook).
Unlike the addHasTablesListener
method, which returns a listener Id
and requires you to remove it manually, the useHasTablesListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useHasTablesListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useHasTablesListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasTablesListener(() => console.log('Tables existence changed'));
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasTables);
// -> 1
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Tables existence changed'
root.unmount();
console.log(store.getListenerStats().hasTables);
// -> 0
Since
v4.4.0
useSetTablesCallback
The useSetTablesCallback
hook returns a parameterized callback that can be used to set the tabular data of a Store
.
useSetTablesCallback<Parameter>(
getTables: (parameter: Parameter, store: Store) => Tables,
getTablesDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, tables: Tables) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
getTables | (parameter: Parameter, store: Store) => Tables | A function which returns the |
getTablesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, tables: Tables) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The first parameter is a function which will produce the Tables
object that will then be used to update the Store
in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the mutation to your application's undo stack.
The Store
to which the callback will make the mutation (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetTablesCallback
hook to create an event handler which updates the Store
when the span
element is clicked.
import {useSetTablesCallback, useTables} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useSetTablesCallback(
(e) => ({pets: {nemo: {species: 'fish', bubbles: e.bubbles}}}),
[],
store,
(store, tables) => console.log(`Updated: ${JSON.stringify(tables)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"pets":{"nemo":{"species":"fish","bubbles":true}}}'
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish","bubbles":true}}}'
Since
v1.0.0
useTables
The useTables
hook returns a Tables
object containing the tabular data of a Store
, and registers a listener so that any changes to that result will cause a re-render.
useTables(storeOrStoreId?: StoreOrStoreId): Tables
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
returns | Tables |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useTables
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Tables
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useTables
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useTables} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTables(store))}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"walnut"}}}</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useTables
hook.
import {Provider, useTables} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTables())}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useTables
hook.
import {Provider, useTables} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTables('petStore'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
Since
v1.0.0
useTablesListener
The useTablesListener
hook registers a listener function with a Store
that will be called whenever tabular data in it changes.
useTablesListener(
listener: TablesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
listener | TablesListener | The function that will be called whenever tabular data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTables
hook).
Unlike the addTablesListener
method, which returns a listener Id
and requires you to remove it manually, the useTablesListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useTablesListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useTablesListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTablesListener(() => console.log('Tables changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().tables);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
root.unmount();
console.log(store.getListenerStats().tables);
// -> 0
Since
v1.0.0
useTableIds
The useTableIds
hook returns the Ids
of every Table
in a Store
, and registers a listener so that any changes to that result will cause a re-render.
useTableIds(storeOrStoreId?: StoreOrStoreId): Ids
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useTableIds
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useTableIds
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useTableIds} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTableIds(store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
store.setCell('species', 'dog', 'price', 5);
console.log(app.innerHTML);
// -> '<span>["pets","species"]</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useTableIds
hook.
import {Provider, useTableIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTableIds())}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useTableIds
hook.
import {Provider, useTableIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTableIds('petStore'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
Since
v1.0.0
useTableIdsListener
The useTableIdsListener
hook registers a listener function with a Store
that will be called whenever the Table
Ids
in it change.
useTableIdsListener(
listener: TableIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
listener | TableIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTableIds
hook).
Unlike the addTableIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useTableIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useTableIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useTableIdsListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTableIdsListener(() => console.log('Table Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().tableIds);
// -> 1
store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'
root.unmount();
console.log(store.getListenerStats().tableIds);
// -> 0
Since
v1.0.0
useDelTableCallback
The useDelTableCallback
hook returns a parameterized callback that can be used to remove a single Table
from a Store
.
useDelTableCallback<Parameter>(
tableId: string | GetId<Parameter>,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
tableId | string | GetId<Parameter> | The |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the deletion.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the deletion to your application's undo stack.
The Store
to which the callback will make the deletion (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelTableCallback
hook to create an event handler which deletes from the Store
when the span
element is clicked.
import {useDelTableCallback, useTables} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelTableCallback('pets', store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v1.0.0
useHasTable
The useHasTable
hook returns a boolean indicating whether a given Table
exists in the Store
, and registers a listener so that any changes to that result will cause a re-render.
useHasTable(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | boolean |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useHasTable
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useHasTable
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasTable} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useHasTable('pets', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.delTable('pets');
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useHasTable
hook.
import {Provider, useHasTable} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasTable('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useHasTable
hook.
import {Provider, useHasTable} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasTable('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v4.4.0
useHasTableCell
The useHasTableCell
hook returns a boolean indicating whether a given Cell
exists anywhere in a Table
, not just in a specific Row
, and registers a listener so that any changes to that result will cause a re-render.
useHasTableCell(
tableId: string,
cellId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
cellId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | boolean |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useHasTableCell
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useHasTableCell
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasTableCell} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useHasTableCell('pets', 'legs', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>false</span>'
store.setRow('pets', 'felix', {color: 'black', legs: 4});
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useHasTableCell
hook.
import {Provider, useHasTableCell} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasTableCell('pets', 'legs'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useHasTableCell
hook.
import {Provider, useHasTableCell} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useHasTableCell('pets', 'legs', 'petStore'))}
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
Since
v4.4.0
useHasTableCellListener
The useHasTableCellListener
hook registers a listener function with the Store
that will be called when a Cell
is added to or removed from anywhere in a Table
as a whole.
useHasTableCellListener(
tableId: IdOrNull,
cellId: IdOrNull,
listener: HasTableCellListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
cellId | IdOrNull | |
listener | HasTableCellListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasTableCell
hook).
You can either listen to a single Table
Cell
being added or removed (by specifying the Table
Id
and Cell
Id
, as the method's first two parameters) or changes to any Table
Cell
(by providing null
wildcards).
Unlike the addHasTableCellIds method, which returns a listener Id
and requires you to remove it manually, the useHasTableCellListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useHasTableCellListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useHasTableCellListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasTableCellListener('pets', 'color', () =>
console.log('Table Cell existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasTableCell);
// -> 1
store.setRow('pets', 'fido', {color: 'brown'});
// -> 'Table Cell existence changed'
root.unmount();
console.log(store.getListenerStats().hasTableCell);
// -> 0
Since
v4.4.0
useHasTableListener
The useHasTableListener
hook registers a listener function with the Store
that will be called when a Table
is added to or removed from the Store
.
useHasTableListener(
tableId: IdOrNull,
listener: HasTableListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | HasTableListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasTable
hook).
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Unlike the addHasTableListener
method, which returns a listener Id
and requires you to remove it manually, the useHasTableListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useHasTableListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useHasTableListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasTableListener('pets', () =>
console.log('Table existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasTable);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Table existence changed'
root.unmount();
console.log(store.getListenerStats().hasTable);
// -> 0
Since
v4.4.0
useSetTableCallback
The useSetTableCallback
hook returns a parameterized callback that can be used to set the data of a single Table
in a Store
.
useSetTableCallback<Parameter>(
tableId: string | GetId<Parameter>,
getTable: (parameter: Parameter, store: Store) => Table,
getTableDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, table: Table) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
tableId | string | GetId<Parameter> | The |
getTable | (parameter: Parameter, store: Store) => Table | A function which returns the |
getTableDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, table: Table) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The second parameter is a function which will produce the Table
object that will then be used to update the Store
in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the mutation to your application's undo stack.
The Store
to which the callback will make the mutation (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetTableCallback
hook to create an event handler which updates the Store
when the span
element is clicked.
import {useSetTableCallback, useTable} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setTable('pets', {nemo: {species: 'fish'}});
const App = () => {
const handleClick = useSetTableCallback(
'pets',
(e) => ({nemo: {species: 'fish', bubbles: e.bubbles}}),
[],
store,
(store, table) => console.log(`Updated: ${JSON.stringify(table)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTable('pets', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish"}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"nemo":{"species":"fish","bubbles":true}}'
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish","bubbles":true}}'
Since
v1.0.0
useTable
The useTable
hook returns an object containing the data of a single Table
in a Store
, and registers a listener so that any changes to that result will cause a re-render.
useTable(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): Table
Type | Description | |
---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Table | An object containing the entire data of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useTable
hook lets you indicate which Store
to get data for: omit the final optional final parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useTable
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useTable} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTable('pets', store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut"}}</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useTable
hook.
import {Provider, useTable} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTable('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useTable
hook.
import {Provider, useTable} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useTable('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
Since
v1.0.0
useTableCellIds
The useTableCellIds
hook returns the Ids
of every Cell
used across the whole Table
, and registers a listener so that any changes to that result will cause a re-render.
useTableCellIds(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids | An array of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useTableCellIds
hook lets you indicate which Store
to get data for: omit the optional final parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table
Cell
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useTableCellIds
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useTableCellIds} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useTableCellIds('pets', store))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
store.setCell('pets', 'felix', 'species', 'cat');
console.log(app.innerHTML);
// -> '<span>["color","species"]</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useTableCellIds
hook.
import {Provider, useTableCellIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTableCellIds('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useTableCellIds
hook.
import {Provider, useTableCellIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useTableCellIds('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
Since
v3.3.0
useTableCellIdsListener
The useTableCellIdsListener
hook registers a listener function with a Store
that will be called whenever the Cell
Ids
that appear anywhere in a Table
change.
useTableCellIdsListener(
tableId: IdOrNull,
listener: TableCellIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | TableCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTableCellIds
hook).
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing null
).
Unlike the addTableCellIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useTableCellIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useTableCellIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useTableCellIdsListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTableCellIdsListener('pets', () => console.log('Cell Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().tableCellIds);
// -> 1
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Cell Ids changed'
root.unmount();
console.log(store.getListenerStats().rowIds);
// -> 0
Since
v1.0.0
useTableListener
The useTableListener
hook registers a listener function with a Store
that will be called whenever data in a Table
changes.
useTableListener(
tableId: IdOrNull,
listener: TableListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | TableListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTable
hook).
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing a null
wildcard).
Unlike the addTableListener
method, which returns a listener Id
and requires you to remove it manually, the useTableListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useTableListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useTableListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTableListener('pets', () => console.log('Table changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().table);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Table changed'
root.unmount();
console.log(store.getListenerStats().table);
// -> 0
Since
v1.0.0
useRowIds
The useRowIds
hook returns the Ids
of every Row
in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
useRowIds(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useRowIds
hook lets you indicate which Store
to get data for: omit the optional final parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useRowIds
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRowIds} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useRowIds('pets', store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
store.setCell('pets', 'felix', 'color', 'black');
console.log(app.innerHTML);
// -> '<span>["fido","felix"]</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useRowIds
hook.
import {Provider, useRowIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useRowIds('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useRowIds
hook.
import {Provider, useRowIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useRowIds('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
Since
v1.0.0
useRowIdsListener
The useRowIdsListener
hook registers a listener function with a Store
that will be called whenever the Row
Ids
in a Table
change.
useRowIdsListener(
tableId: IdOrNull,
listener: RowIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRowIds
hook).
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing null
).
Unlike the addRowIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useRowIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useRowIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useRowIdsListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useRowIdsListener('pets', () => console.log('Row Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().rowIds);
// -> 1
store.setRow('pets', 'felix', {color: 'black'});
// -> 'Row Ids changed'
root.unmount();
console.log(store.getListenerStats().rowIds);
// -> 0
Since
v1.0.0
useSortedRowIds
The useSortedRowIds
hook returns the sorted (and optionally, paginated) Ids
of every Row
in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
useSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
storeOrStoreId?: StoreOrStoreId,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useSortedRowIds
hook lets you indicate which Store
to get data for: omit the optional final parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the sorted Row
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useSortedRowIds
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useSortedRowIds} from 'tinybase/ui-react';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const App = () => (
<span>
{JSON.stringify(
useSortedRowIds('pets', 'species', false, 0, undefined, store),
)}
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
store.setRow('pets', 'cujo', {species: 'wolf'});
console.log(app.innerHTML);
// -> '<span>["felix","fido","cujo"]</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useSortedRowIds
hook.
import {Provider, useSortedRowIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useSortedRowIds('pets'))}</span>;
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useSortedRowIds
hook.
import {Provider, useSortedRowIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useSortedRowIds('pets', 'species', false, 0, undefined, 'petStore'),
)}
</span>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
Since
v2.0.0
useSortedRowIdsListener
The useSortedRowIdsListener
hook registers a listener function with a Store
that will be called whenever sorted (and optionally, paginated) Row
Ids
in a Table
change.
useSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener | The function that will be called whenever the sorted |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSortedRowIds
hook).
Unlike the addSortedRowIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useSortedRowIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useSortedRowIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useSortedRowIdsListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useSortedRowIdsListener('pets', 'species', false, 0, undefined, () =>
console.log('Sorted Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().sortedRowIds);
// -> 1
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids changed'
root.unmount();
console.log(store.getListenerStats().sortedRowIds);
// -> 0
Since
v2.0.0
useAddRowCallback
The useAddRowCallback
hook returns a parameterized callback that can be used to create a new Row
in a Store
.
useAddRowCallback<Parameter>(
tableId: string | GetId<Parameter>,
getRow: (parameter: Parameter, store: Store) => Row,
getRowDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (rowId: undefined | string, store: Store, row: Row) => void,
thenDeps?: DependencyList,
reuseRowIds?: boolean,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
tableId | string | GetId<Parameter> | The |
getRow | (parameter: Parameter, store: Store) => Row | A function which returns the |
getRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (rowId: undefined | string, store: Store, row: Row) => void | A function which is called after the mutation, with the new |
thenDeps? | DependencyList | An optional array of dependencies for the |
reuseRowIds? | boolean | Whether |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The second parameter is a function which will produce the Row
object that will then be used to update the Store
in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the mutation to your application's undo stack.
The Store
to which the callback will make the mutation (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
The reuseRowIds
parameter defaults to true
, which means that if you delete a Row
and then add another, the Id
will be re-used - unless you delete the entire Table
, in which case all Row
Ids
will reset. Otherwise, if you specify reuseRowIds
to be false
, then the Id
will be a monotonically increasing string representation of an increasing integer, regardless of any you may have previously deleted.
Example
This example uses the useAddRowCallback
hook to create an event handler which updates the Store
when the span
element is clicked.
import {useAddRowCallback, useTable} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
const handleClick = useAddRowCallback(
'pets',
(e) => ({species: 'frog', bubbles: e.bubbles}),
[],
store,
(rowId) => console.log(`Added row: ${rowId}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTable('pets', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish"}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Added row: 0'
console.log(span.innerHTML);
// -> '{"0":{"species":"frog","bubbles":true},"nemo":{"species":"fish"}}'
Since
v1.0.0
useDelRowCallback
The useDelRowCallback
hook returns a parameterized callback that can be used to remove a single Row
from a Table
.
useDelRowCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the deletion.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the deletion to your application's undo stack.
The Store
to which the callback will make the deletion (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelRowCallback
hook to create an event handler which deletes from the Store
when the span
element is clicked.
import {useDelRowCallback, useTables} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelRowCallback('pets', 'nemo', store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v1.0.0
useHasRow
The useHasRow
hook returns a boolean indicating whether a given Row
exists in the Store
, and registers a listener so that any changes to that result will cause a re-render.
useHasRow(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | boolean |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useHasRow
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useHasRow
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasRow} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useHasRow('pets', 'felix', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>false</span>'
store.setCell('pets', 'felix', 'color', 'black');
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useHasRow
hook.
import {Provider, useHasRow} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasRow('pets', 'felix'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useHasRow
hook.
import {Provider, useHasRow} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasRow('pets', 'felix', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
Since
v4.4.0
useHasRowListener
The useHasRowListener
hook registers a listener function with the Store
that will be called when a Row
is added to or removed from the Store
.
useHasRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: HasRowListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | HasRowListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasRow
hook).
You can either listen to a single Row
being added or removed (by specifying the Table
Id
and Row
Id
, as the method's first two parameters) or changes to any Row
(by providing null
wildcards).
Both, either, or neither of the tableId
and rowId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Unlike the addHasRowListener
method, which returns a listener Id
and requires you to remove it manually, the useHasRowListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useHasRowListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useHasRowListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasRowListener('pets', 'fido', () =>
console.log('Row existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasRow);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Row existence changed'
root.unmount();
console.log(store.getListenerStats().hasRow);
// -> 0
Since
v4.4.0
useRow
The useRow
hook returns an object containing the data of a single Row
in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
useRow(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
): Row
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Row | An object containing the entire data of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useRow
hook lets you indicate which Store
to get data for: omit the final optional final parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useRow
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRow} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useRow('pets', 'fido', store))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"color":"walnut"}</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useRow
hook.
import {Provider, useRow} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useRow('pets', 'fido'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useRow
hook.
import {Provider, useRow} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useRow('pets', 'fido', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
Since
v1.0.0
useRowCount
The useRowCount
hook returns the count of the Row
objects in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
useRowCount(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): number
Type | Description | |
---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | number |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useRowCount
hook lets you indicate which Store
to get data for: omit the optional final parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the count of Row
objects will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useRowCount
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useRowCount} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{useRowCount('pets', store)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>1</span>'
store.setCell('pets', 'felix', 'color', 'black');
console.log(app.innerHTML);
// -> '<span>2</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useRowCount
hook.
import {Provider, useRowCount} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useRowCount('pets')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>1</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useRowCount
hook.
import {Provider, useRowCount} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useRowCount('pets', 'petStore')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>1</span>'
Since
v4.1.0
useRowCountListener
The useRowCountListener
hook registers a listener function with a Store
that will be called whenever the count of the Row
objects in a Table
changes.
useRowCountListener(
tableId: IdOrNull,
listener: RowCountListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | RowCountListener | The function that will be called whenever the count of the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRowCount
hook).
You can either listen to a single Table
(by specifying its Id
as the method's first parameter) or changes to any Table
(by providing null
).
Unlike the addRowCountListener
method, which returns a listener Id
and requires you to remove it manually, the useRowCountListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useRowCountListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useRowCountListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useRowCountListener('pets', () => console.log('Row count changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().rowCount);
// -> 1
store.setRow('pets', 'felix', {color: 'black'});
// -> 'Row count changed'
root.unmount();
console.log(store.getListenerStats().rowCount);
// -> 0
Since
v4.1.0
useRowListener
The useRowListener
hook registers a listener function with a Store
that will be called whenever data in a Row
changes.
useRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: RowListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRow
hook).
You can either listen to a single Row
(by specifying the Table
Id
and Row
Id
as the method's first two parameters) or changes to any Row
(by providing null
wildcards).
Both, either, or neither of the tableId
and rowId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Unlike the addRowListener
method, which returns a listener Id
and requires you to remove it manually, the useRowListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useRowListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useRowListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useRowListener('pets', 'fido', () => console.log('Row changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().row);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Row changed'
root.unmount();
console.log(store.getListenerStats().row);
// -> 0
Since
v1.0.0
useSetPartialRowCallback
The useSetPartialRowCallback
hook returns a parameterized callback that can be used to set partial data of a single Row
in the Store
, leaving other Cell
values unaffected.
useSetPartialRowCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
getPartialRow: (parameter: Parameter, store: Store) => Row,
getPartialRowDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, partialRow: Row) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
getPartialRow | (parameter: Parameter, store: Store) => Row | A function which returns the partial |
getPartialRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, partialRow: Row) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The third parameter is a function which will produce the partial Row
object that will then be used to update the Store
in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the mutation to your application's undo stack.
The Store
to which the callback will make the mutation (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetPartialRowCallback
hook to create an event handler which updates the Store
when the span
element is clicked.
import {useRow, useSetPartialRowCallback} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
const handleClick = useSetPartialRowCallback(
'pets',
'nemo',
(e) => ({bubbles: e.bubbles}),
[],
store,
(store, partialRow) =>
console.log(`Updated: ${JSON.stringify(partialRow)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'
console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
Since
v1.0.0
useSetRowCallback
The useSetRowCallback
hook returns a parameterized callback that can be used to set the data of a single Row
in a Store
.
useSetRowCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
getRow: (parameter: Parameter, store: Store) => Row,
getRowDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, row: Row) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
getRow | (parameter: Parameter, store: Store) => Row | A function which returns the |
getRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, row: Row) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The third parameter is a function which will produce the Row
object that will then be used to update the Store
in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the mutation to your application's undo stack.
The Store
to which the callback will make the mutation (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetRowCallback
hook to create an event handler which updates the Store
when the span
element is clicked.
import {useRow, useSetRowCallback} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
const handleClick = useSetRowCallback(
'pets',
'nemo',
(e) => ({species: 'fish', bubbles: e.bubbles}),
[],
store,
(store, row) => console.log(`Updated: ${JSON.stringify(row)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"species":"fish","bubbles":true}'
console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
Since
v1.0.0
useCellIds
The useCellIds
hook returns the Ids
of every Cell
in a given Row
, in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
useCellIds(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useCellIds
hook lets you indicate which Store
to get data for: omit the optional final parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Cell
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useCellIds
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCellIds} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useCellIds('pets', 'fido', store))}</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
store.setCell('pets', 'fido', 'species', 'dog');
console.log(app.innerHTML);
// -> '<span>["color","species"]</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useCellIds
hook.
import {Provider, useCellIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useCellIds('pets', 'fido'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useCellIds
hook.
import {Provider, useCellIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useCellIds('pets', 'fido', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
Since
v1.0.0
useCellIdsListener
The useCellIdsListener
hook registers a listener function with a Store
that will be called whenever the Cell
Ids
in a Row
change.
useCellIdsListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: CellIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCellIds
hook).
You can either listen to a single Row
(by specifying the Table
Id
and Row
Id
as the method's first two parameters) or changes to any Row
(by providing null
wildcards).
Both, either, or neither of the tableId
and rowId
parameters can be wildcarded with null
. You can listen to a specific Row
in a specific Table
, any Row
in a specific Table
, a specific Row
in any Table
, or any Row
in any Table
.
Unlike the addCellIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useCellIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useCellIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useCellIdsListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useCellIdsListener('pets', 'fido', () =>
console.log('Cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().cellIds);
// -> 1
store.setCell('pets', 'fido', 'species', 'dog');
// -> 'Cell Ids changed'
root.unmount();
console.log(store.getListenerStats().cellIds);
// -> 0
Since
v1.0.0
useCell
The useCell
hook returns an object containing the value of a single Cell
in a given Row
, in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
useCell(
tableId: string,
rowId: string,
cellId: string,
storeOrStoreId?: StoreOrStoreId,
): CellOrUndefined
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | CellOrUndefined | The value of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useCell
hook lets you indicate which Store
to get data for: omit the final optional final parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Cell
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useCell
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useCell} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{useCell('pets', 'fido', 'color', store)}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useCell
hook.
import {Provider, useCell} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCell('pets', 'fido', 'color')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useCell
hook.
import {Provider, useCell} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useCell('pets', 'fido', 'color', 'petStore')}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v1.0.0
useCellListener
The useCellListener
hook registers a listener function with a Store
that will be called whenever data in a Cell
changes.
useCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: CellListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCell
hook).
You can either listen to a single Cell
(by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or changes to any Cell
(by providing null
wildcards).
All, some, or none of the tableId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific Row
in a specific Table
, any Cell
in any Row
in any Table
, for example - or every other combination of wildcards.
Unlike the addCellListener
method, which returns a listener Id
and requires you to remove it manually, the useCellListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useCellListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useCellListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useCellListener('pets', 'fido', 'color', () =>
console.log('Cell changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().cell);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Cell changed'
root.unmount();
console.log(store.getListenerStats().cell);
// -> 0
Since
v1.0.0
useDelCellCallback
The useDelCellCallback
hook returns a parameterized callback that can be used to remove a single Cell
from a Row
.
useDelCellCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
cellId: string | GetId<Parameter>,
forceDel?: boolean,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
cellId | string | GetId<Parameter> | The |
forceDel? | boolean | An optional flag to indicate that the whole |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the deletion.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the deletion to your application's undo stack.
The Store
to which the callback will make the deletion (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelCellCallback
hook to create an event handler which deletes from the Store
when the span
element is clicked.
import {useDelCellCallback, useTables} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelCellCallback(
'pets',
'nemo',
'species',
false,
store,
() => console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v1.0.0
useHasCell
The useHasCell
hook returns a boolean indicating whether a given Cell
exists in a given Row
in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
useHasCell(
tableId: string,
rowId: string,
cellId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | boolean | Whether a |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useHasCell
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Cell
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useHasCell
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasCell} from 'tinybase/ui-react';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useHasCell('pets', 'fido', 'legs', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>false</span>'
store.setCell('pets', 'fido', 'legs', 4);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useHasCell
hook.
import {Provider, useHasCell} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasCell('pets', 'fido', 'legs'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useHasCell
hook.
import {Provider, useHasCell} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useHasCell('pets', 'fido', 'legs', 'petStore'))}
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
Since
v4.4.0
useHasCellListener
The useHasCellListener
hook registers a listener function with the Store
that will be called when a Cell
is added to or removed from the Store
.
useHasCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: HasCellListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | HasCellListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasCell
hook).
You can either listen to a single Cell
being added or removed (by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or changes to any Cell
(by providing null
wildcards).
All, some, or none of the tableId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific Row
in a specific Table
, any Cell
in any Row
in any Table
, for example - or every other combination of wildcards.
Unlike the addHasCellListener
method, which returns a listener Id
and requires you to remove it manually, the useHasCellListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useHasCellListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useHasCellListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasCellListener('pets', 'fido', 'color', () =>
console.log('Cell existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasCell);
// -> 1
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell existence changed'
root.unmount();
console.log(store.getListenerStats().hasCell);
// -> 0
Since
v4.4.0
useSetCellCallback
The useSetCellCallback
hook returns a parameterized callback that can be used to set the value of a single Cell
in a Store
.
useSetCellCallback<Parameter>(
tableId: string | GetId<Parameter>,
rowId: string | GetId<Parameter>,
cellId: string | GetId<Parameter>,
getCell: (parameter: Parameter, store: Store) => Cell | MapCell,
getCellDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, cell: Cell | MapCell) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
tableId | string | GetId<Parameter> | The |
rowId | string | GetId<Parameter> | The |
cellId | string | GetId<Parameter> | The |
getCell | (parameter: Parameter, store: Store) => Cell | MapCell | A function which returns the |
getCellDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, cell: Cell | MapCell) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The fourth parameter is a function which will produce the Cell
object that will then be used to update the Store
in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the mutation to your application's undo stack.
The Store
to which the callback will make the mutation (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Examples
This example uses the useSetCellCallback
hook to create an event handler which updates the Store
with a Cell
value when the span
element is clicked.
import {useRow, useSetCellCallback} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setCell('pets', 'nemo', 'species', 'fish');
const App = () => {
const handleClick = useSetCellCallback(
'pets',
'nemo',
'bubbles',
(e) => e.bubbles,
[],
store,
(store, cell) => console.log(`Updated: ${cell}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: true'
console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
This example uses the useSetCellCallback
hook to create an event handler which updates the Store
via a MapCell
function when the span
element is clicked.
import {useRow, useSetCellCallback} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setCell('pets', 'nemo', 'visits', 1);
const App = () => {
const handleClick = useSetCellCallback(
'pets',
'nemo',
'visits',
(e) => (visits) => visits + (e.bubbles ? 1 : 0),
[],
store,
() => console.log(`Updated with MapCell function`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"visits":1}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated with MapCell function'
console.log(span.innerHTML);
// -> '{"visits":2}'
Since
v1.0.0
useDelValuesCallback
The useDelValuesCallback
hook returns a callback that can be used to remove all of the keyed value data in a Store
.
useDelValuesCallback(
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in a Store
.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the deletion to your application's undo stack.
The Store
to which the callback will make the deletion (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelValuesCallback
hook to create an event handler which deletes from the Store
when the span
element is clicked.
import {useDelValuesCallback, useValues} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const App = () => {
const handleClick = useDelValuesCallback(store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v3.0.0
useHasValues
The useHasValues
hook returns a boolean indicating whether any Values
exist in the Store
, and registers a listener so that any changes to that result will cause a re-render.
useHasValues(storeOrStoreId?: StoreOrStoreId): boolean
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
returns | boolean | Whether any |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useHasValues
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Values
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useHasValues
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasValues} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useHasValues(store))}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.delValue('open');
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useHasValues
hook.
import {Provider, useHasValues} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasValues())}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useHasValues
hook.
import {Provider, useHasValues} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasValues('petStore'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v4.4.0
useHasValuesListener
The useHasValuesListener
hook registers a listener function with the Store
that will be called when Values
as a whole are added to or removed from the Store
.
useHasValuesListener(
listener: HasValuesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
listener | HasValuesListener | The function that will be called whenever |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasValues
hook).
Unlike the addHasValuesListener
method, which returns a listener Id
and requires you to remove it manually, the useHasValuesListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useHasValuesListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useHasValuesListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasValuesListener(() => console.log('Values existence changed'));
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasValues);
// -> 1
store.setValue('open', true);
// -> 'Values existence changed'
root.unmount();
console.log(store.getListenerStats().hasValues);
// -> 0
Since
v4.4.0
useSetPartialValuesCallback
The useSetPartialValuesCallback
hook returns a parameterized callback that can be used to set partial Values
data in the Store
, leaving other Values
unaffected.
useSetPartialValuesCallback<Parameter>(
getPartialValues: (parameter: Parameter, store: Store) => Values,
getPartialValuesDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, partialValues: Values) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
getPartialValues | (parameter: Parameter, store: Store) => Values | A function which returns the partial |
getPartialValuesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, partialValues: Values) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The third parameter is a function which will produce the partial Values
object that will then be used to update the Store
in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the mutation to your application's undo stack.
The Store
to which the callback will make the mutation (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetPartialValuesCallback
hook to create an event handler which updates the Store
when the span
element is clicked.
import {useSetPartialValuesCallback, useValues} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const App = () => {
const handleClick = useSetPartialValuesCallback(
(e) => ({bubbles: e.bubbles}),
[],
store,
(store, partialValues) =>
console.log(`Updated: ${JSON.stringify(partialValues)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'
console.log(span.innerHTML);
// -> '{"open":true,"bubbles":true}'
Since
v3.0.0
useSetValuesCallback
The useSetValuesCallback
hook returns a parameterized callback that can be used to set the keyed value data of a Store
.
useSetValuesCallback<Parameter>(
getValues: (parameter: Parameter, store: Store) => Values,
getValuesDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, values: Values) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
getValues | (parameter: Parameter, store: Store) => Values | A function which returns the |
getValuesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, values: Values) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The first parameter is a function which will produce the Values
object that will then be used to update the Store
in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the mutation to your application's undo stack.
The Store
to which the callback will make the mutation (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetValuesCallback
hook to create an event handler which updates the Store
when the span
element is clicked.
import {useSetValuesCallback, useValues} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true});
const App = () => {
const handleClick = useSetValuesCallback(
(e) => ({bubbles: e.bubbles}),
[],
store,
(store, values) => console.log(`Updated: ${JSON.stringify(values)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'
console.log(span.innerHTML);
// -> '{"bubbles":true}'
Since
v3.0.0
useValues
The useValues
hook returns a Values
object containing the keyed value data of a Store
, and registers a listener so that any changes to that result will cause a re-render.
useValues(storeOrStoreId?: StoreOrStoreId): Values
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
returns | Values | A |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useValues
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Values
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useValues
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useValues} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValues(store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"open":true}</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>{"open":false}</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useValues
hook.
import {Provider, useValues} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValues())}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"open":true}</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useValues
hook.
import {Provider, useValues} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValues('petStore'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"open":true}</span>'
Since
v3.0.0
useValuesListener
The useValuesListener
hook registers a listener function with a Store
that will be called whenever keyed value data in it changes.
useValuesListener(
listener: ValuesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
listener | ValuesListener | The function that will be called whenever keyed value data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useValues
hook).
Unlike the addValuesListener
method, which returns a listener Id
and requires you to remove it manually, the useValuesListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useValuesListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useValuesListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useValuesListener(() => console.log('Values changed'));
return <span>App</span>;
};
const store = createStore().setValues({open: true});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().values);
// -> 1
store.setValue('open', false);
// -> 'Values changed'
root.unmount();
console.log(store.getListenerStats().values);
// -> 0
Since
v3.0.0
useDelValueCallback
The useDelValueCallback
hook returns a parameterized callback that can be used to remove a single Value
from a Store
.
useDelValueCallback<Parameter>(
valueId: string | GetId<Parameter>,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
valueId | string | GetId<Parameter> | The |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the deletion.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the deletion to your application's undo stack.
The Store
to which the callback will make the deletion (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelValueCallback
hook to create an event handler which deletes from the Store
when the span
element is clicked.
import {useDelValueCallback, useValues} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setValues({open: true, employees: 3});
const App = () => {
const handleClick = useDelValueCallback('open', store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true,"employees":3}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{"employees":3}'
Since
v3.0.0
useHasValue
The useHasValue
hook returns a boolean indicating whether a given Value
exists in the Store
, and registers a listener so that any changes to that result will cause a re-render.
useHasValue(
valueId: string,
storeOrStoreId?: StoreOrStoreId,
): boolean
Type | Description | |
---|---|---|
valueId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | boolean |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useHasValue
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Value
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useHasValue
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useHasValue} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => (
<span>{JSON.stringify(useHasValue('employees', store))}</span>
);
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>false</span>'
store.setValue('employees', 3);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useHasValue
hook.
import {Provider, useHasValue} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useHasValue('employees'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useHasValue
hook.
import {Provider, useHasValue} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useHasValue('employees', 'petStore'))}</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>false</span>'
Since
v4.4.0
useHasValueListener
The useHasValueListener
hook registers a listener function with the Store
that will be called when a Value
is added to or removed from the Store
.
useHasValueListener(
valueId: IdOrNull,
listener: HasValueListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
valueId | IdOrNull | |
listener | HasValueListener | The function that will be called whenever the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useHasValue
hook).
You can either listen to a single Value
being added or removed (by specifying the Value
Id
) or any Value
being added or removed (by providing a null
wildcard).
Unlike the addHasValueListener
method, which returns a listener Id
and requires you to remove it manually, the useHasValueListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useHasValueListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useHasValueListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useHasValueListener('open', () =>
console.log('Value existence changed'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().hasValue);
// -> 1
store.setValue('open', false);
// -> 'Value existence changed'
root.unmount();
console.log(store.getListenerStats().hasValue);
// -> 0
Since
v4.4.0
useSetValueCallback
The useSetValueCallback
hook returns a parameterized callback that can be used to set the data of a single Value
in a Store
.
useSetValueCallback<Parameter>(
valueId: string | GetId<Parameter>,
getValue: (parameter: Parameter, store: Store) => Value | MapValue,
getValueDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, value: Value | MapValue) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
Type | Description | |
---|---|---|
valueId | string | GetId<Parameter> | The |
getValue | (parameter: Parameter, store: Store) => Value | MapValue | A function which returns the |
getValueDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, value: Value | MapValue) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store
. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The second parameter is a function which will produce the Value
object that will then be used to update the Store
in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then
function (with its own set of dependencies) which will be called just after the Store
has been updated. This is a useful place to call the addCheckpoint
method, for example, if you wish to add the mutation to your application's undo stack.
The Store
to which the callback will make the mutation (indicated by the hook's storeOrStoreId
parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetValueCallback
hook to create an event handler which updates the Store
when the span
element is clicked.
import {useSetValueCallback, useValues} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setValue('open', true);
const App = () => {
const handleClick = useSetValueCallback(
'bubbles',
(e) => e.bubbles,
[],
store,
(store, value) => console.log(`Updated: ${JSON.stringify(value)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: true'
console.log(span.innerHTML);
// -> '{"open":true,"bubbles":true}'
Since
v3.0.0
useValue
The useValue
hook returns an object containing the data of a single Value
in a Store
, and registers a listener so that any changes to that result will cause a re-render.
useValue(
valueId: string,
storeOrStoreId?: StoreOrStoreId,
): ValueOrUndefined
Type | Description | |
---|---|---|
valueId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | ValueOrUndefined | An object containing the entire data of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useValue
hook lets you indicate which Store
to get data for: omit the final optional final parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Value
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useValue
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useValue} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValue('open', store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useValue
hook.
import {Provider, useValue} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValue('open'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useValue
hook.
import {Provider, useValue} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useValue('open', 'petStore'))}</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v3.0.0
useValueIds
The useValueIds
hook returns the Ids
of every Value
in a Store
, and registers a listener so that any changes to that result will cause a re-render.
useValueIds(storeOrStoreId?: StoreOrStoreId): Ids
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Store
or a set of Store
objects named by Id
. The useValueIds
hook lets you indicate which Store
to get data for: omit the optional parameter for the default context Store
, provide an Id
for a named context Store
, or provide a Store
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Value
Ids
will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store
outside the application, which is used in the useValueIds
hook by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
import {useValueIds} from 'tinybase/ui-react';
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValueIds(store))}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["open"]</span>'
store.setValue('employees', 3);
console.log(app.innerHTML);
// -> '<span>["open","employees"]</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useValueIds
hook.
import {Provider, useValueIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValueIds())}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["open"]</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useValueIds
hook.
import {Provider, useValueIds} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValueIds('petStore'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["open"]</span>'
Since
v3.0.0
useValueIdsListener
The useValueIdsListener
hook registers a listener function with a Store
that will be called whenever the Value
Ids
in it change.
useValueIdsListener(
listener: ValueIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
listener | ValueIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useValueIds
hook).
Unlike the addValueIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useValueIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useValueIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useValueIdsListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useValueIdsListener(() => console.log('Value Ids changed'));
return <span>App</span>;
};
const store = createStore().setValues({open: true});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().valueIds);
// -> 1
store.setValue('employees', 3);
// -> 'Value Ids changed'
root.unmount();
console.log(store.getListenerStats().valueIds);
// -> 0
Since
v3.0.0
useValueListener
The useValueListener
hook registers a listener function with a Store
that will be called whenever data in a Value
changes.
useValueListener(
valueId: IdOrNull,
listener: ValueListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
valueId | IdOrNull | |
listener | ValueListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useValue
hook).
You can either listen to a single Value
(by specifying its Id
as the method's first parameter) or changes to any Value
(by providing a null
wildcard).
Unlike the addValueListener
method, which returns a listener Id
and requires you to remove it manually, the useValueListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useValueListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useValueListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useValueListener('open', () => console.log('Value changed'));
return <span>App</span>;
};
const store = createStore().setValues({open: true});
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().value);
// -> 1
store.setValue('open', false);
// -> 'Value changed'
root.unmount();
console.log(store.getListenerStats().value);
// -> 0
Since
v3.0.0
useDidFinishTransactionListener
The useDidFinishTransactionListener
hook registers a listener function with a Store
that will be called just after other non-mutating listeners are called at the end of the transaction.
useDidFinishTransactionListener(
listener: TransactionListener,
listenerDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
listener | TransactionListener | The function that will be called after the end of a transaction. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
Unlike the addDidFinishTransactionListener
method, which returns a listener Id
and requires you to remove it manually, the useDidFinishTransactionListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useDidFinishTransactionListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {
Provider,
useDidFinishTransactionListener,
} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useDidFinishTransactionListener(() =>
console.log('Did finish transaction'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().transaction);
// -> 1
store.setValue('open', false);
// -> 'Did finish transaction'
root.unmount();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v4.2.2
useStartTransactionListener
The useStartTransactionListener
hook registers a listener function with the Store
that will be called at the start of a transaction.
useStartTransactionListener(
listener: TransactionListener,
listenerDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
listener | TransactionListener | The function that will be called at the start of a transaction. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
Unlike the addStartTransactionListener
method, which returns a listener Id
and requires you to remove it manually, the useStartTransactionListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useStartTransactionListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {Provider, useStartTransactionListener} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useStartTransactionListener(() => console.log('Start transaction'));
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().transaction);
// -> 1
store.setValue('open', false);
// -> 'Start transaction'
root.unmount();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v4.2.2
useWillFinishTransactionListener
The useWillFinishTransactionListener
hook registers a listener function with a Store
that will be called just before other non-mutating listeners are called at the end of the transaction.
useWillFinishTransactionListener(
listener: TransactionListener,
listenerDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
listener | TransactionListener | The function that will be called before the end of a transaction. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | This has no return value. |
Unlike the addWillFinisTransactionListener method, which returns a listener Id
and requires you to remove it manually, the useWillFinishTransactionListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Store
will be deleted.
Example
This example uses the useWillFinishTransactionListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store
.
import {
Provider,
useWillFinishTransactionListener,
} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useWillFinishTransactionListener(() =>
console.log('Will finish transaction'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().transaction);
// -> 1
store.setValue('open', false);
// -> 'Will finish transaction'
root.unmount();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v4.2.2
Synchronizer hooks
This is the collection of synchronizer hooks within the ui-react
module. There are 6 synchronizer hooks in total.
useCreateSynchronizer
The useCreateSynchronizer
hook is used to create a Synchronizer
within a React application along with convenient memoization and callbacks.
useCreateSynchronizer<SynchronizerOrUndefined>(
store: undefined | MergeableStore,
create: (store: MergeableStore) => Promise<SynchronizerOrUndefined>,
createDeps?: DependencyList,
destroy?: (synchronizer: Synchronizer) => void,
destroyDeps?: DependencyList,
): SynchronizerOrUndefined
Type | Description | |
---|---|---|
store | undefined | MergeableStore | A reference to the |
create | (store: MergeableStore) => Promise<SynchronizerOrUndefined> | An asynchronous function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
destroy? | (synchronizer: Synchronizer) => void | An optional callback whenever the |
destroyDeps? | DependencyList | An optional array of dependencies for the |
returns | SynchronizerOrUndefined | A reference to the |
It is possible to create a Synchronizer
outside of the React app with the regular createSynchronizer function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To prevent a new Synchronizer
being created every time the app renders or re-renders, the useCreateSynchronizer
hook performs the creation in an effect.
If your asynchronous create
function (the second parameter to the hook) contains dependencies, the changing of which should cause the Synchronizer
to be recreated, you can provide them in an array in the third parameter, just as you would for any React hook with dependencies. The MergeableStore
passed in as the first parameter of this hook is used as a dependency by default.
The create
function can return undefined, meaning that you can enable or disable synchronization conditionally within this hook. This is useful for applications which might turn on or off their cloud synchronization or collaboration features.
This hook ensures the Synchronizer
object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Synchronizer
at the top level of a React application. Even though the App component is rendered twice, the Synchronizer
creation only occurs once by default.
import {
useCreateMergeableStore,
useCreateSynchronizer,
useTables,
} from 'tinybase/ui-react';
import React from 'react';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
const App = () => {
const store = useCreateMergeableStore(() => createMergeableStore('s1'));
useCreateSynchronizer(store, async (store) => {
console.log('Synchronizer created');
return await createLocalSynchronizer(store, 'pets');
});
return <span>{JSON.stringify(useTables(store))}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
// -> 'Synchronizer created'
// ...
root.render(<App />);
// No second Synchronizer creation
root.unmount();
This example creates a Synchronizer
at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateSynchronizer
hook takes the url
prop as a dependency, and so the Synchronizer
object is created again on the second render. The first is destroyed and the destroy
parameter is called for it. A then
parameter is provided to start both Synchronizers' synchronization.
import {
useCreateMergeableStore,
useCreateSynchronizer,
useTables,
} from 'tinybase/ui-react';
import React from 'react';
import {WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
const server1 = createWsServer(new WebSocketServer({port: 8044}));
const server2 = createWsServer(new WebSocketServer({port: 8045}));
const App = ({url}) => {
const store = useCreateMergeableStore(() => createMergeableStore('s1'));
useCreateSynchronizer(
store,
async (store) => {
const webSocket = new WebSocket(url);
console.log(`Synchronizer created for ${webSocket.url}`);
return await createWsSynchronizer(store, webSocket);
},
[url],
(synchronizer) => {
const webSocket = synchronizer.getWebSocket();
console.log(`Synchronizer destroyed for ${webSocket.url}`);
},
);
return <span>{JSON.stringify(useTables(store))}</span>;
};
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App url="ws://localhost:8044/" />);
// ...
// -> 'Synchronizer created for ws://localhost:8044/'
root.render(<App url="ws://localhost:8045/" />);
// ...
// -> 'Synchronizer created for ws://localhost:8045/'
// -> 'Synchronizer destroyed for ws://localhost:8044/'
root.unmount();
// -> 'Synchronizer destroyed for ws://localhost:8045/'
server1.destroy();
server2.destroy();
Since
v5.0.0
useSynchronizer
The useSynchronizer
hook is used to get a reference to a Synchronizer
object from within a Provider
component context.
useSynchronizer(id?: string): Synchronizer | undefined
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Synchronizer | undefined | A reference to the |
A Provider
component is used to wrap part of an application in a context. It can contain a default Synchronizer
object (or a set of Synchronizer
objects named by Id
) that can be easily accessed without having to be passed down as props through every component.
The useSynchronizer
hook lets you either get a reference to the default Synchronizer
object (when called without a parameter), or one of the Synchronizer
objects that are named by Id
(when called with an Id
parameter).
Examples
This example creates a Provider context into which a default Synchronizer
object is provided. A component within it then uses the useSynchronizer
hook to get a reference to the Synchronizer
object again, without the need to have it passed as a prop.
import {Provider, useSynchronizer} from 'tinybase/ui-react';
import React from 'react';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
const App = ({synchronizer}) => (
<Provider synchronizer={synchronizer}>
<Pane />
</Provider>
);
const Pane = () => <span>{useSynchronizer().getStatus()}</span>;
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Synchronizer
object is provided, named by Id
. A component within it then uses the useSynchronizer
hook with that Id
to get a reference to the Synchronizer
object again, without the need to have it passed as a prop.
import {Provider, useSynchronizer} from 'tinybase/ui-react';
import React from 'react';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
const App = ({synchronizer}) => (
<Provider synchronizersById={{petSynchronizer: synchronizer}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useSynchronizer('petSynchronizer').getStatus()}</span>
);
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
useSynchronizerIds
The useSynchronizerIds
hook is used to retrieve the Ids
of all the named Synchronizer
objects present in the current Provider
component context.
useSynchronizerIds(): Ids
Example
This example adds two named Synchronizer
objects to a Provider context and an inner component accesses their Ids
.
import {
Provider,
useCreateMergeableStore,
useCreateSynchronizer,
useSynchronizerIds,
} from 'tinybase/ui-react';
import React from 'react';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
const App = () => {
const store1 = useCreateMergeableStore(createMergeableStore);
const synchronizer1 = useCreateSynchronizer(
store1,
createLocalSynchronizer,
);
const store2 = useCreateMergeableStore(createMergeableStore);
const synchronizer2 = useCreateSynchronizer(
store2,
createLocalSynchronizer,
);
return (
<Provider synchronizersById={{synchronizer1, synchronizer2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useSynchronizerIds())}</span>;
const app = document.createElement('div');
createRoot(app).render(<App />);
// ...
console.log(app.innerHTML);
// -> '<span>["synchronizer1","synchronizer2"]</span>'
Since
v5.3.0
useSynchronizerOrSynchronizerById
The useSynchronizerOrSynchronizerById
hook is used to get a reference to a Synchronizer
object from within a Provider
component context, or have it passed directly to this hook.
useSynchronizerOrSynchronizerById(synchronizerOrSynchronizerId?: SynchronizerOrSynchronizerId): Synchronizer | undefined
Type | Description | |
---|---|---|
synchronizerOrSynchronizerId? | SynchronizerOrSynchronizerId | Either an |
returns | Synchronizer | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Synchronizer
object and which might have been passed in explicitly to the component or is to be picked up from the context by Id
(a common pattern for Synchronizer
-based components).
This is unlikely to be used often. For most situations, you will want to use the useSynchronizer
hook.
Example
This example creates a Provider context into which a default Synchronizer
object is provided. A component within it then uses the useSynchronizerOrSynchronizerById
hook to get a reference to the Synchronizer
object again, without the need to have it passed as a prop. Note however, that unlike the useSynchronizer
hook example, this component would also work if you were to pass the Synchronizer
object directly into it, making it more portable.
import {
Provider,
useSynchronizerOrSynchronizerById,
} from 'tinybase/ui-react';
import React from 'react';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
const App = ({synchronizer}) => (
<Provider synchronizer={synchronizer}>
<Pane />
</Provider>
);
const Pane = ({synchronizer}) => (
<span>
{useSynchronizerOrSynchronizerById(synchronizer).getStatus()}
</span>
);
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
useSynchronizerStatus
The useSynchronizerStatus
hook returns a the status of a Synchronizer
, and registers a listener so that any changes to it will cause a re-render.
useSynchronizerStatus(synchronizerOrSynchronizerId?: SynchronizerOrSynchronizerId): Status
Type | Description | |
---|---|---|
synchronizerOrSynchronizerId? | SynchronizerOrSynchronizerId | The |
returns | Status | The status of the |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Synchronizer
or a set of Synchronizer
objects named by Id
. The useSynchronizerStatus
hook lets you indicate which Synchronizer
to get data for: omit the optional parameter for the default context Synchronizer
, provide an Id
for a named context Synchronizer
, or provide a Synchronizer
explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Synchronizer
status will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Synchronizer
outside the application, which is used in the useSynchronizerStatus
hook by reference. A change to the status of the Synchronizer
re-renders the component.
import React from 'react';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
import {useSynchronizerStatus} from 'tinybase/ui-react';
const synchronizer = createLocalSynchronizer(createMergeableStore());
const App = () => <span>{useSynchronizerStatus(synchronizer)}</span>;
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a default Synchronizer
is provided. A component within it then uses the useSynchronizerStatus
hook.
import {Provider, useSynchronizerStatus} from 'tinybase/ui-react';
import React from 'react';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
const App = ({synchronizer}) => (
<Provider synchronizer={synchronizer}>
<Pane />
</Provider>
);
const Pane = () => <span>{useSynchronizerStatus()}</span>;
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
createRoot(app).render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Synchronizer
is provided, named by Id
. A component within it then uses the useSynchronizerStatus
hook.
import {Provider, useSynchronizerStatus} from 'tinybase/ui-react';
import React from 'react';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
const App = ({synchronizer}) => (
<Provider synchronizersById={{petSynchronizer: synchronizer}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useSynchronizerStatus('petSynchronizer')}</span>;
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
createRoot(app).render(<App synchronizer={synchronizer} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v5.3.0
useSynchronizerStatusListener
The useSynchronizerStatusListener
hook registers a listener function with the Synchronizer
that will be called when its status changes.
useSynchronizerStatusListener(
listener: StatusListener<MergeableStoreOnly>,
listenerDeps?: DependencyList,
synchronizerOrSynchronizerId?: SynchronizerOrSynchronizerId,
): void
Type | Description | |
---|---|---|
listener | StatusListener<MergeableStoreOnly> | The function that will be called whenever the status of the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
synchronizerOrSynchronizerId? | SynchronizerOrSynchronizerId | The |
returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSynchronizerStatus
hook).
Unlike the addStatusListener
method, which returns a listener Id
and requires you to remove it manually, the useSynchronizerStatusListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Synchronizer
will be deleted.
Example
This example uses the useSynchronizerStatusListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Synchronizer
.
import {Provider, useSynchronizerStatusListener} from 'tinybase/ui-react';
import React from 'react';
import {createLocalSynchronizer} from 'tinybase/synchronizers/synchronizer-local';
import {createMergeableStore} from 'tinybase';
import {createRoot} from 'react-dom/client';
const App = ({synchronizer}) => (
<Provider synchronizer={synchronizer}>
<Pane />
</Provider>
);
const Pane = () => {
useSynchronizerStatusListener((synchronizer, status) =>
console.log('Synchronizer status changed: ' + status),
);
return <span>App</span>;
};
const synchronizer = createLocalSynchronizer(createMergeableStore());
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App synchronizer={synchronizer} />);
synchronizer.load();
// -> 'Synchronizer status changed: 1'
// ...
// -> 'Synchronizer status changed: 0'
synchronizer.save();
// -> 'Synchronizer status changed: 2'
// ...
// -> 'Synchronizer status changed: 0'
Since
v5.3.0
Checkpoints components
This is the collection of checkpoints components within the ui-react
module. There are 4 checkpoints components in total.
BackwardCheckpointsView
The BackwardCheckpointsView
component renders a list of previous checkpoints that the underlying Store
can go back to.
BackwardCheckpointsView(props: BackwardCheckpointsProps): ComponentReturnType
Type | Description | |
---|---|---|
props | BackwardCheckpointsProps | The props for this component. |
returns | ComponentReturnType | A rendering of the previous checkpoints, if present. |
The component's props identify which previous checkpoints to render based on the Checkpoints
object (which is either the default context Checkpoints
object, a named context Checkpoints
object, or an explicit reference).
This component renders a list by iterating over each checkpoints. By default these are in turn rendered with the CheckpointView
component, but you can override this behavior by providing a checkpointComponent
prop, a custom component of your own that will render a checkpoint based on CheckpointProps
. You can also pass additional props to your custom component with the getCheckpointComponentProps
callback prop.
This component uses the useCheckpointIds
hook under the covers, which means that any changes to the checkpoint Ids
in the Checkpoints
object will cause a re-render.
Examples
This example creates a Checkpoints
object outside the application, which is used in the BackwardCheckpointsView
component by reference to render a list of previous checkpoints.
import {createCheckpoints, createStore} from 'tinybase';
import {BackwardCheckpointsView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<BackwardCheckpointsView checkpoints={checkpoints} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div></div>'
checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
console.log(app.innerHTML);
// -> '<div>initial</div>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>initial/identified</div>'
This example creates a Provider context into which a default Checkpoints
object is provided. The BackwardCheckpointsView
component within it then renders the list of previous checkpoints (with Ids
for readability).
import {BackwardCheckpointsView, Provider} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<BackwardCheckpointsView debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div>0:{initial}1:{identified}</div>'
This example creates a Provider context into which a default Checkpoints
object is provided. The BackwardCheckpointsView
component within it then renders the list of previous checkpoints with a custom Row
component and a custom props callback.
import {
BackwardCheckpointsView,
CheckpointView,
Provider,
} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const getBoldProp = (checkpointId) => ({bold: checkpointId == '0'});
const Pane = () => (
<div>
<BackwardCheckpointsView
checkpointComponent={FormattedCheckpointView}
getCheckpointComponentProps={getBoldProp}
/>
</div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
<span>
{bold ? <b>{checkpointId}</b> : checkpointId}
{': '}
<CheckpointView
checkpoints={checkpoints}
checkpointId={checkpointId}
/>
</span>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div><span><b>0</b>: initial</span><span>1: identified</span></div>'
Since
v1.0.0
CheckpointView
The CheckpointView
component simply renders the label of a checkpoint.
CheckpointView(props: CheckpointProps): ComponentReturnType
Type | Description | |
---|---|---|
props | CheckpointProps | The props for this component. |
returns | ComponentReturnType | A rendering of the checkpoint: its label if present, or |
The component's props identify which checkpoint to render based on Checkpoint Id
and Checkpoints
object (which is either the default context Checkpoints
object, a named context Checkpoints
object, or an explicit reference).
The primary purpose of this component is to render multiple checkpoints in a BackwardCheckpointsView
component or ForwardCheckpointsView
component.
This component uses the useCheckpoint
hook under the covers, which means that any changes to the local Row
Ids
in the Relationship
will cause a re-render.
Example
This example creates a Checkpoints
object outside the application, which is used in the CheckpointView
component by reference to render a checkpoint with a label (with its Id
for readability).
import {createCheckpoints, createStore} from 'tinybase';
import {CheckpointView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<CheckpointView
checkpointId="1"
checkpoints={checkpoints}
debugIds={true}
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>1:{}</div>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>1:{sale}</div>'
checkpoints.setCheckpoint('1', 'sold');
console.log(app.innerHTML);
// -> '<div>1:{sold}</div>'
Since
v1.0.0
CurrentCheckpointView
The CurrentCheckpointView
component renders the current checkpoint that the underlying Store
is currently on.
CurrentCheckpointView(props: CurrentCheckpointProps): ComponentReturnType
Type | Description | |
---|---|---|
props | CurrentCheckpointProps | The props for this component. |
returns | ComponentReturnType | A rendering of the current checkpoint, if present. |
The component's props identify which current checkpoint to render based on the Checkpoints
object (which is either the default context Checkpoints
object, a named context Checkpoints
object, or an explicit reference).
By default the current checkpoint is rendered with the CheckpointView
component, but you can override this behavior by providing a checkpointComponent
prop, a custom component of your own that will render a checkpoint based on CheckpointProps
. You can also pass additional props to your custom component with the getCheckpointComponentProps
callback prop.
This component uses the useCheckpointIds
hook under the covers, which means that any changes to the current checkpoint Id
in the Checkpoints
object will cause a re-render.
Examples
This example creates a Checkpoints
object outside the application, which is used in the CurrentCheckpointView
component by reference to render the current checkpoints.
import {createCheckpoints, createStore} from 'tinybase';
import {CurrentCheckpointView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<CurrentCheckpointView checkpoints={checkpoints} />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div></div>'
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
console.log(app.innerHTML);
// -> '<div>identified</div>'
store.setCell('pets', 'fido', 'sold', true);
console.log(app.innerHTML);
// -> '<div></div>'
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>sale</div>'
This example creates a Provider context into which a default Checkpoints
object is provided. The CurrentCheckpointView
component within it then renders current checkpoint (with its Id
for readability).
import {CurrentCheckpointView, Provider} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<CurrentCheckpointView debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div>1:{identified}</div>'
This example creates a Provider context into which a default Checkpoints
object is provided. The CurrentCheckpointView
component within it then renders the list of future checkpoints with a custom Row
component and a custom props callback.
import {
CheckpointView,
CurrentCheckpointView,
Provider,
} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const getBoldProp = (checkpointId) => ({bold: checkpointId == '1'});
const Pane = () => (
<div>
<CurrentCheckpointView
checkpointComponent={FormattedCheckpointView}
getCheckpointComponentProps={getBoldProp}
/>
</div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
<span>
{bold ? <b>{checkpointId}</b> : checkpointId}
{': '}
<CheckpointView
checkpoints={checkpoints}
checkpointId={checkpointId}
/>
</span>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div><span><b>1</b>: identified</span></div>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div><span>2: sale</span></div>'
Since
v1.0.0
ForwardCheckpointsView
The ForwardCheckpointsView
component renders a list of future checkpoints that the underlying Store
can go forwards to.
ForwardCheckpointsView(props: ForwardCheckpointsProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ForwardCheckpointsProps | The props for this component. |
returns | ComponentReturnType | A rendering of the future checkpoints, if present. |
The component's props identify which future checkpoints to render based on the Checkpoints
object (which is either the default context Checkpoints
object, a named context Checkpoints
object, or an explicit reference).
This component renders a list by iterating over each checkpoints. By default these are in turn rendered with the CheckpointView
component, but you can override this behavior by providing a checkpointComponent
prop, a custom component of your own that will render a checkpoint based on CheckpointProps
. You can also pass additional props to your custom component with the getCheckpointComponentProps
callback prop.
This component uses the useCheckpointIds
hook under the covers, which means that any changes to the checkpoint Ids
in the Checkpoints
object will cause a re-render.
Examples
This example creates a Checkpoints
object outside the application, which is used in the ForwardCheckpointsView
component by reference to render a list of future checkpoints.
import {createCheckpoints, createStore} from 'tinybase';
import {ForwardCheckpointsView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<ForwardCheckpointsView checkpoints={checkpoints} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div></div>'
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goBackward();
console.log(app.innerHTML);
// -> '<div>sale</div>'
checkpoints.goBackward();
console.log(app.innerHTML);
// -> '<div>identified/sale</div>'
This example creates a Provider context into which a default Checkpoints
object is provided. The ForwardCheckpointsView
component within it then renders the list of future checkpoints (with Ids
for readability).
import {ForwardCheckpointsView, Provider} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ForwardCheckpointsView debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goTo('0');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div>1:{identified}2:{sale}</div>'
This example creates a Provider context into which a default Checkpoints
object is provided. The ForwardCheckpointsView
component within it then renders the list of future checkpoints with a custom Row
component and a custom props callback.
import {
CheckpointView,
ForwardCheckpointsView,
Provider,
} from 'tinybase/ui-react';
import {createCheckpoints, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const getBoldProp = (checkpointId) => ({bold: checkpointId == '1'});
const Pane = () => (
<div>
<ForwardCheckpointsView
checkpointComponent={FormattedCheckpointView}
getCheckpointComponentProps={getBoldProp}
/>
</div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
<span>
{bold ? <b>{checkpointId}</b> : checkpointId}
{': '}
<CheckpointView
checkpoints={checkpoints}
checkpointId={checkpointId}
/>
</span>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goTo('0');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div><span><b>1</b>: identified</span><span>2: sale</span></div>'
Since
v1.0.0
Context components
This is the collection of context components within the ui-react
module. There is only one function, Provider
.
Provider
The Provider
component is used to wrap part of an application in a context that provides default objects to be used by hooks and components within.
Provider(props: ProviderProps & {children: ReactNode}): ComponentReturnType
Type | Description | |
---|---|---|
props | ProviderProps & {children: ReactNode} | The props for this component. |
returns | ComponentReturnType | A rendering of the child components. |
Store
, Metrics
, Indexes
, Relationships
, Queries
, Checkpoints
, Persister
, and Synchronizer
objects can be passed into the context of an application and used throughout. One of each type of object can be provided as a default within the context. Additionally, multiple of each type of object can be provided in an Id
-keyed map to the ___ById
props.
Provider contexts can be nested and the objects passed in will be merged. For example, if an outer context contains a default Metrics
object and an inner context contains only a default Store
, both the Metrics
objects and the Store
will be visible within the inner context. If the outer context contains a Store
named by Id
and the inner context contains a Store
named by a different Id
, both will be visible within the inner context.
Examples
This example creates a Provider context into which a Store
and a Metrics
object are provided, one by default, and one named by Id
. Components within it then render content from both, without the need to have them passed as props.
import {CellView, Provider, useMetric} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({store, metrics}) => (
<Provider store={store} metricsById={{petStore: metrics}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="species" rowId="dog" cellId="price" />,
<CellView tableId="species" rowId="cat" cellId="price" />,
{useMetric('highestPrice', 'petStore')}
</span>
);
const store = createStore();
store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App store={store} metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5,4,5</span>'
This example creates nested Provider contexts into which Store
and Metrics
objects are provided, showing how visibility is merged.
import {
CellView,
Provider,
useCreateStore,
useMetric,
} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({petStore, metrics}) => (
<Provider storesById={{pet: petStore}} metrics={metrics}>
<OuterPane />
</Provider>
);
const OuterPane = () => {
const planetStore = useCreateStore(() =>
createStore().setTables({planets: {mars: {moons: 2}}}),
);
return (
<Provider storesById={{planet: planetStore}}>
<InnerPane />
</Provider>
);
};
const InnerPane = () => (
<span>
<CellView tableId="species" rowId="dog" cellId="price" store="pet" />,
{useMetric('highestPrice')},
<CellView
tableId="planets"
rowId="mars"
cellId="moons"
store="planet"
/>
</span>
);
const petStore = createStore();
petStore.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(petStore);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App petStore={petStore} metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5,5,2</span>'
Since
v1.0.0
Indexes components
This is the collection of indexes components within the ui-react
module. There are only two indexes components, IndexView
and SliceView
.
IndexView
The IndexView
component renders the contents of a Index
, and registers a listener so that any changes to that result will cause a re-render.
IndexView(props: IndexProps): ComponentReturnType
Type | Description | |
---|---|---|
props | IndexProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props identify which Index
to render based on Index
Id
, and Indexes
object (which is either the default context Indexes
object, a named context Indexes
object, or an explicit reference).
This component renders a Index
by iterating over its Slice
objects. By default these are in turn rendered with the SliceView
component, but you can override this behavior by providing a sliceComponent
prop, a custom component of your own that will render a Slice
based on SliceProps
. You can also pass additional props to your custom component with the getSliceComponentProps
callback prop.
This component uses the useSliceIds
hook under the covers, which means that any changes to the structure of the Index
will cause a re-render.
Examples
This example creates an Indexes
object outside the application, which is used in the IndexView
component by reference. A change to the Slice
Ids
re-renders the component.
import {createIndexes, createStore} from 'tinybase';
import {IndexView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<div>
<IndexView indexId="bySpecies" indexes={indexes} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog/cat</div>'
store.setRow('pets', 'lowly', {species: 'worm'});
console.log(app.innerHTML);
// -> '<div>dog/cat/worm</div>'
This example creates a Provider context into which a default Indexes
object is provided. The IndexView
component within it then renders the Index
(with Ids
for readability).
import {IndexView, Provider} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<IndexView indexId="bySpecies" debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div>bySpecies:{dog:{fido:{species:{dog}}cujo:{species:{dog}}}}</div>'
This example creates a Provider context into which a default Indexes
object is provided. The IndexView
component within it then renders the Index
with a custom Slice
component and a custom props callback.
import {IndexView, Provider, SliceView} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const getBoldProp = (sliceId) => ({bold: sliceId == 'dog'});
const Pane = () => (
<div>
<IndexView
indexId="bySpecies"
sliceComponent={FormattedSliceView}
getSliceComponentProps={getBoldProp}
/>
</div>
);
const FormattedSliceView = ({indexId, sliceId, bold}) => (
<span>
{bold ? <b>{sliceId}</b> : sliceId}
{': '}
<SliceView indexId={indexId} sliceId={sliceId} separator="/" />
</span>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div><span><b>dog</b>: dog/dog</span><span>cat: cat</span></div>'
Since
v1.0.0
SliceView
The SliceView
component renders the contents of a Slice
, and registers a listener so that any changes to that result will cause a re-render.
SliceView(props: SliceProps): ComponentReturnType
Type | Description | |
---|---|---|
props | SliceProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props identify which Slice
to render based on Index
Id
, Slice
Id
, and Indexes
object (which is either the default context Indexes
object, a named context Indexes
object, or an explicit reference).
This component renders a Slice
by iterating over its Row
objects. By default these are in turn rendered with the RowView
component, but you can override this behavior by providing a rowComponent
prop, a custom component of your own that will render a Row
based on RowProps
. You can also pass additional props to your custom component with the getRowComponentProps
callback prop.
This component uses the useSliceRowIds
hook under the covers, which means that any changes to the structure of the Slice
will cause a re-render.
Examples
This example creates an Indexes
object outside the application, which is used in the SliceView
component by reference. A change to the Row
Ids
re-renders the component.
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {SliceView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<div>
<SliceView
indexId="bySpecies"
sliceId="dog"
indexes={indexes}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setRow('pets', 'cujo', {species: 'dog'});
console.log(app.innerHTML);
// -> '<div>dog/dog</div>'
This example creates a Provider context into which a default Indexes
object is provided. The SliceView
component within it then renders the Slice
(with Ids
for readability).
import {Provider, SliceView} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<SliceView indexId="bySpecies" sliceId="dog" debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div>dog:{fido:{species:{dog}}cujo:{species:{dog}}}</div>'
This example creates a Provider context into which a default Indexes
object is provided. The SliceView
component within it then renders the Slice
with a custom Row
component and a custom props callback.
import {Provider, RowView, SliceView} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<SliceView
indexId="bySpecies"
sliceId="dog"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} separator="/" />
</span>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog/brown</span><span>cujo: dog</span></div>'
Since
v1.0.0
Metrics components
This is the collection of metrics components within the ui-react
module. There is only one function, MetricView
.
MetricView
The MetricView
component renders the current value of a Metric
, and registers a listener so that any changes to that result will cause a re-render.
MetricView(props: MetricProps): ComponentReturnType
Type | Description | |
---|---|---|
props | MetricProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props can identify which Metrics
object to get data for: omit the optional final parameter for the default context Metrics
object, provide an Id
for a named context Metrics
object, or by explicit reference.
This component uses the useMetric
hook under the covers, which means that any changes to the Metric
will cause a re-render.
Examples
This example creates a Metrics
object outside the application, which is used in the MetricView
component hook by reference. A change to the Metric
re-renders the component.
import {createMetrics, createStore} from 'tinybase';
import {MetricView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const App = () => (
<div>
<MetricView metricId="highestPrice" metrics={metrics} />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>5</div>'
store.setCell('species', 'horse', 'price', 20);
console.log(app.innerHTML);
// -> '<div>20</div>'
This example creates a Provider context into which a default Metrics
object is provided. The MetricView
component within it then renders the Metric
(with its Id
for readability).
import {MetricView, Provider} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<MetricView metricId="highestPrice" debugIds={true} />
</div>
);
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<div>highestPrice:{5}</div>'
This example creates a Provider context into which a default Metrics
object is provided. The MetricView
component within it then attempts to render a non-existent Metric
.
import {MetricView, Provider} from 'tinybase/ui-react';
import {createMetrics, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<MetricView metricId="lowestPrice" />
</div>
);
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<div></div>'
Since
v1.0.0
Queries components
This is the collection of queries components within the ui-react
module. There are 4 queries components in total.
ResultSortedTableView
The ResultSortedTableView
component renders the contents of a single query's sorted ResultTable
in a Queries
object, and registers a listener so that any changes to that result will cause a re-render.
ResultSortedTableView(props: ResultSortedTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultSortedTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props identify which ResultTable
to render based on query Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, or by explicit reference). It also takes a Cell
Id
to sort by and a boolean to indicate that the sorting should be in descending order. The offset
and limit
props are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
This component renders a ResultTable
by iterating over its Row
objects, in the order dictated by the sort parameters. By default these are in turn rendered with the ResultRowView
component, but you can override this behavior by providing a resultRowComponent
prop, a custom component of your own that will render a Row
based on ResultRowProps
. You can also pass additional props to your custom component with the getResultRowComponentProps
callback prop.
This component uses the useResultSortedRowIds
hook under the covers, which means that any changes to the structure or sorting of the ResultTable
will cause a re-render.
Examples
This example creates a Queries
object outside the application, which is used in the ResultSortedTableView
component by reference. A change to the data in the Store
re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {ResultSortedTableView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
queries={queries}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>black/brown</div>'
store.setCell('pets', 'felix', 'color', 'white');
console.log(app.innerHTML);
// -> '<div>brown/white</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultSortedTableView
component within it then renders the Table
(with Ids
for readability).
import {Provider, ResultSortedTableView} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
debugIds={true}
/>
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div>petColors:{felix:{color:{black}}fido:{color:{brown}}}</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultSortedTableView
component within it then renders the Table
with a custom Row
component and a custom props callback.
import {
Provider,
ResultRowView,
ResultSortedTableView,
} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
resultRowComponent={FormattedRowView}
getResultRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({queryId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<ResultRowView queryId={queryId} rowId={rowId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div><span>felix: black</span><span><b>fido</b>: brown</span></div>'
Since
v2.0.0
ResultTableView
The ResultTableView
component renders the contents of a single query's ResultTable
in a Queries
object, and registers a listener so that any changes to that result will cause a re-render.
ResultTableView(props: ResultTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props identify which ResultTable
to render based on query Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, or by explicit reference).
This component renders a ResultTable
by iterating over its Row
objects. By default these are in turn rendered with the ResultRowView
component, but you can override this behavior by providing a resultRowComponent
prop, a custom component of your own that will render a Row
based on ResultRowProps
. You can also pass additional props to your custom component with the getResultRowComponentProps
callback prop.
This component uses the useResultRowIds
hook under the covers, which means that any changes to the structure of the ResultTable
will cause a re-render.
Examples
This example creates a Queries
object outside the application, which is used in the ResultTableView
component by reference. A change to the data in the Store
re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {ResultTableView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<div>
<ResultTableView queryId="petColors" queries={queries} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>brown/black</div>'
store.setRow('pets', 'cujo', {species: 'dog', color: 'black'});
console.log(app.innerHTML);
// -> '<div>brown/black/black</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultTableView
component within it then renders the Table
(with Ids
for readability).
import {Provider, ResultTableView} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultTableView queryId="petColors" debugIds={true} />
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div>petColors:{fido:{color:{brown}}felix:{color:{black}}}</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultTableView
component within it then renders the Table
with a custom Row
component and a custom props callback.
import {Provider, ResultRowView, ResultTableView} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<ResultTableView
queryId="petColors"
resultRowComponent={FormattedRowView}
getResultRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({queryId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<ResultRowView queryId={queryId} rowId={rowId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: brown</span><span>felix: black</span></div>'
Since
v2.0.0
ResultRowView
The ResultRowView
component renders the contents of a single Row
in a given query's ResultTable
, and registers a listener so that any changes to that result will cause a re-render.
ResultRowView(props: ResultRowProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultRowProps | The props for this component. |
returns | ComponentReturnType | A rendering of the result |
The component's props identify which Row
to render based on query Id
, Row
Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, or an explicit reference).
This component renders a Row
by iterating over its Cell
values. By default these are in turn rendered with the ResultCellView
component, but you can override this behavior by providing a resultCellComponent
prop, a custom component of your own that will render a Cell
based on ResultCellProps
. You can also pass additional props to your custom component with the getResultCellComponentProps
callback prop.
You can create your own ResultRowView-like component to customize the way that a result Row
is rendered: see the ResultTableView
component for more details.
This component uses the useResultCellIds
hook under the covers, which means that any changes to the structure of the result Row
will cause a re-render.
Examples
This example creates a Queries
object outside the application, which is used in the ResultRowView
component by reference. A change to the data in the Store
re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {ResultRowView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => {
select('species');
select('color');
},
);
const App = () => (
<div>
<ResultRowView
queryId="petColors"
rowId="fido"
queries={queries}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog/brown</div>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<div>dog/walnut</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultRowView
component within it then renders the Row
(with Ids
for readability).
import {Provider, ResultRowView} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultRowView queryId="petColors" rowId="fido" debugIds={true} />
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => {
select('species');
select('color');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div>fido:{species:{dog}color:{brown}}</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultRowView
component within it then renders the Row
with a custom Cell
component and a custom props callback.
import {Provider, ResultCellView, ResultRowView} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const getBoldProp = (cellId) => ({bold: cellId == 'species'});
const Pane = () => (
<div>
<ResultRowView
queryId="petColors"
rowId="fido"
resultCellComponent={FormattedResultCellView}
getResultCellComponentProps={getBoldProp}
/>
</div>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<span>
{bold ? <b>{cellId}</b> : cellId}
{': '}
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => {
select('species');
select('color');
});
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div><span><b>species</b>: dog</span><span>color: brown</span></div>'
Since
v2.0.0
ResultCellView
The ResultCellView
component renders the value of a single Cell
in a given Row
, in a given query's ResultTable
, and registers a listener so that any changes to that result will cause a re-render.
ResultCellView(props: ResultCellProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultCellProps | The props for this component. |
returns | ComponentReturnType | A rendering of the result |
The component's props identify which Cell
to render based on query Id
, Row
Id
, Cell
Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, or an explicit reference).
A Cell
contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own ResultCellView-like component to customize the way that a Cell
is rendered: see the ResultRowView
component for more details.
This component uses the useResultCell
hook under the covers, which means that any changes to the specified Cell
will cause a re-render.
Examples
This example creates a Queries
object outside the application, which is used in the ResultCellView
component by reference. A change to the data in the Store
re-renders the component.
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {ResultCellView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<span>
<ResultCellView
queryId="petColors"
rowId="fido"
cellId="color"
queries={queries}
/>
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Queries
object is provided. The ResultCellView
component within it then renders the Cell
(with its Id
for readability).
import {Provider, ResultCellView} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ResultCellView
queryId="petColors"
rowId="fido"
cellId="color"
debugIds={true}
/>
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>color:{brown}</span>'
This example creates a Provider context into which a default Queries
object is provided. The ResultCellView
component within it then attempts to render a non-existent Cell
.
import {Provider, ResultCellView} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ResultCellView queryId="petColors" rowId="fido" cellId="height" />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span></span>'
Since
v2.0.0
Relationships components
This is the collection of relationships components within the ui-react
module. There are only three relationships components, LinkedRowsView
, LocalRowsView
, and RemoteRowView
.
LinkedRowsView
The LinkedRowsView
component renders the local Row
objects for a given remote Row
in a Relationship
, and registers a listener so that any changes to that result will cause a re-render.
LinkedRowsView(props: LinkedRowsProps): ComponentReturnType
Type | Description | |
---|---|---|
props | LinkedRowsProps | The props for this component. |
returns | ComponentReturnType | A rendering of the local |
The component's props identify which local Rows to render based on Relationship
Id
, remote Row
Id
, and Relationships
object (which is either the default context Relationships
object, a named context Relationships
object, or an explicit reference).
By default the local Rows are rendered with the RowView
component, but you can override this behavior by providing a rowComponent
prop, a custom component of your own that will render the Row
based on RowProps
. You can also pass additional props to your custom component with the getRowComponentProps
callback prop.
This component uses the useLocalRowIds
hook under the covers, which means that any changes to the local Row
Ids
in the Relationship
will cause a re-render.
Examples
This example creates a Relationships
object outside the application, which is used in the LinkedRowsView
component by reference. A change to the Row
Ids
re-renders the component.
import {createRelationships, createStore} from 'tinybase';
import {LinkedRowsView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const store = createStore().setTable('pets', {
fido: {next: 'felix'},
felix: {next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const App = () => (
<div>
<LinkedRowsView
relationshipId="petSequence"
firstRowId="fido"
relationships={relationships}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>felix/cujo/dog</div>'
store.setRow('pets', 'toto', {species: 'dog'});
store.setRow('pets', 'cujo', {next: 'toto'});
console.log(app.innerHTML);
// -> '<div>felix/cujo/toto/dog</div>'
This example creates a Provider context into which a default Relationships
object is provided. The LinkedRowsView
component within it then renders the local Row
objects (with Ids
for readability).
import {LinkedRowsView, Provider} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<LinkedRowsView
relationshipId="petSequence"
firstRowId="fido"
debugIds={true}
/>
</div>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {next: 'felix'},
felix: {species: 'cat'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div>fido:{fido:{next:{felix}}felix:{species:{cat}}}</div>'
This example creates a Provider context into which a default Relationships
object is provided. The LinkedRowsView
component within it then renders the local Row
objects with a custom Row
component and a custom props callback.
import {LinkedRowsView, Provider, RowView} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<LinkedRowsView
relationshipId="petSequence"
firstRowId="fido"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} />
</span>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {next: 'felix'},
felix: {species: 'cat'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: felix</span><span>felix: cat</span></div>'
Since
v1.0.0
LocalRowsView
The LocalRowsView
component renders the local Row
objects for a given remote Row
in a Relationship
, and registers a listener so that any changes to that result will cause a re-render.
LocalRowsView(props: LocalRowsProps): ComponentReturnType
Type | Description | |
---|---|---|
props | LocalRowsProps | The props for this component. |
returns | ComponentReturnType | A rendering of the local |
The component's props identify which local Rows to render based on Relationship
Id
, remote Row
Id
, and Relationships
object (which is either the default context Relationships
object, a named context Relationships
object, or an explicit reference).
By default the local Rows are rendered with the RowView
component, but you can override this behavior by providing a rowComponent
prop, a custom component of your own that will render the Row
based on RowProps
. You can also pass additional props to your custom component with the getRowComponentProps
callback prop.
This component uses the useLocalRowIds
hook under the covers, which means that any changes to the local Row
Ids
in the Relationship
will cause a re-render.
Examples
This example creates a Relationships
object outside the application, which is used in the LocalRowsView
component by reference. A change to the Row
Ids
re-renders the component.
import {createRelationships, createStore} from 'tinybase';
import {LocalRowsView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<div>
<LocalRowsView
relationshipId="petSpecies"
remoteRowId="dog"
relationships={relationships}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog/dog</div>'
store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<div>dog/dog/dog</div>'
This example creates a Provider context into which a default Relationships
object is provided. The LocalRowsView
component within it then renders the local Row
objects (with Ids
for readability).
import {LocalRowsView, Provider} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<LocalRowsView
relationshipId="petSpecies"
remoteRowId="dog"
debugIds={true}
/>
</div>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div>dog:{fido:{species:{dog}}cujo:{species:{dog}}}</div>'
This example creates a Provider context into which a default Relationships
object is provided. The LocalRowsView
component within it then renders the local Row
objects with a custom Row
component and a custom props callback.
import {LocalRowsView, Provider, RowView} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<LocalRowsView
relationshipId="petSpecies"
remoteRowId="dog"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} />
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog</span><span>cujo: dog</span></div>'
Since
v1.0.0
RemoteRowView
The RemoteRowView
component renders the remote Row
Id
for a given local Row
in a Relationship
, and registers a listener so that any changes to that result will cause a re-render.
RemoteRowView(props: RemoteRowProps): ComponentReturnType
Type | Description | |
---|---|---|
props | RemoteRowProps | The props for this component. |
returns | ComponentReturnType | A rendering of the remote |
The component's props identify which remote Row
to render based on Relationship
Id
, local Row
Id
, and Relationships
object (which is either the default context Relationships
object, a named context Relationships
object, or an explicit reference).
By default the remote Row
is rendered with the RowView
component, but you can override this behavior by providing a rowComponent
prop, a custom component of your own that will render the Row
based on RowProps
. You can also pass additional props to your custom component with the getRowComponentProps
callback prop.
This component uses the useRemoteRowId
hook under the covers, which means that any changes to the remote Row
Id
in the Relationship
will cause a re-render.
Examples
This example creates a Relationships
object outside the application, which is used in the RemoteRowView
component by reference. A change to the Row
Ids
re-renders the component.
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {RemoteRowView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<div>
<RemoteRowView
relationshipId="petSpecies"
localRowId="cujo"
relationships={relationships}
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>5</div>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<div>10</div>'
This example creates a Provider context into which a default Relationships
object is provided. The RemoteRowView
component within it then renders the remote Row
(with Ids
for readability).
import {Provider, RemoteRowView} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<RemoteRowView
relationshipId="petSpecies"
localRowId="cujo"
debugIds={true}
/>
</div>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div>cujo:{dog:{price:{5}}}</div>'
This example creates a Provider context into which a default Relationships
object is provided. The RemoteRowView
component within it then renders the remote Row
with a custom Row
component and a custom props callback.
import {Provider, RemoteRowView, RowView} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'dog'});
const Pane = () => (
<div>
<RemoteRowView
relationshipId="petSpecies"
localRowId="cujo"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} />
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div><span><b>dog</b>: 5</span></div>'
Since
v1.0.0
Store components
This is the collection of store components within the ui-react
module. There are 7 store components in total.
TablesView
The TablesView
component renders the tabular contents of a Store
, and registers a listener so that any changes to that result will cause a re-render.
TablesView(props: TablesProps): ComponentReturnType
Type | Description | |
---|---|---|
props | TablesProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props can identify which Store
to render - either the default context Store
, a named context Store
, or an explicit reference.
This component renders a Store
by iterating over its Table
objects. By default these are in turn rendered with the TableView
component, but you can override this behavior by providing a tableComponent
prop, a custom component of your own that will render a Table
based on TableProps
. You can also pass additional props to your custom component with the getTableComponentProps
callback prop.
This component uses the useTableIds
hook under the covers, which means that any changes to the structure of the Store
will cause a re-render.
Examples
This example creates a Store
outside the application, which is used in the TablesView
component by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {TablesView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const App = () => (
<div>
<TablesView store={store} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setTable('species', {dog: {price: 5}});
console.log(app.innerHTML);
// -> '<div>dog/5</div>'
This example creates a Provider context into which a default Store
is provided. The TablesView
component within it then renders the Store
(with Ids
for readability).
import {Provider, TablesView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<TablesView debugIds={true} />
</div>
);
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>pets:{fido:{species:{dog}}}species:{dog:{price:{5}}}</div>'
This example creates a Provider context into which a default Store
is provided. The TablesView
component within it then renders the Store
with a custom Table
component and a custom props callback.
import {Provider, TableView, TablesView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (tableId) => ({bold: tableId == 'pets'});
const Pane = () => (
<div>
<TablesView
tableComponent={FormattedTableView}
getTableComponentProps={getBoldProp}
/>
</div>
);
const FormattedTableView = ({tableId, bold}) => (
<span>
{bold ? <b>{tableId}</b> : tableId}
{': '}
<TableView tableId={tableId} />
</span>
);
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>pets</b>: dog</span><span>species: 5</span></div>'
Since
v1.0.0
TableView
The TableView
component renders the contents of a single Table
in a Store
, and registers a listener so that any changes to that result will cause a re-render.
TableView(props: TableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | TableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props identify which Table
to render based on Table
Id
, and Store
(which is either the default context Store
, a named context Store
, or by explicit reference).
This component renders a Table
by iterating over its Row
objects. By default these are in turn rendered with the RowView
component, but you can override this behavior by providing a rowComponent
prop, a custom component of your own that will render a Row
based on RowProps
. You can also pass additional props to your custom component with the getRowComponentProps
callback prop.
You can create your own TableView-like component to customize the way that a Table
is rendered: see the TablesView
component for more details.
This component uses the useRowIds
hook under the covers, which means that any changes to the structure of the Table
will cause a re-render.
Since v4.1.0, you can use the customCellIds
prop if you want to render a prescribed set of the Table
's Cells in a given order for each Row
.
Examples
This example creates a Store
outside the application, which is used in the TableView
component by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {TableView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const App = () => (
<div>
<TableView tableId="pets" store={store} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setRow('pets', 'felix', {species: 'cat'});
console.log(app.innerHTML);
// -> '<div>dog/cat</div>'
This example creates a Provider context into which a default Store
is provided. The TableView
component within it then renders the Table
for a custom set of Cell
Ids
(and rendered with Ids
for readability).
import {Provider, TableView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const customCellIds = ['species'];
const Pane = () => (
<div>
<TableView
tableId="pets"
customCellIds={customCellIds}
debugIds={true}
/>
</div>
);
const store = createStore().setTable('pets', {
fido: {color: 'black', species: 'dog'},
felix: {color: 'brown', species: 'cat'},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>pets:{fido:{species:{dog}}felix:{species:{cat}}}</div>'
This example creates a Provider context into which a default Store
is provided. The TableView
component within it then renders the Table
with a custom Row
component and a custom props callback.
import {Provider, RowView, TableView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<TableView
tableId="pets"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView tableId={tableId} rowId={rowId} />
</span>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog</span><span>felix: cat</span></div>'
Since
v1.0.0
SortedTableView
The SortedTableView
component renders the contents of a single sorted Table
in a Store
, and registers a listener so that any changes to that result will cause a re-render.
SortedTableView(props: SortedTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | SortedTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props identify which Table
to render based on Table
Id
, and Store
(which is either the default context Store
, a named context Store
, or by explicit reference). It also takes a Cell
Id
to sort by and a boolean to indicate that the sorting should be in descending order. The offset
and limit
props are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
This component renders a Table
by iterating over its Row
objects, in the order dictated by the sort parameters. By default these are in turn rendered with the RowView
component, but you can override this behavior by providing a rowComponent
prop, a custom component of your own that will render a Row
based on RowProps
. You can also pass additional props to your custom component with the getRowComponentProps
callback prop.
This component uses the useSortedRowIds
hook under the covers, which means that any changes to the structure or sorting of the Table
will cause a re-render.
Since v4.1.0, you can use the customCellIds
prop if you want to render a prescribed set of the Table
's Cells in a given order for each Row
.
Examples
This example creates a Store
outside the application, which is used in the SortedTableView
component by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {SortedTableView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const App = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
store={store}
separator="/"
/>
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>cat/dog</div>'
store.setRow('pets', 'cujo', {species: 'wolf'});
console.log(app.innerHTML);
// -> '<div>cat/dog/wolf</div>'
This example creates a Provider context into which a default Store
is provided. The SortedTableView
component within it then renders the Table
for a custom set of Cell
Ids
(and rendered with Ids
for readability).
import {Provider, SortedTableView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const customCellIds = ['species'];
const Pane = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
customCellIds={customCellIds}
debugIds={true}
/>
</div>
);
const store = createStore().setTables({
pets: {
fido: {color: 'black', species: 'dog'},
felix: {color: 'brown', species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>pets:{felix:{species:{cat}}fido:{species:{dog}}}</div>'
This example creates a Provider context into which a default Store
is provided. The SortedTableView
component within it then renders the Table
with a custom Row
component and a custom props callback.
import {Provider, RowView, SortedTableView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (rowId) => ({bold: rowId == 'fido'});
const Pane = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
rowComponent={FormattedRowView}
getRowComponentProps={getBoldProp}
/>
</div>
);
const FormattedRowView = ({tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView tableId={tableId} rowId={rowId} />
</span>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span>felix: cat</span><span><b>fido</b>: dog</span></div>'
Since
v2.0.0
RowView
The RowView
component renders the contents of a single Row
in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
RowView(props: RowProps): ComponentReturnType
Type | Description | |
---|---|---|
props | RowProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props identify which Row
to render based on Table
Id
, Row
Id
, and Store
(which is either the default context Store
, a named context Store
, or an explicit reference).
This component renders a Row
by iterating over its Cell
values. By default these are in turn rendered with the CellView
component, but you can override this behavior by providing a cellComponent
prop, a custom component of your own that will render a Cell
based on CellProps
. You can also pass additional props to your custom component with the getCellComponentProps
callback prop.
You can create your own RowView-like component to customize the way that a Row
is rendered: see the TableView
component for more details.
Since v4.1.0, you can use the customCellIds
prop if you want to render a prescribed set of the Row
's Cells in a given order. Otherwise, this component uses the useCellIds
hook under the covers, which means that any changes to the structure of the Row
will cause a re-render.
Examples
This example creates a Store
outside the application, which is used in the RowView
component by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {RowView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setRow('pets', 'fido', {species: 'dog'});
const App = () => (
<div>
<RowView tableId="pets" rowId="fido" store={store} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<div>dog/walnut</div>'
This example creates a Provider context into which a default Store
is provided. The RowView
component within it then renders the Row
for a custom set of Cell
Ids
(and rendered with Ids
for readability).
import {Provider, RowView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const customCellIds = ['color', 'species'];
const Pane = () => (
<div>
<RowView
tableId="pets"
rowId="fido"
customCellIds={customCellIds}
debugIds={true}
/>
</div>
);
const store = createStore().setRow('pets', 'fido', {
species: 'dog',
color: 'walnut',
legs: 4,
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>fido:{color:{walnut}species:{dog}}</div>'
This example creates a Provider context into which a default Store
is provided. The RowView
component within it then renders the Row
with a custom Cell
component and a custom props callback.
import {CellView, Provider, RowView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (cellId) => ({bold: cellId == 'species'});
const Pane = () => (
<div>
<RowView
tableId="pets"
rowId="fido"
cellComponent={FormattedCellView}
getCellComponentProps={getBoldProp}
/>
</div>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<span>
{bold ? <b>{cellId}</b> : cellId}
{': '}
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</span>
);
const store = createStore().setRow('pets', 'fido', {
species: 'dog',
color: 'walnut',
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>species</b>: dog</span><span>color: walnut</span></div>'
Since
v1.0.0
CellView
The CellView
component renders the value of a single Cell
in a given Row
, in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
CellView(props: CellProps): ComponentReturnType
Type | Description | |
---|---|---|
props | CellProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props identify which Cell
to render based on Table
Id
, Row
Id
, Cell
Id
, and Store
(which is either the default context Store
, a named context Store
, or an explicit reference).
A Cell
contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own CellView-like component to customize the way that a Cell
is rendered: see the RowView
component for more details.
This component uses the useCell
hook under the covers, which means that any changes to the specified Cell
will cause a re-render.
Examples
This example creates a Store
outside the application, which is used in the CellView
component by reference. A change to the data in the Store
re-renders the component.
import {CellView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>
<CellView tableId="pets" rowId="fido" cellId="color" store={store} />
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Store
is provided. The CellView
component within it then renders the Cell
(with its Id
for readability).
import {CellView, Provider} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="pets" rowId="fido" cellId="color" debugIds={true} />
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>color:{brown}</span>'
This example creates a Provider context into which a default Store
is provided. The CellView
component within it then attempts to render a non-existent Cell
.
import {CellView, Provider} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="pets" rowId="fido" cellId="height" />
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span></span>'
Since
v1.0.0
ValuesView
The ValuesView
component renders the keyed value contents of a Store
, and registers a listener so that any changes to that result will cause a re-render.
ValuesView(props: ValuesProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ValuesProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props can identify which Store
to render - either the default context Store
, a named context Store
, or an explicit reference.
This component renders a Store
by iterating over its Value
objects. By default these are in turn rendered with the ValueView
component, but you can override this behavior by providing a valueComponent
prop, a custom component of your own that will render a Value
based on ValueProps
. You can also pass additional props to your custom component with the getValueComponentProps
callback prop.
This component uses the useValueIds
hook under the covers, which means that any changes to the Values
in the Store
will cause a re-render.
This component uses the useValueIds
hook under the covers, which means that any changes to the Store
's Values
will cause a re-render.
Examples
This example creates a Store
outside the application, which is used in the ValuesView
component by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {ValuesView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setValue('open', true);
const App = () => (
<div>
<ValuesView store={store} separator="/" />
</div>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>true</div>'
store.setValue('employees', 3);
console.log(app.innerHTML);
// -> '<div>true/3</div>'
This example creates a Provider context into which a default Store
is provided. The ValuesView
component within it then renders the Values
(with Ids
for readability).
import {Provider, ValuesView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ValuesView debugIds={true} />
</div>
);
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>open:{true}employees:{3}</div>'
This example creates a Provider context into which a default Store
is provided. The ValuesView
component within it then renders the Values
with a custom Value
component and a custom props callback.
import {Provider, ValueView, ValuesView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (valueId) => ({bold: valueId == 'open'});
const Pane = () => (
<div>
<ValuesView
valueComponent={FormattedValueView}
getValueComponentProps={getBoldProp}
/>
</div>
);
const FormattedValueView = ({valueId, bold}) => (
<span>
{bold ? <b>{valueId}</b> : valueId}
{': '}
<ValueView valueId={valueId} />
</span>
);
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>open</b>: true</span><span>employees: 3</span></div>'
Since
v3.0.0
ValueView
The ValueView
component renders the value of a single Value
, and registers a listener so that any changes to that result will cause a re-render.
ValueView(props: ValueProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ValueProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
The component's props identify which Value
to render based on Value
Id
and Store
(which is either the default context Store
, a named context Store
, or an explicit reference).
A Value
contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own ValueView-like component to customize the way that a Value
is rendered: see the ValuesView
component for more details.
This component uses the useValue
hook under the covers, which means that any changes to the specified Value
will cause a re-render.
Examples
This example creates a Store
outside the application, which is used in the ValueView
component by reference. A change to the data in the Store
re-renders the component.
import React from 'react';
import {ValueView} from 'tinybase/ui-react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const store = createStore().setValue('open', true);
const App = () => (
<span>
<ValueView valueId="open" store={store} />
</span>
);
const app = document.createElement('div');
createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store
is provided. The ValueView
component within it then renders the Value
(with its Id
for readability).
import {Provider, ValueView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ValueView valueId="open" debugIds={true} />
</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>open:{true}</span>'
This example creates a Provider context into which a default Store
is provided. The ValueView
component within it then attempts to render a non-existent Value
.
import {Provider, ValueView} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ValueView valueId="website" />
</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span></span>'
Since
v3.0.0
Other functions
This is the collection of other functions within the ui-react
module. There are 6 other functions in total.
useProvideCheckpoints
useProvideCheckpoints(
checkpointsId: string,
checkpoints: Checkpoints,
): void
Type | Description | |
---|---|---|
checkpointsId | string | |
checkpoints | Checkpoints | |
returns | void | This has no return value. |
useProvideIndexes
useProvideIndexes(
indexesId: string,
indexes: Indexes,
): void
Type | Description | |
---|---|---|
indexesId | string | |
indexes | Indexes | |
returns | void | This has no return value. |
useProvidePersister
useProvidePersister(
persisterId: string,
persister: undefined | AnyPersister,
): void
Type | Description | |
---|---|---|
persisterId | string | |
persister | undefined | AnyPersister | |
returns | void | This has no return value. |
useProvideQueries
useProvideQueries(
queriesId: string,
queries: Queries,
): void
Type | Description | |
---|---|---|
queriesId | string | |
queries | Queries | |
returns | void | This has no return value. |
useProvideRelationships
useProvideRelationships(
relationshipsId: string,
relationships: Relationships,
): void
Type | Description | |
---|---|---|
relationshipsId | string | |
relationships | Relationships | |
returns | void | This has no return value. |
useProvideSynchronizer
useProvideSynchronizer(
synchronizerId: string,
synchronizer: undefined | Synchronizer,
): void
Type | Description | |
---|---|---|
synchronizerId | string | |
synchronizer | undefined | Synchronizer | |
returns | void | This has no return value. |
Type Aliases
These are the type aliases within the ui-react
module.
Checkpoints type aliases
This is the collection of checkpoints type aliases within the ui-react
module. There is only one type alias, UndoOrRedoInformation
.
UndoOrRedoInformation
The UndoOrRedoInformation
type is an array that you can fetch from a Checkpoints
object to that indicates if and how you can move the state of the underlying Store
forward or backward.
[boolean, Callback, Id | undefined, string]
This type is useful if you are building undo or redo buttons. See the useUndoInformation
hook and the useRedoInformation
hook for more details and examples.
Since
v1.0.0
Component type aliases
This is the collection of component type aliases within the ui-react
module. There is only one type alias, ComponentReturnType
.
ComponentReturnType
ComponentReturnType
is a simple alias for what a React component can return: either a ReactElement, or null
for an empty component.
ReactElement<any, any> | null
Since
v1.0.0
Identity type aliases
This is the collection of identity type aliases within the ui-react
module. There are 9 identity type aliases in total.
StoreOrStoreId
The StoreOrStoreId
type is used when you need to refer to a Store
in a React hook or component.
Store | Id
In some simple cases you will already have a direct reference to the Store
.
This module also includes a Provider
component that can be used to wrap multiple Store
objects into a context that can be used throughout the app. In this case you will want to refer to a Store
by its Id
in that context.
Many hooks and components in this ui-react
module take this type as a parameter or a prop, allowing you to pass in either the Store
or its Id
.
Since
v1.0.0
CheckpointsOrCheckpointsId
The CheckpointsOrCheckpointsId
type is used when you need to refer to a Checkpoints
object in a React hook or component.
Checkpoints | Id
In some simple cases you will already have a direct reference to the Checkpoints
object.
This module also includes a Provider
component that can be used to wrap multiple Checkpoints
objects into a context that can be used throughout the app. In this case you will want to refer to a Checkpoints
object by its Id
in that context.
Many hooks and components in this ui-react
module take this type as a parameter or a prop, allowing you to pass in either the Checkpoints
object or its Id
.
Since
v1.0.0
GetId
The GetId
type describes a function which, when passed a parameter, will return an Id
.
(
parameter: Parameter,
store: Store,
): Id
This type is used in hooks that create callbacks - like the useSetTableCallback
hook or useSetRowCallback
hook - so that the Id
arguments of the object to set can also be dependent on the event or parameter provided (as well as the object itself being set).
Since
v1.0.0
IndexesOrIndexesId
The IndexesOrIndexesId
type is used when you need to refer to an Indexes
object in a React hook or component.
Indexes | Id
In some simple cases you will already have a direct reference to the Indexes
object.
This module also includes a Provider
component that can be used to wrap multiple Indexes
objects into a context that can be used throughout the app. In this case you will want to refer to an Indexes
object by its Id
in that context.
Many hooks and components in this ui-react
module take this type as a parameter or a prop, allowing you to pass in either the Indexes
object or its Id
.
Since
v1.0.0
MetricsOrMetricsId
The MetricsOrMetricsId
type is used when you need to refer to a Metrics
object in a React hook or component.
Metrics | Id
In some simple cases you will already have a direct reference to the Metrics
object.
This module also includes a Provider
component that can be used to wrap multiple Metrics
objects into a context that can be used throughout the app. In this case you will want to refer to a Metrics
object by its Id
in that context.
Many hooks and components in this ui-react
module take this type as a parameter or a prop, allowing you to pass in either the Metrics
object or its Id
.
Since
v1.0.0
PersisterOrPersisterId
The PersisterOrPersisterId
type is used when you need to refer to a Persister
object in a React hook or component.
AnyPersister | Id
In some simple cases you will already have a direct reference to the Persister
object.
This module also includes a Provider
component that can be used to wrap multiple Persister
objects into a context that can be used throughout the app. In this case you will want to refer to a Persister
object by its Id
in that context.
Many hooks and components in this ui-react
module take this type as a parameter or a prop, allowing you to pass in either the Persister
or its Id
.
Since
v5.3.0
QueriesOrQueriesId
The QueriesOrQueriesId
type is used when you need to refer to a Queries
object in a React hook or component.
Queries | Id
In some simple cases you will already have a direct reference to the Queries
object.
This module also includes a Provider
component that can be used to wrap multiple Queries
objects into a context that can be used throughout the app. In this case you will want to refer to a Queries
object by its Id
in that context.
Many hooks and components in this ui-react
module take this type as a parameter or a prop, allowing you to pass in either the Queries
object or its Id
.
Since
v2.0.0
RelationshipsOrRelationshipsId
The RelationshipsOrRelationshipsId
type is used when you need to refer to a Relationships
object in a React hook or component.
Relationships | Id
In some simple cases you will already have a direct reference to the Relationships
object.
This module also includes a Provider
component that can be used to wrap multiple Relationships
objects into a context that can be used throughout the app. In this case you will want to refer to a Relationships
object by its Id
in that context.
Many hooks and components in this ui-react
module take this type as a parameter or a prop, allowing you to pass in either the Relationships
object or its Id
.
Since
v1.0.0
SynchronizerOrSynchronizerId
The SynchronizerOrSynchronizerId
type is used when you need to refer to a Synchronizer
object in a React hook or component.
Synchronizer | Id
In some simple cases you will already have a direct reference to the Synchronizer
object.
This module also includes a Provider
component that can be used to wrap multiple Synchronizer
objects into a context that can be used throughout the app. In this case you will want to refer to a Synchronizer
object by its Id
in that context.
Many hooks and components in this ui-react
module take this type as a parameter or a prop, allowing you to pass in either the Synchronizer
or its Id
.
Since
v5.3.0
Props type aliases
This is the collection of props type aliases within the ui-react
module. There are 23 props type aliases in total.
TablesProps
TablesProps
props are used for components that refer to all the Tables
in a Store
, such as the TablesView
component.
{
store?: StoreOrStoreId;
tableComponent?: ComponentType<TableProps>;
getTableComponentProps?: (tableId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
store? | StoreOrStoreId | The |
tableComponent? | ComponentType<TableProps> | A component for rendering each |
getTableComponentProps? | (tableId: Id) => ExtraProps | A custom function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
TableProps
TableProps
props are used for components that refer to a single Table
in a Store
, such as the TableView
component.
{
tableId: Id;
store?: StoreOrStoreId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
customCellIds?: Ids;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
tableId | Id | |
store? | StoreOrStoreId | The |
rowComponent? | ComponentType<RowProps> | A custom component for rendering each |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each custom |
customCellIds? | Ids | An optional list of |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
SortedTableProps
SortedTableProps
props are used for components that refer to a single sorted Table
in a Store
, such as the SortedTableView
component.
{
tableId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
store?: StoreOrStoreId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
customCellIds?: Ids;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
tableId | Id | |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
store? | StoreOrStoreId | The |
rowComponent? | ComponentType<RowProps> | A custom component for rendering each |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each custom |
customCellIds? | Ids | An optional list of |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
RowProps
RowProps
props are used for components that refer to a single Row
in a Table
, such as the RowView
component.
{
tableId: Id;
rowId: Id;
store?: StoreOrStoreId;
cellComponent?: ComponentType<CellProps>;
getCellComponentProps?: (cellId: Id) => ExtraProps;
customCellIds?: Ids;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
tableId | Id | |
rowId | Id | |
store? | StoreOrStoreId | The |
cellComponent? | ComponentType<CellProps> | A custom component for rendering each |
getCellComponentProps? | (cellId: Id) => ExtraProps | A function for generating extra props for each custom |
customCellIds? | Ids | An optional list of |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
CellProps
CellProps
props are used for components that refer to a single Cell
in a Row
, such as the CellView
component.
{
tableId: Id;
rowId: Id;
cellId: Id;
store?: StoreOrStoreId;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
tableId | Id | |
rowId | Id | |
cellId | Id | |
store? | StoreOrStoreId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
MetricProps
MetricProps
props are used for components that refer to a single Metric
in a Metrics
object, such as the MetricView
component.
{
metricId: Id;
metrics?: MetricsOrMetricsId;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
metricId | Id | |
metrics? | MetricsOrMetricsId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
IndexProps
IndexProps
props are used for components that refer to a single Index
in an Indexes
object, such as the IndexView
component.
{
indexId: Id;
indexes?: IndexesOrIndexesId;
sliceComponent?: ComponentType<SliceProps>;
getSliceComponentProps?: (sliceId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
indexId | Id | |
indexes? | IndexesOrIndexesId | The |
sliceComponent? | ComponentType<SliceProps> | |
getSliceComponentProps? | (sliceId: Id) => ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
SliceProps
SliceProps
props are used for components that refer to a single Slice
in an Index
object, such as the SliceView
component.
{
indexId: Id;
sliceId: Id;
indexes?: IndexesOrIndexesId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
indexId | Id | |
sliceId | Id | |
indexes? | IndexesOrIndexesId | The |
rowComponent? | ComponentType<RowProps> | |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
LocalRowsProps
LocalRowsProps
props are used for components that refer to a single Relationship
in a Relationships
object, and where you want to render local Rows based on a remote Row
, such as the LocalRowsView
component.
{
relationshipId: Id;
remoteRowId: Id;
relationships?: RelationshipsOrRelationshipsId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
relationshipId | Id | The |
remoteRowId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
RemoteRowProps
RemoteRowProps
props are used for components that refer to a single Relationship
in a Relationships
object, and where you want to render a remote Row
based on a local Row
, such as in the RemoteRowView
component.
{
relationshipId: Id;
localRowId: Id;
relationships?: RelationshipsOrRelationshipsId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
relationshipId | Id | The |
localRowId | Id | |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
LinkedRowsProps
LinkedRowsProps
props are used for components that refer to a single Relationship
in a Relationships
object, and where you want to render a linked list of Rows starting from a first Row
, such as the LinkedRowsView
component.
{
relationshipId: Id;
firstRowId: Id;
relationships?: RelationshipsOrRelationshipsId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
relationshipId | Id | The |
firstRowId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
ResultTableProps
ResultTableProps
props are used for components that refer to a single query ResultTable
, such as the ResultTableView
component.
{
queryId: Id;
queries?: QueriesOrQueriesId;
resultRowComponent?: ComponentType<ResultRowProps>;
getResultRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
queryId | Id | The |
queries? | QueriesOrQueriesId | The |
resultRowComponent? | ComponentType<ResultRowProps> | A custom component for rendering each |
getResultRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
ResultSortedTableProps
ResultSortedTableProps
props are used for components that refer to a single sorted query ResultTable
, such as the ResultSortedTableView
component.
{
queryId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
queries?: QueriesOrQueriesId;
resultRowComponent?: ComponentType<ResultRowProps>;
getResultRowComponentProps?: (rowId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
queryId | Id | The |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
queries? | QueriesOrQueriesId | The |
resultRowComponent? | ComponentType<ResultRowProps> | A custom component for rendering each |
getResultRowComponentProps? | (rowId: Id) => ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
ResultRowProps
ResultRowProps
props are used for components that refer to a single Row
in a query ResultTable
, such as the ResultRowView
component.
{
queryId: Id;
rowId: Id;
queries?: QueriesOrQueriesId;
resultCellComponent?: ComponentType<ResultCellProps>;
getResultCellComponentProps?: (cellId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
queryId | Id | The |
rowId | Id | The |
queries? | QueriesOrQueriesId | The |
resultCellComponent? | ComponentType<ResultCellProps> | A custom component for rendering each |
getResultCellComponentProps? | (cellId: Id) => ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
ResultCellProps
ResultRowProps
props are used for components that refer to a single Cell
in a Row
of a ResultTable
, such as the ResultCellView
component.
{
queryId: Id;
rowId: Id;
cellId: Id;
queries?: QueriesOrQueriesId;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
queryId | Id | The |
rowId | Id | |
cellId | Id | |
queries? | QueriesOrQueriesId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
BackwardCheckpointsProps
BackwardCheckpointsProps
props are used for components that refer to a list of previous checkpoints in a Checkpoints
object, such as the BackwardCheckpointsView
component.
{
checkpoints?: CheckpointsOrCheckpointsId;
checkpointComponent?: ComponentType<CheckpointProps>;
getCheckpointComponentProps?: (checkpointId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | (checkpointId: Id) => ExtraProps | A function for generating extra props for each checkpoint component based on its |
separator? | ReactElement | string | A component or string to separate each Checkpoint component. |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
CurrentCheckpointProps
CurrentCheckpointsProps props are used for components that refer to the current checkpoints in a Checkpoints
object, such as the BackwardCheckpointsView
component.
{
checkpoints?: CheckpointsOrCheckpointsId;
checkpointComponent?: ComponentType<CheckpointProps>;
getCheckpointComponentProps?: (checkpointId: Id) => ExtraProps;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | (checkpointId: Id) => ExtraProps | A function for generating extra props for each checkpoint component based on its |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
ForwardCheckpointsProps
ForwardCheckpointsProps
props are used for components that refer to a list of future checkpoints in a Checkpoints
object, such as the ForwardCheckpointsView
component.
{
checkpoints?: CheckpointsOrCheckpointsId;
checkpointComponent?: ComponentType<CheckpointProps>;
getCheckpointComponentProps?: (checkpointId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | (checkpointId: Id) => ExtraProps | A function for generating extra props for each checkpoint component based on its |
separator? | ReactElement | string | A component or string to separate each Checkpoint component. |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
ProviderProps
ProviderProps
props are used with the Provider
component, so that Store
Metrics
, Indexes
, Relationships
, Queries
, and Checkpoints
objects can be passed into the context of an application and used throughout.
{
store?: Store;
storesById?: {[storeId: Id]: Store};
metrics?: Metrics;
metricsById?: {[metricsId: Id]: Metrics};
indexes?: Indexes;
indexesById?: {[indexesId: Id]: Indexes};
relationships?: Relationships;
relationshipsById?: {[relationshipsId: Id]: Relationships};
queries?: Queries;
queriesById?: {[queriesId: Id]: Queries};
checkpoints?: Checkpoints;
checkpointsById?: {[checkpointsId: Id]: Checkpoints};
persister?: AnyPersister;
persistersById?: {[persisterId: Id]: AnyPersister};
synchronizer?: Synchronizer;
synchronizersById?: {[synchronizerId: Id]: Synchronizer};
}
Type | Description | |
---|---|---|
store? | Store | A default single |
storesById? | {[storeId: Id]: Store} | An object containing multiple |
metrics? | Metrics | A default single |
metricsById? | {[metricsId: Id]: Metrics} | An object containing multiple |
indexes? | Indexes | A default single |
indexesById? | {[indexesId: Id]: Indexes} | An object containing multiple |
relationships? | Relationships | A default single |
relationshipsById? | {[relationshipsId: Id]: Relationships} | An object containing multiple |
queries? | Queries | A default single |
queriesById? | {[queriesId: Id]: Queries} | An object containing multiple |
checkpoints? | Checkpoints | A default single |
checkpointsById? | {[checkpointsId: Id]: Checkpoints} | An object containing multiple |
persister? | AnyPersister | A default single |
persistersById? | {[persisterId: Id]: AnyPersister} | An object containing multiple |
synchronizer? | Synchronizer | A default single |
synchronizersById? | {[synchronizerId: Id]: Synchronizer} | An object containing multiple |
One of each type of object can be provided as a default within the context. Additionally, multiple of each type of object can be provided in an Id
-keyed map to the ___ById
props.
Since
v1.0.0
ExtraProps
The ExtraProps
type represents a set of arbitrary additional props.
{[propName: string]: any}
Since
v1.0.0
ValuesProps
ValuesProps
props are used for components that refer to all the Values
in a Store
, such as the ValuesView
component.
{
store?: StoreOrStoreId;
valueComponent?: ComponentType<ValueProps>;
getValueComponentProps?: (valueId: Id) => ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
store? | StoreOrStoreId | The |
valueComponent? | ComponentType<ValueProps> | A custom component for rendering each |
getValueComponentProps? | (valueId: Id) => ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v3.0.0
ValueProps
ValueProps
props are used for components that refer to a single Value
in a Row
, such as the ValueView
component.
{
valueId: Id;
store?: StoreOrStoreId;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
valueId | Id | |
store? | StoreOrStoreId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v3.0.0
CheckpointProps
CheckpointProps
props are used for components that refer to a single checkpoint in a Checkpoints
object, such as the CheckpointView
component.
{
checkpointId: Id;
checkpoints?: CheckpointsOrCheckpointsId;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
checkpointId | Id | The |
checkpoints? | CheckpointsOrCheckpointsId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v1.0.0
ui-react-dom
The ui-react-dom
module of the TinyBase project provides components to make it easy to create web-based reactive apps with Store
objects.
The components in this module use the react-dom module and so are not appropriate for environments like React Native (although those in the lower-level ui-react
module are).
See also
UI Components demos
Since
v4.1.0
Functions
These are the functions within the ui-react-dom
module.
Indexes components
This is the collection of indexes components within the ui-react-dom
module. There is only one function, SliceInHtmlTable
.
SliceInHtmlTable
The SliceInHtmlTable
component renders the contents of a Slice
as an HTML
SliceInHtmlTable(props: SliceInHtmlTableProps & HtmlTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | SliceInHtmlTableProps & HtmlTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
See the <SliceInHtmlTable /> demo for this component in action.
The component's props identify which Slice
to render based on Index
Id
, Slice
Id
, and Indexes
object (which is either the default context Indexes
object, a named context Indexes
object, or an explicit reference).
This component renders a Slice
by iterating over its Row
objects. By default the Cells are in turn rendered with the CellView
component, but you can override this behavior by providing a component
for each Cell
in the customCells
prop. You can pass additional props to that custom component with the getComponentProps
callback. See the CustomCell
type for more details.
This component uses the useSliceRowIds
hook under the covers, which means that any changes to the structure of the Slice
will cause a re-render.
You can use the headerRow
and idColumn
props to control whether labels and Ids
appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Indexes
object is provided. The SliceInHtmlTable
component within it then renders the Slice
in a <table> element with a CSS class.
import {createIndexes, createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {SliceInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<SliceInHtmlTable indexId="bySpecies" sliceId="dog" className="slice" />
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// ->
`
<table class="slice">
<thead>
<tr>
<th>Id</th>
<th>species</th>
</tr>
</thead>
<tbody>
<tr>
<th>fido</th>
<td>dog</td>
</tr>
<tr>
<th>cujo</th>
<td>dog</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Indexes
object is provided. The SliceInHtmlTable
component within it then renders the Slice
with a custom component and a custom props callback for the species
Cell
. The header row at the top of the table and the Id
column at the start of each row is removed.
import {CellView, Provider} from 'tinybase/ui-react';
import {createIndexes, createStore} from 'tinybase';
import React from 'react';
import {SliceInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<SliceInHtmlTable
indexId="bySpecies"
sliceId="dog"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
species: {
component: FormattedCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>fido</b>:</td>
</tr>
<tr>
<td>cujo:</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
Queries components
This is the collection of queries components within the ui-react-dom
module. There are only two queries components, ResultSortedTableInHtmlTable
and ResultTableInHtmlTable
.
ResultSortedTableInHtmlTable
The SortedTableInHtmlTable
component renders the contents of a single query's sorted ResultTable
in a Queries
object as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
ResultSortedTableInHtmlTable(props: ResultSortedTableInHtmlTableProps & HtmlTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultSortedTableInHtmlTableProps & HtmlTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
See the <ResultSortedTableInHtmlTable /> demo for this component in action.
The component's props identify which ResultTable
to render based on query Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, or by explicit reference). It also takes a Cell
Id
to sort by and a boolean to indicate that the sorting should be in descending order. The offset
and limit
props are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
This component renders a ResultTable
by iterating over its Row
objects, in the order dictated by the sort parameters. By default the Cells are in turn rendered with the CellView
component, but you can override this behavior by providing a component
for each Cell
in the customCells
prop. You can pass additional props to that custom component with the getComponentProps
callback. See the ResultCustomCell type for more details.
This component uses the useSortedRowIds
hook under the covers, which means that any changes to the structure or sorting of the ResultTable
will cause a re-render.
You can use the headerRow
and idColumn
props to control whether the Ids
appear in a <th> element at the top of the table, and the start of each row.
The sortOnClick
prop makes the table's sorting interactive such that the user can click on a column heading to sort by that column. The style classes sorted
and ascending
(or descending
) are added so that you can provide hints to the user how the sorting is being applied.
Provide a paginator component for the ResultTable
with the paginator
prop. Set to true
to use the default SortedTablePaginator, or provide your own component that accepts SortedTablePaginatorProps
.
Finally, the onChange
prop lets you listen to a user's changes to the ResultTable
's sorting or pagination.
Examples
This example creates a Provider context into which a default Queries
object is provided. The ResultSortedTableInHtmlTable
component within it then renders the ResultTable
in a <table> element with a CSS class.
import {createQueries, createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {ResultSortedTableInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultSortedTableInHtmlTable
queryId="petColors"
cellId="color"
className="table"
/>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th class="sorted ascending">↑ color</th>
</tr>
</thead>
<tbody>
<tr>
<th>felix</th>
<td>black</td>
</tr>
<tr>
<th>fido</th>
<td>brown</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Queries
object is provided. The ResultSortedTableInHtmlTable
component within it then renders the ResultTable
with a custom component and a custom props callback for the color
Cell
. The header row at the top of the table and the Id
column at the start of each row is removed.
import {Provider, ResultCellView} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {ResultSortedTableInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultSortedTableInHtmlTable
queryId="petColors"
cellId="color"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
color: {
component: FormattedResultCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td>felix:black</td>
</tr>
<tr>
<td><b>fido</b>:brown</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
ResultTableInHtmlTable
The ResultTableInHtmlTable
component renders the contents of a single query's ResultTable
in a Queries
object as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
ResultTableInHtmlTable(props: ResultTableInHtmlTableProps & HtmlTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultTableInHtmlTableProps & HtmlTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
See the <ResultTableInHtmlTable /> demo for this component in action.
The component's props identify which ResultTable
to render based on query Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, or by explicit reference).
This component renders a ResultTable
by iterating over its Row
objects. By default the Cells are in turn rendered with the CellView
component, but you can override this behavior by providing a component
for each Cell
in the customCells
prop. You can pass additional props to that custom component with the getComponentProps
callback. See the ResultCustomCell type for more details.
This component uses the useRowIds
hook under the covers, which means that any changes to the structure of the Table
will cause a re-render.
You can use the headerRow
and idColumn
props to control whether the Ids
appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Queries
object is provided. The ResultTableInHtmlTable
component within it then renders the ResultTable
in a <table> element with a CSS class.
import {createQueries, createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {ResultTableInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultTableInHtmlTable queryId="petColors" className="table" />
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>color</th>
</tr>
</thead>
<tbody>
<tr>
<th>fido</th>
<td>brown</td>
</tr>
<tr>
<th>felix</th>
<td>black</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Queries
object is provided. The ResultTableInHtmlTable
component within it then renders the ResultTable
with a custom component and a custom props callback for the color
Cell
. The header row at the top of the table and the Id
column at the start of each row is removed.
import {Provider, ResultCellView} from 'tinybase/ui-react';
import {createQueries, createStore} from 'tinybase';
import React from 'react';
import {ResultTableInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultTableInHtmlTable
queryId="petColors"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
color: {
component: FormattedResultCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>fido</b>:brown</td>
</tr>
<tr>
<td>felix:black</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
Relationships components
This is the collection of relationships components within the ui-react-dom
module. There is only one function, RelationshipInHtmlTable
.
RelationshipInHtmlTable
The RelationshipInHtmlTable
component renders the contents of the two Tables
linked by a Relationship
as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
RelationshipInHtmlTable(props: RelationshipInHtmlTableProps & HtmlTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | RelationshipInHtmlTableProps & HtmlTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the two |
See the <RelationshipInHtmlTable /> demo for this component in action.
The component's props identify which Relationship
to render based on Relationship
Id
and Relationships
object (which is either the default context Relationships
object, a named context Relationships
object, or an explicit reference).
This component renders the two Table
objects by iterating over their related Row
objects. By default the Cells are in turn rendered with the CellView
component, but you can override this behavior by providing a component
for each Cell
in the customCells
prop. You can pass additional props to that custom component with the getComponentProps
callback. See the CustomCell
type for more details.
Note the use of dotted 'tableId.cellId' string pairs when specifying custom rendering for the cells in this table, since Cells from both the relationship's 'local' and 'remote' Table
objects can be rendered and need to be distinguished.
This component uses the useRowIds and useRemoteRowId
hooks under the covers, which means that any changes to the structure of either Table
resulting in a change to the relationship will cause a re-render.
You can use the headerRow
and idColumn
props to control whether labels and Ids
appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Relationships
object is provided. The RelationshipInHtmlTable
component within it then renders the two Tables
linked by a relationship in a <table> element with a CSS class. Note the dotted pairs that are used as column headings.
import {createRelationships, createStore} from 'tinybase';
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {RelationshipInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<RelationshipInHtmlTable
relationshipId="petSpecies"
className="relationship"
/>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// ->
`
<table class="relationship">
<thead>
<tr>
<th>pets.Id</th>
<th>species.Id</th>
<th>pets.species</th>
<th>species.price</th>
</tr>
</thead>
<tbody>
<tr>
<th>fido</th>
<th>dog</th>
<td>dog</td>
<td>5</td>
</tr>
<tr>
<th>cujo</th>
<th>dog</th>
<td>dog</td>
<td>5</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Relationships
object is provided. The RelationshipInHtmlTable
component within it then renders the two Tables
linked by a relationship with a custom component and a custom props callback for the species
Cell
. The header row at the top of the table and the Id
column at the start of each row is removed.
import {CellView, Provider} from 'tinybase/ui-react';
import {createRelationships, createStore} from 'tinybase';
import React from 'react';
import {RelationshipInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<RelationshipInHtmlTable
relationshipId="petSpecies"
customCells={customCells}
idColumn={false}
headerRow={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, store, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView
tableId={tableId}
rowId={rowId}
cellId={cellId}
store={store}
/>
</>
);
const customCells = {
'species.price': {
component: FormattedCellView,
getComponentProps: (rowId) => ({bold: rowId == 'dog'}),
},
};
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'wolf'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>dog</b>:5</td>
</tr>
<tr>
<td>wolf:10</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
Store components
This is the collection of store components within the ui-react-dom
module. There are 6 store components in total.
TableInHtmlTable
The TableInHtmlTable
component renders the contents of a single Table
in a Store
as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
TableInHtmlTable(props: TableInHtmlTableProps & HtmlTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | TableInHtmlTableProps & HtmlTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
See the <TableInHtmlTable /> demo for this component in action.
The component's props identify which Table
to render based on Table
Id
, and Store
(which is either the default context Store
, a named context Store
, or by explicit reference).
This component renders a Table
by iterating over its Row
objects. By default the Cells are in turn rendered with the CellView
component, but you can override this behavior by providing a component
for each Cell
in the customCells
prop. You can pass additional props to that custom component with the getComponentProps
callback. See the CustomCell
type for more details.
This component uses the useRowIds
hook under the covers, which means that any changes to the structure of the Table
will cause a re-render.
You can use the headerRow
and idColumn
props to control whether the Ids
appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Store
is provided. The TableInHtmlTable
component within it then renders the Table
in a <table> element with a CSS class.
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {TableInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <TableInHtmlTable tableId="pets" className="table" />;
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>species</th>
</tr>
</thead>
<tbody>
<tr>
<th>fido</th>
<td>dog</td>
</tr>
<tr>
<th>felix</th>
<td>cat</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Store
is provided. The TableInHtmlTable
component within it then renders the Table
with a custom component and a custom props callback for the species
Cell
. The header row at the top of the table and the Id
column at the start of each row is removed.
import {CellView, Provider} from 'tinybase/ui-react';
import React from 'react';
import {TableInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<TableInHtmlTable
tableId="pets"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
species: {
component: FormattedCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>fido</b>:dog</td>
</tr>
<tr>
<td>felix:cat</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
SortedTableInHtmlTable
The SortedTableInHtmlTable
component renders the contents of a single sorted Table
in a Store
, as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
SortedTableInHtmlTable(props: SortedTableInHtmlTableProps & HtmlTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | SortedTableInHtmlTableProps & HtmlTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
See the <SortedTableInHtmlTable /> demo for this component in action.
The component's props identify which Table
to render based on Table
Id
, and Store
(which is either the default context Store
, a named context Store
, or by explicit reference). It also takes a Cell
Id
to sort by and a boolean to indicate that the sorting should be in descending order. The offset
and limit
props are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
This component renders a ResultTable
by iterating over its Row
objects, in the order dictated by the sort parameters. By default the Cells are in turn rendered with the CellView
component, but you can override this behavior by providing a component
for each Cell
in the customCells
prop. You can pass additional props to that custom component with the getComponentProps
callback. See the CustomCell
type for more details.
This component uses the useSortedRowIds
hook under the covers, which means that any changes to the structure or sorting of the Table
will cause a re-render.
You can use the headerRow
and idColumn
props to control whether the Ids
appear in a <th> element at the top of the table, and the start of each row.
The sortOnClick
prop makes the table's sorting interactive such that the user can click on a column heading to sort by that column. The style classes sorted
and ascending
(or descending
) are added so that you can provide hints to the user how the sorting is being applied.
Provide a paginator component for the Table
with the paginator
prop. Set to true
to use the default SortedTablePaginator, or provide your own component that accepts SortedTablePaginatorProps
.
Finally, the onChange
prop lets you listen to a user's changes to the Table
's sorting or pagination.
Examples
This example creates a Provider context into which a default Store
is provided. The SortedTableInHtmlTable
component within it then renders the Table
in a <table> element with a CSS class.
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {SortedTableInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<SortedTableInHtmlTable
tableId="pets"
cellId="species"
className="table"
/>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th class="sorted ascending">↑ species</th>
</tr>
</thead>
<tbody>
<tr>
<th>felix</th>
<td>cat</td>
</tr>
<tr>
<th>fido</th>
<td>dog</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Store
is provided. The SortedTableInHtmlTable
component within it then renders the Table
with a custom component and a custom props callback for the species
Cell
. The header row at the top of the table and the Id
column at the start of each row is removed.
import {CellView, Provider} from 'tinybase/ui-react';
import React from 'react';
import {SortedTableInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<SortedTableInHtmlTable
tableId="pets"
cellId="species"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
species: {
component: FormattedCellView,
getComponentProps: (rowId) => ({bold: rowId == 'fido'}),
},
};
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td>felix:cat</td>
</tr>
<tr>
<td><b>fido</b>:dog</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
SortedTablePaginator
The SortedTablePaginator
component renders a paginator for a sorted table.
SortedTablePaginator(props: SortedTablePaginatorProps): ComponentReturnType
Type | Description | |
---|---|---|
props | SortedTablePaginatorProps | The props for this component. |
returns | ComponentReturnType | The rendering of a paginator control with a label, and next and previous buttons, where appropriate. |
See the <SortedTableInHtmlTable /> demo for this component in action.
The component displays 'previous' and 'next' buttons for paging through the Table
if there are more Row
Ids
than fit in each page. The component will also display a label that shows which Row
Ids
are being displayed.
The component's props identify initial pagination settings, and it will fire an event when the pagination changes.
Example
This example creates a Provider context into which a default Store
is provided. The SortedTableInHtmlTable
component within it then renders the Table
in a <table> element with a SortedTablePaginator (the default).
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {SortedTableInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<SortedTableInHtmlTable
tableId="pets"
cellId="species"
limit={2}
paginator={true}
/>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
lowly: {species: 'worm'},
polly: {species: 'parrot'},
},
});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<caption>
<button class="previous" disabled="">←</button>
<button class="next">→</button>
1 to 2 of 5 rows
</caption>
<thead>
<tr>
<th>Id</th>
<th class="sorted ascending">↑ species</th>
</tr>
</thead>
<tbody>
<tr>
<th>felix</th>
<td>cat</td>
</tr>
<tr>
<th>fido</th>
<td>dog</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
ValuesInHtmlTable
The ValuesInHtmlTable
component renders the keyed value contents of a Store
as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
ValuesInHtmlTable(props: ValuesInHtmlTableProps & HtmlTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ValuesInHtmlTableProps & HtmlTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the |
See the <ValuesInHtmlTable /> demo for this component in action.
The component's props identify which Row
to render based on Table
Id
, Row
Id
, and Store
(which is either the default context Store
, a named context Store
, or an explicit reference).
This component renders a Store
by iterating over its Value
objects. By default the Values
are in turn rendered with the ValueView
component, but you can override this behavior by providing a valueComponent
prop, a custom component of your own that will render a Value
based on ValueProps
. You can also pass additional props to your custom component with the getValueComponentProps
callback prop.
This component uses the useValueIds
hook under the covers, which means that any changes to the structure of the Values
in the Store
will cause a re-render.
You can use the headerRow
and idColumn
props to control whether labels and Ids
appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Store
is provided. The ValuesInHtmlTable
component within it then renders the Values
in a <table> element with a CSS class.
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {ValuesInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <ValuesInHtmlTable className="values" />;
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table class="values">
<thead>
<tr>
<th>Id</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>open</th>
<td>true</td>
</tr>
<tr>
<th>employees</th>
<td>3</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Store
is provided. The ValuesInHtmlTable
component within it then renders the Row
with a custom Cell
component and a custom props callback. The header row at the top of the table and the Id
column at the start of each row is removed.
import {Provider, ValueView} from 'tinybase/ui-react';
import React from 'react';
import {ValuesInHtmlTable} from 'tinybase/ui-react-dom';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const getBoldProp = (valueId) => ({bold: valueId == 'open'});
const Pane = () => (
<ValuesInHtmlTable
valueComponent={FormattedValueView}
getValueComponentProps={getBoldProp}
headerRow={false}
idColumn={false}
/>
);
const FormattedValueView = ({valueId, bold}) => (
<>
{bold ? <b>{valueId}</b> : valueId}
{': '}
<ValueView valueId={valueId} />
</>
);
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr><td><b>open</b>: true</td></tr>
<tr><td>employees: 3</td></tr>
</tbody>
</table>
`;
Since
v4.1.0
EditableCellView
The EditableCellView
component renders the value of a single Cell
in a way that can be edited in a web browser, and registers a listener so that any changes to that result will cause a re-render.
EditableCellView(props: CellProps & {
className?: string;
showType?: boolean;
}): ComponentReturnType
Type | Description | |
---|---|---|
props | CellProps & { className?: string; showType?: boolean; } | The props for this component. |
returns | ComponentReturnType | An editable rendering of the |
See the <EditableCellView /> demo for this component in action.
The component's props identify which Cell
to render based on Table
Id
, Row
Id
, Cell
Id
, and Store
(which is either the default context Store
, a named context Store
, or an explicit reference).
A Cell
contains a string, number, or boolean, so the value is rendered in an appropriate <input> tag and a button lets the user change type, if possible.
Set the showType
prop to false to remove the ability for the user to see or change the Cell
type. They will also not be able to change the type if there is a TablesSchema
applied to the Store
.
This component uses the useCell
hook under the covers, which means that any changes to the specified Cell
outside of this component will cause a re-render.
You can provide a custom className prop which well be used on the root of the resulting element. If omitted the element's class will be editableCell
. The debugIds prop has no effect on this component.
Example
This example creates a Provider context into which a default Store
is provided. The EditableCellView
component within it then renders an editable Cell
.
import {EditableCellView} from 'tinybase/ui-react-dom';
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<EditableCellView tableId="pets" rowId="fido" cellId="color" />
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<div class="editableCell">
<button class="string">string</button>
<input value="brown">
</div>
`;
Since
v4.1.0
EditableValueView
The EditableValueView
component renders the value of a single Value
in a way that can be edited in a web browser, and registers a listener so that any changes to that result will cause a re-render.
EditableValueView(props: ValueProps & {
className?: string;
showType?: boolean;
}): ComponentReturnType
Type | Description | |
---|---|---|
props | ValueProps & { className?: string; showType?: boolean; } | The props for this component. |
returns | ComponentReturnType | An editable rendering of the |
See the <EditableValueView /> demo for this component in action.
The component's props identify which Value
to render based on Table
Id
, Row
Id
, Value
Id
, and Store
(which is either the default context Store
, a named context Store
, or an explicit reference).
A Value
contains a string, number, or boolean, so the value is rendered in an appropriate <input> tag and a button lets the user change type, if possible.
Set the showType
prop to false to remove the ability for the user to see or change the Value
type. They will also not be able to change the type if there is a ValuesSchema
applied to the Store
.
This component uses the useValue
hook under the covers, which means that any changes to the specified Value
outside of this component will cause a re-render.
You can provide a custom className prop which well be used on the root of the resulting element. If omitted the element's class will be editableValue
. The debugIds prop has no effect on this component.
Example
This example creates a Provider context into which a default Store
is provided. The EditableValueView
component within it then renders an editable Value
.
import {EditableValueView} from 'tinybase/ui-react-dom';
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <EditableValueView valueId="employees" />;
const store = createStore().setValue('employees', 3);
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<div class="editableValue">
<button class="number">number</button>
<input type="number" value="3">
</div>
`;
Since
v4.1.0
Type Aliases
These are the type aliases within the ui-react-dom
module.
Configuration type aliases
This is the collection of configuration type aliases within the ui-react-dom
module. There are only two configuration type aliases, CustomCell
and CustomResultCell
.
CustomCell
The CustomCell
object is used to configure custom cell rendering in an HTML table.
{
label?: string;
component?: ComponentType<CellProps>;
getComponentProps?: (rowId: Id, cellId: Id) => ExtraProps;
}
Type | Description | |
---|---|---|
label? | string | An optional string that will be used as the label at the top of the table column for this |
component? | ComponentType<CellProps> | An optional custom component for rendering each |
getComponentProps? | (rowId: Id, cellId: Id) => ExtraProps | An optional function for generating extra props for each custom |
Since
v4.1.0
CustomResultCell
The CustomResultCell
object is used to configure custom cell rendering for query results in an HTML table.
{
label?: string;
component?: ComponentType<ResultCellProps>;
getComponentProps?: (rowId: Id, cellId: Id) => ExtraProps;
}
Type | Description | |
---|---|---|
label? | string | An optional string that will be used as the label at the top of the table column for this |
component? | ComponentType<ResultCellProps> | An optional custom component for rendering each |
getComponentProps? | (rowId: Id, cellId: Id) => ExtraProps | An optional function for generating extra props for each custom |
Since
v4.1.0
Props type aliases
This is the collection of props type aliases within the ui-react-dom
module. There are 9 props type aliases in total.
TableInHtmlTableProps
TableInHtmlTableProps
props are used for components that will render a Table
in an HTML table, such as the TableInHtmlTable
component.
{
tableId: Id;
store?: StoreOrStoreId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
}
Type | Description | |
---|---|---|
tableId | Id | |
store? | StoreOrStoreId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of |
Since
v4.1.0
HtmlTableProps
HtmlTableProps
props are used for components that will render in an HTML table, such as the TableInHtmlTable
component or SortedTableInHtmlTable
component.
{
className?: string;
headerRow?: boolean;
idColumn?: boolean;
}
Type | Description | |
---|---|---|
className? | string | A string className to use on the root of the resulting element. |
headerRow? | boolean | Whether a header row should be rendered at the top of the table, defaulting to |
idColumn? | boolean | Whether an |
Since
v4.1.0
RelationshipInHtmlTableProps
RelationshipInHtmlTableProps
props are used for components that will render the contents of the two Tables
linked by a Relationship
as an HTML table, such as the RelationshipInHtmlTable
component.
{
relationshipId: Id;
relationships?: RelationshipsOrRelationshipsId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
}
Type | Description | |
---|---|---|
relationshipId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of dotted 'tableId.cellId' string pairs to use for rendering a prescribed set of the relationship |
Note the use of dotted 'tableId.cellId' string pairs when specifying custom rendering for the cells in this table, since Cells from both the relationship's 'local' and 'remote' Table
objects can be rendered and need to be distinguished.
Since
v4.1.0
ResultSortedTableInHtmlTableProps
ResultSortedTableInHtmlTableProps
props are used for components that will render a sorted Table
in an HTML table, such as the SortedTableInHtmlTable
component.
{
queryId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
queries?: QueriesOrQueriesId;
customCells?: Ids | {[cellId: Id]: string | CustomResultCell};
sortOnClick?: boolean;
paginator?: boolean | ComponentType<SortedTablePaginatorProps>;
onChange?: (sortAndOffset: [cellId: Id | undefined, descending: boolean, offset: number]) => void;
}
Type | Description | |
---|---|---|
queryId | Id | The |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
queries? | QueriesOrQueriesId | The |
customCells? | Ids | {[cellId: Id]: string | CustomResultCell} | An optional list of |
sortOnClick? | boolean | Whether the table should be interactive such that clicking a header changes the sorting and/or direction. |
paginator? | boolean | ComponentType<SortedTablePaginatorProps> | Either |
onChange? | (sortAndOffset: [cellId: Id | undefined, descending: boolean, offset: number]) => void | A function that is called whenever the sorting or pagination of the |
Since
v4.1.0
ResultTableInHtmlTableProps
ResultTableInHtmlTableProps
props are used for components that will render a ResultTable
in an HTML table, such as the ResultTableInHtmlTable
component.
{
queryId: Id;
queries?: QueriesOrQueriesId;
customCells?: Ids | {[cellId: Id]: string | CustomResultCell};
}
Type | Description | |
---|---|---|
queryId | Id | The |
queries? | QueriesOrQueriesId | The |
customCells? | Ids | {[cellId: Id]: string | CustomResultCell} | An optional list of |
Since
v4.1.0
SliceInHtmlTableProps
SliceInHtmlTableProps
props are used for components that will render an Index
Slice
in an HTML table, such as the SliceInHtmlTable
component.
{
indexId: Id;
sliceId: Id;
indexes?: IndexesOrIndexesId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
}
Type | Description | |
---|---|---|
indexId | Id | |
sliceId | Id | |
indexes? | IndexesOrIndexesId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of |
Since
v4.1.0
SortedTableInHtmlTableProps
SortedTableInHtmlTableProps
props are used for components that will render a sorted Table
in an HTML table, such as the SortedTableInHtmlTable
component.
{
tableId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
store?: StoreOrStoreId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
sortOnClick?: boolean;
paginator?: boolean | ComponentType<SortedTablePaginatorProps>;
onChange?: (sortAndOffset: [cellId: Id | undefined, descending: boolean, offset: number]) => void;
}
Type | Description | |
---|---|---|
tableId | Id | |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
store? | StoreOrStoreId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of |
sortOnClick? | boolean | Whether the table should be interactive such that clicking a header changes the sorting and/or direction. |
paginator? | boolean | ComponentType<SortedTablePaginatorProps> | Either |
onChange? | (sortAndOffset: [cellId: Id | undefined, descending: boolean, offset: number]) => void | A function that is called whenever the sorting or pagination of the |
Since
v4.1.0
SortedTablePaginatorProps
SortedTablePaginatorProps
props are used for components that will be used as a table paginator, such as the SortedTablePaginator
component.
{
onChange: (offset: number) => void;
offset?: number;
limit?: number;
total: number;
singular?: string;
plural?: string;
}
Type | Description | |
---|---|---|
onChange | (offset: number) => void | An event that will fire when the offset is updated, called with the new offset. |
offset? | number | |
limit? | number | |
total | number | |
singular? | string | A noun to use in the pagination label for a single row, defaulting to 'row'. |
plural? | string | A noun to use in the pagination label for multiple rows, defaulting to the value of the singular noun suffixed with the letter 's'. |
Since
v4.1.0
ValuesInHtmlTableProps
ValuesInHtmlTableProps
props are used for components that will render Values
in an HTML table, such as the ValuesInHtmlTable
component.
{
store?: StoreOrStoreId;
editable?: boolean;
valueComponent?: ComponentType<ValueProps>;
getValueComponentProps?: (valueId: Id) => ExtraProps;
}
Type | Description | |
---|---|---|
store? | StoreOrStoreId | The |
editable? | boolean | Whether the |
valueComponent? | ComponentType<ValueProps> | A custom component for rendering each |
getValueComponentProps? | (valueId: Id) => ExtraProps | A function for generating extra props for each custom |
Since
v4.1.0
ui-react-inspector
The ui-react-inspector
module of the TinyBase project provides a component to help debug the state of your TinyBase stores and other objects.
The component in this module uses the react-dom module and so is not appropriate for environments like React Native.
See also
<Inspector /> demo
Since
v5.0.0
Functions
There is one function, Inspector
, within the ui-react-inspector
module.
Inspector
The Inspector
component renders a tool which allows you to view and edit the content of a Store
in a debug web environment.
Inspector(props: InspectorProps): ComponentReturnType
Type | Description | |
---|---|---|
props | InspectorProps | The props for this component. |
returns | ComponentReturnType | The rendering of the inspector tool. |
See the <Inspector /> demo for this component in action.
The component displays a nub in the corner of the screen which you may then click to interact with all the Store
objects in the Provider
component context.
The component's props identify the nub's initial location and panel state, though subsequent user changes to that will be preserved on each reload.
Example
This example creates a Provider context into which a default Store
is provided. The Inspector
component within it then renders the inspector tool.
import {Inspector} from 'tinybase/ui-react-inspector';
import {Provider} from 'tinybase/ui-react';
import React from 'react';
import {createRoot} from 'react-dom/client';
import {createStore} from 'tinybase';
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <Inspector />;
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const app = document.createElement('div');
createRoot(app).render(<App store={store} />);
// ...
console.log(app.innerHTML.substring(0, 30));
// -> '<aside id="tinybaseInspector">'
Since
v5.0.0
Type Aliases
There is one type alias, InspectorProps
, within the ui-react-inspector
module.
InspectorProps
InspectorProps
props are used to configure the Inspector
component.
{
position?: "top" | "right" | "bottom" | "left" | "full";
open?: boolean;
}
Type | Description | |
---|---|---|
position? | "top" | "right" | "bottom" | "left" | "full" | An optional string to indicate where you want the inspector to first appear. |
open? | boolean | An optional boolean to indicate whether the inspector should start in the opened state. |
Since
v5.0.0