@happyvertical/smrt-core 0.43.1 → 0.43.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"collection.js","names":[],"sources":["../src/collection.ts"],"sourcesContent":["import { createLogger } from '@happyvertical/logger';\nimport { buildWhere, type DatabaseInterface } from '@happyvertical/sql';\nimport type { SmrtClassOptions } from './class';\nimport { SmrtClass } from './class';\nimport {\n buildQueryCacheKey,\n type CollectionCacheConfig,\n ensureCacheInvalidationListener,\n getCachedRows,\n getCacheGeneration,\n invalidateCollectionCache,\n registerCrossProcessCacheInterest,\n resolveDbCacheKey,\n setCachedRows,\n} from './collection-cache';\n// Leaf-module import (not './dispatch'): the dispatch barrel re-exports\n// DispatchCollection, which extends SmrtCollection — importing the barrel from\n// this module would create an evaluation-order cycle.\nimport {\n isTenantScopedClassResolved,\n resolveDispatchTenantScope,\n} from './dispatch/tenant-resolver.js';\nimport { EmbeddingProvider } from './embeddings/provider';\nimport { EmbeddingStorage } from './embeddings/storage';\nimport type { ClassEmbeddingConfig } from './embeddings/types';\nimport {\n createInterceptorContext,\n GlobalInterceptors,\n type ListOptions as InterceptorListOptions,\n resolveGetStringFilter,\n} from './interceptors';\nimport type { SmrtObject } from './object';\nimport {\n DEFAULT_LIST_LIMIT,\n MAX_LIST_LIMIT,\n QueryBoundsError,\n QueryOrderByError,\n} from './query-bounds';\nimport { ObjectRegistry } from './registry';\nimport type { SmrtObjectConstructor } from './registry/types';\nimport { detectEngine } from './schema/ddl/index';\nimport { verifyPersistenceTable } from './schema/table-verifier';\nimport {\n classnameToTablename,\n fieldsFromClass,\n formatDataJs,\n toCamelCase,\n toSafeInteger,\n toSnakeCase,\n} from './utils';\nimport { chunkArray, IN_LIST_CHUNK_SIZE } from './utils/chunk';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * `_smrt_contexts.owner_id` value for collection-level (as opposed to\n * per-object) memory. Tenant-scoped collections suffix the active tenant id\n * (`__collection__:<tenantId>`) so learned memory never crosses tenants —\n * see {@link SmrtCollection.resolveCollectionMemoryOwnerId} (#2365).\n */\nconst COLLECTION_MEMORY_OWNER_ID = '__collection__';\n\n/** Maximum number of independent facet fields accepted by one call. */\nexport const MAX_FACET_FIELDS = 20;\n\n/** Default number of distinct values returned for each facet field. */\nexport const DEFAULT_FACET_LIMIT = DEFAULT_LIST_LIMIT;\n\n/** Hard ceiling for distinct values returned for one facet field. */\nexport const MAX_FACET_LIMIT = MAX_LIST_LIMIT;\n\n/**\n * Validate an optional collection-level list bound (#2367).\n *\n * @returns the bound, or `undefined` when the option was not supplied\n * @throws {QueryBoundsError} when the option is present but not a positive\n * integer — a `maxListLimit` of `0` or `-1` would silently make every read on\n * the collection return nothing\n */\nfunction assertOptionalListBound(\n value: number | undefined,\n optionName: string,\n): number | undefined {\n if (value === undefined) return undefined;\n if (!Number.isSafeInteger(value) || value < 1) {\n throw new QueryBoundsError(\n `Invalid ${optionName}: expected a positive integer, got ${String(value)}.`,\n 'INVALID_QUERY_BOUNDS',\n { optionName, value },\n );\n }\n return value;\n}\n\n/**\n * Validate a per-query `limit`/`offset` before it is bound (#2367).\n *\n * `0` is allowed — `LIMIT 0` and `OFFSET 0` are both meaningful — but `NaN`,\n * `Infinity`, negatives and fractions are not: they used to be bound verbatim\n * and surface as a driver-level 500 for what is a caller error.\n *\n * @returns the bound, or `undefined` when the caller supplied none\n * @throws {QueryBoundsError} when the value is not a non-negative integer\n */\nfunction assertQueryBound(\n value: number | undefined,\n parameterName: string,\n): number | undefined {\n if (value === undefined || value === null) return undefined;\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new QueryBoundsError(\n `Invalid ${parameterName}: expected a non-negative integer, got ${String(value)}.`,\n 'INVALID_QUERY_BOUNDS',\n { parameterName, value },\n );\n }\n return value;\n}\n\n/**\n * Resolve _meta_type in WHERE clause from simple class name to qualified name (Issue #713)\n *\n * This helper function allows queries like { _meta_type: 'Image' } to work correctly\n * when the database stores qualified names like '@happyvertical/smrt-assets:Image'.\n *\n * @param where - The original WHERE clause object\n * @returns Modified WHERE clause with qualified _meta_type, or original if no resolution needed\n */\nfunction resolveMetaTypeInWhere<T extends Record<string, unknown>>(\n where: T | T[][] | undefined,\n): T | T[][] | undefined {\n if (Array.isArray(where)) {\n return where.map((group) =>\n group.map((condition) => resolveMetaTypeInWhere(condition) as T),\n );\n }\n if (!where?._meta_type || typeof where._meta_type !== 'string') {\n return where;\n }\n\n const metaTypeValue = where._meta_type as string;\n\n // Only resolve if it's a simple class name (no ':' means not qualified)\n if (metaTypeValue.includes(':')) {\n return where;\n }\n\n const registeredClass = ObjectRegistry.getClass(metaTypeValue);\n if (registeredClass?.qualifiedName) {\n return {\n ...where,\n _meta_type: registeredClass.qualifiedName,\n };\n }\n\n return where;\n}\n\n/** Add an AND predicate without collapsing a caller's DNF OR branches. */\nfunction andWhereCondition<T extends Record<string, unknown>>(\n where: T | T[][] | undefined,\n condition: T,\n): T | T[][] {\n if (Array.isArray(where)) {\n return where.map((group) => [condition, ...group]);\n }\n // The collection-owned predicate is an enforcement boundary (for example,\n // an STI child collection's discriminator), so it must win when a caller\n // supplies the same key.\n return { ...(where ?? {}), ...condition } as T;\n}\n\n/**\n * Field names that carry a stored embedding vector for a class (Issue #2281)\n *\n * `SmrtObject.generateEmbeddings()` writes one vector per configured field and,\n * when `combinedField` is set, an extra vector under `combinedField.name`. Any\n * of those names is a legitimate target for the collection's semantic-search\n * methods, so validation and error messages should describe the whole set.\n *\n * @param config - Resolved embedding configuration for the class\n * @returns Configured field names, plus the combined field name when distinct\n */\nfunction getSearchableEmbeddingFields(\n config: Pick<ClassEmbeddingConfig, 'fields' | 'combinedField'>,\n): string[] {\n const combinedName = config.combinedField?.name;\n if (!combinedName || config.fields.includes(combinedName)) {\n return config.fields;\n }\n return [...config.fields, combinedName];\n}\n\n/**\n * Keys from SmrtObject and SmrtClass that should not appear as user-facing\n * create/update input fields. These are framework-internal properties.\n */\ntype SmrtInternalKeys =\n | keyof SmrtClass\n | '_tableName'\n | '_loadedRelationships'\n | '_id'\n | '_slug'\n | '_context'\n | '_ai'\n | '_fs'\n | '_db'\n | '_className'\n | 'options'\n | '_skipLoad'\n | '_skipAutoEmbeddings'\n | '_extractingFields'\n | 'initialize'\n | 'save'\n | 'delete'\n | 'loadFromId'\n | 'loadFromSlug'\n | 'is'\n | 'do'\n | 'toJSON'\n | 'transformJSON';\n\n/**\n * Input type for `collection.create()`. Constrains input to valid model fields\n * while preserving backwards compatibility via an index signature escape hatch.\n *\n * Provides IDE autocompletion for known fields from the model class while still\n * accepting arbitrary keys for dynamic usage patterns (e.g., STI meta fields).\n *\n * @example\n * ```typescript\n * // IDE will suggest: name, price, quantity, categoryId, etc.\n * await productCollection.create({\n * name: 'Widget',\n * price: 9.99,\n * _meta_type: 'SpecialProduct', // STI discriminator\n * });\n * ```\n */\nexport type SmrtCreateInput<T extends SmrtObject> = Partial<\n Omit<\n {\n [K in keyof T as T[K] extends (...args: never[]) => unknown\n ? never\n : K]: T[K];\n },\n SmrtInternalKeys\n >\n> & {\n /** STI discriminator for polymorphic creation */\n _meta_type?: string;\n /** Skip database loading (framework internal) */\n _skipLoad?: boolean;\n /** Skip save-time embedding auto-generation (framework internal) */\n _skipAutoEmbeddings?: boolean;\n /**\n * Persist via a plain INSERT instead of the natural-key upsert: a collision\n * on the primary key or any unique constraint raises a typed error rather\n * than dedup-updating an existing row in place. For write paths where row\n * identity is an explicit client-supplied `id` (sync-apply, #1759).\n */\n _insertOnly?: boolean;\n /** Allow arbitrary additional fields for dynamic usage */\n [key: string]: unknown;\n};\n\n/**\n * Basic WHERE clause type for point lookups and AND-only collection reads.\n *\n * Uses `Record<string, unknown>` because WHERE keys often include operator\n * suffixes (e.g., `'price >'`, `'name like'`, `'status in'`) which can't be\n * expressed as mapped types from model properties. Runtime validation in\n * `convertWhereKeys()` handles field and operator checking.\n */\nexport type SmrtWhereClause<T extends SmrtObject> = Record<string, unknown>;\n\n/**\n * Bounded disjunctive-normal-form WHERE clause for collection list/count/facet\n * reads. Inner arrays are ANDed and outer arrays are ORed by the SQL adapter.\n * Keeping this at the collection boundary preserves field validation, STI, and\n * tenant interception for callers that need an allowlisted `any` filter.\n */\nexport type SmrtListWhereClause<T extends SmrtObject> =\n | SmrtWhereClause<T>\n | SmrtWhereClause<T>[][];\n\ntype SmrtModelData<T extends SmrtObject> = Omit<\n {\n [K in keyof T as T[K] extends (...args: never[]) => unknown\n ? never\n : K]: T[K];\n },\n SmrtInternalKeys\n>;\n\n/**\n * Logical field names accepted by `collection.list({ select })`.\n *\n * `select` is intentionally a column-backed projection primitive: callers pass\n * SMRT field names such as `parentId`, and the collection maps them to database\n * columns such as `parent_id` while returning rows keyed by the original SMRT\n * field names.\n */\nexport type SmrtSelectField<T extends SmrtObject> =\n | Extract<keyof SmrtModelData<T>, string>\n | 'id'\n | 'slug'\n | 'context'\n | 'created_at'\n | 'updated_at'\n | '_meta_type';\n\ntype SmrtCoreSelectedFieldData = {\n id: string | null | undefined;\n slug: string | null | undefined;\n context: string;\n created_at: Date | null | undefined;\n updated_at: Date | null | undefined;\n _meta_type: string;\n};\n\n/**\n * Plain row shape returned by `collection.list({ select })`.\n */\nexport type SmrtSelectedRow<\n T extends SmrtObject,\n Select extends readonly SmrtSelectField<T>[],\n> = {\n [Field in Select[number]]: Field extends keyof SmrtCoreSelectedFieldData\n ? SmrtCoreSelectedFieldData[Field]\n : Field extends keyof SmrtModelData<T>\n ? SmrtModelData<T>[Field]\n : unknown;\n};\n\n/** A field requested by {@link SmrtCollection.facets}. */\nexport interface SmrtFacetRequest<T extends SmrtObject> {\n /** A column-backed SMRT field name (for example, `status`). */\n field: SmrtSelectField<T>;\n /**\n * Maximum number of values to return. Omit to use the collection's\n * `defaultListLimit` or 50, subject to `maxListLimit` and the hard maximum\n * facet limit.\n */\n limit?: number;\n}\n\n/** One distinct facet value and the number of matching rows containing it. */\nexport interface SmrtFacetValue {\n value: unknown;\n count: number;\n}\n\n/** Database-backed values returned for one requested facet field. */\nexport interface SmrtFacetResult {\n field: string;\n values: SmrtFacetValue[];\n}\n\n/** Options for a database-backed facet query. */\nexport interface SmrtFacetOptions<T extends SmrtObject> {\n /**\n * One or more fields (at most {@link MAX_FACET_FIELDS}). Each field may\n * optionally carry its own value limit.\n */\n fields: readonly (SmrtSelectField<T> | SmrtFacetRequest<T>)[];\n /** The same SMRT WHERE syntax accepted by {@link SmrtCollection.list}. */\n where?: SmrtListWhereClause<T>;\n}\n\n/** Exact unfiltered and filtered row counts for a list scope. */\nexport interface SmrtCollectionCounts {\n total: number;\n filtered: number;\n}\n\nexport interface SmrtListOptions<ModelType extends SmrtObject> {\n where?: SmrtListWhereClause<ModelType>;\n offset?: number;\n limit?: number;\n orderBy?: string | string[];\n /**\n * Column-backed field projection. When present, `list()` returns plain rows\n * instead of hydrated model instances.\n */\n select?: readonly SmrtSelectField<ModelType>[];\n /**\n * Relationships to eagerly load. Only supported for hydrated list() calls.\n */\n include?: string[];\n /**\n * Opt-in read-through cache for this call (issue #1498).\n */\n cache?: CollectionCacheConfig | false;\n}\n\n/**\n * Declarative latest-row configuration for a declared `@oneToMany` relation.\n *\n * `orderBy` determines which related row wins for each parent. `sortBy`, when\n * supplied, orders parent rows by a field on that winning row before the\n * parent's own `limit`/`offset` are applied. Related rows are returned as\n * plain selected data rather than hydrated objects so this remains a bounded\n * read primitive.\n */\nexport interface SmrtLatestRelatedOptions {\n /** The `@oneToMany` field on the parent collection's item class. */\n relation: string;\n /** Related field(s) used to select the latest row, e.g. `createdAt DESC`. */\n orderBy: string | string[];\n /** Column-backed related fields returned in `latestRelated`. Defaults to the declared related primary key. */\n select?: readonly string[];\n /** Related field(s) used to order parent rows by the selected latest row. */\n sortBy?: string | string[];\n}\n\n/** Options accepted by {@link SmrtCollection.listWithLatestRelated}. */\nexport type SmrtLatestRelatedListOptions<ModelType extends SmrtObject> = Omit<\n SmrtListOptions<ModelType>,\n 'select' | 'include' | 'cache'\n> & {\n latestRelated: SmrtLatestRelatedOptions;\n};\n\n/** A hydrated parent paired with its selected latest related row, if present. */\nexport interface SmrtLatestRelatedRow<ModelType extends SmrtObject> {\n parent: ModelType;\n latestRelated: Record<string, unknown> | null;\n}\n\n/**\n * Minimal runtime shape of a field definition as consumed by the collection's\n * query/hydration helpers (`getFieldsSync()`, `convertWhereKeys()`,\n * `formatDataJs()`). These come from `fieldsFromClass()` /\n * `ObjectRegistry.getFields()` and only a few members are read here: `type`\n * (for `formatDataJs` value coercion) and the field-level access markers. The\n * index signature keeps the bag open for the other registry-attached members\n * without forcing `any`.\n */\ninterface CollectionFieldDefinition {\n type?: string;\n primaryKey?: boolean;\n transient?: boolean;\n sensitive?: boolean;\n readPermission?: string;\n _meta?: {\n sensitive?: boolean;\n readPermission?: string;\n transient?: boolean;\n primaryKey?: boolean;\n __smrtSystemField?: boolean;\n } & Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Configuration options for SmrtCollection\n */\nexport interface SmrtCollectionOptions extends SmrtClassOptions {\n /**\n * Page size `list()` applies when the caller passes no `limit` (#2367).\n *\n * Unset by default — `list()` is the framework's bulk-read primitive and its\n * internal callers expect every matching row, so an implicit default would\n * truncate correct queries. Set this on collections whose reads are always\n * paged.\n */\n defaultListLimit?: number;\n\n /**\n * Ceiling `list()` clamps every `limit` down to (#2367).\n *\n * Unset by default. The generated REST/SvelteKit/MCP surfaces enforce\n * `MAX_LIST_LIMIT` on their own untrusted input regardless of this option;\n * set it to extend the same ceiling to direct programmatic reads.\n */\n maxListLimit?: number;\n}\n\n// S4 #1579: the constructor `options` and static `create(options)` params are\n// left as `any` deliberately. This type is satisfied by every concrete model\n// class (`static readonly _itemClass = Product`), whose constructor/`create`\n// option bag is contravariant — narrowing to `SmrtCreateInput<ModelType>` or\n// `unknown` rejects those assignments. Mirrors the `SmrtObjectConstructor`\n// escape hatch in registry/types.\nexport type SmrtCollectionItemClass<ModelType extends SmrtObject> = (new (\n // biome-ignore lint/suspicious/noExplicitAny: contravariant constructor option bag — narrowing to `SmrtCreateInput<ModelType>`/`unknown` rejects every concrete `static _itemClass = Product` assignment. Mirrors `SmrtObjectConstructor`. S4 #1579.\n options: any,\n) => ModelType) & {\n // biome-ignore lint/suspicious/noExplicitAny: contravariant `create()` option bag — same variance constraint as the constructor above. S4 #1579.\n create(options: any): ModelType | Promise<ModelType>;\n};\n\n/**\n * Typed CRUD collection for a specific `SmrtObject` subclass.\n *\n * Each concrete collection pairs with exactly one model class via the required\n * `static readonly _itemClass` property. Use the static `create()` factory —\n * not `new` — to get a fully initialized, ready-to-query instance.\n *\n * Key capabilities:\n * - **Query**: `list()`, `get()`, `count()`, `query()` (raw SQL), `listByIds()`\n * - **Mutation**: `create()`, `getOrUpsert()`, `delete()`\n * - **Eager loading**: pass `include: ['fieldName']` to `list()` to avoid N+1 queries\n * - **STI support**: automatically filters by `_meta_type` for child collections;\n * polymorphically hydrates the correct subclass when listing/getting\n * - **Interceptors**: all read/write paths run `GlobalInterceptors` hooks\n * (used by multi-tenancy, audit logging, etc.)\n *\n * @typeParam ModelType - The `SmrtObject` subclass managed by this collection\n *\n * @example\n * ```typescript\n * @smrt()\n * class Product extends SmrtObject {\n * name: string = '';\n * price: number = 0.0;\n * }\n *\n * @smrt()\n * class Products extends SmrtCollection<Product> {\n * static readonly _itemClass = Product;\n * }\n *\n * const products = await Products.create({ db: myDb });\n * const widget = await products.create({ name: 'Widget', price: 9.99 });\n * const all = await products.list({ where: { 'price >': 5 }, orderBy: 'price ASC' });\n * ```\n */\nexport class SmrtCollection<ModelType extends SmrtObject> extends SmrtClass {\n /**\n * Cached fields for sync access during queries.\n * Populated during create() to avoid async getFields() calls on every query.\n * @private\n */\n private _cachedFields: Record<string, CollectionFieldDefinition> | null =\n null;\n\n /**\n * Opt-in list bounds (#2367). See {@link SmrtCollectionOptions.defaultListLimit}\n * and {@link SmrtCollectionOptions.maxListLimit}.\n * @private\n */\n private _defaultListLimit: number | undefined;\n private _maxListLimit: number | undefined;\n\n private getRegisteredItemClass() {\n return (\n ObjectRegistry.getClassByConstructor(\n this._itemClass as unknown as SmrtObjectConstructor,\n ) || ObjectRegistry.getClass(this._itemClass.name)\n );\n }\n\n private getResolvedItemClassName(): string {\n return this.getRegisteredItemClass()?.name || this._itemClass.name;\n }\n\n private getResolvedItemQualifiedName(): string {\n const registered = this.getRegisteredItemClass();\n return (\n registered?.qualifiedName || registered?.name || this._itemClass.name\n );\n }\n\n /**\n * Resolve the identifier whitelist and the sensitive-column blocklist that\n * every caller-supplied identifier on this collection is checked against.\n *\n * Extracted from `convertWhereKeys()` so `orderBy` validation enforces the\n * *same* sets (#2367). `where` (#1540) and `select` (#1902) both rejected\n * sensitive columns while `orderBy` accepted any identifier, which made\n * `?orderBy=api_secret&limit=1` an ordering oracle over a column the same\n * request is forbidden to filter on or project.\n *\n * @returns the snake_case identifiers this collection accepts, the sensitive\n * names it must refuse, and whether whitelist enforcement applies at all —\n * classes with no registered fields (manifest-less inline test classes,\n * #869) have nothing to validate against and fall through to SQL.\n * @private\n */\n private collectQueryableFieldNames(\n fields: Record<string, CollectionFieldDefinition>,\n ): {\n validFieldNames: Set<string>;\n sensitiveFieldNames: Set<string>;\n readPermissionFieldNames: Set<string>;\n skipFieldValidation: boolean;\n } {\n // `toSnakeCase()` — the SAME function `SchemaGenerator` names columns with\n // (`schema/generator.ts`) — so this set holds real column names. Note it\n // STRIPS a leading underscore, while `toDbColumnName()` preserves one; the\n // two disagree for a declared `_rank` field, whose actual column is `rank`.\n // `orderBy` reconciles that at the term (see `buildOrderBySql`).\n const validFieldNames = new Set(\n Object.keys(fields).map((f) => toSnakeCase(f)),\n );\n\n // Security (#1540): collect snake_case names of `@field({ sensitive: true })`\n // columns so they can't be used as `where` filter keys. Filtering on a\n // secret column turns the generated REST/MCP `where` surface into a value\n // oracle (e.g. `apiSecret[like]=sk-%`), exfiltrating the secret one\n // character at a time even though it's excluded from serialization.\n const sensitiveFieldNames = this.collectSensitiveFieldNames(fields);\n\n // Security (#2367): the same collection for `@field({ readPermission })`\n // columns. These are stripped from `toPublicJSON()` for callers without the\n // permission and are refused outright by `select`, so ordering by one is the\n // same oracle in a different door. Kept in its own set because `where`\n // (#1540) deliberately does not consult it and this must not change that.\n const readPermissionFieldNames =\n this.collectReadPermissionFieldNames(fields);\n\n // Add standard SMRT fields that are always valid\n validFieldNames.add('id');\n validFieldNames.add('slug');\n validFieldNames.add('context');\n validFieldNames.add('created_at');\n validFieldNames.add('updated_at');\n\n // Add STI discriminator field for polymorphic queries.\n // R5-canon: use the qualified item name as the lookup key so a\n // same-simple-name class in another package can't yield the wrong\n // tableStrategy.\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n if (tableStrategy === 'sti') {\n // Add both with and without leading underscore (toSnakeCase strips leading _)\n validFieldNames.add('_meta_type');\n validFieldNames.add('meta_type');\n validFieldNames.add('_meta_data');\n validFieldNames.add('meta_data');\n\n // Issue #869: For STI classes, also include fields from all ancestor classes\n // This ensures child class collections can filter by parent class fields.\n //\n // inheritanceChain entries are qualified names (when the\n // registration has one). Compare self against the qualified form;\n // the framework-base sentinels stay as simple names since they're\n // never registered. The simple-name `itemClassName` is also checked\n // as a defensive fall-through for unqualified registrations.\n const inheritanceChain =\n ObjectRegistry.getInheritanceChain(itemQualifiedName);\n for (const ancestorName of inheritanceChain) {\n if (\n ancestorName === 'SmrtObject' ||\n ancestorName === 'SmrtClass' ||\n ancestorName === itemQualifiedName ||\n ancestorName === itemClassName\n ) {\n continue; // Skip framework base classes and self (already included)\n }\n const ancestorFields = ObjectRegistry.getFields(ancestorName);\n for (const fieldName of ancestorFields.keys()) {\n validFieldNames.add(toSnakeCase(fieldName));\n }\n this.collectSensitiveFieldNames(ancestorFields, sensitiveFieldNames);\n this.collectReadPermissionFieldNames(\n ancestorFields,\n readPermissionFieldNames,\n );\n }\n\n // Security (#1540): a base collection serializes (and so must protect)\n // STI descendant fields too — a child's `@meta` sensitive field lives in\n // `_meta_data` and would otherwise be probe-able via `_meta_data.<prop>`\n // from the base collection. Mirror toJSON()/getSensitiveFieldNames().\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n if (stiBase) {\n for (const descendant of ObjectRegistry.getDescendants(stiBase)) {\n const descendantFields = ObjectRegistry.getFields(descendant);\n this.collectSensitiveFieldNames(\n descendantFields,\n sensitiveFieldNames,\n );\n this.collectReadPermissionFieldNames(\n descendantFields,\n readPermissionFieldNames,\n );\n }\n }\n }\n\n // Issue #869: If no fields are registered (no manifest for test classes),\n // skip field validation. Invalid fields will fail at SQL level instead.\n // This commonly happens for inline test classes decorated with @smrt().\n return {\n readPermissionFieldNames,\n skipFieldValidation: Object.keys(fields).length === 0,\n sensitiveFieldNames,\n validFieldNames,\n };\n }\n\n /**\n * Build the `ORDER BY` clause for `list()`, validating every term against the\n * same whitelist and sensitive-column blocklist as `where` and `select`\n * (#2367).\n *\n * `orderBy` is interpolated UNPARAMETERIZED into the identifier position, and\n * it arrives from the generated REST/MCP/SvelteKit surfaces as caller input.\n * The identifier regex here has always blocked injection; what it did not\n * block was ordering by a column the caller may not read. Sorting is a\n * comparison, and a comparison against a secret is an oracle: repeatedly\n * requesting `?orderBy=api_secret&limit=1` (with a shrinking `where`) walks\n * the secret's value ordering without ever serializing the column.\n *\n * @param orderBy - one `'<field> [ASC|DESC]'` term or an array of them\n * @param fields - the collection's cached field definitions\n * @returns the `' ORDER BY ...'` fragment, or `''` when no ordering was asked\n * for\n * @throws {QueryOrderByError} on a malformed term, an unknown column, or a\n * sensitive column — all rendered as a 400 by the generated surfaces\n * @private\n */\n private buildOrderBySql(\n orderBy: string | string[] | undefined,\n fields: Record<string, CollectionFieldDefinition>,\n ): string {\n if (!orderBy) return '';\n\n const orderByItems = Array.isArray(orderBy) ? orderBy : [orderBy];\n if (orderByItems.length === 0) return '';\n\n const itemClassName = this.getResolvedItemClassName();\n const {\n readPermissionFieldNames,\n sensitiveFieldNames,\n skipFieldValidation,\n validFieldNames,\n } = this.collectQueryableFieldNames(fields);\n\n // Column → definition, so a term can be checked for column-backing after it\n // passes the name whitelist. Keyed by `toSnakeCase` because that is what\n // `SchemaGenerator` names the column with — a declared `_rank` field lives\n // in a column called `rank`, not `_rank`.\n const definitionByColumn = new Map<string, CollectionFieldDefinition>();\n for (const [fieldName, fieldDef] of Object.entries(fields)) {\n definitionByColumn.set(toSnakeCase(fieldName), fieldDef);\n }\n\n // `id`/`slug`/`context` are added to the whitelist unconditionally, but the\n // schema generator omits them for a class that declares its own primary key.\n // Without this, `?orderBy=id` on such a model passes the whitelist, matches\n // no definition, and reaches the driver as `no such column: id` — a 500.\n // Same derivation `resolveProjectionSelect` uses for `select`.\n const customPrimaryKey = this.hasCustomPrimaryKey(fields);\n const registeredFields = ObjectRegistry.getFields(\n this.getResolvedItemQualifiedName(),\n );\n const explicitFieldNames = new Set(\n (registeredFields.size > 0\n ? registeredFields\n : ObjectRegistry.getFields(itemClassName)\n ).keys(),\n );\n\n const terms = orderByItems.map((item) => {\n const [field, direction = 'ASC'] = String(item).trim().split(/\\s+/);\n\n // Validate field name\n if (!/^[a-zA-Z0-9_]+$/.test(field)) {\n throw new QueryOrderByError(\n `Invalid field name for ordering: ${field}`,\n `Invalid field name for ordering: ${field}`,\n 'INVALID_ORDER_BY',\n { field },\n );\n }\n\n // Validate direction\n const normalizedDirection = direction.toUpperCase();\n if (normalizedDirection !== 'ASC' && normalizedDirection !== 'DESC') {\n throw new QueryOrderByError(\n `Invalid sort direction: ${direction}. Must be ASC or DESC.`,\n `Invalid sort direction: ${direction}. Must be ASC or DESC.`,\n 'INVALID_ORDER_BY',\n { direction },\n );\n }\n\n // Resolve the term to a real column. The two normalizations disagree on a\n // leading underscore, and each is right for a different case:\n //\n // - a DECLARED field is named by `toSnakeCase`, which strips it, so\n // `_rank` lives in a column called `rank`;\n // - the STI framework columns `_meta_type`/`_meta_data` really do carry\n // the underscore and are never declared fields.\n //\n // Prefer the declared column when one exists, otherwise keep the\n // underscore-preserving form so `_metaType` still reaches `_meta_type`.\n // Emitting the resolved name (rather than one normalization applied\n // blindly) is what keeps a term that passes the whitelist from naming a\n // column that does not exist.\n const strippedName = toSnakeCase(field);\n const columnName = definitionByColumn.has(strippedName)\n ? strippedName\n : this.toDbColumnName(field);\n\n // Security (#2367): refuse sensitive columns before the whitelist check,\n // so the rejection reason for a secret is never \"no such field\" (which\n // would itself confirm the column exists elsewhere).\n if (\n sensitiveFieldNames.has(columnName) ||\n sensitiveFieldNames.has(field)\n ) {\n throw new QueryOrderByError(\n `Invalid orderBy field: '${field}'. Ordering by sensitive fields is not allowed.`,\n `Invalid orderBy field: '${field}'. Ordering by sensitive fields is not allowed.`,\n 'INVALID_ORDER_BY_SENSITIVE',\n { field },\n );\n }\n\n // Security (#2367): same for `@field({ readPermission })` columns.\n // `toPublicJSON()` redacts them per caller and `select` refuses them, so\n // an ordering over one is the same oracle through a different door.\n if (\n readPermissionFieldNames.has(columnName) ||\n readPermissionFieldNames.has(field)\n ) {\n throw new QueryOrderByError(\n `Invalid orderBy field: '${field}'. Ordering by permission-gated fields is not allowed.`,\n `Invalid orderBy field: '${field}'. Ordering by permission-gated fields is not allowed.`,\n 'INVALID_ORDER_BY_RESTRICTED',\n { field },\n );\n }\n\n // Whitelist, mirroring `where`. Skipped for manifest-less inline test\n // classes with no registered fields (#869), exactly as `where` does.\n if (!skipFieldValidation && !validFieldNames.has(columnName)) {\n throw new QueryOrderByError(\n `Invalid orderBy field: '${field}'. Field does not exist on ${itemClassName}. ` +\n `Valid fields: ${Array.from(validFieldNames).sort().join(', ')}`,\n // The public form omits the field list: the column list is schema\n // detail an unauthenticated caller has no need for.\n `Invalid orderBy field: '${field}'.`,\n 'INVALID_ORDER_BY_UNKNOWN_FIELD',\n { field, itemClassName },\n );\n }\n\n // Being a registered field is not the same as being a column (#2367).\n // `oneToMany`/`manyToMany` are relationship-only, `meta` lives inside the\n // STI `_meta_data` blob, and `transient` is never persisted — the schema\n // generator emits no column for any of them. Ordering by one reached the\n // driver as `no such column`, which carries no 4xx status and so surfaced\n // as a 500 on a public endpoint. `select` already rejects the same set.\n const fieldDef = definitionByColumn.get(columnName);\n const fieldType = fieldDef?.type;\n const isTransient =\n fieldDef?.transient === true || fieldDef?._meta?.transient === true;\n if (\n fieldType === 'meta' ||\n fieldType === 'oneToMany' ||\n fieldType === 'manyToMany' ||\n isTransient ||\n this.isOmittedCustomPrimaryKeySystemField(\n columnName,\n customPrimaryKey,\n explicitFieldNames,\n )\n ) {\n throw new QueryOrderByError(\n `Invalid orderBy field: '${field}'. Field is not column-backed on ${itemClassName}.`,\n `Invalid orderBy field: '${field}'. Field is not column-backed.`,\n 'INVALID_ORDER_BY_NOT_COLUMN_BACKED',\n { field, itemClassName },\n );\n }\n\n return `${columnName} ${normalizedDirection}`;\n });\n\n return ` ORDER BY ${terms.join(', ')}`;\n }\n\n /**\n * Apply this collection's configured list bounds (#2367).\n *\n * Both bounds are opt-in (`defaultListLimit` / `maxListLimit` collection\n * options) and default to unset. `list()` is the framework's bulk-read\n * primitive — relationship loaders, junction hydration and `listByIds()` all\n * go through it expecting every matching row — so a framework-wide implicit\n * default would silently truncate correct queries instead of bounding an\n * attack surface. The generated surfaces, where untrusted input actually\n * arrives, apply {@link DEFAULT_LIST_LIMIT}/{@link MAX_LIST_LIMIT}\n * unconditionally; an application that wants the same ceiling on its own\n * programmatic reads sets `maxListLimit` when constructing the collection.\n *\n * @private\n */\n private applyListBounds(limit: number | undefined): number | undefined {\n const effective = limit ?? this._defaultListLimit;\n if (effective === undefined) return undefined;\n return this._maxListLimit === undefined\n ? effective\n : Math.min(effective, this._maxListLimit);\n }\n\n /**\n * Convert WHERE clause field names from camelCase to snake_case while preserving operators.\n * Validates operators and field names to prevent SQL injection and invalid queries.\n *\n * Uses cached fields for sync access (issue #663) to avoid async overhead on every query.\n *\n * @param where - WHERE clause object with camelCase field names\n * @returns WHERE clause object with snake_case field names\n * @private\n *\n * @example\n * ```typescript\n * // Input: { 'typeId': 'foo', 'categoryId >': 100 }\n * // Output: { 'type_id': 'foo', 'category_id >': 100 }\n * ```\n */\n private convertWhereKeys(\n where: SmrtListWhereClause<ModelType>,\n ): SmrtListWhereClause<ModelType> {\n if (Array.isArray(where)) {\n if (\n where.length === 0 ||\n where.some((group) => !Array.isArray(group) || group.length === 0)\n ) {\n throw new Error(\n 'Invalid DNF where clause: each OR branch must contain at least one condition.',\n );\n }\n return where.map((group) =>\n group.map((condition) => this.convertWhereKeysRecord(condition)),\n );\n }\n return this.convertWhereKeysRecord(where);\n }\n\n private convertWhereKeysRecord(\n where: Record<string, unknown>,\n ): Record<string, unknown> {\n // Whitelist of allowed SQL operators.\n //\n // This list is a contract, not a wish list (#2276): every entry must be an\n // operator `@happyvertical/sql`'s `parseConditionKey` recognises, because a\n // key this method accepts is passed straight to `buildWhere`. An operator\n // accepted here but unknown there is not \"unimplemented\" — `buildWhere`\n // fails to split it off the key, tries to read `\"<field> <operator>\"` as one\n // identifier, and throws `Invalid SQL identifier` from inside the query\n // builder. The caller gets a SQL-layer error naming SQL-layer concepts for a\n // query the API told them was valid. Keep this list in lockstep with\n // `VALID_OPERATORS` in `@happyvertical/sql`'s `shared/utils.ts`; the\n // \"executes every accepted operator\" test in\n // `src/__tests__/issue-2276-where-contract.test.ts` is what enforces it.\n const VALID_OPERATORS = [\n '=',\n '>',\n '<',\n '>=',\n '<=',\n '!=',\n 'in',\n 'not in',\n 'like',\n ];\n\n // Operators callers reach for that this layer cannot honour, mapped to the\n // supported way to express the same intent. Being listed here only improves\n // the rejection message — these are rejected exactly like any other unknown\n // operator. A Map, not an object literal, because the lookup key is\n // caller-supplied: `{ 'name toString': 'x' }` would otherwise find\n // `Object.prototype.toString` and splice a function into the error text.\n const UNSUPPORTED_OPERATOR_HINTS = new Map<string, string>([\n // `contains` shipped in this whitelist but never existed in the SQL layer,\n // so it has always failed at execution (#2276). It is not re-implemented\n // here as `like '%value%'` because that would be a different operator\n // wearing its name: `%` and `_` in the value would silently become\n // wildcards (escaping them needs a `LIKE ... ESCAPE` clause `buildWhere`\n // cannot emit), and `LIKE` is case-insensitive on SQLite but\n // case-sensitive on PostgreSQL, so the same call would mean two things.\n // Restoring it belongs in the SQL layer, where the dialect is known —\n // tracked in happyvertical/sdk#1192.\n [\n 'contains',\n \"Use 'like' with explicit wildcards instead, e.g. { 'name like': '%term%' }.\",\n ],\n ]);\n\n // Get schema fields for validation (sync from cache)\n const fields = this.getFieldsSync();\n const itemClassName = this.getResolvedItemClassName();\n const { sensitiveFieldNames, skipFieldValidation, validFieldNames } =\n this.collectQueryableFieldNames(fields);\n\n const converted: Record<string, unknown> = {};\n\n // Check for prototype pollution attempts using own properties only\n // __proto__ is a special property that doesn't show up in Object.entries()\n // but can still be checked with hasOwnProperty\n if (\n Object.hasOwn(where, '__proto__') ||\n Object.hasOwn(where, 'constructor') ||\n Object.hasOwn(where, 'prototype')\n ) {\n throw new Error(\n `Invalid WHERE clause: Prototype pollution attempts are not allowed. ` +\n `Detected dangerous properties in WHERE clause.`,\n );\n }\n\n for (const [key, value] of Object.entries(where)) {\n // Split field name and operator (e.g., \"typeId >\" → [\"typeId\", \">\"])\n const parts = key.trim().split(/\\s+/);\n const fieldName = parts[0];\n const operator = parts.slice(1).join(' ') || '=';\n\n // Check for dangerous prototype pollution field names\n if (\n fieldName === '__proto__' ||\n fieldName === 'constructor' ||\n fieldName === 'prototype' ||\n fieldName.includes('__proto__') ||\n fieldName.includes('constructor') ||\n fieldName.includes('prototype')\n ) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Prototype pollution attempts are not allowed.`,\n );\n }\n\n // Split a dot-notation JSON path (e.g. 'metadata.userId') into its base\n // column and path suffix. The suffix is rejected further down (#2276), but\n // it is parsed and reassembled first so the guards between here and there\n // — the #1379 identifier check, the #1540 sensitive-`@meta`-path check,\n // and the schema field whitelist — still see the whole key rather than a\n // truncated column name.\n const dotIndex = fieldName.indexOf('.');\n const baseFieldName =\n dotIndex >= 0 ? fieldName.substring(0, dotIndex) : fieldName;\n const jsonPath = dotIndex >= 0 ? fieldName.substring(dotIndex) : null;\n\n // Convert base field name to snake_case, preserving leading underscores\n const snakeBaseFieldName = baseFieldName.startsWith('_')\n ? `_${toSnakeCase(baseFieldName.slice(1))}`\n : toSnakeCase(baseFieldName);\n\n // Full snake_case field name with JSON path preserved\n const snakeFieldName = jsonPath\n ? `${snakeBaseFieldName}${jsonPath}`\n : snakeBaseFieldName;\n\n // Security (#1379): the field identifier — base column plus any\n // dot-notation JSON-path segments — is interpolated UNPARAMETERIZED into\n // the SQL field position by the downstream query builder. The manifest\n // whitelist below only validates the base column, and is skipped entirely\n // when a class has no registered fields (skipFieldValidation), so without\n // this guard a crafted key such as\n // `metadata.x))/**/UNION/**/SELECT/**/secret/**/FROM/**/users--`\n // (SQL comments substituting for blocked whitespace) injects arbitrary SQL\n // via the JSON-path suffix — remotely reachable through the generated\n // REST/MCP `where` surfaces and able to defeat the tenant filter. Enforce\n // that the whole identifier is a strict dot-separated identifier so no\n // parens/quotes/operators/whitespace can survive, regardless of whether\n // the manifest whitelist below runs.\n if (!/^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z0-9_]+)*$/.test(snakeFieldName)) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Field names must be identifiers (letters, digits, underscore) ` +\n `with optional dot-separated JSON-path segments.`,\n );\n }\n\n // Security (#1540): reject filters that target a sensitive column. This\n // runs before operator/field validation so the secret value-probing\n // oracle is closed even for otherwise-valid operators. We reject when the\n // base column itself is sensitive, OR when an STI `@meta` sensitive field\n // is probed via a `_meta_data.<prop>` JSON path (the prop keeps its\n // camelCase name). The JSON-path check is scoped to the `_meta_data`\n // column so legitimate filters on unrelated JSON fields (e.g.\n // `metadata.apiSecret` on a non-sensitive `metadata` column) aren't\n // falsely rejected.\n // Use the NORMALIZED column name so camelCase aliases (`_metaData`,\n // `metaData`) that also resolve to the `_meta_data` column are scoped in —\n // checking raw `baseFieldName` here let `_metaData.apiSecret` slip past\n // the segment check while still resolving to the meta column.\n const baseIsMetaData =\n snakeBaseFieldName === '_meta_data' ||\n snakeBaseFieldName === 'meta_data';\n const metaPathTargetsSensitive =\n baseIsMetaData &&\n !!jsonPath &&\n jsonPath\n .split('.')\n .filter(Boolean)\n .some(\n (segment) =>\n sensitiveFieldNames.has(segment) ||\n sensitiveFieldNames.has(toSnakeCase(segment)),\n );\n if (\n sensitiveFieldNames.has(snakeBaseFieldName) ||\n metaPathTargetsSensitive\n ) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Filtering on sensitive fields is not allowed.`,\n );\n }\n\n // Auto-detect IN operator when value is an array without explicit operator\n const effectiveOperator =\n operator === '=' && Array.isArray(value) ? 'in' : operator;\n\n // Validate operator\n if (!VALID_OPERATORS.includes(effectiveOperator)) {\n const hint = UNSUPPORTED_OPERATOR_HINTS.get(effectiveOperator);\n throw new Error(\n `Invalid WHERE clause operator: '${operator}'. ` +\n `Valid operators: ${VALID_OPERATORS.join(', ')}` +\n (hint ? `. ${hint}` : ''),\n );\n }\n\n // Validate field name exists in schema (validate base column name only)\n // Issue #869: Skip validation when no fields are registered (manifest-less test classes)\n if (!skipFieldValidation && !validFieldNames.has(snakeBaseFieldName)) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Field does not exist on ${itemClassName}. ` +\n `Valid fields: ${Array.from(validFieldNames).sort().join(', ')}`,\n );\n }\n\n // Reject dot-notation JSON paths (#2276).\n //\n // These validated but were never rewritten into a JSON extraction\n // expression, so `metadata.color` reached SQL verbatim, where it reads as\n // the qualified column reference `metadata.color` — a column `color` on a\n // table `metadata`. SQLite answers `no such column`, surfaced to the\n // caller as an opaque `DatabaseError: Failed to execute raw query` for a\n // query this method had just accepted.\n //\n // Rewriting them here would mean emitting dialect-specific extraction\n // (`json_extract(col, '$.path')` on SQLite, `col->>'path'` on\n // PostgreSQL/DuckDB) and resolving the comparison typing that differs\n // between them — `->>` yields text, so `metadata.count > 5` would compare\n // a string against a number on PostgreSQL while working on SQLite. That is\n // a feature with its own semantics to settle, not a repair of this\n // validator, so this rejects until it exists — tracked in #2282.\n //\n // Placed last among the field checks on purpose. Every guard above\n // describes a different problem with the same key, and each one's message\n // is more specific than this one: a sensitive `_meta_data.<prop>` path\n // must still report that it was rejected for being sensitive (#1540), and\n // a typo'd base column must still report that the column does not exist\n // rather than being told to \"filter on the '<typo>' column itself\". By\n // running here, the advice this message gives is only ever offered for a\n // column that actually exists — except under `skipFieldValidation`, where\n // the class has no registered fields and nothing knows which columns are\n // real (#869). There the base column named below is only the caller's own\n // word for it, exactly as it is for every other key on such a class.\n if (jsonPath) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Dot-notation JSON paths are not supported — the path is not ` +\n `rewritten into a JSON extraction expression, so it would reach SQL ` +\n `as a qualified column reference and fail at execution. ` +\n `Filter on the '${baseFieldName}' column itself, or filter the ` +\n `extracted values in application code.`,\n );\n }\n\n // Validate operator-specific value types\n if (\n (effectiveOperator === 'in' || effectiveOperator === 'not in') &&\n !Array.isArray(value)\n ) {\n throw new Error(\n `WHERE clause operator '${effectiveOperator}' requires an array value for field '${fieldName}', ` +\n `got ${typeof value}`,\n );\n }\n\n // Reject empty arrays for IN/NOT IN operators (generates invalid SQL)\n if (\n (effectiveOperator === 'in' || effectiveOperator === 'not in') &&\n Array.isArray(value) &&\n value.length === 0\n ) {\n throw new Error(\n `WHERE clause operator '${effectiveOperator}' requires a non-empty array for field '${fieldName}'. ` +\n `Use listByIds([]) for graceful empty array handling.`,\n );\n }\n\n if (effectiveOperator === 'like' && typeof value !== 'string') {\n throw new Error(\n `WHERE clause operator 'like' requires a string value for field '${fieldName}', ` +\n `got ${typeof value}`,\n );\n }\n\n // Reconstruct key with operator\n const newKey =\n effectiveOperator === '='\n ? snakeFieldName\n : `${snakeFieldName} ${effectiveOperator}`;\n\n converted[newKey] = value;\n }\n\n return converted;\n }\n\n private toDbColumnName(fieldName: string): string {\n return fieldName.startsWith('_')\n ? `_${toSnakeCase(fieldName.slice(1))}`\n : toSnakeCase(fieldName);\n }\n\n private assertSafeProjectionFieldName(fieldName: string): void {\n if (\n fieldName === '__proto__' ||\n fieldName === 'constructor' ||\n fieldName === 'prototype'\n ) {\n throw new Error(\n `Invalid select field: '${fieldName}'. Prototype pollution attempts are not allowed.`,\n );\n }\n\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(fieldName)) {\n throw new Error(\n `Invalid select field: '${fieldName}'. Field names must be identifiers (letters, digits, underscore).`,\n );\n }\n }\n\n private quoteProjectionIdentifier(identifier: string): string {\n this.assertSafeProjectionFieldName(identifier);\n return `\"${identifier.replace(/\"/g, '\"\"')}\"`;\n }\n\n private isSensitiveFieldDefinition(\n fieldDef: CollectionFieldDefinition | null | undefined,\n ): boolean {\n return fieldDef?.sensitive === true || fieldDef?._meta?.sensitive === true;\n }\n\n private getReadPermissionFieldDefinition(\n fieldDef: CollectionFieldDefinition | null | undefined,\n ): string | undefined {\n if (typeof fieldDef?.readPermission === 'string') {\n return fieldDef.readPermission;\n }\n if (typeof fieldDef?._meta?.readPermission === 'string') {\n return fieldDef._meta.readPermission;\n }\n return undefined;\n }\n\n private collectSensitiveFieldNames(\n fieldMap:\n | Record<string, CollectionFieldDefinition>\n | Map<string, CollectionFieldDefinition>,\n target = new Set<string>(),\n ): Set<string> {\n const entries =\n fieldMap instanceof Map ? fieldMap.entries() : Object.entries(fieldMap);\n\n for (const [fieldName, fieldDef] of entries) {\n if (this.isSensitiveFieldDefinition(fieldDef)) {\n // Store both the snake_case column form and the raw property name so\n // STI `@meta` fields probed via `_meta_data.<prop>` JSON paths (which\n // keep the camelCase key) are also rejected.\n target.add(toSnakeCase(fieldName));\n target.add(fieldName);\n }\n }\n\n return target;\n }\n\n /**\n * The `collectSensitiveFieldNames` sibling for `@field({ readPermission })`\n * columns (#2367).\n *\n * These are per-caller redacted by `toPublicJSON()` and refused outright by\n * `select`, so an ordering over one leaks comparisons about a value the\n * caller may not read. `list()` has no permission context, so — exactly as\n * `select` does — they are refused unconditionally rather than conditionally.\n */\n private collectReadPermissionFieldNames(\n fieldMap:\n | Record<string, CollectionFieldDefinition>\n | Map<string, CollectionFieldDefinition>,\n target = new Set<string>(),\n ): Set<string> {\n const entries =\n fieldMap instanceof Map ? fieldMap.entries() : Object.entries(fieldMap);\n\n for (const [fieldName, fieldDef] of entries) {\n if (this.getReadPermissionFieldDefinition(fieldDef) !== undefined) {\n target.add(toSnakeCase(fieldName));\n target.add(fieldName);\n }\n }\n\n return target;\n }\n\n private hasCustomPrimaryKey(\n fields: Record<string, CollectionFieldDefinition>,\n ): boolean {\n return Object.values(fields).some(\n (fieldDef) =>\n fieldDef.primaryKey === true || fieldDef._meta?.primaryKey === true,\n );\n }\n\n private resolvePrimaryKeyField(\n fields: Record<string, CollectionFieldDefinition>,\n label: string,\n ): { field: string; column: string } {\n const declared = Object.entries(fields)\n .filter(\n ([, fieldDef]) =>\n fieldDef.primaryKey === true || fieldDef._meta?.primaryKey === true,\n )\n .map(([field]) => field);\n if (declared.length > 1) {\n throw new Error(\n `${label} declares multiple primary-key fields (${declared.join(', ')}); listWithLatestRelated requires a single primary key.`,\n );\n }\n const field = declared[0] ?? 'id';\n return { field, column: this.toDbColumnName(field) };\n }\n\n private getDatabaseEngine(): ReturnType<typeof detectEngine> {\n const db = this.db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n client?: { constructor?: { name?: string }; connection?: unknown };\n exportTable?: unknown;\n };\n const clientName = db.client?.constructor?.name?.toLowerCase() ?? '';\n const isDuckDbConnection =\n clientName.includes('duckdb') ||\n (typeof db.exportTable === 'function' &&\n db.client !== undefined &&\n 'connection' in db.client);\n return detectEngine(\n db.url || db.config?.url || '',\n this.getDatabaseEngineHint() ||\n db.type ||\n db.config?.type ||\n (isDuckDbConnection ? 'duckdb' : undefined),\n );\n }\n\n private isOmittedCustomPrimaryKeySystemField(\n fieldName: string,\n hasCustomPrimaryKey: boolean,\n explicitFieldNames: ReadonlySet<string>,\n ): boolean {\n return (\n hasCustomPrimaryKey &&\n !explicitFieldNames.has(fieldName) &&\n (fieldName === 'id' || fieldName === 'slug' || fieldName === 'context')\n );\n }\n\n private resolveProjectionSelect(\n select: readonly string[],\n fields: Record<string, CollectionFieldDefinition>,\n isSTI: boolean,\n ): { sql: string; outputFields: string[] } {\n if (!Array.isArray(select)) {\n throw new Error(\n 'Invalid select option: expected an array of field names.',\n );\n }\n if (select.length === 0) {\n throw new Error(\n 'Invalid select option: at least one field name is required.',\n );\n }\n\n const customPrimaryKey = this.hasCustomPrimaryKey(fields);\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n const schema =\n ObjectRegistry.getSchema(itemQualifiedName) ??\n ObjectRegistry.getSchema(itemClassName);\n const schemaColumnNames = schema?.columns\n ? new Set(Object.keys(schema.columns))\n : undefined;\n const registeredFields = ObjectRegistry.getFields(itemQualifiedName);\n const explicitFields =\n registeredFields.size > 0\n ? registeredFields\n : ObjectRegistry.getFields(itemClassName);\n const explicitFieldNames = new Set(explicitFields.keys());\n\n const validFieldNames = new Set<string>();\n for (const [fieldName, fieldDef] of Object.entries(fields)) {\n if (\n this.isOmittedCustomPrimaryKeySystemField(\n fieldName,\n customPrimaryKey,\n explicitFieldNames,\n )\n ) {\n continue;\n }\n const columnName = this.toDbColumnName(fieldName);\n if (schemaColumnNames && !schemaColumnNames.has(columnName)) {\n continue;\n }\n validFieldNames.add(fieldName);\n }\n if (isSTI && (!schemaColumnNames || schemaColumnNames.has('_meta_type'))) {\n validFieldNames.add('_meta_type');\n }\n\n const seen = new Set<string>();\n const outputFields: string[] = [];\n const selectExpressions: string[] = [];\n\n for (const fieldName of select) {\n if (typeof fieldName !== 'string') {\n throw new Error(\n `Invalid select field: expected a string field name, got ${typeof fieldName}.`,\n );\n }\n this.assertSafeProjectionFieldName(fieldName);\n\n if (seen.has(fieldName)) {\n throw new Error(\n `Invalid select field: '${fieldName}' was requested more than once.`,\n );\n }\n seen.add(fieldName);\n\n if (!validFieldNames.has(fieldName)) {\n throw new Error(\n `Invalid select field: '${fieldName}'. Field does not exist on ${itemClassName}. ` +\n `Valid fields: ${Array.from(validFieldNames).sort().join(', ')}`,\n );\n }\n\n const fieldDef = fields[fieldName];\n const fieldType = fieldDef?.type;\n if (this.isSensitiveFieldDefinition(fieldDef)) {\n throw new Error(\n `Invalid select field: '${fieldName}' is sensitive and cannot be projected by collection.list({ select }).`,\n );\n }\n const readPermission = this.getReadPermissionFieldDefinition(fieldDef);\n if (readPermission) {\n throw new Error(\n `Invalid select field: '${fieldName}' is gated by readPermission '${readPermission}' and cannot be projected by collection.list({ select }).`,\n );\n }\n\n const isTransient =\n fieldDef?.transient === true || fieldDef?._meta?.transient === true;\n const isRelationshipOnly =\n fieldType === 'oneToMany' || fieldType === 'manyToMany';\n if (fieldType === 'meta' || isTransient || isRelationshipOnly) {\n throw new Error(\n `Invalid select field: '${fieldName}' is not a column-backed field on ${itemClassName}.`,\n );\n }\n\n const columnName = this.toDbColumnName(fieldName);\n selectExpressions.push(\n `${this.quoteProjectionIdentifier(columnName)} AS ${this.quoteProjectionIdentifier(fieldName)}`,\n );\n outputFields.push(fieldName);\n }\n\n return {\n sql: selectExpressions.join(', '),\n outputFields,\n };\n }\n\n /**\n * Parse and validate order terms while retaining the resolved database\n * column. The normal list order builder remains the source of truth for\n * field, sensitive-field, and permission validation; this companion shape\n * lets the latest-row CTE qualify the same safe identifiers with its alias.\n */\n private resolveOrderByTerms(\n orderBy: string | string[],\n fields: Record<string, CollectionFieldDefinition>,\n label: string,\n ): Array<{ field: string; column: string; direction: 'ASC' | 'DESC' }> {\n const validatedSql = this.buildOrderBySql(orderBy, fields);\n const items = Array.isArray(orderBy) ? orderBy : [orderBy];\n if (items.length === 0) {\n throw new Error(\n `Invalid ${label}: at least one order field is required.`,\n );\n }\n\n const validatedTerms = validatedSql\n .replace(/^ ORDER BY /, '')\n .split(', ')\n .map((term) => term.split(/\\s+/));\n\n return items.map((item, index) => {\n const [field, direction = 'ASC'] = String(item).trim().split(/\\s+/);\n if (!field) {\n throw new Error(`Invalid ${label}: an order field is required.`);\n }\n const normalizedDirection = direction.toUpperCase();\n if (normalizedDirection !== 'ASC' && normalizedDirection !== 'DESC') {\n throw new Error(\n `Invalid ${label} direction: ${direction}. Must be ASC or DESC.`,\n );\n }\n\n return {\n column: validatedTerms[index]?.[0] ?? this.toDbColumnName(field),\n direction: normalizedDirection,\n field,\n };\n });\n }\n\n private qualifyLatestOrderTerms(\n alias: string,\n terms: ReadonlyArray<{\n column: string;\n direction: 'ASC' | 'DESC';\n }>,\n ): string {\n return terms\n .flatMap((term) => {\n const column = `${alias}.${this.quoteProjectionIdentifier(term.column)}`;\n // Explicit NULLS LAST keeps ranking and parent sorting identical on\n // PostgreSQL, SQLite, DuckDB, and JSON-on-DuckDB.\n return [\n `CASE WHEN ${column} IS NULL THEN 1 ELSE 0 END ASC`,\n `${column} ${term.direction}`,\n ];\n })\n .join(', ');\n }\n\n private qualifyLatestParentOrderTerms(\n alias: string,\n terms: ReadonlyArray<{\n column: string;\n direction: 'ASC' | 'DESC';\n }>,\n ): string {\n return terms\n .flatMap((term) => {\n const column = `${alias}.${this.quoteProjectionIdentifier(term.column)}`;\n return [\n `CASE WHEN ${column} IS NULL THEN 1 ELSE 0 END ASC`,\n `${column} ${term.direction}`,\n ];\n })\n .join(', ');\n }\n\n private resolveOneToManyInverseForeignKey(\n relationship: import('./registry').RelationshipMetadata,\n ): import('./registry').RelationshipMetadata {\n const inverseCandidates = ObjectRegistry.getInverseRelationshipsForSelf(\n this._itemClass.name,\n ).filter(\n (candidate) =>\n candidate.sourceClass === relationship.targetClass &&\n candidate.type === 'foreignKey',\n );\n const explicitForeignKey = relationship.options?.foreignKey as\n | string\n | undefined;\n const matchedForeignKey = explicitForeignKey\n ? inverseCandidates.find(\n (candidate) => candidate.fieldName === explicitForeignKey,\n )\n : undefined;\n if (explicitForeignKey && !matchedForeignKey) {\n throw new Error(\n `oneToMany ${relationship.fieldName} specifies foreignKey '${explicitForeignKey}', but ${relationship.targetClass} has no matching inverse foreignKey. Candidates: ${inverseCandidates.map((candidate) => candidate.fieldName).join(', ') || '(none)'}`,\n );\n }\n\n const inverseForeignKey =\n matchedForeignKey ??\n inverseCandidates.find(\n (candidate) => candidate.targetClass === this._itemClass.name,\n ) ??\n inverseCandidates[0];\n if (!inverseForeignKey) {\n throw new Error(\n `Could not find inverse foreignKey on ${relationship.targetClass} for oneToMany relationship ${relationship.fieldName}`,\n );\n }\n return inverseForeignKey;\n }\n\n /**\n * Load a page of hydrated parents with one selected latest row from a\n * declared `@oneToMany` relation. The parent page is sliced only after the\n * latest-row join and optional related sort are applied, so unrelated rows\n * are never hydrated in application code.\n *\n * Missing related rows return `latestRelated: null`. Related order and sort\n * fields place null values after non-null values consistently on all\n * supported adapters. The related row is a plain projection and is never a\n * full `SmrtObject` hydration.\n *\n * @example\n * ```typescript\n * const page = await opportunities.listWithLatestRelated({\n * latestRelated: {\n * relation: 'evaluations',\n * orderBy: 'evaluatedAt DESC',\n * select: ['score', 'evaluatedAt'],\n * sortBy: 'score DESC',\n * },\n * limit: 25,\n * offset: 0,\n * });\n * console.log(page[0].parent, page[0].latestRelated?.score);\n * ```\n */\n public async listWithLatestRelated(\n options: SmrtLatestRelatedListOptions<ModelType>,\n ): Promise<SmrtLatestRelatedRow<ModelType>[]> {\n await this.ensureStorageReady();\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const interceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n itemClassName,\n options as InterceptorListOptions,\n interceptorContext,\n )) ?? (options as InterceptorListOptions);\n\n let { where, offset, limit, orderBy } =\n interceptedOptions as SmrtListOptions<ModelType>;\n const latestOptions =\n (interceptedOptions as SmrtLatestRelatedListOptions<ModelType>)\n .latestRelated ?? options.latestRelated;\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n const isSTI = tableStrategy === 'sti';\n if (isSTI) {\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n if (stiBase && stiBase !== itemQualifiedName) {\n where = { _meta_type: itemQualifiedName, ...(where || {}) };\n }\n }\n where = resolveMetaTypeInWhere(where);\n\n const relationship = ObjectRegistry.getRelationships(\n this._itemClass.name,\n ).find(\n (candidate) =>\n candidate.fieldName === latestOptions.relation &&\n candidate.type === 'oneToMany',\n );\n if (!relationship) {\n throw new Error(\n `latestRelated.relation '${latestOptions.relation}' is not a declared oneToMany relationship on ${itemClassName}.`,\n );\n }\n\n const inverseForeignKey =\n this.resolveOneToManyInverseForeignKey(relationship);\n const relatedCollection = await ObjectRegistry.getCollection(\n relationship.targetClass,\n this.options,\n );\n await relatedCollection.ensureStorageReady();\n const relatedFields = relatedCollection.getFieldsSync();\n const relatedItemClassName = relatedCollection.getResolvedItemClassName();\n const relatedQualifiedName =\n relatedCollection.getResolvedItemQualifiedName();\n const relatedIsSTI =\n ObjectRegistry.getTableStrategy(relatedQualifiedName) === 'sti';\n\n // Run the related collection's read-scope interceptors as well as the\n // parent's. Relation targets can have their own tenant or authorization\n // policy; omitting this scope would make the CTE a raw bypass of the\n // target collection's normal list contract.\n const relatedInterceptorContext = createInterceptorContext(\n relatedItemClassName,\n 'list',\n relatedCollection.constructor.name,\n );\n const relatedInterceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n relatedItemClassName,\n { where: {} } as InterceptorListOptions,\n relatedInterceptorContext,\n )) ?? ({ where: {} } as InterceptorListOptions);\n let relatedWhere = (\n relatedInterceptedOptions as SmrtListOptions<SmrtObject>\n ).where;\n if (relatedIsSTI) {\n const relatedStiBase = ObjectRegistry.getSTIBase(relatedQualifiedName);\n if (relatedStiBase && relatedStiBase !== relatedQualifiedName) {\n relatedWhere = {\n _meta_type: relatedQualifiedName,\n ...(relatedWhere || {}),\n };\n }\n }\n relatedWhere = resolveMetaTypeInWhere(relatedWhere);\n const { sql: relatedWhereSql, values: relatedWhereValues } = buildWhere(\n relatedCollection.convertWhereKeys(relatedWhere || {}),\n );\n const relatedPrimaryKey = relatedCollection.resolvePrimaryKeyField(\n relatedFields,\n `Related model ${relatedItemClassName}`,\n );\n const relatedSelect = latestOptions.select ?? [relatedPrimaryKey.field];\n const relatedProjection = relatedCollection.resolveProjectionSelect(\n relatedSelect,\n relatedFields,\n relatedIsSTI,\n );\n const relatedOrderTerms = relatedCollection.resolveOrderByTerms(\n latestOptions.orderBy,\n relatedFields,\n 'latestRelated.orderBy',\n );\n const relatedSortTerms = latestOptions.sortBy\n ? relatedCollection.resolveOrderByTerms(\n latestOptions.sortBy,\n relatedFields,\n 'latestRelated.sortBy',\n )\n : [];\n\n const parentFields = this.getFieldsSync();\n const parentPrimaryKey = this.resolvePrimaryKeyField(\n parentFields,\n `Parent model ${itemClassName}`,\n );\n const relatedOutputFields = new Set(relatedProjection.outputFields);\n relatedOutputFields.add(relatedPrimaryKey.field);\n relatedOutputFields.add(inverseForeignKey.fieldName);\n if (parentFields.tenantId && relatedFields.tenantId) {\n relatedOutputFields.add('tenantId');\n }\n for (const term of [...relatedOrderTerms, ...relatedSortTerms]) {\n relatedOutputFields.add(term.field);\n }\n const relatedFieldColumns = new Map(\n [...relatedOrderTerms, ...relatedSortTerms].map((term) => [\n term.field,\n term.column,\n ]),\n );\n const parentSchema =\n ObjectRegistry.getSchema(itemQualifiedName) ??\n ObjectRegistry.getSchema(itemClassName);\n const liveParentSchema =\n typeof this.db.getTableSchema === 'function'\n ? await this.db.getTableSchema(this.tableName)\n : undefined;\n const parentColumnNames = Object.keys(\n liveParentSchema?.columns ?? parentSchema?.columns ?? {},\n );\n const occupiedParentIdentifiers = new Set([\n ...Object.keys(parentFields),\n ...Object.keys(parentFields).map((fieldName) =>\n this.toDbColumnName(fieldName),\n ),\n ...parentColumnNames,\n ...Object.keys(parentSchema?.columns ?? {}),\n ]);\n const relatedFieldAliases = new Map<string, string>();\n let nextAliasIndex = 0;\n for (const fieldName of relatedOutputFields) {\n let alias: string;\n do {\n alias = `__smrt_lr_${nextAliasIndex++}`;\n } while (\n occupiedParentIdentifiers.has(alias) ||\n [...relatedFieldAliases.values()].includes(alias)\n );\n relatedFieldAliases.set(fieldName, alias);\n }\n const parentSelectSql =\n parentColumnNames.length > 0\n ? parentColumnNames\n .map((columnName) => this.quoteProjectionIdentifier(columnName))\n .join(', ')\n : '*';\n const relatedAlias = (fieldName: string): string => {\n const alias = relatedFieldAliases.get(fieldName);\n if (!alias) {\n throw new Error(`Missing latest-related alias for '${fieldName}'.`);\n }\n return alias;\n };\n const relatedSelectExpressions = Array.from(relatedOutputFields).map(\n (fieldName) => {\n const column =\n relatedFieldColumns.get(fieldName) ??\n relatedCollection.toDbColumnName(fieldName);\n const alias = relatedAlias(fieldName);\n return `${relatedCollection.quoteProjectionIdentifier(column)} AS ${relatedCollection.quoteProjectionIdentifier(alias)}`;\n },\n );\n const relatedRankOrder = [\n relatedCollection.qualifyLatestOrderTerms('r', relatedOrderTerms),\n `r.${relatedCollection.quoteProjectionIdentifier(relatedPrimaryKey.column)} DESC`,\n ]\n .filter(Boolean)\n .join(', ');\n const latestRankAlias =\n relatedCollection.quoteProjectionIdentifier('__smrt_latest_rank');\n const latestRelatedAlias = (fieldName: string): string =>\n relatedCollection.quoteProjectionIdentifier(relatedAlias(fieldName));\n const relatedCte = `WITH ranked_latest_related AS (SELECT ${relatedSelectExpressions\n .map((expression) => `r.${expression}`)\n .join(\n ', ',\n )}, ROW_NUMBER() OVER (PARTITION BY r.${relatedCollection.quoteProjectionIdentifier(relatedCollection.toDbColumnName(inverseForeignKey.fieldName))} ORDER BY ${relatedRankOrder}) AS ${latestRankAlias} FROM ${relatedCollection.tableName} r${relatedWhereSql ? ` ${relatedWhereSql}` : ''}) `;\n\n const parentWhere = this.convertWhereKeys(where || {});\n const { sql: rawWhereSql, values: parentWhereValues } =\n buildWhere(parentWhere);\n const parentWhereSql = rawWhereSql.replace(\n /\\$(\\d+)/g,\n (_match, index: string) =>\n `$${Number(index) + relatedWhereValues.length}`,\n );\n const relatedJoinConditions = [\n `rr.${latestRankAlias} = 1`,\n `rr.${latestRelatedAlias(inverseForeignKey.fieldName)} = ${this.quoteProjectionIdentifier(parentPrimaryKey.column)}`,\n ];\n if (parentFields.tenantId && relatedFields.tenantId) {\n relatedJoinConditions.push(\n `(rr.${latestRelatedAlias('tenantId')} = ${this.quoteProjectionIdentifier('tenant_id')} OR (rr.${latestRelatedAlias('tenantId')} IS NULL AND ${this.quoteProjectionIdentifier('tenant_id')} IS NULL))`,\n );\n }\n\n const orderFragments: string[] = [];\n if (relatedSortTerms.length > 0) {\n orderFragments.push(\n relatedCollection.qualifyLatestParentOrderTerms(\n 'rr',\n relatedSortTerms.map((term) => ({\n ...term,\n column: relatedAlias(term.field),\n })),\n ),\n );\n }\n if (orderBy) {\n const parentOrderTerms = this.resolveOrderByTerms(\n orderBy,\n parentFields,\n 'orderBy',\n );\n orderFragments.push(\n parentOrderTerms\n .map(\n (term) =>\n `${this.quoteProjectionIdentifier(term.column)} ${term.direction}`,\n )\n .join(', '),\n );\n }\n orderFragments.push(\n `${this.quoteProjectionIdentifier(parentPrimaryKey.column)} ASC`,\n );\n\n const boundedLimit = this.applyListBounds(assertQueryBound(limit, 'limit'));\n const boundedOffset = assertQueryBound(offset, 'offset');\n let limitOffsetSql = '';\n const limitOffsetValues: number[] = [];\n let nextParameter =\n relatedWhereValues.length + parentWhereValues.length + 1;\n if (boundedLimit !== undefined) {\n limitOffsetSql += ` LIMIT $${nextParameter++}`;\n limitOffsetValues.push(boundedLimit);\n }\n if (boundedOffset !== undefined) {\n if (boundedLimit === undefined) {\n // SQLite requires LIMIT before OFFSET. SQLite uses -1 for an\n // unlimited limit; DuckDB and PostgreSQL use the standard LIMIT ALL.\n limitOffsetSql +=\n this.getDatabaseEngine() === 'sqlite' ? ' LIMIT -1' : ' LIMIT ALL';\n }\n limitOffsetSql += ` OFFSET $${nextParameter++}`;\n limitOffsetValues.push(boundedOffset);\n }\n\n const sql = `${relatedCte}SELECT ${parentSelectSql}, ${Array.from(\n relatedOutputFields,\n )\n .map(\n (fieldName) =>\n `rr.${latestRelatedAlias(fieldName)} AS ${this.quoteProjectionIdentifier(relatedAlias(fieldName))}`,\n )\n .join(\n ', ',\n )} FROM ${this.quoteProjectionIdentifier(this.tableName)} LEFT JOIN ranked_latest_related rr ON ${relatedJoinConditions.join(' AND ')} ${parentWhereSql} ${orderFragments.length > 0 ? `ORDER BY ${orderFragments.join(', ')}` : ''}${limitOffsetSql}`;\n const rows = await this.db.query(\n sql,\n ...relatedWhereValues,\n ...parentWhereValues,\n ...limitOffsetValues,\n );\n\n // Hydrate in result order. initialize() hooks may issue queries through\n // the same transaction-bound PostgreSQL client, so overlapping hydration\n // would violate the collection-wide serial hydration invariant.\n const instances: ModelType[] = [];\n const relatedAliasNames = new Set(relatedFieldAliases.values());\n for (const row of rows.rows as Record<string, unknown>[]) {\n const parentRow = Object.fromEntries(\n Object.entries(row).filter(([key]) => !relatedAliasNames.has(key)),\n );\n instances.push(\n await this.hydrateResultRow(parentRow, parentFields, isSTI),\n );\n }\n\n // Materialize the related projection by the parent primary key before\n // afterList hooks run. Hooks may filter or reorder hydrated parents; an\n // array-index pairing after those hooks could attach one parent's related\n // data to another parent.\n const relatedByParentId = new Map<string, Record<string, unknown> | null>();\n for (const row of rows.rows as Record<string, unknown>[]) {\n const parentId = row[parentPrimaryKey.column];\n if (parentId === null || parentId === undefined) continue;\n\n const relatedRow = Object.fromEntries(\n relatedProjection.outputFields.map((fieldName) => [\n fieldName,\n row[relatedAlias(fieldName)],\n ]),\n );\n const relatedId = row[relatedAlias(relatedPrimaryKey.field)];\n relatedByParentId.set(\n String(parentId),\n relatedId === null || relatedId === undefined\n ? null\n : relatedCollection.formatProjectionRow(\n relatedRow,\n relatedFields,\n relatedProjection.outputFields,\n ),\n );\n }\n\n const finalInstances = await GlobalInterceptors.executeAfterList(\n itemClassName,\n instances,\n interceptorContext,\n );\n\n return finalInstances.map((parent) => {\n return {\n latestRelated:\n (parent as unknown as Record<string, unknown>)[\n parentPrimaryKey.field\n ] === null ||\n (parent as unknown as Record<string, unknown>)[\n parentPrimaryKey.field\n ] === undefined\n ? null\n : (relatedByParentId.get(\n String(\n (parent as unknown as Record<string, unknown>)[\n parentPrimaryKey.field\n ],\n ),\n ) ?? null),\n parent,\n };\n });\n }\n\n private formatProjectionRow(\n row: Record<string, unknown>,\n fields: Record<string, CollectionFieldDefinition>,\n outputFields: readonly string[],\n ): Record<string, unknown> {\n const formattedData = this.withHydratedCoreFields(\n formatDataJs(row, fields),\n );\n const projected: Record<string, unknown> = {};\n\n for (const fieldName of outputFields) {\n projected[fieldName] = formattedData[fieldName];\n }\n\n return projected;\n }\n\n /**\n * Gets the class constructor for items in this collection\n */\n protected get _itemClass(): SmrtCollectionItemClass<ModelType> {\n const ctor = this.constructor as {\n readonly _itemClass?: SmrtCollectionItemClass<ModelType>;\n };\n if (!ctor._itemClass) {\n const className = this.constructor.name;\n const errorMessage = [\n `Collection \"${className}\" must define a static _itemClass property.`,\n '',\n 'Example:',\n ` class ${className} extends SmrtCollection<YourItemClass> {`,\n ' static readonly _itemClass = YourItemClass;',\n ' }',\n '',\n 'Make sure your item class is imported and defined before the collection class.',\n ].join('\\n');\n\n throw new Error(errorMessage);\n }\n return ctor._itemClass;\n }\n\n /**\n * Gets the model class constructor handled by this collection.\n */\n public getItemClass(): SmrtCollectionItemClass<ModelType> {\n return this._itemClass;\n }\n\n /**\n * Static reference to the item class constructor.\n *\n * S4 #1579: kept as `any`. Subclasses assign a concrete model constructor\n * (`static readonly _itemClass = Product`) here; the base declares the slot\n * generically and `SmrtCollection` is invariant in `ModelType`, so a narrower\n * `SmrtObjectConstructor` / `SmrtCollectionItemClass<SmrtObject>` type rejects\n * the heterogeneous concrete assignments. Mirrors the registry escape hatch.\n */\n // biome-ignore lint/suspicious/noExplicitAny: subclasses assign a concrete `static _itemClass = Product`; `SmrtCollection` is invariant in `ModelType`, so a narrower constructor type rejects the heterogeneous assignments. S4 #1579.\n static readonly _itemClass: any;\n\n /**\n * Validates that the collection is properly configured\n * Call this during development to catch configuration issues early\n */\n static validate(): void {\n if (!SmrtCollection._itemClass) {\n const className = SmrtCollection.name;\n const errorMessage = [\n `Collection \"${className}\" is missing required static _itemClass property.`,\n '',\n 'Fix by adding:',\n ` class ${className} extends SmrtCollection<YourItemClass> {`,\n ' static readonly _itemClass = YourItemClass;',\n ' }',\n ].join('\\n');\n throw new Error(errorMessage);\n }\n\n // Validate that _itemClass has required methods\n if (typeof SmrtCollection._itemClass !== 'function') {\n throw new Error(\n `Collection \"${SmrtCollection.name}\"._itemClass must be a constructor function`,\n );\n }\n\n // Check if it has a create method (static or prototype)\n const hasCreateMethod =\n typeof SmrtCollection._itemClass.create === 'function' ||\n typeof SmrtCollection._itemClass.prototype?.create === 'function';\n\n if (!hasCreateMethod) {\n logger.warn(\n `Collection \"${SmrtCollection.name}\"._itemClass should have a create() method for optimal functionality`,\n );\n }\n }\n\n /**\n * Database table name for this collection\n */\n public _tableName!: string;\n\n /**\n * Creates a new SmrtCollection instance\n *\n * @deprecated Use the static create() factory method instead\n * @param options - Configuration options\n */\n constructor(options: SmrtCollectionOptions = {}) {\n super(options);\n\n // #2367: validate the configured bounds at construction, not at query time.\n // A bad ceiling that only surfaces on the first paged read is a production\n // incident; a bad ceiling that fails to construct is a startup error.\n this._defaultListLimit = assertOptionalListBound(\n options.defaultListLimit,\n 'defaultListLimit',\n );\n this._maxListLimit = assertOptionalListBound(\n options.maxListLimit,\n 'maxListLimit',\n );\n\n // Auto-register the collection if it's not the base SmrtCollection and has an _itemClass.\n // `this.constructor` is typed as the structureless `Function`; view it as the\n // collection-class shape (static `_itemClass` constructor + the new-able ctor\n // that `registerCollection` stores) so the static slot can be read and the\n // class registered without `any`.\n const collectionCtor = this.constructor as unknown as {\n _itemClass?: SmrtObjectConstructor;\n } & (new (\n options: SmrtCollectionOptions,\n ) => SmrtCollection<SmrtObject>);\n if (this.constructor !== SmrtCollection && collectionCtor._itemClass) {\n const itemClass = collectionCtor._itemClass;\n const itemClassName =\n ObjectRegistry.getClassByConstructor(itemClass)?.name || itemClass.name;\n ObjectRegistry.registerCollection(itemClassName, collectionCtor);\n }\n }\n\n /**\n * Factory method — the recommended way to instantiate a collection.\n *\n * Creates the collection instance, calls `initialize()`, and pre-populates\n * the field cache used by synchronous query helpers.\n *\n * Pass the same `options` object you received in a `SmrtObject` constructor\n * or a `SvelteKit` load function to share the database connection.\n *\n * @param options - Database, AI, filesystem, and other configuration options.\n * At minimum, provide `db` (or `persistence`) to connect to a database.\n * @returns A fully initialized, ready-to-query collection instance\n *\n * @example\n * ```typescript\n * // Share a DB connection from a SvelteKit load function\n * const products = await Products.create(event.locals.smrtOptions);\n *\n * // Explicit configuration\n * const products = await Products.create({\n * db: myDb,\n * ai: { provider: 'openai', apiKey: process.env.OPENAI_API_KEY },\n * });\n * ```\n */\n // S4 #1579: `SmrtCollection<any>` bound is irreducible. `SmrtCollection` is\n // invariant in `ModelType` (it has `create(options: SmrtCreateInput<ModelType>)`),\n // so a concrete `Products extends SmrtCollection<Product>` does not satisfy\n // `T extends SmrtCollection<SmrtObject>`. The `any` bound is the only constraint\n // every subclass collection satisfies.\n // biome-ignore lint/suspicious/noExplicitAny: `SmrtCollection` is invariant in `ModelType`, so concrete `Products extends SmrtCollection<Product>` fails `T extends SmrtCollection<SmrtObject>`; the `any` bound is the only one every subclass satisfies. S4 #1579.\n static async create<T extends SmrtCollection<any>>(\n this: new (\n options?: SmrtCollectionOptions,\n ) => T,\n // `SmrtCollectionOptions`, not `SmrtClassOptions`: the collection-only\n // options (#2367 list bounds) must be passable through the recommended\n // factory without a cast, and every `SmrtClassOptions` value still satisfies\n // this because the extra members are optional.\n options: SmrtCollectionOptions = {},\n ): Promise<T> {\n // Extract only collection-compatible options from the incoming bag.\n // This is an allowlist: an option absent from BOTH the destructuring and the\n // literal below never reaches the constructor, however it was passed.\n const {\n _className,\n db,\n defaultListLimit, // #2367\n persistence, // Also extract persistence alias\n ai,\n fs,\n logging,\n maxListLimit, // #2367\n metrics,\n pubsub,\n sanitization,\n signals,\n } = options;\n\n const collectionOptions: SmrtCollectionOptions = {\n _className,\n db,\n persistence, // Pass persistence through so initialize() can map it to db\n ai,\n defaultListLimit,\n fs,\n logging,\n maxListLimit,\n metrics,\n pubsub,\n sanitization,\n signals,\n };\n\n // Defense-in-depth: SmrtJunction subclasses MUST have their ITEM class\n // registered with ObjectRegistry — that's where the field metadata,\n // tableName, and conflictColumns come from, which byLeft/byRight/\n // attach/detach/setLinks all depend on. The scanner is supposed to\n // catch every junction subclass via the FRAMEWORK_BASE_CLASSES list\n // in packages/scanner, but if a future refactor adds a new abstract\n // junction base without updating that list, or if a class is loaded\n // outside the normal manifest pipeline, we'd silently fall through to\n // empty-field-metadata behavior (the issue #1132 class of bug). Fail\n // loudly here instead of producing wrong results later.\n //\n // We check the ITEM class registration (not the collection class) —\n // collection constructors are stored separately via\n // registerCollection(itemClassName, ctor), not in the classes map.\n // View the static `this` as the junction-aware collection-class shape so the\n // junction sentinel and item constructor can be read without `any`.\n const staticSelf = this as unknown as {\n name: string;\n _isJunctionBase?: boolean;\n _itemClass?: SmrtObjectConstructor;\n };\n if (staticSelf._isJunctionBase === true) {\n const itemCtor = staticSelf._itemClass;\n const itemRegistered =\n itemCtor && ObjectRegistry.getClassByConstructor(itemCtor);\n if (!itemRegistered) {\n throw new Error(\n `SmrtJunction subclass \"${this.name}\" has no registered item class. ` +\n `The scanner likely didn't pick up its model — usually because the ` +\n `consuming package's manifest doesn't include \"${itemCtor?.name ?? '<unknown>'}\". ` +\n `Check that the scanner's FRAMEWORK_BASE_CLASSES ` +\n `(packages/scanner/src/inheritance-resolver.ts) recognizes every ` +\n `framework abstract base in your inheritance chain, that the package ` +\n `has been built (manifest.json present in dist/), and that runtime ` +\n `manifest loading (__smrt-register__.ts) runs before any junction ` +\n `is instantiated.`,\n );\n }\n }\n\n // Create instance using protected constructor\n const instance = new this(collectionOptions);\n\n // Perform async initialization\n await instance.initialize();\n\n // Cache fields for sync access during queries (issue #663)\n // This eliminates async getFields() calls on every query\n const fields = await instance.getFields();\n instance._cachedFields = fields;\n\n // Return fully initialized instance\n return instance;\n }\n\n /**\n * Async initialization hook for the collection.\n *\n * Called automatically by the static `create()` factory. In most cases you\n * do not need to call this directly — use `create()` instead.\n *\n * Runtime schema checks are deferred until a query actually touches the\n * backing table. This keeps collection construction safe during SSR and\n * import-time module evaluation without mutating application schema.\n *\n * @returns This instance (enables chaining)\n */\n public async initialize(): Promise<this> {\n await super.initialize();\n\n // Defer table verification until the first real query. That keeps\n // construction lightweight while ensuring runtime never auto-creates app\n // tables behind the scenes.\n\n return this;\n }\n\n /**\n * Verify that the collection's backing table exists before running a query.\n *\n * This is a fail-fast check only. It does not create or alter schema.\n */\n public async ensureStorageReady(): Promise<void> {\n await verifyPersistenceTable(\n this.db,\n this.tableName,\n this.getResolvedItemClassName(),\n );\n }\n\n /**\n * Find a single record by criteria (convenience method - delegates to get())\n *\n * @param options - Query options with where clause\n * @returns Promise resolving to the object or null if not found\n *\n * @example\n * ```typescript\n * const councils = await Councils.create({ persistence: { type: 'sql', url: 'db.sqlite' } });\n * const council = await councils.findOne({ where: { name: 'Example Council' } });\n * ```\n */\n public async findOne(options: {\n where: SmrtWhereClause<ModelType>;\n }): Promise<ModelType | null> {\n return await this.get(options.where);\n }\n\n /**\n * Find a record by ID (convenience method - delegates to get())\n *\n * @param id - Record ID to find (string or Field instance)\n * @returns Promise resolving to the object or null if not found\n *\n * @example\n * ```typescript\n * const councils = await Councils.create({ persistence: { type: 'sql', url: 'db.sqlite' } });\n * const council = await councils.findById('uuid-123');\n *\n * // Also works with Field instances (e.g., from foreignKey fields)\n * const meeting = new Meeting({ councilId: 'uuid-123' });\n * const council = await councils.findById(meeting.councilId);\n * ```\n */\n public async findById(id: string): Promise<ModelType | null> {\n return await this.get(id);\n }\n\n /**\n * Find all records matching criteria (convenience method - delegates to list())\n *\n * @param options - Query options (where, orderBy, limit, etc.)\n * @returns Promise resolving to array of objects\n *\n * @example\n * ```typescript\n * const councils = await Councils.create({ persistence: { type: 'sql', url: 'db.sqlite' } });\n * const active = await councils.findAll({ where: { status: 'active' } });\n * ```\n */\n public async findAll(\n options: {\n where?: SmrtWhereClause<ModelType>;\n orderBy?: string | string[];\n limit?: number;\n offset?: number;\n include?: string[];\n } = {},\n ): Promise<ModelType[]> {\n return await this.list(options);\n }\n\n /**\n * Find multiple objects by their IDs in a single query.\n *\n * This is a convenience method that avoids N+1 queries when you have\n * a list of IDs and need to fetch the corresponding records.\n *\n * The id list is chunked at `IN_LIST_CHUNK_SIZE` (#2367). `collection.list()`\n * expands an array-valued WHERE into one `id IN (?, ?, ...)` clause and does\n * not chunk it, so an unbounded caller-sized array hits the backend's\n * bind-variable ceiling — `SQLITE_MAX_VARIABLE_NUMBER` (999 pre-3.32, 32766\n * after) or PostgreSQL's 65535 — and the whole query fails before it runs.\n * The relationship, junction and hierarchy loaders have chunked at this value\n * for exactly this reason; `listByIds()` is the remaining path that took ids\n * straight from the caller and did not.\n *\n * @param ids - Array of UUIDs to fetch\n * @returns Promise resolving to array of objects (order not guaranteed;\n * results are concatenated in chunk order)\n *\n * @example\n * ```typescript\n * const profiles = await profileCollection.listByIds(['id1', 'id2', 'id3']);\n * ```\n */\n public async listByIds(ids: string[]): Promise<ModelType[]> {\n if (ids.length === 0) return [];\n\n // Never chunk above a configured ceiling: an explicit `limit` is clamped by\n // `maxListLimit`, so a chunk larger than the ceiling would come back\n // truncated and silently drop ids the caller asked for.\n const chunkSize =\n this._maxListLimit === undefined\n ? IN_LIST_CHUNK_SIZE\n : Math.min(IN_LIST_CHUNK_SIZE, this._maxListLimit);\n\n const chunks = chunkArray(ids, chunkSize);\n if (chunks.length === 1) {\n return this.list({ limit: chunks[0].length, where: { id: chunks[0] } });\n }\n\n const results: ModelType[] = [];\n for (const idChunk of chunks) {\n results.push(\n ...(await this.list({ limit: idChunk.length, where: { id: idChunk } })),\n );\n }\n return results;\n }\n\n /**\n * Resolve the effective cache config for a read (issue #1498).\n *\n * Per-call options win: `false` forces a fresh read, `{ ttl }` enables\n * caching for this call. Otherwise falls back to the model-level\n * `@smrt({ cache })` config (inheritance-aware). Returns undefined when\n * the read should go straight to the database — the default.\n */\n private resolveReadCacheConfig(\n perCall?: CollectionCacheConfig | false,\n ): CollectionCacheConfig | undefined {\n if (perCall === false) return undefined;\n if (perCall) return perCall.ttl > 0 ? perCall : undefined;\n return ObjectRegistry.resolveCollectionCacheConfig(\n this.getResolvedItemQualifiedName(),\n );\n }\n\n /**\n * Execute a SELECT, optionally through the collection read cache.\n *\n * The cache key is the final SQL + bound parameters — computed after\n * interceptors (tenancy filters) and STI discriminators are applied, so\n * differently-scoped queries can never share an entry. Cached values are\n * raw rows; callers still run their normal post-query formatting path.\n */\n private async queryRowsWithCache(\n sql: string,\n params: unknown[],\n cacheConfig: CollectionCacheConfig | undefined,\n ): Promise<Record<string, unknown>[]> {\n if (!cacheConfig) {\n const result = await this.db.query(sql, ...params);\n return result.rows;\n }\n\n const dbKey = resolveDbCacheKey(this.db);\n const queryKey = buildQueryCacheKey(sql, params);\n\n // Start consuming peer invalidations before the first cached read so\n // a remote write can't go unseen for longer than necessary, and record\n // table-scoped interest so this process's own writes broadcast even\n // when the crossProcess opt-in only exists at the call site.\n if (cacheConfig.crossProcess) {\n ensureCacheInvalidationListener(this.db);\n registerCrossProcessCacheInterest(dbKey, this.tableName);\n }\n\n const cached = getCachedRows(dbKey, this.tableName, queryKey);\n if (cached) {\n return cached;\n }\n\n // Capture the table's invalidation generation BEFORE the round-trip; if a\n // concurrent write invalidates while this SELECT is in flight, setCachedRows\n // sees the bumped generation and drops the now-stale result instead of\n // caching it for the full TTL.\n const generation = getCacheGeneration(dbKey, this.tableName);\n const result = await this.db.query(sql, ...params);\n setCachedRows(\n dbKey,\n this.tableName,\n queryKey,\n result.rows,\n cacheConfig.ttl,\n generation,\n );\n return result.rows;\n }\n\n /**\n * Retrieves a single object from the collection by ID, slug, or a custom filter.\n *\n * Filter resolution:\n * - UUID string → `WHERE id = ?`\n * - Non-UUID string → `WHERE slug = ? AND context = ''`\n * - Object → `WHERE <key> = <value> [AND ...]`\n *\n * For STI child collections, an `AND _meta_type = '<qualifiedName>'` clause is\n * automatically appended so you only receive the correct subclass.\n *\n * Runs `beforeGet` / `afterGet` interceptors (used by multi-tenancy, etc.).\n *\n * @param filter - UUID string, slug string, or a WHERE conditions object\n * @param options.cache - Opt-in read-through cache for this call\n * (`{ ttl }` in milliseconds), or `false` to force a fresh read when the\n * model opted in via `@smrt({ cache })`. Defaults to the model config.\n * @returns The matching object instance, or `null` if not found\n *\n * @example\n * ```typescript\n * // By UUID\n * const product = await products.get('550e8400-e29b-41d4-a716-446655440000');\n *\n * // By slug\n * const product = await products.get('my-widget');\n *\n * // By custom filter\n * const product = await products.get({ sku: 'WID-001' });\n *\n * // Cached read (memoized for 60s, invalidated on writes)\n * const product = await products.get('my-widget', { cache: { ttl: 60_000 } });\n * ```\n *\n * @see {@link list} for multiple results\n * @see {@link findById} / {@link findOne} for convenience aliases\n */\n public async get(\n filter: string | SmrtWhereClause<ModelType>,\n options: { cache?: CollectionCacheConfig | false } = {},\n ): Promise<ModelType | null> {\n await this.ensureStorageReady();\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n\n // Execute beforeGet interceptors (e.g., tenancy validation)\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'get',\n this.constructor.name,\n );\n const interceptedFilter = await GlobalInterceptors.executeBeforeGet(\n itemClassName,\n filter,\n interceptorContext,\n );\n\n // String-filter resolution shares one helper with `beforeGet` interceptors\n // (#2365): an interceptor that rewrites the string into an object filter\n // must resolve id-vs-slug exactly the way this method would.\n let where: Record<string, unknown> =\n typeof interceptedFilter === 'string'\n ? resolveGetStringFilter(interceptedFilter)\n : interceptedFilter;\n\n // Fix for issue #386: Add _meta_type filter for STI child collections.\n // R5-canon: use the qualified item name as the LOOKUP KEY too, not\n // just for the comparison — passing the simple `itemClassName` can\n // resolve to another package's same-simple-name class via\n // `findClass`'s multi-strategy lookup, which would yield the WRONG\n // table-strategy / STI base.\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n const isSTI = tableStrategy === 'sti';\n\n if (isSTI) {\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n if (stiBase && stiBase !== itemQualifiedName) {\n where = {\n _meta_type: itemQualifiedName,\n ...where,\n };\n }\n }\n\n // convertWhereKeys is now sync (issue #663) - no await needed\n const convertedWhere = this.convertWhereKeys(where);\n const { sql: whereSql, values: whereValues } = buildWhere(convertedWhere);\n\n // `get()` reads `rows[0]` and discards the rest, so the row cap belongs in\n // the query, not in JavaScript (#2367). Without it a filter that is not\n // backed by a unique index — `get({ status: 'active' })`, or any slug lookup\n // on a table whose natural-key index was replaced by custom\n // `conflictColumns` — streams every matching row through the driver and\n // hydrates none of them. The literal is safe to interpolate: it is not\n // caller-derived, and `$n` placeholders here would have to be numbered after\n // the WHERE values on PostgreSQL.\n const fullSQL = `SELECT * FROM ${this.tableName} ${whereSql} LIMIT 1`;\n const rows = await this.queryRowsWithCache(\n fullSQL,\n whereValues,\n this.resolveReadCacheConfig(options.cache),\n );\n\n if (!rows?.[0]) {\n // Execute afterGet with null result\n // Explicitly specify type parameter since TypeScript can't infer from null\n return await GlobalInterceptors.executeAfterGet<ModelType>(\n itemClassName,\n null,\n interceptorContext,\n );\n }\n\n const fields = this.getFieldsSync();\n const instance = await this.hydrateResultRow(rows[0], fields, isSTI);\n\n // Execute afterGet interceptors (e.g., tenant validation)\n return await GlobalInterceptors.executeAfterGet(\n itemClassName,\n instance,\n interceptorContext,\n );\n }\n\n /**\n * Lists records from the collection with flexible filtering options\n *\n * @param options - Query options object\n * @param options.where - Record of conditions to filter results. Each key can include an operator\n * separated by a space (e.g., 'price >', 'name like'). Default operator is '='.\n * @param options.offset - Number of records to skip\n * @param options.limit - Maximum number of records to return\n * @param options.orderBy - Field(s) to order results by, with optional direction\n * @param options.select - Column-backed fields to return as plain rows without hydrating model instances\n *\n * @example\n * ```typescript\n * // Find active products priced between $100-$200\n * await collection.list({\n * where: {\n * 'price >': 100,\n * 'price <=': 200,\n * 'status': 'active', // equals operator is default\n * 'category in': ['A', 'B', 'C'], // IN operator for arrays\n * 'name like': '%shirt%', // LIKE for pattern matching\n * 'deleted_at !=': null // exclude deleted items\n * },\n * limit: 10,\n * offset: 0\n * });\n *\n * // Find users matching pattern but not in specific roles\n * await users.list({\n * where: {\n * 'email like': '%@company.com',\n * 'active': true,\n * 'role in': ['guest', 'blocked'],\n * 'last_login <': lastMonth\n * }\n * });\n *\n * // Fetch compact page-key rows without constructing full objects\n * await collection.list({\n * select: ['id', 'title', 'accountId'],\n * where: { status: 'open' },\n * orderBy: 'created_at DESC',\n * limit: 50,\n * });\n * ```\n *\n * @returns Promise resolving to an array of model instances, or plain selected rows when `select` is present\n */\n public async list<const Select extends readonly SmrtSelectField<ModelType>[]>(\n options: SmrtListOptions<ModelType> & {\n select: Select;\n include?: never;\n },\n ): Promise<SmrtSelectedRow<ModelType, Select>[]>;\n public async list(\n options?: Omit<SmrtListOptions<ModelType>, 'select'> & {\n select?: undefined;\n },\n ): Promise<ModelType[]>;\n public async list(\n options: SmrtListOptions<ModelType> = {},\n ): Promise<ModelType[] | Record<string, unknown>[]> {\n await this.ensureStorageReady();\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n\n // Execute beforeList interceptors (e.g., tenancy filtering)\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const interceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n itemClassName,\n options as InterceptorListOptions,\n interceptorContext,\n )) ??\n (options as InterceptorListOptions | undefined) ??\n {};\n\n let { where, offset, limit, orderBy, select } =\n interceptedOptions as SmrtListOptions<ModelType>;\n\n // STI: Child collections should automatically filter by _meta_type.\n // R5-canon: qualified-key lookup so a same-simple-name class in\n // another package can't yield the wrong table strategy / STI base.\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n const isSTI = tableStrategy === 'sti';\n\n if (isSTI) {\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n if (stiBase && stiBase !== itemQualifiedName) {\n where = andWhereCondition(where, {\n _meta_type: itemQualifiedName,\n });\n }\n }\n\n // Resolve _meta_type to qualified name if user provided simple class name (Issue #713)\n where = resolveMetaTypeInWhere(where);\n\n // convertWhereKeys is now sync (issue #663) - no await needed\n const convertedWhere = this.convertWhereKeys(where || {});\n const { sql: whereSql, values: whereValues } = buildWhere(convertedWhere);\n\n const fields = this.getFieldsSync();\n const projection =\n select !== undefined\n ? this.resolveProjectionSelect(select, fields, isSTI)\n : undefined;\n if (\n projection &&\n interceptedOptions.include &&\n interceptedOptions.include.length > 0\n ) {\n throw new Error(\n 'collection.list({ select }) returns plain projection rows and cannot eager-load relationships. Remove include or omit select.',\n );\n }\n\n const orderBySql = this.buildOrderBySql(orderBy, fields);\n\n let limitOffsetSql = '';\n const limitOffsetValues: (number | undefined)[] = [];\n let paramIndex = whereValues.length + 1;\n\n // #2367: reject a malformed bound here rather than binding it. A NaN or\n // fractional `limit` used to reach the driver verbatim, where PostgreSQL\n // answers `invalid input syntax for type bigint` — a 500 for what is a\n // caller error. Then apply the collection's opt-in default/ceiling.\n const boundedLimit = this.applyListBounds(assertQueryBound(limit, 'limit'));\n const boundedOffset = assertQueryBound(offset, 'offset');\n\n if (boundedLimit !== undefined) {\n limitOffsetSql += ` LIMIT $${paramIndex++}`;\n limitOffsetValues.push(boundedLimit);\n }\n\n if (boundedOffset !== undefined) {\n limitOffsetSql += ` OFFSET $${paramIndex++}`;\n limitOffsetValues.push(boundedOffset);\n }\n\n const selectSql = projection ? projection.sql : '*';\n const sql = `SELECT ${selectSql} FROM ${this.tableName} ${whereSql} ${orderBySql} ${limitOffsetSql}`;\n const params = [...whereValues, ...limitOffsetValues];\n\n // Resolve the cache preference from the post-interceptor options —\n // beforeList interceptors may legally set or clear `cache` (e.g. force\n // read-through on admin paths, enable caching per tenant).\n const rows = await this.queryRowsWithCache(\n sql,\n params,\n this.resolveReadCacheConfig(\n (interceptedOptions as { cache?: CollectionCacheConfig | false }).cache,\n ),\n );\n\n if (projection) {\n // Projection is the no-hydration path. beforeList interceptors still ran\n // above, but afterList hooks are intentionally skipped because they are\n // defined around hydrated SmrtObject instances.\n return rows.map((item) =>\n this.formatProjectionRow(item, fields, projection.outputFields),\n );\n }\n\n // STI: Hydrate instances polymorphically based on _meta_type\n // Reuse tableStrategy and isSTI from earlier in the function\n const instances = await this.hydrateResultRows(rows, fields, isSTI);\n\n // Eager load specified relationships\n if (interceptedOptions.include && interceptedOptions.include.length > 0) {\n // Cast to ModelType[] - instances are guaranteed to be subtypes of ModelType\n // even in STI mode where they may be different subclasses\n await this.eagerLoadRelationships(\n instances as ModelType[],\n interceptedOptions.include,\n );\n }\n\n // Execute afterList interceptors (e.g., tenant validation)\n const finalInstances = await GlobalInterceptors.executeAfterList(\n itemClassName,\n instances,\n interceptorContext,\n );\n\n return finalInstances;\n }\n\n /**\n * Eagerly load relationships for a collection of instances\n *\n * Optimizes loading by batching queries for foreignKey relationships to avoid N+1 queries.\n *\n * @param instances - Array of object instances to load relationships for\n * @param relationships - Array of relationship field names to load\n * @private\n */\n private async eagerLoadRelationships(\n instances: ModelType[],\n relationships: string[],\n ): Promise<void> {\n if (instances.length === 0) return;\n\n for (const fieldName of relationships) {\n // Get relationship metadata\n const relationshipMeta = ObjectRegistry.getRelationships(\n this._itemClass.name,\n );\n const relationship = relationshipMeta.find(\n (r) => r.fieldName === fieldName,\n );\n\n if (!relationship) {\n logger.warn(\n `Relationship ${fieldName} not found on ${this._itemClass.name}, skipping eager load`,\n );\n continue;\n }\n\n if (\n relationship.type === 'foreignKey' ||\n relationship.type === 'crossPackageRef'\n ) {\n // crossPackageRef target lives in another package — make sure its\n // manifest is loaded before we try to instantiate the target collection.\n if (relationship.type === 'crossPackageRef') {\n await ObjectRegistry.ensureManifestLoaded(relationship.targetClass);\n }\n // Batch load foreignKey / crossPackageRef relationships\n await this.batchLoadForeignKeys(instances, fieldName, relationship);\n } else if (relationship.type === 'oneToMany') {\n // Load oneToMany relationships (less optimizable)\n await this.batchLoadOneToMany(instances, fieldName, relationship);\n } else if (relationship.type === 'manyToMany') {\n await this.batchLoadManyToMany(instances, fieldName, relationship);\n }\n }\n }\n\n /**\n * Batch load foreignKey relationships to avoid N+1 queries\n *\n * @param instances - Instances to load relationships for\n * @param fieldName - Name of the foreignKey field\n * @param relationship - Relationship metadata\n * @private\n */\n private async batchLoadForeignKeys(\n instances: ModelType[],\n fieldName: string,\n relationship: import('./registry').RelationshipMetadata,\n ): Promise<void> {\n // Collect all unique foreign key values\n const foreignKeyValues = new Set<string>();\n for (const instance of instances) {\n const value = instance[fieldName as keyof ModelType];\n if (value && typeof value === 'string') {\n foreignKeyValues.add(value);\n }\n }\n\n if (foreignKeyValues.size === 0) return;\n\n // Get or create cached collection instance\n let targetCollection: SmrtCollection<SmrtObject> | undefined;\n try {\n targetCollection = await ObjectRegistry.getCollection(\n relationship.targetClass,\n this.options,\n );\n } catch (error) {\n logger.warn(`Could not get collection for ${relationship.targetClass}`, {\n error,\n });\n return;\n }\n\n // Load all related objects, chunked to stay under SQLITE_MAX_VARIABLE_NUMBER (999).\n const fkValueList = Array.from(foreignKeyValues);\n const relatedObjects: SmrtObject[] = [];\n for (const idChunk of chunkArray(fkValueList, IN_LIST_CHUNK_SIZE)) {\n const batch = await targetCollection.list({\n where: { 'id in': idChunk },\n });\n relatedObjects.push(...batch);\n }\n\n // Build a map of ID to object for quick lookup\n const relatedMap = new Map<string, SmrtObject>();\n for (const obj of relatedObjects) {\n if (obj.id) relatedMap.set(obj.id, obj);\n }\n\n // Assign loaded objects to instances\n for (const instance of instances) {\n const foreignKeyValue = instance[fieldName as keyof ModelType];\n if (foreignKeyValue && typeof foreignKeyValue === 'string') {\n const relatedObject = relatedMap.get(foreignKeyValue);\n if (relatedObject) {\n // Prime the lazy-load cache through SmrtObject's sanctioned internal\n // setter rather than reaching into the private field directly.\n instance._setLoadedRelationship(fieldName, relatedObject);\n }\n }\n }\n }\n\n /**\n * Batch load oneToMany relationships\n *\n * @param instances - Instances to load relationships for\n * @param fieldName - Name of the oneToMany field\n * @param relationship - Relationship metadata\n * @private\n */\n private async batchLoadOneToMany(\n instances: ModelType[],\n fieldName: string,\n relationship: import('./registry').RelationshipMetadata,\n ): Promise<void> {\n // Find the inverse foreignKey field. An instance can satisfy an inverse FK\n // that targets its own class or any (STI) ancestor it inherits the\n // oneToMany from. Mirrors loadRelatedMany so lazy and eager (`include:`)\n // loading resolve the same inverse side.\n const inverseRelationships = ObjectRegistry.getInverseRelationshipsForSelf(\n this._itemClass.name,\n );\n const inverseCandidates = inverseRelationships.filter(\n (r) =>\n r.sourceClass === relationship.targetClass && r.type === 'foreignKey',\n );\n // Honor an explicit `@oneToMany(Target, { foreignKey })` when the target\n // declares multiple foreign keys back to this class; otherwise fall back\n // to the first match (legacy behavior).\n const explicitForeignKey = relationship.options?.foreignKey as\n | string\n | undefined;\n const matchedForeignKey = explicitForeignKey\n ? inverseCandidates.find((r) => r.fieldName === explicitForeignKey)\n : undefined;\n if (explicitForeignKey && !matchedForeignKey) {\n // A misspelled / stale `foreignKey` is a configuration error, not a\n // recoverable data condition — fail loudly here too so eager (`include:`)\n // loading behaves identically to lazy loadRelatedMany rather than\n // silently producing empty arrays.\n throw new Error(\n `oneToMany ${fieldName} specifies foreignKey '${explicitForeignKey}', but ${relationship.targetClass} has no matching inverse foreignKey. Candidates: ${inverseCandidates.map((r) => r.fieldName).join(', ') || '(none)'}`,\n );\n }\n // Prefer an inverse FK that targets this exact class before falling back\n // to an ancestor's (mirrors loadRelatedMany).\n const inverseForeignKey =\n matchedForeignKey ??\n inverseCandidates.find((r) => r.targetClass === this._itemClass.name) ??\n inverseCandidates[0];\n\n if (!inverseForeignKey) {\n logger.warn(\n `Could not find inverse foreignKey for oneToMany ${fieldName}`,\n );\n return;\n }\n\n // Collect all instance IDs\n const instanceIds = instances\n .map((i) => i.id)\n .filter((id): id is string => !!id);\n\n if (instanceIds.length === 0) return;\n\n // Get or create cached collection instance\n let targetCollection: SmrtCollection<SmrtObject> | undefined;\n try {\n targetCollection = await ObjectRegistry.getCollection(\n relationship.targetClass,\n this.options,\n );\n } catch (error) {\n logger.warn(`Could not get collection for ${relationship.targetClass}`, {\n error,\n });\n return;\n }\n\n // Load all related objects, chunked to stay under SQLITE_MAX_VARIABLE_NUMBER (999).\n const relatedObjects: SmrtObject[] = [];\n for (const idChunk of chunkArray(instanceIds, IN_LIST_CHUNK_SIZE)) {\n const batch = await targetCollection.list({\n where: { [`${inverseForeignKey.fieldName} in`]: idChunk },\n });\n relatedObjects.push(...batch);\n }\n\n // Group related objects by the foreign key value. The key type is `unknown`\n // (not `string`) to preserve the prior behavior exactly: a non-string FK\n // value still creates its own bucket rather than being skipped.\n const relatedMap = new Map<unknown, SmrtObject[]>();\n for (const obj of relatedObjects) {\n // Access dynamic property safely - obj is a SmrtObject with dynamic fields\n const foreignKeyValue = (obj as unknown as Record<string, unknown>)[\n inverseForeignKey.fieldName\n ];\n if (!relatedMap.has(foreignKeyValue)) {\n relatedMap.set(foreignKeyValue, []);\n }\n relatedMap.get(foreignKeyValue)?.push(obj);\n }\n\n // Assign loaded objects to instances\n for (const instance of instances) {\n const relatedArray = relatedMap.get(instance.id as string) || [];\n // Prime the lazy-load cache through SmrtObject's sanctioned internal\n // setter rather than reaching into the private field directly.\n instance._setLoadedRelationship(fieldName, relatedArray);\n }\n }\n\n /**\n * Batch-load manyToMany relationships through a junction table.\n *\n * Issues two queries instead of N: one against the junction table to map\n * source IDs to target IDs, and one against the target table to hydrate\n * the related rows. Results are grouped by source instance.\n *\n * @param instances - Instances whose manyToMany field should be populated\n * @param fieldName - Name of the @manyToMany decorated field\n * @param relationship - Relationship metadata from the registry\n * @private\n */\n private async batchLoadManyToMany(\n instances: ModelType[],\n fieldName: string,\n relationship: import('./registry').RelationshipMetadata,\n ): Promise<void> {\n const instanceIds = instances\n .map((i) => i.id)\n .filter((id): id is string => !!id);\n if (instanceIds.length === 0) return;\n\n // Delegate join-coordinate resolution to a sample instance — it shares the\n // same registry metadata as every other instance in this batch.\n let through: string;\n let sourceColumn: string;\n let targetColumn: string;\n let targetClassName: string;\n try {\n // `resolveManyToManyJoin` is a protected SmrtObject method; view the\n // sample instance as the shape exposing it. The call keeps `this` bound\n // (method invoked on the receiver), so registry metadata resolves on the\n // sample as intended.\n const sample = instances[0] as unknown as {\n resolveManyToManyJoin(\n fieldName: string,\n relationship: import('./registry').RelationshipMetadata,\n ): Promise<{\n through: string;\n sourceColumn: string;\n targetColumn: string;\n targetClassName: string;\n }>;\n };\n const join = await sample.resolveManyToManyJoin(fieldName, relationship);\n through = join.through;\n sourceColumn = join.sourceColumn;\n targetColumn = join.targetColumn;\n targetClassName = join.targetClassName;\n } catch (error) {\n logger.warn(\n `Could not resolve manyToMany join for ${fieldName} on ${this._itemClass.name}`,\n { error },\n );\n return;\n }\n\n // Default empty arrays for every instance so callers always see an array.\n // Seed through SmrtObject's sanctioned internal setter rather than reaching\n // into the private relationship-cache field directly.\n for (const instance of instances) {\n instance._setLoadedRelationship(fieldName, []);\n }\n\n // Pull junction rows in chunks. SQLite's default SQLITE_MAX_VARIABLE_NUMBER\n // is 999, so we cap each IN-list at IN_LIST_CHUNK_SIZE to stay well below\n // the limit on the supported backends (sqlite/libsql/postgres).\n const junctionRowsAll: Array<{\n [key: string]: unknown;\n }> = [];\n for (const idChunk of chunkArray(instanceIds, IN_LIST_CHUNK_SIZE)) {\n const placeholders = idChunk.map(() => '?').join(', ');\n const result = await this.db.query(\n `SELECT \"${sourceColumn}\", \"${targetColumn}\" FROM \"${through}\" WHERE \"${sourceColumn}\" IN (${placeholders})`,\n idChunk,\n );\n junctionRowsAll.push(...result.rows);\n }\n\n if (junctionRowsAll.length === 0) return;\n\n // sourceId -> [targetIds...]\n const sourceToTargets = new Map<string, string[]>();\n const allTargetIds = new Set<string>();\n for (const row of junctionRowsAll) {\n const sId = row[sourceColumn];\n const tId = row[targetColumn];\n if (typeof sId !== 'string' || typeof tId !== 'string') continue;\n allTargetIds.add(tId);\n const list = sourceToTargets.get(sId) ?? [];\n list.push(tId);\n sourceToTargets.set(sId, list);\n }\n\n if (allTargetIds.size === 0) return;\n\n // Hydrate all target objects. Also chunked so we don't blow the\n // placeholder limit on a wide manyToMany.\n let targetCollection: SmrtCollection<SmrtObject>;\n try {\n targetCollection = await ObjectRegistry.getCollection(\n targetClassName,\n this.options,\n );\n } catch (error) {\n logger.warn(\n `Could not get collection for manyToMany target ${targetClassName}`,\n { error },\n );\n return;\n }\n const targetIdList = Array.from(allTargetIds);\n const targetObjects: SmrtObject[] = [];\n for (const idChunk of chunkArray(targetIdList, IN_LIST_CHUNK_SIZE)) {\n const batch = await targetCollection.list({\n where: { 'id in': idChunk },\n });\n targetObjects.push(...batch);\n }\n\n const targetById = new Map<string, SmrtObject>();\n for (const obj of targetObjects) {\n if (obj.id) targetById.set(obj.id, obj);\n }\n\n // Assign grouped results\n for (const instance of instances) {\n const targetIds = sourceToTargets.get(instance.id as string) ?? [];\n const objects = targetIds\n .map((id) => targetById.get(id))\n .filter((o) => o !== undefined);\n // Seed through SmrtObject's sanctioned internal setter rather than reaching\n // into the private relationship-cache field directly.\n instance._setLoadedRelationship(fieldName, objects);\n }\n }\n\n /**\n * Creates and persists a new instance of the collection's item class.\n *\n * Instantiates the model with the given field values, calls `initialize()`,\n * assigns a UUID if none is provided, then calls `save()` to write the row\n * to the database.\n *\n * For STI collections, pass `_meta_type` to create a specific subclass.\n * The correct constructor is resolved via `ObjectRegistry` (polymorphic creation).\n *\n * @param options - Field values for the new object. Accepts any public field on\n * the model class plus the STI `_meta_type` discriminator.\n * @returns The newly created and saved model instance\n * @throws {ValidationError} If a `required` field is missing or a unique constraint is violated\n * @throws {DatabaseError} If the write fails\n *\n * @example\n * ```typescript\n * // Regular creation\n * const product = await products.create({ name: 'Widget', price: 9.99 });\n * console.log(product.id); // UUID assigned during save\n *\n * // STI polymorphic creation\n * const article = await contents.create({\n * _meta_type: '@happyvertical/smrt-content:Article',\n * title: 'Hello World',\n * });\n * ```\n *\n * @see {@link getOrUpsert} to avoid duplicates by finding-or-creating\n */\n public async create(options: SmrtCreateInput<ModelType>) {\n let itemClassName = this.getResolvedItemClassName();\n\n // Ensure manifest is loaded before creating instance metadata; save() will\n // ensure the actual table exists right before persistence.\n await ObjectRegistry.ensureManifestLoaded(itemClassName);\n itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n\n // STI: Check for polymorphic instantiation.\n // R5-canon: qualified-key lookup so colliding simple names across\n // packages don't yield the wrong table strategy.\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n\n if (tableStrategy === 'sti' && options._meta_type) {\n // Use polymorphic instantiation for STI child classes\n // Pass raw options (not params) - createPolymorphic() will add ai, db, _skipLoad\n const instance = await this.createPolymorphic(\n options._meta_type,\n options,\n );\n // Generate ID if not provided, then save. `_id` is a private SmrtObject\n // backing field; view as a writable bag for the runtime assignment.\n if (!instance.id) {\n (instance as unknown as { _id?: string })._id = crypto.randomUUID();\n }\n if (options._insertOnly === true) {\n instance.requireInsertOnSave();\n }\n await instance.save();\n return instance;\n }\n\n // For non-STI or STI base class without _meta_type, proceed with direct instantiation\n // Schema already initialized in Collection.create() static factory\n const params = {\n ai: this.options.ai,\n // Pass the actual database instance, not options\n // This ensures objects share the same connection as the collection\n // Critical for in-memory databases like DuckDB :memory: where each\n // connection gets a separate database\n db: this.db,\n _skipLoad: true, // Don't try to load from DB - this is a new object\n ...options,\n };\n\n // Direct instantiation - all SmrtObject classes support this pattern\n const instance = new this._itemClass(params);\n await instance.initialize();\n\n // For STI collections, set _meta_type to the qualified class name (fix for issue #442)\n // This ensures _meta_type is available immediately after creation, not just after DB load\n // Use constructor-based lookup to avoid name collision issues (issue #713)\n // When classes are registered with qualified names as keys (e.g., from consumer plugin),\n // name-based lookup fails. Constructor lookup uses a WeakMap index for O(1) lookups.\n if (tableStrategy === 'sti') {\n // `_meta_type` is a runtime-only STI field; view as a writable bag.\n (instance as unknown as { _meta_type?: string })._meta_type =\n itemQualifiedName;\n }\n // Generate ID if not provided, then save. `_id` is a private SmrtObject\n // backing field; view as a writable bag for the runtime assignment.\n if (!instance.id) {\n (instance as unknown as { _id?: string })._id = crypto.randomUUID();\n }\n if (options._insertOnly === true) {\n instance.requireInsertOnSave();\n }\n await instance.save();\n return instance;\n }\n\n /**\n * Creates an instance of the correct subclass for STI polymorphic queries\n *\n * @param className - Name of the class to instantiate (from _meta_type)\n * @param options - Data to initialize the instance with\n * @returns Promise resolving to the instance of the correct subclass\n * @private\n */\n private async createPolymorphic(\n className: string | null | undefined,\n options: Record<string, unknown>,\n hydrationOptions: { hydrateOnly?: boolean } = {},\n ): Promise<ModelType> {\n // Schema already initialized in Collection.create() static factory\n\n // Validate discriminator is present\n if (!className || className === null || className === undefined) {\n const { DatabaseError } = await import('./errors.js');\n throw DatabaseError.missingDiscriminator(\n this._itemClass.name,\n typeof options?.id === 'string' ? options.id : undefined,\n );\n }\n\n // Get the correct class constructor from ObjectRegistry\n // Support both qualified names (new format: @pkg:Class) and simple names (legacy)\n let registeredClass = ObjectRegistry.getClassByQualifiedName(className);\n if (!registeredClass) {\n // Fall back to simple name lookup for legacy data\n registeredClass = ObjectRegistry.getClass(className);\n }\n\n if (!registeredClass) {\n await ObjectRegistry.ensureManifestLoaded(className);\n registeredClass = ObjectRegistry.getClassByQualifiedName(className);\n if (!registeredClass) {\n registeredClass = ObjectRegistry.getClass(className);\n }\n }\n\n if (!registeredClass) {\n throw new Error(\n `STI polymorphic query failed: Class '${className}' not found in ObjectRegistry. ` +\n `Ensure the class is registered with @smrt() decorator or available via an installed SMRT manifest.`,\n );\n }\n\n const params = {\n ai: this.options.ai,\n db: this.db,\n _skipLoad: true,\n ...(hydrationOptions.hydrateOnly\n ? {\n _reuseInitializedDb: true,\n _deferRuntimeInitialization: true,\n }\n : {}),\n ...options,\n };\n\n // Instantiate the correct subclass\n const instance = new registeredClass.constructor(params);\n if (hydrationOptions.hydrateOnly) {\n // Models need to distinguish authoritative DB hydration from a fresh\n // `_skipLoad` instance while initialize() captures immutable state.\n instance.markAsPersisted();\n }\n await instance.initialize();\n\n // Ensure _meta_type is set on the instance (fix for issue #442)\n // Use qualified name if available, otherwise keep the input className\n // (which may already be qualified for new data or simple for legacy data).\n // `_meta_type` is a runtime-only STI field; view as a writable bag.\n (instance as unknown as { _meta_type?: string })._meta_type =\n registeredClass?.qualifiedName || className;\n\n return instance as ModelType;\n }\n\n /**\n * Finds an existing record matching `data` or creates it if not found.\n *\n * Look-up priority:\n * 1. `data.id` — query by primary key\n * 2. `data.slug` — query by slug + context\n * 3. Fallback — query by the full `data` object as a WHERE clause\n *\n * If a matching record is found, it is updated with any changed fields from\n * `data` (diffed against the existing record) and saved. If no match is\n * found, `defaults` are merged with `data` and a new record is created.\n *\n * @param data - The field values to find or upsert\n * @param defaults - Extra default values applied only when creating a new record\n * @returns The existing (possibly updated) or newly created object instance\n *\n * @example\n * ```typescript\n * // Find-or-create a tag by slug\n * const tag = await tags.getOrUpsert({ slug: 'javascript', name: 'JavaScript' });\n *\n * // With defaults applied only on creation\n * const user = await users.getOrUpsert(\n * { email: 'alice@example.com' },\n * { role: 'member', active: true },\n * );\n * ```\n *\n * @see {@link create} for always-insert semantics\n * @see {@link get} for read-only lookup\n */\n public async getOrUpsert(\n data: Record<string, unknown>,\n defaults: Record<string, unknown> = {},\n ) {\n const logicalData = this.normalizeLogicalData(data);\n const logicalDefaults = this.normalizeLogicalData(defaults);\n let where: Record<string, unknown> = {};\n const diffData = { ...logicalData };\n if (logicalData.id) {\n where = { id: logicalData.id };\n delete diffData.id;\n delete diffData.slug;\n delete diffData.context;\n } else if (logicalData.slug) {\n where = { slug: logicalData.slug, context: logicalData.context || '' };\n delete diffData.slug;\n delete diffData.context;\n } else {\n where = logicalData;\n }\n // Force a fresh read: this probe decides create-vs-update, so a stale cache\n // hit could update a row that no longer exists or miss one that does.\n const existing = await this.get(where, { cache: false });\n if (existing) {\n const diff = this.getDiffSync(existing, diffData);\n if (diff) {\n Object.assign(existing, diff);\n await existing.save();\n }\n return existing;\n }\n const createData = {\n ...logicalDefaults,\n ...logicalData,\n } as SmrtCreateInput<ModelType>;\n\n return await this.create(createData);\n }\n\n /**\n * Gets differences between an existing object and new data\n *\n * @param existing - Existing object\n * @param data - New data\n * @returns Object containing only the changed fields\n */\n async getDiff(\n existing: SmrtObject | Record<string, unknown>,\n data: Record<string, unknown>,\n ): Promise<Record<string, unknown> | null> {\n return this.getDiffSync(existing, data);\n }\n\n private getDiffSync(\n existing: SmrtObject | Record<string, unknown>,\n data: Record<string, unknown>,\n ): Record<string, unknown> | null {\n const fields = this.getFieldsSync();\n const validKeys = new Set([\n ...Object.keys(fields),\n 'id',\n 'slug',\n 'context',\n ]);\n // `existing` may be a SmrtObject instance (no index signature); read its\n // dynamic fields through a record view.\n const existingRecord = existing as unknown as Record<string, unknown>;\n const diff = Object.keys(data).reduce(\n (acc, key) => {\n if (\n validKeys.has(key) &&\n !this.areEquivalentValues(existingRecord[key], data[key])\n ) {\n acc[key] = data[key];\n }\n return acc;\n },\n {} as Record<string, unknown>,\n );\n\n return Object.keys(diff).length > 0 ? diff : null;\n }\n\n /**\n * Gets field definitions for the collection's item class\n *\n * @returns Object containing field definitions\n */\n async getFields(): Promise<Record<string, CollectionFieldDefinition>> {\n // `fieldsFromClass` returns the open `Record<string, unknown>` field bag;\n // its entries structurally match `CollectionFieldDefinition` (`name`,\n // `type`, `_meta`). View through `unknown` at this boundary.\n return (await fieldsFromClass(this._itemClass)) as unknown as Record<\n string,\n CollectionFieldDefinition\n >;\n }\n\n /**\n * Normalize user input into the model's logical field names before diffing or creation.\n *\n * Accepts both camelCase and snake_case keys while preserving framework meta fields.\n */\n private normalizeLogicalData(\n data: Record<string, unknown>,\n ): Record<string, unknown> {\n const fields = this.getFieldsSync();\n const normalized: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) {\n normalized[key] = value;\n continue;\n }\n\n const camelKey = key.includes('_') ? toCamelCase(key) : key;\n const snakeKey = key.includes('_') ? key : toSnakeCase(key);\n const outputKey =\n key in fields\n ? key\n : camelKey in fields\n ? camelKey\n : snakeKey in fields\n ? snakeKey\n : key;\n\n normalized[outputKey] = value;\n }\n\n return normalized;\n }\n\n /**\n * Preserve persisted core timestamp fields during lightweight hydration.\n */\n private withHydratedCoreFields(\n data: Record<string, unknown>,\n ): Record<string, unknown> {\n const hydratedData = { ...data };\n\n if (\n hydratedData.createdAt !== undefined &&\n hydratedData.created_at === undefined\n ) {\n hydratedData.created_at = hydratedData.createdAt;\n }\n\n if (\n hydratedData.updatedAt !== undefined &&\n hydratedData.updated_at === undefined\n ) {\n hydratedData.updated_at = hydratedData.updatedAt;\n }\n\n return hydratedData;\n }\n\n /**\n * Treat date-equivalent values as unchanged even if their runtime types differ.\n */\n private areEquivalentValues(\n existingValue: unknown,\n nextValue: unknown,\n ): boolean {\n if (existingValue instanceof Date || nextValue instanceof Date) {\n const existingTime = this.toComparableTime(existingValue);\n const nextTime = this.toComparableTime(nextValue);\n\n if (existingTime !== null && nextTime !== null) {\n return existingTime === nextTime;\n }\n }\n\n return existingValue === nextValue;\n }\n\n /**\n * Convert supported date inputs into comparable timestamps.\n */\n private toComparableTime(value: unknown): number | null {\n if (value instanceof Date) {\n const timestamp = value.getTime();\n return Number.isNaN(timestamp) ? null : timestamp;\n }\n\n if (typeof value === 'string' && value.trim()) {\n const parsedDate = new Date(value);\n const timestamp = parsedDate.getTime();\n return Number.isNaN(timestamp) ? null : timestamp;\n }\n\n return null;\n }\n\n /**\n * Gets field definitions synchronously from cache.\n *\n * This method provides sync access to fields for use in query methods,\n * avoiding the async overhead of getFields() on every query.\n * Fields are cached during create() initialization.\n *\n * @returns Map containing field definitions\n * @private\n */\n getFieldsSync(): Record<string, CollectionFieldDefinition> {\n if (this._cachedFields) {\n return this._cachedFields;\n }\n // Fallback to ObjectRegistry sync method if cache not populated\n // This handles edge cases where collection wasn't created via static create()\n const className = this.getResolvedItemClassName();\n const fields = ObjectRegistry.getFields(className);\n // Convert Map to Record for consistency with getFields() return type.\n // Registry `RegisteredField`s are a structural superset of the members the\n // collection reads (`type`, `sensitive`, `_meta`); view through `unknown`.\n return Object.fromEntries(fields) as unknown as Record<\n string,\n CollectionFieldDefinition\n >;\n }\n\n /**\n * Generates database schema for the collection's item class\n *\n * Leverages ObjectRegistry's cached schema for instant retrieval.\n *\n * @returns Schema object for database setup\n */\n async generateSchema() {\n // Always generate fresh schema to ensure latest field mapping is used\n const { generateSchema } = await import('./schema/utils.js');\n return await generateSchema(this._itemClass);\n }\n\n /**\n * Gets the database table name for this collection\n */\n get tableName() {\n if (!this._tableName) {\n // For STI, use the base class's table name from schema (manifest-derived).\n // R5-canon: use the qualified item name as the lookup key so a\n // colliding simple name in another package can't yield the wrong\n // tableStrategy / STI base and route every downstream query\n // (get/list/count/create) to the wrong table.\n const className = this.getResolvedItemClassName();\n const qualifiedName = this.getResolvedItemQualifiedName();\n const tableStrategy = ObjectRegistry.getTableStrategy(qualifiedName);\n const fallbackTableName =\n (this._itemClass as unknown as { SMRT_TABLE_NAME?: string })\n .SMRT_TABLE_NAME || classnameToTablename(className);\n\n if (tableStrategy === 'sti') {\n const stiBase = ObjectRegistry.getSTIBase(qualifiedName);\n if (stiBase) {\n // Use base class's schema tableName (from manifest)\n const baseSchema = ObjectRegistry.getSchema(stiBase);\n if (baseSchema?.tableName) {\n this._tableName = baseSchema.tableName;\n } else {\n // Fallback to own schema tableName\n const ownSchema = ObjectRegistry.getSchema(className);\n this._tableName = ownSchema?.tableName || fallbackTableName;\n }\n } else {\n // Fallback to own schema tableName\n const ownSchema = ObjectRegistry.getSchema(className);\n this._tableName = ownSchema?.tableName || fallbackTableName;\n }\n } else {\n // CTI: Use own schema tableName\n const ownSchema = ObjectRegistry.getSchema(className);\n this._tableName = ownSchema?.tableName || fallbackTableName;\n }\n }\n return this._tableName;\n }\n\n /**\n * Generates a table name from the collection class name\n *\n * @returns Generated table name\n */\n generateTableName() {\n // Convert camelCase/PascalCase to snake_case and pluralize\n const tableName = this._className\n // Insert underscore between lower & upper case letters\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n // Convert to lowercase\n .toLowerCase()\n // Handle basic pluralization rules\n .replace(/([^s])$/, '$1s')\n // Handle special cases ending in 'y'\n .replace(/y$/, 'ies');\n\n return tableName;\n }\n\n /**\n * Deletes a record from the collection by ID\n *\n * Loads the object and calls its delete() method, ensuring all interceptors\n * and lifecycle hooks (beforeDelete/afterDelete) are executed correctly.\n *\n * @param id - The ID of the record to delete\n * @returns Promise resolving to true if deleted, false if not found\n *\n * @example\n * ```typescript\n * const success = await collection.delete('some-uuid');\n * if (success) {\n * console.log('Record deleted');\n * }\n * ```\n */\n public async delete(id: string): Promise<boolean> {\n // Schema already initialized in Collection.create() static factory\n\n // Ensure manifest is loaded for external packages\n await ObjectRegistry.ensureManifestLoaded(this.getResolvedItemClassName());\n\n // Load the object first - if it doesn't exist, return false immediately.\n // Force a fresh read: this probe gates a mutation and decides the return\n // value, so a stale cache hit could delete a phantom (returning true for a\n // row another process already removed) or skip a real delete.\n const instance = await this.get(id, { cache: false });\n if (!instance) {\n return false;\n }\n\n // Call the object's delete() method, which handles:\n // - beforeDelete interceptors\n // - beforeDelete lifecycle hooks\n // - Database deletion\n // - afterDelete lifecycle hooks\n // - afterDelete interceptors\n await instance.delete();\n\n return true;\n }\n\n /**\n * Returns the number of records matching the given filter conditions.\n *\n * Executes a `SELECT COUNT(*)` query with the same WHERE conversion as\n * `list()` (camelCase field names, operator suffixes, STI auto-filtering).\n * `limit`, `offset`, and `orderBy` are not applicable and are ignored.\n *\n * Note: `count()` is never served from the read cache (issue #1498) — only\n * `list()`/`get()` are. On a page that caches `list()`, a `count()` issued\n * in the same request reflects the live database and may briefly diverge\n * from the cached rows within the TTL window.\n *\n * @param options.where - Filter conditions (same syntax as `list()`)\n * @returns Total count of matching records as an integer\n *\n * @example\n * ```typescript\n * const total = await products.count();\n * const activeCount = await products.count({ where: { status: 'active' } });\n * const expensiveCount = await products.count({ where: { 'price >': 100 } });\n * ```\n *\n * @see {@link list} for retrieving the actual records\n */\n public async count(options: { where?: SmrtListWhereClause<ModelType> } = {}) {\n await this.ensureStorageReady();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n const itemClassName = this.getResolvedItemClassName();\n\n // Security (#1540): count() is a list-shaped read, so run the same\n // `beforeList` interceptors (tenant filtering, etc.). Without this, count()\n // bypasses tenant scoping and returns a cross-tenant total even when the\n // matching list() is correctly filtered — leaking row counts across tenants.\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const interceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n itemClassName,\n options as InterceptorListOptions,\n interceptorContext,\n )) ??\n (options as InterceptorListOptions | undefined) ??\n {};\n\n let { where } = interceptedOptions;\n\n // Fix for issue #386: Add _meta_type filter for STI child collections.\n // R5-canon: qualified-key lookup throughout — pass `itemQualifiedName`\n // to both `getTableStrategy` and `getSTIBase` so a colliding\n // simple-name class in another package can't yield the wrong table\n // strategy / STI base.\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n const isSTI = tableStrategy === 'sti';\n\n if (isSTI) {\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n if (stiBase && stiBase !== itemQualifiedName) {\n where = andWhereCondition(where, {\n _meta_type: itemQualifiedName,\n });\n }\n }\n\n // Resolve _meta_type to qualified name if user provided simple class name (Issue #713)\n where = resolveMetaTypeInWhere(where);\n\n // convertWhereKeys is now sync (issue #663) - no await needed\n const { sql: whereSql, values: whereValues } = buildWhere(\n this.convertWhereKeys(where || {}),\n );\n\n const result = await this.db.query(\n `SELECT COUNT(*) as count FROM ${this.tableName} ${whereSql}`,\n ...whereValues,\n );\n\n return toSafeInteger(result.rows[0].count, 'Collection count');\n }\n\n /**\n * Return database-backed distinct values and row counts for one or more\n * column-backed fields.\n *\n * Each requested field is executed as a bounded `GROUP BY` query. Keeping\n * one query per field uses standard SQL while avoiding a full collection\n * read or model hydration. Scalar facet behavior is covered on SQLite and\n * DuckDB; optional PostgreSQL scalar coverage runs in the `test:postgres`\n * lane when `SMRT_TEST_POSTGRES_URL` is configured.\n * The same `beforeList` interceptors as `list()` and `count()` run first,\n * so tenancy and other read scopes are applied to every facet query.\n *\n * Facets group by the value stored in the column. SMRT does not split,\n * unnest, or otherwise interpret array/string-list fields; consumers that\n * need a facet per array member should maintain a scalar join table or use\n * a consumer-specific query. JSON fields are grouped by their stored JSON\n * value and decoded in the returned `value` when the field metadata allows\n * it. Array/JSON encoding behavior is adapter-specific and is covered by\n * the SQLite/DuckDB tests only; the optional PostgreSQL test covers scalar\n * grouping.\n *\n * @param options.fields One or more fields, optionally with per-field limits\n * @param options.where Filter conditions using the same syntax as `list()`\n * @returns One value/count list per requested field, in request order\n *\n * Limits are always bounded: an explicit per-field limit is clamped to\n * {@link MAX_FACET_LIMIT}, the collection's `maxListLimit` when configured,\n * and the lower of those ceilings. When omitted, the effective limit is the\n * collection's `defaultListLimit` or {@link DEFAULT_FACET_LIMIT}, subject to\n * the same ceilings. This keeps facet queries bounded even when collection\n * list bounds are unset.\n */\n public async facets(\n options: SmrtFacetOptions<ModelType>,\n ): Promise<SmrtFacetResult[]> {\n await this.ensureStorageReady();\n\n if (!options || !Array.isArray(options.fields)) {\n throw new Error('Invalid facets option: fields must be an array.');\n }\n if (options.fields.length === 0) {\n throw new Error('Invalid facets option: at least one field is required.');\n }\n if (options.fields.length > MAX_FACET_FIELDS) {\n throw new Error(\n `Invalid facets option: at most ${MAX_FACET_FIELDS} fields are allowed.`,\n );\n }\n\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const interceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n itemClassName,\n options as unknown as InterceptorListOptions,\n interceptorContext,\n )) ??\n (options as unknown as InterceptorListOptions) ??\n {};\n\n let { where } =\n interceptedOptions as unknown as SmrtFacetOptions<ModelType>;\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n const isSTI = tableStrategy === 'sti';\n if (isSTI) {\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n if (stiBase && stiBase !== itemQualifiedName) {\n where = andWhereCondition(where, {\n _meta_type: itemQualifiedName,\n });\n }\n }\n where = resolveMetaTypeInWhere(where);\n\n const { sql: whereSql, values: whereValues } = buildWhere(\n this.convertWhereKeys(where || {}),\n );\n const fields = this.getFieldsSync();\n const interceptedFields = (\n interceptedOptions as unknown as Partial<SmrtFacetOptions<ModelType>>\n ).fields;\n const effectiveFields = interceptedFields ?? options.fields;\n if (!Array.isArray(effectiveFields) || effectiveFields.length === 0) {\n throw new Error(\n 'Invalid facets option: at least one field is required after interception.',\n );\n }\n if (effectiveFields.length > MAX_FACET_FIELDS) {\n throw new Error(\n `Invalid facets option: at most ${MAX_FACET_FIELDS} fields are allowed after interception.`,\n );\n }\n const requested = effectiveFields.map((entry) =>\n typeof entry === 'string' ? { field: entry } : entry,\n );\n const seen = new Set<string>();\n const results: SmrtFacetResult[] = [];\n\n for (const request of requested) {\n if (!request || typeof request.field !== 'string') {\n throw new Error(\n 'Invalid facet field: expected a string or { field, limit }.',\n );\n }\n const field = request.field;\n if (seen.has(field)) {\n throw new Error(\n `Invalid facet field: '${field}' was requested more than once.`,\n );\n }\n seen.add(field);\n\n // Reuse projection validation so facets cannot expose sensitive,\n // readPermission-gated, relationship-only, transient, or unknown\n // fields. The returned SQL is intentionally not used: aggregation needs\n // the same safe column identifier without hydrating a projection row.\n this.resolveProjectionSelect([field], fields, isSTI);\n\n const columnName = this.toDbColumnName(field);\n const quotedColumn = this.quoteProjectionIdentifier(columnName);\n const quotedField = this.quoteProjectionIdentifier(field);\n const requestedLimit =\n request.limit === undefined\n ? (this._defaultListLimit ?? DEFAULT_FACET_LIMIT)\n : assertQueryBound(request.limit, `facet limit for '${field}'`);\n const collectionLimit = this._maxListLimit ?? MAX_FACET_LIMIT;\n const limit = Math.min(\n requestedLimit ?? DEFAULT_FACET_LIMIT,\n collectionLimit,\n MAX_FACET_LIMIT,\n );\n const sql =\n `SELECT ${quotedColumn} AS ${quotedField}, ` +\n `COUNT(*) AS \"__smrt_facet_count\" FROM ${this.tableName} ` +\n `${whereSql} GROUP BY ${quotedColumn} ` +\n `ORDER BY \"__smrt_facet_count\" DESC, ` +\n `CASE WHEN ${quotedColumn} IS NULL THEN 1 ELSE 0 END ASC, ` +\n `${quotedColumn} ASC ` +\n `LIMIT $${whereValues.length + 1}`;\n const params = [...whereValues, limit];\n const queryResult = await this.db.query(sql, ...params);\n\n results.push({\n field,\n values: queryResult.rows.map((row) => {\n const rawValue = row[field];\n const formatted = formatDataJs({ [columnName]: rawValue }, fields);\n const formattedKey = Object.hasOwn(formatted, field)\n ? field\n : toCamelCase(field);\n const value = Object.hasOwn(formatted, formattedKey)\n ? formatted[formattedKey]\n : rawValue;\n return {\n value,\n count: toSafeInteger(\n row.__smrt_facet_count,\n `Facet count for '${field}'`,\n ),\n };\n }),\n });\n }\n\n return results;\n }\n\n /**\n * Return both the tenant/read-scoped total and a filtered count.\n *\n * This intentionally issues two `COUNT(*)` queries rather than reading\n * rows into application memory. `count()` applies `beforeList` for each\n * query, so the unfiltered total remains tenant-scoped while the filtered\n * count adds the caller's `where` conditions.\n */\n public async counts(\n options: { where?: SmrtListWhereClause<ModelType> } = {},\n ): Promise<SmrtCollectionCounts> {\n const total = await this.count();\n const filtered = await this.count(options);\n return { total, filtered };\n }\n\n /**\n * Resolve this collection's STI child discriminator — the qualified item\n * class name to use as a `_meta_type` scope — when the collection's item is\n * an STI **child** (shares a table with sibling subtypes), or `null` for STI\n * bases, CTI, and non-STI item classes.\n *\n * Mirrors the automatic `_meta_type` scoping that `list()` / `get()` apply\n * for STI child collections (#386), using the same `ObjectRegistry` source of\n * truth (`getTableStrategy` + `getSTIBase`). Raw-SQL helpers that bypass\n * `list()` — e.g. the tenant global / with-globals lookups in\n * `@happyvertical/smrt-tenancy` (#1600) — call this to scope a shared STI\n * table to the collection's own subtype, so they never surface sibling rows.\n * Deriving the scope here keeps every consumer consistent with `list()`\n * without each one hand-classifying its collection's table strategy.\n *\n * @returns The qualified `_meta_type` for an STI child collection, or `null`\n * when no `_meta_type` scoping applies (STI base / CTI / non-STI).\n */\n public getStiChildMetaType(): string | null {\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n if (ObjectRegistry.getTableStrategy(itemQualifiedName) !== 'sti') {\n return null;\n }\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n return stiBase && stiBase !== itemQualifiedName ? itemQualifiedName : null;\n }\n\n /**\n * Execute a raw SQL query and hydrate results as collection item instances\n *\n * Provides full SQL power for complex queries (JOINs, CTEs, NOT EXISTS, etc.)\n * while still returning properly hydrated SMRT objects.\n *\n * @param sql - Raw SQL query string (should select from this.tableName)\n * @param params - Query parameters for prepared statement\n * @returns Promise resolving to array of hydrated model instances\n *\n * @example\n * ```typescript\n * // Find meetings without corresponding recaps (NOT EXISTS pattern)\n * const meetings = await meetingCollection.query(`\n * SELECT m.* FROM meetings m\n * WHERE m.start_date < datetime('now')\n * AND NOT EXISTS (\n * SELECT 1 FROM contents c\n * WHERE c.meeting_id = m.id\n * AND c._meta_type = 'MeetingRecap'\n * )\n * ORDER BY m.start_date DESC\n * LIMIT ?\n * `, [10]);\n *\n * // Complex JOIN query\n * const products = await productCollection.query(`\n * SELECT p.* FROM products p\n * INNER JOIN categories c ON p.category_id = c.id\n * WHERE c.name = ? AND p.price > ?\n * ORDER BY p.price ASC\n * `, ['Electronics', 100]);\n * ```\n */\n public async query(\n sql: string,\n params: unknown[] = [],\n options: { allowRawOnTenantScoped?: boolean } = {},\n ): Promise<ModelType[]> {\n await this.ensureStorageReady();\n\n // Execute beforeQuery interceptors (e.g., tenant raw SQL policy)\n const interceptorContext = createInterceptorContext(\n this._itemClass.name,\n 'query',\n this.constructor.name,\n );\n const interceptedQuery = await GlobalInterceptors.executeBeforeQuery(\n this._itemClass.name,\n { sql, params, allowRawOnTenantScoped: options.allowRawOnTenantScoped },\n interceptorContext,\n );\n\n const result = await this.db.query(\n interceptedQuery.sql,\n ...interceptedQuery.params,\n );\n\n // query() is a raw escape hatch documented for SELECTs, but it can run any\n // SQL. A write issued through it bypasses the save()/delete() invalidation\n // hooks, so conservatively bust this table's cache when the statement looks\n // like a mutation (issue #1498). The word-boundary match ignores column\n // names like `updated_at`/`deleted_at`; a false positive (e.g. SELECT ...\n // FOR UPDATE) only costs a cache miss, never a stale read.\n if (\n /\\b(?:insert|update|delete|merge|truncate|replace)\\b/i.test(\n interceptedQuery.sql,\n )\n ) {\n invalidateCollectionCache(resolveDbCacheKey(this.db), this.tableName);\n }\n\n const fields = this.getFieldsSync();\n\n // STI: Check if we need polymorphic hydration.\n // R5-canon: pass the qualified item name so a colliding simple\n // name in another package can't yield the wrong tableStrategy\n // and feed the wrong `isSTI` boolean into `hydrateResultRow`.\n const tableStrategy = ObjectRegistry.getTableStrategy(\n this.getResolvedItemQualifiedName(),\n );\n const isSTI = tableStrategy === 'sti';\n\n const instances = await this.hydrateResultRows(result.rows, fields, isSTI);\n\n // Execute afterQuery interceptors\n return await GlobalInterceptors.executeAfterQuery(\n this._itemClass.name,\n instances,\n interceptorContext,\n );\n }\n\n private async hydrateResultRow(\n row: Record<string, unknown>,\n fields: Record<string, CollectionFieldDefinition>,\n isSTI: boolean,\n ): Promise<ModelType> {\n const formattedData = this.withHydratedCoreFields(\n formatDataJs(row, fields),\n );\n\n const metaType = formattedData._meta_type;\n if (isSTI && metaType) {\n const polymorphicInstance = await this.createPolymorphic(\n typeof metaType === 'string' ? metaType : String(metaType),\n formattedData,\n { hydrateOnly: true },\n );\n return polymorphicInstance;\n }\n\n const instanceParams = {\n ai: this.options.ai,\n db: this.db,\n _skipLoad: true,\n _reuseInitializedDb: true,\n _deferRuntimeInitialization: true,\n ...formattedData,\n };\n\n const instance = new this._itemClass(instanceParams);\n // Mark before initialize() so model lifecycle guards can safely snapshot\n // authoritative hydrated values without probing the database. A fresh\n // caller-created `_skipLoad` instance remains unpersisted.\n instance.markAsPersisted();\n await instance.initialize();\n\n if (isSTI) {\n const registeredClass = ObjectRegistry.getClass(this._itemClass.name);\n // Dynamic STI discriminator write — `_meta_type` is a runtime-only field\n // not in the static SmrtObject shape; view as a writable bag.\n (instance as unknown as { _meta_type?: string })._meta_type =\n registeredClass?.qualifiedName || this._itemClass.name;\n }\n\n return instance;\n }\n\n /**\n * Hydrate rows in result order.\n *\n * A model's initialize() hook may query through the collection database.\n * PostgreSQL transaction adapters bind every query to one pg client, which\n * cannot execute overlapping client.query() calls once pg 9 removes its\n * deprecated per-client queue. Serial hydration therefore protects every\n * model hook without relying on driver queuing and preserves row order.\n */\n private async hydrateResultRows(\n rows: Record<string, unknown>[],\n fields: Record<string, CollectionFieldDefinition>,\n isSTI: boolean,\n ): Promise<ModelType[]> {\n const instances: ModelType[] = [];\n for (const row of rows) {\n instances.push(await this.hydrateResultRow(row, fields, isSTI));\n }\n return instances;\n }\n\n /**\n * Resolve the `_smrt_contexts.owner_id` key for collection-level memory\n * (#2365).\n *\n * `_smrt_contexts` has no tenant column, so before this hook a tenant-scoped\n * collection's learned memory (`remember()`/`recall()`) was filed under one\n * shared `__collection__` key and leaked across tenants. For a tenant-scoped\n * item class under an active tenant context the key becomes\n * `__collection__:<tenantId>`; everything else (tenancy disabled, no active\n * tenant, system context, non-tenant-scoped class) keeps the shared\n * `__collection__` key. Tenant-keyed memory is strictly isolated — a tenant\n * recall does NOT fall back to the shared key, so memory learned outside a\n * tenant context is invisible inside one (and vice versa).\n *\n * Tenant resolution uses the same core-side trust anchors as the generated\n * REST read scope (#1782): `resolveDispatchTenantScope()` for the active\n * tenant and the triple tenant-scoped-class check covering `@smrt()` config,\n * manifest config, and the tenancy-filled `@TenantScoped()` resolver.\n */\n private resolveCollectionMemoryOwnerId(): string {\n const itemClassName = this.getResolvedItemClassName();\n const tenantScoped =\n ObjectRegistry.isTenantScoped(itemClassName) ||\n !!ObjectRegistry.getConfig(itemClassName)?.tenantScoped ||\n isTenantScopedClassResolved(itemClassName);\n if (!tenantScoped) {\n return COLLECTION_MEMORY_OWNER_ID;\n }\n const scope = resolveDispatchTenantScope();\n if (!scope.enforced || !scope.tenantId) {\n return COLLECTION_MEMORY_OWNER_ID;\n }\n return `${COLLECTION_MEMORY_OWNER_ID}:${scope.tenantId}`;\n }\n\n /**\n * Remember collection-level context\n *\n * Stores context applicable to all instances of this collection type.\n * Use for patterns that apply to the entire collection (e.g., default parsing strategies).\n *\n * Under an active tenant context on a tenant-scoped class, memory is keyed\n * per tenant and never shared across tenants (#2365) — see\n * {@link resolveCollectionMemoryOwnerId}.\n *\n * `expiresAt` is stored on the `_smrt_contexts` row as caller-managed metadata.\n * {@link recall} and {@link recallAll} do **not** filter on it, so an expired\n * entry is still returned; filter or delete expired entries yourself.\n *\n * @param options - Context options\n * @returns Promise that resolves when context is stored\n * @example\n * ```typescript\n * // Remember a default parsing strategy for all documents\n * await documentCollection.remember({\n * scope: 'parser/default',\n * key: 'selector',\n * value: { pattern: '.content article' },\n * confidence: 0.8\n * });\n *\n * // Update an existing context entry by specifying id\n * await documentCollection.remember({\n * id: 'existing-context-id',\n * scope: 'parser/default',\n * key: 'selector',\n * value: { pattern: '.content main article' },\n * confidence: 0.85\n * });\n * ```\n */\n public async remember(options: {\n id?: string;\n scope: string;\n key: string;\n value: unknown;\n metadata?: unknown;\n confidence?: number;\n version?: number;\n expiresAt?: Date;\n }): Promise<void> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n const id = options.id || crypto.randomUUID();\n const now = new Date();\n\n // Use upsert() for database-agnostic INSERT OR REPLACE\n // SQLite: INSERT OR REPLACE\n // Postgres/DuckDB: INSERT ... ON CONFLICT ... DO UPDATE\n await this.systemDb.upsert(\n '_smrt_contexts',\n // UNIQUE constraint: (owner_class, owner_id, scope, key, version)\n ['owner_class', 'owner_id', 'scope', 'key', 'version'],\n {\n id,\n owner_class: this._itemClass.name,\n owner_id: this.resolveCollectionMemoryOwnerId(),\n scope: options.scope,\n key: options.key,\n value: JSON.stringify(options.value),\n metadata: options.metadata ? JSON.stringify(options.metadata) : null,\n version: options.version ?? 1,\n confidence: options.confidence ?? 1.0,\n success_count: 0,\n failure_count: 0,\n created_at: now,\n updated_at: now,\n last_used_at: now,\n expires_at: options.expiresAt ?? null,\n },\n );\n }\n\n /**\n * Recall collection-level context\n *\n * Retrieves context that applies to all instances of this collection.\n *\n * @param options - Recall options\n * @returns Promise resolving to the context value or null if not found\n * @example\n * ```typescript\n * // Recall default parsing strategy\n * const strategy = await documentCollection.recall({\n * scope: 'parser/default',\n * key: 'selector',\n * minConfidence: 0.5\n * });\n * ```\n */\n public async recall(options: {\n scope: string;\n key: string;\n includeAncestors?: boolean;\n minConfidence?: number;\n }): Promise<unknown> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n const ownerId = this.resolveCollectionMemoryOwnerId();\n\n // Use single() with template literals for custom SQL query.\n // The query projects `value` (a JSON string) and `confidence`.\n let result: Record<string, unknown> | null;\n if (options.minConfidence !== undefined) {\n result = await this.systemDb.single`\n SELECT value, confidence\n FROM _smrt_contexts\n WHERE owner_class = ${this._itemClass.name}\n AND owner_id = ${ownerId}\n AND scope = ${options.scope}\n AND key = ${options.key}\n AND confidence >= ${options.minConfidence}\n ORDER BY confidence DESC, version DESC\n LIMIT 1\n `;\n } else {\n result = await this.systemDb.single`\n SELECT value, confidence\n FROM _smrt_contexts\n WHERE owner_class = ${this._itemClass.name}\n AND owner_id = ${ownerId}\n AND scope = ${options.scope}\n AND key = ${options.key}\n ORDER BY confidence DESC, version DESC\n LIMIT 1\n `;\n }\n\n if (result) {\n // Guard against a corrupted _smrt_contexts row: a single malformed value\n // must not throw an uncaught SyntaxError out of recall(). Treat it as a\n // miss and continue to the ancestor fallback (#1378). `value` is the\n // queried JSON text column; the try/catch covers a non-string/corrupt row.\n try {\n return JSON.parse(result.value as string);\n } catch (error) {\n logger.warn('Skipping corrupted _smrt_contexts value in recall()', {\n ownerClass: this._itemClass.name,\n scope: options.scope,\n key: options.key,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Hierarchical fallback to parent scopes\n if (options.includeAncestors) {\n const scopeParts = options.scope.split('/');\n while (scopeParts.length > 0) {\n scopeParts.pop();\n const parentScope = scopeParts.join('/') || 'global';\n\n const parentResult = await this.recall({\n ...options,\n scope: parentScope,\n includeAncestors: false,\n });\n\n if (parentResult) return parentResult;\n }\n }\n\n return null;\n }\n\n /**\n * Recall all collection-level context in a scope\n *\n * Returns a Map of key -> value for all collection contexts matching the criteria.\n *\n * @param options - Recall options\n * @returns Promise resolving to Map of key -> value pairs\n * @example\n * ```typescript\n * // Get all default strategies\n * const strategies = await documentCollection.recallAll({\n * scope: 'parser/default',\n * minConfidence: 0.5\n * });\n * ```\n */\n public async recallAll(\n options: {\n scope?: string;\n includeDescendants?: boolean;\n minConfidence?: number;\n } = {},\n ): Promise<Map<string, unknown>> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n const results = new Map<string, unknown>();\n\n let query = `\n SELECT key, value, confidence\n FROM _smrt_contexts\n WHERE owner_class = ? AND owner_id = ?\n `;\n const params: unknown[] = [\n this._itemClass.name,\n this.resolveCollectionMemoryOwnerId(),\n ];\n\n if (options.scope) {\n if (options.includeDescendants) {\n query += ` AND (scope = ? OR scope LIKE ?)`;\n params.push(options.scope, `${options.scope}/%`);\n } else {\n query += ` AND scope = ?`;\n params.push(options.scope);\n }\n }\n\n if (options.minConfidence !== undefined) {\n query += ` AND confidence >= ?`;\n params.push(options.minConfidence);\n }\n\n query += ` ORDER BY confidence DESC`;\n\n const { rows } = await this.systemDb.query(query, ...params);\n\n for (const row of rows) {\n // Skip a corrupted row rather than aborting the whole recallAll() (#1378).\n try {\n results.set(row.key, JSON.parse(row.value));\n } catch (error) {\n logger.warn('Skipping corrupted _smrt_contexts value in recallAll()', {\n ownerClass: this._itemClass.name,\n scope: options.scope,\n key: row.key,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return results;\n }\n\n /**\n * Forget collection-level context\n *\n * Deletes collection context by scope and key.\n *\n * @param options - Context identification\n * @returns Promise that resolves when context is deleted\n * @example\n * ```typescript\n * // Remove a default strategy\n * await documentCollection.forget({\n * scope: 'parser/default',\n * key: 'selector'\n * });\n * ```\n */\n public async forget(options: { scope: string; key: string }): Promise<void> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n await this.systemDb.query(\n `DELETE FROM _smrt_contexts\n WHERE owner_class = ? AND owner_id = ? AND scope = ? AND key = ?`,\n this._itemClass.name,\n this.resolveCollectionMemoryOwnerId(),\n options.scope,\n options.key,\n );\n }\n\n /**\n * Forget all collection-level context in a scope\n *\n * Deletes all collection contexts matching the scope pattern.\n *\n * @param options - Scope options\n * @returns Promise resolving to number of contexts deleted\n * @example\n * ```typescript\n * // Clear all default strategies\n * const count = await documentCollection.forgetScope({\n * scope: 'parser/default',\n * includeDescendants: true\n * });\n * ```\n */\n public async forgetScope(options: {\n scope: string;\n includeDescendants?: boolean;\n }): Promise<number> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n let query = `\n DELETE FROM _smrt_contexts\n WHERE owner_class = ? AND owner_id = ?\n `;\n const params: unknown[] = [\n this._itemClass.name,\n this.resolveCollectionMemoryOwnerId(),\n ];\n\n if (options.includeDescendants) {\n query += ` AND (scope = ? OR scope LIKE ?)`;\n params.push(options.scope, `${options.scope}/%`);\n } else {\n query += ` AND scope = ?`;\n params.push(options.scope);\n }\n\n const { rowCount } = await this.systemDb.query(query, ...params);\n return rowCount || 0;\n }\n\n // ============================================================================\n // Semantic Search Methods\n // ============================================================================\n\n /**\n * Semantic search by text query\n *\n * Generates an embedding for the query text and finds similar objects\n * based on cosine similarity of stored embeddings.\n *\n * Under an active tenant context on a tenant-scoped class, candidates are\n * restricted to the tenant's rows BEFORE top-K ranking (#2365), so results\n * are never starved by — and similarity ranks never leak — other tenants'\n * content.\n *\n * @param query - Text to search for\n * @param options - Search options\n * @param options.field - Specific field to search (defaults to first embedding\n * field). Any configured embedding field, or the configured\n * `combinedField.name`, is accepted.\n * @param options.limit - Maximum results to return (default: 10)\n * @param options.minSimilarity - Minimum similarity threshold 0-1 (default: 0)\n * @param options.where - Additional WHERE filters to apply\n * @returns Promise resolving to array of objects with _similarity score\n *\n * @example\n * ```typescript\n * const results = await articles.semanticSearch('machine learning trends', {\n * limit: 10,\n * minSimilarity: 0.7\n * });\n *\n * for (const article of results) {\n * console.log(`${article.title} (similarity: ${article._similarity})`);\n * }\n *\n * // Search the combined vector built from `combinedField.template`\n * const combined = await articles.semanticSearch('machine learning trends', {\n * field: 'content'\n * });\n * ```\n */\n public async semanticSearch(\n query: string,\n options: {\n field?: string;\n limit?: number;\n minSimilarity?: number;\n where?: SmrtWhereClause<ModelType>;\n } = {},\n ): Promise<Array<ModelType & { _similarity: number }>> {\n const { field, limit = 10, minSimilarity = 0, where } = options;\n\n // Get embedding config\n const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(\n this._itemClass.name,\n );\n\n if (!embeddingConfig) {\n throw new Error(\n `No embedding configuration found for ${this._itemClass.name}. ` +\n `Add embeddings config to @smrt() decorator.`,\n );\n }\n\n // Determine which field to search.\n // `generateEmbeddings()` also stores a vector under the configured\n // `combinedField.name`, and `findSimilar`/`findSimilarToEmbedding` happily\n // search it, so it belongs in the searchable set here too (#2281).\n const searchField = field || embeddingConfig.fields[0];\n const searchableFields = getSearchableEmbeddingFields(embeddingConfig);\n if (!searchableFields.includes(searchField)) {\n throw new Error(\n `Field '${searchField}' is not configured for embeddings on ${this._itemClass.name}. ` +\n `Available fields: ${searchableFields.join(', ')}`,\n );\n }\n\n // Create embedding provider from the resolved class/project config so\n // query embeddings use the same model as stored object embeddings.\n const provider = new EmbeddingProvider(\n {\n dimensions: embeddingConfig.dimensions,\n provider: embeddingConfig.provider,\n localModel: embeddingConfig.localModel,\n aiModel: embeddingConfig.aiModel,\n fallbackToAI: embeddingConfig.fallbackToAI,\n },\n this.ai,\n );\n\n // Generate embedding for query\n const [queryEmbedding] = await provider.embed(query);\n\n // Find similar embeddings\n return this.findSimilarToEmbedding(queryEmbedding, {\n field: searchField,\n limit,\n minSimilarity,\n where,\n });\n }\n\n /**\n * Find objects similar to a given object\n *\n * Uses stored embeddings to find objects most similar to the provided object.\n *\n * @param object - Object to find similar items for (or object ID)\n * @param options - Search options\n * @param options.field - Specific field to compare (defaults to first embedding field)\n * @param options.limit - Maximum results to return (default: 5)\n * @param options.excludeSelf - Whether to exclude the source object (default: true)\n * @returns Promise resolving to array of similar objects with _similarity score\n *\n * @example\n * ```typescript\n * const article = await articles.get('some-article-id');\n * const similar = await articles.findSimilar(article, {\n * limit: 5,\n * excludeSelf: true\n * });\n * ```\n */\n public async findSimilar(\n object: ModelType | string,\n options: {\n field?: string;\n limit?: number;\n excludeSelf?: boolean;\n } = {},\n ): Promise<Array<ModelType & { _similarity: number }>> {\n const { field, limit = 5, excludeSelf = true } = options;\n\n // Get the object if ID was provided\n let sourceObject: ModelType;\n if (typeof object === 'string') {\n const found = await this.get(object);\n if (!found) {\n throw new Error(`Object not found: ${object}`);\n }\n sourceObject = found;\n } else {\n sourceObject = object;\n }\n\n // Get embedding config\n const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(\n this._itemClass.name,\n );\n\n if (!embeddingConfig) {\n throw new Error(\n `No embedding configuration found for ${this._itemClass.name}.`,\n );\n }\n\n // Determine which field to use\n const searchField = field || embeddingConfig.fields[0];\n\n // Get the source object's embedding using consistent model name from EmbeddingProvider\n const provider = new EmbeddingProvider(\n {\n dimensions: embeddingConfig.dimensions,\n provider: embeddingConfig.provider,\n localModel: embeddingConfig.localModel,\n aiModel: embeddingConfig.aiModel,\n fallbackToAI: embeddingConfig.fallbackToAI,\n },\n this.ai,\n );\n const model = provider.getModelName();\n\n const storedEmbedding = await EmbeddingStorage.get(\n this.systemDb,\n this._itemClass.name,\n sourceObject.id as string,\n searchField,\n model,\n );\n\n if (!storedEmbedding) {\n throw new Error(\n `No embedding found for object ${sourceObject.id} field '${searchField}'. ` +\n `Generate embeddings first with object.generateEmbeddings().`,\n );\n }\n\n // Find similar using the embedding\n const where = excludeSelf ? { 'id !=': sourceObject.id } : undefined;\n\n return this.findSimilarToEmbedding(storedEmbedding.embedding, {\n field: searchField,\n limit,\n minSimilarity: 0,\n where,\n });\n }\n\n /**\n * Find objects similar to a raw embedding vector\n *\n * Low-level method for finding objects by embedding similarity.\n *\n * @param embedding - Embedding vector to compare against\n * @param options - Search options\n * @param options.field - Field name to search (defaults to first embedding field)\n * @param options.limit - Maximum results to return (default: 10)\n * @param options.minSimilarity - Minimum similarity threshold 0-1 (default: 0)\n * @param options.where - Additional WHERE filters to apply\n * @returns Promise resolving to array of objects with _similarity score\n *\n * @example\n * ```typescript\n * // Using a pre-computed embedding\n * const results = await articles.findSimilarToEmbedding(myEmbedding, {\n * limit: 10,\n * minSimilarity: 0.5\n * });\n * ```\n */\n public async findSimilarToEmbedding(\n embedding: number[],\n options: {\n field?: string;\n limit?: number;\n minSimilarity?: number;\n where?: SmrtWhereClause<ModelType>;\n } = {},\n ): Promise<Array<ModelType & { _similarity: number }>> {\n const { field, limit = 10, minSimilarity = 0, where } = options;\n\n // Get embedding config\n const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(\n this._itemClass.name,\n );\n\n if (!embeddingConfig) {\n throw new Error(\n `No embedding configuration found for ${this._itemClass.name}.`,\n );\n }\n\n // Determine which field to search\n const searchField = field || embeddingConfig.fields[0];\n\n // Get model name using EmbeddingProvider for consistency\n const provider = new EmbeddingProvider(\n {\n dimensions: embeddingConfig.dimensions,\n provider: embeddingConfig.provider,\n localModel: embeddingConfig.localModel,\n aiModel: embeddingConfig.aiModel,\n fallbackToAI: embeddingConfig.fallbackToAI,\n },\n this.ai,\n );\n const model = provider.getModelName();\n\n // Resolve storage strategy\n const projectConfig = ObjectRegistry.getProjectEmbeddingConfig();\n const storage = projectConfig?.storage || 'json';\n const vector = storage === 'native' ? this.systemDb.vector : undefined;\n\n // Tenant pre-filter (#2365): _smrt_embeddings has no tenant column, so\n // top-K used to rank across ALL tenants and only the final list() below\n // dropped foreign rows — starving results and soft-leaking which tenants\n // have similar content. Resolve the tenant read predicate through the\n // SAME beforeList interceptor pipeline that guards collection reads (an\n // empty where comes back either untouched — tenancy off, class not\n // scoped, bypass/system context — or with exactly the injected tenant\n // predicate, or beforeList throws for `mode: 'required'` without\n // context), then restrict the candidate id set BEFORE ranking.\n const itemClassName = this.getResolvedItemClassName();\n const tenantPrefilterContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const tenantPrefilter = await GlobalInterceptors.executeBeforeList(\n itemClassName,\n { where: {} },\n tenantPrefilterContext,\n );\n let candidateObjectIds: string[] | undefined;\n if (\n tenantPrefilter.where &&\n Object.keys(tenantPrefilter.where).length > 0\n ) {\n const candidateWhere = this.convertWhereKeys(tenantPrefilter.where);\n const { sql: candidateWhereSql, values: candidateValues } =\n buildWhere(candidateWhere);\n // Deliberately unbounded: _smrt_embeddings has no tenant column, so the\n // only exact way to scope ranking is the full id set of the tenant's\n // rows. Capping it here would silently drop rows from the ranking —\n // wrong results, not a perf tweak. The native path chunks the ids\n // against bind-variable limits (see searchSimilar); the id-only\n // projection keeps the row cost minimal. If this becomes a hot spot for\n // very large tenants, the durable fix is a tenant column on\n // _smrt_embeddings, not a cap.\n const { rows: candidateRows } = await this.db.query(\n `SELECT id FROM ${this.tableName} ${candidateWhereSql}`,\n ...candidateValues,\n );\n candidateObjectIds = candidateRows.map((row: Record<string, unknown>) =>\n String(row.id),\n );\n if (candidateObjectIds.length === 0) {\n return [];\n }\n }\n\n // Use unified search method (delegates to native or in-memory)\n const scored = await EmbeddingStorage.searchSimilar(\n this.systemDb,\n this._itemClass.name,\n embedding,\n {\n field: searchField,\n model,\n limit,\n minSimilarity,\n objectIds: candidateObjectIds,\n },\n vector,\n );\n\n if (scored.length === 0) {\n return [];\n }\n\n // Load the objects\n const objectIds = scored.map((s) => s.objectId);\n const similarityMap = new Map(\n scored.map((s) => [s.objectId, s.similarity]),\n );\n\n // Build where clause with additional filters\n const whereClause = where\n ? { 'id in': objectIds, ...where }\n : { 'id in': objectIds };\n\n const objects = await this.list({\n where: whereClause as SmrtWhereClause<ModelType>,\n });\n\n // Add similarity scores to objects and sort by similarity.\n // We directly assign _similarity to avoid losing class properties when\n // spreading; view as a writable bag for the computed transient field.\n const results = objects.map((obj) => {\n (obj as unknown as { _similarity: number })._similarity =\n similarityMap.get(obj.id as string) || 0;\n return obj as ModelType & { _similarity: number };\n });\n\n results.sort((a, b) => b._similarity - a._similarity);\n\n return results;\n }\n\n /**\n * Generate missing embeddings for all objects in the collection\n *\n * Batch generates embeddings for objects that don't have them yet\n * or have stale embeddings.\n *\n * @param options - Generation options\n * @param options.batchSize - Number of objects to process at once (default: 50)\n * @param options.onProgress - Progress callback\n * @returns Promise resolving to generation statistics\n *\n * @example\n * ```typescript\n * const stats = await articles.generateMissingEmbeddings({\n * batchSize: 100,\n * onProgress: ({ completed, total }) => {\n * console.log(`Progress: ${completed}/${total}`);\n * }\n * });\n *\n * console.log(`Generated: ${stats.generated}, Skipped: ${stats.skipped}`);\n * ```\n */\n public async generateMissingEmbeddings(\n options: {\n batchSize?: number;\n onProgress?: (progress: { completed: number; total: number }) => void;\n } = {},\n ): Promise<{ generated: number; skipped: number }> {\n const { batchSize = 50, onProgress } = options;\n\n // Get embedding config\n const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(\n this._itemClass.name,\n );\n\n if (!embeddingConfig) {\n throw new Error(\n `No embedding configuration found for ${this._itemClass.name}.`,\n );\n }\n\n // Count total objects\n const total = await this.count({});\n let completed = 0;\n let generated = 0;\n let skipped = 0;\n\n // Process in batches\n let offset = 0;\n while (offset < total) {\n const batch = await this.list({ limit: batchSize, offset });\n\n for (const obj of batch) {\n try {\n // Check if embeddings are stale. `hasStaleEmbeddings` /\n // `generateEmbeddings` are runtime SmrtObject embedding methods not in\n // the static shape; view as such and call on the receiver (keeps\n // `this` bound).\n const embeddable = obj as unknown as {\n hasStaleEmbeddings(): Promise<boolean>;\n generateEmbeddings(): Promise<unknown>;\n };\n const hasStale = await embeddable.hasStaleEmbeddings();\n if (hasStale) {\n await embeddable.generateEmbeddings();\n generated++;\n } else {\n skipped++;\n }\n } catch (error) {\n logger.warn(`Failed to generate embeddings for ${obj.id}`, {\n error: error instanceof Error ? error.message : error,\n });\n skipped++;\n }\n\n completed++;\n if (onProgress) {\n onProgress({ completed, total });\n }\n }\n\n offset += batchSize;\n }\n\n return { generated, skipped };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"collection.js","names":[],"sources":["../src/collection.ts"],"sourcesContent":["import { createLogger } from '@happyvertical/logger';\nimport { buildWhere, type DatabaseInterface } from '@happyvertical/sql';\nimport type { SmrtClassOptions } from './class';\nimport { SmrtClass } from './class';\nimport {\n buildQueryCacheKey,\n type CollectionCacheConfig,\n ensureCacheInvalidationListener,\n getCachedRows,\n getCacheGeneration,\n invalidateCollectionCache,\n registerCrossProcessCacheInterest,\n resolveDbCacheKey,\n setCachedRows,\n} from './collection-cache';\n// Leaf-module import (not './dispatch'): the dispatch barrel re-exports\n// DispatchCollection, which extends SmrtCollection — importing the barrel from\n// this module would create an evaluation-order cycle.\nimport {\n isTenantScopedClassResolved,\n resolveDispatchTenantScope,\n} from './dispatch/tenant-resolver.js';\nimport { EmbeddingProvider } from './embeddings/provider';\nimport { EmbeddingStorage } from './embeddings/storage';\nimport type { ClassEmbeddingConfig } from './embeddings/types';\nimport {\n createInterceptorContext,\n GlobalInterceptors,\n type ListOptions as InterceptorListOptions,\n resolveGetStringFilter,\n} from './interceptors';\nimport type { SmrtObject } from './object';\nimport {\n DEFAULT_LIST_LIMIT,\n MAX_LIST_LIMIT,\n QueryBoundsError,\n QueryOrderByError,\n} from './query-bounds';\nimport { ObjectRegistry } from './registry';\nimport type { SmrtObjectConstructor } from './registry/types';\nimport { detectEngine } from './schema/ddl/index';\nimport { verifyPersistenceTable } from './schema/table-verifier';\nimport {\n classnameToTablename,\n fieldsFromClass,\n formatDataJs,\n toCamelCase,\n toSafeInteger,\n toSnakeCase,\n} from './utils';\nimport { chunkArray, IN_LIST_CHUNK_SIZE } from './utils/chunk';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * `_smrt_contexts.owner_id` value for collection-level (as opposed to\n * per-object) memory. Tenant-scoped collections suffix the active tenant id\n * (`__collection__:<tenantId>`) so learned memory never crosses tenants —\n * see {@link SmrtCollection.resolveCollectionMemoryOwnerId} (#2365).\n */\nconst COLLECTION_MEMORY_OWNER_ID = '__collection__';\n\n/** Maximum number of independent facet fields accepted by one call. */\nexport const MAX_FACET_FIELDS = 20;\n\n/** Default number of distinct values returned for each facet field. */\nexport const DEFAULT_FACET_LIMIT = DEFAULT_LIST_LIMIT;\n\n/** Hard ceiling for distinct values returned for one facet field. */\nexport const MAX_FACET_LIMIT = MAX_LIST_LIMIT;\n\n/** Maximum number of qualified STI discriminators accepted by one read. */\nexport const MAX_STI_READ_SCOPE_TYPES = 50;\n\n/**\n * Validate an optional collection-level list bound (#2367).\n *\n * @returns the bound, or `undefined` when the option was not supplied\n * @throws {QueryBoundsError} when the option is present but not a positive\n * integer — a `maxListLimit` of `0` or `-1` would silently make every read on\n * the collection return nothing\n */\nfunction assertOptionalListBound(\n value: number | undefined,\n optionName: string,\n): number | undefined {\n if (value === undefined) return undefined;\n if (!Number.isSafeInteger(value) || value < 1) {\n throw new QueryBoundsError(\n `Invalid ${optionName}: expected a positive integer, got ${String(value)}.`,\n 'INVALID_QUERY_BOUNDS',\n { optionName, value },\n );\n }\n return value;\n}\n\n/**\n * Validate a per-query `limit`/`offset` before it is bound (#2367).\n *\n * `0` is allowed — `LIMIT 0` and `OFFSET 0` are both meaningful — but `NaN`,\n * `Infinity`, negatives and fractions are not: they used to be bound verbatim\n * and surface as a driver-level 500 for what is a caller error.\n *\n * @returns the bound, or `undefined` when the caller supplied none\n * @throws {QueryBoundsError} when the value is not a non-negative integer\n */\nfunction assertQueryBound(\n value: number | undefined,\n parameterName: string,\n): number | undefined {\n if (value === undefined || value === null) return undefined;\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new QueryBoundsError(\n `Invalid ${parameterName}: expected a non-negative integer, got ${String(value)}.`,\n 'INVALID_QUERY_BOUNDS',\n { parameterName, value },\n );\n }\n return value;\n}\n\n/**\n * Resolve _meta_type in WHERE clause from simple class name to qualified name (Issue #713)\n *\n * This helper function allows queries like { _meta_type: 'Image' } to work correctly\n * when the database stores qualified names like '@happyvertical/smrt-assets:Image'.\n *\n * @param where - The original WHERE clause object\n * @returns Modified WHERE clause with qualified _meta_type, or original if no resolution needed\n */\nfunction resolveMetaTypeInWhere<T extends Record<string, unknown>>(\n where: T | T[][] | undefined,\n): T | T[][] | undefined {\n if (Array.isArray(where)) {\n return where.map((group) =>\n group.map((condition) => resolveMetaTypeInWhere(condition) as T),\n );\n }\n if (!where?._meta_type || typeof where._meta_type !== 'string') {\n return where;\n }\n\n const metaTypeValue = where._meta_type as string;\n\n // Only resolve if it's a simple class name (no ':' means not qualified)\n if (metaTypeValue.includes(':')) {\n return where;\n }\n\n const registeredClass = ObjectRegistry.getClass(metaTypeValue);\n if (registeredClass?.qualifiedName) {\n return {\n ...where,\n _meta_type: registeredClass.qualifiedName,\n };\n }\n\n return where;\n}\n\n/** Add an AND predicate without collapsing a caller's DNF OR branches. */\nfunction andWhereCondition<T extends Record<string, unknown>>(\n where: T | T[][] | undefined,\n condition: T,\n): T | T[][] {\n if (Array.isArray(where)) {\n return where.map((group) => [condition, ...group]);\n }\n // The collection-owned predicate is an enforcement boundary (for example,\n // an STI child collection's discriminator), so it must win when a caller\n // supplies the same key.\n return { ...(where ?? {}), ...condition } as T;\n}\n\n/**\n * Field names that carry a stored embedding vector for a class (Issue #2281)\n *\n * `SmrtObject.generateEmbeddings()` writes one vector per configured field and,\n * when `combinedField` is set, an extra vector under `combinedField.name`. Any\n * of those names is a legitimate target for the collection's semantic-search\n * methods, so validation and error messages should describe the whole set.\n *\n * @param config - Resolved embedding configuration for the class\n * @returns Configured field names, plus the combined field name when distinct\n */\nfunction getSearchableEmbeddingFields(\n config: Pick<ClassEmbeddingConfig, 'fields' | 'combinedField'>,\n): string[] {\n const combinedName = config.combinedField?.name;\n if (!combinedName || config.fields.includes(combinedName)) {\n return config.fields;\n }\n return [...config.fields, combinedName];\n}\n\n/**\n * Keys from SmrtObject and SmrtClass that should not appear as user-facing\n * create/update input fields. These are framework-internal properties.\n */\ntype SmrtInternalKeys =\n | keyof SmrtClass\n | '_tableName'\n | '_loadedRelationships'\n | '_id'\n | '_slug'\n | '_context'\n | '_ai'\n | '_fs'\n | '_db'\n | '_className'\n | 'options'\n | '_skipLoad'\n | '_skipAutoEmbeddings'\n | '_extractingFields'\n | 'initialize'\n | 'save'\n | 'delete'\n | 'loadFromId'\n | 'loadFromSlug'\n | 'is'\n | 'do'\n | 'toJSON'\n | 'transformJSON';\n\n/**\n * Input type for `collection.create()`. Constrains input to valid model fields\n * while preserving backwards compatibility via an index signature escape hatch.\n *\n * Provides IDE autocompletion for known fields from the model class while still\n * accepting arbitrary keys for dynamic usage patterns (e.g., STI meta fields).\n *\n * @example\n * ```typescript\n * // IDE will suggest: name, price, quantity, categoryId, etc.\n * await productCollection.create({\n * name: 'Widget',\n * price: 9.99,\n * _meta_type: 'SpecialProduct', // STI discriminator\n * });\n * ```\n */\nexport type SmrtCreateInput<T extends SmrtObject> = Partial<\n Omit<\n {\n [K in keyof T as T[K] extends (...args: never[]) => unknown\n ? never\n : K]: T[K];\n },\n SmrtInternalKeys\n >\n> & {\n /** STI discriminator for polymorphic creation */\n _meta_type?: string;\n /** Skip database loading (framework internal) */\n _skipLoad?: boolean;\n /** Skip save-time embedding auto-generation (framework internal) */\n _skipAutoEmbeddings?: boolean;\n /**\n * Persist via a plain INSERT instead of the natural-key upsert: a collision\n * on the primary key or any unique constraint raises a typed error rather\n * than dedup-updating an existing row in place. For write paths where row\n * identity is an explicit client-supplied `id` (sync-apply, #1759).\n */\n _insertOnly?: boolean;\n /** Allow arbitrary additional fields for dynamic usage */\n [key: string]: unknown;\n};\n\n/**\n * Basic WHERE clause type for point lookups and AND-only collection reads.\n *\n * Uses `Record<string, unknown>` because WHERE keys often include operator\n * suffixes (e.g., `'price >'`, `'name like'`, `'status in'`) which can't be\n * expressed as mapped types from model properties. Runtime validation in\n * `convertWhereKeys()` handles field and operator checking.\n */\nexport type SmrtWhereClause<T extends SmrtObject> = Record<string, unknown>;\n\n/**\n * Bounded disjunctive-normal-form WHERE clause for collection list/count/facet\n * reads. Inner arrays are ANDed and outer arrays are ORed by the SQL adapter.\n * Keeping this at the collection boundary preserves field validation, STI, and\n * tenant interception for callers that need an allowlisted `any` filter.\n */\nexport type SmrtListWhereClause<T extends SmrtObject> =\n | SmrtWhereClause<T>\n | SmrtWhereClause<T>[][];\n\ntype SmrtModelData<T extends SmrtObject> = Omit<\n {\n [K in keyof T as T[K] extends (...args: never[]) => unknown\n ? never\n : K]: T[K];\n },\n SmrtInternalKeys\n>;\n\n/**\n * Logical field names accepted by `collection.list({ select })`.\n *\n * `select` is intentionally a column-backed projection primitive: callers pass\n * SMRT field names such as `parentId`, and the collection maps them to database\n * columns such as `parent_id` while returning rows keyed by the original SMRT\n * field names.\n */\nexport type SmrtSelectField<T extends SmrtObject> =\n | Extract<keyof SmrtModelData<T>, string>\n | 'id'\n | 'slug'\n | 'context'\n | 'created_at'\n | 'updated_at'\n | '_meta_type';\n\ntype SmrtCoreSelectedFieldData = {\n id: string | null | undefined;\n slug: string | null | undefined;\n context: string;\n created_at: Date | null | undefined;\n updated_at: Date | null | undefined;\n _meta_type: string;\n};\n\n/**\n * Plain row shape returned by `collection.list({ select })`.\n */\nexport type SmrtSelectedRow<\n T extends SmrtObject,\n Select extends readonly SmrtSelectField<T>[],\n> = {\n [Field in Select[number]]: Field extends keyof SmrtCoreSelectedFieldData\n ? SmrtCoreSelectedFieldData[Field]\n : Field extends keyof SmrtModelData<T>\n ? SmrtModelData<T>[Field]\n : unknown;\n};\n\n/** A field requested by {@link SmrtCollection.facets}. */\nexport interface SmrtFacetRequest<T extends SmrtObject> {\n /** A column-backed SMRT field name (for example, `status`). */\n field: SmrtSelectField<T>;\n /**\n * Maximum number of values to return. Omit to use the collection's\n * `defaultListLimit` or 50, subject to `maxListLimit` and the hard maximum\n * facet limit.\n */\n limit?: number;\n}\n\n/** One distinct facet value and the number of matching rows containing it. */\nexport interface SmrtFacetValue {\n value: unknown;\n count: number;\n}\n\n/** Database-backed values returned for one requested facet field. */\nexport interface SmrtFacetResult {\n field: string;\n values: SmrtFacetValue[];\n}\n\n/** Options for a database-backed facet query. */\nexport interface SmrtFacetOptions<T extends SmrtObject> {\n /**\n * One or more fields (at most {@link MAX_FACET_FIELDS}). Each field may\n * optionally carry its own value limit.\n */\n fields: readonly (SmrtSelectField<T> | SmrtFacetRequest<T>)[];\n /** The same SMRT WHERE syntax accepted by {@link SmrtCollection.list}. */\n where?: SmrtListWhereClause<T>;\n /** Explicit sibling discriminator scope for an STI child collection. */\n stiScope?: SmrtStiReadScope;\n}\n\n/** Exact unfiltered and filtered row counts for a list scope. */\nexport interface SmrtCollectionCounts {\n total: number;\n filtered: number;\n}\n\n/**\n * Explicit, bounded discriminator scope for reads through an STI child\n * collection. Every entry must be a registered qualified type in the same STI\n * hierarchy as the collection item.\n */\nexport interface SmrtStiReadScope {\n types: readonly string[];\n}\n\nexport interface SmrtListOptions<ModelType extends SmrtObject> {\n where?: SmrtListWhereClause<ModelType>;\n offset?: number;\n limit?: number;\n orderBy?: string | string[];\n /**\n * Column-backed field projection. When present, `list()` returns plain rows\n * instead of hydrated model instances.\n */\n select?: readonly SmrtSelectField<ModelType>[];\n /**\n * Relationships to eagerly load. Only supported for hydrated list() calls.\n */\n include?: string[];\n /**\n * Opt-in read-through cache for this call (issue #1498).\n */\n cache?: CollectionCacheConfig | false;\n /**\n * Opt in to reading an allowlist of sibling types through an STI child\n * collection. Omit to retain the child-only discriminator scope.\n */\n stiScope?: SmrtStiReadScope;\n}\n\n/**\n * Declarative latest-row configuration for a declared `@oneToMany` relation.\n *\n * `orderBy` determines which related row wins for each parent. `sortBy`, when\n * supplied, orders parent rows by a field on that winning row before the\n * parent's own `limit`/`offset` are applied. Related rows are returned as\n * plain selected data rather than hydrated objects so this remains a bounded\n * read primitive.\n */\nexport interface SmrtLatestRelatedOptions {\n /** The `@oneToMany` field on the parent collection's item class. */\n relation: string;\n /** Related field(s) used to select the latest row, e.g. `createdAt DESC`. */\n orderBy: string | string[];\n /** Column-backed related fields returned in `latestRelated`. Defaults to the declared related primary key. */\n select?: readonly string[];\n /** Related field(s) used to order parent rows by the selected latest row. */\n sortBy?: string | string[];\n}\n\n/** Options accepted by {@link SmrtCollection.listWithLatestRelated}. */\nexport type SmrtLatestRelatedListOptions<ModelType extends SmrtObject> = Omit<\n SmrtListOptions<ModelType>,\n 'select' | 'include' | 'cache'\n> & {\n latestRelated: SmrtLatestRelatedOptions;\n};\n\n/** A hydrated parent paired with its selected latest related row, if present. */\nexport interface SmrtLatestRelatedRow<ModelType extends SmrtObject> {\n parent: ModelType;\n latestRelated: Record<string, unknown> | null;\n}\n\n/**\n * Minimal runtime shape of a field definition as consumed by the collection's\n * query/hydration helpers (`getFieldsSync()`, `convertWhereKeys()`,\n * `formatDataJs()`). These come from `fieldsFromClass()` /\n * `ObjectRegistry.getFields()` and only a few members are read here: `type`\n * (for `formatDataJs` value coercion) and the field-level access markers. The\n * index signature keeps the bag open for the other registry-attached members\n * without forcing `any`.\n */\ninterface CollectionFieldDefinition {\n type?: string;\n primaryKey?: boolean;\n transient?: boolean;\n sensitive?: boolean;\n readPermission?: string;\n _meta?: {\n sensitive?: boolean;\n readPermission?: string;\n transient?: boolean;\n primaryKey?: boolean;\n __smrtSystemField?: boolean;\n } & Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Configuration options for SmrtCollection\n */\nexport interface SmrtCollectionOptions extends SmrtClassOptions {\n /**\n * Page size `list()` applies when the caller passes no `limit` (#2367).\n *\n * Unset by default — `list()` is the framework's bulk-read primitive and its\n * internal callers expect every matching row, so an implicit default would\n * truncate correct queries. Set this on collections whose reads are always\n * paged.\n */\n defaultListLimit?: number;\n\n /**\n * Ceiling `list()` clamps every `limit` down to (#2367).\n *\n * Unset by default. The generated REST/SvelteKit/MCP surfaces enforce\n * `MAX_LIST_LIMIT` on their own untrusted input regardless of this option;\n * set it to extend the same ceiling to direct programmatic reads.\n */\n maxListLimit?: number;\n}\n\n// S4 #1579: the constructor `options` and static `create(options)` params are\n// left as `any` deliberately. This type is satisfied by every concrete model\n// class (`static readonly _itemClass = Product`), whose constructor/`create`\n// option bag is contravariant — narrowing to `SmrtCreateInput<ModelType>` or\n// `unknown` rejects those assignments. Mirrors the `SmrtObjectConstructor`\n// escape hatch in registry/types.\nexport type SmrtCollectionItemClass<ModelType extends SmrtObject> = (new (\n // biome-ignore lint/suspicious/noExplicitAny: contravariant constructor option bag — narrowing to `SmrtCreateInput<ModelType>`/`unknown` rejects every concrete `static _itemClass = Product` assignment. Mirrors `SmrtObjectConstructor`. S4 #1579.\n options: any,\n) => ModelType) & {\n // biome-ignore lint/suspicious/noExplicitAny: contravariant `create()` option bag — same variance constraint as the constructor above. S4 #1579.\n create(options: any): ModelType | Promise<ModelType>;\n};\n\n/**\n * Typed CRUD collection for a specific `SmrtObject` subclass.\n *\n * Each concrete collection pairs with exactly one model class via the required\n * `static readonly _itemClass` property. Use the static `create()` factory —\n * not `new` — to get a fully initialized, ready-to-query instance.\n *\n * Key capabilities:\n * - **Query**: `list()`, `get()`, `count()`, `query()` (raw SQL), `listByIds()`\n * - **Mutation**: `create()`, `getOrUpsert()`, `delete()`\n * - **Eager loading**: pass `include: ['fieldName']` to `list()` to avoid N+1 queries\n * - **STI support**: automatically filters by `_meta_type` for child collections;\n * polymorphically hydrates the correct subclass when listing/getting\n * - **Interceptors**: all read/write paths run `GlobalInterceptors` hooks\n * (used by multi-tenancy, audit logging, etc.)\n *\n * @typeParam ModelType - The `SmrtObject` subclass managed by this collection\n *\n * @example\n * ```typescript\n * @smrt()\n * class Product extends SmrtObject {\n * name: string = '';\n * price: number = 0.0;\n * }\n *\n * @smrt()\n * class Products extends SmrtCollection<Product> {\n * static readonly _itemClass = Product;\n * }\n *\n * const products = await Products.create({ db: myDb });\n * const widget = await products.create({ name: 'Widget', price: 9.99 });\n * const all = await products.list({ where: { 'price >': 5 }, orderBy: 'price ASC' });\n * ```\n */\nexport class SmrtCollection<ModelType extends SmrtObject> extends SmrtClass {\n /**\n * Cached fields for sync access during queries.\n * Populated during create() to avoid async getFields() calls on every query.\n * @private\n */\n private _cachedFields: Record<string, CollectionFieldDefinition> | null =\n null;\n\n /**\n * Opt-in list bounds (#2367). See {@link SmrtCollectionOptions.defaultListLimit}\n * and {@link SmrtCollectionOptions.maxListLimit}.\n * @private\n */\n private _defaultListLimit: number | undefined;\n private _maxListLimit: number | undefined;\n\n private getRegisteredItemClass() {\n return (\n ObjectRegistry.getClassByConstructor(\n this._itemClass as unknown as SmrtObjectConstructor,\n ) || ObjectRegistry.getClass(this._itemClass.name)\n );\n }\n\n private getResolvedItemClassName(): string {\n return this.getRegisteredItemClass()?.name || this._itemClass.name;\n }\n\n private getResolvedItemQualifiedName(): string {\n const registered = this.getRegisteredItemClass();\n return (\n registered?.qualifiedName || registered?.name || this._itemClass.name\n );\n }\n\n /** Apply and validate this collection's owned STI discriminator predicate. */\n private applyStiReadScope(\n where: SmrtListWhereClause<ModelType> | undefined,\n stiScope: SmrtStiReadScope | undefined,\n ): SmrtListWhereClause<ModelType> | undefined {\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n const isSTI = ObjectRegistry.getTableStrategy(itemQualifiedName) === 'sti';\n const stiBase = isSTI ? ObjectRegistry.getSTIBase(itemQualifiedName) : null;\n const isChild = stiBase !== null && stiBase !== itemQualifiedName;\n\n if (stiScope === undefined) {\n return isChild\n ? andWhereCondition(where, { _meta_type: itemQualifiedName })\n : where;\n }\n if (\n !stiScope ||\n typeof stiScope !== 'object' ||\n Array.isArray(stiScope) ||\n Object.keys(stiScope).some((key) => key !== 'types') ||\n !Array.isArray(stiScope.types)\n ) {\n throw new Error(\n 'Invalid stiScope: expected { types: readonly qualified STI type[] }.',\n );\n }\n if (!isChild) {\n throw new Error(\n 'Invalid stiScope: explicit discriminator scopes require an STI child collection.',\n );\n }\n if (stiScope.types.length === 0) {\n throw new Error('Invalid stiScope: types must not be empty.');\n }\n if (stiScope.types.length > MAX_STI_READ_SCOPE_TYPES) {\n throw new Error(\n `Invalid stiScope: at most ${MAX_STI_READ_SCOPE_TYPES} types are allowed.`,\n );\n }\n\n const seen = new Set<string>();\n for (const type of stiScope.types) {\n if (\n typeof type !== 'string' ||\n !/^@[a-z0-9][a-z0-9._-]*(?:\\/[a-z0-9][a-z0-9._-]*)?:[A-Za-z_$][A-Za-z0-9_$]*$/.test(\n type,\n )\n ) {\n throw new Error(\n `Invalid stiScope type: '${String(type)}' must be a qualified SMRT type.`,\n );\n }\n if (seen.has(type)) {\n throw new Error(`Invalid stiScope: duplicate type '${type}'.`);\n }\n seen.add(type);\n\n const registered = ObjectRegistry.getClass(type);\n if (!registered || registered.qualifiedName !== type) {\n throw new Error(`Invalid stiScope: unknown type '${type}'.`);\n }\n const typeBase = ObjectRegistry.getSTIBase(type);\n if (\n ObjectRegistry.getTableStrategy(type) !== 'sti' ||\n typeBase !== stiBase\n ) {\n throw new Error(\n `Invalid stiScope: type '${type}' does not share STI root '${stiBase}'.`,\n );\n }\n }\n\n return andWhereCondition(where, {\n '_meta_type in': [...stiScope.types],\n });\n }\n\n /**\n * Resolve the identifier whitelist and the sensitive-column blocklist that\n * every caller-supplied identifier on this collection is checked against.\n *\n * Extracted from `convertWhereKeys()` so `orderBy` validation enforces the\n * *same* sets (#2367). `where` (#1540) and `select` (#1902) both rejected\n * sensitive columns while `orderBy` accepted any identifier, which made\n * `?orderBy=api_secret&limit=1` an ordering oracle over a column the same\n * request is forbidden to filter on or project.\n *\n * @returns the snake_case identifiers this collection accepts, the sensitive\n * names it must refuse, and whether whitelist enforcement applies at all —\n * classes with no registered fields (manifest-less inline test classes,\n * #869) have nothing to validate against and fall through to SQL.\n * @private\n */\n private collectQueryableFieldNames(\n fields: Record<string, CollectionFieldDefinition>,\n ): {\n validFieldNames: Set<string>;\n sensitiveFieldNames: Set<string>;\n readPermissionFieldNames: Set<string>;\n skipFieldValidation: boolean;\n } {\n // `toSnakeCase()` — the SAME function `SchemaGenerator` names columns with\n // (`schema/generator.ts`) — so this set holds real column names. Note it\n // STRIPS a leading underscore, while `toDbColumnName()` preserves one; the\n // two disagree for a declared `_rank` field, whose actual column is `rank`.\n // `orderBy` reconciles that at the term (see `buildOrderBySql`).\n const validFieldNames = new Set(\n Object.keys(fields).map((f) => toSnakeCase(f)),\n );\n\n // Security (#1540): collect snake_case names of `@field({ sensitive: true })`\n // columns so they can't be used as `where` filter keys. Filtering on a\n // secret column turns the generated REST/MCP `where` surface into a value\n // oracle (e.g. `apiSecret[like]=sk-%`), exfiltrating the secret one\n // character at a time even though it's excluded from serialization.\n const sensitiveFieldNames = this.collectSensitiveFieldNames(fields);\n\n // Security (#2367): the same collection for `@field({ readPermission })`\n // columns. These are stripped from `toPublicJSON()` for callers without the\n // permission and are refused outright by `select`, so ordering by one is the\n // same oracle in a different door. Kept in its own set because `where`\n // (#1540) deliberately does not consult it and this must not change that.\n const readPermissionFieldNames =\n this.collectReadPermissionFieldNames(fields);\n\n // Add standard SMRT fields that are always valid\n validFieldNames.add('id');\n validFieldNames.add('slug');\n validFieldNames.add('context');\n validFieldNames.add('created_at');\n validFieldNames.add('updated_at');\n\n // Add STI discriminator field for polymorphic queries.\n // R5-canon: use the qualified item name as the lookup key so a\n // same-simple-name class in another package can't yield the wrong\n // tableStrategy.\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n if (tableStrategy === 'sti') {\n // Add both with and without leading underscore (toSnakeCase strips leading _)\n validFieldNames.add('_meta_type');\n validFieldNames.add('meta_type');\n validFieldNames.add('_meta_data');\n validFieldNames.add('meta_data');\n\n // Issue #869: For STI classes, also include fields from all ancestor classes\n // This ensures child class collections can filter by parent class fields.\n //\n // inheritanceChain entries are qualified names (when the\n // registration has one). Compare self against the qualified form;\n // the framework-base sentinels stay as simple names since they're\n // never registered. The simple-name `itemClassName` is also checked\n // as a defensive fall-through for unqualified registrations.\n const inheritanceChain =\n ObjectRegistry.getInheritanceChain(itemQualifiedName);\n for (const ancestorName of inheritanceChain) {\n if (\n ancestorName === 'SmrtObject' ||\n ancestorName === 'SmrtClass' ||\n ancestorName === itemQualifiedName ||\n ancestorName === itemClassName\n ) {\n continue; // Skip framework base classes and self (already included)\n }\n const ancestorFields = ObjectRegistry.getFields(ancestorName);\n for (const fieldName of ancestorFields.keys()) {\n validFieldNames.add(toSnakeCase(fieldName));\n }\n this.collectSensitiveFieldNames(ancestorFields, sensitiveFieldNames);\n this.collectReadPermissionFieldNames(\n ancestorFields,\n readPermissionFieldNames,\n );\n }\n\n // Security (#1540): a base collection serializes (and so must protect)\n // STI descendant fields too — a child's `@meta` sensitive field lives in\n // `_meta_data` and would otherwise be probe-able via `_meta_data.<prop>`\n // from the base collection. Mirror toJSON()/getSensitiveFieldNames().\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n if (stiBase) {\n for (const descendant of ObjectRegistry.getDescendants(stiBase)) {\n const descendantFields = ObjectRegistry.getFields(descendant);\n this.collectSensitiveFieldNames(\n descendantFields,\n sensitiveFieldNames,\n );\n this.collectReadPermissionFieldNames(\n descendantFields,\n readPermissionFieldNames,\n );\n }\n }\n }\n\n // Issue #869: If no fields are registered (no manifest for test classes),\n // skip field validation. Invalid fields will fail at SQL level instead.\n // This commonly happens for inline test classes decorated with @smrt().\n return {\n readPermissionFieldNames,\n skipFieldValidation: Object.keys(fields).length === 0,\n sensitiveFieldNames,\n validFieldNames,\n };\n }\n\n /**\n * Build the `ORDER BY` clause for `list()`, validating every term against the\n * same whitelist and sensitive-column blocklist as `where` and `select`\n * (#2367).\n *\n * `orderBy` is interpolated UNPARAMETERIZED into the identifier position, and\n * it arrives from the generated REST/MCP/SvelteKit surfaces as caller input.\n * The identifier regex here has always blocked injection; what it did not\n * block was ordering by a column the caller may not read. Sorting is a\n * comparison, and a comparison against a secret is an oracle: repeatedly\n * requesting `?orderBy=api_secret&limit=1` (with a shrinking `where`) walks\n * the secret's value ordering without ever serializing the column.\n *\n * @param orderBy - one `'<field> [ASC|DESC]'` term or an array of them\n * @param fields - the collection's cached field definitions\n * @returns the `' ORDER BY ...'` fragment, or `''` when no ordering was asked\n * for\n * @throws {QueryOrderByError} on a malformed term, an unknown column, or a\n * sensitive column — all rendered as a 400 by the generated surfaces\n * @private\n */\n private buildOrderBySql(\n orderBy: string | string[] | undefined,\n fields: Record<string, CollectionFieldDefinition>,\n ): string {\n if (!orderBy) return '';\n\n const orderByItems = Array.isArray(orderBy) ? orderBy : [orderBy];\n if (orderByItems.length === 0) return '';\n\n const itemClassName = this.getResolvedItemClassName();\n const {\n readPermissionFieldNames,\n sensitiveFieldNames,\n skipFieldValidation,\n validFieldNames,\n } = this.collectQueryableFieldNames(fields);\n\n // Column → definition, so a term can be checked for column-backing after it\n // passes the name whitelist. Keyed by `toSnakeCase` because that is what\n // `SchemaGenerator` names the column with — a declared `_rank` field lives\n // in a column called `rank`, not `_rank`.\n const definitionByColumn = new Map<string, CollectionFieldDefinition>();\n for (const [fieldName, fieldDef] of Object.entries(fields)) {\n definitionByColumn.set(toSnakeCase(fieldName), fieldDef);\n }\n\n // `id`/`slug`/`context` are added to the whitelist unconditionally, but the\n // schema generator omits them for a class that declares its own primary key.\n // Without this, `?orderBy=id` on such a model passes the whitelist, matches\n // no definition, and reaches the driver as `no such column: id` — a 500.\n // Same derivation `resolveProjectionSelect` uses for `select`.\n const customPrimaryKey = this.hasCustomPrimaryKey(fields);\n const registeredFields = ObjectRegistry.getFields(\n this.getResolvedItemQualifiedName(),\n );\n const explicitFieldNames = new Set(\n (registeredFields.size > 0\n ? registeredFields\n : ObjectRegistry.getFields(itemClassName)\n ).keys(),\n );\n\n const terms = orderByItems.map((item) => {\n const [field, direction = 'ASC'] = String(item).trim().split(/\\s+/);\n\n // Validate field name\n if (!/^[a-zA-Z0-9_]+$/.test(field)) {\n throw new QueryOrderByError(\n `Invalid field name for ordering: ${field}`,\n `Invalid field name for ordering: ${field}`,\n 'INVALID_ORDER_BY',\n { field },\n );\n }\n\n // Validate direction\n const normalizedDirection = direction.toUpperCase();\n if (normalizedDirection !== 'ASC' && normalizedDirection !== 'DESC') {\n throw new QueryOrderByError(\n `Invalid sort direction: ${direction}. Must be ASC or DESC.`,\n `Invalid sort direction: ${direction}. Must be ASC or DESC.`,\n 'INVALID_ORDER_BY',\n { direction },\n );\n }\n\n // Resolve the term to a real column. The two normalizations disagree on a\n // leading underscore, and each is right for a different case:\n //\n // - a DECLARED field is named by `toSnakeCase`, which strips it, so\n // `_rank` lives in a column called `rank`;\n // - the STI framework columns `_meta_type`/`_meta_data` really do carry\n // the underscore and are never declared fields.\n //\n // Prefer the declared column when one exists, otherwise keep the\n // underscore-preserving form so `_metaType` still reaches `_meta_type`.\n // Emitting the resolved name (rather than one normalization applied\n // blindly) is what keeps a term that passes the whitelist from naming a\n // column that does not exist.\n const strippedName = toSnakeCase(field);\n const columnName = definitionByColumn.has(strippedName)\n ? strippedName\n : this.toDbColumnName(field);\n\n // Security (#2367): refuse sensitive columns before the whitelist check,\n // so the rejection reason for a secret is never \"no such field\" (which\n // would itself confirm the column exists elsewhere).\n if (\n sensitiveFieldNames.has(columnName) ||\n sensitiveFieldNames.has(field)\n ) {\n throw new QueryOrderByError(\n `Invalid orderBy field: '${field}'. Ordering by sensitive fields is not allowed.`,\n `Invalid orderBy field: '${field}'. Ordering by sensitive fields is not allowed.`,\n 'INVALID_ORDER_BY_SENSITIVE',\n { field },\n );\n }\n\n // Security (#2367): same for `@field({ readPermission })` columns.\n // `toPublicJSON()` redacts them per caller and `select` refuses them, so\n // an ordering over one is the same oracle through a different door.\n if (\n readPermissionFieldNames.has(columnName) ||\n readPermissionFieldNames.has(field)\n ) {\n throw new QueryOrderByError(\n `Invalid orderBy field: '${field}'. Ordering by permission-gated fields is not allowed.`,\n `Invalid orderBy field: '${field}'. Ordering by permission-gated fields is not allowed.`,\n 'INVALID_ORDER_BY_RESTRICTED',\n { field },\n );\n }\n\n // Whitelist, mirroring `where`. Skipped for manifest-less inline test\n // classes with no registered fields (#869), exactly as `where` does.\n if (!skipFieldValidation && !validFieldNames.has(columnName)) {\n throw new QueryOrderByError(\n `Invalid orderBy field: '${field}'. Field does not exist on ${itemClassName}. ` +\n `Valid fields: ${Array.from(validFieldNames).sort().join(', ')}`,\n // The public form omits the field list: the column list is schema\n // detail an unauthenticated caller has no need for.\n `Invalid orderBy field: '${field}'.`,\n 'INVALID_ORDER_BY_UNKNOWN_FIELD',\n { field, itemClassName },\n );\n }\n\n // Being a registered field is not the same as being a column (#2367).\n // `oneToMany`/`manyToMany` are relationship-only, `meta` lives inside the\n // STI `_meta_data` blob, and `transient` is never persisted — the schema\n // generator emits no column for any of them. Ordering by one reached the\n // driver as `no such column`, which carries no 4xx status and so surfaced\n // as a 500 on a public endpoint. `select` already rejects the same set.\n const fieldDef = definitionByColumn.get(columnName);\n const fieldType = fieldDef?.type;\n const isTransient =\n fieldDef?.transient === true || fieldDef?._meta?.transient === true;\n if (\n fieldType === 'meta' ||\n fieldType === 'oneToMany' ||\n fieldType === 'manyToMany' ||\n isTransient ||\n this.isOmittedCustomPrimaryKeySystemField(\n columnName,\n customPrimaryKey,\n explicitFieldNames,\n )\n ) {\n throw new QueryOrderByError(\n `Invalid orderBy field: '${field}'. Field is not column-backed on ${itemClassName}.`,\n `Invalid orderBy field: '${field}'. Field is not column-backed.`,\n 'INVALID_ORDER_BY_NOT_COLUMN_BACKED',\n { field, itemClassName },\n );\n }\n\n return `${columnName} ${normalizedDirection}`;\n });\n\n return ` ORDER BY ${terms.join(', ')}`;\n }\n\n /**\n * Apply this collection's configured list bounds (#2367).\n *\n * Both bounds are opt-in (`defaultListLimit` / `maxListLimit` collection\n * options) and default to unset. `list()` is the framework's bulk-read\n * primitive — relationship loaders, junction hydration and `listByIds()` all\n * go through it expecting every matching row — so a framework-wide implicit\n * default would silently truncate correct queries instead of bounding an\n * attack surface. The generated surfaces, where untrusted input actually\n * arrives, apply {@link DEFAULT_LIST_LIMIT}/{@link MAX_LIST_LIMIT}\n * unconditionally; an application that wants the same ceiling on its own\n * programmatic reads sets `maxListLimit` when constructing the collection.\n *\n * @private\n */\n private applyListBounds(limit: number | undefined): number | undefined {\n const effective = limit ?? this._defaultListLimit;\n if (effective === undefined) return undefined;\n return this._maxListLimit === undefined\n ? effective\n : Math.min(effective, this._maxListLimit);\n }\n\n /**\n * Convert WHERE clause field names from camelCase to snake_case while preserving operators.\n * Validates operators and field names to prevent SQL injection and invalid queries.\n *\n * Uses cached fields for sync access (issue #663) to avoid async overhead on every query.\n *\n * @param where - WHERE clause object with camelCase field names\n * @returns WHERE clause object with snake_case field names\n * @private\n *\n * @example\n * ```typescript\n * // Input: { 'typeId': 'foo', 'categoryId >': 100 }\n * // Output: { 'type_id': 'foo', 'category_id >': 100 }\n * ```\n */\n private convertWhereKeys(\n where: SmrtListWhereClause<ModelType>,\n ): SmrtListWhereClause<ModelType> {\n if (Array.isArray(where)) {\n if (\n where.length === 0 ||\n where.some((group) => !Array.isArray(group) || group.length === 0)\n ) {\n throw new Error(\n 'Invalid DNF where clause: each OR branch must contain at least one condition.',\n );\n }\n return where.map((group) =>\n group.map((condition) => this.convertWhereKeysRecord(condition)),\n );\n }\n return this.convertWhereKeysRecord(where);\n }\n\n private convertWhereKeysRecord(\n where: Record<string, unknown>,\n ): Record<string, unknown> {\n // Whitelist of allowed SQL operators.\n //\n // This list is a contract, not a wish list (#2276): every entry must be an\n // operator `@happyvertical/sql`'s `parseConditionKey` recognises, because a\n // key this method accepts is passed straight to `buildWhere`. An operator\n // accepted here but unknown there is not \"unimplemented\" — `buildWhere`\n // fails to split it off the key, tries to read `\"<field> <operator>\"` as one\n // identifier, and throws `Invalid SQL identifier` from inside the query\n // builder. The caller gets a SQL-layer error naming SQL-layer concepts for a\n // query the API told them was valid. Keep this list in lockstep with\n // `VALID_OPERATORS` in `@happyvertical/sql`'s `shared/utils.ts`; the\n // \"executes every accepted operator\" test in\n // `src/__tests__/issue-2276-where-contract.test.ts` is what enforces it.\n const VALID_OPERATORS = [\n '=',\n '>',\n '<',\n '>=',\n '<=',\n '!=',\n 'in',\n 'not in',\n 'like',\n ];\n\n // Operators callers reach for that this layer cannot honour, mapped to the\n // supported way to express the same intent. Being listed here only improves\n // the rejection message — these are rejected exactly like any other unknown\n // operator. A Map, not an object literal, because the lookup key is\n // caller-supplied: `{ 'name toString': 'x' }` would otherwise find\n // `Object.prototype.toString` and splice a function into the error text.\n const UNSUPPORTED_OPERATOR_HINTS = new Map<string, string>([\n // `contains` shipped in this whitelist but never existed in the SQL layer,\n // so it has always failed at execution (#2276). It is not re-implemented\n // here as `like '%value%'` because that would be a different operator\n // wearing its name: `%` and `_` in the value would silently become\n // wildcards (escaping them needs a `LIKE ... ESCAPE` clause `buildWhere`\n // cannot emit), and `LIKE` is case-insensitive on SQLite but\n // case-sensitive on PostgreSQL, so the same call would mean two things.\n // Restoring it belongs in the SQL layer, where the dialect is known —\n // tracked in happyvertical/sdk#1192.\n [\n 'contains',\n \"Use 'like' with explicit wildcards instead, e.g. { 'name like': '%term%' }.\",\n ],\n ]);\n\n // Get schema fields for validation (sync from cache)\n const fields = this.getFieldsSync();\n const itemClassName = this.getResolvedItemClassName();\n const { sensitiveFieldNames, skipFieldValidation, validFieldNames } =\n this.collectQueryableFieldNames(fields);\n\n const converted: Record<string, unknown> = {};\n\n // Check for prototype pollution attempts using own properties only\n // __proto__ is a special property that doesn't show up in Object.entries()\n // but can still be checked with hasOwnProperty\n if (\n Object.hasOwn(where, '__proto__') ||\n Object.hasOwn(where, 'constructor') ||\n Object.hasOwn(where, 'prototype')\n ) {\n throw new Error(\n `Invalid WHERE clause: Prototype pollution attempts are not allowed. ` +\n `Detected dangerous properties in WHERE clause.`,\n );\n }\n\n for (const [key, value] of Object.entries(where)) {\n // Split field name and operator (e.g., \"typeId >\" → [\"typeId\", \">\"])\n const parts = key.trim().split(/\\s+/);\n const fieldName = parts[0];\n const operator = parts.slice(1).join(' ') || '=';\n\n // Check for dangerous prototype pollution field names\n if (\n fieldName === '__proto__' ||\n fieldName === 'constructor' ||\n fieldName === 'prototype' ||\n fieldName.includes('__proto__') ||\n fieldName.includes('constructor') ||\n fieldName.includes('prototype')\n ) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Prototype pollution attempts are not allowed.`,\n );\n }\n\n // Split a dot-notation JSON path (e.g. 'metadata.userId') into its base\n // column and path suffix. The suffix is rejected further down (#2276), but\n // it is parsed and reassembled first so the guards between here and there\n // — the #1379 identifier check, the #1540 sensitive-`@meta`-path check,\n // and the schema field whitelist — still see the whole key rather than a\n // truncated column name.\n const dotIndex = fieldName.indexOf('.');\n const baseFieldName =\n dotIndex >= 0 ? fieldName.substring(0, dotIndex) : fieldName;\n const jsonPath = dotIndex >= 0 ? fieldName.substring(dotIndex) : null;\n\n // Convert base field name to snake_case, preserving leading underscores\n const snakeBaseFieldName = baseFieldName.startsWith('_')\n ? `_${toSnakeCase(baseFieldName.slice(1))}`\n : toSnakeCase(baseFieldName);\n\n // Full snake_case field name with JSON path preserved\n const snakeFieldName = jsonPath\n ? `${snakeBaseFieldName}${jsonPath}`\n : snakeBaseFieldName;\n\n // Security (#1379): the field identifier — base column plus any\n // dot-notation JSON-path segments — is interpolated UNPARAMETERIZED into\n // the SQL field position by the downstream query builder. The manifest\n // whitelist below only validates the base column, and is skipped entirely\n // when a class has no registered fields (skipFieldValidation), so without\n // this guard a crafted key such as\n // `metadata.x))/**/UNION/**/SELECT/**/secret/**/FROM/**/users--`\n // (SQL comments substituting for blocked whitespace) injects arbitrary SQL\n // via the JSON-path suffix — remotely reachable through the generated\n // REST/MCP `where` surfaces and able to defeat the tenant filter. Enforce\n // that the whole identifier is a strict dot-separated identifier so no\n // parens/quotes/operators/whitespace can survive, regardless of whether\n // the manifest whitelist below runs.\n if (!/^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z0-9_]+)*$/.test(snakeFieldName)) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Field names must be identifiers (letters, digits, underscore) ` +\n `with optional dot-separated JSON-path segments.`,\n );\n }\n\n // Security (#1540): reject filters that target a sensitive column. This\n // runs before operator/field validation so the secret value-probing\n // oracle is closed even for otherwise-valid operators. We reject when the\n // base column itself is sensitive, OR when an STI `@meta` sensitive field\n // is probed via a `_meta_data.<prop>` JSON path (the prop keeps its\n // camelCase name). The JSON-path check is scoped to the `_meta_data`\n // column so legitimate filters on unrelated JSON fields (e.g.\n // `metadata.apiSecret` on a non-sensitive `metadata` column) aren't\n // falsely rejected.\n // Use the NORMALIZED column name so camelCase aliases (`_metaData`,\n // `metaData`) that also resolve to the `_meta_data` column are scoped in —\n // checking raw `baseFieldName` here let `_metaData.apiSecret` slip past\n // the segment check while still resolving to the meta column.\n const baseIsMetaData =\n snakeBaseFieldName === '_meta_data' ||\n snakeBaseFieldName === 'meta_data';\n const metaPathTargetsSensitive =\n baseIsMetaData &&\n !!jsonPath &&\n jsonPath\n .split('.')\n .filter(Boolean)\n .some(\n (segment) =>\n sensitiveFieldNames.has(segment) ||\n sensitiveFieldNames.has(toSnakeCase(segment)),\n );\n if (\n sensitiveFieldNames.has(snakeBaseFieldName) ||\n metaPathTargetsSensitive\n ) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Filtering on sensitive fields is not allowed.`,\n );\n }\n\n // Auto-detect IN operator when value is an array without explicit operator\n const effectiveOperator =\n operator === '=' && Array.isArray(value) ? 'in' : operator;\n\n // Validate operator\n if (!VALID_OPERATORS.includes(effectiveOperator)) {\n const hint = UNSUPPORTED_OPERATOR_HINTS.get(effectiveOperator);\n throw new Error(\n `Invalid WHERE clause operator: '${operator}'. ` +\n `Valid operators: ${VALID_OPERATORS.join(', ')}` +\n (hint ? `. ${hint}` : ''),\n );\n }\n\n // Validate field name exists in schema (validate base column name only)\n // Issue #869: Skip validation when no fields are registered (manifest-less test classes)\n if (!skipFieldValidation && !validFieldNames.has(snakeBaseFieldName)) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Field does not exist on ${itemClassName}. ` +\n `Valid fields: ${Array.from(validFieldNames).sort().join(', ')}`,\n );\n }\n\n // Reject dot-notation JSON paths (#2276).\n //\n // These validated but were never rewritten into a JSON extraction\n // expression, so `metadata.color` reached SQL verbatim, where it reads as\n // the qualified column reference `metadata.color` — a column `color` on a\n // table `metadata`. SQLite answers `no such column`, surfaced to the\n // caller as an opaque `DatabaseError: Failed to execute raw query` for a\n // query this method had just accepted.\n //\n // Rewriting them here would mean emitting dialect-specific extraction\n // (`json_extract(col, '$.path')` on SQLite, `col->>'path'` on\n // PostgreSQL/DuckDB) and resolving the comparison typing that differs\n // between them — `->>` yields text, so `metadata.count > 5` would compare\n // a string against a number on PostgreSQL while working on SQLite. That is\n // a feature with its own semantics to settle, not a repair of this\n // validator, so this rejects until it exists — tracked in #2282.\n //\n // Placed last among the field checks on purpose. Every guard above\n // describes a different problem with the same key, and each one's message\n // is more specific than this one: a sensitive `_meta_data.<prop>` path\n // must still report that it was rejected for being sensitive (#1540), and\n // a typo'd base column must still report that the column does not exist\n // rather than being told to \"filter on the '<typo>' column itself\". By\n // running here, the advice this message gives is only ever offered for a\n // column that actually exists — except under `skipFieldValidation`, where\n // the class has no registered fields and nothing knows which columns are\n // real (#869). There the base column named below is only the caller's own\n // word for it, exactly as it is for every other key on such a class.\n if (jsonPath) {\n throw new Error(\n `Invalid WHERE clause field: '${fieldName}'. ` +\n `Dot-notation JSON paths are not supported — the path is not ` +\n `rewritten into a JSON extraction expression, so it would reach SQL ` +\n `as a qualified column reference and fail at execution. ` +\n `Filter on the '${baseFieldName}' column itself, or filter the ` +\n `extracted values in application code.`,\n );\n }\n\n // Validate operator-specific value types\n if (\n (effectiveOperator === 'in' || effectiveOperator === 'not in') &&\n !Array.isArray(value)\n ) {\n throw new Error(\n `WHERE clause operator '${effectiveOperator}' requires an array value for field '${fieldName}', ` +\n `got ${typeof value}`,\n );\n }\n\n // Reject empty arrays for IN/NOT IN operators (generates invalid SQL)\n if (\n (effectiveOperator === 'in' || effectiveOperator === 'not in') &&\n Array.isArray(value) &&\n value.length === 0\n ) {\n throw new Error(\n `WHERE clause operator '${effectiveOperator}' requires a non-empty array for field '${fieldName}'. ` +\n `Use listByIds([]) for graceful empty array handling.`,\n );\n }\n\n if (effectiveOperator === 'like' && typeof value !== 'string') {\n throw new Error(\n `WHERE clause operator 'like' requires a string value for field '${fieldName}', ` +\n `got ${typeof value}`,\n );\n }\n\n // Reconstruct key with operator\n const newKey =\n effectiveOperator === '='\n ? snakeFieldName\n : `${snakeFieldName} ${effectiveOperator}`;\n\n converted[newKey] = value;\n }\n\n return converted;\n }\n\n private toDbColumnName(fieldName: string): string {\n return fieldName.startsWith('_')\n ? `_${toSnakeCase(fieldName.slice(1))}`\n : toSnakeCase(fieldName);\n }\n\n private assertSafeProjectionFieldName(fieldName: string): void {\n if (\n fieldName === '__proto__' ||\n fieldName === 'constructor' ||\n fieldName === 'prototype'\n ) {\n throw new Error(\n `Invalid select field: '${fieldName}'. Prototype pollution attempts are not allowed.`,\n );\n }\n\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(fieldName)) {\n throw new Error(\n `Invalid select field: '${fieldName}'. Field names must be identifiers (letters, digits, underscore).`,\n );\n }\n }\n\n private quoteProjectionIdentifier(identifier: string): string {\n this.assertSafeProjectionFieldName(identifier);\n return `\"${identifier.replace(/\"/g, '\"\"')}\"`;\n }\n\n private isSensitiveFieldDefinition(\n fieldDef: CollectionFieldDefinition | null | undefined,\n ): boolean {\n return fieldDef?.sensitive === true || fieldDef?._meta?.sensitive === true;\n }\n\n private getReadPermissionFieldDefinition(\n fieldDef: CollectionFieldDefinition | null | undefined,\n ): string | undefined {\n if (typeof fieldDef?.readPermission === 'string') {\n return fieldDef.readPermission;\n }\n if (typeof fieldDef?._meta?.readPermission === 'string') {\n return fieldDef._meta.readPermission;\n }\n return undefined;\n }\n\n private collectSensitiveFieldNames(\n fieldMap:\n | Record<string, CollectionFieldDefinition>\n | Map<string, CollectionFieldDefinition>,\n target = new Set<string>(),\n ): Set<string> {\n const entries =\n fieldMap instanceof Map ? fieldMap.entries() : Object.entries(fieldMap);\n\n for (const [fieldName, fieldDef] of entries) {\n if (this.isSensitiveFieldDefinition(fieldDef)) {\n // Store both the snake_case column form and the raw property name so\n // STI `@meta` fields probed via `_meta_data.<prop>` JSON paths (which\n // keep the camelCase key) are also rejected.\n target.add(toSnakeCase(fieldName));\n target.add(fieldName);\n }\n }\n\n return target;\n }\n\n /**\n * The `collectSensitiveFieldNames` sibling for `@field({ readPermission })`\n * columns (#2367).\n *\n * These are per-caller redacted by `toPublicJSON()` and refused outright by\n * `select`, so an ordering over one leaks comparisons about a value the\n * caller may not read. `list()` has no permission context, so — exactly as\n * `select` does — they are refused unconditionally rather than conditionally.\n */\n private collectReadPermissionFieldNames(\n fieldMap:\n | Record<string, CollectionFieldDefinition>\n | Map<string, CollectionFieldDefinition>,\n target = new Set<string>(),\n ): Set<string> {\n const entries =\n fieldMap instanceof Map ? fieldMap.entries() : Object.entries(fieldMap);\n\n for (const [fieldName, fieldDef] of entries) {\n if (this.getReadPermissionFieldDefinition(fieldDef) !== undefined) {\n target.add(toSnakeCase(fieldName));\n target.add(fieldName);\n }\n }\n\n return target;\n }\n\n private hasCustomPrimaryKey(\n fields: Record<string, CollectionFieldDefinition>,\n ): boolean {\n return Object.values(fields).some(\n (fieldDef) =>\n fieldDef.primaryKey === true || fieldDef._meta?.primaryKey === true,\n );\n }\n\n private resolvePrimaryKeyField(\n fields: Record<string, CollectionFieldDefinition>,\n label: string,\n ): { field: string; column: string } {\n const declared = Object.entries(fields)\n .filter(\n ([, fieldDef]) =>\n fieldDef.primaryKey === true || fieldDef._meta?.primaryKey === true,\n )\n .map(([field]) => field);\n if (declared.length > 1) {\n throw new Error(\n `${label} declares multiple primary-key fields (${declared.join(', ')}); listWithLatestRelated requires a single primary key.`,\n );\n }\n const field = declared[0] ?? 'id';\n return { field, column: this.toDbColumnName(field) };\n }\n\n private getDatabaseEngine(): ReturnType<typeof detectEngine> {\n const db = this.db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n client?: { constructor?: { name?: string }; connection?: unknown };\n exportTable?: unknown;\n };\n const clientName = db.client?.constructor?.name?.toLowerCase() ?? '';\n const isDuckDbConnection =\n clientName.includes('duckdb') ||\n (typeof db.exportTable === 'function' &&\n db.client !== undefined &&\n 'connection' in db.client);\n return detectEngine(\n db.url || db.config?.url || '',\n this.getDatabaseEngineHint() ||\n db.type ||\n db.config?.type ||\n (isDuckDbConnection ? 'duckdb' : undefined),\n );\n }\n\n private isOmittedCustomPrimaryKeySystemField(\n fieldName: string,\n hasCustomPrimaryKey: boolean,\n explicitFieldNames: ReadonlySet<string>,\n ): boolean {\n return (\n hasCustomPrimaryKey &&\n !explicitFieldNames.has(fieldName) &&\n (fieldName === 'id' || fieldName === 'slug' || fieldName === 'context')\n );\n }\n\n private resolveProjectionSelect(\n select: readonly string[],\n fields: Record<string, CollectionFieldDefinition>,\n isSTI: boolean,\n ): { sql: string; outputFields: string[] } {\n if (!Array.isArray(select)) {\n throw new Error(\n 'Invalid select option: expected an array of field names.',\n );\n }\n if (select.length === 0) {\n throw new Error(\n 'Invalid select option: at least one field name is required.',\n );\n }\n\n const customPrimaryKey = this.hasCustomPrimaryKey(fields);\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n const schema =\n ObjectRegistry.getSchema(itemQualifiedName) ??\n ObjectRegistry.getSchema(itemClassName);\n const schemaColumnNames = schema?.columns\n ? new Set(Object.keys(schema.columns))\n : undefined;\n const registeredFields = ObjectRegistry.getFields(itemQualifiedName);\n const explicitFields =\n registeredFields.size > 0\n ? registeredFields\n : ObjectRegistry.getFields(itemClassName);\n const explicitFieldNames = new Set(explicitFields.keys());\n\n const validFieldNames = new Set<string>();\n for (const [fieldName, fieldDef] of Object.entries(fields)) {\n if (\n this.isOmittedCustomPrimaryKeySystemField(\n fieldName,\n customPrimaryKey,\n explicitFieldNames,\n )\n ) {\n continue;\n }\n const columnName = this.toDbColumnName(fieldName);\n if (schemaColumnNames && !schemaColumnNames.has(columnName)) {\n continue;\n }\n validFieldNames.add(fieldName);\n }\n if (isSTI && (!schemaColumnNames || schemaColumnNames.has('_meta_type'))) {\n validFieldNames.add('_meta_type');\n }\n\n const seen = new Set<string>();\n const outputFields: string[] = [];\n const selectExpressions: string[] = [];\n\n for (const fieldName of select) {\n if (typeof fieldName !== 'string') {\n throw new Error(\n `Invalid select field: expected a string field name, got ${typeof fieldName}.`,\n );\n }\n this.assertSafeProjectionFieldName(fieldName);\n\n if (seen.has(fieldName)) {\n throw new Error(\n `Invalid select field: '${fieldName}' was requested more than once.`,\n );\n }\n seen.add(fieldName);\n\n if (!validFieldNames.has(fieldName)) {\n throw new Error(\n `Invalid select field: '${fieldName}'. Field does not exist on ${itemClassName}. ` +\n `Valid fields: ${Array.from(validFieldNames).sort().join(', ')}`,\n );\n }\n\n const fieldDef = fields[fieldName];\n const fieldType = fieldDef?.type;\n if (this.isSensitiveFieldDefinition(fieldDef)) {\n throw new Error(\n `Invalid select field: '${fieldName}' is sensitive and cannot be projected by collection.list({ select }).`,\n );\n }\n const readPermission = this.getReadPermissionFieldDefinition(fieldDef);\n if (readPermission) {\n throw new Error(\n `Invalid select field: '${fieldName}' is gated by readPermission '${readPermission}' and cannot be projected by collection.list({ select }).`,\n );\n }\n\n const isTransient =\n fieldDef?.transient === true || fieldDef?._meta?.transient === true;\n const isRelationshipOnly =\n fieldType === 'oneToMany' || fieldType === 'manyToMany';\n if (fieldType === 'meta' || isTransient || isRelationshipOnly) {\n throw new Error(\n `Invalid select field: '${fieldName}' is not a column-backed field on ${itemClassName}.`,\n );\n }\n\n const columnName = this.toDbColumnName(fieldName);\n selectExpressions.push(\n `${this.quoteProjectionIdentifier(columnName)} AS ${this.quoteProjectionIdentifier(fieldName)}`,\n );\n outputFields.push(fieldName);\n }\n\n return {\n sql: selectExpressions.join(', '),\n outputFields,\n };\n }\n\n /**\n * Parse and validate order terms while retaining the resolved database\n * column. The normal list order builder remains the source of truth for\n * field, sensitive-field, and permission validation; this companion shape\n * lets the latest-row CTE qualify the same safe identifiers with its alias.\n */\n private resolveOrderByTerms(\n orderBy: string | string[],\n fields: Record<string, CollectionFieldDefinition>,\n label: string,\n ): Array<{ field: string; column: string; direction: 'ASC' | 'DESC' }> {\n const validatedSql = this.buildOrderBySql(orderBy, fields);\n const items = Array.isArray(orderBy) ? orderBy : [orderBy];\n if (items.length === 0) {\n throw new Error(\n `Invalid ${label}: at least one order field is required.`,\n );\n }\n\n const validatedTerms = validatedSql\n .replace(/^ ORDER BY /, '')\n .split(', ')\n .map((term) => term.split(/\\s+/));\n\n return items.map((item, index) => {\n const [field, direction = 'ASC'] = String(item).trim().split(/\\s+/);\n if (!field) {\n throw new Error(`Invalid ${label}: an order field is required.`);\n }\n const normalizedDirection = direction.toUpperCase();\n if (normalizedDirection !== 'ASC' && normalizedDirection !== 'DESC') {\n throw new Error(\n `Invalid ${label} direction: ${direction}. Must be ASC or DESC.`,\n );\n }\n\n return {\n column: validatedTerms[index]?.[0] ?? this.toDbColumnName(field),\n direction: normalizedDirection,\n field,\n };\n });\n }\n\n private qualifyLatestOrderTerms(\n alias: string,\n terms: ReadonlyArray<{\n column: string;\n direction: 'ASC' | 'DESC';\n }>,\n ): string {\n return terms\n .flatMap((term) => {\n const column = `${alias}.${this.quoteProjectionIdentifier(term.column)}`;\n // Explicit NULLS LAST keeps ranking and parent sorting identical on\n // PostgreSQL, SQLite, DuckDB, and JSON-on-DuckDB.\n return [\n `CASE WHEN ${column} IS NULL THEN 1 ELSE 0 END ASC`,\n `${column} ${term.direction}`,\n ];\n })\n .join(', ');\n }\n\n private qualifyLatestParentOrderTerms(\n alias: string,\n terms: ReadonlyArray<{\n column: string;\n direction: 'ASC' | 'DESC';\n }>,\n ): string {\n return terms\n .flatMap((term) => {\n const column = `${alias}.${this.quoteProjectionIdentifier(term.column)}`;\n return [\n `CASE WHEN ${column} IS NULL THEN 1 ELSE 0 END ASC`,\n `${column} ${term.direction}`,\n ];\n })\n .join(', ');\n }\n\n private resolveOneToManyInverseForeignKey(\n relationship: import('./registry').RelationshipMetadata,\n ): import('./registry').RelationshipMetadata {\n const inverseCandidates = ObjectRegistry.getInverseRelationshipsForSelf(\n this._itemClass.name,\n ).filter(\n (candidate) =>\n candidate.sourceClass === relationship.targetClass &&\n candidate.type === 'foreignKey',\n );\n const explicitForeignKey = relationship.options?.foreignKey as\n | string\n | undefined;\n const matchedForeignKey = explicitForeignKey\n ? inverseCandidates.find(\n (candidate) => candidate.fieldName === explicitForeignKey,\n )\n : undefined;\n if (explicitForeignKey && !matchedForeignKey) {\n throw new Error(\n `oneToMany ${relationship.fieldName} specifies foreignKey '${explicitForeignKey}', but ${relationship.targetClass} has no matching inverse foreignKey. Candidates: ${inverseCandidates.map((candidate) => candidate.fieldName).join(', ') || '(none)'}`,\n );\n }\n\n const inverseForeignKey =\n matchedForeignKey ??\n inverseCandidates.find(\n (candidate) => candidate.targetClass === this._itemClass.name,\n ) ??\n inverseCandidates[0];\n if (!inverseForeignKey) {\n throw new Error(\n `Could not find inverse foreignKey on ${relationship.targetClass} for oneToMany relationship ${relationship.fieldName}`,\n );\n }\n return inverseForeignKey;\n }\n\n /**\n * Load a page of hydrated parents with one selected latest row from a\n * declared `@oneToMany` relation. The parent page is sliced only after the\n * latest-row join and optional related sort are applied, so unrelated rows\n * are never hydrated in application code.\n *\n * Missing related rows return `latestRelated: null`. Related order and sort\n * fields place null values after non-null values consistently on all\n * supported adapters. The related row is a plain projection and is never a\n * full `SmrtObject` hydration.\n *\n * @example\n * ```typescript\n * const page = await opportunities.listWithLatestRelated({\n * latestRelated: {\n * relation: 'evaluations',\n * orderBy: 'evaluatedAt DESC',\n * select: ['score', 'evaluatedAt'],\n * sortBy: 'score DESC',\n * },\n * limit: 25,\n * offset: 0,\n * });\n * console.log(page[0].parent, page[0].latestRelated?.score);\n * ```\n */\n public async listWithLatestRelated(\n options: Omit<SmrtLatestRelatedListOptions<ModelType>, 'stiScope'> & {\n stiScope: SmrtStiReadScope;\n },\n ): Promise<SmrtLatestRelatedRow<SmrtObject>[]>;\n public async listWithLatestRelated(\n options: Omit<SmrtLatestRelatedListOptions<ModelType>, 'stiScope'> & {\n stiScope?: undefined;\n },\n ): Promise<SmrtLatestRelatedRow<ModelType>[]>;\n public async listWithLatestRelated(\n options: SmrtLatestRelatedListOptions<ModelType>,\n ): Promise<SmrtLatestRelatedRow<SmrtObject>[]>;\n public async listWithLatestRelated(\n options: SmrtLatestRelatedListOptions<ModelType>,\n ): Promise<SmrtLatestRelatedRow<SmrtObject>[]> {\n await this.ensureStorageReady();\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const interceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n itemClassName,\n options as InterceptorListOptions,\n interceptorContext,\n )) ?? (options as InterceptorListOptions);\n\n let { where, offset, limit, orderBy, stiScope } =\n interceptedOptions as SmrtListOptions<ModelType>;\n const latestOptions =\n (interceptedOptions as SmrtLatestRelatedListOptions<ModelType>)\n .latestRelated ?? options.latestRelated;\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n const isSTI = tableStrategy === 'sti';\n where = this.applyStiReadScope(where, stiScope);\n where = resolveMetaTypeInWhere(where);\n\n const relationship = ObjectRegistry.getRelationships(\n this._itemClass.name,\n ).find(\n (candidate) =>\n candidate.fieldName === latestOptions.relation &&\n candidate.type === 'oneToMany',\n );\n if (!relationship) {\n throw new Error(\n `latestRelated.relation '${latestOptions.relation}' is not a declared oneToMany relationship on ${itemClassName}.`,\n );\n }\n\n const inverseForeignKey =\n this.resolveOneToManyInverseForeignKey(relationship);\n const relatedCollection = await ObjectRegistry.getCollection(\n relationship.targetClass,\n this.options,\n );\n await relatedCollection.ensureStorageReady();\n const relatedFields = relatedCollection.getFieldsSync();\n const relatedItemClassName = relatedCollection.getResolvedItemClassName();\n const relatedQualifiedName =\n relatedCollection.getResolvedItemQualifiedName();\n const relatedIsSTI =\n ObjectRegistry.getTableStrategy(relatedQualifiedName) === 'sti';\n\n // Run the related collection's read-scope interceptors as well as the\n // parent's. Relation targets can have their own tenant or authorization\n // policy; omitting this scope would make the CTE a raw bypass of the\n // target collection's normal list contract.\n const relatedInterceptorContext = createInterceptorContext(\n relatedItemClassName,\n 'list',\n relatedCollection.constructor.name,\n );\n const relatedInterceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n relatedItemClassName,\n { where: {} } as InterceptorListOptions,\n relatedInterceptorContext,\n )) ?? ({ where: {} } as InterceptorListOptions);\n let relatedWhere = (\n relatedInterceptedOptions as SmrtListOptions<SmrtObject>\n ).where;\n if (relatedIsSTI) {\n const relatedStiBase = ObjectRegistry.getSTIBase(relatedQualifiedName);\n if (relatedStiBase && relatedStiBase !== relatedQualifiedName) {\n relatedWhere = {\n _meta_type: relatedQualifiedName,\n ...(relatedWhere || {}),\n };\n }\n }\n relatedWhere = resolveMetaTypeInWhere(relatedWhere);\n const { sql: relatedWhereSql, values: relatedWhereValues } = buildWhere(\n relatedCollection.convertWhereKeys(relatedWhere || {}),\n );\n const relatedPrimaryKey = relatedCollection.resolvePrimaryKeyField(\n relatedFields,\n `Related model ${relatedItemClassName}`,\n );\n const relatedSelect = latestOptions.select ?? [relatedPrimaryKey.field];\n const relatedProjection = relatedCollection.resolveProjectionSelect(\n relatedSelect,\n relatedFields,\n relatedIsSTI,\n );\n const relatedOrderTerms = relatedCollection.resolveOrderByTerms(\n latestOptions.orderBy,\n relatedFields,\n 'latestRelated.orderBy',\n );\n const relatedSortTerms = latestOptions.sortBy\n ? relatedCollection.resolveOrderByTerms(\n latestOptions.sortBy,\n relatedFields,\n 'latestRelated.sortBy',\n )\n : [];\n\n const parentFields = this.getFieldsSync();\n const parentPrimaryKey = this.resolvePrimaryKeyField(\n parentFields,\n `Parent model ${itemClassName}`,\n );\n const relatedOutputFields = new Set(relatedProjection.outputFields);\n relatedOutputFields.add(relatedPrimaryKey.field);\n relatedOutputFields.add(inverseForeignKey.fieldName);\n if (parentFields.tenantId && relatedFields.tenantId) {\n relatedOutputFields.add('tenantId');\n }\n for (const term of [...relatedOrderTerms, ...relatedSortTerms]) {\n relatedOutputFields.add(term.field);\n }\n const relatedFieldColumns = new Map(\n [...relatedOrderTerms, ...relatedSortTerms].map((term) => [\n term.field,\n term.column,\n ]),\n );\n const parentSchema =\n ObjectRegistry.getSchema(itemQualifiedName) ??\n ObjectRegistry.getSchema(itemClassName);\n const liveParentSchema =\n typeof this.db.getTableSchema === 'function'\n ? await this.db.getTableSchema(this.tableName)\n : undefined;\n const parentColumnNames = Object.keys(\n liveParentSchema?.columns ?? parentSchema?.columns ?? {},\n );\n const occupiedParentIdentifiers = new Set([\n ...Object.keys(parentFields),\n ...Object.keys(parentFields).map((fieldName) =>\n this.toDbColumnName(fieldName),\n ),\n ...parentColumnNames,\n ...Object.keys(parentSchema?.columns ?? {}),\n ]);\n const relatedFieldAliases = new Map<string, string>();\n let nextAliasIndex = 0;\n for (const fieldName of relatedOutputFields) {\n let alias: string;\n do {\n alias = `__smrt_lr_${nextAliasIndex++}`;\n } while (\n occupiedParentIdentifiers.has(alias) ||\n [...relatedFieldAliases.values()].includes(alias)\n );\n relatedFieldAliases.set(fieldName, alias);\n }\n const parentSelectSql =\n parentColumnNames.length > 0\n ? parentColumnNames\n .map((columnName) => this.quoteProjectionIdentifier(columnName))\n .join(', ')\n : '*';\n const relatedAlias = (fieldName: string): string => {\n const alias = relatedFieldAliases.get(fieldName);\n if (!alias) {\n throw new Error(`Missing latest-related alias for '${fieldName}'.`);\n }\n return alias;\n };\n const relatedSelectExpressions = Array.from(relatedOutputFields).map(\n (fieldName) => {\n const column =\n relatedFieldColumns.get(fieldName) ??\n relatedCollection.toDbColumnName(fieldName);\n const alias = relatedAlias(fieldName);\n return `${relatedCollection.quoteProjectionIdentifier(column)} AS ${relatedCollection.quoteProjectionIdentifier(alias)}`;\n },\n );\n const relatedRankOrder = [\n relatedCollection.qualifyLatestOrderTerms('r', relatedOrderTerms),\n `r.${relatedCollection.quoteProjectionIdentifier(relatedPrimaryKey.column)} DESC`,\n ]\n .filter(Boolean)\n .join(', ');\n const latestRankAlias =\n relatedCollection.quoteProjectionIdentifier('__smrt_latest_rank');\n const latestRelatedAlias = (fieldName: string): string =>\n relatedCollection.quoteProjectionIdentifier(relatedAlias(fieldName));\n const relatedCte = `WITH ranked_latest_related AS (SELECT ${relatedSelectExpressions\n .map((expression) => `r.${expression}`)\n .join(\n ', ',\n )}, ROW_NUMBER() OVER (PARTITION BY r.${relatedCollection.quoteProjectionIdentifier(relatedCollection.toDbColumnName(inverseForeignKey.fieldName))} ORDER BY ${relatedRankOrder}) AS ${latestRankAlias} FROM ${relatedCollection.tableName} r${relatedWhereSql ? ` ${relatedWhereSql}` : ''}) `;\n\n const parentWhere = this.convertWhereKeys(where || {});\n const { sql: rawWhereSql, values: parentWhereValues } =\n buildWhere(parentWhere);\n const parentWhereSql = rawWhereSql.replace(\n /\\$(\\d+)/g,\n (_match, index: string) =>\n `$${Number(index) + relatedWhereValues.length}`,\n );\n const relatedJoinConditions = [\n `rr.${latestRankAlias} = 1`,\n `rr.${latestRelatedAlias(inverseForeignKey.fieldName)} = ${this.quoteProjectionIdentifier(parentPrimaryKey.column)}`,\n ];\n if (parentFields.tenantId && relatedFields.tenantId) {\n relatedJoinConditions.push(\n `(rr.${latestRelatedAlias('tenantId')} = ${this.quoteProjectionIdentifier('tenant_id')} OR (rr.${latestRelatedAlias('tenantId')} IS NULL AND ${this.quoteProjectionIdentifier('tenant_id')} IS NULL))`,\n );\n }\n\n const orderFragments: string[] = [];\n if (relatedSortTerms.length > 0) {\n orderFragments.push(\n relatedCollection.qualifyLatestParentOrderTerms(\n 'rr',\n relatedSortTerms.map((term) => ({\n ...term,\n column: relatedAlias(term.field),\n })),\n ),\n );\n }\n if (orderBy) {\n const parentOrderTerms = this.resolveOrderByTerms(\n orderBy,\n parentFields,\n 'orderBy',\n );\n orderFragments.push(\n parentOrderTerms\n .map(\n (term) =>\n `${this.quoteProjectionIdentifier(term.column)} ${term.direction}`,\n )\n .join(', '),\n );\n }\n orderFragments.push(\n `${this.quoteProjectionIdentifier(parentPrimaryKey.column)} ASC`,\n );\n\n const boundedLimit = this.applyListBounds(assertQueryBound(limit, 'limit'));\n const boundedOffset = assertQueryBound(offset, 'offset');\n let limitOffsetSql = '';\n const limitOffsetValues: number[] = [];\n let nextParameter =\n relatedWhereValues.length + parentWhereValues.length + 1;\n if (boundedLimit !== undefined) {\n limitOffsetSql += ` LIMIT $${nextParameter++}`;\n limitOffsetValues.push(boundedLimit);\n }\n if (boundedOffset !== undefined) {\n if (boundedLimit === undefined) {\n // SQLite requires LIMIT before OFFSET. SQLite uses -1 for an\n // unlimited limit; DuckDB and PostgreSQL use the standard LIMIT ALL.\n limitOffsetSql +=\n this.getDatabaseEngine() === 'sqlite' ? ' LIMIT -1' : ' LIMIT ALL';\n }\n limitOffsetSql += ` OFFSET $${nextParameter++}`;\n limitOffsetValues.push(boundedOffset);\n }\n\n const sql = `${relatedCte}SELECT ${parentSelectSql}, ${Array.from(\n relatedOutputFields,\n )\n .map(\n (fieldName) =>\n `rr.${latestRelatedAlias(fieldName)} AS ${this.quoteProjectionIdentifier(relatedAlias(fieldName))}`,\n )\n .join(\n ', ',\n )} FROM ${this.quoteProjectionIdentifier(this.tableName)} LEFT JOIN ranked_latest_related rr ON ${relatedJoinConditions.join(' AND ')} ${parentWhereSql} ${orderFragments.length > 0 ? `ORDER BY ${orderFragments.join(', ')}` : ''}${limitOffsetSql}`;\n const rows = await this.db.query(\n sql,\n ...relatedWhereValues,\n ...parentWhereValues,\n ...limitOffsetValues,\n );\n\n // Hydrate in result order. initialize() hooks may issue queries through\n // the same transaction-bound PostgreSQL client, so overlapping hydration\n // would violate the collection-wide serial hydration invariant.\n const instances: ModelType[] = [];\n const relatedAliasNames = new Set(relatedFieldAliases.values());\n const polymorphicFields = new Map<\n string,\n Record<string, CollectionFieldDefinition>\n >();\n for (const row of rows.rows as Record<string, unknown>[]) {\n const parentRow = Object.fromEntries(\n Object.entries(row).filter(([key]) => !relatedAliasNames.has(key)),\n );\n instances.push(\n await this.hydrateResultRow(\n parentRow,\n parentFields,\n isSTI,\n polymorphicFields,\n ),\n );\n }\n\n // Materialize the related projection by the parent primary key before\n // afterList hooks run. Hooks may filter or reorder hydrated parents; an\n // array-index pairing after those hooks could attach one parent's related\n // data to another parent.\n const relatedByParentId = new Map<string, Record<string, unknown> | null>();\n for (const row of rows.rows as Record<string, unknown>[]) {\n const parentId = row[parentPrimaryKey.column];\n if (parentId === null || parentId === undefined) continue;\n\n const relatedRow = Object.fromEntries(\n relatedProjection.outputFields.map((fieldName) => [\n fieldName,\n row[relatedAlias(fieldName)],\n ]),\n );\n const relatedId = row[relatedAlias(relatedPrimaryKey.field)];\n relatedByParentId.set(\n String(parentId),\n relatedId === null || relatedId === undefined\n ? null\n : relatedCollection.formatProjectionRow(\n relatedRow,\n relatedFields,\n relatedProjection.outputFields,\n ),\n );\n }\n\n const finalInstances = await GlobalInterceptors.executeAfterList(\n itemClassName,\n instances,\n interceptorContext,\n );\n\n return finalInstances.map((parent) => {\n return {\n latestRelated:\n (parent as unknown as Record<string, unknown>)[\n parentPrimaryKey.field\n ] === null ||\n (parent as unknown as Record<string, unknown>)[\n parentPrimaryKey.field\n ] === undefined\n ? null\n : (relatedByParentId.get(\n String(\n (parent as unknown as Record<string, unknown>)[\n parentPrimaryKey.field\n ],\n ),\n ) ?? null),\n parent,\n };\n });\n }\n\n private formatProjectionRow(\n row: Record<string, unknown>,\n fields: Record<string, CollectionFieldDefinition>,\n outputFields: readonly string[],\n ): Record<string, unknown> {\n const formattedData = this.withHydratedCoreFields(\n formatDataJs(row, fields),\n );\n const projected: Record<string, unknown> = {};\n\n for (const fieldName of outputFields) {\n projected[fieldName] = formattedData[fieldName];\n }\n\n return projected;\n }\n\n /**\n * Gets the class constructor for items in this collection\n */\n protected get _itemClass(): SmrtCollectionItemClass<ModelType> {\n const ctor = this.constructor as {\n readonly _itemClass?: SmrtCollectionItemClass<ModelType>;\n };\n if (!ctor._itemClass) {\n const className = this.constructor.name;\n const errorMessage = [\n `Collection \"${className}\" must define a static _itemClass property.`,\n '',\n 'Example:',\n ` class ${className} extends SmrtCollection<YourItemClass> {`,\n ' static readonly _itemClass = YourItemClass;',\n ' }',\n '',\n 'Make sure your item class is imported and defined before the collection class.',\n ].join('\\n');\n\n throw new Error(errorMessage);\n }\n return ctor._itemClass;\n }\n\n /**\n * Gets the model class constructor handled by this collection.\n */\n public getItemClass(): SmrtCollectionItemClass<ModelType> {\n return this._itemClass;\n }\n\n /**\n * Static reference to the item class constructor.\n *\n * S4 #1579: kept as `any`. Subclasses assign a concrete model constructor\n * (`static readonly _itemClass = Product`) here; the base declares the slot\n * generically and `SmrtCollection` is invariant in `ModelType`, so a narrower\n * `SmrtObjectConstructor` / `SmrtCollectionItemClass<SmrtObject>` type rejects\n * the heterogeneous concrete assignments. Mirrors the registry escape hatch.\n */\n // biome-ignore lint/suspicious/noExplicitAny: subclasses assign a concrete `static _itemClass = Product`; `SmrtCollection` is invariant in `ModelType`, so a narrower constructor type rejects the heterogeneous assignments. S4 #1579.\n static readonly _itemClass: any;\n\n /**\n * Validates that the collection is properly configured\n * Call this during development to catch configuration issues early\n */\n static validate(): void {\n if (!SmrtCollection._itemClass) {\n const className = SmrtCollection.name;\n const errorMessage = [\n `Collection \"${className}\" is missing required static _itemClass property.`,\n '',\n 'Fix by adding:',\n ` class ${className} extends SmrtCollection<YourItemClass> {`,\n ' static readonly _itemClass = YourItemClass;',\n ' }',\n ].join('\\n');\n throw new Error(errorMessage);\n }\n\n // Validate that _itemClass has required methods\n if (typeof SmrtCollection._itemClass !== 'function') {\n throw new Error(\n `Collection \"${SmrtCollection.name}\"._itemClass must be a constructor function`,\n );\n }\n\n // Check if it has a create method (static or prototype)\n const hasCreateMethod =\n typeof SmrtCollection._itemClass.create === 'function' ||\n typeof SmrtCollection._itemClass.prototype?.create === 'function';\n\n if (!hasCreateMethod) {\n logger.warn(\n `Collection \"${SmrtCollection.name}\"._itemClass should have a create() method for optimal functionality`,\n );\n }\n }\n\n /**\n * Database table name for this collection\n */\n public _tableName!: string;\n\n /**\n * Creates a new SmrtCollection instance\n *\n * @deprecated Use the static create() factory method instead\n * @param options - Configuration options\n */\n constructor(options: SmrtCollectionOptions = {}) {\n super(options);\n\n // #2367: validate the configured bounds at construction, not at query time.\n // A bad ceiling that only surfaces on the first paged read is a production\n // incident; a bad ceiling that fails to construct is a startup error.\n this._defaultListLimit = assertOptionalListBound(\n options.defaultListLimit,\n 'defaultListLimit',\n );\n this._maxListLimit = assertOptionalListBound(\n options.maxListLimit,\n 'maxListLimit',\n );\n\n // Auto-register the collection if it's not the base SmrtCollection and has an _itemClass.\n // `this.constructor` is typed as the structureless `Function`; view it as the\n // collection-class shape (static `_itemClass` constructor + the new-able ctor\n // that `registerCollection` stores) so the static slot can be read and the\n // class registered without `any`.\n const collectionCtor = this.constructor as unknown as {\n _itemClass?: SmrtObjectConstructor;\n } & (new (\n options: SmrtCollectionOptions,\n ) => SmrtCollection<SmrtObject>);\n if (this.constructor !== SmrtCollection && collectionCtor._itemClass) {\n const itemClass = collectionCtor._itemClass;\n const itemClassName =\n ObjectRegistry.getClassByConstructor(itemClass)?.name || itemClass.name;\n ObjectRegistry.registerCollection(itemClassName, collectionCtor);\n }\n }\n\n /**\n * Factory method — the recommended way to instantiate a collection.\n *\n * Creates the collection instance, calls `initialize()`, and pre-populates\n * the field cache used by synchronous query helpers.\n *\n * Pass the same `options` object you received in a `SmrtObject` constructor\n * or a `SvelteKit` load function to share the database connection.\n *\n * @param options - Database, AI, filesystem, and other configuration options.\n * At minimum, provide `db` (or `persistence`) to connect to a database.\n * @returns A fully initialized, ready-to-query collection instance\n *\n * @example\n * ```typescript\n * // Share a DB connection from a SvelteKit load function\n * const products = await Products.create(event.locals.smrtOptions);\n *\n * // Explicit configuration\n * const products = await Products.create({\n * db: myDb,\n * ai: { provider: 'openai', apiKey: process.env.OPENAI_API_KEY },\n * });\n * ```\n */\n // S4 #1579: `SmrtCollection<any>` bound is irreducible. `SmrtCollection` is\n // invariant in `ModelType` (it has `create(options: SmrtCreateInput<ModelType>)`),\n // so a concrete `Products extends SmrtCollection<Product>` does not satisfy\n // `T extends SmrtCollection<SmrtObject>`. The `any` bound is the only constraint\n // every subclass collection satisfies.\n // biome-ignore lint/suspicious/noExplicitAny: `SmrtCollection` is invariant in `ModelType`, so concrete `Products extends SmrtCollection<Product>` fails `T extends SmrtCollection<SmrtObject>`; the `any` bound is the only one every subclass satisfies. S4 #1579.\n static async create<T extends SmrtCollection<any>>(\n this: new (\n options?: SmrtCollectionOptions,\n ) => T,\n // `SmrtCollectionOptions`, not `SmrtClassOptions`: the collection-only\n // options (#2367 list bounds) must be passable through the recommended\n // factory without a cast, and every `SmrtClassOptions` value still satisfies\n // this because the extra members are optional.\n options: SmrtCollectionOptions = {},\n ): Promise<T> {\n // Extract only collection-compatible options from the incoming bag.\n // This is an allowlist: an option absent from BOTH the destructuring and the\n // literal below never reaches the constructor, however it was passed.\n const {\n _className,\n db,\n defaultListLimit, // #2367\n persistence, // Also extract persistence alias\n ai,\n fs,\n logging,\n maxListLimit, // #2367\n metrics,\n pubsub,\n sanitization,\n signals,\n } = options;\n\n const collectionOptions: SmrtCollectionOptions = {\n _className,\n db,\n persistence, // Pass persistence through so initialize() can map it to db\n ai,\n defaultListLimit,\n fs,\n logging,\n maxListLimit,\n metrics,\n pubsub,\n sanitization,\n signals,\n };\n\n // Defense-in-depth: SmrtJunction subclasses MUST have their ITEM class\n // registered with ObjectRegistry — that's where the field metadata,\n // tableName, and conflictColumns come from, which byLeft/byRight/\n // attach/detach/setLinks all depend on. The scanner is supposed to\n // catch every junction subclass via the FRAMEWORK_BASE_CLASSES list\n // in packages/scanner, but if a future refactor adds a new abstract\n // junction base without updating that list, or if a class is loaded\n // outside the normal manifest pipeline, we'd silently fall through to\n // empty-field-metadata behavior (the issue #1132 class of bug). Fail\n // loudly here instead of producing wrong results later.\n //\n // We check the ITEM class registration (not the collection class) —\n // collection constructors are stored separately via\n // registerCollection(itemClassName, ctor), not in the classes map.\n // View the static `this` as the junction-aware collection-class shape so the\n // junction sentinel and item constructor can be read without `any`.\n const staticSelf = this as unknown as {\n name: string;\n _isJunctionBase?: boolean;\n _itemClass?: SmrtObjectConstructor;\n };\n if (staticSelf._isJunctionBase === true) {\n const itemCtor = staticSelf._itemClass;\n const itemRegistered =\n itemCtor && ObjectRegistry.getClassByConstructor(itemCtor);\n if (!itemRegistered) {\n throw new Error(\n `SmrtJunction subclass \"${this.name}\" has no registered item class. ` +\n `The scanner likely didn't pick up its model — usually because the ` +\n `consuming package's manifest doesn't include \"${itemCtor?.name ?? '<unknown>'}\". ` +\n `Check that the scanner's FRAMEWORK_BASE_CLASSES ` +\n `(packages/scanner/src/inheritance-resolver.ts) recognizes every ` +\n `framework abstract base in your inheritance chain, that the package ` +\n `has been built (manifest.json present in dist/), and that runtime ` +\n `manifest loading (__smrt-register__.ts) runs before any junction ` +\n `is instantiated.`,\n );\n }\n }\n\n // Create instance using protected constructor\n const instance = new this(collectionOptions);\n\n // Perform async initialization\n await instance.initialize();\n\n // Cache fields for sync access during queries (issue #663)\n // This eliminates async getFields() calls on every query\n const fields = await instance.getFields();\n instance._cachedFields = fields;\n\n // Return fully initialized instance\n return instance;\n }\n\n /**\n * Async initialization hook for the collection.\n *\n * Called automatically by the static `create()` factory. In most cases you\n * do not need to call this directly — use `create()` instead.\n *\n * Runtime schema checks are deferred until a query actually touches the\n * backing table. This keeps collection construction safe during SSR and\n * import-time module evaluation without mutating application schema.\n *\n * @returns This instance (enables chaining)\n */\n public async initialize(): Promise<this> {\n await super.initialize();\n\n // Defer table verification until the first real query. That keeps\n // construction lightweight while ensuring runtime never auto-creates app\n // tables behind the scenes.\n\n return this;\n }\n\n /**\n * Verify that the collection's backing table exists before running a query.\n *\n * This is a fail-fast check only. It does not create or alter schema.\n */\n public async ensureStorageReady(): Promise<void> {\n await verifyPersistenceTable(\n this.db,\n this.tableName,\n this.getResolvedItemClassName(),\n );\n }\n\n /**\n * Find a single record by criteria (convenience method - delegates to get())\n *\n * @param options - Query options with where clause\n * @returns Promise resolving to the object or null if not found\n *\n * @example\n * ```typescript\n * const councils = await Councils.create({ persistence: { type: 'sql', url: 'db.sqlite' } });\n * const council = await councils.findOne({ where: { name: 'Example Council' } });\n * ```\n */\n public async findOne(options: {\n where: SmrtWhereClause<ModelType>;\n }): Promise<ModelType | null> {\n return await this.get(options.where);\n }\n\n /**\n * Find a record by ID (convenience method - delegates to get())\n *\n * @param id - Record ID to find (string or Field instance)\n * @returns Promise resolving to the object or null if not found\n *\n * @example\n * ```typescript\n * const councils = await Councils.create({ persistence: { type: 'sql', url: 'db.sqlite' } });\n * const council = await councils.findById('uuid-123');\n *\n * // Also works with Field instances (e.g., from foreignKey fields)\n * const meeting = new Meeting({ councilId: 'uuid-123' });\n * const council = await councils.findById(meeting.councilId);\n * ```\n */\n public async findById(id: string): Promise<ModelType | null> {\n return await this.get(id);\n }\n\n /**\n * Find all records matching criteria (convenience method - delegates to list())\n *\n * @param options - Query options (where, orderBy, limit, etc.)\n * @returns Promise resolving to array of objects\n *\n * @example\n * ```typescript\n * const councils = await Councils.create({ persistence: { type: 'sql', url: 'db.sqlite' } });\n * const active = await councils.findAll({ where: { status: 'active' } });\n * ```\n */\n public async findAll(\n options: {\n where?: SmrtWhereClause<ModelType>;\n orderBy?: string | string[];\n limit?: number;\n offset?: number;\n include?: string[];\n } = {},\n ): Promise<ModelType[]> {\n return await this.list(options);\n }\n\n /**\n * Find multiple objects by their IDs in a single query.\n *\n * This is a convenience method that avoids N+1 queries when you have\n * a list of IDs and need to fetch the corresponding records.\n *\n * The id list is chunked at `IN_LIST_CHUNK_SIZE` (#2367). `collection.list()`\n * expands an array-valued WHERE into one `id IN (?, ?, ...)` clause and does\n * not chunk it, so an unbounded caller-sized array hits the backend's\n * bind-variable ceiling — `SQLITE_MAX_VARIABLE_NUMBER` (999 pre-3.32, 32766\n * after) or PostgreSQL's 65535 — and the whole query fails before it runs.\n * The relationship, junction and hierarchy loaders have chunked at this value\n * for exactly this reason; `listByIds()` is the remaining path that took ids\n * straight from the caller and did not.\n *\n * @param ids - Array of UUIDs to fetch\n * @returns Promise resolving to array of objects (order not guaranteed;\n * results are concatenated in chunk order)\n *\n * @example\n * ```typescript\n * const profiles = await profileCollection.listByIds(['id1', 'id2', 'id3']);\n * ```\n */\n public async listByIds(ids: string[]): Promise<ModelType[]> {\n if (ids.length === 0) return [];\n\n // Never chunk above a configured ceiling: an explicit `limit` is clamped by\n // `maxListLimit`, so a chunk larger than the ceiling would come back\n // truncated and silently drop ids the caller asked for.\n const chunkSize =\n this._maxListLimit === undefined\n ? IN_LIST_CHUNK_SIZE\n : Math.min(IN_LIST_CHUNK_SIZE, this._maxListLimit);\n\n const chunks = chunkArray(ids, chunkSize);\n if (chunks.length === 1) {\n return this.list({ limit: chunks[0].length, where: { id: chunks[0] } });\n }\n\n const results: ModelType[] = [];\n for (const idChunk of chunks) {\n results.push(\n ...(await this.list({ limit: idChunk.length, where: { id: idChunk } })),\n );\n }\n return results;\n }\n\n /**\n * Resolve the effective cache config for a read (issue #1498).\n *\n * Per-call options win: `false` forces a fresh read, `{ ttl }` enables\n * caching for this call. Otherwise falls back to the model-level\n * `@smrt({ cache })` config (inheritance-aware). Returns undefined when\n * the read should go straight to the database — the default.\n */\n private resolveReadCacheConfig(\n perCall?: CollectionCacheConfig | false,\n ): CollectionCacheConfig | undefined {\n if (perCall === false) return undefined;\n if (perCall) return perCall.ttl > 0 ? perCall : undefined;\n return ObjectRegistry.resolveCollectionCacheConfig(\n this.getResolvedItemQualifiedName(),\n );\n }\n\n /**\n * Execute a SELECT, optionally through the collection read cache.\n *\n * The cache key is the final SQL + bound parameters — computed after\n * interceptors (tenancy filters) and STI discriminators are applied, so\n * differently-scoped queries can never share an entry. Cached values are\n * raw rows; callers still run their normal post-query formatting path.\n */\n private async queryRowsWithCache(\n sql: string,\n params: unknown[],\n cacheConfig: CollectionCacheConfig | undefined,\n ): Promise<Record<string, unknown>[]> {\n if (!cacheConfig) {\n const result = await this.db.query(sql, ...params);\n return result.rows;\n }\n\n const dbKey = resolveDbCacheKey(this.db);\n const queryKey = buildQueryCacheKey(sql, params);\n\n // Start consuming peer invalidations before the first cached read so\n // a remote write can't go unseen for longer than necessary, and record\n // table-scoped interest so this process's own writes broadcast even\n // when the crossProcess opt-in only exists at the call site.\n if (cacheConfig.crossProcess) {\n ensureCacheInvalidationListener(this.db);\n registerCrossProcessCacheInterest(dbKey, this.tableName);\n }\n\n const cached = getCachedRows(dbKey, this.tableName, queryKey);\n if (cached) {\n return cached;\n }\n\n // Capture the table's invalidation generation BEFORE the round-trip; if a\n // concurrent write invalidates while this SELECT is in flight, setCachedRows\n // sees the bumped generation and drops the now-stale result instead of\n // caching it for the full TTL.\n const generation = getCacheGeneration(dbKey, this.tableName);\n const result = await this.db.query(sql, ...params);\n setCachedRows(\n dbKey,\n this.tableName,\n queryKey,\n result.rows,\n cacheConfig.ttl,\n generation,\n );\n return result.rows;\n }\n\n /**\n * Retrieves a single object from the collection by ID, slug, or a custom filter.\n *\n * Filter resolution:\n * - UUID string → `WHERE id = ?`\n * - Non-UUID string → `WHERE slug = ? AND context = ''`\n * - Object → `WHERE <key> = <value> [AND ...]`\n *\n * For STI child collections, an `AND _meta_type = '<qualifiedName>'` clause is\n * automatically appended so you only receive the correct subclass.\n *\n * Runs `beforeGet` / `afterGet` interceptors (used by multi-tenancy, etc.).\n *\n * @param filter - UUID string, slug string, or a WHERE conditions object\n * @param options.cache - Opt-in read-through cache for this call\n * (`{ ttl }` in milliseconds), or `false` to force a fresh read when the\n * model opted in via `@smrt({ cache })`. Defaults to the model config.\n * @returns The matching object instance, or `null` if not found\n *\n * @example\n * ```typescript\n * // By UUID\n * const product = await products.get('550e8400-e29b-41d4-a716-446655440000');\n *\n * // By slug\n * const product = await products.get('my-widget');\n *\n * // By custom filter\n * const product = await products.get({ sku: 'WID-001' });\n *\n * // Cached read (memoized for 60s, invalidated on writes)\n * const product = await products.get('my-widget', { cache: { ttl: 60_000 } });\n * ```\n *\n * @see {@link list} for multiple results\n * @see {@link findById} / {@link findOne} for convenience aliases\n */\n public async get(\n filter: string | SmrtWhereClause<ModelType>,\n options: { cache?: CollectionCacheConfig | false } = {},\n ): Promise<ModelType | null> {\n await this.ensureStorageReady();\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n\n // Execute beforeGet interceptors (e.g., tenancy validation)\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'get',\n this.constructor.name,\n );\n const interceptedFilter = await GlobalInterceptors.executeBeforeGet(\n itemClassName,\n filter,\n interceptorContext,\n );\n\n // String-filter resolution shares one helper with `beforeGet` interceptors\n // (#2365): an interceptor that rewrites the string into an object filter\n // must resolve id-vs-slug exactly the way this method would.\n let where: Record<string, unknown> =\n typeof interceptedFilter === 'string'\n ? resolveGetStringFilter(interceptedFilter)\n : interceptedFilter;\n\n // Fix for issue #386: Add _meta_type filter for STI child collections.\n // R5-canon: use the qualified item name as the LOOKUP KEY too, not\n // just for the comparison — passing the simple `itemClassName` can\n // resolve to another package's same-simple-name class via\n // `findClass`'s multi-strategy lookup, which would yield the WRONG\n // table-strategy / STI base.\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n const isSTI = tableStrategy === 'sti';\n\n if (isSTI) {\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n if (stiBase && stiBase !== itemQualifiedName) {\n where = {\n _meta_type: itemQualifiedName,\n ...where,\n };\n }\n }\n\n // convertWhereKeys is now sync (issue #663) - no await needed\n const convertedWhere = this.convertWhereKeys(where);\n const { sql: whereSql, values: whereValues } = buildWhere(convertedWhere);\n\n // `get()` reads `rows[0]` and discards the rest, so the row cap belongs in\n // the query, not in JavaScript (#2367). Without it a filter that is not\n // backed by a unique index — `get({ status: 'active' })`, or any slug lookup\n // on a table whose natural-key index was replaced by custom\n // `conflictColumns` — streams every matching row through the driver and\n // hydrates none of them. The literal is safe to interpolate: it is not\n // caller-derived, and `$n` placeholders here would have to be numbered after\n // the WHERE values on PostgreSQL.\n const fullSQL = `SELECT * FROM ${this.tableName} ${whereSql} LIMIT 1`;\n const rows = await this.queryRowsWithCache(\n fullSQL,\n whereValues,\n this.resolveReadCacheConfig(options.cache),\n );\n\n if (!rows?.[0]) {\n // Execute afterGet with null result\n // Explicitly specify type parameter since TypeScript can't infer from null\n return await GlobalInterceptors.executeAfterGet<ModelType>(\n itemClassName,\n null,\n interceptorContext,\n );\n }\n\n const fields = this.getFieldsSync();\n const instance = await this.hydrateResultRow(rows[0], fields, isSTI);\n\n // Execute afterGet interceptors (e.g., tenant validation)\n return await GlobalInterceptors.executeAfterGet(\n itemClassName,\n instance,\n interceptorContext,\n );\n }\n\n /**\n * Lists records from the collection with flexible filtering options\n *\n * @param options - Query options object\n * @param options.where - Record of conditions to filter results. Each key can include an operator\n * separated by a space (e.g., 'price >', 'name like'). Default operator is '='.\n * @param options.offset - Number of records to skip\n * @param options.limit - Maximum number of records to return\n * @param options.orderBy - Field(s) to order results by, with optional direction\n * @param options.select - Column-backed fields to return as plain rows without hydrating model instances\n *\n * @example\n * ```typescript\n * // Find active products priced between $100-$200\n * await collection.list({\n * where: {\n * 'price >': 100,\n * 'price <=': 200,\n * 'status': 'active', // equals operator is default\n * 'category in': ['A', 'B', 'C'], // IN operator for arrays\n * 'name like': '%shirt%', // LIKE for pattern matching\n * 'deleted_at !=': null // exclude deleted items\n * },\n * limit: 10,\n * offset: 0\n * });\n *\n * // Find users matching pattern but not in specific roles\n * await users.list({\n * where: {\n * 'email like': '%@company.com',\n * 'active': true,\n * 'role in': ['guest', 'blocked'],\n * 'last_login <': lastMonth\n * }\n * });\n *\n * // Fetch compact page-key rows without constructing full objects\n * await collection.list({\n * select: ['id', 'title', 'accountId'],\n * where: { status: 'open' },\n * orderBy: 'created_at DESC',\n * limit: 50,\n * });\n * ```\n *\n * @returns Promise resolving to an array of model instances, or plain selected rows when `select` is present\n */\n public async list<const Select extends readonly SmrtSelectField<ModelType>[]>(\n options: Omit<SmrtListOptions<ModelType>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<Record<string, unknown>[]>;\n public async list<const Select extends readonly SmrtSelectField<ModelType>[]>(\n options: Omit<SmrtListOptions<ModelType>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope?: undefined;\n },\n ): Promise<SmrtSelectedRow<ModelType, Select>[]>;\n public async list(\n options: Omit<SmrtListOptions<ModelType>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<SmrtObject[]>;\n public async list(options?: undefined): Promise<ModelType[]>;\n public async list(\n options: Omit<SmrtListOptions<ModelType>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope?: undefined;\n },\n ): Promise<ModelType[]>;\n public async list(\n options:\n | (Omit<SmrtListOptions<ModelType>, 'select'> & {\n select?: undefined;\n })\n | undefined,\n ): Promise<SmrtObject[]>;\n public async list(\n options: SmrtListOptions<ModelType> | undefined,\n ): Promise<SmrtObject[] | Record<string, unknown>[]>;\n public async list(\n options: Omit<SmrtListOptions<ModelType>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope?: undefined;\n },\n ): Promise<ModelType[]>;\n public async list(\n options: SmrtListOptions<ModelType> = {},\n ): Promise<SmrtObject[] | Record<string, unknown>[]> {\n await this.ensureStorageReady();\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n\n // Execute beforeList interceptors (e.g., tenancy filtering)\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const interceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n itemClassName,\n options as InterceptorListOptions,\n interceptorContext,\n )) ??\n (options as InterceptorListOptions | undefined) ??\n {};\n\n let { where, offset, limit, orderBy, select, stiScope } =\n interceptedOptions as SmrtListOptions<ModelType>;\n\n // STI: Child collections should automatically filter by _meta_type.\n // R5-canon: qualified-key lookup so a same-simple-name class in\n // another package can't yield the wrong table strategy / STI base.\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n const isSTI = tableStrategy === 'sti';\n\n where = this.applyStiReadScope(where, stiScope);\n\n // Resolve _meta_type to qualified name if user provided simple class name (Issue #713)\n where = resolveMetaTypeInWhere(where);\n\n // convertWhereKeys is now sync (issue #663) - no await needed\n const convertedWhere = this.convertWhereKeys(where || {});\n const { sql: whereSql, values: whereValues } = buildWhere(convertedWhere);\n\n const fields = this.getFieldsSync();\n const projection =\n select !== undefined\n ? this.resolveProjectionSelect(select, fields, isSTI)\n : undefined;\n if (\n projection &&\n interceptedOptions.include &&\n interceptedOptions.include.length > 0\n ) {\n throw new Error(\n 'collection.list({ select }) returns plain projection rows and cannot eager-load relationships. Remove include or omit select.',\n );\n }\n\n const orderBySql = this.buildOrderBySql(orderBy, fields);\n\n let limitOffsetSql = '';\n const limitOffsetValues: (number | undefined)[] = [];\n let paramIndex = whereValues.length + 1;\n\n // #2367: reject a malformed bound here rather than binding it. A NaN or\n // fractional `limit` used to reach the driver verbatim, where PostgreSQL\n // answers `invalid input syntax for type bigint` — a 500 for what is a\n // caller error. Then apply the collection's opt-in default/ceiling.\n const boundedLimit = this.applyListBounds(assertQueryBound(limit, 'limit'));\n const boundedOffset = assertQueryBound(offset, 'offset');\n\n if (boundedLimit !== undefined) {\n limitOffsetSql += ` LIMIT $${paramIndex++}`;\n limitOffsetValues.push(boundedLimit);\n }\n\n if (boundedOffset !== undefined) {\n limitOffsetSql += ` OFFSET $${paramIndex++}`;\n limitOffsetValues.push(boundedOffset);\n }\n\n const selectSql = projection ? projection.sql : '*';\n const sql = `SELECT ${selectSql} FROM ${this.tableName} ${whereSql} ${orderBySql} ${limitOffsetSql}`;\n const params = [...whereValues, ...limitOffsetValues];\n\n // Resolve the cache preference from the post-interceptor options —\n // beforeList interceptors may legally set or clear `cache` (e.g. force\n // read-through on admin paths, enable caching per tenant).\n const rows = await this.queryRowsWithCache(\n sql,\n params,\n this.resolveReadCacheConfig(\n (interceptedOptions as { cache?: CollectionCacheConfig | false }).cache,\n ),\n );\n\n if (projection) {\n // Projection is the no-hydration path. beforeList interceptors still ran\n // above, but afterList hooks are intentionally skipped because they are\n // defined around hydrated SmrtObject instances.\n return rows.map((item) =>\n this.formatProjectionRow(item, fields, projection.outputFields),\n );\n }\n\n // STI: Hydrate instances polymorphically based on _meta_type\n // Reuse tableStrategy and isSTI from earlier in the function\n const instances = await this.hydrateResultRows(rows, fields, isSTI);\n\n // Eager load specified relationships\n if (interceptedOptions.include && interceptedOptions.include.length > 0) {\n // Cast to ModelType[] - instances are guaranteed to be subtypes of ModelType\n // even in STI mode where they may be different subclasses\n await this.eagerLoadRelationships(\n instances as ModelType[],\n interceptedOptions.include,\n );\n }\n\n // Execute afterList interceptors (e.g., tenant validation)\n const finalInstances = await GlobalInterceptors.executeAfterList(\n itemClassName,\n instances,\n interceptorContext,\n );\n\n return finalInstances;\n }\n\n /**\n * Eagerly load relationships for a collection of instances\n *\n * Optimizes loading by batching queries for foreignKey relationships to avoid N+1 queries.\n *\n * @param instances - Array of object instances to load relationships for\n * @param relationships - Array of relationship field names to load\n * @private\n */\n private async eagerLoadRelationships(\n instances: ModelType[],\n relationships: string[],\n ): Promise<void> {\n if (instances.length === 0) return;\n\n for (const fieldName of relationships) {\n // Get relationship metadata\n const relationshipMeta = ObjectRegistry.getRelationships(\n this._itemClass.name,\n );\n const relationship = relationshipMeta.find(\n (r) => r.fieldName === fieldName,\n );\n\n if (!relationship) {\n logger.warn(\n `Relationship ${fieldName} not found on ${this._itemClass.name}, skipping eager load`,\n );\n continue;\n }\n\n if (\n relationship.type === 'foreignKey' ||\n relationship.type === 'crossPackageRef'\n ) {\n // crossPackageRef target lives in another package — make sure its\n // manifest is loaded before we try to instantiate the target collection.\n if (relationship.type === 'crossPackageRef') {\n await ObjectRegistry.ensureManifestLoaded(relationship.targetClass);\n }\n // Batch load foreignKey / crossPackageRef relationships\n await this.batchLoadForeignKeys(instances, fieldName, relationship);\n } else if (relationship.type === 'oneToMany') {\n // Load oneToMany relationships (less optimizable)\n await this.batchLoadOneToMany(instances, fieldName, relationship);\n } else if (relationship.type === 'manyToMany') {\n await this.batchLoadManyToMany(instances, fieldName, relationship);\n }\n }\n }\n\n /**\n * Batch load foreignKey relationships to avoid N+1 queries\n *\n * @param instances - Instances to load relationships for\n * @param fieldName - Name of the foreignKey field\n * @param relationship - Relationship metadata\n * @private\n */\n private async batchLoadForeignKeys(\n instances: ModelType[],\n fieldName: string,\n relationship: import('./registry').RelationshipMetadata,\n ): Promise<void> {\n // Collect all unique foreign key values\n const foreignKeyValues = new Set<string>();\n for (const instance of instances) {\n const value = instance[fieldName as keyof ModelType];\n if (value && typeof value === 'string') {\n foreignKeyValues.add(value);\n }\n }\n\n if (foreignKeyValues.size === 0) return;\n\n // Get or create cached collection instance\n let targetCollection: SmrtCollection<SmrtObject> | undefined;\n try {\n targetCollection = await ObjectRegistry.getCollection(\n relationship.targetClass,\n this.options,\n );\n } catch (error) {\n logger.warn(`Could not get collection for ${relationship.targetClass}`, {\n error,\n });\n return;\n }\n\n // Load all related objects, chunked to stay under SQLITE_MAX_VARIABLE_NUMBER (999).\n const fkValueList = Array.from(foreignKeyValues);\n const relatedObjects: SmrtObject[] = [];\n for (const idChunk of chunkArray(fkValueList, IN_LIST_CHUNK_SIZE)) {\n const batch = await targetCollection.list({\n where: { 'id in': idChunk },\n });\n relatedObjects.push(...batch);\n }\n\n // Build a map of ID to object for quick lookup\n const relatedMap = new Map<string, SmrtObject>();\n for (const obj of relatedObjects) {\n if (obj.id) relatedMap.set(obj.id, obj);\n }\n\n // Assign loaded objects to instances\n for (const instance of instances) {\n const foreignKeyValue = instance[fieldName as keyof ModelType];\n if (foreignKeyValue && typeof foreignKeyValue === 'string') {\n const relatedObject = relatedMap.get(foreignKeyValue);\n if (relatedObject) {\n // Prime the lazy-load cache through SmrtObject's sanctioned internal\n // setter rather than reaching into the private field directly.\n instance._setLoadedRelationship(fieldName, relatedObject);\n }\n }\n }\n }\n\n /**\n * Batch load oneToMany relationships\n *\n * @param instances - Instances to load relationships for\n * @param fieldName - Name of the oneToMany field\n * @param relationship - Relationship metadata\n * @private\n */\n private async batchLoadOneToMany(\n instances: ModelType[],\n fieldName: string,\n relationship: import('./registry').RelationshipMetadata,\n ): Promise<void> {\n // Find the inverse foreignKey field. An instance can satisfy an inverse FK\n // that targets its own class or any (STI) ancestor it inherits the\n // oneToMany from. Mirrors loadRelatedMany so lazy and eager (`include:`)\n // loading resolve the same inverse side.\n const inverseRelationships = ObjectRegistry.getInverseRelationshipsForSelf(\n this._itemClass.name,\n );\n const inverseCandidates = inverseRelationships.filter(\n (r) =>\n r.sourceClass === relationship.targetClass && r.type === 'foreignKey',\n );\n // Honor an explicit `@oneToMany(Target, { foreignKey })` when the target\n // declares multiple foreign keys back to this class; otherwise fall back\n // to the first match (legacy behavior).\n const explicitForeignKey = relationship.options?.foreignKey as\n | string\n | undefined;\n const matchedForeignKey = explicitForeignKey\n ? inverseCandidates.find((r) => r.fieldName === explicitForeignKey)\n : undefined;\n if (explicitForeignKey && !matchedForeignKey) {\n // A misspelled / stale `foreignKey` is a configuration error, not a\n // recoverable data condition — fail loudly here too so eager (`include:`)\n // loading behaves identically to lazy loadRelatedMany rather than\n // silently producing empty arrays.\n throw new Error(\n `oneToMany ${fieldName} specifies foreignKey '${explicitForeignKey}', but ${relationship.targetClass} has no matching inverse foreignKey. Candidates: ${inverseCandidates.map((r) => r.fieldName).join(', ') || '(none)'}`,\n );\n }\n // Prefer an inverse FK that targets this exact class before falling back\n // to an ancestor's (mirrors loadRelatedMany).\n const inverseForeignKey =\n matchedForeignKey ??\n inverseCandidates.find((r) => r.targetClass === this._itemClass.name) ??\n inverseCandidates[0];\n\n if (!inverseForeignKey) {\n logger.warn(\n `Could not find inverse foreignKey for oneToMany ${fieldName}`,\n );\n return;\n }\n\n // Collect all instance IDs\n const instanceIds = instances\n .map((i) => i.id)\n .filter((id): id is string => !!id);\n\n if (instanceIds.length === 0) return;\n\n // Get or create cached collection instance\n let targetCollection: SmrtCollection<SmrtObject> | undefined;\n try {\n targetCollection = await ObjectRegistry.getCollection(\n relationship.targetClass,\n this.options,\n );\n } catch (error) {\n logger.warn(`Could not get collection for ${relationship.targetClass}`, {\n error,\n });\n return;\n }\n\n // Load all related objects, chunked to stay under SQLITE_MAX_VARIABLE_NUMBER (999).\n const relatedObjects: SmrtObject[] = [];\n for (const idChunk of chunkArray(instanceIds, IN_LIST_CHUNK_SIZE)) {\n const batch = await targetCollection.list({\n where: { [`${inverseForeignKey.fieldName} in`]: idChunk },\n });\n relatedObjects.push(...batch);\n }\n\n // Group related objects by the foreign key value. The key type is `unknown`\n // (not `string`) to preserve the prior behavior exactly: a non-string FK\n // value still creates its own bucket rather than being skipped.\n const relatedMap = new Map<unknown, SmrtObject[]>();\n for (const obj of relatedObjects) {\n // Access dynamic property safely - obj is a SmrtObject with dynamic fields\n const foreignKeyValue = (obj as unknown as Record<string, unknown>)[\n inverseForeignKey.fieldName\n ];\n if (!relatedMap.has(foreignKeyValue)) {\n relatedMap.set(foreignKeyValue, []);\n }\n relatedMap.get(foreignKeyValue)?.push(obj);\n }\n\n // Assign loaded objects to instances\n for (const instance of instances) {\n const relatedArray = relatedMap.get(instance.id as string) || [];\n // Prime the lazy-load cache through SmrtObject's sanctioned internal\n // setter rather than reaching into the private field directly.\n instance._setLoadedRelationship(fieldName, relatedArray);\n }\n }\n\n /**\n * Batch-load manyToMany relationships through a junction table.\n *\n * Issues two queries instead of N: one against the junction table to map\n * source IDs to target IDs, and one against the target table to hydrate\n * the related rows. Results are grouped by source instance.\n *\n * @param instances - Instances whose manyToMany field should be populated\n * @param fieldName - Name of the @manyToMany decorated field\n * @param relationship - Relationship metadata from the registry\n * @private\n */\n private async batchLoadManyToMany(\n instances: ModelType[],\n fieldName: string,\n relationship: import('./registry').RelationshipMetadata,\n ): Promise<void> {\n const instanceIds = instances\n .map((i) => i.id)\n .filter((id): id is string => !!id);\n if (instanceIds.length === 0) return;\n\n // Delegate join-coordinate resolution to a sample instance — it shares the\n // same registry metadata as every other instance in this batch.\n let through: string;\n let sourceColumn: string;\n let targetColumn: string;\n let targetClassName: string;\n try {\n // `resolveManyToManyJoin` is a protected SmrtObject method; view the\n // sample instance as the shape exposing it. The call keeps `this` bound\n // (method invoked on the receiver), so registry metadata resolves on the\n // sample as intended.\n const sample = instances[0] as unknown as {\n resolveManyToManyJoin(\n fieldName: string,\n relationship: import('./registry').RelationshipMetadata,\n ): Promise<{\n through: string;\n sourceColumn: string;\n targetColumn: string;\n targetClassName: string;\n }>;\n };\n const join = await sample.resolveManyToManyJoin(fieldName, relationship);\n through = join.through;\n sourceColumn = join.sourceColumn;\n targetColumn = join.targetColumn;\n targetClassName = join.targetClassName;\n } catch (error) {\n logger.warn(\n `Could not resolve manyToMany join for ${fieldName} on ${this._itemClass.name}`,\n { error },\n );\n return;\n }\n\n // Default empty arrays for every instance so callers always see an array.\n // Seed through SmrtObject's sanctioned internal setter rather than reaching\n // into the private relationship-cache field directly.\n for (const instance of instances) {\n instance._setLoadedRelationship(fieldName, []);\n }\n\n // Pull junction rows in chunks. SQLite's default SQLITE_MAX_VARIABLE_NUMBER\n // is 999, so we cap each IN-list at IN_LIST_CHUNK_SIZE to stay well below\n // the limit on the supported backends (sqlite/libsql/postgres).\n const junctionRowsAll: Array<{\n [key: string]: unknown;\n }> = [];\n for (const idChunk of chunkArray(instanceIds, IN_LIST_CHUNK_SIZE)) {\n const placeholders = idChunk.map(() => '?').join(', ');\n const result = await this.db.query(\n `SELECT \"${sourceColumn}\", \"${targetColumn}\" FROM \"${through}\" WHERE \"${sourceColumn}\" IN (${placeholders})`,\n idChunk,\n );\n junctionRowsAll.push(...result.rows);\n }\n\n if (junctionRowsAll.length === 0) return;\n\n // sourceId -> [targetIds...]\n const sourceToTargets = new Map<string, string[]>();\n const allTargetIds = new Set<string>();\n for (const row of junctionRowsAll) {\n const sId = row[sourceColumn];\n const tId = row[targetColumn];\n if (typeof sId !== 'string' || typeof tId !== 'string') continue;\n allTargetIds.add(tId);\n const list = sourceToTargets.get(sId) ?? [];\n list.push(tId);\n sourceToTargets.set(sId, list);\n }\n\n if (allTargetIds.size === 0) return;\n\n // Hydrate all target objects. Also chunked so we don't blow the\n // placeholder limit on a wide manyToMany.\n let targetCollection: SmrtCollection<SmrtObject>;\n try {\n targetCollection = await ObjectRegistry.getCollection(\n targetClassName,\n this.options,\n );\n } catch (error) {\n logger.warn(\n `Could not get collection for manyToMany target ${targetClassName}`,\n { error },\n );\n return;\n }\n const targetIdList = Array.from(allTargetIds);\n const targetObjects: SmrtObject[] = [];\n for (const idChunk of chunkArray(targetIdList, IN_LIST_CHUNK_SIZE)) {\n const batch = await targetCollection.list({\n where: { 'id in': idChunk },\n });\n targetObjects.push(...batch);\n }\n\n const targetById = new Map<string, SmrtObject>();\n for (const obj of targetObjects) {\n if (obj.id) targetById.set(obj.id, obj);\n }\n\n // Assign grouped results\n for (const instance of instances) {\n const targetIds = sourceToTargets.get(instance.id as string) ?? [];\n const objects = targetIds\n .map((id) => targetById.get(id))\n .filter((o) => o !== undefined);\n // Seed through SmrtObject's sanctioned internal setter rather than reaching\n // into the private relationship-cache field directly.\n instance._setLoadedRelationship(fieldName, objects);\n }\n }\n\n /**\n * Creates and persists a new instance of the collection's item class.\n *\n * Instantiates the model with the given field values, calls `initialize()`,\n * assigns a UUID if none is provided, then calls `save()` to write the row\n * to the database.\n *\n * For STI collections, pass `_meta_type` to create a specific subclass.\n * The correct constructor is resolved via `ObjectRegistry` (polymorphic creation).\n *\n * @param options - Field values for the new object. Accepts any public field on\n * the model class plus the STI `_meta_type` discriminator.\n * @returns The newly created and saved model instance\n * @throws {ValidationError} If a `required` field is missing or a unique constraint is violated\n * @throws {DatabaseError} If the write fails\n *\n * @example\n * ```typescript\n * // Regular creation\n * const product = await products.create({ name: 'Widget', price: 9.99 });\n * console.log(product.id); // UUID assigned during save\n *\n * // STI polymorphic creation\n * const article = await contents.create({\n * _meta_type: '@happyvertical/smrt-content:Article',\n * title: 'Hello World',\n * });\n * ```\n *\n * @see {@link getOrUpsert} to avoid duplicates by finding-or-creating\n */\n public async create(options: SmrtCreateInput<ModelType>) {\n let itemClassName = this.getResolvedItemClassName();\n\n // Ensure manifest is loaded before creating instance metadata; save() will\n // ensure the actual table exists right before persistence.\n await ObjectRegistry.ensureManifestLoaded(itemClassName);\n itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n\n // STI: Check for polymorphic instantiation.\n // R5-canon: qualified-key lookup so colliding simple names across\n // packages don't yield the wrong table strategy.\n const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);\n\n if (tableStrategy === 'sti' && options._meta_type) {\n // Use polymorphic instantiation for STI child classes\n // Pass raw options (not params) - createPolymorphic() will add ai, db, _skipLoad\n const instance = await this.createPolymorphic(\n options._meta_type,\n options,\n );\n // Generate ID if not provided, then save. `_id` is a private SmrtObject\n // backing field; view as a writable bag for the runtime assignment.\n if (!instance.id) {\n (instance as unknown as { _id?: string })._id = crypto.randomUUID();\n }\n if (options._insertOnly === true) {\n instance.requireInsertOnSave();\n }\n await instance.save();\n return instance;\n }\n\n // For non-STI or STI base class without _meta_type, proceed with direct instantiation\n // Schema already initialized in Collection.create() static factory\n const params = {\n ai: this.options.ai,\n // Pass the actual database instance, not options\n // This ensures objects share the same connection as the collection\n // Critical for in-memory databases like DuckDB :memory: where each\n // connection gets a separate database\n db: this.db,\n _skipLoad: true, // Don't try to load from DB - this is a new object\n ...options,\n };\n\n // Direct instantiation - all SmrtObject classes support this pattern\n const instance = new this._itemClass(params);\n await instance.initialize();\n\n // For STI collections, set _meta_type to the qualified class name (fix for issue #442)\n // This ensures _meta_type is available immediately after creation, not just after DB load\n // Use constructor-based lookup to avoid name collision issues (issue #713)\n // When classes are registered with qualified names as keys (e.g., from consumer plugin),\n // name-based lookup fails. Constructor lookup uses a WeakMap index for O(1) lookups.\n if (tableStrategy === 'sti') {\n // `_meta_type` is a runtime-only STI field; view as a writable bag.\n (instance as unknown as { _meta_type?: string })._meta_type =\n itemQualifiedName;\n }\n // Generate ID if not provided, then save. `_id` is a private SmrtObject\n // backing field; view as a writable bag for the runtime assignment.\n if (!instance.id) {\n (instance as unknown as { _id?: string })._id = crypto.randomUUID();\n }\n if (options._insertOnly === true) {\n instance.requireInsertOnSave();\n }\n await instance.save();\n return instance;\n }\n\n /**\n * Creates an instance of the correct subclass for STI polymorphic queries\n *\n * @param className - Name of the class to instantiate (from _meta_type)\n * @param options - Data to initialize the instance with\n * @returns Promise resolving to the instance of the correct subclass\n * @private\n */\n private async createPolymorphic(\n className: string | null | undefined,\n options: Record<string, unknown>,\n hydrationOptions: { hydrateOnly?: boolean } = {},\n ): Promise<ModelType> {\n // Schema already initialized in Collection.create() static factory\n\n // Validate discriminator is present\n if (!className || className === null || className === undefined) {\n const { DatabaseError } = await import('./errors.js');\n throw DatabaseError.missingDiscriminator(\n this._itemClass.name,\n typeof options?.id === 'string' ? options.id : undefined,\n );\n }\n\n // Get the correct class constructor from ObjectRegistry\n // Support both qualified names (new format: @pkg:Class) and simple names (legacy)\n let registeredClass = ObjectRegistry.getClassByQualifiedName(className);\n if (!registeredClass) {\n // Fall back to simple name lookup for legacy data\n registeredClass = ObjectRegistry.getClass(className);\n }\n\n if (!registeredClass) {\n await ObjectRegistry.ensureManifestLoaded(className);\n registeredClass = ObjectRegistry.getClassByQualifiedName(className);\n if (!registeredClass) {\n registeredClass = ObjectRegistry.getClass(className);\n }\n }\n\n if (!registeredClass) {\n throw new Error(\n `STI polymorphic query failed: Class '${className}' not found in ObjectRegistry. ` +\n `Ensure the class is registered with @smrt() decorator or available via an installed SMRT manifest.`,\n );\n }\n\n const params = {\n ai: this.options.ai,\n db: this.db,\n _skipLoad: true,\n ...(hydrationOptions.hydrateOnly\n ? {\n _reuseInitializedDb: true,\n _deferRuntimeInitialization: true,\n }\n : {}),\n ...options,\n };\n\n // Instantiate the correct subclass\n const instance = new registeredClass.constructor(params);\n if (hydrationOptions.hydrateOnly) {\n // Models need to distinguish authoritative DB hydration from a fresh\n // `_skipLoad` instance while initialize() captures immutable state.\n instance.markAsPersisted();\n }\n await instance.initialize();\n\n // Ensure _meta_type is set on the instance (fix for issue #442)\n // Use qualified name if available, otherwise keep the input className\n // (which may already be qualified for new data or simple for legacy data).\n // `_meta_type` is a runtime-only STI field; view as a writable bag.\n (instance as unknown as { _meta_type?: string })._meta_type =\n registeredClass?.qualifiedName || className;\n\n return instance as ModelType;\n }\n\n /**\n * Finds an existing record matching `data` or creates it if not found.\n *\n * Look-up priority:\n * 1. `data.id` — query by primary key\n * 2. `data.slug` — query by slug + context\n * 3. Fallback — query by the full `data` object as a WHERE clause\n *\n * If a matching record is found, it is updated with any changed fields from\n * `data` (diffed against the existing record) and saved. If no match is\n * found, `defaults` are merged with `data` and a new record is created.\n *\n * @param data - The field values to find or upsert\n * @param defaults - Extra default values applied only when creating a new record\n * @returns The existing (possibly updated) or newly created object instance\n *\n * @example\n * ```typescript\n * // Find-or-create a tag by slug\n * const tag = await tags.getOrUpsert({ slug: 'javascript', name: 'JavaScript' });\n *\n * // With defaults applied only on creation\n * const user = await users.getOrUpsert(\n * { email: 'alice@example.com' },\n * { role: 'member', active: true },\n * );\n * ```\n *\n * @see {@link create} for always-insert semantics\n * @see {@link get} for read-only lookup\n */\n public async getOrUpsert(\n data: Record<string, unknown>,\n defaults: Record<string, unknown> = {},\n ) {\n const logicalData = this.normalizeLogicalData(data);\n const logicalDefaults = this.normalizeLogicalData(defaults);\n let where: Record<string, unknown> = {};\n const diffData = { ...logicalData };\n if (logicalData.id) {\n where = { id: logicalData.id };\n delete diffData.id;\n delete diffData.slug;\n delete diffData.context;\n } else if (logicalData.slug) {\n where = { slug: logicalData.slug, context: logicalData.context || '' };\n delete diffData.slug;\n delete diffData.context;\n } else {\n where = logicalData;\n }\n // Force a fresh read: this probe decides create-vs-update, so a stale cache\n // hit could update a row that no longer exists or miss one that does.\n const existing = await this.get(where, { cache: false });\n if (existing) {\n const diff = this.getDiffSync(existing, diffData);\n if (diff) {\n Object.assign(existing, diff);\n await existing.save();\n }\n return existing;\n }\n const createData = {\n ...logicalDefaults,\n ...logicalData,\n } as SmrtCreateInput<ModelType>;\n\n return await this.create(createData);\n }\n\n /**\n * Gets differences between an existing object and new data\n *\n * @param existing - Existing object\n * @param data - New data\n * @returns Object containing only the changed fields\n */\n async getDiff(\n existing: SmrtObject | Record<string, unknown>,\n data: Record<string, unknown>,\n ): Promise<Record<string, unknown> | null> {\n return this.getDiffSync(existing, data);\n }\n\n private getDiffSync(\n existing: SmrtObject | Record<string, unknown>,\n data: Record<string, unknown>,\n ): Record<string, unknown> | null {\n const fields = this.getFieldsSync();\n const validKeys = new Set([\n ...Object.keys(fields),\n 'id',\n 'slug',\n 'context',\n ]);\n // `existing` may be a SmrtObject instance (no index signature); read its\n // dynamic fields through a record view.\n const existingRecord = existing as unknown as Record<string, unknown>;\n const diff = Object.keys(data).reduce(\n (acc, key) => {\n if (\n validKeys.has(key) &&\n !this.areEquivalentValues(existingRecord[key], data[key])\n ) {\n acc[key] = data[key];\n }\n return acc;\n },\n {} as Record<string, unknown>,\n );\n\n return Object.keys(diff).length > 0 ? diff : null;\n }\n\n /**\n * Gets field definitions for the collection's item class\n *\n * @returns Object containing field definitions\n */\n async getFields(): Promise<Record<string, CollectionFieldDefinition>> {\n // `fieldsFromClass` returns the open `Record<string, unknown>` field bag;\n // its entries structurally match `CollectionFieldDefinition` (`name`,\n // `type`, `_meta`). View through `unknown` at this boundary.\n return (await fieldsFromClass(this._itemClass)) as unknown as Record<\n string,\n CollectionFieldDefinition\n >;\n }\n\n /**\n * Normalize user input into the model's logical field names before diffing or creation.\n *\n * Accepts both camelCase and snake_case keys while preserving framework meta fields.\n */\n private normalizeLogicalData(\n data: Record<string, unknown>,\n ): Record<string, unknown> {\n const fields = this.getFieldsSync();\n const normalized: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) {\n normalized[key] = value;\n continue;\n }\n\n const camelKey = key.includes('_') ? toCamelCase(key) : key;\n const snakeKey = key.includes('_') ? key : toSnakeCase(key);\n const outputKey =\n key in fields\n ? key\n : camelKey in fields\n ? camelKey\n : snakeKey in fields\n ? snakeKey\n : key;\n\n normalized[outputKey] = value;\n }\n\n return normalized;\n }\n\n /**\n * Preserve persisted core timestamp fields during lightweight hydration.\n */\n private withHydratedCoreFields(\n data: Record<string, unknown>,\n ): Record<string, unknown> {\n const hydratedData = { ...data };\n\n if (\n hydratedData.createdAt !== undefined &&\n hydratedData.created_at === undefined\n ) {\n hydratedData.created_at = hydratedData.createdAt;\n }\n\n if (\n hydratedData.updatedAt !== undefined &&\n hydratedData.updated_at === undefined\n ) {\n hydratedData.updated_at = hydratedData.updatedAt;\n }\n\n return hydratedData;\n }\n\n /**\n * Treat date-equivalent values as unchanged even if their runtime types differ.\n */\n private areEquivalentValues(\n existingValue: unknown,\n nextValue: unknown,\n ): boolean {\n if (existingValue instanceof Date || nextValue instanceof Date) {\n const existingTime = this.toComparableTime(existingValue);\n const nextTime = this.toComparableTime(nextValue);\n\n if (existingTime !== null && nextTime !== null) {\n return existingTime === nextTime;\n }\n }\n\n return existingValue === nextValue;\n }\n\n /**\n * Convert supported date inputs into comparable timestamps.\n */\n private toComparableTime(value: unknown): number | null {\n if (value instanceof Date) {\n const timestamp = value.getTime();\n return Number.isNaN(timestamp) ? null : timestamp;\n }\n\n if (typeof value === 'string' && value.trim()) {\n const parsedDate = new Date(value);\n const timestamp = parsedDate.getTime();\n return Number.isNaN(timestamp) ? null : timestamp;\n }\n\n return null;\n }\n\n /**\n * Gets field definitions synchronously from cache.\n *\n * This method provides sync access to fields for use in query methods,\n * avoiding the async overhead of getFields() on every query.\n * Fields are cached during create() initialization.\n *\n * @returns Map containing field definitions\n * @private\n */\n getFieldsSync(): Record<string, CollectionFieldDefinition> {\n if (this._cachedFields) {\n return this._cachedFields;\n }\n // Fallback to ObjectRegistry sync method if cache not populated\n // This handles edge cases where collection wasn't created via static create()\n const className = this.getResolvedItemClassName();\n const fields = ObjectRegistry.getFields(className);\n // Convert Map to Record for consistency with getFields() return type.\n // Registry `RegisteredField`s are a structural superset of the members the\n // collection reads (`type`, `sensitive`, `_meta`); view through `unknown`.\n return Object.fromEntries(fields) as unknown as Record<\n string,\n CollectionFieldDefinition\n >;\n }\n\n /**\n * Generates database schema for the collection's item class\n *\n * Leverages ObjectRegistry's cached schema for instant retrieval.\n *\n * @returns Schema object for database setup\n */\n async generateSchema() {\n // Always generate fresh schema to ensure latest field mapping is used\n const { generateSchema } = await import('./schema/utils.js');\n return await generateSchema(this._itemClass);\n }\n\n /**\n * Gets the database table name for this collection\n */\n get tableName() {\n if (!this._tableName) {\n // For STI, use the base class's table name from schema (manifest-derived).\n // R5-canon: use the qualified item name as the lookup key so a\n // colliding simple name in another package can't yield the wrong\n // tableStrategy / STI base and route every downstream query\n // (get/list/count/create) to the wrong table.\n const className = this.getResolvedItemClassName();\n const qualifiedName = this.getResolvedItemQualifiedName();\n const tableStrategy = ObjectRegistry.getTableStrategy(qualifiedName);\n const fallbackTableName =\n (this._itemClass as unknown as { SMRT_TABLE_NAME?: string })\n .SMRT_TABLE_NAME || classnameToTablename(className);\n\n if (tableStrategy === 'sti') {\n const stiBase = ObjectRegistry.getSTIBase(qualifiedName);\n if (stiBase) {\n // Use base class's schema tableName (from manifest)\n const baseSchema = ObjectRegistry.getSchema(stiBase);\n if (baseSchema?.tableName) {\n this._tableName = baseSchema.tableName;\n } else {\n // Fallback to own schema tableName\n const ownSchema = ObjectRegistry.getSchema(className);\n this._tableName = ownSchema?.tableName || fallbackTableName;\n }\n } else {\n // Fallback to own schema tableName\n const ownSchema = ObjectRegistry.getSchema(className);\n this._tableName = ownSchema?.tableName || fallbackTableName;\n }\n } else {\n // CTI: Use own schema tableName\n const ownSchema = ObjectRegistry.getSchema(className);\n this._tableName = ownSchema?.tableName || fallbackTableName;\n }\n }\n return this._tableName;\n }\n\n /**\n * Generates a table name from the collection class name\n *\n * @returns Generated table name\n */\n generateTableName() {\n // Convert camelCase/PascalCase to snake_case and pluralize\n const tableName = this._className\n // Insert underscore between lower & upper case letters\n .replace(/([a-z])([A-Z])/g, '$1_$2')\n // Convert to lowercase\n .toLowerCase()\n // Handle basic pluralization rules\n .replace(/([^s])$/, '$1s')\n // Handle special cases ending in 'y'\n .replace(/y$/, 'ies');\n\n return tableName;\n }\n\n /**\n * Deletes a record from the collection by ID\n *\n * Loads the object and calls its delete() method, ensuring all interceptors\n * and lifecycle hooks (beforeDelete/afterDelete) are executed correctly.\n *\n * @param id - The ID of the record to delete\n * @returns Promise resolving to true if deleted, false if not found\n *\n * @example\n * ```typescript\n * const success = await collection.delete('some-uuid');\n * if (success) {\n * console.log('Record deleted');\n * }\n * ```\n */\n public async delete(id: string): Promise<boolean> {\n // Schema already initialized in Collection.create() static factory\n\n // Ensure manifest is loaded for external packages\n await ObjectRegistry.ensureManifestLoaded(this.getResolvedItemClassName());\n\n // Load the object first - if it doesn't exist, return false immediately.\n // Force a fresh read: this probe gates a mutation and decides the return\n // value, so a stale cache hit could delete a phantom (returning true for a\n // row another process already removed) or skip a real delete.\n const instance = await this.get(id, { cache: false });\n if (!instance) {\n return false;\n }\n\n // Call the object's delete() method, which handles:\n // - beforeDelete interceptors\n // - beforeDelete lifecycle hooks\n // - Database deletion\n // - afterDelete lifecycle hooks\n // - afterDelete interceptors\n await instance.delete();\n\n return true;\n }\n\n /**\n * Returns the number of records matching the given filter conditions.\n *\n * Executes a `SELECT COUNT(*)` query with the same WHERE conversion as\n * `list()` (camelCase field names, operator suffixes, STI auto-filtering).\n * `limit`, `offset`, and `orderBy` are not applicable and are ignored.\n *\n * Note: `count()` is never served from the read cache (issue #1498) — only\n * `list()`/`get()` are. On a page that caches `list()`, a `count()` issued\n * in the same request reflects the live database and may briefly diverge\n * from the cached rows within the TTL window.\n *\n * @param options.where - Filter conditions (same syntax as `list()`)\n * @returns Total count of matching records as an integer\n *\n * @example\n * ```typescript\n * const total = await products.count();\n * const activeCount = await products.count({ where: { status: 'active' } });\n * const expensiveCount = await products.count({ where: { 'price >': 100 } });\n * ```\n *\n * @see {@link list} for retrieving the actual records\n */\n public async count(\n options: {\n where?: SmrtListWhereClause<ModelType>;\n stiScope?: SmrtStiReadScope;\n } = {},\n ) {\n await this.ensureStorageReady();\n const itemClassName = this.getResolvedItemClassName();\n\n // Security (#1540): count() is a list-shaped read, so run the same\n // `beforeList` interceptors (tenant filtering, etc.). Without this, count()\n // bypasses tenant scoping and returns a cross-tenant total even when the\n // matching list() is correctly filtered — leaking row counts across tenants.\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const interceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n itemClassName,\n options as InterceptorListOptions,\n interceptorContext,\n )) ??\n (options as InterceptorListOptions | undefined) ??\n {};\n\n let { where, stiScope } = interceptedOptions as SmrtListOptions<ModelType>;\n\n where = this.applyStiReadScope(where, stiScope);\n\n // Resolve _meta_type to qualified name if user provided simple class name (Issue #713)\n where = resolveMetaTypeInWhere(where);\n\n // convertWhereKeys is now sync (issue #663) - no await needed\n const { sql: whereSql, values: whereValues } = buildWhere(\n this.convertWhereKeys(where || {}),\n );\n\n const result = await this.db.query(\n `SELECT COUNT(*) as count FROM ${this.tableName} ${whereSql}`,\n ...whereValues,\n );\n\n return toSafeInteger(result.rows[0].count, 'Collection count');\n }\n\n /**\n * Return database-backed distinct values and row counts for one or more\n * column-backed fields.\n *\n * Each requested field is executed as a bounded `GROUP BY` query. Keeping\n * one query per field uses standard SQL while avoiding a full collection\n * read or model hydration. Scalar facet behavior is covered on SQLite and\n * DuckDB; optional PostgreSQL scalar coverage runs in the `test:postgres`\n * lane when `SMRT_TEST_POSTGRES_URL` is configured.\n * The same `beforeList` interceptors as `list()` and `count()` run first,\n * so tenancy and other read scopes are applied to every facet query.\n *\n * Facets group by the value stored in the column. SMRT does not split,\n * unnest, or otherwise interpret array/string-list fields; consumers that\n * need a facet per array member should maintain a scalar join table or use\n * a consumer-specific query. JSON fields are grouped by their stored JSON\n * value and decoded in the returned `value` when the field metadata allows\n * it. Array/JSON encoding behavior is adapter-specific and is covered by\n * the SQLite/DuckDB tests only; the optional PostgreSQL test covers scalar\n * grouping.\n *\n * @param options.fields One or more fields, optionally with per-field limits\n * @param options.where Filter conditions using the same syntax as `list()`\n * @returns One value/count list per requested field, in request order\n *\n * Limits are always bounded: an explicit per-field limit is clamped to\n * {@link MAX_FACET_LIMIT}, the collection's `maxListLimit` when configured,\n * and the lower of those ceilings. When omitted, the effective limit is the\n * collection's `defaultListLimit` or {@link DEFAULT_FACET_LIMIT}, subject to\n * the same ceilings. This keeps facet queries bounded even when collection\n * list bounds are unset.\n */\n public async facets(\n options: SmrtFacetOptions<ModelType>,\n ): Promise<SmrtFacetResult[]> {\n await this.ensureStorageReady();\n\n if (!options || !Array.isArray(options.fields)) {\n throw new Error('Invalid facets option: fields must be an array.');\n }\n if (options.fields.length === 0) {\n throw new Error('Invalid facets option: at least one field is required.');\n }\n if (options.fields.length > MAX_FACET_FIELDS) {\n throw new Error(\n `Invalid facets option: at most ${MAX_FACET_FIELDS} fields are allowed.`,\n );\n }\n\n const itemClassName = this.getResolvedItemClassName();\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n const interceptorContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const interceptedOptions =\n (await GlobalInterceptors.executeBeforeList(\n itemClassName,\n options as unknown as InterceptorListOptions,\n interceptorContext,\n )) ??\n (options as unknown as InterceptorListOptions) ??\n {};\n\n let { where, stiScope } =\n interceptedOptions as unknown as SmrtFacetOptions<ModelType>;\n const isSTI = ObjectRegistry.getTableStrategy(itemQualifiedName) === 'sti';\n where = this.applyStiReadScope(where, stiScope);\n where = resolveMetaTypeInWhere(where);\n\n const { sql: whereSql, values: whereValues } = buildWhere(\n this.convertWhereKeys(where || {}),\n );\n const fields = this.getFieldsSync();\n const interceptedFields = (\n interceptedOptions as unknown as Partial<SmrtFacetOptions<ModelType>>\n ).fields;\n const effectiveFields = interceptedFields ?? options.fields;\n if (!Array.isArray(effectiveFields) || effectiveFields.length === 0) {\n throw new Error(\n 'Invalid facets option: at least one field is required after interception.',\n );\n }\n if (effectiveFields.length > MAX_FACET_FIELDS) {\n throw new Error(\n `Invalid facets option: at most ${MAX_FACET_FIELDS} fields are allowed after interception.`,\n );\n }\n const requested = effectiveFields.map((entry) =>\n typeof entry === 'string' ? { field: entry } : entry,\n );\n const seen = new Set<string>();\n const results: SmrtFacetResult[] = [];\n\n for (const request of requested) {\n if (!request || typeof request.field !== 'string') {\n throw new Error(\n 'Invalid facet field: expected a string or { field, limit }.',\n );\n }\n const field = request.field;\n if (seen.has(field)) {\n throw new Error(\n `Invalid facet field: '${field}' was requested more than once.`,\n );\n }\n seen.add(field);\n\n // Reuse projection validation so facets cannot expose sensitive,\n // readPermission-gated, relationship-only, transient, or unknown\n // fields. The returned SQL is intentionally not used: aggregation needs\n // the same safe column identifier without hydrating a projection row.\n this.resolveProjectionSelect([field], fields, isSTI);\n\n const columnName = this.toDbColumnName(field);\n const quotedColumn = this.quoteProjectionIdentifier(columnName);\n const quotedField = this.quoteProjectionIdentifier(field);\n const requestedLimit =\n request.limit === undefined\n ? (this._defaultListLimit ?? DEFAULT_FACET_LIMIT)\n : assertQueryBound(request.limit, `facet limit for '${field}'`);\n const collectionLimit = this._maxListLimit ?? MAX_FACET_LIMIT;\n const limit = Math.min(\n requestedLimit ?? DEFAULT_FACET_LIMIT,\n collectionLimit,\n MAX_FACET_LIMIT,\n );\n const sql =\n `SELECT ${quotedColumn} AS ${quotedField}, ` +\n `COUNT(*) AS \"__smrt_facet_count\" FROM ${this.tableName} ` +\n `${whereSql} GROUP BY ${quotedColumn} ` +\n `ORDER BY \"__smrt_facet_count\" DESC, ` +\n `CASE WHEN ${quotedColumn} IS NULL THEN 1 ELSE 0 END ASC, ` +\n `${quotedColumn} ASC ` +\n `LIMIT $${whereValues.length + 1}`;\n const params = [...whereValues, limit];\n const queryResult = await this.db.query(sql, ...params);\n\n results.push({\n field,\n values: queryResult.rows.map((row) => {\n const rawValue = row[field];\n const formatted = formatDataJs({ [columnName]: rawValue }, fields);\n const formattedKey = Object.hasOwn(formatted, field)\n ? field\n : toCamelCase(field);\n const value = Object.hasOwn(formatted, formattedKey)\n ? formatted[formattedKey]\n : rawValue;\n return {\n value,\n count: toSafeInteger(\n row.__smrt_facet_count,\n `Facet count for '${field}'`,\n ),\n };\n }),\n });\n }\n\n return results;\n }\n\n /**\n * Return both the tenant/read-scoped total and a filtered count.\n *\n * This intentionally issues two `COUNT(*)` queries rather than reading\n * rows into application memory. `count()` applies `beforeList` for each\n * query, so the unfiltered total remains tenant-scoped while the filtered\n * count adds the caller's `where` conditions.\n */\n public async counts(\n options: {\n where?: SmrtListWhereClause<ModelType>;\n stiScope?: SmrtStiReadScope;\n } = {},\n ): Promise<SmrtCollectionCounts> {\n const total = await this.count({ stiScope: options.stiScope });\n const filtered = await this.count(options);\n return { total, filtered };\n }\n\n /**\n * Resolve this collection's STI child discriminator — the qualified item\n * class name to use as a `_meta_type` scope — when the collection's item is\n * an STI **child** (shares a table with sibling subtypes), or `null` for STI\n * bases, CTI, and non-STI item classes.\n *\n * Mirrors the automatic `_meta_type` scoping that `list()` / `get()` apply\n * for STI child collections (#386), using the same `ObjectRegistry` source of\n * truth (`getTableStrategy` + `getSTIBase`). Raw-SQL helpers that bypass\n * `list()` — e.g. the tenant global / with-globals lookups in\n * `@happyvertical/smrt-tenancy` (#1600) — call this to scope a shared STI\n * table to the collection's own subtype, so they never surface sibling rows.\n * Deriving the scope here keeps every consumer consistent with `list()`\n * without each one hand-classifying its collection's table strategy.\n *\n * @returns The qualified `_meta_type` for an STI child collection, or `null`\n * when no `_meta_type` scoping applies (STI base / CTI / non-STI).\n */\n public getStiChildMetaType(): string | null {\n const itemQualifiedName = this.getResolvedItemQualifiedName();\n if (ObjectRegistry.getTableStrategy(itemQualifiedName) !== 'sti') {\n return null;\n }\n const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);\n return stiBase && stiBase !== itemQualifiedName ? itemQualifiedName : null;\n }\n\n /**\n * Execute a raw SQL query and hydrate results as collection item instances\n *\n * Provides full SQL power for complex queries (JOINs, CTEs, NOT EXISTS, etc.)\n * while still returning properly hydrated SMRT objects.\n *\n * @param sql - Raw SQL query string (should select from this.tableName)\n * @param params - Query parameters for prepared statement\n * @returns Promise resolving to array of hydrated model instances\n *\n * @example\n * ```typescript\n * // Find meetings without corresponding recaps (NOT EXISTS pattern)\n * const meetings = await meetingCollection.query(`\n * SELECT m.* FROM meetings m\n * WHERE m.start_date < datetime('now')\n * AND NOT EXISTS (\n * SELECT 1 FROM contents c\n * WHERE c.meeting_id = m.id\n * AND c._meta_type = 'MeetingRecap'\n * )\n * ORDER BY m.start_date DESC\n * LIMIT ?\n * `, [10]);\n *\n * // Complex JOIN query\n * const products = await productCollection.query(`\n * SELECT p.* FROM products p\n * INNER JOIN categories c ON p.category_id = c.id\n * WHERE c.name = ? AND p.price > ?\n * ORDER BY p.price ASC\n * `, ['Electronics', 100]);\n * ```\n */\n public async query(\n sql: string,\n params: unknown[] = [],\n options: { allowRawOnTenantScoped?: boolean } = {},\n ): Promise<ModelType[]> {\n await this.ensureStorageReady();\n\n // Execute beforeQuery interceptors (e.g., tenant raw SQL policy)\n const interceptorContext = createInterceptorContext(\n this._itemClass.name,\n 'query',\n this.constructor.name,\n );\n const interceptedQuery = await GlobalInterceptors.executeBeforeQuery(\n this._itemClass.name,\n { sql, params, allowRawOnTenantScoped: options.allowRawOnTenantScoped },\n interceptorContext,\n );\n\n const result = await this.db.query(\n interceptedQuery.sql,\n ...interceptedQuery.params,\n );\n\n // query() is a raw escape hatch documented for SELECTs, but it can run any\n // SQL. A write issued through it bypasses the save()/delete() invalidation\n // hooks, so conservatively bust this table's cache when the statement looks\n // like a mutation (issue #1498). The word-boundary match ignores column\n // names like `updated_at`/`deleted_at`; a false positive (e.g. SELECT ...\n // FOR UPDATE) only costs a cache miss, never a stale read.\n if (\n /\\b(?:insert|update|delete|merge|truncate|replace)\\b/i.test(\n interceptedQuery.sql,\n )\n ) {\n invalidateCollectionCache(resolveDbCacheKey(this.db), this.tableName);\n }\n\n const fields = this.getFieldsSync();\n\n // STI: Check if we need polymorphic hydration.\n // R5-canon: pass the qualified item name so a colliding simple\n // name in another package can't yield the wrong tableStrategy\n // and feed the wrong `isSTI` boolean into `hydrateResultRow`.\n const tableStrategy = ObjectRegistry.getTableStrategy(\n this.getResolvedItemQualifiedName(),\n );\n const isSTI = tableStrategy === 'sti';\n\n const instances = await this.hydrateResultRows(result.rows, fields, isSTI);\n\n // Execute afterQuery interceptors\n return await GlobalInterceptors.executeAfterQuery(\n this._itemClass.name,\n instances,\n interceptorContext,\n );\n }\n\n private async hydrateResultRow(\n row: Record<string, unknown>,\n fields: Record<string, CollectionFieldDefinition>,\n isSTI: boolean,\n polymorphicFields = new Map<\n string,\n Record<string, CollectionFieldDefinition>\n >(),\n ): Promise<ModelType> {\n const hydrationFields = await this.resolveHydrationFields(\n row,\n fields,\n isSTI,\n polymorphicFields,\n );\n const formattedData = this.withHydratedCoreFields(\n formatDataJs(row, hydrationFields),\n );\n\n const metaType = formattedData._meta_type;\n if (isSTI && metaType) {\n const polymorphicInstance = await this.createPolymorphic(\n typeof metaType === 'string' ? metaType : String(metaType),\n formattedData,\n { hydrateOnly: true },\n );\n return polymorphicInstance;\n }\n\n const instanceParams = {\n ai: this.options.ai,\n db: this.db,\n _skipLoad: true,\n _reuseInitializedDb: true,\n _deferRuntimeInitialization: true,\n ...formattedData,\n };\n\n const instance = new this._itemClass(instanceParams);\n // Mark before initialize() so model lifecycle guards can safely snapshot\n // authoritative hydrated values without probing the database. A fresh\n // caller-created `_skipLoad` instance remains unpersisted.\n instance.markAsPersisted();\n await instance.initialize();\n\n if (isSTI) {\n const registeredClass = ObjectRegistry.getClass(this._itemClass.name);\n // Dynamic STI discriminator write — `_meta_type` is a runtime-only field\n // not in the static SmrtObject shape; view as a writable bag.\n (instance as unknown as { _meta_type?: string })._meta_type =\n registeredClass?.qualifiedName || this._itemClass.name;\n }\n\n return instance;\n }\n\n /**\n * Resolve inherited field metadata for an STI row's concrete discriminator.\n * The caller-provided collection fields remain the safe fallback for\n * non-polymorphic, malformed, or not-yet-registered rows.\n */\n private async resolveHydrationFields(\n row: Record<string, unknown>,\n fallbackFields: Record<string, CollectionFieldDefinition>,\n isSTI: boolean,\n polymorphicFields: Map<string, Record<string, CollectionFieldDefinition>>,\n ): Promise<Record<string, CollectionFieldDefinition>> {\n if (!isSTI) return fallbackFields;\n\n const rawMetaType = row._meta_type ?? row._metaType;\n if (rawMetaType === null || rawMetaType === undefined) {\n return fallbackFields;\n }\n\n const metaType = String(rawMetaType);\n const cached = polymorphicFields.get(metaType);\n if (cached) return cached;\n if (!ObjectRegistry.getClass(metaType)) {\n await ObjectRegistry.ensureManifestLoaded(metaType);\n }\n\n const registeredFields = await ObjectRegistry.getAllFields(metaType);\n if (registeredFields.size === 0) return fallbackFields;\n\n const concreteFields: Record<string, CollectionFieldDefinition> = {};\n for (const [fieldName, field] of registeredFields) {\n concreteFields[fieldName] = { type: field.type };\n }\n polymorphicFields.set(metaType, concreteFields);\n return concreteFields;\n }\n\n /**\n * Hydrate rows in result order.\n *\n * A model's initialize() hook may query through the collection database.\n * PostgreSQL transaction adapters bind every query to one pg client, which\n * cannot execute overlapping client.query() calls once pg 9 removes its\n * deprecated per-client queue. Serial hydration therefore protects every\n * model hook without relying on driver queuing and preserves row order.\n */\n private async hydrateResultRows(\n rows: Record<string, unknown>[],\n fields: Record<string, CollectionFieldDefinition>,\n isSTI: boolean,\n ): Promise<ModelType[]> {\n const instances: ModelType[] = [];\n const polymorphicFields = new Map<\n string,\n Record<string, CollectionFieldDefinition>\n >();\n for (const row of rows) {\n instances.push(\n await this.hydrateResultRow(row, fields, isSTI, polymorphicFields),\n );\n }\n return instances;\n }\n\n /**\n * Resolve the `_smrt_contexts.owner_id` key for collection-level memory\n * (#2365).\n *\n * `_smrt_contexts` has no tenant column, so before this hook a tenant-scoped\n * collection's learned memory (`remember()`/`recall()`) was filed under one\n * shared `__collection__` key and leaked across tenants. For a tenant-scoped\n * item class under an active tenant context the key becomes\n * `__collection__:<tenantId>`; everything else (tenancy disabled, no active\n * tenant, system context, non-tenant-scoped class) keeps the shared\n * `__collection__` key. Tenant-keyed memory is strictly isolated — a tenant\n * recall does NOT fall back to the shared key, so memory learned outside a\n * tenant context is invisible inside one (and vice versa).\n *\n * Tenant resolution uses the same core-side trust anchors as the generated\n * REST read scope (#1782): `resolveDispatchTenantScope()` for the active\n * tenant and the triple tenant-scoped-class check covering `@smrt()` config,\n * manifest config, and the tenancy-filled `@TenantScoped()` resolver.\n */\n private resolveCollectionMemoryOwnerId(): string {\n const itemClassName = this.getResolvedItemClassName();\n const tenantScoped =\n ObjectRegistry.isTenantScoped(itemClassName) ||\n !!ObjectRegistry.getConfig(itemClassName)?.tenantScoped ||\n isTenantScopedClassResolved(itemClassName);\n if (!tenantScoped) {\n return COLLECTION_MEMORY_OWNER_ID;\n }\n const scope = resolveDispatchTenantScope();\n if (!scope.enforced || !scope.tenantId) {\n return COLLECTION_MEMORY_OWNER_ID;\n }\n return `${COLLECTION_MEMORY_OWNER_ID}:${scope.tenantId}`;\n }\n\n /**\n * Remember collection-level context\n *\n * Stores context applicable to all instances of this collection type.\n * Use for patterns that apply to the entire collection (e.g., default parsing strategies).\n *\n * Under an active tenant context on a tenant-scoped class, memory is keyed\n * per tenant and never shared across tenants (#2365) — see\n * {@link resolveCollectionMemoryOwnerId}.\n *\n * `expiresAt` is stored on the `_smrt_contexts` row as caller-managed metadata.\n * {@link recall} and {@link recallAll} do **not** filter on it, so an expired\n * entry is still returned; filter or delete expired entries yourself.\n *\n * @param options - Context options\n * @returns Promise that resolves when context is stored\n * @example\n * ```typescript\n * // Remember a default parsing strategy for all documents\n * await documentCollection.remember({\n * scope: 'parser/default',\n * key: 'selector',\n * value: { pattern: '.content article' },\n * confidence: 0.8\n * });\n *\n * // Update an existing context entry by specifying id\n * await documentCollection.remember({\n * id: 'existing-context-id',\n * scope: 'parser/default',\n * key: 'selector',\n * value: { pattern: '.content main article' },\n * confidence: 0.85\n * });\n * ```\n */\n public async remember(options: {\n id?: string;\n scope: string;\n key: string;\n value: unknown;\n metadata?: unknown;\n confidence?: number;\n version?: number;\n expiresAt?: Date;\n }): Promise<void> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n const id = options.id || crypto.randomUUID();\n const now = new Date();\n\n // Use upsert() for database-agnostic INSERT OR REPLACE\n // SQLite: INSERT OR REPLACE\n // Postgres/DuckDB: INSERT ... ON CONFLICT ... DO UPDATE\n await this.systemDb.upsert(\n '_smrt_contexts',\n // UNIQUE constraint: (owner_class, owner_id, scope, key, version)\n ['owner_class', 'owner_id', 'scope', 'key', 'version'],\n {\n id,\n owner_class: this._itemClass.name,\n owner_id: this.resolveCollectionMemoryOwnerId(),\n scope: options.scope,\n key: options.key,\n value: JSON.stringify(options.value),\n metadata: options.metadata ? JSON.stringify(options.metadata) : null,\n version: options.version ?? 1,\n confidence: options.confidence ?? 1.0,\n success_count: 0,\n failure_count: 0,\n created_at: now,\n updated_at: now,\n last_used_at: now,\n expires_at: options.expiresAt ?? null,\n },\n );\n }\n\n /**\n * Recall collection-level context\n *\n * Retrieves context that applies to all instances of this collection.\n *\n * @param options - Recall options\n * @returns Promise resolving to the context value or null if not found\n * @example\n * ```typescript\n * // Recall default parsing strategy\n * const strategy = await documentCollection.recall({\n * scope: 'parser/default',\n * key: 'selector',\n * minConfidence: 0.5\n * });\n * ```\n */\n public async recall(options: {\n scope: string;\n key: string;\n includeAncestors?: boolean;\n minConfidence?: number;\n }): Promise<unknown> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n const ownerId = this.resolveCollectionMemoryOwnerId();\n\n // Use single() with template literals for custom SQL query.\n // The query projects `value` (a JSON string) and `confidence`.\n let result: Record<string, unknown> | null;\n if (options.minConfidence !== undefined) {\n result = await this.systemDb.single`\n SELECT value, confidence\n FROM _smrt_contexts\n WHERE owner_class = ${this._itemClass.name}\n AND owner_id = ${ownerId}\n AND scope = ${options.scope}\n AND key = ${options.key}\n AND confidence >= ${options.minConfidence}\n ORDER BY confidence DESC, version DESC\n LIMIT 1\n `;\n } else {\n result = await this.systemDb.single`\n SELECT value, confidence\n FROM _smrt_contexts\n WHERE owner_class = ${this._itemClass.name}\n AND owner_id = ${ownerId}\n AND scope = ${options.scope}\n AND key = ${options.key}\n ORDER BY confidence DESC, version DESC\n LIMIT 1\n `;\n }\n\n if (result) {\n // Guard against a corrupted _smrt_contexts row: a single malformed value\n // must not throw an uncaught SyntaxError out of recall(). Treat it as a\n // miss and continue to the ancestor fallback (#1378). `value` is the\n // queried JSON text column; the try/catch covers a non-string/corrupt row.\n try {\n return JSON.parse(result.value as string);\n } catch (error) {\n logger.warn('Skipping corrupted _smrt_contexts value in recall()', {\n ownerClass: this._itemClass.name,\n scope: options.scope,\n key: options.key,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Hierarchical fallback to parent scopes\n if (options.includeAncestors) {\n const scopeParts = options.scope.split('/');\n while (scopeParts.length > 0) {\n scopeParts.pop();\n const parentScope = scopeParts.join('/') || 'global';\n\n const parentResult = await this.recall({\n ...options,\n scope: parentScope,\n includeAncestors: false,\n });\n\n if (parentResult) return parentResult;\n }\n }\n\n return null;\n }\n\n /**\n * Recall all collection-level context in a scope\n *\n * Returns a Map of key -> value for all collection contexts matching the criteria.\n *\n * @param options - Recall options\n * @returns Promise resolving to Map of key -> value pairs\n * @example\n * ```typescript\n * // Get all default strategies\n * const strategies = await documentCollection.recallAll({\n * scope: 'parser/default',\n * minConfidence: 0.5\n * });\n * ```\n */\n public async recallAll(\n options: {\n scope?: string;\n includeDescendants?: boolean;\n minConfidence?: number;\n } = {},\n ): Promise<Map<string, unknown>> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n const results = new Map<string, unknown>();\n\n let query = `\n SELECT key, value, confidence\n FROM _smrt_contexts\n WHERE owner_class = ? AND owner_id = ?\n `;\n const params: unknown[] = [\n this._itemClass.name,\n this.resolveCollectionMemoryOwnerId(),\n ];\n\n if (options.scope) {\n if (options.includeDescendants) {\n query += ` AND (scope = ? OR scope LIKE ?)`;\n params.push(options.scope, `${options.scope}/%`);\n } else {\n query += ` AND scope = ?`;\n params.push(options.scope);\n }\n }\n\n if (options.minConfidence !== undefined) {\n query += ` AND confidence >= ?`;\n params.push(options.minConfidence);\n }\n\n query += ` ORDER BY confidence DESC`;\n\n const { rows } = await this.systemDb.query(query, ...params);\n\n for (const row of rows) {\n // Skip a corrupted row rather than aborting the whole recallAll() (#1378).\n try {\n results.set(row.key, JSON.parse(row.value));\n } catch (error) {\n logger.warn('Skipping corrupted _smrt_contexts value in recallAll()', {\n ownerClass: this._itemClass.name,\n scope: options.scope,\n key: row.key,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return results;\n }\n\n /**\n * Forget collection-level context\n *\n * Deletes collection context by scope and key.\n *\n * @param options - Context identification\n * @returns Promise that resolves when context is deleted\n * @example\n * ```typescript\n * // Remove a default strategy\n * await documentCollection.forget({\n * scope: 'parser/default',\n * key: 'selector'\n * });\n * ```\n */\n public async forget(options: { scope: string; key: string }): Promise<void> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n await this.systemDb.query(\n `DELETE FROM _smrt_contexts\n WHERE owner_class = ? AND owner_id = ? AND scope = ? AND key = ?`,\n this._itemClass.name,\n this.resolveCollectionMemoryOwnerId(),\n options.scope,\n options.key,\n );\n }\n\n /**\n * Forget all collection-level context in a scope\n *\n * Deletes all collection contexts matching the scope pattern.\n *\n * @param options - Scope options\n * @returns Promise resolving to number of contexts deleted\n * @example\n * ```typescript\n * // Clear all default strategies\n * const count = await documentCollection.forgetScope({\n * scope: 'parser/default',\n * includeDescendants: true\n * });\n * ```\n */\n public async forgetScope(options: {\n scope: string;\n includeDescendants?: boolean;\n }): Promise<number> {\n if (!this.systemDb) {\n throw new Error('Database not initialized. Call initialize() first.');\n }\n\n let query = `\n DELETE FROM _smrt_contexts\n WHERE owner_class = ? AND owner_id = ?\n `;\n const params: unknown[] = [\n this._itemClass.name,\n this.resolveCollectionMemoryOwnerId(),\n ];\n\n if (options.includeDescendants) {\n query += ` AND (scope = ? OR scope LIKE ?)`;\n params.push(options.scope, `${options.scope}/%`);\n } else {\n query += ` AND scope = ?`;\n params.push(options.scope);\n }\n\n const { rowCount } = await this.systemDb.query(query, ...params);\n return rowCount || 0;\n }\n\n // ============================================================================\n // Semantic Search Methods\n // ============================================================================\n\n /**\n * Semantic search by text query\n *\n * Generates an embedding for the query text and finds similar objects\n * based on cosine similarity of stored embeddings.\n *\n * Under an active tenant context on a tenant-scoped class, candidates are\n * restricted to the tenant's rows BEFORE top-K ranking (#2365), so results\n * are never starved by — and similarity ranks never leak — other tenants'\n * content.\n *\n * @param query - Text to search for\n * @param options - Search options\n * @param options.field - Specific field to search (defaults to first embedding\n * field). Any configured embedding field, or the configured\n * `combinedField.name`, is accepted.\n * @param options.limit - Maximum results to return (default: 10)\n * @param options.minSimilarity - Minimum similarity threshold 0-1 (default: 0)\n * @param options.where - Additional WHERE filters to apply\n * @returns Promise resolving to array of objects with _similarity score\n *\n * @example\n * ```typescript\n * const results = await articles.semanticSearch('machine learning trends', {\n * limit: 10,\n * minSimilarity: 0.7\n * });\n *\n * for (const article of results) {\n * console.log(`${article.title} (similarity: ${article._similarity})`);\n * }\n *\n * // Search the combined vector built from `combinedField.template`\n * const combined = await articles.semanticSearch('machine learning trends', {\n * field: 'content'\n * });\n * ```\n */\n public async semanticSearch(\n query: string,\n options: {\n field?: string;\n limit?: number;\n minSimilarity?: number;\n where?: SmrtWhereClause<ModelType>;\n } = {},\n ): Promise<Array<ModelType & { _similarity: number }>> {\n const { field, limit = 10, minSimilarity = 0, where } = options;\n\n // Get embedding config\n const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(\n this._itemClass.name,\n );\n\n if (!embeddingConfig) {\n throw new Error(\n `No embedding configuration found for ${this._itemClass.name}. ` +\n `Add embeddings config to @smrt() decorator.`,\n );\n }\n\n // Determine which field to search.\n // `generateEmbeddings()` also stores a vector under the configured\n // `combinedField.name`, and `findSimilar`/`findSimilarToEmbedding` happily\n // search it, so it belongs in the searchable set here too (#2281).\n const searchField = field || embeddingConfig.fields[0];\n const searchableFields = getSearchableEmbeddingFields(embeddingConfig);\n if (!searchableFields.includes(searchField)) {\n throw new Error(\n `Field '${searchField}' is not configured for embeddings on ${this._itemClass.name}. ` +\n `Available fields: ${searchableFields.join(', ')}`,\n );\n }\n\n // Create embedding provider from the resolved class/project config so\n // query embeddings use the same model as stored object embeddings.\n const provider = new EmbeddingProvider(\n {\n dimensions: embeddingConfig.dimensions,\n provider: embeddingConfig.provider,\n localModel: embeddingConfig.localModel,\n aiModel: embeddingConfig.aiModel,\n fallbackToAI: embeddingConfig.fallbackToAI,\n },\n this.ai,\n );\n\n // Generate embedding for query\n const [queryEmbedding] = await provider.embed(query);\n\n // Find similar embeddings\n return this.findSimilarToEmbedding(queryEmbedding, {\n field: searchField,\n limit,\n minSimilarity,\n where,\n });\n }\n\n /**\n * Find objects similar to a given object\n *\n * Uses stored embeddings to find objects most similar to the provided object.\n *\n * @param object - Object to find similar items for (or object ID)\n * @param options - Search options\n * @param options.field - Specific field to compare (defaults to first embedding field)\n * @param options.limit - Maximum results to return (default: 5)\n * @param options.excludeSelf - Whether to exclude the source object (default: true)\n * @returns Promise resolving to array of similar objects with _similarity score\n *\n * @example\n * ```typescript\n * const article = await articles.get('some-article-id');\n * const similar = await articles.findSimilar(article, {\n * limit: 5,\n * excludeSelf: true\n * });\n * ```\n */\n public async findSimilar(\n object: ModelType | string,\n options: {\n field?: string;\n limit?: number;\n excludeSelf?: boolean;\n } = {},\n ): Promise<Array<ModelType & { _similarity: number }>> {\n const { field, limit = 5, excludeSelf = true } = options;\n\n // Get the object if ID was provided\n let sourceObject: ModelType;\n if (typeof object === 'string') {\n const found = await this.get(object);\n if (!found) {\n throw new Error(`Object not found: ${object}`);\n }\n sourceObject = found;\n } else {\n sourceObject = object;\n }\n\n // Get embedding config\n const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(\n this._itemClass.name,\n );\n\n if (!embeddingConfig) {\n throw new Error(\n `No embedding configuration found for ${this._itemClass.name}.`,\n );\n }\n\n // Determine which field to use\n const searchField = field || embeddingConfig.fields[0];\n\n // Get the source object's embedding using consistent model name from EmbeddingProvider\n const provider = new EmbeddingProvider(\n {\n dimensions: embeddingConfig.dimensions,\n provider: embeddingConfig.provider,\n localModel: embeddingConfig.localModel,\n aiModel: embeddingConfig.aiModel,\n fallbackToAI: embeddingConfig.fallbackToAI,\n },\n this.ai,\n );\n const model = provider.getModelName();\n\n const storedEmbedding = await EmbeddingStorage.get(\n this.systemDb,\n this._itemClass.name,\n sourceObject.id as string,\n searchField,\n model,\n );\n\n if (!storedEmbedding) {\n throw new Error(\n `No embedding found for object ${sourceObject.id} field '${searchField}'. ` +\n `Generate embeddings first with object.generateEmbeddings().`,\n );\n }\n\n // Find similar using the embedding\n const where = excludeSelf ? { 'id !=': sourceObject.id } : undefined;\n\n return this.findSimilarToEmbedding(storedEmbedding.embedding, {\n field: searchField,\n limit,\n minSimilarity: 0,\n where,\n });\n }\n\n /**\n * Find objects similar to a raw embedding vector\n *\n * Low-level method for finding objects by embedding similarity.\n *\n * @param embedding - Embedding vector to compare against\n * @param options - Search options\n * @param options.field - Field name to search (defaults to first embedding field)\n * @param options.limit - Maximum results to return (default: 10)\n * @param options.minSimilarity - Minimum similarity threshold 0-1 (default: 0)\n * @param options.where - Additional WHERE filters to apply\n * @returns Promise resolving to array of objects with _similarity score\n *\n * @example\n * ```typescript\n * // Using a pre-computed embedding\n * const results = await articles.findSimilarToEmbedding(myEmbedding, {\n * limit: 10,\n * minSimilarity: 0.5\n * });\n * ```\n */\n public async findSimilarToEmbedding(\n embedding: number[],\n options: {\n field?: string;\n limit?: number;\n minSimilarity?: number;\n where?: SmrtWhereClause<ModelType>;\n } = {},\n ): Promise<Array<ModelType & { _similarity: number }>> {\n const { field, limit = 10, minSimilarity = 0, where } = options;\n\n // Get embedding config\n const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(\n this._itemClass.name,\n );\n\n if (!embeddingConfig) {\n throw new Error(\n `No embedding configuration found for ${this._itemClass.name}.`,\n );\n }\n\n // Determine which field to search\n const searchField = field || embeddingConfig.fields[0];\n\n // Get model name using EmbeddingProvider for consistency\n const provider = new EmbeddingProvider(\n {\n dimensions: embeddingConfig.dimensions,\n provider: embeddingConfig.provider,\n localModel: embeddingConfig.localModel,\n aiModel: embeddingConfig.aiModel,\n fallbackToAI: embeddingConfig.fallbackToAI,\n },\n this.ai,\n );\n const model = provider.getModelName();\n\n // Resolve storage strategy\n const projectConfig = ObjectRegistry.getProjectEmbeddingConfig();\n const storage = projectConfig?.storage || 'json';\n const vector = storage === 'native' ? this.systemDb.vector : undefined;\n\n // Tenant pre-filter (#2365): _smrt_embeddings has no tenant column, so\n // top-K used to rank across ALL tenants and only the final list() below\n // dropped foreign rows — starving results and soft-leaking which tenants\n // have similar content. Resolve the tenant read predicate through the\n // SAME beforeList interceptor pipeline that guards collection reads (an\n // empty where comes back either untouched — tenancy off, class not\n // scoped, bypass/system context — or with exactly the injected tenant\n // predicate, or beforeList throws for `mode: 'required'` without\n // context), then restrict the candidate id set BEFORE ranking.\n const itemClassName = this.getResolvedItemClassName();\n const tenantPrefilterContext = createInterceptorContext(\n itemClassName,\n 'list',\n this.constructor.name,\n );\n const tenantPrefilter = await GlobalInterceptors.executeBeforeList(\n itemClassName,\n { where: {} },\n tenantPrefilterContext,\n );\n let candidateObjectIds: string[] | undefined;\n if (\n tenantPrefilter.where &&\n Object.keys(tenantPrefilter.where).length > 0\n ) {\n const candidateWhere = this.convertWhereKeys(tenantPrefilter.where);\n const { sql: candidateWhereSql, values: candidateValues } =\n buildWhere(candidateWhere);\n // Deliberately unbounded: _smrt_embeddings has no tenant column, so the\n // only exact way to scope ranking is the full id set of the tenant's\n // rows. Capping it here would silently drop rows from the ranking —\n // wrong results, not a perf tweak. The native path chunks the ids\n // against bind-variable limits (see searchSimilar); the id-only\n // projection keeps the row cost minimal. If this becomes a hot spot for\n // very large tenants, the durable fix is a tenant column on\n // _smrt_embeddings, not a cap.\n const { rows: candidateRows } = await this.db.query(\n `SELECT id FROM ${this.tableName} ${candidateWhereSql}`,\n ...candidateValues,\n );\n candidateObjectIds = candidateRows.map((row: Record<string, unknown>) =>\n String(row.id),\n );\n if (candidateObjectIds.length === 0) {\n return [];\n }\n }\n\n // Use unified search method (delegates to native or in-memory)\n const scored = await EmbeddingStorage.searchSimilar(\n this.systemDb,\n this._itemClass.name,\n embedding,\n {\n field: searchField,\n model,\n limit,\n minSimilarity,\n objectIds: candidateObjectIds,\n },\n vector,\n );\n\n if (scored.length === 0) {\n return [];\n }\n\n // Load the objects\n const objectIds = scored.map((s) => s.objectId);\n const similarityMap = new Map(\n scored.map((s) => [s.objectId, s.similarity]),\n );\n\n // Build where clause with additional filters\n const whereClause = where\n ? { 'id in': objectIds, ...where }\n : { 'id in': objectIds };\n\n const objects = await this.list({\n where: whereClause as SmrtWhereClause<ModelType>,\n });\n\n // Add similarity scores to objects and sort by similarity.\n // We directly assign _similarity to avoid losing class properties when\n // spreading; view as a writable bag for the computed transient field.\n const results = objects.map((obj) => {\n (obj as unknown as { _similarity: number })._similarity =\n similarityMap.get(obj.id as string) || 0;\n return obj as ModelType & { _similarity: number };\n });\n\n results.sort((a, b) => b._similarity - a._similarity);\n\n return results;\n }\n\n /**\n * Generate missing embeddings for all objects in the collection\n *\n * Batch generates embeddings for objects that don't have them yet\n * or have stale embeddings.\n *\n * @param options - Generation options\n * @param options.batchSize - Number of objects to process at once (default: 50)\n * @param options.onProgress - Progress callback\n * @returns Promise resolving to generation statistics\n *\n * @example\n * ```typescript\n * const stats = await articles.generateMissingEmbeddings({\n * batchSize: 100,\n * onProgress: ({ completed, total }) => {\n * console.log(`Progress: ${completed}/${total}`);\n * }\n * });\n *\n * console.log(`Generated: ${stats.generated}, Skipped: ${stats.skipped}`);\n * ```\n */\n public async generateMissingEmbeddings(\n options: {\n batchSize?: number;\n onProgress?: (progress: { completed: number; total: number }) => void;\n } = {},\n ): Promise<{ generated: number; skipped: number }> {\n const { batchSize = 50, onProgress } = options;\n\n // Get embedding config\n const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(\n this._itemClass.name,\n );\n\n if (!embeddingConfig) {\n throw new Error(\n `No embedding configuration found for ${this._itemClass.name}.`,\n );\n }\n\n // Count total objects\n const total = await this.count({});\n let completed = 0;\n let generated = 0;\n let skipped = 0;\n\n // Process in batches\n let offset = 0;\n while (offset < total) {\n const batch = await this.list({ limit: batchSize, offset });\n\n for (const obj of batch) {\n try {\n // Check if embeddings are stale. `hasStaleEmbeddings` /\n // `generateEmbeddings` are runtime SmrtObject embedding methods not in\n // the static shape; view as such and call on the receiver (keeps\n // `this` bound).\n const embeddable = obj as unknown as {\n hasStaleEmbeddings(): Promise<boolean>;\n generateEmbeddings(): Promise<unknown>;\n };\n const hasStale = await embeddable.hasStaleEmbeddings();\n if (hasStale) {\n await embeddable.generateEmbeddings();\n generated++;\n } else {\n skipped++;\n }\n } catch (error) {\n logger.warn(`Failed to generate embeddings for ${obj.id}`, {\n error: error instanceof Error ? error.message : error,\n });\n skipped++;\n }\n\n completed++;\n if (onProgress) {\n onProgress({ completed, total });\n }\n }\n\n offset += batchSize;\n }\n\n return { generated, skipped };\n }\n}\n"],"mappings":"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"}