import { AggregateOperator, Expression, PropertyOrder } from '../query'; import { StateRepository } from '../core'; import { LimeType } from '../limetype'; import { LimeObject, LimeObjectRecord } from './limeobject'; /** * Repository for loading, querying, and managing Lime CRM objects. * * {@link LimeObjectRepository} is the primary service for interacting with business objects * in Lime CRM (deals, companies, people, etc.). It provides methods to: * - Load single objects or collections with filtering, sorting, and pagination * - Load objects related through properties (e.g., deals related to a company) * - Search across limetypes with a free-text query * - Delete objects * - Access cached objects from state * - Load and retrieve JSON schemas for limetypes * * The repository extends {@link StateRepository}, which means: * - All loaded objects are cached in state * - Components can subscribe to object changes * - Use {@link SelectCurrentLimeObject} decorator for automatic reactive updates * * @example Use a decorator to get the current object from context * ```ts * import { State } from '@stencil/core'; * import { * LimeObject, * SelectCurrentLimeObject, * } from '@limetech/lime-web-components'; * * class MyComponent { * // The object is replaced whenever the context changes. * @State() * @SelectCurrentLimeObject() * private currentObject: LimeObject; * * private logCurrentObject() { * console.log(this.currentObject.getValue('name')); * console.log(this.currentObject.id); * } * } * ``` * * @see {@link LimeObject} for the object structure * @see {@link LoadOptions} for query options * @see {@link SearchOptions} for free-text search options * @see {@link ObjectResponse} for response structure * @see {@link StateRepository} for subscription patterns * @public * @group Lime objects */ export interface LimeObjectRepository extends StateRepository { /** * Load a single object by ID into the state. * * Fetches an object from the database and stores it in the repository state, * making it available through {@link getObject}. Triggers state updates that * notify subscribers. * * Prefer using the {@link SelectCurrentLimeObject} decorator for reactive * updates. * * @param limetype - Name of the limetype (e.g., 'deal', 'company', 'person'). * @param id - Unique identifier of the object to load. * @param options - Optional configuration to limit which properties are loaded. * @returns The loaded {@link LimeObject}, or `null` if it does not exist. * * @example Load an object and use the result directly * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deal = await repository.loadObject('deal', 1234); * * if (deal) { * console.log(deal.descriptive); * } * ``` * * @example Load only specific properties for better performance * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * await repository.loadObject('company', 789, { * properties: ['name', 'address', 'phone'], * }); * ``` * * @see {@link getObject} to retrieve the loaded object * @see {@link SelectCurrentLimeObject} decorator for automatic reactive loading */ loadObject(limetype: string, id: number, options?: Pick): Promise; /** * Load a collection of objects with filtering, sorting, and pagination. * * Queries the database for objects matching the specified criteria. Results are * both returned in the promise and stored in the repository state. Supports: * - Complex filter expressions with AND/OR/NOT logic * - Multi-column sorting * - Pagination via limit and offset * - Selecting specific properties to load * - Aggregate calculations (count, sum, avg, etc.) * * @param limetype - Name of the limetype to query (e.g., 'deal', 'company'). * @param options - Query configuration including filters, sorting, and pagination. * @returns Promise resolving to {@link ObjectResponse} with objects and metadata. * * @example Load active deals over a given value, sorted by value * ```ts * import { Operator, PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * const response = await repository.loadObjects('deal', { * filter: { * op: Operator.AND, * exp: [ * { key: 'status', op: Operator.EQUALS, exp: 'active' }, * { key: 'value', op: Operator.GREATER_OR_EQUAL, exp: 50_000 }, * ], * }, * order: [{ name: 'value', direction: 'DESC' }], * limit: 50, * offset: 0, * }); * * console.log( * `Loaded ${response.objects.length} of ${response.totalCount} deals` * ); * ``` * * @example Load with pagination for a table * ```ts * import { Operator, PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const pageSize = 25; * const pageNumber = 0; * * const response = await repository.loadObjects('company', { * filter: { key: 'active', op: Operator.EQUALS, exp: true }, * limit: pageSize, * offset: pageNumber * pageSize, * order: [{ name: 'name', direction: 'ASC' }], * }); * * const companies = response.objects; * const totalPages = Math.ceil(response.totalCount / pageSize); * ``` * * @example Load only specific properties for better performance * ```ts * import { Operator, PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * const response = await repository.loadObjects('deal', { * filter: { key: 'status', op: Operator.EQUALS, exp: 'active' }, * properties: ['name', 'value', 'closedate', 'company.name'], * limit: 100, * }); * ``` * * @example Load with aggregate calculations * ```ts * import { * AggregateOperator, * Operator, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const companyId = 456; * * const response = await repository.loadObjects('deal', { * filter: { key: 'company', op: Operator.EQUALS, exp: companyId }, * properties: [ * 'name', * 'value', * { * name: 'tasks.status', * key: 'completed_tasks_count', * operator: AggregateOperator.Count, * filter: 'completed_tasks_filter', * }, * ], * }); * * for (const deal of response.objects) { * console.log(`${deal['name']}: ${deal['completed_tasks_count']} tasks`); * } * ``` * * @see {@link LoadOptions} for all query options * @see {@link ObjectResponse} for response structure * @see {@link Expression} for building filter conditions */ loadObjects(limetype: string, options?: LoadOptions): Promise; /** * Load objects related to another object through a relation property. * * Fetches objects connected via a relationship (e.g., deals related to a company, * tasks assigned to a person). This is equivalent to querying the related limetype * with a filter, but more convenient and follows the data model relationships. * * Supports the same filtering, sorting, and pagination options as {@link loadObjects}. * * @param limetype - Name of the limetype that owns the relation (e.g., 'company'). * @param id - ID of the owning object. * @param property - Name of the relation property (e.g., 'deals', 'contacts'). * @param options - Query configuration for the related objects. * @returns Promise resolving to {@link ObjectResponse} with related objects. * * @example Load all open deals for a company * ```ts * import { Operator, PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const companyId = 456; * * const response = await repository.loadRelations('company', companyId, 'deals', { * filter: { * key: 'status', * op: Operator.IN, * exp: ['open', 'in_progress'], * }, * order: [{ name: 'value', direction: 'DESC' }], * limit: 20, * }); * * console.log(`Company has ${response.totalCount} open deals`); * ``` * * @example Load with pagination and count * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const companyId = 456; * const page = 0; * const pageSize = 25; * * const response = await repository.loadRelations( * 'company', * companyId, * 'contacts', * { * order: [{ name: 'name', direction: 'ASC' }], * limit: pageSize, * offset: page * pageSize, * } * ); * * const contacts = response.objects; * const totalContacts = response.totalCount; * ``` * * @see {@link LoadOptions} for query options * @see {@link ObjectResponse} for response structure */ loadRelations(limetype: string, id: number, property: string, options?: LoadOptions): Promise; /** * Search for objects matching a free-text query. * * Searches every limetype that has search enabled, or only the limetypes * given in {@link SearchOptions.limetypes}. Matching objects are stored in * the repository state, and the response carries the number of hits per * limetype in {@link SearchResponse.aggregates}. * * The query is matched term by term, and each term matches as a prefix, * which is what makes search-as-you-type work. * * @param text - The text to search for. * @param options - Search configuration. * @returns Promise resolving to {@link SearchResponse} with matching objects. * * @example Search within a single limetype * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const limetypes = platform.get(PlatformServiceName.LimeTypeRepository); * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * const company = limetypes.getLimeType('company'); * * if (company) { * const response = await repository.search('Lundalogik', { * limetypes: [company], * limit: 25, * }); * * console.log(`Found ${response.objects.length} companies`); * } * ``` * * @example Read the hit count for each limetype * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const limetypes = platform.get(PlatformServiceName.LimeTypeRepository); * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * // `limit: 0` reports the counts without returning any objects. * const response = await repository.search('Lundalogik', { limit: 0 }); * * const aggregates = Object.entries(response.aggregates ?? {}); * * for (const [name, [hits]] of aggregates) { * const limetype = limetypes.getLimeType(name); * * console.log( * `${limetype?.localname.plural ?? name}: ${hits.totalCount ?? 0}` * ); * } * ``` * * @see {@link SearchOptions} for search options * @see {@link SearchResponse} for response structure * * @beta */ search(text: string, options?: SearchOptions): Promise; /** * Load a JSON schema for a limetype. * * Fetches the JSON schema definition for the specified limetype and stores it * in the repository cache. The schema describes the structure, validation rules, * and UI hints for the limetype's properties. * * Schemas are typically used by form builders, validation libraries, and UI * generation tools. The generic type parameter allows you to specify the * expected schema structure. * * @param limetype - Name of the limetype to load the schema for. * @returns Promise resolving to the schema object. * * @example Load a schema for form generation * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const formSchema = await repository.loadSchema('deal'); * ``` * * @example Load with type safety * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * interface DealSchema extends Record { * properties: { * name: { type: 'string'; required: true }; * value: { type: 'number'; minimum: 0 }; * status: { type: 'string'; enum: string[] }; * }; * } * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const schema = await repository.loadSchema('deal'); * * console.log(schema.properties.name.required); * ``` * * @see {@link getSchema} to retrieve a cached schema without loading */ loadSchema = Record>(limetype: string): Promise; /** * Get a cached JSON schema that has already been loaded. * * Retrieves a schema from the repository cache without making a network request. * Returns `null` if the schema has not been loaded yet. Use {@link loadSchema} * to fetch and cache the schema first. * * @param limetype - Name of the limetype to get the schema for. * @returns The schema if it has been loaded, otherwise `null`. * * @example Fall back to loading when the schema is not cached * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * let schema = repository.getSchema('deal'); * * if (!schema) { * schema = await repository.loadSchema('deal'); * } * ``` * * @see {@link loadSchema} to load the schema into cache */ getSchema = Record>(limetype: string): TSchemaType | null; /** * Delete an object from the database. * * Permanently removes the specified object. This operation cannot be undone. * The object will be removed from the repository state after successful deletion. * * Deletion may fail if: * - User lacks delete permissions * - Object has dependent relationships * - Business rules prevent deletion * * @param limetype - Name of the limetype (e.g., 'deal', 'company'). * @param id - ID of the object to delete. * @returns Promise that resolves when deletion is complete. * @throws Error if deletion fails * * @example Delete a single object * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const dealId = 1234; * * try { * await repository.deleteObject('deal', dealId); * } catch (error) { * console.error('Failed to delete deal', error); * } * ``` * * @example Bulk delete with error handling * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const dealIds = [1, 2, 3]; * const failed: number[] = []; * * for (const id of dealIds) { * try { * await repository.deleteObject('deal', id); * } catch { * failed.push(id); * } * } * * console.log(`Deleted ${dealIds.length - failed.length} of ${dealIds.length}`); * ``` */ deleteObject(limetype: string, id: number): Promise; /** * Get a single object that has already been loaded into state. * * Retrieves an object from the repository cache without making a network request. * Returns `undefined` if the object has not been loaded yet. * * Use this to access objects that were previously loaded via {@link loadObject}, * {@link loadObjects}, or {@link loadRelations}. * * @param limetype - Name of the limetype. * @param id - ID of the object to retrieve. * @returns The cached {@link LimeObject}, or `undefined` if not loaded. * * @example Check if an object is already loaded * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deal = repository.getObject('deal', 1234); * * if (deal) { * console.log('Deal name:', deal.getValue('name')); * } else { * repository.loadObject('deal', 1234); * } * ``` * * @example Use in a computed property * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * function getDealName(dealId: number): string { * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deal = repository.getObject('deal', dealId); * * return deal?.getValue('name') ?? 'Loading...'; * } * ``` * * @see {@link loadObject} to load an object into cache * @see {@link LimeObject} for the object structure */ getObject(limetype: string, id: number): LimeObject | undefined; /** * Get all objects of a specific limetype that are loaded in state. * * Retrieves all cached objects for the specified limetype without making a * network request. Only returns objects that have been previously loaded via * {@link loadObject}, {@link loadObjects}, or {@link loadRelations}. * * This does NOT load all objects of a type from the database. Use * {@link loadObjects} to fetch objects from the server. * * @param limetype - Name of the limetype to get objects for. * @returns Array of cached {@link LimeObject}s (may be empty). * * @example Get all deals currently in state * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deals = repository.getObjects('deal'); * * console.log(`${deals.length} deals are cached`); * for (const deal of deals) { * console.log(`- ${deal.getValue('name')}: ${deal.getValue('value')}`); * } * ``` * * @example Check if specific objects are loaded * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * function areDealsCached(dealIds: number[]): boolean { * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const cachedIds = new Set( * repository.getObjects('deal').map((deal) => deal.id) * ); * * return dealIds.every((id) => cachedIds.has(id)); * } * ``` * * @example Use cached data for dropdown options * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const companies = repository.getObjects('company'); * * const options = companies.map((company) => ({ * value: company.id, * label: company.getValue('name'), * })); * ``` * * @see {@link loadObjects} to fetch objects from the server * @see {@link LimeObject} for the object structure */ getObjects(limetype: string): LimeObject[]; } /** * Options for loading objects from the Lime CRM database. * * {@link LoadOptions} defines how to query objects when using {@link LimeObjectRepository} * methods like loadObjects and loadRelations. Supports filtering, sorting, pagination, * and selecting specific properties to load. * * This provides a simpler interface compared to {@link Query}, with automatic state * management and integration with the component platform. * * @example Filter, sort, and limit a query * ```ts * import { Operator, PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * const response = await repository.loadObjects('deal', { * filter: { key: 'status', op: Operator.EQUALS, exp: 'active' }, * order: [{ name: 'value', direction: 'DESC' }], * limit: 50, * }); * * const deals = response.objects; * ``` * * @see {@link LimeObjectRepository.loadObjects} for loading objects * @see {@link Expression} for building filter conditions * @see {@link Query} for more advanced query capabilities * @public * @group Lime objects */ export interface LoadOptions { /** * Maximum number of objects to load. * * Use this with offset for pagination. If omitted, a default limit * will be applied by the backend. */ limit?: number; /** * Number of objects to skip before starting to return results. * * Use this with limit for pagination. For example, limit=50 and offset=100 * will return objects 101-150. */ offset?: number; /** * Filter expression to limit which objects are returned. * * Build complex filter criteria using {@link Expression} with operators * like AND, OR, NOT, and comparison operators. Leave undefined to load * all objects. * * @example Simple equality filter * ```ts * import { LoadOptions, Operator } from '@limetech/lime-web-components'; * * const options: LoadOptions = { * filter: { * key: 'status', * op: Operator.EQUALS, * exp: 'active', * }, * }; * ``` * * @example Nested AND/OR filter * ```ts * import { LoadOptions, Operator } from '@limetech/lime-web-components'; * * const options: LoadOptions = { * filter: { * op: Operator.AND, * exp: [ * { key: 'status', op: Operator.EQUALS, exp: 'active' }, * { * op: Operator.OR, * exp: [ * { key: 'priority', op: Operator.EQUALS, exp: 'high' }, * { key: 'value', op: Operator.GREATER, exp: 50_000 }, * ], * }, * ], * }, * }; * ``` * * @see {@link Expression} for filter expression types * @see {@link Operator} for available operators */ filter?: Expression; /** * Properties to sort results by. * * Specify one or more properties with their sort direction (ASC or DESC). * Objects will be sorted in the order specified, with earlier entries * taking precedence. * * @example Sort by two properties * ```ts * import { LoadOptions } from '@limetech/lime-web-components'; * * // Sort by priority ascending, then value descending * const options: LoadOptions = { * order: [ * { name: 'priority', direction: 'ASC' }, * { name: 'value', direction: 'DESC' }, * ], * }; * ``` * * @see {@link PropertyOrder} */ order?: PropertyOrder[]; /** * Properties to load on the objects. * * Specify which properties to include in the loaded objects. This can * improve performance by only loading needed data. Can include: * - Simple property names (strings) * - Aggregate properties for calculating statistics * * If omitted, all properties will be loaded. * * @example Load only specific properties * ```ts * import { LoadOptions } from '@limetech/lime-web-components'; * * const options: LoadOptions = { * properties: ['name', 'status', 'value', 'company.name'], * }; * ``` * * @example Include aggregate calculations * ```ts * import { AggregateOperator, LoadOptions } from '@limetech/lime-web-components'; * * const options: LoadOptions = { * properties: [ * 'name', * 'value', * { * name: 'deals.value', * key: 'total_deal_value', * operator: AggregateOperator.Sum, * }, * ], * }; * ``` * * @see {@link AggregateProperty} for aggregate definitions */ properties?: Array; /** * Whether to include totals (totalCount, totalRelations, aggregates) in * the response. Set to `false` to skip the extra COUNT query on the server, * which can significantly improve performance for large datasets. * * When `false`, `totalCount` and `totalRelations` will be `null` in the * response. Use a separate request with `limit: 0` to fetch totals * without object data. * * Maps to the `?includeTotals=false` query parameter. Defaults to `true`. * * @alpha */ includeTotals?: boolean; } /** * Options for searching objects with {@link LimeObjectRepository.search}. * * @example Search within a single limetype * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const limetypes = platform.get(PlatformServiceName.LimeTypeRepository); * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * const company = limetypes.getLimeType('company'); * * if (company) { * const response = await repository.search('Lundalogik', { * limetypes: [company], * limit: 25, * }); * * console.log(`Found ${response.objects.length} companies`); * } * ``` * * @see {@link LimeObjectRepository.search} for searching * @beta * @group Lime objects */ export interface SearchOptions { /** * Limetypes to search within. * * Only objects of these limetypes are returned, and a limetype named here * is searched even when search is disabled for it. Hit counts are still * reported for every limetype that has search enabled, so the user can see * what a wider search would find. If omitted, all limetypes with search * enabled are searched. */ limetypes?: LimeType[]; /** * Maximum number of objects to return. * * Use `0` to get hit counts and totals without any objects. If omitted, a * default limit is applied by the backend. */ limit?: number; /** * Whether to match the search terms whole instead of as prefixes. * * By default each term is treated as a prefix, so searching for * `Vaksala-lunda 29:2` also matches `Vaksala-lunda 29:21`. When `true`, * every term must match in full and in order, ignoring case, so * `Vaksala-lunda 29:21` is no longer a hit. * * Relation descriptive and option properties are always matched as * prefixes, and ignore this option. * * @example Search for an exact string * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * // Matches `Vaksala-lunda 29:2`, but not `Vaksala-lunda 29:21` * const response = await repository.search('Vaksala-lunda 29:2', { * exact: true, * }); * * console.log(`Found ${response.totalCount} objects`); * ``` * * @alpha */ exact?: boolean; } /** * Response from {@link LimeObjectRepository.search}. * * Carries the matching objects and their total, plus the number of hits per * limetype. This is not an {@link ObjectResponse}: search reports hit counts * under `aggregates` rather than the aggregate calculations a query returns, * and never carries `totalRelations`. * * @example Read the hit count for each limetype * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const limetypes = platform.get(PlatformServiceName.LimeTypeRepository); * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * // `limit: 0` reports the counts without returning any objects. * const response = await repository.search('Lundalogik', { limit: 0 }); * * const aggregates = Object.entries(response.aggregates ?? {}); * * for (const [name, [hits]] of aggregates) { * const limetype = limetypes.getLimeType(name); * * console.log( * `${limetype?.localname.plural ?? name}: ${hits.totalCount ?? 0}` * ); * } * ``` * * @see {@link LimeObjectRepository.search} for searching * @see {@link SearchOptions} for search options * @beta * @group Lime objects */ export interface SearchResponse { /** * Objects that matched the query, across every limetype searched. * * Ordered by search score with the best match first, and capped by * {@link SearchOptions.limit}. Objects of different limetypes are mixed * together, so read `_limetype` to tell them apart. * * These records carry `_id`, `_limetype`, `_descriptive` and the * properties covered by the search index, not the full object. Load the * object by id when you need the rest. */ objects: LimeObjectRecord[]; /** * Total number of matching objects across every limetype that was counted. * * Not capped by {@link SearchOptions.limit}, so this can exceed * `objects.length`. It is also wider than `objects` when hit counts cover * limetypes that were counted but not returned, so prefer the per-limetype * counts in {@link aggregates} when reporting on what came back. */ totalCount: number; /** * Number of hits per limetype, keyed by limetype name. * * The platform reports one count per limetype, so each entry holds a * single {@link SearchHitCount}: `{ company: [{ totalCount: 5 }] }`. * * A limetype that produced no hits is usually left out entirely rather * than reported as zero, so treat a missing key as zero. */ aggregates?: SearchHitCounts; } /** * Hit counts from {@link LimeObjectRepository.search}, keyed by limetype name. * * @see {@link SearchResponse.aggregates} * @beta * @group Lime objects */ export type SearchHitCounts = Record; /** * The number of objects of a single limetype that matched a search. * * @see {@link SearchResponse.aggregates} * @beta * @group Lime objects */ export interface SearchHitCount { /** * Number of matching objects of this limetype. * * A limetype named in {@link SearchOptions.limetypes} is reported even * when it has no hits, and the platform reports that as `null` rather * than `0`. Coalesce before using the value: `totalCount ?? 0`. */ totalCount: number | null; } /** * @public * @group Lime objects */ export interface AggregateProperty { /** * Nested path to the property to aggregate on */ name: string; /** * Key of the property on the response object */ key: string; /** * The operator to use when aggregating the values */ operator: AggregateOperator; /** * Name of the filter to use when aggregating the values */ filter?: string; } /** * Response from loading objects via {@link LimeObjectRepository}. * * Contains the objects that matched the query, along with count information * and any requested aggregate calculations. Used by {@link LimeObjectRepository.loadObjects} * and {@link LimeObjectRepository.loadRelations}. Free-text search returns a * {@link SearchResponse} instead. * * The response provides: * - `objects` - The actual data records that matched the query * - `totalCount` - Total number of matching objects (for pagination) * - `totalRelations` - Total count without filters (only for loadRelations) * - `aggregates` - Calculated statistics grouped by criteria * * @example Basic usage with pagination * ```ts * import { Operator, PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const pageSize = 25; * * const response = await repository.loadObjects('deal', { * filter: { key: 'status', op: Operator.EQUALS, exp: 'active' }, * limit: pageSize, * offset: 0, * }); * * console.log( * `Showing ${response.objects.length} of ${response.totalCount} deals` * ); * * const totalPages = Math.ceil(response.totalCount / pageSize); * ``` * * @see {@link LimeObjectRepository.loadObjects} * @see {@link LimeObjectRepository.loadRelations} * @see {@link SearchResponse} for the free-text search response * @see {@link LoadOptions} for query configuration * @public * @group Lime objects */ export interface ObjectResponse { /** * Array of objects that matched the query. * * Each object contains the properties specified in {@link LoadOptions.properties}, * or all properties if none were specified. The length may be less than * {@link totalCount} when using limit/offset for pagination. * * Note: The type is `object[]` rather than `LimeObject[]`. In practice the * objects behave like {@link LimeObject} instances (with `getValue()`, `id`, * etc.), but the type is intentionally loose here. */ objects: LimeObjectRecord[]; /** * Total number of objects that match the query filter. * * This count reflects the total number of objects matching the filter, * regardless of the limit parameter. Use this for pagination calculations. * * @example Work out the page count * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const pageSize = 25; * * const response = await repository.loadObjects('deal', { limit: pageSize }); * * const totalPages = Math.ceil(response.totalCount / pageSize); * const hasMore = response.objects.length < response.totalCount; * ``` */ totalCount: number; /** * Total number of related objects without any filter applied. * * Only present in responses from {@link LimeObjectRepository.loadRelations}. * Represents the total count of all related objects, ignoring the filter * but respecting the relationship. Use this to show "X of Y total relations". * * @example Compare the filtered count with the total * ```ts * import { Operator, PlatformServiceName } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const companyId = 456; * * const response = await repository.loadRelations('company', companyId, 'deals', { * filter: { key: 'status', op: Operator.EQUALS, exp: 'active' }, * }); * * console.log(`${response.totalCount} active deals`); * console.log(`${response.totalRelations} total deals for this company`); * ``` */ totalRelations?: number; /** * Aggregated calculation results grouped by criteria. * * Contains the results of aggregate operations (COUNT, SUM, AVG, MAX, MIN) * requested via {@link AggregateProperty} in the query properties. * Results are organized by group name, with each group containing * an array of aggregation values. * * @example Read two aggregate groups from one query * ```ts * import { * AggregateOperator, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * * const response = await repository.loadObjects('deal', { * properties: [ * 'name', * { * name: 'value', * key: 'total_value', * operator: AggregateOperator.Sum, * }, * { * name: 'value', * key: 'avg_value', * operator: AggregateOperator.Average, * }, * ], * }); * * // Both aggregates share the `default` group, one key each. * const defaultGroup = response.aggregates?.['default']?.[0]; * const totalValue = defaultGroup?.['total_value']; * const avgValue = defaultGroup?.['avg_value']; * ``` * * @see {@link AggregationGroups} for the aggregate structure * @see {@link AggregateProperty} for defining aggregates */ aggregates?: AggregationGroups; } /** * @public * @group Lime objects */ export interface AggregationGroups { /** * Name of the aggregation group along with a list of aggregations */ [name: string]: ObjectAggregation[]; } /** * @public * @group Lime objects */ export interface ObjectAggregation { /** * Name of the property being aggregated along with its aggregated value */ [property: string]: number | string; }