UNPKG

tinybase

Version:

A reactive data store and sync engine.

1,321 lines (1,294 loc) 46.2 kB
/** * The relationships module of the TinyBase project provides the ability to * create and track relationships between the data in Store objects. * * The main entry point to this module is the createRelationships function, * which returns a new Relationships object. From there, you can create new * Relationship definitions, access the associations within those Relationships * directly, and register listeners for when they change. * @packageDocumentation * @module relationships * @since v1.0.0 */ import type {Id, IdOrNull, Ids} from '../common/index.d.cts'; import type {GetCell, RowCallback, Store} from '../store/index.d.cts'; /** * The Relationship type represents the concept of a map that connects one Row * object to another, often in another Table. * * The Relationship has a one-to-many nature. One local Row Id is linked to one * remote Row Id (in the remote Table), as described by the * setRelationshipDefinition method - and one remote Row Id may map back to * multiple local Row Ids (in the local Table). * * A Relationship where the local Table is the same as the remote Table can be * used to model a 'linked list', where Row A references Row B, Row B references * Row C, and so on. * * Note that the Relationship type is not actually used in the API, and you * instead enumerate and access its structure with the getRemoteRowId method, * the getLocalRowIds method, and the getLinkedRowIds method. * @category Concept * @since v1.0.0 */ export type Relationship = { remoteRowId: {[localRowId: Id]: Id}; localRowIds: {[remoteRowId: Id]: Ids}; linkedRowIds: {[firstRowId: Id]: Ids}; }; /** * The RelationshipCallback type describes a function that takes a * Relationship's Id and a callback to loop over each local Row within it. * * A RelationshipCallback is provided when using the forEachRelationship method, * so that you can do something based on every Relationship in the Relationships * object. See that method for specific examples. * @param relationshipId The Id of the Relationship that the callback can * operate on. * @param forEachRow A function that will let you iterate over the local Row * objects in this Relationship. * @category Callback * @since v1.0.0 */ export type RelationshipCallback = ( relationshipId: Id, forEachRow: (rowCallback: RowCallback) => void, ) => void; /** * The RelationshipIdsListener type describes a function that is used to listen * to Relationship definitions being added or removed. * * A RelationshipIdsListener is provided when using the * addRelationshipIdsListener method. See that method for specific examples. * * When called, a RelationshipIdsListener is given a reference to the * Relationships object. * @param relationships A reference to the Relationships object that changed. * @category Listener * @since v1.0.0 */ export type RelationshipIdsListener = (relationships: Relationships) => void; /** * The RemoteRowIdListener type describes a function that is used to listen to * changes to the remote Row Id end of a Relationship. * * A RemoteRowIdListener is provided when using the addRemoteRowIdListener * method. See that method for specific examples. * * When called, a RemoteRowIdListener is given a reference to the Relationships * object, the Id of the Relationship that changed, and the Id of the local Row * whose remote Row Id changed. * @param relationships A reference to the Relationships object that changed. * @param relationshipId The Id of the Relationship that changed. * @param localRowId The Id of the local Row whose remote Row Id changed. * @category Listener * @since v1.0.0 */ export type RemoteRowIdListener = ( relationships: Relationships, relationshipId: Id, localRowId: Id, ) => void; /** * The LocalRowIdsListener type describes a function that is used to listen to * changes to the local Row Id ends of a Relationship. * * A LocalRowIdsListener is provided when using the addLocalRowIdsListener * method. See that method for specific examples. * * When called, a LocalRowIdsListener is given a reference to the Relationships * object, the Id of the Relationship that changed, and the Id of the remote Row * whose local Row Ids changed. * @param relationships A reference to the Relationships object that changed. * @param relationshipId The Id of the Relationship that changed. * @param remoteRowId The Id of the remote Row whose local Row Ids changed. * @category Listener * @since v1.0.0 */ export type LocalRowIdsListener = ( relationships: Relationships, relationshipId: Id, remoteRowId: Id, ) => void; /** * The LinkedRowIdsListener type describes a function that is used to listen to * changes to the local Row Id ends of a Relationship. * * A LinkedRowIdsListener is provided when using the addLinkedRowIdsListener * method. See that method for specific examples. * * When called, a LinkedRowIdsListener is given a reference to the Relationships * object, the Id of the Relationship that changed, and the Id of the first Row * of the the linked list whose members changed. * @param relationships A reference to the Relationships object that changed. * @param relationshipId The Id of the Relationship that changed. * @param firstRowId The Id of the first Row of the the linked list whose * members changed. * @category Listener * @since v1.0.0 */ export type LinkedRowIdsListener = ( relationships: Relationships, relationshipId: Id, firstRowId: Id, ) => void; /** * The RelationshipsListenerStats type describes the number of listeners * registered with the Relationships object, and can be used for debugging * purposes. * * A RelationshipsListenerStats object is returned from the getListenerStats * method. * @category Development * @since v1.0.0 */ export type RelationshipsListenerStats = { /** * The number of RemoteRowIdListener functions registered with the * Relationships object. * @category Stat * @since v1.0.0 */ remoteRowId: number; /** * The number of LocalRowIdsListener functions registered with the * Relationships object. * @category Stat * @since v1.0.0 */ localRowIds: number; /** * The number of LinkedRowId functions registered with the Relationships * object. * @category Stat * @since v1.0.0 */ linkedRowIds: number; }; /** * A Relationships object lets you associate a Row in a one Table with the Id of * a Row in another Table. * * This is useful for creating parent-child relationships between the data in * different Table objects, but it can also be used to model a linked list of * Row objects in the same Table. * * Create a Relationships object easily with the createRelationships function. * From there, you can add new Relationship definitions (with the * setRelationshipDefinition method), query their contents (with the * getRemoteRowId method, the getLocalRowIds method, and the getLinkedRowIds * method), and add listeners for when they change (with the * addRemoteRowIdListener method, the addLocalRowIdsListener method, and the * addLinkedRowIdsListener method). * * This module defaults to creating relationships between Row objects by using * one of their Cell values. However, far more complex relationships can be * configured with a custom function. * @example * This example shows a very simple lifecycle of a Relationships object: from * creation, to adding definitions (both local/remote table and linked list), * getting their contents, and then registering and removing listeners for them. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog', next: 'felix'}, * felix: {species: 'cat', next: 'cujo'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * * // A local/remote table relationship: * relationships.setRelationshipDefinition( * 'petSpecies', // relationshipId * 'pets', // localTableId to link from * 'species', // remoteTableId to link to * 'species', // cellId containing remote key * ); * console.log(relationships.getRemoteRowId('petSpecies', 'fido')); * // -> 'dog' * console.log(relationships.getLocalRowIds('petSpecies', 'dog')); * // -> ['fido', 'cujo'] * * // A linked list relationship: * relationships.setRelationshipDefinition( * 'petSequence', // relationshipId * 'pets', // localTableId to link from * 'pets', // the same remoteTableId to link within * 'next', // cellId containing link key * ); * console.log(relationships.getLinkedRowIds('petSequence', 'fido')); * // -> ['fido', 'felix', 'cujo'] * * const listenerId1 = relationships.addLocalRowIdsListener( * 'petSpecies', * 'dog', * () => { * console.log('petSpecies relationship (to dog) changed'); * console.log(relationships.getLocalRowIds('petSpecies', 'dog')); * }, * ); * const listenerId2 = relationships.addLinkedRowIdsListener( * 'petSequence', * 'fido', * () => { * console.log('petSequence linked list (from fido) changed'); * console.log(relationships.getLinkedRowIds('petSequence', 'fido')); * }, * ); * * store.setRow('pets', 'toto', {species: 'dog'}); * // -> 'petSpecies relationship (to dog) changed' * // -> ['fido', 'cujo', 'toto'] * * store.setCell('pets', 'cujo', 'next', 'toto'); * // -> 'petSequence linked list (from fido) changed' * // -> ['fido', 'felix', 'cujo', 'toto'] * * relationships.delListener(listenerId1); * relationships.delListener(listenerId2); * relationships.destroy(); * ``` * @see Using Relationships guide * @see Drawing demo * @category Relationships * @since v1.0.0 */ export interface Relationships { // /** * The setRelationshipDefinition method lets you set the definition of a * Relationship. * * Every Relationship definition is identified by a unique Id, and if you * re-use an existing Id with this method, the previous definition is * overwritten. * * An Relationship is based on connections between Row objects, often in two * different Table objects. Therefore the definition requires the * `localTableId` parameter to specify the 'local' Table to create the * Relationship from, and the `remoteTableId` parameter to specify the * 'remote' Table to create Relationship to. * * A linked list Relationship is one that has the same Table specified as both * local Table Id and remote Table Id, allowing you to create a sequence of * Row objects within that one Table. * * A local Row is related to a remote Row by specifying which of its (local) * Cell values contains the (remote) Row Id, using the `getRemoteRowId` * parameter. Alternatively, a custom function can be provided that produces * your own remote Row Id from the local Row as a whole. * @param relationshipId The Id of the Relationship to define. * @param localTableId The Id of the local Table for the Relationship. * @param remoteTableId The Id of the remote Table for the Relationship (or * the same as the `localTableId` in the case of a linked list). * @param getRemoteRowId Either the Id of the Cell containing, or a function * that produces, the Id that is used to indicate which Row in the remote * Table a local Row is related to. * @returns A reference to the Relationships object. * @example * This example creates a Store, creates a Relationships object, and defines * a simple Relationship based on the values in the `species` Cell of the * `pets` Table that relates a Row to another in the `species` Table. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog'}, * felix: {species: 'cat'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSpecies', // relationshipId * 'pets', // localTableId to link from * 'species', // remoteTableId to link to * 'species', // cellId containing remote key * ); * * console.log(relationships.getRemoteRowId('petSpecies', 'fido')); * // -> 'dog' * console.log(relationships.getLocalRowIds('petSpecies', 'dog')); * // -> ['fido', 'cujo'] * ``` * @example * This example creates a Store, creates a Relationships object, and defines * a linked list Relationship based on the values in the `next` Cell of the * `pets` Table that relates a Row to another in the same Table. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore().setTable('pets', { * fido: {species: 'dog', next: 'felix'}, * felix: {species: 'cat', next: 'cujo'}, * cujo: {species: 'dog'}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSequence', // relationshipId * 'pets', // localTableId to link from * 'pets', // the same remoteTableId to link within * 'next', // cellId containing link key * ); * * console.log(relationships.getLinkedRowIds('petSequence', 'fido')); * // -> ['fido', 'felix', 'cujo'] * ``` * @category Configuration * @since v1.0.0 */ setRelationshipDefinition( relationshipId: Id, localTableId: Id, remoteTableId: Id, getRemoteRowId: Id | ((getCell: GetCell, localRowId: Id) => Id), ): Relationships; /** * The delRelationshipDefinition method removes an existing Relationship * definition. * @param relationshipId The Id of the Relationship to remove. * @returns A reference to the Relationships object. * @example * This example creates a Store, creates a Relationships object, defines a * simple Relationship, and then removes it. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog'}, * felix: {species: 'cat'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * console.log(relationships.getRelationshipIds()); * // -> ['petSpecies'] * * relationships.delRelationshipDefinition('petSpecies'); * console.log(relationships.getRelationshipIds()); * // -> [] * ``` * @category Configuration * @since v1.0.0 */ delRelationshipDefinition(relationshipId: Id): Relationships; /** * The getStore method returns a reference to the underlying Store that is * backing this Relationships object. * @returns A reference to the Store. * @example * This example creates a Relationships object against a newly-created Store * and then gets its reference in order to update its data. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const relationships = createRelationships(createStore()); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * relationships.getStore().setCell('pets', 'fido', 'species', 'dog'); * console.log(relationships.getRemoteRowId('petSpecies', 'fido')); * // -> 'dog' * ``` * @category Getter * @since v1.0.0 */ getStore(): Store; /** * The getRelationshipIds method returns an array of the Relationship Ids * registered with this Relationships object. * @returns An array of Ids. * @example * This example creates a Relationships object with two definitions, and then * gets the Ids of the definitions. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const relationships = createRelationships(createStore()) * .setRelationshipDefinition('petSpecies', 'pets', 'species', 'species') * .setRelationshipDefinition('petSequence', 'pets', 'pets', 'next'); * console.log(relationships.getRelationshipIds()); * // -> ['petSpecies', 'petSequence'] * ``` * @category Getter * @since v1.0.0 */ getRelationshipIds(): Ids; /** * The forEachRelationship method takes a function that it will then call for * each Relationship in a specified Relationships object. * * This method is useful for iterating over the structure of the Relationships * object in a functional style. The `relationshipCallback` parameter is a * RelationshipCallback function that will be called with the Id of each * Relationship, and with a function that can then be used to iterate over * each local Row involved in the Relationship. * @param relationshipCallback The function that should be called for every * Relationship. * @example * This example iterates over each Relationship in a Relationships object, and * lists each Row Id within them. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore().setTable('pets', { * fido: {species: 'dog', next: 'felix'}, * felix: {species: 'cat', next: 'cujo'}, * cujo: {species: 'dog'}, * }); * const relationships = createRelationships(store) * .setRelationshipDefinition('petSpecies', 'pets', 'species', 'species') * .setRelationshipDefinition('petSequence', 'pets', 'pets', 'next'); * * relationships.forEachRelationship((relationshipId, forEachRow) => { * console.log(relationshipId); * forEachRow((rowId) => console.log(`- ${rowId}`)); * }); * // -> 'petSpecies' * // -> '- fido' * // -> '- felix' * // -> '- cujo' * // -> 'petSequence' * // -> '- fido' * // -> '- felix' * // -> '- cujo' * ``` * @category Iterator * @since v1.0.0 */ forEachRelationship(relationshipCallback: RelationshipCallback): void; /** * The hasRelationship method returns a boolean indicating whether a given * Relationship exists in the Relationships object. * @param relationshipId The Id of a possible Relationship in the * Relationships object. * @returns Whether a Relationship with that Id exists. * @example * This example shows two simple Relationship existence checks. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const relationships = createRelationships( * createStore(), * ).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species'); * console.log(relationships.hasRelationship('petSpecies')); * // -> true * console.log(relationships.hasRelationship('petColor')); * // -> false * ``` * @category Getter * @since v1.0.0 */ hasRelationship(relationshipId: Id): boolean; /** * The getLocalTableId method returns the Id of the underlying local Table * that is used in the Relationship. * * If the Relationship Id is invalid, the method returns `undefined`. * @param relationshipId The Id of a Relationship. * @returns The Id of the local Table backing the Relationship, or * `undefined`. * @example * This example creates a Relationship object, a single Relationship * definition, and then queries it (and a non-existent definition) to get the * underlying local Table Id. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const relationships = createRelationships(createStore()); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * * console.log(relationships.getLocalTableId('petSpecies')); * // -> 'pets' * console.log(relationships.getLocalTableId('petColor')); * // -> undefined * ``` * @category Getter * @since v1.0.0 */ getLocalTableId(relationshipId: Id): Id | undefined; /** * The getRemoteTableId method returns the Id of the underlying remote Table * that is used in the Relationship. * * If the Relationship Id is invalid, the method returns `undefined`. * @param relationshipId The Id of a Relationship. * @returns The Id of the remote Table backing the Relationship, or * `undefined`. * @example * This example creates a Relationship object, a single Relationship * definition, and then queries it (and a non-existent definition) to get the * underlying remote Table Id. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const relationships = createRelationships(createStore()); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * * console.log(relationships.getRemoteTableId('petSpecies')); * // -> 'species' * console.log(relationships.getRemoteTableId('petColor')); * // -> undefined * ``` * @category Getter * @since v1.0.0 */ getRemoteTableId(relationshipId: Id): Id | undefined; /** * The getRemoteRowId method gets the remote Row Id for a given local Row in a * Relationship. * * If the identified Relationship or Row does not exist (or if the definition * references a Table that does not exist) then `undefined` is returned. * @param relationshipId The Id of the Relationship. * @param localRowId The Id of the local Row in the Relationship. * @returns The remote Row Id in the Relationship, or `undefined`. * @example * This example creates a Store, creates a Relationships object, and defines * a simple Relationship. It then uses getRemoteRowId to see the remote Row Id * in the Relationship (and also the remote Row Ids for a local Row that does * not exist, and for a Relationship that has not been defined). * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog'}, * felix: {species: 'cat'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * * console.log(relationships.getRemoteRowId('petSpecies', 'fido')); * // -> 'dog' * console.log(relationships.getRemoteRowId('petSpecies', 'toto')); * // -> undefined * console.log(relationships.getRemoteRowId('petColor', 'fido')); * // -> undefined * ``` * @category Getter * @since v1.0.0 */ getRemoteRowId(relationshipId: Id, localRowId: Id): Id | undefined; /** * The getLocalRowIds method gets the local Row Ids for a given remote Row in * a Relationship. * * If the identified Relationship or Row does not exist (or if the definition * references a Table that does not exist) then an empty array is returned. * @param relationshipId The Id of the Relationship. * @param remoteRowId The Id of the remote Row in the Relationship. * @returns The local Row Ids in the Relationship, or an empty array. * @example * This example creates a Store, creates a Relationships object, and defines * a simple Relationship. It then uses getLocalRowIds to see the local Row Ids * in the Relationship (and also the local Row Ids for a remote Row that does * not exist, and for a Relationship that has not been defined). * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog'}, * felix: {species: 'cat'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * * console.log(relationships.getLocalRowIds('petSpecies', 'dog')); * // -> ['fido', 'cujo'] * console.log(relationships.getLocalRowIds('petSpecies', 'worm')); * // -> [] * console.log(relationships.getLocalRowIds('petColor', 'brown')); * // -> [] * ``` * @category Getter * @since v1.0.0 */ getLocalRowIds(relationshipId: Id, remoteRowId: Id): Ids; /** * The getLinkedRowIds method gets the linked Row Ids for a given Row in a * linked list Relationship. * * A linked list Relationship is one that has the same Table specified as both * local Table Id and remote Table Id, allowing you to create a sequence of * Row objects within that one Table. * * If the identified Relationship or Row does not exist (or if the definition * references a Table that does not exist) then an array containing just the * first Row Id is returned. * @param relationshipId The Id of the Relationship. * @param firstRowId The Id of the first Row in the linked list Relationship. * @returns The linked Row Ids in the Relationship. * @example * This example creates a Store, creates a Relationships object, and defines * a simple linked list Relationship. It then uses getLinkedRowIds to see the * linked Row Ids in the Relationship (and also the linked Row Ids for a Row * that does not exist, and for a Relationship that has not been defined). * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore().setTable('pets', { * fido: {species: 'dog', next: 'felix'}, * felix: {species: 'cat', next: 'cujo'}, * cujo: {species: 'dog'}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSequence', * 'pets', * 'pets', * 'next', * ); * * console.log(relationships.getLinkedRowIds('petSequence', 'fido')); * // -> ['fido', 'felix', 'cujo'] * console.log(relationships.getLinkedRowIds('petSequence', 'felix')); * // -> ['felix', 'cujo'] * console.log(relationships.getLinkedRowIds('petSequence', 'toto')); * // -> ['toto'] * console.log(relationships.getLinkedRowIds('petFriendships', 'fido')); * // -> ['fido'] * ``` * @category Getter * @since v1.0.0 */ getLinkedRowIds(relationshipId: Id, firstRowId: Id): Ids; /** * The addRelationshipIdsListener method registers a listener function with * the Relationships object that will be called whenever a Relationship * definition is added or removed. * * The provided listener is a RelationshipIdsListener function, and will be * called with a reference to the Relationships object. * @param listener The function that will be called whenever a Relationship * definition is added or removed. * @example * This example creates a Store, a Relationships object, and then registers a * listener that responds to the addition and the removal of a Relationship * definition. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog'}, * felix: {species: 'cat'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * wolf: {price: 10}, * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * const listenerId = relationships.addRelationshipIdsListener( * (relationships) => { * console.log(relationships.getRelationshipIds()); * }, * ); * * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * // -> ['petSpecies'] * relationships.delRelationshipDefinition('petSpecies'); * // -> [] * * relationships.delListener(listenerId); * ``` * @category Listener * @since v4.1.0 */ addRelationshipIdsListener(listener: RelationshipIdsListener): Id; /** * The addRemoteRowIdListener method registers a listener function with the * Relationships object that will be called whenever a remote Row Id in a * Relationship changes. * * You can either listen to a single local Row (by specifying the Relationship * Id and local Row Id as the method's first two parameters), or changes to * any local Row (by providing a `null` wildcards). * * Both, either, or neither of the `relationshipId` and `localRowId` * parameters can be wildcarded with `null`. You can listen to a specific * local Row in a specific Relationship, any local Row in a specific * Relationship, a specific local Row in any Relationship, or any local Row in * any Relationship. * * The provided listener is a RemoteRowIdListener function, and will be called * with a reference to the Relationships object, the Id of the Relationship, * and the Id of the local Row that had its remote Row change. * @param relationshipId The Id of the Relationship to listen to, or `null` as * a wildcard. * @param localRowId The Id of the local Row to listen to, or `null` as a * wildcard. * @param listener The function that will be called whenever the remote Row Id * changes. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example creates a Store, a Relationships object, and then registers a * listener that responds to any changes to a specific local Row's remote Row. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog'}, * felix: {species: 'cat'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * wolf: {price: 10}, * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * * const listenerId = relationships.addRemoteRowIdListener( * 'petSpecies', * 'cujo', * (relationships) => { * console.log('petSpecies relationship (from cujo) changed'); * console.log(relationships.getRemoteRowId('petSpecies', 'cujo')); * }, * ); * * store.setCell('pets', 'cujo', 'species', 'wolf'); * // -> 'petSpecies relationship (from cujo) changed' * // -> 'wolf' * * relationships.delListener(listenerId); * ``` * @example * This example creates a Store, a Relationships object, and then registers a * listener that responds to any changes to any local Row's remote Row. It * also illustrates how you can use the getStore method and the getRemoteRowId * method to resolve the remote Row as a whole. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog', color: 'brown'}, * felix: {species: 'cat', color: 'black'}, * cujo: {species: 'dog', color: 'brown'}, * }) * .setTable('species', { * wolf: {price: 10}, * dog: {price: 5}, * cat: {price: 4}, * }) * .setTable('color', { * brown: {discount: 0.1}, * black: {discount: 0}, * grey: {discount: 0.2}, * }); * * const relationships = createRelationships(store) * .setRelationshipDefinition('petSpecies', 'pets', 'species', 'species') * .setRelationshipDefinition('petColor', 'pets', 'color', 'color'); * * const listenerId = relationships.addRemoteRowIdListener( * null, * null, * (relationships, relationshipId, localRowId) => { * console.log( * `${relationshipId} relationship (from ${localRowId}) changed`, * ); * console.log(relationships.getRemoteRowId(relationshipId, localRowId)); * console.log( * relationships * .getStore() * .getRow( * relationships.getRemoteTableId(relationshipId), * relationships.getRemoteRowId(relationshipId, localRowId), * ), * ); * }, * ); * * store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'}); * // -> 'petSpecies relationship (from cujo) changed' * // -> 'wolf' * // -> {price: 10} * // -> 'petColor relationship (from cujo) changed' * // -> 'grey' * // -> {discount: 0.2} * * relationships.delListener(listenerId); * ``` * @category Listener * @since v1.0.0 */ addRemoteRowIdListener( relationshipId: IdOrNull, localRowId: IdOrNull, listener: RemoteRowIdListener, ): Id; /** * The addLocalRowIdsListener method registers a listener function with the * Relationships object that will be called whenever the local Row Ids in * a Relationship change. * * You can either listen to a single local Row (by specifying the Relationship * Id and local Row Id as the method's first two parameters), or changes to * any local Row (by providing a `null` wildcards). * * Both, either, or neither of the `relationshipId` and `remoteRowId` * parameters can be wildcarded with `null`. You can listen to a specific * remote Row in a specific Relationship, any remote Row in a specific * Relationship, a specific remote Row in any Relationship, or any remote Row * in any Relationship. * * The provided listener is a LocalRowIdsListener function, and will be called * with a reference to the Relationships object, the Id of the Relationship, * and the Id of the remote Row that had its local Row objects change. * @param relationshipId The Id of the Relationship to listen to, or `null` as * a wildcard. * @param remoteRowId The Id of the remote Row to listen to, or `null` as a * wildcard. * @param listener The function that will be called whenever the local Row Ids * change. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example creates a Store, a Relationships object, and then registers a * listener that responds to any changes to a specific remote Row's local Row * objects. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog'}, * felix: {species: 'cat'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * wolf: {price: 10}, * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * * const listenerId = relationships.addLocalRowIdsListener( * 'petSpecies', * 'dog', * (relationships) => { * console.log('petSpecies relationship (to dog) changed'); * console.log(relationships.getLocalRowIds('petSpecies', 'dog')); * }, * ); * * store.setRow('pets', 'toto', {species: 'dog'}); * // -> 'petSpecies relationship (to dog) changed' * // -> ['fido', 'cujo', 'toto'] * * relationships.delListener(listenerId); * ``` * @example * This example creates a Store, a Relationships object, and then registers a * listener that responds to any changes to any remote Row's local Row * objects. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog', color: 'brown'}, * felix: {species: 'cat', color: 'black'}, * cujo: {species: 'dog', color: 'brown'}, * toto: {species: 'dog', color: 'grey'}, * }) * .setTable('species', { * wolf: {price: 10}, * dog: {price: 5}, * cat: {price: 4}, * }) * .setTable('color', { * brown: {discount: 0.1}, * black: {discount: 0}, * grey: {discount: 0.2}, * }); * * const relationships = createRelationships(store) * .setRelationshipDefinition('petSpecies', 'pets', 'species', 'species') * .setRelationshipDefinition('petColor', 'pets', 'color', 'color'); * * const listenerId = relationships.addLocalRowIdsListener( * null, * null, * (relationships, relationshipId, remoteRowId) => { * console.log( * `${relationshipId} relationship (to ${remoteRowId}) changed`, * ); * console.log(relationships.getLocalRowIds(relationshipId, remoteRowId)); * }, * ); * * store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'}); * // -> 'petSpecies relationship (to dog) changed' * // -> ['fido', 'toto'] * // -> 'petSpecies relationship (to wolf) changed' * // -> ['cujo'] * // -> 'petColor relationship (to brown) changed' * // -> ['fido'] * // -> 'petColor relationship (to grey) changed' * // -> ['toto', 'cujo'] * * relationships.delListener(listenerId); * ``` * @category Listener * @since v1.0.0 */ addLocalRowIdsListener( relationshipId: IdOrNull, remoteRowId: IdOrNull, listener: LocalRowIdsListener, ): Id; /** * The addLinkedRowIdsListener method registers a listener function with the * Relationships object that will be called whenever the linked Row Ids in a * linked list Relationship change. * * A linked list Relationship is one that has the same Table specified as both * local Table Id and remote Table Id, allowing you to create a sequence of * Row objects within that one Table. * * You listen to changes to a linked list starting from a single first Row by * specifying the Relationship Id and local Row Id as the method's first two * parameters. * * Unlike other listener registration methods, you cannot provide `null` * wildcards for the first two parameters of the addLinkedRowIdsListener * method. This prevents the prohibitive expense of tracking all the possible * linked lists (and partial linked lists within them) in a Store. * * The provided listener is a LinkedRowIdsListener function, and will be * called with a reference to the Relationships object, the Id of the * Relationship, and the Id of the first Row that had its linked list change. * @param relationshipId The Id of the Relationship to listen to. * @param firstRowId The Id of the first Row of the linked list to listen to. * @param listener The function that will be called whenever the linked Row * Ids change. * @returns A unique Id for the listener that can later be used to remove it. * @example * This example creates a Store, a Relationships object, and then registers a * listener that responds to any changes to a specific first Row's linked Row * objects. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore().setTable('pets', { * fido: {species: 'dog', next: 'felix'}, * felix: {species: 'cat', next: 'cujo'}, * cujo: {species: 'dog'}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSequence', * 'pets', * 'pets', * 'next', * ); * * const listenerId = relationships.addLinkedRowIdsListener( * 'petSequence', * 'fido', * (relationships) => { * console.log('petSequence linked list (from fido) changed'); * console.log(relationships.getLinkedRowIds('petSequence', 'fido')); * }, * ); * * store.setRow('pets', 'toto', {species: 'dog'}); * store.setCell('pets', 'cujo', 'next', 'toto'); * // -> 'petSequence linked list (from fido) changed' * // -> ['fido', 'felix', 'cujo', 'toto'] * * relationships.delListener(listenerId); * ``` * @category Listener * @since v1.0.0 */ addLinkedRowIdsListener( relationshipId: Id, firstRowId: Id, listener: LinkedRowIdsListener, ): Id; /** * The delListener method removes a listener that was previously added to the * Relationships object. * * Use the Id returned by whichever method was used to add the listener. Note * that the Relationships object may re-use this Id for future listeners added * to it. * @param listenerId The Id of the listener to remove. * @returns A reference to the Relationships object. * @example * This example creates a Store, a Relationships object, registers a listener, * and then removes it. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog'}, * felix: {species: 'cat'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * wolf: {price: 10}, * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * * const listenerId = relationships.addLocalRowIdsListener( * 'petSpecies', * 'dog', * () => { * console.log('petSpecies relationship (to dog) changed'); * }, * ); * * store.setRow('pets', 'toto', {species: 'dog'}); * // -> 'petSpecies relationship (to dog) changed' * * relationships.delListener(listenerId); * * store.setRow('pets', 'toto', {species: 'dog'}); * // -> undefined * // The listener is not called. * ``` * @category Listener * @since v1.0.0 */ delListener(listenerId: Id): Relationships; /** * The destroy method should be called when this Relationships 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 Relationships object with a * definition (that registers a RowListener with the underlying Store), * and then destroys it again, removing the listener. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore() * .setTable('pets', { * fido: {species: 'dog'}, * felix: {species: 'cat'}, * cujo: {species: 'dog'}, * }) * .setTable('species', { * wolf: {price: 10}, * dog: {price: 5}, * cat: {price: 4}, * }); * * const relationships = createRelationships(store); * relationships.setRelationshipDefinition( * 'petSpecies', * 'pets', * 'species', * 'species', * ); * console.log(store.getListenerStats().row); * // -> 1 * * relationships.destroy(); * * console.log(store.getListenerStats().row); * // -> 0 * ``` * @category Lifecycle * @since v1.0.0 */ destroy(): void; /** * The getListenerStats method provides a set of statistics about the * listeners registered with the Relationships object, and is used for * debugging purposes. * * The RelationshipsListenerStats object contains a breakdown of the different * types of listener. * * The method is intended to be used during development to ensure your * application is not leaking listener registrations, for example. * @returns A RelationshipsListenerStats object containing Relationships * listener statistics. * @example * This example gets the listener statistics of a Relationships object. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore(); * const relationships = createRelationships(store); * relationships.addRemoteRowIdListener(null, null, () => { * console.log('Remote Row Id changed'); * }); * relationships.addLocalRowIdsListener(null, null, () => { * console.log('Local Row Id changed'); * }); * * const listenerStats = relationships.getListenerStats(); * console.log(listenerStats.remoteRowId); * // -> 1 * console.log(listenerStats.localRowIds); * // -> 1 * ``` * @category Development * @since v1.0.0 */ getListenerStats(): RelationshipsListenerStats; // } /** * The createRelationships function creates a Relationships object, and is the * main entry point into the relationships module. * * A given Store can only have one Relationships object associated with it. If * you call this function twice on the same Store, your second call will return * a reference to the Relationships object created by the first. * @param store The Store for which to register Relationships. * @returns A reference to the new Relationships object. * @example * This example creates a Relationships object. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore(); * const relationships = createRelationships(store); * console.log(relationships.getRelationshipIds()); * // -> [] * ``` * @example * This example creates a Relationships object, and calls the method a second * time for the same Store to return the same object. * * ```js * import {createRelationships, createStore} from 'tinybase'; * * const store = createStore(); * const relationships1 = createRelationships(store); * const relationships2 = createRelationships(store); * console.log(relationships1 === relationships2); * // -> true * ``` * @category Creation * @since v1.0.0 */ export function createRelationships(store: Store): Relationships;