import type { GridFilterBindTarget } from '@xh/hoist/cmp/grid'; import { HoistBase, PlainObject, Some } from '@xh/hoist/core'; import { Field, FieldSpec, Filter, FilterBindTarget, FilterLike, FilterValueSource, StoreRecord, StoreRecordId, StoreRecordOrId, StoreValidationMessagesMap, StoreValidationResultsMap, ValidationResult } from '@xh/hoist/data'; import { StoreValidator } from '@xh/hoist/data/impl/StoreValidator'; import { RecordSet } from './impl/RecordSet'; /** * Configuration for a {@link Store}. At minimum, provide `fields` (or let them be inferred * from GridModel columns). Data can be supplied at construction via `data`, or loaded later * via `Store.loadData()`. * * Can also be passed inline as the `store` config on {@link GridConfig}, where it will be * used to construct a Store automatically. * * See the data package README (`data/README.md`) for tree data, filtering, validation, and * performance tuning guidance. * * @see Store * @see FieldSpec */ export interface StoreConfig { /** Field names, configs, or instances. */ fields?: Array; /** * Default configs applied to `Field` instances constructed internally by this Store. * @see FieldSpec */ fieldDefaults?: Omit; /** * Specification for producing an immutable unique id for each record. May be provided as * either a string property name (default is 'id') or a function that receives the raw data * and returns a string. This property will be normalized to a function upon Store construction. * If there is no natural id to select/generate, you can use `XH.genId` to generate a unique id * on the fly. NOTE that in this case, grids and other components bound to this store will not * be able to maintain record state across reloads. */ idSpec?: StoreRecordIdSpec; /** * Initial data to load in to the Store. */ data?: PlainObject[]; /** * Function to run on each individual data object presented to `loadData()` prior to creating * a `StoreRecord` from that object. This function must return an object, cloning the original * object if edits are necessary. */ processRawData?: (data: PlainObject) => PlainObject; /** * One or more filters or configs to create one. If an array, a single 'AND' filter * will be created. */ filter?: FilterLike; /** True if all children of a passing record should also be considered passing (default false).*/ filterIncludesChildren?: boolean; /** True (default) to load hierarchical/tree data, if any. */ loadTreeData?: boolean; /** * The property on each raw data object that holds its (raw) child objects, if any. * Default 'children', no effect if `loadTreeData: false`. */ loadTreeDataFrom?: string; /** True to treat the root node in hierarchical data as the summary record (default false). */ loadRootAsSummary?: boolean; /** * True to freeze the internal data object of the record. May be set to false to maximize * performance. Note that the internal data of the record should in all cases be considered * immutable (default true). */ freezeData?: boolean; /** * Set to true to indicate that the id for a record implies a fixed position of the record * within the tree hierarchy. May be set to true to maximize performance (default false). */ idEncodesTreePath?: boolean; /** * Performance optimization for large datasets with immutable raw data objects. * * By default, Store reuses existing StoreRecord instances when new data is loaded with * matching IDs and identical field values (determined via deep equality comparison). This * preserves row state in grids for unchanged records. * * When `reuseRecords` is true, the Store skips the fieldwise comparison and instead reuses * records when the raw data object itself is **reference-identical** to the previously loaded * object. This avoids equality checks, record creation, and raw data processing overhead. * * Only use this when your data source provides stable object references for unchanged records. * Should not be used with a `processRawData` function that depends on external state, as that * function will be bypassed on subsequent reloads of reference-identical data. * * Default false. */ reuseRecords?: boolean; /** * Set to true to always validate all uncommitted records on every change to * uncommitted records (add, modify, or remove). Default false. */ validationIsComplex?: boolean; /** * Flags for experimental features. These features are designed for early client-access and * testing, but are not yet part of the Hoist API. */ experimental?: PlainObject; } export interface StoreDefaults { freezeData?: boolean; } /** * Object representing data changes to perform on a Store's committed record set in a single * transaction. */ export interface StoreTransaction { /** * List of raw data objects representing records to be updated. * Updates must be matched to existing records by id in order to be applied. The form of the * update objects should be the same as presented to loadData(), with the exception that any * children property will be ignored, and any existing children for the record being updated * will be preserved. If the record is a child, the new updated instance will be assigned to * the same parent. (Meaning: parent/child relationships *cannot* be modified via updates.) */ update?: PlainObject[]; /** Raw data of new records to be added, */ add?: Array; /** IDs of existing records to be removed. Any descendents will also be removed. */ remove?: StoreRecordId[]; /** * Update to the dedicated summary record(s) for this store. If the store has its * `loadRootAsSummary` flag set to true, the summary record should instead be provided via the * `update` property. */ rawSummaryData?: Some; } /** * Collection of changes made to a Store's RecordSet. Unlike `StoreTransaction` which is used to * specify changes, this object is used to report the actual changes made in a single transaction. */ export interface StoreChangeLog { update?: StoreRecord[]; add?: StoreRecord[]; remove?: StoreRecordId[]; summaryRecords?: StoreRecord[]; } export interface ChildRawData { /** ID of the pre-existing parent record. */ parentId: string; /** * Data for the child records to be added. Can include a `children` property to be processed * into new (grand)child records. */ rawData: PlainObject[]; } export type StoreRecordIdSpec = string | ((data: PlainObject) => StoreRecordId); /** * A managed, observable collection of in-memory {@link StoreRecord}s - the core data container * in Hoist. Used directly by applications and as the data source for {@link GridModel}, * {@link DataViewModel}, and other data-bound components. * * Stores provide: * - Observable record collections with filtering via composable {@link Filter} objects * - Hierarchical/tree data with parent-child navigation * - Local modification tracking (add/modify/remove) with commit/revert * - Record reuse across data reloads to preserve grid row state * - Pluggable validation via {@link Field} rules * * Data is loaded via `loadData()` (full replacement) or `updateData()` (transactional). Fields * can be defined explicitly or inferred from GridModel columns. `Store.defaults` provides * app-wide configuration. * * See the data package README (`data/README.md`) for full documentation including tree data, * filtering patterns, validation, and common pitfalls. * * @see StoreConfig * @see StoreRecord * @see Field * * @mcpHint in-memory data store used by grids and other data components */ export declare class Store extends HoistBase implements FilterBindTarget, FilterValueSource, GridFilterBindTarget { /** App-level defaults for Store. Instance config takes precedence. */ static defaults: StoreDefaults; static isStore(obj: unknown): obj is Store; readonly isFilterValueSource = true; fields: Field[]; idSpec: (data: PlainObject) => StoreRecordId; processRawData: (raw: any) => any; filterIncludesChildren: boolean; loadTreeData: boolean; loadTreeDataFrom: string; loadRootAsSummary: boolean; idEncodesTreePath: boolean; freezeData: boolean; reuseRecords: boolean; validationIsComplex: boolean; filter: Filter; /** Timestamp (ms) of the last time this store's data was changed. */ lastUpdated: number; /** Timestamp (ms) of the last time this store's data was loaded.*/ lastLoaded: number; /** * Records containing summary data, such as top-level aggregations produced by a Hoist Cube * or any other custom aggregation(s) calculated and installed by the application. Set via * {@link loadData} or by loading a tree structure with `loadRootAsSummary` set to true. */ summaryRecords: StoreRecord[]; /** @internal - used internally by any StoreFilterField bound to this store. */ xhFilterText: string; validator: StoreValidator; private _committed; private _current; _filtered: RecordSet; private _dataDefaults; _created: number; private _fieldMap; experimental: any; constructor({ fields, fieldDefaults, idSpec, processRawData, filter, filterIncludesChildren, loadTreeData, loadTreeDataFrom, loadRootAsSummary, freezeData, idEncodesTreePath, reuseRecords, validationIsComplex, experimental, data }: StoreConfig); /** Remove all records from the store. Equivalent to calling `loadData([])`. */ clear(): void; /** * Load a new and complete dataset, replacing any/all pre-existing Records as needed. * * If raw data objects have a `children` property, it will be expected to be an array and its * items will be recursively processed into child Records, each created with a pointer to its * parent's newly assigned StoreRecord ID. * * Note that this process will re-use pre-existing StoreRecord object instances if they are present * in the new dataset (as identified by their ID), contain the same data, and occupy the same * place in any hierarchy across old and new loads. This is to maximize the ability of * downstream consumers (e.g. ag-Grid) to recognize Records that have not changed and do not * need to be re-evaluated / re-rendered. * * Summary data can be provided via `rawSummaryData` or as the root data if the Store was * created with its `loadRootAsSummary` flag set to true. * * @param rawData - source data to load * @param rawSummaryData - source data for optional summary record(s), representing * custom aggregations for the dataset, if desired. */ loadData(rawData: PlainObject[], rawSummaryData?: Some): void; /** * Add, update, or delete Records in this Store. Note that objects passed to this method * for adds and updates should have all the raw source data required to create those Records - * i.e. they should be in the same form as when passed to `loadData()`. The added/updated * source data will be run through this Store's `idSpec` and `processRawData` functions. * * Adds can also be provided as a {@link ChildRawData} object of the form `{rawData, parentId}` * to add new Records under a known, pre-existing parent StoreRecord. * * Unlike `loadData()`, existing Records that are *not* included in this update transaction * will be left in place and as is. * * Records loaded or removed via this method will be considered to be "committed", with the * expectation that inputs to this method were provided by the server or other data source of * record. For modifying particular fields on existing Records, see `modifyRecords()`. For local * adds/removes not sourced from the server, see `addRecords()` and `removeRecords()`. Those * APIs will modify the current RecordSet but leave those changes in an uncommitted state. * * @param rawData - data changes to process. If provided as an array, rawData will be processed * into adds and updates, with updates determined by matching existing records by ID. * @returns changes applied, or null if no record changes were made. */ updateData(rawData: PlainObject[] | StoreTransaction): StoreChangeLog; /** * Re-runs the Filter on the current data. Applications only need to call this method if * the state underlying the filter, other than the record data itself, has changed. Store will * re-filter automatically whenever StoreRecord data is updated or modified. */ refreshFilter(): void; /** * Add new Records to this Store in a local, uncommitted state - i.e. with data that has yet to * be persisted back to, or sourced from, the server or other data source of record. * * Note that data objects passed to this method must include a literal `id` property - this * method does *not* run the Store's `idSpec` function. Callers can generate an id with * `XH.genId()` if no natural ID can be produced locally on the client. * * For StoreRecord additions that originate from the server, call `updateData()` instead. * * @param data - source data for new StoreRecord(s). Note that this data will * *not* be processed by this Store's `processRawData` or `idSpec` functions, but will be * parsed and potentially transformed according to this Store's Field definitions. * @param parentId - ID of the pre-existing parent record under which this new * record should be added, if any. */ addRecords(data: Some, parentId?: StoreRecordId): void; /** * Remove Records from the Store in a local, uncommitted state - i.e. when queuing up a set of * deletes on the client to be flushed back to the server at a later time. * * For StoreRecord deletions that originate from the server, call `updateData()` instead. * * @param records - list of StoreRecord IDs or Records to remove */ removeRecords(records: StoreRecordOrId | StoreRecordOrId[]): void; /** * Modify individual StoreRecord field values in a local, uncommitted state - i.e. when updating a * StoreRecord or Records via an inline grid editor or similar control. * * This method accepts partial updates for any Records to be modified; modifications need only * include the StoreRecord ID and any fields that have changed. * * For StoreRecord updates that originate from the server, call `updateData()` instead. * * @param modifications - field-level modifications to apply to existing * Records in this Store. Each object in the list must have an `id` property identifying * the StoreRecord to modify, plus any other properties with updated field values to apply, * e.g. `{id: 4, quantity: 100}, {id: 5, quantity: 99, customer: 'bob'}`. * @returns changes applied, or null if no record changes were made. */ modifyRecords(modifications: Some): StoreChangeLog; /** * Revert all changes made to the specified Records since they were last committed. * * This restores these Records to the state they were in when last loaded into this Store via * `loadData()` or `updateData()`, undoing any local modifications that might have been applied. * * @param records - StoreRecord IDs or instances to revert */ revertRecords(records: StoreRecordOrId | StoreRecordOrId[]): void; /** * Revert all changes made to the Store since data was last committed. * * This restores all Records to the state they were in when last loaded into this Store via * `loadData()` or `updateData()`, undoing any local modifications that might have been applied, * removing any uncommitted records added locally, and restoring any uncommitted deletes. */ revert(): void; /** Get a specific Field by name.*/ getField(name: string): Field; get fieldNames(): string[]; /** Records in this store, respecting any filter (if applied).*/ get records(): StoreRecord[]; /** All records in this store, unfiltered.*/ get allRecords(): StoreRecord[]; /** All records that were originally loaded into this store.*/ get committedRecords(): StoreRecord[]; /** Records added locally which have not been committed.*/ get addedRecords(): StoreRecord[]; /** Records removed locally which have not been committed.*/ get removedRecords(): StoreRecord[]; /** Records modified locally since they were last loaded. */ get dirtyRecords(): StoreRecord[]; /** Alias for {@link Store.dirtyRecords} */ get modifiedRecords(): StoreRecord[]; /** * Root records in this store, respecting any filter (if applied). * If this store is not hierarchical, this will be identical to 'records'. */ get rootRecords(): StoreRecord[]; /** * Root records in this store, unfiltered. * If this store is not hierarchical, this will be identical to 'allRecords'. */ get allRootRecords(): StoreRecord[]; /** * Single summary data record, if only one (or null if none). Maintained for convenience and * for backwards compat with app code predating support for multiple {@link summaryRecords}. */ get summaryRecord(): StoreRecord; /** True if the store has changes which need to be committed. */ get isDirty(): boolean; /** Alias for {@link Store.isDirty} */ get isModified(): boolean; /** * Set a filter on this store. * * @param filter - one or more filters or configs to create one. If an * array, a single 'AND' filter will be created. */ setFilter(filter: FilterLike): void; setFilterIncludesChildren(val: boolean): void; /** Convenience method to clear the Filter applied to this store. */ clearFilter(): void; /** * @returns true if the StoreRecord is in the store but currently excluded by a filter; * false if the record is either not in the Store at all or not filtered out. */ recordIsFiltered(recOrId: StoreRecordOrId): boolean; getValuesForFieldFilter(fieldName: string, filter?: Filter): any[]; /** * Set whether the root should be loaded as summary data in loadData(). */ setLoadRootAsSummary(loadRootAsSummary: boolean): void; /** The count of the filtered records in the store. */ get count(): number; /** The count of all records in the store. */ get allCount(): number; /** The count of the filtered root records in the store. */ get rootCount(): number; /** The count of all root records in the store. */ get allRootCount(): number; /** True if the store is empty after filters have been applied */ get empty(): boolean; /** True if the store is empty before filters have been applied */ get allEmpty(): boolean; get maxDepth(): number; get errors(): StoreValidationMessagesMap; get validationResults(): StoreValidationResultsMap; /** Count of all validation errors for the store. */ get errorCount(): number; /** Array of all errors for this store. */ get allErrors(): string[]; /** Array of all ValidationResults for this store. */ get allValidationResults(): ValidationResult[]; /** * Get a record by ID, or null if no matching record found. * * @param id - ID of record to be queried. * @param respectFilter - false (default) to return a StoreRecord with the given ID even if an * active filter is excluding it from the primary `records` collection. True to restrict * matches to this Store's post-filter StoreRecord collection only. */ getById(id: StoreRecordId, respectFilter?: boolean): StoreRecord; /** * Get children records for a record. * * See also the 'children' and 'allChildren' properties on StoreRecord - those getters will likely * be more convenient for most app-level callers. * * @param id - ID of record to be queried. * @param respectFilter - true to skip records excluded by any active filter. */ getChildrenById(id: StoreRecordId, respectFilter?: boolean): StoreRecord[]; /** * Get descendant records for a record. * * See also the 'descendants' and 'allDescendants' properties on StoreRecord - those getters will * likely be more convenient for most app-level callers. * * @param id - ID of record to be queried. * @param respectFilter - true to skip records excluded by any active filter. */ getDescendantsById(id: StoreRecordId, respectFilter?: boolean): StoreRecord[]; /** * Get ancestor records for a record. * * See also the 'ancestors' and 'allAncestors' properties on StoreRecord - those getters will * likely be more convenient for most app-level callers. * * @param id - ID of record to be queried. * @param respectFilter - true to skip records excluded by any active filter. */ getAncestorsById(id: StoreRecordId, respectFilter?: boolean): StoreRecord[]; /** True if the store is confirmed to be Valid. */ get isValid(): boolean; /** True if the store is confirmed to be NotValid. */ get isNotValid(): boolean; /** Recompute ValidationResults for all records and return true if the store is valid. */ validateAsync(): Promise; /** Destroy this store, cleaning up any resources used. */ destroy(): void; protected get defaultFieldClass(): typeof Field; setXhFilterText(s: string): void; private getOrThrow; private getCommittedOrThrow; private resetRecords; private parseFields; private rebuildFiltered; private createRecord; private createRecords; private get summaryRecordIds(); private parseRaw; private parseUpdate; private createDataDefaults; private createFieldMap; private parseExperimental; private parseIdSpec; private revertSummaryRecords; }