/** * 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. * @packageDocumentation * @module queries * @since v2.0.0 */ import type { Cell, CellOrUndefined, GetCell, GetIdChanges, Store, } from '../store/index.d.ts'; import type {Id, IdOrNull, Ids} from '../common/index.d.ts'; /** * The ResultTable type is the data structure representing the results of a * query. * * 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 * ```js * import type {ResultTable} from 'tinybase'; * * export const resultTable: ResultTable = { * fido: {species: 'dog', color: 'brown'}, * felix: {species: 'cat'}, * }; * ``` * @category Result * @since v2.0.0 */ export type ResultTable = {[rowId: Id]: ResultRow}; /** * The ResultRow type is the data structure representing a single row in the * results of a query. * * 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 * ```js * import type {ResultRow} from 'tinybase'; * * export const resultRow: ResultRow = {species: 'dog', color: 'brown'}; * ``` * @category Result * @since v2.0.0 */ export type ResultRow = {[cellId: Id]: ResultCell}; /** * The ResultCell type is the data structure representing a single cell in the * results of a query. * * 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 * ```js * import type {ResultCell} from 'tinybase'; * * export const resultCell: ResultCell = 'dog'; * ``` * @category Result * @since v2.0.0 */ export type ResultCell = string | number | boolean; /** * The ResultCellOrUndefined type is the data structure representing a single * cell in the results of a query, or the value `undefined`. * @category Result * @since v2.0.0 */ export type ResultCellOrUndefined = ResultCell | undefined; /** * The Aggregate type describes a custom function that takes an array of Cell * values and returns an aggregate of them. * * 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. * @param cells The array of Cell values to be aggregated. * @param length The length of the array of Cell values to be aggregated. * @returns The value of the aggregation. * @category Aggregators * @since v2.0.0 */ export type Aggregate = (cells: Cell[], length: number) => ResultCell; /** * 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. * * 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. * @param current The current value of the aggregation. * @param add The Cell value being added to the aggregation. * @param length The length of the array of Cell values in the aggregation. * @returns The new value of the aggregation. * @category Aggregators * @since v2.0.0 */ export type AggregateAdd = ( current: Cell, add: Cell, length: number, ) => ResultCellOrUndefined; /** * 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. * * 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. * @param current The current value of the aggregation. * @param remove The Cell value being removed from the aggregation. * @param length The length of the array of Cell values in the aggregation. * @returns The new value of the aggregation. * @category Aggregators * @since v2.0.0 */ export type AggregateRemove = ( current: Cell, remove: Cell, length: number, ) => ResultCellOrUndefined; /** * 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. * * 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. * @param current The current value of the aggregation. * @param add The Cell value being added to the aggregation. * @param remove The Cell value being removed from the aggregation. * @param length The length of the array of Cell values in the aggregation. * @returns The new value of the aggregation. * @category Aggregators * @since v2.0.0 */ export type AggregateReplace = ( current: Cell, add: Cell, remove: Cell, length: number, ) => ResultCellOrUndefined; /** * The QueryCallback type describes a function that takes a query's Id. * * 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. * @param queryId The Id of the query that the callback can operate on. * @category Callback * @since v2.0.0 */ export type QueryCallback = (queryId: Id) => void; /** * The ResultTableCallback type describes a function that takes a ResultTable's * Id and a callback to loop over each ResultRow within it. * * 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. * @param tableId The Id of the ResultTable that the callback can operate on. * @param forEachRow A function that will let you iterate over the ResultRow * objects in this ResultTable. * @category Callback * @since v2.0.0 */ export type ResultTableCallback = ( tableId: Id, forEachRow: (rowCallback: ResultRowCallback) => void, ) => void; /** * The ResultRowCallback type describes a function that takes a ResultRow's Id * and a callback to loop over each ResultCell within it. * * 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. * @param rowId The Id of the ResultRow that the callback can operate on. * @param forEachRow A function that will let you iterate over the ResultCell * values in this ResultRow. * @category Callback * @since v2.0.0 */ export type ResultRowCallback = ( rowId: Id, forEachCell: (cellCallback: ResultCellCallback) => void, ) => void; /** * The ResultCellCallback type describes a function that takes a ResultCell's Id * and its 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. * @param cellId The Id of the ResultCell that the callback can operate on. * @param cell The value of the ResultCell. * @category Callback * @since v2.0.0 */ export type ResultCellCallback = (cellId: Id, cell: ResultCell) => void; /** * The QueryIdsListener type describes a function that is used to listen * to Query definitions being added or removed. * * 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. * @param queries A reference to the Queries object that changed. * @category Listener * @since v2.0.0 */ export type QueryIdsListener = (queries: Queries) => void; /** * The ResultTableListener type describes a function that is used to listen to * changes to a query's ResultTable. * * 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. * @param queries A reference to the Queries object that changed. * @param tableId The Id of the ResultTable that changed, which is also the * query Id. * @param getCellChange A function that returns information about any * ResultCell's changes. * @category Listener * @since v2.0.0 */ export type ResultTableListener = ( queries: Queries, tableId: Id, getCellChange: GetResultCellChange, ) => void; /** * 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. * * 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. * @param queries A reference to the Queries object that changed. * @param tableId The Id of the ResultTable that changed, which is also the * query Id. * @category Listener * @since v4.1.0 */ export type ResultTableCellIdsListener = ( queries: Queries, tableId: Id, getIdChanges: GetIdChanges | undefined, ) => void; /** * The ResultRowCountListener type describes a function that is used to listen * to changes to the number of ResultRow objects in a query's ResultTable. * * 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. * @param queries A reference to the Queries object that changed. * @param tableId The Id of the ResultTable that changed, which is also the * query Id. * @param count The number of ResultRow objects in the ResultTable. * @category Listener * @since v4.1.0 */ export type ResultRowCountListener = ( queries: Queries, tableId: Id, count: number, ) => void; /** * The ResultRowIdsListener type describes a function that is used to listen to * changes to the ResultRow Ids in a query's ResultTable. * * 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. * @param queries A reference to the Queries object that changed. * @param tableId The Id of the ResultTable that changed, which is also the * query Id. * @category Listener * @since v2.0.0 */ export type ResultRowIdsListener = ( queries: Queries, tableId: Id, getIdChanges: GetIdChanges | undefined, ) => void; /** * The ResultSortedRowIdsListener type describes a function that is used to * listen to changes to the sorted ResultRow Ids in a query's ResultTable. * * 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. * @param queries A reference to the Queries object that changed. * @param tableId The Id of the ResultTable that changed, which is also the * query Id. * @param cellId The Id of the ResultCell whose values were used for the * sorting. * @param descending Whether the sorting was in descending order. * @param offset The number of ResultRow Ids skipped. * @param limit The maximum number of ResultRow Ids returned. * @param sortedRowIds The sorted ResultRow Ids themselves. * @category Listener * @since v2.0.0 */ export type ResultSortedRowIdsListener = ( queries: Queries, tableId: Id, cellId: Id | undefined, descending: boolean, offset: number, limit: number | undefined, sortedRowIds: Ids, ) => void; /** * The ResultRowListener type describes a function that is used to listen to * changes to a ResultRow in a query's ResultTable. * * 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. * @param queries A reference to the Queries object that changed. * @param tableId The Id of the ResultTable that changed, which is also the * query Id. * @param rowId The Id of the ResultRow that changed. * @param getCellChange A function that returns information about any * ResultCell's changes. * @category Listener * @since v2.0.0 */ export type ResultRowListener = ( queries: Queries, tableId: Id, rowId: Id, getCellChange: GetResultCellChange, ) => void; /** * 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. * * 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. * @param queries A reference to the Queries object that changed. * @param tableId The Id of the ResultTable that changed, which is also the * query Id. * @param rowId The Id of the ResultRow that changed. * @category Listener * @since v2.0.0 */ export type ResultCellIdsListener = ( queries: Queries, tableId: Id, rowId: Id, getIdChanges: GetIdChanges | undefined, ) => void; /** * The ResultCellListener type describes a function that is used to listen to * changes to a ResultCell in a query's ResultTable. * * 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. * @param queries A reference to the Queries object that changed. * @param tableId The Id of the ResultTable that changed, which is also the * query Id. * @param rowId The Id of the ResultRow that changed. * @param cellId The Id of the ResultCell that changed. * @param newCell The new value of the ResultCell that changed. * @param oldCell The old value of the ResultCell that changed. * @param getCellChange A function that returns information about any * ResultCell's changes. * @category Listener * @since v2.0.0 */ export type ResultCellListener = ( queries: Queries, tableId: Id, rowId: Id, cellId: Id, newCell: ResultCell, oldCell: ResultCell, getCellChange: GetResultCellChange, ) => void; /** * The GetResultCellChange type describes a function that returns information * about any ResultCell's changes during a transaction. * * 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. * @param tableId The Id of the ResultTable to inspect. * @param rowId The Id of the ResultRow to inspect. * @param cellId The Id of the ResultCell to inspect. * @returns A ResultCellChange array containing information about the * ResultCell's changes. * @category Listener * @since v2.0.0 */ export type GetResultCellChange = ( tableId: Id, rowId: Id, cellId: Id, ) => ResultCellChange; /** * The ResultCellChange type describes a ResultCell's changes during a * transaction. * * 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. * @category Listener * @since v2.0.0 */ export type ResultCellChange = [ changed: boolean, oldCell: ResultCellOrUndefined, newCell: ResultCellOrUndefined, ]; /** * The QueriesListenerStats type describes the number of listeners registered * with the Queries object, and can be used for debugging purposes. * * A QueriesListenerStats object is returned from the getListenerStats method. * @category Development * @since v2.0.0 */ export type QueriesListenerStats = { /** * The number of ResultTableListener functions registered with the Queries * object. * @category Stat * @since v2.0.0 */ table: number; /** * The number of ResultTableCellIdsListener functions registered with the * Queries object, since v3.3. * @category Stat * @since v2.0.0 */ tableCellIds: number; /** * The number of ResultRowCountListener functions registered with the Queries * object, since v4.1. * @category Stat * @since v2.0.0 */ rowCount: number; /** * The number of ResultRowIdsListener functions registered with the Queries * object. * @category Stat * @since v2.0.0 */ rowIds: number; /** * The number of SortedRowIdsListener functions registered with the Queries * object. * @category Stat * @since v2.0.0 */ sortedRowIds: number; /** * The number of ResultRowListener functions registered with the Queries * object. * @category Stat * @since v2.0.0 */ row: number; /** * The number of ResultCellIdsListener functions registered with the Queries * object. * @category Stat * @since v2.0.0 */ cellIds: number; /** * The number of ResultCellListener functions registered with the Queries * object. * @category Stat * @since v2.0.0 */ cell: number; }; /** * The GetTableCell type describes a function that takes a Id and returns the * Cell value for a particular Row, optionally in a joined Table. * * 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. * @category Callback * @since v2.0.0 */ export type GetTableCell = { /** * 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. * @param cellId The Id of the Cell to fetch the value for. * @returns A Cell value or `undefined`. * @category Callback * @since v2.0.0 */ (cellId: Id): CellOrUndefined; /** * 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. * @param joinedTableId The Id of the Table to fetch the value from. If the * underlying Table was joined 'as' a different Id, that should instead be * used. * @param joinedCellId The Id of the Cell to fetch the value for. * @returns A Cell value or `undefined`. * @category Callback * @since v2.0.0 */ (joinedTableId: Id, joinedCellId: Id): CellOrUndefined; }; /** * The Select type describes a function that lets you specify a Cell or * calculated value for including into the query's result. * * 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. * @example * This example shows a query that selects two Cells from the main query Table. * * ```js * 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'}} * ``` * @example * This example shows a query that selects two Cells, one from a joined Table. * * ```js * 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'}} * ``` * @example * This example shows a query that calculates a value from two underlying Cells. * * ```js * 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'}} * ``` * @category Definition * @since v2.0.0 */ export type Select = { /** * 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. * @param cellId The Id of the Cell to fetch the value for. * @returns A SelectedAs object so that the selected Cell Id can be optionally * aliased. * @category Definition * @since v2.0.0 */ (cellId: Id): SelectedAs; /** * 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. * @param joinedTableId The Id of the Table to fetch the value from. If the * underlying Table was joined 'as' a different Id, that should instead be * used. * @param joinedCellId The Id of the Cell to fetch the value for. * @returns A SelectedAs object so that the selected Cell Id can be optionally * aliased. * @category Definition * @since v2.0.0 */ (joinedTableId: Id, joinedCellId: Id): SelectedAs; /** * 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. * @param getCell A callback that takes a GetTableCell function and the main * Table's Row Id. These can be used to programmatically create a calculated * value from multiple Cell values and the Row Id. * @returns A SelectedAs object so that the selected Cell Id can be optionally * aliased. * @category Definition * @since v2.0.0 */ ( getCell: (getTableCell: GetTableCell, rowId: Id) => ResultCellOrUndefined, ): SelectedAs; }; /** * The SelectedAs type describes an object returned from calling a Select * function so that the selected Cell Id can be optionally aliased. * * 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: * * ```js * 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'}} * ``` * @category Definition * @since v2.0.0 */ export type SelectedAs = { /** * A function that lets you specify an alias for the Cell Id. * @category Definition * @since v2.0.0 */ as: (selectedCellId: Id) => void; }; /** * 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. * * 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. * @example * This example shows a query that joins a single Table by using an Id present * in the main query Table. * * ```js * 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'}} * ``` * @example * 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. * * ```js * 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'}} * ``` * @example * 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. * * ```js * 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}} * ``` * @example * This example shows a query that joins two Tables, one through the * intermediate other. * * ```js * 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'}} * ``` * @category Definition * @since v2.0.0 */ export type Join = { /** * 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. * @param joinedTableId The Id of the Table to join to. * @param on The Id of the Cell in the root Table that contains the joined * Table's Row Id. * @returns A JoinedAs object so that the joined Table Id can be optionally * aliased. * @category Definition * @since v2.0.0 */ (joinedTableId: Id, on: Id): JoinedAs; /** * 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. * @param joinedTableId The Id of the Table to join to. * @param on A callback that takes a GetCell function and the root Table's Row * Id. These can be used to programmatically calculate the joined Table's Row * Id. * @returns A JoinedAs object so that the joined Table Id can be optionally * aliased. * @category Definition * @since v2.0.0 */ ( joinedTableId: Id, on: (getCell: GetCell, rowId: Id) => Id | undefined, ): JoinedAs; /** * 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. * @param joinedTableId The Id of the distant Table to join to. * @param fromIntermediateJoinedTableId The Id of an intermediate Table (which * should have been in turn joined to the main query table via other Join * clauses). * @param on The Id of the Cell in the intermediate Table that contains the * joined Table's Row Id. * @returns A JoinedAs object so that the joined Table Id can be optionally * aliased. * @category Definition * @since v2.0.0 */ (joinedTableId: Id, fromIntermediateJoinedTableId: Id, on: Id): JoinedAs; /** * 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. * @param joinedTableId The Id of the Table to join to. * @param fromIntermediateJoinedTableId The Id of an intermediate Table (which * should have been in turn joined to the main query table via other Join * clauses). * @param on A callback that takes a GetCell function and the intermediate * Table's Row Id. These can be used to programmatically calculate the joined * Table's Row Id. * @returns A JoinedAs object so that the joined Table Id can be optionally * aliased. * @category Definition * @since v2.0.0 */ ( joinedTableId: Id, fromIntermediateJoinedTableId: Id, on: ( getIntermediateJoinedCell: GetCell, intermediateJoinedRowId: Id, ) => Id | undefined, ): JoinedAs; }; /** * The JoinedAs type describes an object returned from calling a Join function * so that the joined Table Id can be optionally aliased. * * 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. * * ```js * 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'}} * ``` * @category Definition * @since v2.0.0 */ export type JoinedAs = { /** * A function that lets you specify an alias for the joined Table Id. * @category Definition * @since v2.0.0 */ as: (joinedTableId: Id) => void; }; /** * 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. * * 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. * @example * This example shows a query that filters the results from a single Table by * comparing an underlying Cell from it with a value. * * ```js * 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'}} * ``` * @example * 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. * * ```js * 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'}} * ``` * @example * 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. * * ```js * 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'}} * ``` * @category Definition * @since v2.0.0 */ export type Where = { /** * 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. * @param cellId The Id of the Cell in the query's root Table to test. * @param equals The value that the Cell has to have for the Row to be * included in the result. * @category Definition * @since v2.0.0 */ (cellId: Id, equals: Cell): void; /** * 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. * @param joinedTableId The Id of the joined Table to test a value in. If the * underlying Table was joined 'as' a different Id, that should instead be * used. * @param joinedCellId The Id of the Cell in the joined Table to test. * @param equals The value that the Cell has to have for the Row to be * included in the result. * @category Definition * @since v2.0.0 */ (joinedTableId: Id, joinedCellId: Id, equals: Cell): void; /** * 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. * @param condition A callback that takes a GetTableCell function and that * should return `true` for the Row to be included in the result. * @category Definition * @since v2.0.0 */ (condition: (getTableCell: GetTableCell) => boolean): void; }; /** * The Group type describes a function that lets you specify that the values of * a Cell in multiple ResultRows should be aggregated together. * * 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 by` semantics 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. * @param selectedCellId The Id of the Cell to aggregate. If the underlying Cell * was selected 'as' a different Id, that should instead be used. * @param aggregate Either a string representing one of a set of common * aggregation techniques ('count', 'sum', 'avg', 'min', or 'max'), or a * function that aggregates Cell values from each Row to create the aggregate's * overall. * @param aggregateAdd 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 - for example, when a Row is added to the Table. * @param aggregateRemove 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 - for example, when a Row is removed from the Table. * @param aggregateReplace 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 - for example, when a Row is updated. * @returns A GroupedAs object so that the grouped Cell Id can be optionally * aliased. * @example * This example shows a query that calculates the average of all the values in a * single selected Cell from a joined Table. * * ```js * 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 * ``` * @example * This example shows a query that calculates the average of a two Cell values, * aggregated by the two other dimensional 'group by' Cells. * * ```js * 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. * ``` * @category Definition * @since v2.0.0 */ export type Group = ( selectedCellId: Id, aggregate: 'count' | 'sum' | 'avg' | 'min' | 'max' | Aggregate, aggregateAdd?: AggregateAdd, aggregateRemove?: AggregateRemove, aggregateReplace?: AggregateReplace, ) => GroupedAs; /** * The GroupedAs type describes an object returned from calling a Group function * so that the grouped Cell Id can be optionally aliased. * * 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. * * ```js * 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}} * ``` * @category Definition * @since v2.0.0 */ export type GroupedAs = { /** * A function that lets you specify an alias for the grouped Cell Id. * @category Definition * @since v2.0.0 */ as: (groupedCellId: Id) => void; }; /** * The Having type describes a function that lets you specify conditions to * filter results, based on the grouped Cells resulting from a Group clause. * * 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. * @example * This example shows a query that filters the results from a grouped Table by * comparing a Cell from it with a value. * * ```js * 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}} * ``` * @example * This example shows a query that filters the results from a grouped Table with * a condition that is calculated from Cell values. * * ```js * 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. * ``` * @category Definition * @since v2.0.0 */ export type Having = { /** * 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. * @param selectedOrGroupedCellId The Id of the Cell in the query to test. * @param equals The value that the Cell has to have for the Row to be * included in the result. * @category Definition * @since v2.0.0 */ (selectedOrGroupedCellId: Id, equals: Cell): void; /** * Calling this function with one callback parameter is used to include only * those Rows which meet a calculated boolean condition. * @param condition A callback that takes a GetCell function and that should * return `true` for the Row to be included in the result. * @category Definition * @since v2.0.0 */ (condition: (getSelectedOrGroupedCell: GetCell) => boolean): void; }; /** * 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. * * ```js * 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 Using Queries guides * @see Car Analysis demo * @see Movie Database demo * @category Queries * @since v2.0.0 */ export interface Queries { // /** * The setQueryDefinition method lets you set the definition of a query. * * 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 a Cell or * calculated value for including into the query's result. * - The Join type describes a function that lets you specify a Cell or * calculated value to join the main query Table to others, by Row Id. * - The Where type describes a function that lets you specify conditions to * filter results, based on the underlying Cells of the main or joined * Tables. * - The Group type describes a function that lets you specify that the values * of a Cell 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 a Group 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. * @param queryId The Id of the query to define. * @param tableId The Id of the root Table the query will be based on. * @param query A callback which can take a `keywords` object and which uses the functions it contains to define the query. * @returns A reference to the Queries object. * @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. * * ```js * 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'}} * ``` * @category Configuration * @since v2.0.0 */ setQueryDefinition( queryId: Id, tableId: Id, query: (keywords: { select: Select; join: Join; where: Where; group: Group; having: Having; }) => void, ): Queries; /** * The delQueryDefinition method removes an existing query definition. * @param queryId The Id of the query to remove. * @returns A reference to the Queries object. * @example * This example creates a Store, creates a Queries object, defines a simple * query, and then removes it. * * ```js * 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()); * // -> [] * ``` * @category Configuration * @since v2.0.0 */ delQueryDefinition(queryId: Id): Queries; /** * The getStore method returns a reference to the underlying Store that is * backing this Queries object. * @returns A reference to the Store. * @example * This example creates a Queries object against a newly-created Store and * then gets its reference in order to update its data. * * ```js * 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'}} * ``` * @category Getter * @since v2.0.0 */ getStore(): Store; /** * The getQueryIds method returns an array of the query Ids registered with * this Queries object. * @returns An array of Ids. * @example * This example creates a Queries object with two definitions, and then gets * the Ids of the definitions. * * ```js * 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'] * ``` * @category Getter * @since v2.0.0 */ getQueryIds(): Ids; /** * The forEachQuery method takes a function that it will then call for each * Query in the Queries object. * * 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. * @param queryCallback The function that should be called for every query. * @example * This example iterates over each query in a Queries object. * * ```js * 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' * ``` * @category Iterator * @since v2.0.0 */ forEachQuery(queryCallback: QueryCallback): void; /** * The hasQuery method returns a boolean indicating whether a given query * exists in the Queries object. * @param queryId The Id of a possible query in the Queries object. * @returns Whether a query with that Id exists. * @example * This example shows two simple query existence checks. * * ```js * 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 * ``` * @category Getter * @since v2.0.0 */ hasQuery(queryId: Id): boolean; /** * The getTableId method returns the Id of the underlying Table that is * backing a query. * * If the query Id is invalid, the method returns `undefined`. * @param queryId The Id of a query. * @returns The Id of the Table backing the query, or `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. * * ```js * 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 * ``` * @category Getter * @since v2.0.0 */ getTableId(queryId: Id): Id | undefined; /** * The getResultTable method returns an object containing the entire data of * the ResultTable of the given query. * * 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. * @param queryId The Id of a query. * @returns An object containing the entire data of the ResultTable of the * query. * @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. * * ```js * 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')); * // -> {} * ``` * @category Result * @since v2.0.0 */ getResultTable(queryId: Id): ResultTable; /** * The getResultTableCellIds method returns the Ids of every ResultCell used * across the ResultTable of the given query. * * 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. * @param queryId The Id of a query. * @returns An array of the Ids of every ResultCell used across the * ResultTable of the query. * @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. * * ```js * 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')); * // -> [] * ``` * @category Result * @since v4.1.0 */ getResultTableCellIds(queryId: Id): Ids; /** * The getResultRowCount method returns the count of the ResultRow objects in * the ResultTable of the given query. * * This has the same behavior as a Store's getRowCount method. For example, if * the query Id is invalid, the method returns zero. * @param queryId The Id of a query. * @returns The number of ResultRow objects in the result of the query. * @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. * * ```js * 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 * ``` * @category Result * @since v4.1.0 */ getResultRowCount(queryId: Id): number; /** * The getResultRowIds method returns the Ids of every ResultRow in the * ResultTable of the given query. * * 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. * @param queryId The Id of a query. * @returns An array of the Ids of every ResultRow in the result of the query. * @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. * * ```js * 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')); * // -> [] * ``` * @category Result * @since v2.0.0 */ getResultRowIds(queryId: Id): Ids; /** * 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. * * 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. * @param queryId The Id of a query. * @param cellId The Id of the ResultCell whose values are used for the * sorting, or `undefined` to by sort the ResultRow Id itself. * @param descending Whether the sorting should be in descending order. * @param offset The number of ResultRow Ids to skip for pagination purposes, * if any. * @param limit The maximum number of ResultRow Ids to return, or `undefined` * for all. * @returns An array of the sorted Ids of every ResultRow in the result of the * query. * @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. * * ```js * 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')); * // -> [] * ``` * @category Result * @since v2.0.0 */ getResultSortedRowIds( queryId: Id, cellId?: Id, descending?: boolean, offset?: number, limit?: number, ): Ids; /** * The getResultRow method returns an object containing the entire data of a * single ResultRow in the ResultTable of the given query. * * 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. * @param queryId The Id of a query. * @param rowId The Id of the ResultRow in the ResultTable. * @returns An object containing the entire data of the ResultRow in the * ResultTable of the query. * @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. * * ```js * 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')); * // -> {} * ``` * @category Result * @since v2.0.0 */ getResultRow(queryId: Id, rowId: Id): ResultRow; /** * The getResultCellIds method returns the Ids of every ResultCell in a given * ResultRow, in the ResultTable of the given query. * * 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. * @param queryId The Id of a query. * @param rowId The Id of the ResultRow in the ResultTable. * @returns An array of the Ids of every ResultCell in the ResultRow in the * result of the query. * @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. * * ```js * 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')); * // -> [] * ``` * @category Result * @since v2.0.0 */ getResultCellIds(queryId: Id, rowId: Id): Ids; /** * The getResultCell method returns the value of a single ResultCell in a * given ResultRow, in the ResultTable of the given query. * * 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`. * @param queryId The Id of a query. * @param rowId The Id of the ResultRow in the ResultTable. * @param cellId The Id of the ResultCell in the ResultRow. * @returns The value of the ResultCell, or `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. * * ```js * 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 * ``` * @category Result * @since v2.0.0 */ getResultCell(queryId: Id, rowId: Id, cellId: Id): ResultCellOrUndefined; /** * The hasResultTable method returns a boolean indicating whether a given * ResultTable exists. * @param queryId The Id of a possible query. * @returns Whether a ResultTable for that query Id exists. * @example * This example shows two simple ResultTable existence checks. * * ```js * 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 * ``` * @category Result * @since v2.0.0 */ hasResultTable(queryId: Id): boolean; /** * The hasResultRow method returns a boolean indicating whether a given * ResultRow exists. * @param queryId The Id of a possible query. * @param rowId The Id of a possible ResultRow. * @returns Whether a ResultRow for that Id exists. * @example * This example shows two simple ResultRow existence checks. * * ```js * 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 * ``` * @category Result * @since v2.0.0 */ hasResultRow(queryId: Id, rowId: Id): boolean; /** * The hasResultCell method returns a boolean indicating whether a given * ResultCell exists. * @param queryId The Id of a possible query. * @param rowId The Id of a possible ResultRow. * @param cellId The Id of a possible ResultCell. * @returns Whether a ResultCell for that Id exists. * @example * This example shows two simple ResultRow existence checks. * * ```js * 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 * ``` * @category Result * @since v2.0.0 */ hasResultCell(queryId: Id, rowId: Id, cellId: Id): boolean; /** * The forEachResultTable method takes a function that it will then call for * each ResultTable in the Queries object. * * 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. * @param tableCallback The function that should be called for every query's * ResultTable. * @example * This example iterates over each query's ResultTable in a Queries object. * * ```js * 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' * ``` * @category Iterator * @since v2.0.0 */ forEachResultTable(tableCallback: ResultTableCallback): void; /** * The forEachResultRow method takes a function that it will then call for * each ResultRow in the ResultTable of a query. * * 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. * @param queryId The Id of a query. * @param rowCallback The function that should be called for every ResultRow * of the query's ResultTable. * @example * This example iterates over each ResultRow in a query's ResultTable. * * ```js * 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' * ``` * @category Iterator * @since v2.0.0 */ forEachResultRow(queryId: Id, rowCallback: ResultRowCallback): void; /** * The forEachResultCell method takes a function that it will then call for * each ResultCell in the ResultRow of a query. * * 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. * @param queryId The Id of a query. * @param rowId The Id of a ResultRow in the query's ResultTable. * @param cellCallback The function that should be called for every ResultCell * of the query's ResultRow. * @example * This example iterates over each ResultCell in a query's ResultRow. * * ```js * 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' * ``` * @category Iterator * @since v2.0.0 */ forEachResultCell( queryId: Id, rowId: Id, cellCallback: ResultCellCallback, ): void; /** * The addQueryIdsListener method registers a listener function with the * Queries object that will be called whenever an Query definition is added or * removed. * * The provided listener is a QueryIdsListener function, and will be called * with a reference to the Queries object. * @param listener The function that will be called whenever a Query * definition is added or removed. * @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. * * ```js * 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); * ``` * @category Listener * @since v4.1.0 */ addQueryIdsListener(listener: QueryIdsListener): Id; /** * The addResultTableListener method registers a listener function with the * Queries object that will be called whenever data in a ResultTable changes. * * 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). * @param queryId The Id of the query to listen to, or `null` as a wildcard. * @param listener The function that will be called whenever data in the * matching ResultTable changes. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example registers a listener that responds to any changes to a * specific ResultTable. * * ```js * 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); * ``` * @example * This example registers a listener that responds to any changes to any * ResultTable. * * ```js * 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); * ``` * @category Listener * @since v2.0.0 */ addResultTableListener(queryId: IdOrNull, listener: ResultTableListener): Id; /** * 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. * * 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). * @param queryId The Id of the query to listen to, or `null` as a wildcard. * @param listener The function that will be called whenever the Cell * Ids that appear anywhere in the ResultTable change. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example registers a listener that responds to any change to the * Cell Ids of a specific ResultTable. * * ```js * 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); * ``` * @example * This example registers a listener that responds to any change to the * ResultCell Ids of any ResultTable. * * ```js * 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); * ``` * @category Listener * @since v2.0.0 */ addResultTableCellIdsListener( queryId: IdOrNull, listener: ResultTableCellIdsListener, ): Id; /** * 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. * * 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). * @param queryId The Id of the query to listen to, or `null` as a wildcard. * @param listener The function that will be called whenever the number of * ResultRow objects in the ResultTable change. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example registers a listener that responds to a change in the number * of ResultRow objects in a specific ResultTable. * * ```js * 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); * ``` * @example * This example registers a listener that responds to a change in the number * of ResultRow objects any ResultTable. * * ```js * 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); * ``` * @category Listener * @since v4.1.0 */ addResultRowCountListener( queryId: IdOrNull, listener: ResultRowCountListener, ): Id; /** * The addResultRowIdsListener method registers a listener function with the * Queries object that will be called whenever the ResultRow Ids in a * ResultTable change. * * 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). * @param queryId The Id of the query to listen to, or `null` as a wildcard. * @param listener The function that will be called whenever the ResultRow Ids * in the ResultTable change. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example registers a listener that responds to any change to the * ResultRow Ids of a specific ResultTable. * * ```js * 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); * ``` * @example * This example registers a listener that responds to any change to the * ResultRow Ids of any ResultTable. * * ```js * 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); * ``` * @category Listener * @since v2.0.0 */ addResultRowIdsListener( queryId: IdOrNull, listener: ResultRowIdsListener, ): Id; /** * 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. * * 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. * @param queryId The Id of the query to listen to. * @param cellId The Id of the ResultCell whose values are used for the * sorting, or `undefined` to by sort the ResultRow Id itself. * @param descending Whether the sorting should be in descending order. * @param offset The number of ResultRow Ids to skip for pagination purposes, * if any. * @param limit The maximum number of ResultRow Ids to return, or `undefined` * for all. * @param listener The function that will be called whenever the sorted * ResultRow Ids in the ResultTable change. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example registers a listener that responds to any change to the sorted * ResultRow Ids of a specific ResultTable. * * ```js * 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); * ``` * @example * 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. * * ```js * 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); * ``` * @category Listener * @since v2.0.0 */ addResultSortedRowIdsListener( queryId: Id, cellId: Id | undefined, descending: boolean, offset: number, limit: number | undefined, listener: ResultSortedRowIdsListener, ): Id; /** * The addResultRowListener method registers a listener function with the * Queries object that will be called whenever data in a ResultRow changes. * * 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. * @param queryId The Id of the query to listen to, or `null` as a wildcard. * @param rowId The Id of the ResultRow to listen to, or `null` as a wildcard. * @param listener The function that will be called whenever data in the * matching ResultRow changes. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example registers a listener that responds to any changes to a * specific ResultRow. * * ```js * 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); * ``` * @example * This example registers a listener that responds to any changes to any * ResultRow. * * ```js * 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); * ``` * @category Listener * @since v2.0.0 */ addResultRowListener( queryId: IdOrNull, rowId: IdOrNull, listener: ResultRowListener, ): Id; /** * The addResultCellIdsListener method registers a listener function with the * Queries object that will be called whenever the ResultCell Ids in a * ResultRow change. * * 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. * @param queryId The Id of the query to listen to, or `null` as a wildcard. * @param rowId The Id of the ResultRow to listen to, or `null` as a wildcard. * @param listener The function that will be called whenever the ResultCell * Ids in the ResultRow change. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example registers a listener that responds to any change to the * ResultCell Ids of a specific ResultRow. * * ```js * 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); * ``` * @example * This example registers a listener that responds to any change to the * ResultCell Ids of any ResultRow. * * ```js * 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); * ``` * @category Listener * @since v2.0.0 */ addResultCellIdsListener( queryId: IdOrNull, rowId: IdOrNull, listener: ResultCellIdsListener, ): Id; /** * The addResultCellListener method registers a listener function with the * Queries object that will be called whenever data in a ResultCell changes. * * 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. * @param queryId The Id of the query to listen to, or `null` as a wildcard. * @param rowId The Id of the ResultRow to listen to, or `null` as a wildcard. * @param cellId The Id of the ResultCell to listen to, or `null` as a * wildcard. * @param listener The function that will be called whenever data in the * matching ResultCell changes. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example registers a listener that responds to any changes to a * specific ResultCell. * * ```js * 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); * ``` * @example * This example registers a listener that responds to any changes to any * ResultCell. * * ```js * 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); * ``` * @category Listener * @since v2.0.0 */ addResultCellListener( queryId: IdOrNull, rowId: IdOrNull, cellId: IdOrNull, listener: ResultCellListener, ): Id; /** * The delListener method removes a listener that was previously added to the * Queries object. * * Use the Id returned by the addMetricListener method. Note that the Queries * object may re-use this Id for future listeners added to it. * @param listenerId The Id of the listener to remove. * @returns A reference to the Queries object. * @example * This example creates a Store, a Queries object, registers a listener, and * then removes it. * * ```js * 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. * ``` * @category Listener * @since v2.0.0 */ delListener(listenerId: Id): Queries; /** * The destroy method should be called when this Queries object is no longer * used. * * 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. * * ```js * 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 * ``` * @category Lifecycle * @since v2.0.0 */ destroy(): void; /** * The getListenerStats method provides a set of statistics about the * listeners registered with the Queries object, and is used for debugging * purposes. * * The method is intended to be used during development to ensure your * application is not leaking listener registrations, for example. * @returns A QueriesListenerStats object containing Queries listener * statistics. * @example * This example gets the listener statistics of a Queries object. * * ```js * 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 * ``` * @category Development * @since v2.0.0 */ getListenerStats(): QueriesListenerStats; // } /** * The createQueries function creates a Queries object, and is the main entry * point into the queries module. * * 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. * @param store The Store for which to register query definitions. * @returns A reference to the new Queries object. * @example * This example creates a Queries object. * * ```js * import {createQueries, createStore} from 'tinybase'; * * const store = createStore(); * const queries = createQueries(store); * console.log(queries.getQueryIds()); * // -> [] * ``` * @example * This example creates a Queries object, and calls the method a second time * for the same Store to return the same object. * * ```js * import {createQueries, createStore} from 'tinybase'; * * const store = createStore(); * const queries1 = createQueries(store); * const queries2 = createQueries(store); * console.log(queries1 === queries2); * // -> true * ``` * @category Creation * @since v2.0.0 */ export function createQueries(store: Store): Queries;