@open-mercato/shared 0.7.1-develop.7106.1.53fe9dd1b1 → 0.7.1-develop.7111.1.c0d5d7286f

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.
@@ -583,6 +583,11 @@ class BasicQueryEngine {
583
583
  for (const f of cfFilters) {
584
584
  if (typeof f.field === "string" && f.field.startsWith("cf:")) cfKeys.add(f.field.slice(3));
585
585
  }
586
+ if (!isCountProjection) {
587
+ for (const s of opts.sort || []) {
588
+ if (typeof s.field === "string" && s.field.startsWith("cf:")) cfKeys.add(s.field.slice(3));
589
+ }
590
+ }
586
591
  if (projection === "full" && opts.includeCustomFields === true) {
587
592
  if (entityIdToSource.size > 0) {
588
593
  const entityIdList = Array.from(entityIdToSource.keys());
@@ -845,7 +850,9 @@ class BasicQueryEngine {
845
850
  cfSelectedAliases.push(alias);
846
851
  }
847
852
  }
848
- if (!requiresPlaintextSort) q = q.orderBy(alias, s.dir ?? "asc");
853
+ if (!requiresPlaintextSort && cfSelectedAliases.includes(alias)) {
854
+ q = q.orderBy(alias, s.dir ?? "asc");
855
+ }
849
856
  } else {
850
857
  if (!requiresPlaintextSort) q = q.orderBy(qualify(s.field), s.dir ?? "asc");
851
858
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/query/engine.ts"],
4
- "sourcesContent": ["import type { QueryEngine, QueryOptions, QueryResult, QueryResultMeta, EncryptedSortRowCapWarning, ListCountCapWarning, QueryCustomFieldSource, QueryExtensionsConfig, Sort } from './types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql, type RawBuilder } from 'kysely'\nimport {\n applyJoinFilters,\n normalizeFilters,\n partitionFilters,\n resolveJoins,\n type BaseFilter,\n type NormalizedFilter,\n type ResolvedJoin,\n} from './join-utils'\nimport { resolveSearchConfig } from '../search/config'\nimport {\n createSearchTokenAvailability,\n isSearchFilterOp,\n type SearchTokenAvailability,\n type SearchTokenProbeDb,\n type SearchTokenProbeQueryBuilder,\n} from '../search/availability'\nimport { tokenizeText } from '../search/tokenize'\nimport { fieldNameCandidates } from './encrypted-sort'\nimport { isTenantDataEncryptionEnabled } from '../encryption/toggles'\nimport { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from './query-extension-runner'\nimport {\n buildCustomFieldDefinitionIndexFromRows,\n resolveCfDefIndexOrgCandidates,\n type CustomFieldDefinitionRow,\n type ResolvedCustomFieldDefinitions,\n} from '../crud/custom-field-definition-index'\nimport { warnOnCiphertextLikeFallback } from './ciphertext-search-warning'\nimport { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from './encrypted-sort'\nimport { resolveListCountCap } from './count-cap'\nimport { mapWithConcurrency } from './bounded-decrypt'\nimport { parseNumberWithDefault } from '../number'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'query' })\n\nconst DECRYPT_CONCURRENCY = 8\n\ntype AnyDb = Kysely<any>\ntype AnyBuilder = any\n\nconst entityTableCache = new Map<string, string>()\n\ntype EncryptionResolver = () => {\n decryptEntityPayload?: (entityId: EntityId, payload: Record<string, unknown>, tenantId?: string | null, organizationId?: string | null) => Promise<Record<string, unknown>>\n getEncryptedFieldNames?: (entityId: EntityId, tenantId?: string | null, organizationId?: string | null, options?: { ignoreRuntimeHealth?: boolean }) => Promise<readonly string[]>\n isEnabled?: () => boolean\n} | null\n\n/**\n * Membership across name shapes: encryption maps may declare a field as `displayName` or\n * `display_name` (TenantDataEncryptionService resolves both), while query filters carry real\n * column names. Both sides expand through `fieldNameCandidates` at set-build and lookup time.\n */\nexport function isEncryptedLikeField(encrypted: ReadonlySet<string>, field: string): boolean {\n return fieldNameCandidates(field).some((candidate) => encrypted.has(candidate))\n}\n\n// The all-orgs union behind `getEncryptedFieldNames(..., organizationId: null)` is an UNCACHED\n// `encryption_maps` read. Encryption maps change on deploys, not per request, so a short TTL\n// removes the per-search round-trip without meaningfully delaying a map rollout.\nconst ENCRYPTED_LIKE_FIELDS_TTL_MS = 60_000\nconst ENCRYPTED_LIKE_FIELDS_CACHE_CAP = 500\nconst encryptedLikeFieldsCache = new Map<string, { at: number; fields: Set<string> }>()\n\nexport async function resolveEncryptedLikeFieldSet(\n read: () => Promise<readonly string[]>,\n entity: string,\n tenantId: string | null,\n): Promise<Set<string>> {\n const key = `${entity}|${tenantId ?? ''}`\n const hit = encryptedLikeFieldsCache.get(key)\n if (hit && Date.now() - hit.at < ENCRYPTED_LIKE_FIELDS_TTL_MS) return hit.fields\n const names = await read()\n const fields = new Set<string>()\n for (const name of names ?? []) {\n for (const candidate of fieldNameCandidates(String(name))) fields.add(candidate)\n }\n if (encryptedLikeFieldsCache.size >= ENCRYPTED_LIKE_FIELDS_CACHE_CAP) encryptedLikeFieldsCache.clear()\n encryptedLikeFieldsCache.set(key, { at: Date.now(), fields })\n return fields\n}\n\n/** Test-only: the TTL memo would otherwise leak state across specs. */\nexport function clearEncryptedLikeFieldsCache(): void {\n encryptedLikeFieldsCache.clear()\n}\n\n// Module-scoped on purpose: `createRequestContainer` builds a fresh `BasicQueryEngine`\n// per request, so an instance field alone re-pays the `information_schema` probe on\n// every request (#5605). Schema shape (does a table have this column?) is not\n// per-request state \u2014 unlike `tenantEncryptionService` \u2014 so sharing the answer across\n// requests is safe. One map per module instance rather than a true process singleton:\n// standalone builds can duplicate this package, which for a memo is harmless (two\n// caches, both correct), so nothing may be built on top of singleton semantics here.\n//\n// Bounded and TTL'd for two reasons. `columnExists` is reached with caller-supplied\n// field names via `resolveBaseColumn` (sort fields and base filter keys arrive raw from\n// the HTTP layer), so an unbounded map would grow monotonically on request input. And a\n// cached `false` is consumed where the tenant/organization/soft-delete predicates are\n// applied, so a schema change that adds one of those columns must not stay invisible\n// until the process restarts \u2014 a migration applied against a running `yarn dev` or\n// not-yet-recycled pods converges within the TTL instead. The TTL still removes\n// essentially all of the traffic: a hot column is probed twelve times an hour rather\n// than tens of thousands. Set OM_QUERY_COLUMN_EXISTS_CACHE_MS=0 to disable and probe\n// per request again; OM_QUERY_COLUMN_EXISTS_CACHE_MAX_ENTRIES tunes the bound.\nconst COLUMN_EXISTS_CACHE_DEFAULT_TTL_MS = 300_000\nconst COLUMN_EXISTS_CACHE_DEFAULT_MAX_ENTRIES = 10_000\nconst columnExistsCache = new Map<string, { value: boolean; expiresAt: number }>()\n\nfunction resolveColumnExistsCacheTtlMs(): number {\n return parseNumberWithDefault(process.env.OM_QUERY_COLUMN_EXISTS_CACHE_MS, COLUMN_EXISTS_CACHE_DEFAULT_TTL_MS, { integer: true, min: 0 })\n}\n\nfunction resolveColumnExistsCacheMaxEntries(): number {\n return parseNumberWithDefault(process.env.OM_QUERY_COLUMN_EXISTS_CACHE_MAX_ENTRIES, COLUMN_EXISTS_CACHE_DEFAULT_MAX_ENTRIES, { integer: true, min: 1 })\n}\n\nfunction storeColumnExists(key: string, value: boolean, ttlMs: number): void {\n const maxEntries = resolveColumnExistsCacheMaxEntries()\n if (columnExistsCache.size >= maxEntries) {\n const now = Date.now()\n for (const [entryKey, entry] of columnExistsCache) {\n if (entry.expiresAt <= now) columnExistsCache.delete(entryKey)\n }\n if (columnExistsCache.size >= maxEntries) columnExistsCache.clear()\n }\n columnExistsCache.set(key, { value, expiresAt: Date.now() + ttlMs })\n}\n\n/** Test-only: the module-scoped memo would otherwise leak state across specs. */\nexport function clearColumnExistsCache(): void {\n columnExistsCache.clear()\n}\n\n/** Test-only: entry count of the column-existence memo, for the cap regression test. */\nexport function columnExistsCacheSize(): number {\n return columnExistsCache.size\n}\n\ntype ResolvedCustomFieldSource = {\n entityId: EntityId\n alias: string\n table: string\n recordIdExpr: RawBuilder<string>\n /**\n * The base\u2192source join edge for joined sources (absent on the base source).\n * The count projection uses it to correlate cf-value EXISTS subqueries\n * without attaching the source join to the outer query.\n */\n hop?: { fromField: string; toField: string; recordIdColumn: string; type: 'left' | 'inner' }\n}\n\ntype ResultRow = Record<string, unknown>\n\n/**\n * Canonical `module:entity` entity-id shape (both segments snake_case,\n * starting with a lowercase letter). Used to validate caller-supplied entity\n * ids at security-sensitive boundaries before they reach table resolution.\n */\nexport const ENTITY_ID_PATTERN = /^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*$/\n\nexport const isValidEntityIdShape = (value: string): boolean => ENTITY_ID_PATTERN.test(value)\n\nconst pluralizeBaseName = (name: string): string => {\n if (!name) return name\n if (name.endsWith('s')) return name\n if (name.endsWith('y')) return `${name.slice(0, -1)}ies`\n return `${name}s`\n}\n\n/**\n * Accepts a module-declared `EntityExtension.table` only when it is a bare SQL\n * identifier. The value is interpolated into a join clause, so anything else is\n * ignored in favour of the derived table name.\n */\nconst PLAIN_TABLE_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/\n\nconst isPlainTableIdentifier = (value: unknown): value is string =>\n typeof value === 'string' && PLAIN_TABLE_IDENTIFIER_PATTERN.test(value)\n\nconst toPascalCase = (value: string): string => {\n return value\n .split(/[_\\s]+/)\n .filter(Boolean)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('')\n}\n\nconst candidateClassNames = (rawName: string): string[] => {\n const base = toPascalCase(rawName)\n const candidates = new Set<string>()\n if (base) candidates.add(base)\n if (base && !base.endsWith('Entity')) candidates.add(`${base}Entity`)\n return Array.from(candidates)\n}\n\n/**\n * Resolve an entity id to a table name strictly via registered MikroORM metadata.\n *\n * Unlike {@link resolveEntityTableName}, this never falls back to a pluralized\n * guess: it returns `null` when no registered entity matches. Use it for\n * security-sensitive call sites (e.g. the reindexer) that must refuse to read\n * arbitrary, attacker-chosen tables that happen to exist in the schema.\n */\nexport function resolveRegisteredEntityTableName(\n em: EntityManager | undefined,\n entity: EntityId,\n): string | null {\n const parts = String(entity || '').split(':')\n const rawName = (parts[1] && parts[1].trim().length > 0) ? parts[1] : (parts[0] || '').trim()\n const metadata = (em as any)?.getMetadata?.()\n\n if (!metadata || !rawName) return null\n\n const candidates = candidateClassNames(rawName)\n for (const candidate of candidates) {\n try {\n const meta = metadata.find?.(candidate)\n if (meta?.tableName) {\n return String(meta.tableName)\n }\n } catch {}\n }\n\n // Secondary lookup: search ORM metadata by candidate table names\n const modulePrefix = parts[0] ?? ''\n const candidateTables = [\n `${modulePrefix}_${rawName}`,\n pluralizeBaseName(rawName),\n `${modulePrefix}_${pluralizeBaseName(rawName)}`,\n ]\n try {\n const allMeta: any[] = metadata.getAll?.() ?? []\n for (const meta of allMeta) {\n if (meta?.tableName && candidateTables.includes(String(meta.tableName))) {\n return String(meta.tableName)\n }\n }\n } catch {}\n\n return null\n}\n\nexport function resolveEntityTableName(em: EntityManager | undefined, entity: EntityId): string {\n if (entityTableCache.has(entity)) {\n return entityTableCache.get(entity)!\n }\n const parts = String(entity || '').split(':')\n const rawName = (parts[1] && parts[1].trim().length > 0) ? parts[1] : (parts[0] || '').trim()\n\n const registered = resolveRegisteredEntityTableName(em, entity)\n if (registered) {\n entityTableCache.set(entity, registered)\n return registered\n }\n\n const fallback = pluralizeBaseName(rawName || '')\n logger.warn('Could not resolve entity via ORM metadata; falling back to table name \u2014 ensure the entity ID segment matches the class name convention', { entity, fallback })\n entityTableCache.set(entity, fallback)\n return fallback\n}\n\nfunction buildFilterableCustomFieldJoins(\n sources: QueryCustomFieldSource[] | undefined,\n): Array<{\n alias: string\n table?: string\n entityId: EntityId\n from: { field: string }\n to: { field: string }\n type: 'left' | 'inner'\n}> {\n if (!sources || sources.length === 0) return []\n return sources.flatMap((source, index) => {\n if (!source.join) return []\n const alias = typeof source.alias === 'string' && source.alias.trim().length > 0\n ? source.alias.trim()\n : `cfs_${index}`\n return [{\n alias,\n table: source.table,\n entityId: source.entityId,\n from: { field: source.join.fromField },\n to: { field: source.join.toField },\n type: source.join.type === 'inner' ? 'inner' : 'left',\n }]\n })\n}\n\nfunction computeCustomFieldScore(cfg: Record<string, unknown>, kind: string, entityIndex: number) {\n const listVisibleScore = cfg.listVisible === false ? 0 : 1\n const formEditableScore = cfg.formEditable === false ? 0 : 1\n const filterableScore = cfg.filterable ? 1 : 0\n const kindScore = (() => {\n switch (kind) {\n case 'dictionary': return 8\n case 'relation': return 6\n case 'select': return 4\n case 'multiline': return 3\n case 'boolean':\n case 'integer':\n case 'float': return 2\n default: return 1\n }\n })()\n const optionsBonus = Array.isArray(cfg.options) && cfg.options.length ? 2 : 0\n const dictionaryBonus = typeof cfg.dictionaryId === 'string' && (cfg.dictionaryId as string).trim().length ? 5 : 0\n const base = (listVisibleScore * 16) + (formEditableScore * 8) + (filterableScore * 4) + kindScore + optionsBonus + dictionaryBonus\n const penalty = typeof cfg.priority === 'number' ? cfg.priority : 0\n return { base, penalty, entityIndex }\n}\n\n/**\n * BasicQueryEngine \u2014 Kysely-backed fallback query engine.\n *\n * Resolves base tables via MikroORM metadata, applies tenant/organization/\n * deleted_at scoping, handles custom field (cf:*) selection and filtering,\n * and performs entity-extension joins. Used as the fallback for\n * {@link HybridQueryEngine} when the query index is unavailable or incomplete.\n */\nexport class BasicQueryEngine implements QueryEngine {\n private searchAliasSeq = 0\n private searchAvailabilityInstance: SearchTokenAvailability | null = null\n\n constructor(\n private em: EntityManager,\n private getDbFn?: () => AnyDb,\n private resolveEncryptionService?: EncryptionResolver,\n ) {}\n\n private getEncryptionService() {\n try {\n return this.resolveEncryptionService?.() ?? null\n } catch {\n return null\n }\n }\n\n private getDb(): AnyDb {\n if (this.getDbFn) return this.getDbFn()\n const emAny = this.em as any\n if (typeof emAny?.getKysely === 'function') return emAny.getKysely() as AnyDb\n throw new Error('BasicQueryEngine requires an EntityManager exposing getKysely() (MikroORM v7)')\n }\n\n private searchAvailability(): SearchTokenAvailability {\n if (!this.searchAvailabilityInstance) {\n this.searchAvailabilityInstance = createSearchTokenAvailability({\n getDb: () => this.getDb() as unknown as SearchTokenProbeDb,\n getConfig: resolveSearchConfig,\n applyOrganizationScope: (query, column, scope) => this.applyOrganizationScope(\n query as unknown as AnyBuilder,\n column,\n scope,\n ) as unknown as SearchTokenProbeQueryBuilder,\n logDebug: (event, payload) => this.logSearchDebug(event, payload),\n })\n }\n return this.searchAvailabilityInstance\n }\n\n async query<T = any>(entity: EntityId, opts: QueryOptions = {}): Promise<QueryResult<T>> {\n // --- UMES query extension: before-query pipeline ---\n const ext = opts.extensions\n let effectiveOpts = opts\n let extensionCtx: QueryExtensionContext | null = null\n const noop = { resolve: <R = unknown>(_name: string): R => { throw new Error('No DI context') } }\n\n if (ext) {\n extensionCtx = {\n entity: String(entity),\n engine: 'basic',\n tenantId: opts.tenantId ?? '',\n organizationId: opts.organizationId,\n userId: ext.userId,\n em: this.em,\n container: ext.container,\n userFeatures: ext.userFeatures,\n }\n const diCtx = ext.resolve ? { resolve: ext.resolve } : noop\n const beforeResult = await runBeforeQueryPipeline(opts, extensionCtx, diCtx)\n if (beforeResult.blocked) {\n throw new Error(beforeResult.errorMessage ?? 'Query blocked by extension subscriber')\n }\n effectiveOpts = beforeResult.query\n }\n // Strip extensions from effectiveOpts so they don't propagate to sub-queries\n const { extensions: _ext, ...coreOpts } = effectiveOpts\n opts = coreOpts\n\n // Heuristic: map '<module>:user' -> table 'users'\n const table = resolveEntityTableName(this.em, entity)\n const db = this.getDb()\n\n const qualify = (col: string) => `${table}.${col}`\n const orgScope = this.resolveOrganizationScope(opts)\n this.searchAliasSeq = 0\n // Require tenant scope for all queries\n if (!opts.tenantId) {\n throw new Error(\n 'QueryEngine: tenantId is now required for all queries (breaking change). ' +\n 'Please provide a tenantId in QueryOptions, e.g., query(entity, { tenantId: ... }). ' +\n 'See migration guide or documentation for details.'\n )\n }\n const skipAutoScope = opts.omitAutomaticTenantOrgScope === true\n\n const normalizedFilters = normalizeFilters(opts.filters)\n const resolvedJoins = resolveJoins(\n table,\n [...(opts.joins ?? []), ...buildFilterableCustomFieldJoins(opts.customFieldSources)],\n (entityId) => resolveEntityTableName(this.em, entityId as any),\n )\n const joinMap = new Map<string, ResolvedJoin>()\n const aliasTables = new Map<string, string>()\n aliasTables.set(table, table)\n aliasTables.set('base', table)\n for (const join of resolvedJoins) {\n joinMap.set(join.alias, join)\n aliasTables.set(join.alias, join.table)\n }\n const { baseFilters, joinFilters } = partitionFilters(table, normalizedFilters, joinMap)\n const cfFilters = normalizedFilters.filter((filter) => String(filter.field).startsWith('cf:'))\n // Custom-field leaves carrying an orGroup belong to an OR disjunct; applying them\n // one `.where()` at a time would AND them onto every disjunct (#5039).\n const regularCfFilters = cfFilters.filter((filter) => !filter.orGroup)\n const orGroupCfFilters = cfFilters.filter((filter) => filter.orGroup)\n const searchConfig = resolveSearchConfig()\n const searchFilters = [...baseFilters, ...cfFilters].filter((filter) => isSearchFilterOp(filter.op))\n // Callers that opt out of automatic tenant/org scoping own the full\n // visibility predicate. Search-token filtering has its own tenant/org\n // guards, so it must be disabled on this direct-query path as documented\n // by QueryOptions.omitAutomaticTenantOrgScope.\n const searchEnabled = !skipAutoScope && await this.searchAvailability().staticEnabled()\n // Probe `search_tokens` only when this query actually searches (#4723): every consumer of\n // `searchActive` sits behind a like/ilike guard, so on a plain list load the answer is never\n // read \u2014 and the probe is a `LIMIT 1` the planner can resolve as a seq scan over a large\n // `search_tokens`. The join path below already probes lazily for the same reason.\n const hasSearchTokens = searchEnabled && searchFilters.length\n ? await this.searchAvailability().hasTokens(String(entity), opts.tenantId ?? null, orgScope)\n : false\n const searchActive = searchEnabled && hasSearchTokens\n // Opt-in via OM_SEARCH_USE_ILIKE_FOR_NON_ENCRYPTED_FIELDS (default false: the pre-existing\n // rewrite-everything behavior is kept). When enabled, base-column like/ilike is rerouted\n // through search tokens ONLY for encrypted columns, where\n // ILIKE against ciphertext cannot match. On a plaintext column SQL ILIKE is exact, and the token\n // rewrite silently changes the result set: tokenization splits on non-alphanumerics and drops\n // tokens shorter than minTokenLength, so a document-number search like \"ZK 1/2026\" degrades to\n // the tokens {202, 2026} and matches every record from that year instead of the one document.\n // `ignoreRuntimeHealth` asks the on-disk question -- a column holds ciphertext even while the\n // KMS is down -- so an outage keeps encrypted columns on the token path (#4622).\n // `organizationId: null` is deliberate, not an omission: the service then unions in every\n // organization's map (`fetchAllOrganizationFieldNames`), so a field any org encrypts stays on\n // the token path -- a wider set fails safe. Passing the request's org instead would silently\n // break encrypted-column search for orgs without their own map. That union is an UNCACHED\n // `encryption_maps` read, one extra round-trip per searched list request. `null` means\n // the encryption service could not answer at all; keep the pre-existing rewrite-everything\n // behavior then, because guessing \"plaintext\" would turn encrypted-column search into an\n // ILIKE-on-ciphertext that matches nothing.\n let encryptedLikeFields: Set<string> | null = null\n if (\n searchActive &&\n searchConfig.useIlikeForNonEncryptedFields === true &&\n searchFilters.some((filter) => !String(filter.field).startsWith('cf:'))\n ) {\n try {\n const service = this.getEncryptionService()\n const readEncryptedFieldNames = service?.getEncryptedFieldNames?.bind(service)\n if (readEncryptedFieldNames) {\n encryptedLikeFields = await resolveEncryptedLikeFieldSet(\n () => readEncryptedFieldNames(\n String(entity),\n opts.tenantId ?? null,\n null,\n { ignoreRuntimeHealth: true },\n ),\n String(entity),\n opts.tenantId ?? null,\n )\n } else if (isTenantDataEncryptionEnabled()) {\n // Encryption is on but the service is unreachable (a swallowed DI failure looks\n // exactly like \"no service\"): treat the map as UNKNOWN and keep the token rewrite,\n // rather than guessing \"plaintext\" and running ILIKE against ciphertext.\n encryptedLikeFields = null\n } else {\n // Encryption disabled: nothing is ciphertext at rest, exact ILIKE is always right.\n encryptedLikeFields = new Set()\n }\n } catch (err) {\n // The fallback is safe (the old rewrite-everything behavior), but taking it silently\n // would hide that the gate has stopped working.\n logger.warn('search: encrypted-field map unavailable; keeping the token rewrite for all columns', {\n entity: String(entity),\n error: err instanceof Error ? err.message : String(err),\n })\n encryptedLikeFields = null\n }\n }\n if (searchFilters.length) {\n const fields = searchFilters.map((filter) => String(filter.field))\n this.logSearchDebug('search:init', {\n entity: String(entity),\n table,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n fields,\n searchEnabled,\n hasSearchTokens,\n searchActive,\n searchConfig: {\n enabled: searchConfig.enabled,\n minTokenLength: searchConfig.minTokenLength,\n enablePartials: searchConfig.enablePartials,\n hashAlgorithm: searchConfig.hashAlgorithm,\n blocklistedFields: searchConfig.blocklistedFields,\n },\n })\n if (!searchEnabled) {\n this.logSearchDebug('search:disabled', { entity: String(entity), table })\n } else if (!hasSearchTokens) {\n this.logSearchDebug('search:no-search-tokens', {\n entity: String(entity),\n table,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n }\n const fallbackFields = searchFilters\n .filter((filter) => !searchActive || typeof filter.value !== 'string' || tokenizeText(filter.value, searchConfig).hashes.length === 0)\n .map((filter) => String(filter.field))\n if (fallbackFields.length) {\n await warnOnCiphertextLikeFallback({\n entity: String(entity),\n fields: fallbackFields,\n tenantId: opts.tenantId ?? null,\n // `searchEnabled` also folds in the missing-table and\n // omitAutomaticTenantOrgScope cases, which are \"no usable tokens\"\n // rather than \"the operator switched search off\".\n reason: searchActive\n ? 'no-indexable-tokens'\n : searchConfig.enabled ? 'no-search-tokens' : 'search-disabled',\n service: this.getEncryptionService(),\n })\n }\n }\n for (const [alias, joinedFilters] of joinFilters) {\n const filters = joinedFilters.filter((entry) => entry.op === 'like' || entry.op === 'ilike')\n if (!filters.length) continue\n const join = joinMap.get(alias)\n if (!join?.entityId) continue\n const hasJoinedTokens = searchEnabled\n ? await this.searchAvailability().hasTokens(join.entityId, opts.tenantId ?? null, orgScope)\n : false\n const fallbackFields = filters\n .filter((filter) => !hasJoinedTokens || typeof filter.value !== 'string' || tokenizeText(filter.value, searchConfig).hashes.length === 0)\n .map((filter) => filter.column)\n if (!fallbackFields.length) continue\n await warnOnCiphertextLikeFallback({\n entity: join.entityId,\n fields: fallbackFields,\n tenantId: opts.tenantId ?? null,\n reason: hasJoinedTokens\n ? 'no-indexable-tokens'\n : searchConfig.enabled ? 'no-search-tokens' : 'search-disabled',\n service: this.getEncryptionService(),\n })\n }\n const recordIdColumn = qualify('id')\n\n const applyFilterOp = (builder: AnyBuilder, column: string | RawBuilder<unknown>, op: string, value: unknown, fieldName?: string): AnyBuilder => {\n if (\n (op === 'like' || op === 'ilike') &&\n searchActive &&\n typeof value === 'string' &&\n fieldName &&\n typeof column === 'string' &&\n // Plaintext columns keep exact SQL ILIKE -- see the encryptedLikeFields note above. cf:*\n // filters never reach this path (they are applied by the custom-field branches), so this\n // gate only decides base columns. Membership is tested across name-shape candidates --\n // encryption maps may declare `displayName` while the filter carries the column name\n // `display_name`, and a raw comparison would misread that ciphertext column as plaintext.\n (encryptedLikeFields === null || isEncryptedLikeField(encryptedLikeFields, fieldName))\n ) {\n const tokens = tokenizeText(String(value), searchConfig)\n const hashes = tokens.hashes\n if (hashes.length) {\n const result = this.applySearchTokens(builder, {\n entity: String(entity),\n field: fieldName,\n hashes,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n tokens: tokens.tokens,\n })\n this.logSearchDebug('search:filter', {\n entity: String(entity),\n field: fieldName,\n tokens: tokens.tokens,\n hashes,\n applied: result.applied,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n if (result.applied) return result.builder\n } else {\n this.logSearchDebug('search:skip-empty-hashes', {\n entity: String(entity),\n field: fieldName,\n value,\n })\n }\n }\n return this.applyColumnOp(builder, column, op, value)\n }\n\n // `eq` is accepted alongside `like`/`ilike` so that filters against\n // encrypted joined columns (whose ciphertext cannot be compared for\n // equality in SQL) can still resolve via tokenized search. Routing\n // only applies when `searchEnabled` is true AND the joined entity has\n // search tokens installed (`searchAvailable`); for non-searchable or\n // non-encrypted columns the caller still falls through to exact SQL\n // equality via `applyFilterOp`. Note that token match is approximate \u2014\n // callers needing strict equality on encrypted fields should filter on\n // the deterministic `*_hash` column instead.\n const applyJoinFilterOp = async (\n builder: AnyBuilder,\n filter: { column: string; op: string; value?: unknown },\n _qualified: string,\n join: ResolvedJoin,\n ): Promise<{ applied: boolean; builder: AnyBuilder }> => {\n if (!searchEnabled || !join.entityId) return { applied: false, builder }\n if (!['like', 'ilike'].includes(filter.op)) return { applied: false, builder }\n if (typeof filter.value !== 'string' || filter.value.trim().length === 0) return { applied: false, builder }\n\n const searchAvailable = await this.searchAvailability().hasTokens(join.entityId, opts.tenantId ?? null, orgScope)\n if (!searchAvailable) return { applied: false, builder }\n\n const tokens = tokenizeText(String(filter.value), searchConfig)\n if (!tokens.hashes.length) return { applied: false, builder }\n\n const result = this.applySearchTokens(builder, {\n entity: join.entityId,\n field: filter.column,\n hashes: tokens.hashes,\n recordIdColumn: `${join.alias}.id`,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n tokens: tokens.tokens,\n })\n return { applied: result.applied, builder: result.builder }\n }\n\n const regularBaseFilters = baseFilters.filter((f) => !f.orGroup)\n const orGroupFilters = baseFilters.filter((f) => f.orGroup)\n\n const applyAliasScopes = async (builder: AnyBuilder, aliasName: string): Promise<AnyBuilder> => {\n const targetTable = aliasTables.get(aliasName)\n if (!targetTable) return builder\n let next = builder\n if (!skipAutoScope && orgScope && await this.columnExists(targetTable, 'organization_id')) {\n next = this.applyOrganizationScope(next, `${aliasName}.organization_id`, orgScope)\n }\n if (!skipAutoScope && opts.tenantId && await this.columnExists(targetTable, 'tenant_id')) {\n next = next.where(`${aliasName}.tenant_id`, '=', opts.tenantId)\n }\n return next\n }\n\n const fallbackOrgId =\n opts.organizationId\n ?? (Array.isArray(opts.organizationIds) && opts.organizationIds.length === 1 ? opts.organizationIds[0] : null)\n const encryptionService = this.getEncryptionService()\n const resolvedSorts: Sort[] = []\n for (const s of opts.sort || []) {\n if (s.field.startsWith('cf:')) {\n resolvedSorts.push(s)\n } else {\n const column = await this.resolveBaseColumn(table, s.field)\n if (column) resolvedSorts.push({ ...s, field: column })\n }\n }\n const encryptedSortFields = await resolveEncryptedSortFields(\n encryptionService,\n entity,\n resolvedSorts.filter((sort) => !sort.field.startsWith('cf:')).map((sort) => sort.field),\n opts.tenantId ?? null,\n fallbackOrgId,\n )\n const requiresPlaintextSort = encryptedSortFields.size > 0\n\n const tenantId = opts.tenantId\n const sanitize = (s: string) => s.replace(/[^a-zA-Z0-9_]/g, '_')\n\n type BuiltQuery = {\n builder: AnyBuilder\n hasJoinedAggregates: boolean\n cfJsonAliases: Set<string>\n cfMultiAliasByAlias: Map<string, string>\n resolvedCustomFieldDefinitions: ResolvedCustomFieldDefinitions | undefined\n }\n\n // Builds the fully-scoped query from a fresh root. `projection: 'full'` reproduces\n // today's complete selection (base fields + CF projections + extension joins).\n // `projection: 'sortKeys'` selects only `id` + the sort columns \u2014 the slim phase-1\n // candidate scan used when `requiresPlaintextSort`. `projection: 'count'` carries\n // scope + filters only: projection joins (CF defs/values, extensions) are omitted\n // and cf filters are expressed as correlated EXISTS semi-joins, so nothing can\n // multiply base rows and a LIMIT above the query is an enforceable bound.\n // Re-running the WHERE/JOIN logic per projection is cheap: every `columnExists`\n // check is memoized on the module-scoped `columnExistsCache`, so later passes\n // hit no extra DB calls.\n const buildQuery = async (projection: 'full' | 'sortKeys' | 'count'): Promise<BuiltQuery> => {\n const isSortKeysProjection = projection === 'sortKeys'\n const isCountProjection = projection === 'count'\n let q: AnyBuilder = db.selectFrom(table as any)\n\n // Tenant/org/soft-delete scope\n if (!skipAutoScope && orgScope && await this.columnExists(table, 'organization_id')) {\n q = this.applyOrganizationScope(q, qualify('organization_id'), orgScope)\n }\n if (!skipAutoScope && await this.columnExists(table, 'tenant_id')) {\n q = q.where(qualify('tenant_id'), '=', opts.tenantId)\n }\n if (!opts.withDeleted && await this.columnExists(table, 'deleted_at')) {\n q = q.where(qualify('deleted_at'), 'is', null)\n }\n\n for (const filter of regularBaseFilters) {\n const fieldName = String(filter.field)\n let qualified = filter.qualified ?? null\n if (!qualified) {\n const column = await this.resolveBaseColumn(table, fieldName)\n if (!column) {\n q = this.applyIndexDocFilter(q, {\n entity: String(entity),\n field: fieldName,\n op: filter.op,\n value: filter.value,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n withDeleted: opts.withDeleted === true,\n searchActive,\n searchConfig,\n })\n continue\n }\n qualified = qualify(column)\n }\n q = applyFilterOp(q, qualified, filter.op, filter.value, fieldName)\n }\n\n // OR-grouped filters: AND within each group (one $or disjunct), OR between groups.\n // Resolution happens here (it needs async column lookups); the WHERE itself is\n // applied further down, once the cf:* value expressions exist \u2014 an OR group may\n // contain custom-field leaves whose SQL is only available then (#5039).\n type ResolvedOrClause =\n | { kind: 'column'; qualified: string; op: NormalizedFilter['op']; value: unknown }\n | { kind: 'doc'; field: string; op: NormalizedFilter['op']; value: unknown }\n | { kind: 'cf'; key: string; op: NormalizedFilter['op']; value: unknown }\n const resolvedGroupFilters: ResolvedOrClause[][] = []\n if (orGroupFilters.length > 0 || orGroupCfFilters.length > 0) {\n const groups = new Map<string, NormalizedFilter[]>()\n for (const f of [...orGroupFilters, ...orGroupCfFilters]) {\n const group = groups.get(f.orGroup!) ?? []\n group.push(f as NormalizedFilter)\n groups.set(f.orGroup!, group)\n }\n for (const [, groupFilters] of groups) {\n const resolved: ResolvedOrClause[] = []\n for (const filter of groupFilters) {\n const field = String(filter.field)\n if (field.startsWith('cf:')) {\n resolved.push({ kind: 'cf', key: field.slice(3), op: filter.op, value: filter.value })\n continue\n }\n const column = await this.resolveBaseColumn(table, field)\n if (column) {\n resolved.push({ kind: 'column', qualified: qualify(column), op: filter.op, value: filter.value })\n } else {\n // Field is not a base column \u2014 for custom-entity records it lives in\n // entity_indexes.doc. Build an EXISTS sub-filter so `$or` searches\n // across doc fields resolve instead of being silently dropped (#3229).\n resolved.push({ kind: 'doc', field, op: filter.op, value: filter.value })\n }\n }\n if (resolved.length > 0) resolvedGroupFilters.push(resolved)\n }\n }\n\n q = await applyJoinFilters({\n db,\n baseTable: table,\n builder: q,\n joinMap,\n joinFilters,\n aliasTables,\n qualifyBase: (column) => qualify(column),\n applyAliasScope: (builder, alias) => applyAliasScopes(builder, alias),\n applyFilterOp: (builder, column, op, value) => applyFilterOp(builder, column, op, value),\n applyJoinFilterOp,\n columnExists: (tbl, column) => this.columnExists(tbl, column),\n })\n\n // Selection (base columns only here; cf:* handled later)\n if (isCountProjection) {\n // The caller owns the count query's SELECT (a constant inside the\n // bounded subquery, or the aggregate itself when the cap is off).\n } else if (isSortKeysProjection) {\n q = q.select(sql.ref(qualify('id')).as('id'))\n if (await this.columnExists(table, 'tenant_id')) {\n q = q.select(sql.ref(qualify('tenant_id')).as('tenant_id'))\n }\n if (await this.columnExists(table, 'organization_id')) {\n q = q.select(sql.ref(qualify('organization_id')).as('organization_id'))\n }\n for (const s of resolvedSorts) {\n if (!s.field.startsWith('cf:')) q = q.select(sql.ref(qualify(s.field)).as(s.field))\n }\n } else if (opts.fields && opts.fields.length) {\n const cols = new Set(opts.fields.filter((f) => !f.startsWith('cf:')))\n if (requiresPlaintextSort) {\n for (const field of encryptedSortFields) cols.add(field)\n }\n for (const c of cols) {\n // Qualify and alias to base names to avoid ambiguity\n q = q.select(sql.ref(qualify(c)).as(c))\n }\n } else {\n // Default to selecting only base table columns to avoid ambiguity when joining\n q = q.select(sql`${sql.ref(table)}.*`.as('__all'))\n }\n\n // Resolve which custom fields to include\n const cfSourcesResult = this.configureCustomFieldSources(q, table, entity, db, opts, qualify, !isCountProjection)\n q = cfSourcesResult.builder\n const cfSources = cfSourcesResult.sources\n const entityIdToSource = new Map<string, ResolvedCustomFieldSource>()\n for (const source of cfSources) {\n entityIdToSource.set(String(source.entityId), source)\n }\n const requestedCustomFieldKeys = (projection === 'full' && Array.isArray(opts.includeCustomFields))\n ? opts.includeCustomFields.map((key) => String(key))\n : []\n const cfKeys = new Set<string>()\n const keySource = new Map<string, ResolvedCustomFieldSource>()\n // Custom-field definition index threaded onto the result so the CRUD factory\n // can decorate list rows without reloading definitions from the DB (#2133).\n // Output-only \u2014 never resolved for the slim sortKeys projection.\n let resolvedCustomFieldDefinitions: ResolvedCustomFieldDefinitions | undefined\n // Explicit in fields/filters\n if (projection === 'full') {\n for (const f of (opts.fields || [])) {\n if (typeof f === 'string' && f.startsWith('cf:')) cfKeys.add(f.slice(3))\n }\n }\n for (const f of cfFilters) {\n if (typeof f.field === 'string' && f.field.startsWith('cf:')) cfKeys.add(f.field.slice(3))\n }\n if (projection === 'full' && opts.includeCustomFields === true) {\n if (entityIdToSource.size > 0) {\n const entityIdList = Array.from(entityIdToSource.keys())\n const entityOrder = new Map<string, number>()\n entityIdList.forEach((id, idx) => entityOrder.set(id, idx))\n const rows = await db\n .selectFrom('custom_field_defs' as any)\n .select([\n 'key' as any,\n 'entity_id' as any,\n 'config_json' as any,\n 'kind' as any,\n 'organization_id' as any,\n 'tenant_id' as any,\n 'updated_at' as any,\n 'deleted_at' as any,\n ])\n .where('entity_id' as any, 'in', entityIdList)\n .where('is_active' as any, '=', true)\n .where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n .execute() as Array<{\n key: string\n entity_id: string\n config_json: unknown\n kind: string\n organization_id: string | null\n tenant_id: string | null\n updated_at: Date | string | number | null\n deleted_at: Date | string | number | null\n }>\n // Build the decoration index from the same rows, scoped exactly like the\n // factory's loadCustomFieldDefinitionIndex (tenant + is_active already\n // applied in SQL; org + soft-delete applied in the shared builder).\n const orgCandidates = resolveCfDefIndexOrgCandidates(opts.organizationIds, opts.organizationId ?? null)\n const definitionRows: CustomFieldDefinitionRow[] = rows.map((row) => ({\n key: String(row.key),\n entityId: String(row.entity_id),\n kind: row.kind == null ? null : String(row.kind),\n configJson: row.config_json,\n organizationId: row.organization_id == null ? null : String(row.organization_id),\n tenantId: row.tenant_id == null ? null : String(row.tenant_id),\n deletedAt: row.deleted_at ?? null,\n updatedAt: row.updated_at ?? null,\n }))\n resolvedCustomFieldDefinitions = {\n index: buildCustomFieldDefinitionIndexFromRows(definitionRows, { organizationIds: orgCandidates }),\n entityIds: entityIdList,\n tenantId: tenantId ?? null,\n organizationIds: orgCandidates,\n }\n type ScoredCustomFieldRow = {\n key: string\n entityId: string\n kind: string\n config: Record<string, unknown>\n }\n const sorted: ScoredCustomFieldRow[] = rows.map((row) => {\n const raw = row.config_json\n let cfg: Record<string, any> = {}\n if (raw && typeof raw === 'string') {\n try { cfg = JSON.parse(raw) } catch { cfg = {} }\n } else if (raw && typeof raw === 'object') {\n cfg = raw as Record<string, any>\n }\n return {\n key: String(row.key),\n entityId: String(row.entity_id),\n kind: String(row.kind || ''),\n config: cfg,\n }\n })\n sorted.sort((a, b) => {\n const ai = entityOrder.get(a.entityId) ?? Number.MAX_SAFE_INTEGER\n const bi = entityOrder.get(b.entityId) ?? Number.MAX_SAFE_INTEGER\n if (ai !== bi) return ai - bi\n return a.key.localeCompare(b.key)\n })\n const selectedSources = new Map<string, { source: ResolvedCustomFieldSource; score: number; penalty: number; entityIndex: number }>()\n for (const row of sorted) {\n const source = entityIdToSource.get(row.entityId)\n if (!source) continue\n const cfg = row.config || {}\n const entityIndex = entityOrder.get(row.entityId) ?? Number.MAX_SAFE_INTEGER\n const scores = computeCustomFieldScore(cfg, row.kind, entityIndex)\n const existing = selectedSources.get(row.key)\n if (!existing || scores.base > existing.score || (scores.base === existing.score && (scores.penalty < existing.penalty || (scores.penalty === existing.penalty && scores.entityIndex < existing.entityIndex)))) {\n selectedSources.set(row.key, { source, score: scores.base, penalty: scores.penalty, entityIndex: scores.entityIndex })\n }\n cfKeys.add(row.key)\n }\n for (const [key, entry] of selectedSources.entries()) {\n keySource.set(key, entry.source)\n }\n }\n } else if (!isSortKeysProjection && requestedCustomFieldKeys.length > 0) {\n for (const key of requestedCustomFieldKeys) cfKeys.add(key)\n }\n const unresolvedKeys = Array.from(cfKeys).filter((key) => !keySource.has(key))\n if (unresolvedKeys.length > 0 && entityIdToSource.size > 0) {\n const rows = await db\n .selectFrom('custom_field_defs' as any)\n .select(['key' as any, 'entity_id' as any])\n .where('entity_id' as any, 'in', Array.from(entityIdToSource.keys()))\n .where('key' as any, 'in', unresolvedKeys)\n .where('is_active' as any, '=', true)\n .where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n .execute() as Array<{ key: string; entity_id: string }>\n for (const row of rows) {\n const source = entityIdToSource.get(String(row.entity_id))\n if (!source) continue\n if (!keySource.has(row.key)) keySource.set(row.key, source)\n }\n }\n\n const cfValueExprByKey: Record<string, RawBuilder<string | null>> = {}\n const cfSelectedAliases: string[] = []\n const cfJsonAliases = new Set<string>()\n const cfMultiAliasByAlias = new Map<string, string>()\n for (const key of cfKeys) {\n const source = keySource.get(key)\n if (!source) continue\n // The count shape never joins defs/values \u2014 cf filters are applied as\n // correlated EXISTS semi-joins below, so no join can multiply base rows.\n if (isCountProjection) continue\n const entityIdForKey = source.entityId\n const recordIdExpr = source.recordIdExpr\n const sourceAliasSafe = sanitize(source.alias || 'src')\n const keyAliasSafe = sanitize(key)\n const defAlias = `cfd_${sourceAliasSafe}_${keyAliasSafe}`\n const valAlias = `cfv_${sourceAliasSafe}_${keyAliasSafe}`\n // Join definitions for kind resolution\n q = q.leftJoin(`custom_field_defs as ${defAlias}` as any, (jb: any) =>\n jb.on(`${defAlias}.entity_id`, '=', String(entityIdForKey))\n .on(`${defAlias}.key`, '=', key)\n .on(`${defAlias}.is_active`, '=', true)\n .on((eb: any) => eb.or([\n eb(`${defAlias}.tenant_id`, '=', tenantId),\n eb(`${defAlias}.tenant_id`, 'is', null),\n ]))\n )\n // Join values with record match\n q = q.leftJoin(`custom_field_values as ${valAlias}` as any, (jb: any) =>\n jb.on(`${valAlias}.entity_id`, '=', String(entityIdForKey))\n .on(`${valAlias}.field_key`, '=', key)\n .onRef(`${valAlias}.record_id`, '=', recordIdExpr as any)\n .on((eb: any) => eb.or([\n eb(`${valAlias}.tenant_id`, '=', tenantId),\n eb(`${valAlias}.tenant_id`, 'is', null),\n ]))\n )\n // Force a common SQL type across branches to avoid Postgres CASE type conflicts\n const caseExpr = sql<string | null>`CASE ${sql.ref(`${defAlias}.kind`)}\n WHEN 'integer' THEN (${sql.ref(`${valAlias}.value_int`)})::text\n WHEN 'float' THEN (${sql.ref(`${valAlias}.value_float`)})::text\n WHEN 'boolean' THEN (${sql.ref(`${valAlias}.value_bool`)})::text\n WHEN 'multiline' THEN (${sql.ref(`${valAlias}.value_multiline`)})::text\n ELSE (${sql.ref(`${valAlias}.value_text`)})::text\n END`\n cfValueExprByKey[key] = caseExpr\n const alias = sanitize(`cf:${key}`)\n // Project as aggregated to avoid duplicates when multi values exist\n if (!isSortKeysProjection && ((opts.fields || []).includes(`cf:${key}`) || opts.includeCustomFields === true || (requestedCustomFieldKeys.length > 0 && requestedCustomFieldKeys.includes(key)))) {\n const multiAlias = `${alias}__is_multi`\n const isMultiExpr = sql<boolean>`bool_or(coalesce((${sql.ref(`${defAlias}.config_json`)}->>'multi')::boolean, false))`\n const aggregatedArray = sql<unknown>`array_remove(array_agg(DISTINCT ${caseExpr}), NULL)`\n const projExpr = sql<unknown>`CASE WHEN ${isMultiExpr}\n THEN to_jsonb(${aggregatedArray})\n ELSE to_jsonb(max(${caseExpr}))\n END`\n q = q.select(projExpr.as(alias))\n q = q.select(isMultiExpr.as(multiAlias))\n cfSelectedAliases.push(alias)\n cfJsonAliases.add(alias)\n cfMultiAliasByAlias.set(alias, multiAlias)\n }\n }\n\n // Apply cf:* filters (on raw expressions; as EXISTS semi-joins for the count\n // shape). OR-grouped ones are excluded here and combined with their\n // disjunct's other leaves right below.\n for (const f of regularCfFilters) {\n if (!f.field.startsWith('cf:')) continue\n const key = f.field.slice(3)\n const filterSource = keySource.get(key)\n if (!filterSource) continue\n const expr = cfValueExprByKey[key]\n if (!isCountProjection && !expr) continue\n if ((f.op === 'like' || f.op === 'ilike') && searchActive && typeof f.value === 'string') {\n const tokens = tokenizeText(String(f.value), searchConfig)\n const hashes = tokens.hashes\n if (hashes.length) {\n const result = this.applySearchTokens(q, {\n entity: String(entity),\n field: f.field,\n hashes,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n tokens: tokens.tokens,\n })\n this.logSearchDebug('search:cf-filter', {\n entity: String(entity),\n field: f.field,\n tokens: tokens.tokens,\n hashes,\n applied: result.applied,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n if (result.applied) {\n q = result.builder\n continue\n }\n } else {\n this.logSearchDebug('search:cf-skip-empty-hashes', {\n entity: String(entity),\n field: f.field,\n value: f.value,\n })\n }\n }\n if (isCountProjection) {\n q = this.applyCfValueExistsFilter(q, {\n source: filterSource,\n qualify,\n tenantId: tenantId ?? null,\n key,\n op: f.op,\n value: f.value,\n })\n continue\n }\n q = this.applyColumnOp(q, expr, f.op, f.value)\n }\n\n // OR groups are applied here, after the cf:* value expressions exist, so a\n // disjunct mixing base/doc and custom-field leaves is united rather than\n // intersected. A cf leaf whose key resolved no value expression yields no\n // predicate and is dropped; a disjunct left empty by that is dropped too,\n // because an empty AND would read as TRUE and widen the result.\n //\n // Known limitation, shared with the `doc` clause kind above: a leaf inside an OR\n // group compares against the stored value directly and does not route `like` /\n // `ilike` through the search-token index the way the ungrouped path does. On a\n // field covered by an encryption map such a leaf therefore compares against\n // ciphertext and will not match.\n //\n // The count shape never populates cfValueExprByKey (it joins no cf tables), so\n // its applicability test is key resolution itself \u2014 the same condition that\n // gates the full shape's expression map \u2014 and a cf leaf compiles to a\n // correlated EXISTS instead of a value-expression comparison. Dropping it\n // instead would narrow the OR and undercount relative to the display query.\n const cfLeafApplicable = (key: string): boolean =>\n isCountProjection ? keySource.has(key) : Boolean(cfValueExprByKey[key])\n const applicableGroupFilters = resolvedGroupFilters\n .map((group) => group.filter((rf) => rf.kind !== 'cf' || cfLeafApplicable(rf.key)))\n .filter((group) => group.length > 0)\n if (applicableGroupFilters.length > 0) {\n q = q.where((eb: any) => {\n const disjuncts = applicableGroupFilters.map((group) => {\n const parts = group.map((rf) => {\n if (rf.kind === 'column') return this.buildColumnOpExpression(eb, rf.qualified, rf.op, rf.value)\n if (rf.kind === 'cf') {\n if (isCountProjection) {\n return this.buildCfValueExistsExpression(eb, {\n source: keySource.get(rf.key)!,\n qualify,\n tenantId: tenantId ?? null,\n key: rf.key,\n op: rf.op,\n value: rf.value,\n })\n }\n return this.buildColumnOpExpression(eb, cfValueExprByKey[rf.key], rf.op, rf.value)\n }\n return this.buildIndexDocOpExpression(eb, {\n entity: String(entity),\n field: rf.field,\n op: rf.op,\n value: rf.value,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n withDeleted: opts.withDeleted === true,\n })\n })\n return parts.length === 1 ? parts[0] : eb.and(parts)\n })\n return disjuncts.length === 1 ? disjuncts[0] : eb.or(disjuncts)\n })\n }\n\n // Entity extensions joins (no selection yet; enables future filters/projections).\n // Projection-only, so the count shape omits them.\n if (opts.includeExtensions && !isCountProjection) {\n const { getModules } = await import('@open-mercato/shared/lib/i18n/server')\n const allMods = getModules() as any[]\n const allExts = allMods.flatMap((m) => (m as any).entityExtensions || [])\n const exts = allExts.filter((e: any) => e.base === entity)\n const chosen = Array.isArray(opts.includeExtensions)\n ? exts.filter((e: any) => (opts.includeExtensions as string[]).includes(e.extension))\n : exts\n for (const e of chosen) {\n const [, extName] = (e.extension as string).split(':')\n // Uses the SAME derivation as every other table-name fallback in this file\n // (`pluralizeBaseName`, also called at the resolveEntityTableName sites above)\n // rather than a separate inline one. The inline version handled only `+s`, so\n // `example_customer_priority` derived `example_customer_prioritys` against the\n // real `example_customer_priorities`. Behaviour is unchanged for every name that\n // does not end in `y`; `table` below remains the escape hatch for irregular\n // plurals no guesser can win (`person` \u2192 `people`).\n const derivedTable = pluralizeBaseName(extName)\n const extTable = isPlainTableIdentifier(e.table) ? e.table : derivedTable\n const alias = `ext_${sanitize(extName)}`\n q = q.leftJoin(`${extTable} as ${alias}` as any, (jb: any) =>\n jb.onRef(`${alias}.${e.join.extensionKey}`, '=', `${table}.${e.join.baseKey}`)\n )\n }\n }\n\n // Sorting: base fields and cf:* (use aggregated alias for cf)\n for (const s of isCountProjection ? [] : resolvedSorts) {\n if (s.field.startsWith('cf:')) {\n const key = s.field.slice(3)\n const alias = sanitize(`cf:${key}`)\n // Ensure included in projection to sort by\n if (!cfSelectedAliases.includes(alias)) {\n const expr = cfValueExprByKey[key]\n if (expr) {\n q = q.select(sql<string | null>`max(${expr})`.as(alias))\n cfSelectedAliases.push(alias)\n }\n }\n if (!requiresPlaintextSort) q = q.orderBy(alias, (s.dir ?? 'asc') as any)\n } else {\n if (!requiresPlaintextSort) q = q.orderBy(qualify(s.field), (s.dir ?? 'asc') as any)\n }\n }\n\n // Deduplicate if we joined CFs or extensions by grouping on base id. The count\n // shape has neither, and must stay barrier-free for its LIMIT to bind.\n const hasJoinedAggregates = !isCountProjection && (\n (opts.includeExtensions && (Array.isArray(opts.includeExtensions) ? (opts.includeExtensions.length > 0) : true)) ||\n Object.keys(cfValueExprByKey).length > 0\n )\n if (hasJoinedAggregates) {\n q = q.groupBy(`${table}.id`)\n }\n\n return { builder: q, hasJoinedAggregates, cfJsonAliases, cfMultiAliasByAlias, resolvedCustomFieldDefinitions }\n }\n\n // Pagination\n const page = opts.page?.page ?? 1\n const pageSize = opts.page?.pageSize ?? 20\n\n const {\n builder: qFull,\n hasJoinedAggregates,\n cfJsonAliases,\n cfMultiAliasByAlias,\n resolvedCustomFieldDefinitions,\n } = await buildQuery('full')\n\n // The count is built independently of the display query (the `'count'`\n // projection): scope + filters only, cf filters as correlated EXISTS\n // semi-joins, no projection joins. Nothing can multiply base rows, so\n // `count(*)` needs no DISTINCT (completing #2227) and \u2014 when the cap is\n // active \u2014 the LIMIT sits on a row-producing inner query with no\n // aggregate/sort barrier below it, so it actually bounds the scan.\n const countCap = resolveListCountCap()\n const { builder: countShape } = await buildQuery('count')\n let total: number\n let listCountCapWarning: ListCountCapWarning | undefined\n if (countCap !== null) {\n const probe = countShape.select(sql<number>`1`.as('one')).limit(countCap + 1)\n const countRow = await db\n .selectFrom(probe.as('om_count_probe') as any)\n .select(sql<string>`count(*)`.as('count'))\n .executeTakeFirst() as { count: unknown } | undefined\n const probed = Number((countRow as any)?.count ?? 0)\n if (probed > countCap) {\n total = countCap\n listCountCapWarning = { entity, cap: countCap }\n } else {\n total = probed\n }\n } else {\n const countRow = await countShape\n .select(sql<string>`count(*)`.as('count'))\n .executeTakeFirst() as { count: unknown } | undefined\n total = Number((countRow as any)?.count ?? 0)\n }\n\n const svc = encryptionService\n const decryptPayload =\n svc?.decryptEntityPayload?.bind(svc) as\n | ((\n entityId: EntityId,\n payload: Record<string, unknown>,\n tenantId: string | null,\n organizationId: string | null,\n ) => Promise<Record<string, unknown>>)\n | null\n\n const decryptRow = async (item: ResultRow): Promise<ResultRow> => {\n if (!decryptPayload) return item\n try {\n const decrypted = await decryptPayload(\n entity,\n item,\n (item?.tenant_id ?? item?.tenantId ?? opts.tenantId ?? null) as string | null,\n (item?.organization_id ?? item?.organizationId ?? fallbackOrgId ?? null) as string | null,\n )\n return { ...item, ...decrypted }\n } catch (err) {\n logger.error('Error decrypting entity payload', { err })\n return item\n }\n }\n\n const normalizeCfJsonAliases = (rows: ResultRow[]) => {\n if (cfJsonAliases.size === 0) return\n for (const row of rows) {\n for (const alias of cfJsonAliases) {\n const multiAlias = cfMultiAliasByAlias.get(alias)\n const isMulti = multiAlias ? Boolean(row[multiAlias]) : false\n let raw = row[alias]\n if (typeof raw === 'string') {\n try { raw = JSON.parse(raw) } catch { /* ignore malformed json */ }\n }\n if (isMulti) {\n if (raw == null) row[alias] = []\n else if (Array.isArray(raw)) row[alias] = raw\n else row[alias] = [raw]\n } else {\n if (Array.isArray(raw)) row[alias] = raw.length > 0 ? raw[0] : null\n else row[alias] = raw\n }\n if (multiAlias) delete row[multiAlias]\n }\n }\n }\n\n let pagedItems: ResultRow[]\n let encryptedSortRowCapWarning: EncryptedSortRowCapWarning | undefined\n\n if (requiresPlaintextSort) {\n // Phase 1: slim id+sort-columns candidate scan, bounded-concurrency decrypt,\n // in-memory sort over the full candidate set, then slice to the page's ids.\n const cap = resolveEncryptedSortMaxRows()\n let qSort = (await buildQuery('sortKeys')).builder\n if (cap !== null) {\n // Probe one row past the cap: truncation is detected from the candidate\n // scan itself, not by comparing against `total` \u2014 which may itself be\n // capped (`OM_LIST_COUNT_CAP`) and would then never exceed the sort cap.\n qSort = qSort.limit(cap + 1).orderBy(qualify('id'), 'asc' as any)\n }\n const candidateRowsRaw = await qSort.execute() as ResultRow[]\n const sortTruncated = cap !== null && candidateRowsRaw.length > cap\n const candidateRows = sortTruncated && cap !== null ? candidateRowsRaw.slice(0, cap) : candidateRowsRaw\n const decryptedCandidates = decryptPayload\n ? await mapWithConcurrency(candidateRows, DECRYPT_CONCURRENCY, decryptRow)\n : candidateRows\n const orderedCandidates = sortRowsInMemory(decryptedCandidates, resolvedSorts)\n const pageIds = orderedCandidates\n .slice((page - 1) * pageSize, page * pageSize)\n .map((row) => row.id)\n\n if (sortTruncated && cap !== null) {\n encryptedSortRowCapWarning = {\n entity,\n sortFields: resolvedSorts.map((s) => s.field),\n maxRows: cap,\n totalMatched: total,\n }\n }\n\n // Phase 2: fetch + decrypt full rows for just the page's ids, then reassemble\n // in phase-1's order (SQL `WHERE id IN (...)` order is unspecified \u2014 never\n // trust it for ordering).\n if (pageIds.length === 0) {\n pagedItems = []\n } else {\n const pageRows = await qFull.where(qualify('id'), 'in', pageIds).execute() as ResultRow[]\n normalizeCfJsonAliases(pageRows)\n const decryptedPageRows = decryptPayload\n ? await mapWithConcurrency(pageRows, DECRYPT_CONCURRENCY, decryptRow)\n : pageRows\n const byId = new Map(decryptedPageRows.map((row) => [String(row.id), row]))\n pagedItems = pageIds\n .map((id) => byId.get(String(id)))\n .filter((row): row is ResultRow => row != null)\n }\n } else {\n const dataQuery = qFull.limit(pageSize).offset((page - 1) * pageSize)\n const items = await dataQuery.execute() as ResultRow[]\n normalizeCfJsonAliases(items)\n pagedItems = decryptPayload\n ? await mapWithConcurrency(items, DECRYPT_CONCURRENCY, decryptRow)\n : items\n }\n\n let queryResult: QueryResult<T> = { items: pagedItems as unknown as T[], page, pageSize, total }\n\n if (encryptedSortRowCapWarning || listCountCapWarning) {\n const meta: QueryResultMeta = {}\n if (encryptedSortRowCapWarning) meta.encryptedSortRowCapWarning = encryptedSortRowCapWarning\n if (listCountCapWarning) meta.listCountCapWarning = listCountCapWarning\n queryResult.meta = meta\n }\n\n // --- UMES query extension: after-query pipeline ---\n if (ext && extensionCtx) {\n const diCtx = ext.resolve ? { resolve: ext.resolve } : noop\n queryResult = await runAfterQueryPipeline(\n queryResult as QueryResult<Record<string, unknown>>,\n opts,\n extensionCtx,\n diCtx,\n ) as QueryResult<T>\n }\n\n // Attach after the extension pipeline so the field always survives even if a\n // subscriber replaces the whole result object.\n if (resolvedCustomFieldDefinitions) {\n queryResult.customFieldDefinitions = resolvedCustomFieldDefinitions\n }\n\n return queryResult\n }\n\n private applyColumnOp(builder: AnyBuilder, column: string | RawBuilder<unknown>, op: string, value: unknown): AnyBuilder {\n switch (op) {\n case 'eq':\n return value === null\n ? builder.where(column as any, 'is', null)\n : builder.where(column as any, '=', value as any)\n case 'ne':\n return value === null\n ? builder.where(column as any, 'is not', null)\n : builder.where(column as any, '!=', value as any)\n case 'gt':\n return builder.where(column as any, '>', value as any)\n case 'gte':\n return builder.where(column as any, '>=', value as any)\n case 'lt':\n return builder.where(column as any, '<', value as any)\n case 'lte':\n return builder.where(column as any, '<=', value as any)\n case 'in':\n return builder.where(column as any, 'in', Array.isArray(value) ? value : [value])\n case 'nin':\n return builder.where(column as any, 'not in', Array.isArray(value) ? value : [value])\n case 'like':\n return builder.where(column as any, 'like', value as any)\n case 'ilike':\n return builder.where(column as any, 'ilike', value as any)\n case 'exists':\n return value\n ? builder.where(column as any, 'is not', null)\n : builder.where(column as any, 'is', null)\n default:\n return builder\n }\n }\n\n /**\n * Apply a `cf:*` filter as a correlated EXISTS semi-join over\n * `custom_field_values` (+ `custom_field_defs` for kind-based coercion) \u2014\n * the count shape's equivalent of the projection path's leftJoin + WHERE.\n * A semi-join returns each base row at most once, so the count query needs\n * no DISTINCT or GROUP BY and stays boundable by an outer LIMIT.\n *\n * Predicates satisfied by the *absence* of a value row (`eq null`,\n * `exists: false`) become `NOT EXISTS(value) OR EXISTS(null value)`,\n * matching the leftJoin form where a missing row yields a NULL expression.\n */\n private applyCfValueExistsFilter(\n q: AnyBuilder,\n opts: {\n source: ResolvedCustomFieldSource\n qualify: (column: string) => string\n tenantId: string | null\n key: string\n op: NormalizedFilter['op']\n value: unknown\n },\n ): AnyBuilder {\n return q.where((eb: any) => this.buildCfValueExistsExpression(eb, opts))\n }\n\n /**\n * Expression-returning core of `applyCfValueExistsFilter`, so a cf leaf\n * inside an OR group can compile to an EXISTS predicate on the count shape\n * instead of being dropped for lacking a `cfValueExprByKey` entry.\n */\n private buildCfValueExistsExpression(\n eb: any,\n opts: {\n source: ResolvedCustomFieldSource\n qualify: (column: string) => string\n tenantId: string | null\n key: string\n op: NormalizedFilter['op']\n value: unknown\n },\n ): any {\n const { source, qualify, tenantId, key, op, value } = opts\n const seq = this.searchAliasSeq++\n const valAlias = `cfev_${seq}`\n const defAlias = `cfed_${seq}`\n const srcAlias = `cfes_${seq}`\n const caseExpr = sql<string | null>`CASE ${sql.ref(`${defAlias}.kind`)}\n WHEN 'integer' THEN (${sql.ref(`${valAlias}.value_int`)})::text\n WHEN 'float' THEN (${sql.ref(`${valAlias}.value_float`)})::text\n WHEN 'boolean' THEN (${sql.ref(`${valAlias}.value_bool`)})::text\n WHEN 'multiline' THEN (${sql.ref(`${valAlias}.value_multiline`)})::text\n ELSE (${sql.ref(`${valAlias}.value_text`)})::text\n END`\n\n const buildSub = (eb: any): AnyBuilder => {\n let sub: AnyBuilder = eb\n .selectFrom(`custom_field_values as ${valAlias}`)\n .select(sql<number>`1`.as('one'))\n .leftJoin(`custom_field_defs as ${defAlias}`, (jb: any) =>\n jb.on(`${defAlias}.entity_id`, '=', String(source.entityId))\n .on(`${defAlias}.key`, '=', key)\n .on(`${defAlias}.is_active`, '=', true)\n .on((jeb: any) => jeb.or([\n jeb(`${defAlias}.tenant_id`, '=', tenantId),\n jeb(`${defAlias}.tenant_id`, 'is', null),\n ])))\n .where(`${valAlias}.entity_id`, '=', String(source.entityId))\n .where(`${valAlias}.field_key`, '=', key)\n .where((web: any) => web.or([\n web(`${valAlias}.tenant_id`, '=', tenantId),\n web(`${valAlias}.tenant_id`, 'is', null),\n ]))\n if (source.hop) {\n sub = sub\n .innerJoin(`${source.table} as ${srcAlias}`, (jb: any) =>\n jb.on(sql<boolean>`${sql.ref(`${valAlias}.record_id`)} = (${sql.ref(`${srcAlias}.${source.hop!.recordIdColumn}`)})::text`))\n .whereRef(`${srcAlias}.${source.hop.toField}`, '=', qualify(source.hop.fromField))\n } else {\n sub = sub.where(sql<boolean>`${sql.ref(`${valAlias}.record_id`)} = ${source.recordIdExpr}`)\n }\n return sub\n }\n\n const absenceSatisfiable = (op === 'eq' && value === null) || (op === 'exists' && !value)\n if (absenceSatisfiable) {\n return eb.or([\n eb.not(eb.exists(buildSub(eb))),\n eb.exists(buildSub(eb).where(sql<boolean>`${caseExpr} is null`)),\n ])\n }\n\n let predicate: RawBuilder<boolean> | null = null\n switch (op) {\n case 'eq':\n predicate = sql<boolean>`${caseExpr} = ${value}`\n break\n case 'ne':\n predicate = value === null\n ? sql<boolean>`${caseExpr} is not null`\n : sql<boolean>`${caseExpr} != ${value}`\n break\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const operator = sql.raw(op === 'gt' ? '>' : op === 'gte' ? '>=' : op === 'lt' ? '<' : '<=')\n predicate = sql<boolean>`${caseExpr} ${operator} ${value}`\n break\n }\n case 'in': {\n const vals = Array.isArray(value) ? value : [value]\n predicate = sql<boolean>`${caseExpr} in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`\n break\n }\n case 'nin': {\n const vals = Array.isArray(value) ? value : [value]\n predicate = sql<boolean>`${caseExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`\n break\n }\n case 'like':\n predicate = sql<boolean>`${caseExpr} like ${value}`\n break\n case 'ilike':\n predicate = sql<boolean>`${caseExpr} ilike ${value}`\n break\n case 'exists':\n predicate = sql<boolean>`${caseExpr} is not null`\n break\n default:\n // Mirrors buildColumnOpExpression's unknown-op fallback: a neutral\n // predicate, so full and count shapes drop the same leaves.\n return eb.val(true)\n }\n const captured = predicate\n return eb.exists(buildSub(eb).where(captured))\n }\n\n private buildColumnOpExpression(eb: any, column: string | RawBuilder<unknown>, op: string, value: unknown): any {\n switch (op) {\n case 'eq': return value === null ? eb(column, 'is', null) : eb(column, '=', value)\n case 'ne': return value === null ? eb(column, 'is not', null) : eb(column, '!=', value)\n case 'gt': return eb(column, '>', value)\n case 'gte': return eb(column, '>=', value)\n case 'lt': return eb(column, '<', value)\n case 'lte': return eb(column, '<=', value)\n case 'in': return eb(column, 'in', Array.isArray(value) ? value : [value])\n case 'nin': return eb(column, 'not in', Array.isArray(value) ? value : [value])\n case 'like': return eb(column, 'like', value)\n case 'ilike': return eb(column, 'ilike', value)\n case 'exists': return value ? eb(column, 'is not', null) : eb(column, 'is', null)\n default: return eb.val(true)\n }\n }\n\n private async resolveBaseColumn(table: string, field: string): Promise<string | null> {\n if (await this.columnExists(table, field)) return field\n if (field === 'organization_id' && await this.columnExists(table, 'id')) return 'id'\n return null\n }\n\n private async columnExists(table: string, column: string): Promise<boolean> {\n const key = `${table}.${column}`\n const ttlMs = resolveColumnExistsCacheTtlMs()\n const cached = columnExistsCache.get(key)\n if (cached && cached.expiresAt > Date.now()) return cached.value\n const db = this.getDb()\n const exists = await db\n .selectFrom('information_schema.columns' as any)\n .select(sql<number>`1`.as('one'))\n .where('table_name' as any, '=', table)\n .where('column_name' as any, '=', column)\n .limit(1)\n .executeTakeFirst()\n const present = !!exists\n if (ttlMs > 0) storeColumnExists(key, present, ttlMs)\n return present\n }\n\n private applySearchTokens(\n q: AnyBuilder,\n opts: {\n entity: string\n field: string\n hashes: string[]\n recordIdColumn: string\n tenantId?: string | null\n organizationScope?: { ids: string[]; includeNull: boolean } | null\n combineWith?: 'and' | 'or'\n tokens?: string[]\n }\n ): { applied: boolean; builder: AnyBuilder } {\n if (!opts.hashes.length) {\n this.logSearchDebug('search:skip-no-hashes', {\n entity: opts.entity,\n field: opts.field,\n tenantId: opts.tenantId ?? null,\n organizationScope: opts.organizationScope,\n })\n return { applied: false, builder: q }\n }\n const alias = `st_${this.searchAliasSeq++}`\n const engine = this\n this.logSearchDebug('search:apply-search-tokens', {\n entity: opts.entity,\n field: opts.field,\n alias,\n tokenCount: opts.hashes.length,\n tokens: opts.tokens,\n tenantId: opts.tenantId ?? null,\n organizationScope: opts.organizationScope,\n combineWith: opts.combineWith ?? 'and',\n })\n const buildSub = (eb: any) => {\n let sub: AnyBuilder = eb\n .selectFrom(`search_tokens as ${alias}`)\n .select(sql<number>`1`.as('one'))\n .where(`${alias}.entity_type`, '=', opts.entity)\n .where(`${alias}.field`, '=', opts.field)\n .where(sql<boolean>`${sql.ref(`${alias}.entity_id`)} = ${sql.ref(opts.recordIdColumn)}::text`)\n .where(`${alias}.token_hash`, 'in', opts.hashes)\n .groupBy([`${alias}.entity_id`, `${alias}.field`])\n .having(sql<boolean>`count(distinct ${sql.ref(`${alias}.token_hash`)}) >= ${opts.hashes.length}`)\n if (opts.tenantId !== undefined) {\n sub = sub.where(sql<boolean>`${sql.ref(`${alias}.tenant_id`)} is not distinct from ${opts.tenantId ?? null}`)\n }\n if (opts.organizationScope) {\n sub = engine.applyOrganizationScope(sub, `${alias}.organization_id`, opts.organizationScope)\n }\n return sub\n }\n const combiner = opts.combineWith === 'or' ? 'or' : 'and'\n if (combiner === 'or') {\n // When OR combining, caller expects a raw predicate to include in eb.or([...]).\n // We keep the same semantics as the previous knex orWhereExists by mutating the outer builder with a WHERE EXISTS.\n // Return the mutated builder; callers that need per-predicate control should build the sub themselves.\n const next = q.where((eb: any) => eb.or([eb.exists(buildSub(eb))]))\n return { applied: true, builder: next }\n }\n const next = q.where((eb: any) => eb.exists(buildSub(eb)))\n return { applied: true, builder: next }\n }\n\n private applyIndexDocFilter(\n q: AnyBuilder,\n opts: {\n entity: string\n field: string\n op: NormalizedFilter['op']\n value: unknown\n recordIdColumn: string\n tenantId?: string | null\n organizationScope?: { ids: string[]; includeNull: boolean } | null\n withDeleted: boolean\n searchActive: boolean\n searchConfig: ReturnType<typeof resolveSearchConfig>\n }\n ): AnyBuilder {\n if ((opts.op === 'like' || opts.op === 'ilike') && opts.searchActive && typeof opts.value === 'string') {\n const tokens = tokenizeText(String(opts.value), opts.searchConfig)\n const hashes = tokens.hashes\n if (hashes.length) {\n const result = this.applySearchTokens(q, {\n entity: opts.entity,\n field: opts.field,\n hashes,\n recordIdColumn: opts.recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: opts.organizationScope,\n tokens: tokens.tokens,\n })\n this.logSearchDebug('search:index-doc-filter', {\n entity: opts.entity,\n field: opts.field,\n tokens: tokens.tokens,\n hashes,\n applied: result.applied,\n tenantId: opts.tenantId ?? null,\n organizationScope: opts.organizationScope,\n })\n if (result.applied) return result.builder\n } else {\n this.logSearchDebug('search:index-doc-skip-empty-hashes', {\n entity: opts.entity,\n field: opts.field,\n value: opts.value,\n })\n }\n return q\n }\n\n return q.where((eb: any) => this.buildIndexDocOpExpression(eb, opts))\n }\n\n // Builds the entity_indexes EXISTS expression for a single doc-field operator,\n // shared by the regular doc-filter path (applyIndexDocFilter) and the OR-group\n // path so `$or` queries over custom-entity doc fields resolve instead of being\n // silently dropped (#3229).\n private buildIndexDocOpExpression(\n eb: any,\n opts: {\n entity: string\n field: string\n op: NormalizedFilter['op']\n value: unknown\n recordIdColumn: string\n tenantId?: string | null\n organizationScope?: { ids: string[]; includeNull: boolean } | null\n withDeleted: boolean\n }\n ): any {\n const alias = `ei_${this.searchAliasSeq++}`\n const engine = this\n return eb.exists((() => {\n let sub: AnyBuilder = eb\n .selectFrom(`entity_indexes as ${alias}`)\n .select(sql<number>`1`.as('one'))\n .where(`${alias}.entity_type`, '=', opts.entity)\n .where(sql<boolean>`${sql.ref(`${alias}.entity_id`)} = ${sql.ref(opts.recordIdColumn)}::text`)\n if (opts.tenantId !== undefined) {\n sub = sub.where(sql<boolean>`${sql.ref(`${alias}.tenant_id`)} is not distinct from ${opts.tenantId ?? null}`)\n }\n if (opts.organizationScope) {\n sub = engine.applyOrganizationScope(sub, `${alias}.organization_id`, opts.organizationScope)\n }\n if (!opts.withDeleted) {\n sub = sub.where(`${alias}.deleted_at`, 'is', null)\n }\n\n const textExpr = sql<string | null>`(${sql.ref(`${alias}.doc`)} ->> ${opts.field})`\n switch (opts.op) {\n case 'eq':\n sub = opts.value === null\n ? sub.where(sql<boolean>`${textExpr} is null`)\n : sub.where(sql<boolean>`${textExpr} = ${opts.value}`)\n break\n case 'ne':\n sub = opts.value === null\n ? sub.where(sql<boolean>`${textExpr} is not null`)\n : sub.where(sql<boolean>`${textExpr} <> ${opts.value}`)\n break\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const operator = sql.raw(opts.op === 'gt' ? '>' : opts.op === 'gte' ? '>=' : opts.op === 'lt' ? '<' : '<=')\n sub = sub.where(sql<boolean>`${textExpr} ${operator} ${opts.value}`)\n break\n }\n case 'in': {\n const vals = Array.isArray(opts.value) ? opts.value : [opts.value]\n sub = sub.where(sql<boolean>`${textExpr} in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)\n break\n }\n case 'nin': {\n const vals = Array.isArray(opts.value) ? opts.value : [opts.value]\n sub = sub.where(sql<boolean>`${textExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)\n break\n }\n case 'like':\n sub = sub.where(sql<boolean>`${textExpr} like ${opts.value}`); break\n case 'ilike':\n sub = sub.where(sql<boolean>`${textExpr} ilike ${opts.value}`); break\n case 'exists':\n sub = opts.value\n ? sub.where(sql<boolean>`${textExpr} is not null`)\n : sub.where(sql<boolean>`${textExpr} is null`)\n break\n default:\n break\n }\n return sub\n })())\n }\n\n private configureCustomFieldSources(\n q: AnyBuilder,\n baseTable: string,\n baseEntity: EntityId,\n db: AnyDb,\n opts: QueryOptions,\n qualify: (column: string) => string,\n attachJoins: boolean = true,\n ): { builder: AnyBuilder; sources: ResolvedCustomFieldSource[] } {\n const sources: ResolvedCustomFieldSource[] = [\n {\n entityId: baseEntity,\n alias: 'base',\n table: baseTable,\n recordIdExpr: sql<string>`${sql.ref(`${baseTable}.id`)}::text`,\n },\n ]\n const extras: QueryCustomFieldSource[] = opts.customFieldSources ?? []\n let next = q\n extras.forEach((srcOpt, index) => {\n const joinTable = srcOpt.table ?? resolveEntityTableName(this.em, srcOpt.entityId)\n const alias = srcOpt.alias ?? `cfs_${index}`\n const join = srcOpt.join\n if (!join) {\n throw new Error(`QueryEngine: customFieldSources entry for ${String(srcOpt.entityId)} requires a join configuration`)\n }\n const joinType: 'left' | 'inner' = (join.type ?? 'left') === 'inner' ? 'inner' : 'left'\n if (attachJoins) {\n const joinFn = joinType === 'inner' ? 'innerJoin' : 'leftJoin'\n next = (next as any)[joinFn](`${joinTable} as ${alias}`, (jb: any) =>\n jb.onRef(`${alias}.${join.toField}`, '=', qualify(join.fromField)))\n } else if (joinType === 'inner') {\n // The count projection carries no projection joins, but an inner-typed\n // source join restricts the result set \u2014 preserve that as a semi-join.\n next = next.where((eb: any) => eb.exists(\n eb\n .selectFrom(`${joinTable} as ${alias}`)\n .select(sql<number>`1`.as('one'))\n .whereRef(`${alias}.${join.toField}`, '=', qualify(join.fromField)),\n ))\n }\n const recordColumn = srcOpt.recordIdColumn ?? 'id'\n sources.push({\n entityId: srcOpt.entityId,\n alias,\n table: joinTable,\n recordIdExpr: sql<string>`${sql.ref(`${alias}.${recordColumn}`)}::text`,\n hop: { fromField: join.fromField, toField: join.toField, recordIdColumn: recordColumn, type: joinType },\n })\n })\n return { builder: next, sources }\n }\n\n private logSearchDebug(event: string, payload: Record<string, unknown>) {\n try {\n logger.debug(event, payload)\n } catch {}\n }\n\n private resolveOrganizationScope(opts: QueryOptions): { ids: string[]; includeNull: boolean } | null {\n if (opts.organizationIds !== undefined) {\n const raw = (opts.organizationIds ?? []).map((id) => (typeof id === 'string' ? id.trim() : id))\n const includeNull = raw.some((id) => id == null || id === '')\n const ids = raw.filter((id): id is string => typeof id === 'string' && id.length > 0)\n return { ids: Array.from(new Set(ids)), includeNull }\n }\n if (typeof opts.organizationId === 'string' && opts.organizationId.trim().length > 0) {\n return { ids: [opts.organizationId], includeNull: false }\n }\n return null\n }\n\n private applyOrganizationScope(q: AnyBuilder, column: string, scope: { ids: string[]; includeNull: boolean }): AnyBuilder {\n if (!scope) return q\n if (scope.ids.length === 0 && !scope.includeNull) {\n return q.where(sql<boolean>`1 = 0`)\n }\n return q.where((eb: any) => {\n const parts: any[] = []\n if (scope.ids.length > 0) parts.push(eb(column, 'in', scope.ids))\n if (scope.includeNull) parts.push(eb(column, 'is', null))\n if (parts.length === 1) return parts[0]\n return eb.or(parts)\n })\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { QueryEngine, QueryOptions, QueryResult, QueryResultMeta, EncryptedSortRowCapWarning, ListCountCapWarning, QueryCustomFieldSource, QueryExtensionsConfig, Sort } from './types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql, type RawBuilder } from 'kysely'\nimport {\n applyJoinFilters,\n normalizeFilters,\n partitionFilters,\n resolveJoins,\n type BaseFilter,\n type NormalizedFilter,\n type ResolvedJoin,\n} from './join-utils'\nimport { resolveSearchConfig } from '../search/config'\nimport {\n createSearchTokenAvailability,\n isSearchFilterOp,\n type SearchTokenAvailability,\n type SearchTokenProbeDb,\n type SearchTokenProbeQueryBuilder,\n} from '../search/availability'\nimport { tokenizeText } from '../search/tokenize'\nimport { fieldNameCandidates } from './encrypted-sort'\nimport { isTenantDataEncryptionEnabled } from '../encryption/toggles'\nimport { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from './query-extension-runner'\nimport {\n buildCustomFieldDefinitionIndexFromRows,\n resolveCfDefIndexOrgCandidates,\n type CustomFieldDefinitionRow,\n type ResolvedCustomFieldDefinitions,\n} from '../crud/custom-field-definition-index'\nimport { warnOnCiphertextLikeFallback } from './ciphertext-search-warning'\nimport { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from './encrypted-sort'\nimport { resolveListCountCap } from './count-cap'\nimport { mapWithConcurrency } from './bounded-decrypt'\nimport { parseNumberWithDefault } from '../number'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'query' })\n\nconst DECRYPT_CONCURRENCY = 8\n\ntype AnyDb = Kysely<any>\ntype AnyBuilder = any\n\nconst entityTableCache = new Map<string, string>()\n\ntype EncryptionResolver = () => {\n decryptEntityPayload?: (entityId: EntityId, payload: Record<string, unknown>, tenantId?: string | null, organizationId?: string | null) => Promise<Record<string, unknown>>\n getEncryptedFieldNames?: (entityId: EntityId, tenantId?: string | null, organizationId?: string | null, options?: { ignoreRuntimeHealth?: boolean }) => Promise<readonly string[]>\n isEnabled?: () => boolean\n} | null\n\n/**\n * Membership across name shapes: encryption maps may declare a field as `displayName` or\n * `display_name` (TenantDataEncryptionService resolves both), while query filters carry real\n * column names. Both sides expand through `fieldNameCandidates` at set-build and lookup time.\n */\nexport function isEncryptedLikeField(encrypted: ReadonlySet<string>, field: string): boolean {\n return fieldNameCandidates(field).some((candidate) => encrypted.has(candidate))\n}\n\n// The all-orgs union behind `getEncryptedFieldNames(..., organizationId: null)` is an UNCACHED\n// `encryption_maps` read. Encryption maps change on deploys, not per request, so a short TTL\n// removes the per-search round-trip without meaningfully delaying a map rollout.\nconst ENCRYPTED_LIKE_FIELDS_TTL_MS = 60_000\nconst ENCRYPTED_LIKE_FIELDS_CACHE_CAP = 500\nconst encryptedLikeFieldsCache = new Map<string, { at: number; fields: Set<string> }>()\n\nexport async function resolveEncryptedLikeFieldSet(\n read: () => Promise<readonly string[]>,\n entity: string,\n tenantId: string | null,\n): Promise<Set<string>> {\n const key = `${entity}|${tenantId ?? ''}`\n const hit = encryptedLikeFieldsCache.get(key)\n if (hit && Date.now() - hit.at < ENCRYPTED_LIKE_FIELDS_TTL_MS) return hit.fields\n const names = await read()\n const fields = new Set<string>()\n for (const name of names ?? []) {\n for (const candidate of fieldNameCandidates(String(name))) fields.add(candidate)\n }\n if (encryptedLikeFieldsCache.size >= ENCRYPTED_LIKE_FIELDS_CACHE_CAP) encryptedLikeFieldsCache.clear()\n encryptedLikeFieldsCache.set(key, { at: Date.now(), fields })\n return fields\n}\n\n/** Test-only: the TTL memo would otherwise leak state across specs. */\nexport function clearEncryptedLikeFieldsCache(): void {\n encryptedLikeFieldsCache.clear()\n}\n\n// Module-scoped on purpose: `createRequestContainer` builds a fresh `BasicQueryEngine`\n// per request, so an instance field alone re-pays the `information_schema` probe on\n// every request (#5605). Schema shape (does a table have this column?) is not\n// per-request state \u2014 unlike `tenantEncryptionService` \u2014 so sharing the answer across\n// requests is safe. One map per module instance rather than a true process singleton:\n// standalone builds can duplicate this package, which for a memo is harmless (two\n// caches, both correct), so nothing may be built on top of singleton semantics here.\n//\n// Bounded and TTL'd for two reasons. `columnExists` is reached with caller-supplied\n// field names via `resolveBaseColumn` (sort fields and base filter keys arrive raw from\n// the HTTP layer), so an unbounded map would grow monotonically on request input. And a\n// cached `false` is consumed where the tenant/organization/soft-delete predicates are\n// applied, so a schema change that adds one of those columns must not stay invisible\n// until the process restarts \u2014 a migration applied against a running `yarn dev` or\n// not-yet-recycled pods converges within the TTL instead. The TTL still removes\n// essentially all of the traffic: a hot column is probed twelve times an hour rather\n// than tens of thousands. Set OM_QUERY_COLUMN_EXISTS_CACHE_MS=0 to disable and probe\n// per request again; OM_QUERY_COLUMN_EXISTS_CACHE_MAX_ENTRIES tunes the bound.\nconst COLUMN_EXISTS_CACHE_DEFAULT_TTL_MS = 300_000\nconst COLUMN_EXISTS_CACHE_DEFAULT_MAX_ENTRIES = 10_000\nconst columnExistsCache = new Map<string, { value: boolean; expiresAt: number }>()\n\nfunction resolveColumnExistsCacheTtlMs(): number {\n return parseNumberWithDefault(process.env.OM_QUERY_COLUMN_EXISTS_CACHE_MS, COLUMN_EXISTS_CACHE_DEFAULT_TTL_MS, { integer: true, min: 0 })\n}\n\nfunction resolveColumnExistsCacheMaxEntries(): number {\n return parseNumberWithDefault(process.env.OM_QUERY_COLUMN_EXISTS_CACHE_MAX_ENTRIES, COLUMN_EXISTS_CACHE_DEFAULT_MAX_ENTRIES, { integer: true, min: 1 })\n}\n\nfunction storeColumnExists(key: string, value: boolean, ttlMs: number): void {\n const maxEntries = resolveColumnExistsCacheMaxEntries()\n if (columnExistsCache.size >= maxEntries) {\n const now = Date.now()\n for (const [entryKey, entry] of columnExistsCache) {\n if (entry.expiresAt <= now) columnExistsCache.delete(entryKey)\n }\n if (columnExistsCache.size >= maxEntries) columnExistsCache.clear()\n }\n columnExistsCache.set(key, { value, expiresAt: Date.now() + ttlMs })\n}\n\n/** Test-only: the module-scoped memo would otherwise leak state across specs. */\nexport function clearColumnExistsCache(): void {\n columnExistsCache.clear()\n}\n\n/** Test-only: entry count of the column-existence memo, for the cap regression test. */\nexport function columnExistsCacheSize(): number {\n return columnExistsCache.size\n}\n\ntype ResolvedCustomFieldSource = {\n entityId: EntityId\n alias: string\n table: string\n recordIdExpr: RawBuilder<string>\n /**\n * The base\u2192source join edge for joined sources (absent on the base source).\n * The count projection uses it to correlate cf-value EXISTS subqueries\n * without attaching the source join to the outer query.\n */\n hop?: { fromField: string; toField: string; recordIdColumn: string; type: 'left' | 'inner' }\n}\n\ntype ResultRow = Record<string, unknown>\n\n/**\n * Canonical `module:entity` entity-id shape (both segments snake_case,\n * starting with a lowercase letter). Used to validate caller-supplied entity\n * ids at security-sensitive boundaries before they reach table resolution.\n */\nexport const ENTITY_ID_PATTERN = /^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*$/\n\nexport const isValidEntityIdShape = (value: string): boolean => ENTITY_ID_PATTERN.test(value)\n\nconst pluralizeBaseName = (name: string): string => {\n if (!name) return name\n if (name.endsWith('s')) return name\n if (name.endsWith('y')) return `${name.slice(0, -1)}ies`\n return `${name}s`\n}\n\n/**\n * Accepts a module-declared `EntityExtension.table` only when it is a bare SQL\n * identifier. The value is interpolated into a join clause, so anything else is\n * ignored in favour of the derived table name.\n */\nconst PLAIN_TABLE_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/\n\nconst isPlainTableIdentifier = (value: unknown): value is string =>\n typeof value === 'string' && PLAIN_TABLE_IDENTIFIER_PATTERN.test(value)\n\nconst toPascalCase = (value: string): string => {\n return value\n .split(/[_\\s]+/)\n .filter(Boolean)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('')\n}\n\nconst candidateClassNames = (rawName: string): string[] => {\n const base = toPascalCase(rawName)\n const candidates = new Set<string>()\n if (base) candidates.add(base)\n if (base && !base.endsWith('Entity')) candidates.add(`${base}Entity`)\n return Array.from(candidates)\n}\n\n/**\n * Resolve an entity id to a table name strictly via registered MikroORM metadata.\n *\n * Unlike {@link resolveEntityTableName}, this never falls back to a pluralized\n * guess: it returns `null` when no registered entity matches. Use it for\n * security-sensitive call sites (e.g. the reindexer) that must refuse to read\n * arbitrary, attacker-chosen tables that happen to exist in the schema.\n */\nexport function resolveRegisteredEntityTableName(\n em: EntityManager | undefined,\n entity: EntityId,\n): string | null {\n const parts = String(entity || '').split(':')\n const rawName = (parts[1] && parts[1].trim().length > 0) ? parts[1] : (parts[0] || '').trim()\n const metadata = (em as any)?.getMetadata?.()\n\n if (!metadata || !rawName) return null\n\n const candidates = candidateClassNames(rawName)\n for (const candidate of candidates) {\n try {\n const meta = metadata.find?.(candidate)\n if (meta?.tableName) {\n return String(meta.tableName)\n }\n } catch {}\n }\n\n // Secondary lookup: search ORM metadata by candidate table names\n const modulePrefix = parts[0] ?? ''\n const candidateTables = [\n `${modulePrefix}_${rawName}`,\n pluralizeBaseName(rawName),\n `${modulePrefix}_${pluralizeBaseName(rawName)}`,\n ]\n try {\n const allMeta: any[] = metadata.getAll?.() ?? []\n for (const meta of allMeta) {\n if (meta?.tableName && candidateTables.includes(String(meta.tableName))) {\n return String(meta.tableName)\n }\n }\n } catch {}\n\n return null\n}\n\nexport function resolveEntityTableName(em: EntityManager | undefined, entity: EntityId): string {\n if (entityTableCache.has(entity)) {\n return entityTableCache.get(entity)!\n }\n const parts = String(entity || '').split(':')\n const rawName = (parts[1] && parts[1].trim().length > 0) ? parts[1] : (parts[0] || '').trim()\n\n const registered = resolveRegisteredEntityTableName(em, entity)\n if (registered) {\n entityTableCache.set(entity, registered)\n return registered\n }\n\n const fallback = pluralizeBaseName(rawName || '')\n logger.warn('Could not resolve entity via ORM metadata; falling back to table name \u2014 ensure the entity ID segment matches the class name convention', { entity, fallback })\n entityTableCache.set(entity, fallback)\n return fallback\n}\n\nfunction buildFilterableCustomFieldJoins(\n sources: QueryCustomFieldSource[] | undefined,\n): Array<{\n alias: string\n table?: string\n entityId: EntityId\n from: { field: string }\n to: { field: string }\n type: 'left' | 'inner'\n}> {\n if (!sources || sources.length === 0) return []\n return sources.flatMap((source, index) => {\n if (!source.join) return []\n const alias = typeof source.alias === 'string' && source.alias.trim().length > 0\n ? source.alias.trim()\n : `cfs_${index}`\n return [{\n alias,\n table: source.table,\n entityId: source.entityId,\n from: { field: source.join.fromField },\n to: { field: source.join.toField },\n type: source.join.type === 'inner' ? 'inner' : 'left',\n }]\n })\n}\n\nfunction computeCustomFieldScore(cfg: Record<string, unknown>, kind: string, entityIndex: number) {\n const listVisibleScore = cfg.listVisible === false ? 0 : 1\n const formEditableScore = cfg.formEditable === false ? 0 : 1\n const filterableScore = cfg.filterable ? 1 : 0\n const kindScore = (() => {\n switch (kind) {\n case 'dictionary': return 8\n case 'relation': return 6\n case 'select': return 4\n case 'multiline': return 3\n case 'boolean':\n case 'integer':\n case 'float': return 2\n default: return 1\n }\n })()\n const optionsBonus = Array.isArray(cfg.options) && cfg.options.length ? 2 : 0\n const dictionaryBonus = typeof cfg.dictionaryId === 'string' && (cfg.dictionaryId as string).trim().length ? 5 : 0\n const base = (listVisibleScore * 16) + (formEditableScore * 8) + (filterableScore * 4) + kindScore + optionsBonus + dictionaryBonus\n const penalty = typeof cfg.priority === 'number' ? cfg.priority : 0\n return { base, penalty, entityIndex }\n}\n\n/**\n * BasicQueryEngine \u2014 Kysely-backed fallback query engine.\n *\n * Resolves base tables via MikroORM metadata, applies tenant/organization/\n * deleted_at scoping, handles custom field (cf:*) selection and filtering,\n * and performs entity-extension joins. Used as the fallback for\n * {@link HybridQueryEngine} when the query index is unavailable or incomplete.\n */\nexport class BasicQueryEngine implements QueryEngine {\n private searchAliasSeq = 0\n private searchAvailabilityInstance: SearchTokenAvailability | null = null\n\n constructor(\n private em: EntityManager,\n private getDbFn?: () => AnyDb,\n private resolveEncryptionService?: EncryptionResolver,\n ) {}\n\n private getEncryptionService() {\n try {\n return this.resolveEncryptionService?.() ?? null\n } catch {\n return null\n }\n }\n\n private getDb(): AnyDb {\n if (this.getDbFn) return this.getDbFn()\n const emAny = this.em as any\n if (typeof emAny?.getKysely === 'function') return emAny.getKysely() as AnyDb\n throw new Error('BasicQueryEngine requires an EntityManager exposing getKysely() (MikroORM v7)')\n }\n\n private searchAvailability(): SearchTokenAvailability {\n if (!this.searchAvailabilityInstance) {\n this.searchAvailabilityInstance = createSearchTokenAvailability({\n getDb: () => this.getDb() as unknown as SearchTokenProbeDb,\n getConfig: resolveSearchConfig,\n applyOrganizationScope: (query, column, scope) => this.applyOrganizationScope(\n query as unknown as AnyBuilder,\n column,\n scope,\n ) as unknown as SearchTokenProbeQueryBuilder,\n logDebug: (event, payload) => this.logSearchDebug(event, payload),\n })\n }\n return this.searchAvailabilityInstance\n }\n\n async query<T = any>(entity: EntityId, opts: QueryOptions = {}): Promise<QueryResult<T>> {\n // --- UMES query extension: before-query pipeline ---\n const ext = opts.extensions\n let effectiveOpts = opts\n let extensionCtx: QueryExtensionContext | null = null\n const noop = { resolve: <R = unknown>(_name: string): R => { throw new Error('No DI context') } }\n\n if (ext) {\n extensionCtx = {\n entity: String(entity),\n engine: 'basic',\n tenantId: opts.tenantId ?? '',\n organizationId: opts.organizationId,\n userId: ext.userId,\n em: this.em,\n container: ext.container,\n userFeatures: ext.userFeatures,\n }\n const diCtx = ext.resolve ? { resolve: ext.resolve } : noop\n const beforeResult = await runBeforeQueryPipeline(opts, extensionCtx, diCtx)\n if (beforeResult.blocked) {\n throw new Error(beforeResult.errorMessage ?? 'Query blocked by extension subscriber')\n }\n effectiveOpts = beforeResult.query\n }\n // Strip extensions from effectiveOpts so they don't propagate to sub-queries\n const { extensions: _ext, ...coreOpts } = effectiveOpts\n opts = coreOpts\n\n // Heuristic: map '<module>:user' -> table 'users'\n const table = resolveEntityTableName(this.em, entity)\n const db = this.getDb()\n\n const qualify = (col: string) => `${table}.${col}`\n const orgScope = this.resolveOrganizationScope(opts)\n this.searchAliasSeq = 0\n // Require tenant scope for all queries\n if (!opts.tenantId) {\n throw new Error(\n 'QueryEngine: tenantId is now required for all queries (breaking change). ' +\n 'Please provide a tenantId in QueryOptions, e.g., query(entity, { tenantId: ... }). ' +\n 'See migration guide or documentation for details.'\n )\n }\n const skipAutoScope = opts.omitAutomaticTenantOrgScope === true\n\n const normalizedFilters = normalizeFilters(opts.filters)\n const resolvedJoins = resolveJoins(\n table,\n [...(opts.joins ?? []), ...buildFilterableCustomFieldJoins(opts.customFieldSources)],\n (entityId) => resolveEntityTableName(this.em, entityId as any),\n )\n const joinMap = new Map<string, ResolvedJoin>()\n const aliasTables = new Map<string, string>()\n aliasTables.set(table, table)\n aliasTables.set('base', table)\n for (const join of resolvedJoins) {\n joinMap.set(join.alias, join)\n aliasTables.set(join.alias, join.table)\n }\n const { baseFilters, joinFilters } = partitionFilters(table, normalizedFilters, joinMap)\n const cfFilters = normalizedFilters.filter((filter) => String(filter.field).startsWith('cf:'))\n // Custom-field leaves carrying an orGroup belong to an OR disjunct; applying them\n // one `.where()` at a time would AND them onto every disjunct (#5039).\n const regularCfFilters = cfFilters.filter((filter) => !filter.orGroup)\n const orGroupCfFilters = cfFilters.filter((filter) => filter.orGroup)\n const searchConfig = resolveSearchConfig()\n const searchFilters = [...baseFilters, ...cfFilters].filter((filter) => isSearchFilterOp(filter.op))\n // Callers that opt out of automatic tenant/org scoping own the full\n // visibility predicate. Search-token filtering has its own tenant/org\n // guards, so it must be disabled on this direct-query path as documented\n // by QueryOptions.omitAutomaticTenantOrgScope.\n const searchEnabled = !skipAutoScope && await this.searchAvailability().staticEnabled()\n // Probe `search_tokens` only when this query actually searches (#4723): every consumer of\n // `searchActive` sits behind a like/ilike guard, so on a plain list load the answer is never\n // read \u2014 and the probe is a `LIMIT 1` the planner can resolve as a seq scan over a large\n // `search_tokens`. The join path below already probes lazily for the same reason.\n const hasSearchTokens = searchEnabled && searchFilters.length\n ? await this.searchAvailability().hasTokens(String(entity), opts.tenantId ?? null, orgScope)\n : false\n const searchActive = searchEnabled && hasSearchTokens\n // Opt-in via OM_SEARCH_USE_ILIKE_FOR_NON_ENCRYPTED_FIELDS (default false: the pre-existing\n // rewrite-everything behavior is kept). When enabled, base-column like/ilike is rerouted\n // through search tokens ONLY for encrypted columns, where\n // ILIKE against ciphertext cannot match. On a plaintext column SQL ILIKE is exact, and the token\n // rewrite silently changes the result set: tokenization splits on non-alphanumerics and drops\n // tokens shorter than minTokenLength, so a document-number search like \"ZK 1/2026\" degrades to\n // the tokens {202, 2026} and matches every record from that year instead of the one document.\n // `ignoreRuntimeHealth` asks the on-disk question -- a column holds ciphertext even while the\n // KMS is down -- so an outage keeps encrypted columns on the token path (#4622).\n // `organizationId: null` is deliberate, not an omission: the service then unions in every\n // organization's map (`fetchAllOrganizationFieldNames`), so a field any org encrypts stays on\n // the token path -- a wider set fails safe. Passing the request's org instead would silently\n // break encrypted-column search for orgs without their own map. That union is an UNCACHED\n // `encryption_maps` read, one extra round-trip per searched list request. `null` means\n // the encryption service could not answer at all; keep the pre-existing rewrite-everything\n // behavior then, because guessing \"plaintext\" would turn encrypted-column search into an\n // ILIKE-on-ciphertext that matches nothing.\n let encryptedLikeFields: Set<string> | null = null\n if (\n searchActive &&\n searchConfig.useIlikeForNonEncryptedFields === true &&\n searchFilters.some((filter) => !String(filter.field).startsWith('cf:'))\n ) {\n try {\n const service = this.getEncryptionService()\n const readEncryptedFieldNames = service?.getEncryptedFieldNames?.bind(service)\n if (readEncryptedFieldNames) {\n encryptedLikeFields = await resolveEncryptedLikeFieldSet(\n () => readEncryptedFieldNames(\n String(entity),\n opts.tenantId ?? null,\n null,\n { ignoreRuntimeHealth: true },\n ),\n String(entity),\n opts.tenantId ?? null,\n )\n } else if (isTenantDataEncryptionEnabled()) {\n // Encryption is on but the service is unreachable (a swallowed DI failure looks\n // exactly like \"no service\"): treat the map as UNKNOWN and keep the token rewrite,\n // rather than guessing \"plaintext\" and running ILIKE against ciphertext.\n encryptedLikeFields = null\n } else {\n // Encryption disabled: nothing is ciphertext at rest, exact ILIKE is always right.\n encryptedLikeFields = new Set()\n }\n } catch (err) {\n // The fallback is safe (the old rewrite-everything behavior), but taking it silently\n // would hide that the gate has stopped working.\n logger.warn('search: encrypted-field map unavailable; keeping the token rewrite for all columns', {\n entity: String(entity),\n error: err instanceof Error ? err.message : String(err),\n })\n encryptedLikeFields = null\n }\n }\n if (searchFilters.length) {\n const fields = searchFilters.map((filter) => String(filter.field))\n this.logSearchDebug('search:init', {\n entity: String(entity),\n table,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n fields,\n searchEnabled,\n hasSearchTokens,\n searchActive,\n searchConfig: {\n enabled: searchConfig.enabled,\n minTokenLength: searchConfig.minTokenLength,\n enablePartials: searchConfig.enablePartials,\n hashAlgorithm: searchConfig.hashAlgorithm,\n blocklistedFields: searchConfig.blocklistedFields,\n },\n })\n if (!searchEnabled) {\n this.logSearchDebug('search:disabled', { entity: String(entity), table })\n } else if (!hasSearchTokens) {\n this.logSearchDebug('search:no-search-tokens', {\n entity: String(entity),\n table,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n }\n const fallbackFields = searchFilters\n .filter((filter) => !searchActive || typeof filter.value !== 'string' || tokenizeText(filter.value, searchConfig).hashes.length === 0)\n .map((filter) => String(filter.field))\n if (fallbackFields.length) {\n await warnOnCiphertextLikeFallback({\n entity: String(entity),\n fields: fallbackFields,\n tenantId: opts.tenantId ?? null,\n // `searchEnabled` also folds in the missing-table and\n // omitAutomaticTenantOrgScope cases, which are \"no usable tokens\"\n // rather than \"the operator switched search off\".\n reason: searchActive\n ? 'no-indexable-tokens'\n : searchConfig.enabled ? 'no-search-tokens' : 'search-disabled',\n service: this.getEncryptionService(),\n })\n }\n }\n for (const [alias, joinedFilters] of joinFilters) {\n const filters = joinedFilters.filter((entry) => entry.op === 'like' || entry.op === 'ilike')\n if (!filters.length) continue\n const join = joinMap.get(alias)\n if (!join?.entityId) continue\n const hasJoinedTokens = searchEnabled\n ? await this.searchAvailability().hasTokens(join.entityId, opts.tenantId ?? null, orgScope)\n : false\n const fallbackFields = filters\n .filter((filter) => !hasJoinedTokens || typeof filter.value !== 'string' || tokenizeText(filter.value, searchConfig).hashes.length === 0)\n .map((filter) => filter.column)\n if (!fallbackFields.length) continue\n await warnOnCiphertextLikeFallback({\n entity: join.entityId,\n fields: fallbackFields,\n tenantId: opts.tenantId ?? null,\n reason: hasJoinedTokens\n ? 'no-indexable-tokens'\n : searchConfig.enabled ? 'no-search-tokens' : 'search-disabled',\n service: this.getEncryptionService(),\n })\n }\n const recordIdColumn = qualify('id')\n\n const applyFilterOp = (builder: AnyBuilder, column: string | RawBuilder<unknown>, op: string, value: unknown, fieldName?: string): AnyBuilder => {\n if (\n (op === 'like' || op === 'ilike') &&\n searchActive &&\n typeof value === 'string' &&\n fieldName &&\n typeof column === 'string' &&\n // Plaintext columns keep exact SQL ILIKE -- see the encryptedLikeFields note above. cf:*\n // filters never reach this path (they are applied by the custom-field branches), so this\n // gate only decides base columns. Membership is tested across name-shape candidates --\n // encryption maps may declare `displayName` while the filter carries the column name\n // `display_name`, and a raw comparison would misread that ciphertext column as plaintext.\n (encryptedLikeFields === null || isEncryptedLikeField(encryptedLikeFields, fieldName))\n ) {\n const tokens = tokenizeText(String(value), searchConfig)\n const hashes = tokens.hashes\n if (hashes.length) {\n const result = this.applySearchTokens(builder, {\n entity: String(entity),\n field: fieldName,\n hashes,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n tokens: tokens.tokens,\n })\n this.logSearchDebug('search:filter', {\n entity: String(entity),\n field: fieldName,\n tokens: tokens.tokens,\n hashes,\n applied: result.applied,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n if (result.applied) return result.builder\n } else {\n this.logSearchDebug('search:skip-empty-hashes', {\n entity: String(entity),\n field: fieldName,\n value,\n })\n }\n }\n return this.applyColumnOp(builder, column, op, value)\n }\n\n // `eq` is accepted alongside `like`/`ilike` so that filters against\n // encrypted joined columns (whose ciphertext cannot be compared for\n // equality in SQL) can still resolve via tokenized search. Routing\n // only applies when `searchEnabled` is true AND the joined entity has\n // search tokens installed (`searchAvailable`); for non-searchable or\n // non-encrypted columns the caller still falls through to exact SQL\n // equality via `applyFilterOp`. Note that token match is approximate \u2014\n // callers needing strict equality on encrypted fields should filter on\n // the deterministic `*_hash` column instead.\n const applyJoinFilterOp = async (\n builder: AnyBuilder,\n filter: { column: string; op: string; value?: unknown },\n _qualified: string,\n join: ResolvedJoin,\n ): Promise<{ applied: boolean; builder: AnyBuilder }> => {\n if (!searchEnabled || !join.entityId) return { applied: false, builder }\n if (!['like', 'ilike'].includes(filter.op)) return { applied: false, builder }\n if (typeof filter.value !== 'string' || filter.value.trim().length === 0) return { applied: false, builder }\n\n const searchAvailable = await this.searchAvailability().hasTokens(join.entityId, opts.tenantId ?? null, orgScope)\n if (!searchAvailable) return { applied: false, builder }\n\n const tokens = tokenizeText(String(filter.value), searchConfig)\n if (!tokens.hashes.length) return { applied: false, builder }\n\n const result = this.applySearchTokens(builder, {\n entity: join.entityId,\n field: filter.column,\n hashes: tokens.hashes,\n recordIdColumn: `${join.alias}.id`,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n tokens: tokens.tokens,\n })\n return { applied: result.applied, builder: result.builder }\n }\n\n const regularBaseFilters = baseFilters.filter((f) => !f.orGroup)\n const orGroupFilters = baseFilters.filter((f) => f.orGroup)\n\n const applyAliasScopes = async (builder: AnyBuilder, aliasName: string): Promise<AnyBuilder> => {\n const targetTable = aliasTables.get(aliasName)\n if (!targetTable) return builder\n let next = builder\n if (!skipAutoScope && orgScope && await this.columnExists(targetTable, 'organization_id')) {\n next = this.applyOrganizationScope(next, `${aliasName}.organization_id`, orgScope)\n }\n if (!skipAutoScope && opts.tenantId && await this.columnExists(targetTable, 'tenant_id')) {\n next = next.where(`${aliasName}.tenant_id`, '=', opts.tenantId)\n }\n return next\n }\n\n const fallbackOrgId =\n opts.organizationId\n ?? (Array.isArray(opts.organizationIds) && opts.organizationIds.length === 1 ? opts.organizationIds[0] : null)\n const encryptionService = this.getEncryptionService()\n const resolvedSorts: Sort[] = []\n for (const s of opts.sort || []) {\n if (s.field.startsWith('cf:')) {\n resolvedSorts.push(s)\n } else {\n const column = await this.resolveBaseColumn(table, s.field)\n if (column) resolvedSorts.push({ ...s, field: column })\n }\n }\n const encryptedSortFields = await resolveEncryptedSortFields(\n encryptionService,\n entity,\n resolvedSorts.filter((sort) => !sort.field.startsWith('cf:')).map((sort) => sort.field),\n opts.tenantId ?? null,\n fallbackOrgId,\n )\n const requiresPlaintextSort = encryptedSortFields.size > 0\n\n const tenantId = opts.tenantId\n const sanitize = (s: string) => s.replace(/[^a-zA-Z0-9_]/g, '_')\n\n type BuiltQuery = {\n builder: AnyBuilder\n hasJoinedAggregates: boolean\n cfJsonAliases: Set<string>\n cfMultiAliasByAlias: Map<string, string>\n resolvedCustomFieldDefinitions: ResolvedCustomFieldDefinitions | undefined\n }\n\n // Builds the fully-scoped query from a fresh root. `projection: 'full'` reproduces\n // today's complete selection (base fields + CF projections + extension joins).\n // `projection: 'sortKeys'` selects only `id` + the sort columns \u2014 the slim phase-1\n // candidate scan used when `requiresPlaintextSort`. `projection: 'count'` carries\n // scope + filters only: projection joins (CF defs/values, extensions) are omitted\n // and cf filters are expressed as correlated EXISTS semi-joins, so nothing can\n // multiply base rows and a LIMIT above the query is an enforceable bound.\n // Re-running the WHERE/JOIN logic per projection is cheap: every `columnExists`\n // check is memoized on the module-scoped `columnExistsCache`, so later passes\n // hit no extra DB calls.\n const buildQuery = async (projection: 'full' | 'sortKeys' | 'count'): Promise<BuiltQuery> => {\n const isSortKeysProjection = projection === 'sortKeys'\n const isCountProjection = projection === 'count'\n let q: AnyBuilder = db.selectFrom(table as any)\n\n // Tenant/org/soft-delete scope\n if (!skipAutoScope && orgScope && await this.columnExists(table, 'organization_id')) {\n q = this.applyOrganizationScope(q, qualify('organization_id'), orgScope)\n }\n if (!skipAutoScope && await this.columnExists(table, 'tenant_id')) {\n q = q.where(qualify('tenant_id'), '=', opts.tenantId)\n }\n if (!opts.withDeleted && await this.columnExists(table, 'deleted_at')) {\n q = q.where(qualify('deleted_at'), 'is', null)\n }\n\n for (const filter of regularBaseFilters) {\n const fieldName = String(filter.field)\n let qualified = filter.qualified ?? null\n if (!qualified) {\n const column = await this.resolveBaseColumn(table, fieldName)\n if (!column) {\n q = this.applyIndexDocFilter(q, {\n entity: String(entity),\n field: fieldName,\n op: filter.op,\n value: filter.value,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n withDeleted: opts.withDeleted === true,\n searchActive,\n searchConfig,\n })\n continue\n }\n qualified = qualify(column)\n }\n q = applyFilterOp(q, qualified, filter.op, filter.value, fieldName)\n }\n\n // OR-grouped filters: AND within each group (one $or disjunct), OR between groups.\n // Resolution happens here (it needs async column lookups); the WHERE itself is\n // applied further down, once the cf:* value expressions exist \u2014 an OR group may\n // contain custom-field leaves whose SQL is only available then (#5039).\n type ResolvedOrClause =\n | { kind: 'column'; qualified: string; op: NormalizedFilter['op']; value: unknown }\n | { kind: 'doc'; field: string; op: NormalizedFilter['op']; value: unknown }\n | { kind: 'cf'; key: string; op: NormalizedFilter['op']; value: unknown }\n const resolvedGroupFilters: ResolvedOrClause[][] = []\n if (orGroupFilters.length > 0 || orGroupCfFilters.length > 0) {\n const groups = new Map<string, NormalizedFilter[]>()\n for (const f of [...orGroupFilters, ...orGroupCfFilters]) {\n const group = groups.get(f.orGroup!) ?? []\n group.push(f as NormalizedFilter)\n groups.set(f.orGroup!, group)\n }\n for (const [, groupFilters] of groups) {\n const resolved: ResolvedOrClause[] = []\n for (const filter of groupFilters) {\n const field = String(filter.field)\n if (field.startsWith('cf:')) {\n resolved.push({ kind: 'cf', key: field.slice(3), op: filter.op, value: filter.value })\n continue\n }\n const column = await this.resolveBaseColumn(table, field)\n if (column) {\n resolved.push({ kind: 'column', qualified: qualify(column), op: filter.op, value: filter.value })\n } else {\n // Field is not a base column \u2014 for custom-entity records it lives in\n // entity_indexes.doc. Build an EXISTS sub-filter so `$or` searches\n // across doc fields resolve instead of being silently dropped (#3229).\n resolved.push({ kind: 'doc', field, op: filter.op, value: filter.value })\n }\n }\n if (resolved.length > 0) resolvedGroupFilters.push(resolved)\n }\n }\n\n q = await applyJoinFilters({\n db,\n baseTable: table,\n builder: q,\n joinMap,\n joinFilters,\n aliasTables,\n qualifyBase: (column) => qualify(column),\n applyAliasScope: (builder, alias) => applyAliasScopes(builder, alias),\n applyFilterOp: (builder, column, op, value) => applyFilterOp(builder, column, op, value),\n applyJoinFilterOp,\n columnExists: (tbl, column) => this.columnExists(tbl, column),\n })\n\n // Selection (base columns only here; cf:* handled later)\n if (isCountProjection) {\n // The caller owns the count query's SELECT (a constant inside the\n // bounded subquery, or the aggregate itself when the cap is off).\n } else if (isSortKeysProjection) {\n q = q.select(sql.ref(qualify('id')).as('id'))\n if (await this.columnExists(table, 'tenant_id')) {\n q = q.select(sql.ref(qualify('tenant_id')).as('tenant_id'))\n }\n if (await this.columnExists(table, 'organization_id')) {\n q = q.select(sql.ref(qualify('organization_id')).as('organization_id'))\n }\n for (const s of resolvedSorts) {\n if (!s.field.startsWith('cf:')) q = q.select(sql.ref(qualify(s.field)).as(s.field))\n }\n } else if (opts.fields && opts.fields.length) {\n const cols = new Set(opts.fields.filter((f) => !f.startsWith('cf:')))\n if (requiresPlaintextSort) {\n for (const field of encryptedSortFields) cols.add(field)\n }\n for (const c of cols) {\n // Qualify and alias to base names to avoid ambiguity\n q = q.select(sql.ref(qualify(c)).as(c))\n }\n } else {\n // Default to selecting only base table columns to avoid ambiguity when joining\n q = q.select(sql`${sql.ref(table)}.*`.as('__all'))\n }\n\n // Resolve which custom fields to include\n const cfSourcesResult = this.configureCustomFieldSources(q, table, entity, db, opts, qualify, !isCountProjection)\n q = cfSourcesResult.builder\n const cfSources = cfSourcesResult.sources\n const entityIdToSource = new Map<string, ResolvedCustomFieldSource>()\n for (const source of cfSources) {\n entityIdToSource.set(String(source.entityId), source)\n }\n const requestedCustomFieldKeys = (projection === 'full' && Array.isArray(opts.includeCustomFields))\n ? opts.includeCustomFields.map((key) => String(key))\n : []\n const cfKeys = new Set<string>()\n const keySource = new Map<string, ResolvedCustomFieldSource>()\n // Custom-field definition index threaded onto the result so the CRUD factory\n // can decorate list rows without reloading definitions from the DB (#2133).\n // Output-only \u2014 never resolved for the slim sortKeys projection.\n let resolvedCustomFieldDefinitions: ResolvedCustomFieldDefinitions | undefined\n // Explicit in fields/filters\n if (projection === 'full') {\n for (const f of (opts.fields || [])) {\n if (typeof f === 'string' && f.startsWith('cf:')) cfKeys.add(f.slice(3))\n }\n }\n for (const f of cfFilters) {\n if (typeof f.field === 'string' && f.field.startsWith('cf:')) cfKeys.add(f.field.slice(3))\n }\n // A `cf:` sort needs the same joins and value expression as a `cf:` field or\n // filter: the sort loop below orders by an aggregated alias that only exists\n // once the key reached `cfKeys`. Without this, a sort-only custom-field query\n // emitted `ORDER BY \"cf_<key>\"` against a column it never selected (#5521).\n // The count shape never sorts, so it stays out of this.\n if (!isCountProjection) {\n for (const s of (opts.sort || [])) {\n if (typeof s.field === 'string' && s.field.startsWith('cf:')) cfKeys.add(s.field.slice(3))\n }\n }\n if (projection === 'full' && opts.includeCustomFields === true) {\n if (entityIdToSource.size > 0) {\n const entityIdList = Array.from(entityIdToSource.keys())\n const entityOrder = new Map<string, number>()\n entityIdList.forEach((id, idx) => entityOrder.set(id, idx))\n const rows = await db\n .selectFrom('custom_field_defs' as any)\n .select([\n 'key' as any,\n 'entity_id' as any,\n 'config_json' as any,\n 'kind' as any,\n 'organization_id' as any,\n 'tenant_id' as any,\n 'updated_at' as any,\n 'deleted_at' as any,\n ])\n .where('entity_id' as any, 'in', entityIdList)\n .where('is_active' as any, '=', true)\n .where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n .execute() as Array<{\n key: string\n entity_id: string\n config_json: unknown\n kind: string\n organization_id: string | null\n tenant_id: string | null\n updated_at: Date | string | number | null\n deleted_at: Date | string | number | null\n }>\n // Build the decoration index from the same rows, scoped exactly like the\n // factory's loadCustomFieldDefinitionIndex (tenant + is_active already\n // applied in SQL; org + soft-delete applied in the shared builder).\n const orgCandidates = resolveCfDefIndexOrgCandidates(opts.organizationIds, opts.organizationId ?? null)\n const definitionRows: CustomFieldDefinitionRow[] = rows.map((row) => ({\n key: String(row.key),\n entityId: String(row.entity_id),\n kind: row.kind == null ? null : String(row.kind),\n configJson: row.config_json,\n organizationId: row.organization_id == null ? null : String(row.organization_id),\n tenantId: row.tenant_id == null ? null : String(row.tenant_id),\n deletedAt: row.deleted_at ?? null,\n updatedAt: row.updated_at ?? null,\n }))\n resolvedCustomFieldDefinitions = {\n index: buildCustomFieldDefinitionIndexFromRows(definitionRows, { organizationIds: orgCandidates }),\n entityIds: entityIdList,\n tenantId: tenantId ?? null,\n organizationIds: orgCandidates,\n }\n type ScoredCustomFieldRow = {\n key: string\n entityId: string\n kind: string\n config: Record<string, unknown>\n }\n const sorted: ScoredCustomFieldRow[] = rows.map((row) => {\n const raw = row.config_json\n let cfg: Record<string, any> = {}\n if (raw && typeof raw === 'string') {\n try { cfg = JSON.parse(raw) } catch { cfg = {} }\n } else if (raw && typeof raw === 'object') {\n cfg = raw as Record<string, any>\n }\n return {\n key: String(row.key),\n entityId: String(row.entity_id),\n kind: String(row.kind || ''),\n config: cfg,\n }\n })\n sorted.sort((a, b) => {\n const ai = entityOrder.get(a.entityId) ?? Number.MAX_SAFE_INTEGER\n const bi = entityOrder.get(b.entityId) ?? Number.MAX_SAFE_INTEGER\n if (ai !== bi) return ai - bi\n return a.key.localeCompare(b.key)\n })\n const selectedSources = new Map<string, { source: ResolvedCustomFieldSource; score: number; penalty: number; entityIndex: number }>()\n for (const row of sorted) {\n const source = entityIdToSource.get(row.entityId)\n if (!source) continue\n const cfg = row.config || {}\n const entityIndex = entityOrder.get(row.entityId) ?? Number.MAX_SAFE_INTEGER\n const scores = computeCustomFieldScore(cfg, row.kind, entityIndex)\n const existing = selectedSources.get(row.key)\n if (!existing || scores.base > existing.score || (scores.base === existing.score && (scores.penalty < existing.penalty || (scores.penalty === existing.penalty && scores.entityIndex < existing.entityIndex)))) {\n selectedSources.set(row.key, { source, score: scores.base, penalty: scores.penalty, entityIndex: scores.entityIndex })\n }\n cfKeys.add(row.key)\n }\n for (const [key, entry] of selectedSources.entries()) {\n keySource.set(key, entry.source)\n }\n }\n } else if (!isSortKeysProjection && requestedCustomFieldKeys.length > 0) {\n for (const key of requestedCustomFieldKeys) cfKeys.add(key)\n }\n const unresolvedKeys = Array.from(cfKeys).filter((key) => !keySource.has(key))\n if (unresolvedKeys.length > 0 && entityIdToSource.size > 0) {\n const rows = await db\n .selectFrom('custom_field_defs' as any)\n .select(['key' as any, 'entity_id' as any])\n .where('entity_id' as any, 'in', Array.from(entityIdToSource.keys()))\n .where('key' as any, 'in', unresolvedKeys)\n .where('is_active' as any, '=', true)\n .where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n .execute() as Array<{ key: string; entity_id: string }>\n for (const row of rows) {\n const source = entityIdToSource.get(String(row.entity_id))\n if (!source) continue\n if (!keySource.has(row.key)) keySource.set(row.key, source)\n }\n }\n\n const cfValueExprByKey: Record<string, RawBuilder<string | null>> = {}\n const cfSelectedAliases: string[] = []\n const cfJsonAliases = new Set<string>()\n const cfMultiAliasByAlias = new Map<string, string>()\n for (const key of cfKeys) {\n const source = keySource.get(key)\n if (!source) continue\n // The count shape never joins defs/values \u2014 cf filters are applied as\n // correlated EXISTS semi-joins below, so no join can multiply base rows.\n if (isCountProjection) continue\n const entityIdForKey = source.entityId\n const recordIdExpr = source.recordIdExpr\n const sourceAliasSafe = sanitize(source.alias || 'src')\n const keyAliasSafe = sanitize(key)\n const defAlias = `cfd_${sourceAliasSafe}_${keyAliasSafe}`\n const valAlias = `cfv_${sourceAliasSafe}_${keyAliasSafe}`\n // Join definitions for kind resolution\n q = q.leftJoin(`custom_field_defs as ${defAlias}` as any, (jb: any) =>\n jb.on(`${defAlias}.entity_id`, '=', String(entityIdForKey))\n .on(`${defAlias}.key`, '=', key)\n .on(`${defAlias}.is_active`, '=', true)\n .on((eb: any) => eb.or([\n eb(`${defAlias}.tenant_id`, '=', tenantId),\n eb(`${defAlias}.tenant_id`, 'is', null),\n ]))\n )\n // Join values with record match\n q = q.leftJoin(`custom_field_values as ${valAlias}` as any, (jb: any) =>\n jb.on(`${valAlias}.entity_id`, '=', String(entityIdForKey))\n .on(`${valAlias}.field_key`, '=', key)\n .onRef(`${valAlias}.record_id`, '=', recordIdExpr as any)\n .on((eb: any) => eb.or([\n eb(`${valAlias}.tenant_id`, '=', tenantId),\n eb(`${valAlias}.tenant_id`, 'is', null),\n ]))\n )\n // Force a common SQL type across branches to avoid Postgres CASE type conflicts\n const caseExpr = sql<string | null>`CASE ${sql.ref(`${defAlias}.kind`)}\n WHEN 'integer' THEN (${sql.ref(`${valAlias}.value_int`)})::text\n WHEN 'float' THEN (${sql.ref(`${valAlias}.value_float`)})::text\n WHEN 'boolean' THEN (${sql.ref(`${valAlias}.value_bool`)})::text\n WHEN 'multiline' THEN (${sql.ref(`${valAlias}.value_multiline`)})::text\n ELSE (${sql.ref(`${valAlias}.value_text`)})::text\n END`\n cfValueExprByKey[key] = caseExpr\n const alias = sanitize(`cf:${key}`)\n // Project as aggregated to avoid duplicates when multi values exist\n if (!isSortKeysProjection && ((opts.fields || []).includes(`cf:${key}`) || opts.includeCustomFields === true || (requestedCustomFieldKeys.length > 0 && requestedCustomFieldKeys.includes(key)))) {\n const multiAlias = `${alias}__is_multi`\n const isMultiExpr = sql<boolean>`bool_or(coalesce((${sql.ref(`${defAlias}.config_json`)}->>'multi')::boolean, false))`\n const aggregatedArray = sql<unknown>`array_remove(array_agg(DISTINCT ${caseExpr}), NULL)`\n const projExpr = sql<unknown>`CASE WHEN ${isMultiExpr}\n THEN to_jsonb(${aggregatedArray})\n ELSE to_jsonb(max(${caseExpr}))\n END`\n q = q.select(projExpr.as(alias))\n q = q.select(isMultiExpr.as(multiAlias))\n cfSelectedAliases.push(alias)\n cfJsonAliases.add(alias)\n cfMultiAliasByAlias.set(alias, multiAlias)\n }\n }\n\n // Apply cf:* filters (on raw expressions; as EXISTS semi-joins for the count\n // shape). OR-grouped ones are excluded here and combined with their\n // disjunct's other leaves right below.\n for (const f of regularCfFilters) {\n if (!f.field.startsWith('cf:')) continue\n const key = f.field.slice(3)\n const filterSource = keySource.get(key)\n if (!filterSource) continue\n const expr = cfValueExprByKey[key]\n if (!isCountProjection && !expr) continue\n if ((f.op === 'like' || f.op === 'ilike') && searchActive && typeof f.value === 'string') {\n const tokens = tokenizeText(String(f.value), searchConfig)\n const hashes = tokens.hashes\n if (hashes.length) {\n const result = this.applySearchTokens(q, {\n entity: String(entity),\n field: f.field,\n hashes,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n tokens: tokens.tokens,\n })\n this.logSearchDebug('search:cf-filter', {\n entity: String(entity),\n field: f.field,\n tokens: tokens.tokens,\n hashes,\n applied: result.applied,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n if (result.applied) {\n q = result.builder\n continue\n }\n } else {\n this.logSearchDebug('search:cf-skip-empty-hashes', {\n entity: String(entity),\n field: f.field,\n value: f.value,\n })\n }\n }\n if (isCountProjection) {\n q = this.applyCfValueExistsFilter(q, {\n source: filterSource,\n qualify,\n tenantId: tenantId ?? null,\n key,\n op: f.op,\n value: f.value,\n })\n continue\n }\n q = this.applyColumnOp(q, expr, f.op, f.value)\n }\n\n // OR groups are applied here, after the cf:* value expressions exist, so a\n // disjunct mixing base/doc and custom-field leaves is united rather than\n // intersected. A cf leaf whose key resolved no value expression yields no\n // predicate and is dropped; a disjunct left empty by that is dropped too,\n // because an empty AND would read as TRUE and widen the result.\n //\n // Known limitation, shared with the `doc` clause kind above: a leaf inside an OR\n // group compares against the stored value directly and does not route `like` /\n // `ilike` through the search-token index the way the ungrouped path does. On a\n // field covered by an encryption map such a leaf therefore compares against\n // ciphertext and will not match.\n //\n // The count shape never populates cfValueExprByKey (it joins no cf tables), so\n // its applicability test is key resolution itself \u2014 the same condition that\n // gates the full shape's expression map \u2014 and a cf leaf compiles to a\n // correlated EXISTS instead of a value-expression comparison. Dropping it\n // instead would narrow the OR and undercount relative to the display query.\n const cfLeafApplicable = (key: string): boolean =>\n isCountProjection ? keySource.has(key) : Boolean(cfValueExprByKey[key])\n const applicableGroupFilters = resolvedGroupFilters\n .map((group) => group.filter((rf) => rf.kind !== 'cf' || cfLeafApplicable(rf.key)))\n .filter((group) => group.length > 0)\n if (applicableGroupFilters.length > 0) {\n q = q.where((eb: any) => {\n const disjuncts = applicableGroupFilters.map((group) => {\n const parts = group.map((rf) => {\n if (rf.kind === 'column') return this.buildColumnOpExpression(eb, rf.qualified, rf.op, rf.value)\n if (rf.kind === 'cf') {\n if (isCountProjection) {\n return this.buildCfValueExistsExpression(eb, {\n source: keySource.get(rf.key)!,\n qualify,\n tenantId: tenantId ?? null,\n key: rf.key,\n op: rf.op,\n value: rf.value,\n })\n }\n return this.buildColumnOpExpression(eb, cfValueExprByKey[rf.key], rf.op, rf.value)\n }\n return this.buildIndexDocOpExpression(eb, {\n entity: String(entity),\n field: rf.field,\n op: rf.op,\n value: rf.value,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n withDeleted: opts.withDeleted === true,\n })\n })\n return parts.length === 1 ? parts[0] : eb.and(parts)\n })\n return disjuncts.length === 1 ? disjuncts[0] : eb.or(disjuncts)\n })\n }\n\n // Entity extensions joins (no selection yet; enables future filters/projections).\n // Projection-only, so the count shape omits them.\n if (opts.includeExtensions && !isCountProjection) {\n const { getModules } = await import('@open-mercato/shared/lib/i18n/server')\n const allMods = getModules() as any[]\n const allExts = allMods.flatMap((m) => (m as any).entityExtensions || [])\n const exts = allExts.filter((e: any) => e.base === entity)\n const chosen = Array.isArray(opts.includeExtensions)\n ? exts.filter((e: any) => (opts.includeExtensions as string[]).includes(e.extension))\n : exts\n for (const e of chosen) {\n const [, extName] = (e.extension as string).split(':')\n // Uses the SAME derivation as every other table-name fallback in this file\n // (`pluralizeBaseName`, also called at the resolveEntityTableName sites above)\n // rather than a separate inline one. The inline version handled only `+s`, so\n // `example_customer_priority` derived `example_customer_prioritys` against the\n // real `example_customer_priorities`. Behaviour is unchanged for every name that\n // does not end in `y`; `table` below remains the escape hatch for irregular\n // plurals no guesser can win (`person` \u2192 `people`).\n const derivedTable = pluralizeBaseName(extName)\n const extTable = isPlainTableIdentifier(e.table) ? e.table : derivedTable\n const alias = `ext_${sanitize(extName)}`\n q = q.leftJoin(`${extTable} as ${alias}` as any, (jb: any) =>\n jb.onRef(`${alias}.${e.join.extensionKey}`, '=', `${table}.${e.join.baseKey}`)\n )\n }\n }\n\n // Sorting: base fields and cf:* (use aggregated alias for cf)\n for (const s of isCountProjection ? [] : resolvedSorts) {\n if (s.field.startsWith('cf:')) {\n const key = s.field.slice(3)\n const alias = sanitize(`cf:${key}`)\n // Ensure included in projection to sort by\n if (!cfSelectedAliases.includes(alias)) {\n const expr = cfValueExprByKey[key]\n if (expr) {\n q = q.select(sql<string | null>`max(${expr})`.as(alias))\n cfSelectedAliases.push(alias)\n }\n }\n // Only order by an alias the projection actually carries. A key that\n // resolved to no expression is dropped rather than emitted as an\n // unselected alias, which is what the base-column branch above already\n // does when `resolveBaseColumn` returns null.\n if (!requiresPlaintextSort && cfSelectedAliases.includes(alias)) {\n q = q.orderBy(alias, (s.dir ?? 'asc') as any)\n }\n } else {\n if (!requiresPlaintextSort) q = q.orderBy(qualify(s.field), (s.dir ?? 'asc') as any)\n }\n }\n\n // Deduplicate if we joined CFs or extensions by grouping on base id. The count\n // shape has neither, and must stay barrier-free for its LIMIT to bind.\n const hasJoinedAggregates = !isCountProjection && (\n (opts.includeExtensions && (Array.isArray(opts.includeExtensions) ? (opts.includeExtensions.length > 0) : true)) ||\n Object.keys(cfValueExprByKey).length > 0\n )\n if (hasJoinedAggregates) {\n q = q.groupBy(`${table}.id`)\n }\n\n return { builder: q, hasJoinedAggregates, cfJsonAliases, cfMultiAliasByAlias, resolvedCustomFieldDefinitions }\n }\n\n // Pagination\n const page = opts.page?.page ?? 1\n const pageSize = opts.page?.pageSize ?? 20\n\n const {\n builder: qFull,\n hasJoinedAggregates,\n cfJsonAliases,\n cfMultiAliasByAlias,\n resolvedCustomFieldDefinitions,\n } = await buildQuery('full')\n\n // The count is built independently of the display query (the `'count'`\n // projection): scope + filters only, cf filters as correlated EXISTS\n // semi-joins, no projection joins. Nothing can multiply base rows, so\n // `count(*)` needs no DISTINCT (completing #2227) and \u2014 when the cap is\n // active \u2014 the LIMIT sits on a row-producing inner query with no\n // aggregate/sort barrier below it, so it actually bounds the scan.\n const countCap = resolveListCountCap()\n const { builder: countShape } = await buildQuery('count')\n let total: number\n let listCountCapWarning: ListCountCapWarning | undefined\n if (countCap !== null) {\n const probe = countShape.select(sql<number>`1`.as('one')).limit(countCap + 1)\n const countRow = await db\n .selectFrom(probe.as('om_count_probe') as any)\n .select(sql<string>`count(*)`.as('count'))\n .executeTakeFirst() as { count: unknown } | undefined\n const probed = Number((countRow as any)?.count ?? 0)\n if (probed > countCap) {\n total = countCap\n listCountCapWarning = { entity, cap: countCap }\n } else {\n total = probed\n }\n } else {\n const countRow = await countShape\n .select(sql<string>`count(*)`.as('count'))\n .executeTakeFirst() as { count: unknown } | undefined\n total = Number((countRow as any)?.count ?? 0)\n }\n\n const svc = encryptionService\n const decryptPayload =\n svc?.decryptEntityPayload?.bind(svc) as\n | ((\n entityId: EntityId,\n payload: Record<string, unknown>,\n tenantId: string | null,\n organizationId: string | null,\n ) => Promise<Record<string, unknown>>)\n | null\n\n const decryptRow = async (item: ResultRow): Promise<ResultRow> => {\n if (!decryptPayload) return item\n try {\n const decrypted = await decryptPayload(\n entity,\n item,\n (item?.tenant_id ?? item?.tenantId ?? opts.tenantId ?? null) as string | null,\n (item?.organization_id ?? item?.organizationId ?? fallbackOrgId ?? null) as string | null,\n )\n return { ...item, ...decrypted }\n } catch (err) {\n logger.error('Error decrypting entity payload', { err })\n return item\n }\n }\n\n const normalizeCfJsonAliases = (rows: ResultRow[]) => {\n if (cfJsonAliases.size === 0) return\n for (const row of rows) {\n for (const alias of cfJsonAliases) {\n const multiAlias = cfMultiAliasByAlias.get(alias)\n const isMulti = multiAlias ? Boolean(row[multiAlias]) : false\n let raw = row[alias]\n if (typeof raw === 'string') {\n try { raw = JSON.parse(raw) } catch { /* ignore malformed json */ }\n }\n if (isMulti) {\n if (raw == null) row[alias] = []\n else if (Array.isArray(raw)) row[alias] = raw\n else row[alias] = [raw]\n } else {\n if (Array.isArray(raw)) row[alias] = raw.length > 0 ? raw[0] : null\n else row[alias] = raw\n }\n if (multiAlias) delete row[multiAlias]\n }\n }\n }\n\n let pagedItems: ResultRow[]\n let encryptedSortRowCapWarning: EncryptedSortRowCapWarning | undefined\n\n if (requiresPlaintextSort) {\n // Phase 1: slim id+sort-columns candidate scan, bounded-concurrency decrypt,\n // in-memory sort over the full candidate set, then slice to the page's ids.\n const cap = resolveEncryptedSortMaxRows()\n let qSort = (await buildQuery('sortKeys')).builder\n if (cap !== null) {\n // Probe one row past the cap: truncation is detected from the candidate\n // scan itself, not by comparing against `total` \u2014 which may itself be\n // capped (`OM_LIST_COUNT_CAP`) and would then never exceed the sort cap.\n qSort = qSort.limit(cap + 1).orderBy(qualify('id'), 'asc' as any)\n }\n const candidateRowsRaw = await qSort.execute() as ResultRow[]\n const sortTruncated = cap !== null && candidateRowsRaw.length > cap\n const candidateRows = sortTruncated && cap !== null ? candidateRowsRaw.slice(0, cap) : candidateRowsRaw\n const decryptedCandidates = decryptPayload\n ? await mapWithConcurrency(candidateRows, DECRYPT_CONCURRENCY, decryptRow)\n : candidateRows\n const orderedCandidates = sortRowsInMemory(decryptedCandidates, resolvedSorts)\n const pageIds = orderedCandidates\n .slice((page - 1) * pageSize, page * pageSize)\n .map((row) => row.id)\n\n if (sortTruncated && cap !== null) {\n encryptedSortRowCapWarning = {\n entity,\n sortFields: resolvedSorts.map((s) => s.field),\n maxRows: cap,\n totalMatched: total,\n }\n }\n\n // Phase 2: fetch + decrypt full rows for just the page's ids, then reassemble\n // in phase-1's order (SQL `WHERE id IN (...)` order is unspecified \u2014 never\n // trust it for ordering).\n if (pageIds.length === 0) {\n pagedItems = []\n } else {\n const pageRows = await qFull.where(qualify('id'), 'in', pageIds).execute() as ResultRow[]\n normalizeCfJsonAliases(pageRows)\n const decryptedPageRows = decryptPayload\n ? await mapWithConcurrency(pageRows, DECRYPT_CONCURRENCY, decryptRow)\n : pageRows\n const byId = new Map(decryptedPageRows.map((row) => [String(row.id), row]))\n pagedItems = pageIds\n .map((id) => byId.get(String(id)))\n .filter((row): row is ResultRow => row != null)\n }\n } else {\n const dataQuery = qFull.limit(pageSize).offset((page - 1) * pageSize)\n const items = await dataQuery.execute() as ResultRow[]\n normalizeCfJsonAliases(items)\n pagedItems = decryptPayload\n ? await mapWithConcurrency(items, DECRYPT_CONCURRENCY, decryptRow)\n : items\n }\n\n let queryResult: QueryResult<T> = { items: pagedItems as unknown as T[], page, pageSize, total }\n\n if (encryptedSortRowCapWarning || listCountCapWarning) {\n const meta: QueryResultMeta = {}\n if (encryptedSortRowCapWarning) meta.encryptedSortRowCapWarning = encryptedSortRowCapWarning\n if (listCountCapWarning) meta.listCountCapWarning = listCountCapWarning\n queryResult.meta = meta\n }\n\n // --- UMES query extension: after-query pipeline ---\n if (ext && extensionCtx) {\n const diCtx = ext.resolve ? { resolve: ext.resolve } : noop\n queryResult = await runAfterQueryPipeline(\n queryResult as QueryResult<Record<string, unknown>>,\n opts,\n extensionCtx,\n diCtx,\n ) as QueryResult<T>\n }\n\n // Attach after the extension pipeline so the field always survives even if a\n // subscriber replaces the whole result object.\n if (resolvedCustomFieldDefinitions) {\n queryResult.customFieldDefinitions = resolvedCustomFieldDefinitions\n }\n\n return queryResult\n }\n\n private applyColumnOp(builder: AnyBuilder, column: string | RawBuilder<unknown>, op: string, value: unknown): AnyBuilder {\n switch (op) {\n case 'eq':\n return value === null\n ? builder.where(column as any, 'is', null)\n : builder.where(column as any, '=', value as any)\n case 'ne':\n return value === null\n ? builder.where(column as any, 'is not', null)\n : builder.where(column as any, '!=', value as any)\n case 'gt':\n return builder.where(column as any, '>', value as any)\n case 'gte':\n return builder.where(column as any, '>=', value as any)\n case 'lt':\n return builder.where(column as any, '<', value as any)\n case 'lte':\n return builder.where(column as any, '<=', value as any)\n case 'in':\n return builder.where(column as any, 'in', Array.isArray(value) ? value : [value])\n case 'nin':\n return builder.where(column as any, 'not in', Array.isArray(value) ? value : [value])\n case 'like':\n return builder.where(column as any, 'like', value as any)\n case 'ilike':\n return builder.where(column as any, 'ilike', value as any)\n case 'exists':\n return value\n ? builder.where(column as any, 'is not', null)\n : builder.where(column as any, 'is', null)\n default:\n return builder\n }\n }\n\n /**\n * Apply a `cf:*` filter as a correlated EXISTS semi-join over\n * `custom_field_values` (+ `custom_field_defs` for kind-based coercion) \u2014\n * the count shape's equivalent of the projection path's leftJoin + WHERE.\n * A semi-join returns each base row at most once, so the count query needs\n * no DISTINCT or GROUP BY and stays boundable by an outer LIMIT.\n *\n * Predicates satisfied by the *absence* of a value row (`eq null`,\n * `exists: false`) become `NOT EXISTS(value) OR EXISTS(null value)`,\n * matching the leftJoin form where a missing row yields a NULL expression.\n */\n private applyCfValueExistsFilter(\n q: AnyBuilder,\n opts: {\n source: ResolvedCustomFieldSource\n qualify: (column: string) => string\n tenantId: string | null\n key: string\n op: NormalizedFilter['op']\n value: unknown\n },\n ): AnyBuilder {\n return q.where((eb: any) => this.buildCfValueExistsExpression(eb, opts))\n }\n\n /**\n * Expression-returning core of `applyCfValueExistsFilter`, so a cf leaf\n * inside an OR group can compile to an EXISTS predicate on the count shape\n * instead of being dropped for lacking a `cfValueExprByKey` entry.\n */\n private buildCfValueExistsExpression(\n eb: any,\n opts: {\n source: ResolvedCustomFieldSource\n qualify: (column: string) => string\n tenantId: string | null\n key: string\n op: NormalizedFilter['op']\n value: unknown\n },\n ): any {\n const { source, qualify, tenantId, key, op, value } = opts\n const seq = this.searchAliasSeq++\n const valAlias = `cfev_${seq}`\n const defAlias = `cfed_${seq}`\n const srcAlias = `cfes_${seq}`\n const caseExpr = sql<string | null>`CASE ${sql.ref(`${defAlias}.kind`)}\n WHEN 'integer' THEN (${sql.ref(`${valAlias}.value_int`)})::text\n WHEN 'float' THEN (${sql.ref(`${valAlias}.value_float`)})::text\n WHEN 'boolean' THEN (${sql.ref(`${valAlias}.value_bool`)})::text\n WHEN 'multiline' THEN (${sql.ref(`${valAlias}.value_multiline`)})::text\n ELSE (${sql.ref(`${valAlias}.value_text`)})::text\n END`\n\n const buildSub = (eb: any): AnyBuilder => {\n let sub: AnyBuilder = eb\n .selectFrom(`custom_field_values as ${valAlias}`)\n .select(sql<number>`1`.as('one'))\n .leftJoin(`custom_field_defs as ${defAlias}`, (jb: any) =>\n jb.on(`${defAlias}.entity_id`, '=', String(source.entityId))\n .on(`${defAlias}.key`, '=', key)\n .on(`${defAlias}.is_active`, '=', true)\n .on((jeb: any) => jeb.or([\n jeb(`${defAlias}.tenant_id`, '=', tenantId),\n jeb(`${defAlias}.tenant_id`, 'is', null),\n ])))\n .where(`${valAlias}.entity_id`, '=', String(source.entityId))\n .where(`${valAlias}.field_key`, '=', key)\n .where((web: any) => web.or([\n web(`${valAlias}.tenant_id`, '=', tenantId),\n web(`${valAlias}.tenant_id`, 'is', null),\n ]))\n if (source.hop) {\n sub = sub\n .innerJoin(`${source.table} as ${srcAlias}`, (jb: any) =>\n jb.on(sql<boolean>`${sql.ref(`${valAlias}.record_id`)} = (${sql.ref(`${srcAlias}.${source.hop!.recordIdColumn}`)})::text`))\n .whereRef(`${srcAlias}.${source.hop.toField}`, '=', qualify(source.hop.fromField))\n } else {\n sub = sub.where(sql<boolean>`${sql.ref(`${valAlias}.record_id`)} = ${source.recordIdExpr}`)\n }\n return sub\n }\n\n const absenceSatisfiable = (op === 'eq' && value === null) || (op === 'exists' && !value)\n if (absenceSatisfiable) {\n return eb.or([\n eb.not(eb.exists(buildSub(eb))),\n eb.exists(buildSub(eb).where(sql<boolean>`${caseExpr} is null`)),\n ])\n }\n\n let predicate: RawBuilder<boolean> | null = null\n switch (op) {\n case 'eq':\n predicate = sql<boolean>`${caseExpr} = ${value}`\n break\n case 'ne':\n predicate = value === null\n ? sql<boolean>`${caseExpr} is not null`\n : sql<boolean>`${caseExpr} != ${value}`\n break\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const operator = sql.raw(op === 'gt' ? '>' : op === 'gte' ? '>=' : op === 'lt' ? '<' : '<=')\n predicate = sql<boolean>`${caseExpr} ${operator} ${value}`\n break\n }\n case 'in': {\n const vals = Array.isArray(value) ? value : [value]\n predicate = sql<boolean>`${caseExpr} in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`\n break\n }\n case 'nin': {\n const vals = Array.isArray(value) ? value : [value]\n predicate = sql<boolean>`${caseExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`\n break\n }\n case 'like':\n predicate = sql<boolean>`${caseExpr} like ${value}`\n break\n case 'ilike':\n predicate = sql<boolean>`${caseExpr} ilike ${value}`\n break\n case 'exists':\n predicate = sql<boolean>`${caseExpr} is not null`\n break\n default:\n // Mirrors buildColumnOpExpression's unknown-op fallback: a neutral\n // predicate, so full and count shapes drop the same leaves.\n return eb.val(true)\n }\n const captured = predicate\n return eb.exists(buildSub(eb).where(captured))\n }\n\n private buildColumnOpExpression(eb: any, column: string | RawBuilder<unknown>, op: string, value: unknown): any {\n switch (op) {\n case 'eq': return value === null ? eb(column, 'is', null) : eb(column, '=', value)\n case 'ne': return value === null ? eb(column, 'is not', null) : eb(column, '!=', value)\n case 'gt': return eb(column, '>', value)\n case 'gte': return eb(column, '>=', value)\n case 'lt': return eb(column, '<', value)\n case 'lte': return eb(column, '<=', value)\n case 'in': return eb(column, 'in', Array.isArray(value) ? value : [value])\n case 'nin': return eb(column, 'not in', Array.isArray(value) ? value : [value])\n case 'like': return eb(column, 'like', value)\n case 'ilike': return eb(column, 'ilike', value)\n case 'exists': return value ? eb(column, 'is not', null) : eb(column, 'is', null)\n default: return eb.val(true)\n }\n }\n\n private async resolveBaseColumn(table: string, field: string): Promise<string | null> {\n if (await this.columnExists(table, field)) return field\n if (field === 'organization_id' && await this.columnExists(table, 'id')) return 'id'\n return null\n }\n\n private async columnExists(table: string, column: string): Promise<boolean> {\n const key = `${table}.${column}`\n const ttlMs = resolveColumnExistsCacheTtlMs()\n const cached = columnExistsCache.get(key)\n if (cached && cached.expiresAt > Date.now()) return cached.value\n const db = this.getDb()\n const exists = await db\n .selectFrom('information_schema.columns' as any)\n .select(sql<number>`1`.as('one'))\n .where('table_name' as any, '=', table)\n .where('column_name' as any, '=', column)\n .limit(1)\n .executeTakeFirst()\n const present = !!exists\n if (ttlMs > 0) storeColumnExists(key, present, ttlMs)\n return present\n }\n\n private applySearchTokens(\n q: AnyBuilder,\n opts: {\n entity: string\n field: string\n hashes: string[]\n recordIdColumn: string\n tenantId?: string | null\n organizationScope?: { ids: string[]; includeNull: boolean } | null\n combineWith?: 'and' | 'or'\n tokens?: string[]\n }\n ): { applied: boolean; builder: AnyBuilder } {\n if (!opts.hashes.length) {\n this.logSearchDebug('search:skip-no-hashes', {\n entity: opts.entity,\n field: opts.field,\n tenantId: opts.tenantId ?? null,\n organizationScope: opts.organizationScope,\n })\n return { applied: false, builder: q }\n }\n const alias = `st_${this.searchAliasSeq++}`\n const engine = this\n this.logSearchDebug('search:apply-search-tokens', {\n entity: opts.entity,\n field: opts.field,\n alias,\n tokenCount: opts.hashes.length,\n tokens: opts.tokens,\n tenantId: opts.tenantId ?? null,\n organizationScope: opts.organizationScope,\n combineWith: opts.combineWith ?? 'and',\n })\n const buildSub = (eb: any) => {\n let sub: AnyBuilder = eb\n .selectFrom(`search_tokens as ${alias}`)\n .select(sql<number>`1`.as('one'))\n .where(`${alias}.entity_type`, '=', opts.entity)\n .where(`${alias}.field`, '=', opts.field)\n .where(sql<boolean>`${sql.ref(`${alias}.entity_id`)} = ${sql.ref(opts.recordIdColumn)}::text`)\n .where(`${alias}.token_hash`, 'in', opts.hashes)\n .groupBy([`${alias}.entity_id`, `${alias}.field`])\n .having(sql<boolean>`count(distinct ${sql.ref(`${alias}.token_hash`)}) >= ${opts.hashes.length}`)\n if (opts.tenantId !== undefined) {\n sub = sub.where(sql<boolean>`${sql.ref(`${alias}.tenant_id`)} is not distinct from ${opts.tenantId ?? null}`)\n }\n if (opts.organizationScope) {\n sub = engine.applyOrganizationScope(sub, `${alias}.organization_id`, opts.organizationScope)\n }\n return sub\n }\n const combiner = opts.combineWith === 'or' ? 'or' : 'and'\n if (combiner === 'or') {\n // When OR combining, caller expects a raw predicate to include in eb.or([...]).\n // We keep the same semantics as the previous knex orWhereExists by mutating the outer builder with a WHERE EXISTS.\n // Return the mutated builder; callers that need per-predicate control should build the sub themselves.\n const next = q.where((eb: any) => eb.or([eb.exists(buildSub(eb))]))\n return { applied: true, builder: next }\n }\n const next = q.where((eb: any) => eb.exists(buildSub(eb)))\n return { applied: true, builder: next }\n }\n\n private applyIndexDocFilter(\n q: AnyBuilder,\n opts: {\n entity: string\n field: string\n op: NormalizedFilter['op']\n value: unknown\n recordIdColumn: string\n tenantId?: string | null\n organizationScope?: { ids: string[]; includeNull: boolean } | null\n withDeleted: boolean\n searchActive: boolean\n searchConfig: ReturnType<typeof resolveSearchConfig>\n }\n ): AnyBuilder {\n if ((opts.op === 'like' || opts.op === 'ilike') && opts.searchActive && typeof opts.value === 'string') {\n const tokens = tokenizeText(String(opts.value), opts.searchConfig)\n const hashes = tokens.hashes\n if (hashes.length) {\n const result = this.applySearchTokens(q, {\n entity: opts.entity,\n field: opts.field,\n hashes,\n recordIdColumn: opts.recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: opts.organizationScope,\n tokens: tokens.tokens,\n })\n this.logSearchDebug('search:index-doc-filter', {\n entity: opts.entity,\n field: opts.field,\n tokens: tokens.tokens,\n hashes,\n applied: result.applied,\n tenantId: opts.tenantId ?? null,\n organizationScope: opts.organizationScope,\n })\n if (result.applied) return result.builder\n } else {\n this.logSearchDebug('search:index-doc-skip-empty-hashes', {\n entity: opts.entity,\n field: opts.field,\n value: opts.value,\n })\n }\n return q\n }\n\n return q.where((eb: any) => this.buildIndexDocOpExpression(eb, opts))\n }\n\n // Builds the entity_indexes EXISTS expression for a single doc-field operator,\n // shared by the regular doc-filter path (applyIndexDocFilter) and the OR-group\n // path so `$or` queries over custom-entity doc fields resolve instead of being\n // silently dropped (#3229).\n private buildIndexDocOpExpression(\n eb: any,\n opts: {\n entity: string\n field: string\n op: NormalizedFilter['op']\n value: unknown\n recordIdColumn: string\n tenantId?: string | null\n organizationScope?: { ids: string[]; includeNull: boolean } | null\n withDeleted: boolean\n }\n ): any {\n const alias = `ei_${this.searchAliasSeq++}`\n const engine = this\n return eb.exists((() => {\n let sub: AnyBuilder = eb\n .selectFrom(`entity_indexes as ${alias}`)\n .select(sql<number>`1`.as('one'))\n .where(`${alias}.entity_type`, '=', opts.entity)\n .where(sql<boolean>`${sql.ref(`${alias}.entity_id`)} = ${sql.ref(opts.recordIdColumn)}::text`)\n if (opts.tenantId !== undefined) {\n sub = sub.where(sql<boolean>`${sql.ref(`${alias}.tenant_id`)} is not distinct from ${opts.tenantId ?? null}`)\n }\n if (opts.organizationScope) {\n sub = engine.applyOrganizationScope(sub, `${alias}.organization_id`, opts.organizationScope)\n }\n if (!opts.withDeleted) {\n sub = sub.where(`${alias}.deleted_at`, 'is', null)\n }\n\n const textExpr = sql<string | null>`(${sql.ref(`${alias}.doc`)} ->> ${opts.field})`\n switch (opts.op) {\n case 'eq':\n sub = opts.value === null\n ? sub.where(sql<boolean>`${textExpr} is null`)\n : sub.where(sql<boolean>`${textExpr} = ${opts.value}`)\n break\n case 'ne':\n sub = opts.value === null\n ? sub.where(sql<boolean>`${textExpr} is not null`)\n : sub.where(sql<boolean>`${textExpr} <> ${opts.value}`)\n break\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const operator = sql.raw(opts.op === 'gt' ? '>' : opts.op === 'gte' ? '>=' : opts.op === 'lt' ? '<' : '<=')\n sub = sub.where(sql<boolean>`${textExpr} ${operator} ${opts.value}`)\n break\n }\n case 'in': {\n const vals = Array.isArray(opts.value) ? opts.value : [opts.value]\n sub = sub.where(sql<boolean>`${textExpr} in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)\n break\n }\n case 'nin': {\n const vals = Array.isArray(opts.value) ? opts.value : [opts.value]\n sub = sub.where(sql<boolean>`${textExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)\n break\n }\n case 'like':\n sub = sub.where(sql<boolean>`${textExpr} like ${opts.value}`); break\n case 'ilike':\n sub = sub.where(sql<boolean>`${textExpr} ilike ${opts.value}`); break\n case 'exists':\n sub = opts.value\n ? sub.where(sql<boolean>`${textExpr} is not null`)\n : sub.where(sql<boolean>`${textExpr} is null`)\n break\n default:\n break\n }\n return sub\n })())\n }\n\n private configureCustomFieldSources(\n q: AnyBuilder,\n baseTable: string,\n baseEntity: EntityId,\n db: AnyDb,\n opts: QueryOptions,\n qualify: (column: string) => string,\n attachJoins: boolean = true,\n ): { builder: AnyBuilder; sources: ResolvedCustomFieldSource[] } {\n const sources: ResolvedCustomFieldSource[] = [\n {\n entityId: baseEntity,\n alias: 'base',\n table: baseTable,\n recordIdExpr: sql<string>`${sql.ref(`${baseTable}.id`)}::text`,\n },\n ]\n const extras: QueryCustomFieldSource[] = opts.customFieldSources ?? []\n let next = q\n extras.forEach((srcOpt, index) => {\n const joinTable = srcOpt.table ?? resolveEntityTableName(this.em, srcOpt.entityId)\n const alias = srcOpt.alias ?? `cfs_${index}`\n const join = srcOpt.join\n if (!join) {\n throw new Error(`QueryEngine: customFieldSources entry for ${String(srcOpt.entityId)} requires a join configuration`)\n }\n const joinType: 'left' | 'inner' = (join.type ?? 'left') === 'inner' ? 'inner' : 'left'\n if (attachJoins) {\n const joinFn = joinType === 'inner' ? 'innerJoin' : 'leftJoin'\n next = (next as any)[joinFn](`${joinTable} as ${alias}`, (jb: any) =>\n jb.onRef(`${alias}.${join.toField}`, '=', qualify(join.fromField)))\n } else if (joinType === 'inner') {\n // The count projection carries no projection joins, but an inner-typed\n // source join restricts the result set \u2014 preserve that as a semi-join.\n next = next.where((eb: any) => eb.exists(\n eb\n .selectFrom(`${joinTable} as ${alias}`)\n .select(sql<number>`1`.as('one'))\n .whereRef(`${alias}.${join.toField}`, '=', qualify(join.fromField)),\n ))\n }\n const recordColumn = srcOpt.recordIdColumn ?? 'id'\n sources.push({\n entityId: srcOpt.entityId,\n alias,\n table: joinTable,\n recordIdExpr: sql<string>`${sql.ref(`${alias}.${recordColumn}`)}::text`,\n hop: { fromField: join.fromField, toField: join.toField, recordIdColumn: recordColumn, type: joinType },\n })\n })\n return { builder: next, sources }\n }\n\n private logSearchDebug(event: string, payload: Record<string, unknown>) {\n try {\n logger.debug(event, payload)\n } catch {}\n }\n\n private resolveOrganizationScope(opts: QueryOptions): { ids: string[]; includeNull: boolean } | null {\n if (opts.organizationIds !== undefined) {\n const raw = (opts.organizationIds ?? []).map((id) => (typeof id === 'string' ? id.trim() : id))\n const includeNull = raw.some((id) => id == null || id === '')\n const ids = raw.filter((id): id is string => typeof id === 'string' && id.length > 0)\n return { ids: Array.from(new Set(ids)), includeNull }\n }\n if (typeof opts.organizationId === 'string' && opts.organizationId.trim().length > 0) {\n return { ids: [opts.organizationId], includeNull: false }\n }\n return null\n }\n\n private applyOrganizationScope(q: AnyBuilder, column: string, scope: { ids: string[]; includeNull: boolean }): AnyBuilder {\n if (!scope) return q\n if (scope.ids.length === 0 && !scope.includeNull) {\n return q.where(sql<boolean>`1 = 0`)\n }\n return q.where((eb: any) => {\n const parts: any[] = []\n if (scope.ids.length > 0) parts.push(eb(column, 'in', scope.ids))\n if (scope.includeNull) parts.push(eb(column, 'is', null))\n if (parts.length === 1) return parts[0]\n return eb.or(parts)\n })\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["resolvedCustomFieldDefinitions", "cfJsonAliases", "cfMultiAliasByAlias", "hasJoinedAggregates", "eb", "next"]
7
7
  }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.7.1-develop.7106.1.53fe9dd1b1";
1
+ const APP_VERSION = "0.7.1-develop.7111.1.c0d5d7286f";
2
2
  const appVersion = APP_VERSION;
3
3
  export {
4
4
  APP_VERSION,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/version.ts"],
4
- "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.7.1-develop.7106.1.53fe9dd1b1';\nexport const appVersion = APP_VERSION;\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.7.1-develop.7111.1.c0d5d7286f';\nexport const appVersion = APP_VERSION;\n"],
5
5
  "mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/shared",
3
- "version": "0.7.1-develop.7106.1.53fe9dd1b1",
3
+ "version": "0.7.1-develop.7111.1.c0d5d7286f",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -109,7 +109,7 @@
109
109
  "@mikro-orm/core": "^7.1.8",
110
110
  "@mikro-orm/decorators": "^7.1.8",
111
111
  "@mikro-orm/postgresql": "^7.1.8",
112
- "@open-mercato/cache": "0.7.1-develop.7106.1.53fe9dd1b1",
112
+ "@open-mercato/cache": "0.7.1-develop.7111.1.c0d5d7286f",
113
113
  "@types/html-to-text": "^9.0.4",
114
114
  "@types/sanitize-html": "^2.16.1",
115
115
  "dotenv": "^17.4.2",
@@ -244,6 +244,12 @@ function createFakeKysely(overrides?: FakeData) {
244
244
  return db
245
245
  }
246
246
 
247
+ // Selects are recorded verbatim, so a plain column arrives as a string while an
248
+ // aggregated cf projection arrives as Kysely's aliased raw builder.
249
+ function selectAliases(call: any): string[] {
250
+ return call._ops.selects.map((s: any) => (typeof s === 'string' ? s : String(s?.alias ?? '')))
251
+ }
252
+
247
253
  describe('BasicQueryEngine (Kysely)', () => {
248
254
  test('pluralizes entity names ending with y correctly', async () => {
249
255
  const fakeDb = createFakeKysely()
@@ -287,6 +293,39 @@ describe('BasicQueryEngine (Kysely)', () => {
287
293
  expect(hasExtJoin).toBe(true)
288
294
  })
289
295
 
296
+ test('a cf sort alone projects and joins the key it orders by', async () => {
297
+ const fakeDb = createFakeKysely()
298
+ const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
299
+ await engine.query('auth:user', {
300
+ fields: ['id', 'email'],
301
+ sort: [{ field: 'cf:vip', dir: SortDir.Asc }],
302
+ organizationId: '1',
303
+ tenantId: 't1',
304
+ })
305
+ const baseCall = fakeDb._calls.find((b: any) => b._ops.table === 'users')
306
+ expect(baseCall._ops.orderBys).toContainEqual(['cf_vip', 'asc'])
307
+ // Ordering by an alias the query never selected is a Postgres 42703, so the
308
+ // sort has to bring its own projection and joins along (#5521).
309
+ expect(selectAliases(baseCall)).toContain('cf_vip')
310
+ expect(baseCall._ops.joins.length).toBeGreaterThan(0)
311
+ })
312
+
313
+ test('a cf sort that resolves to no definition is dropped, not ordered by', async () => {
314
+ const fakeDb = createFakeKysely()
315
+ const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
316
+ await engine.query('auth:user', {
317
+ fields: ['id', 'email'],
318
+ sort: [{ field: 'cf:no_such_key', dir: SortDir.Asc }],
319
+ organizationId: '1',
320
+ tenantId: 't1',
321
+ })
322
+ const baseCall = fakeDb._calls.find((b: any) => b._ops.table === 'users')
323
+ // Dropping an unresolvable sort is what the base-column branch already does;
324
+ // the alternative here was an ORDER BY over a column that is never selected.
325
+ expect(baseCall._ops.orderBys).toEqual([])
326
+ expect(selectAliases(baseCall)).not.toContain('cf_no_such_key')
327
+ })
328
+
290
329
  test('customFieldSources join additional profiles for custom fields', async () => {
291
330
  const fakeDb = createFakeKysely({
292
331
  custom_field_defs: [
@@ -863,6 +863,16 @@ export class BasicQueryEngine implements QueryEngine {
863
863
  for (const f of cfFilters) {
864
864
  if (typeof f.field === 'string' && f.field.startsWith('cf:')) cfKeys.add(f.field.slice(3))
865
865
  }
866
+ // A `cf:` sort needs the same joins and value expression as a `cf:` field or
867
+ // filter: the sort loop below orders by an aggregated alias that only exists
868
+ // once the key reached `cfKeys`. Without this, a sort-only custom-field query
869
+ // emitted `ORDER BY "cf_<key>"` against a column it never selected (#5521).
870
+ // The count shape never sorts, so it stays out of this.
871
+ if (!isCountProjection) {
872
+ for (const s of (opts.sort || [])) {
873
+ if (typeof s.field === 'string' && s.field.startsWith('cf:')) cfKeys.add(s.field.slice(3))
874
+ }
875
+ }
866
876
  if (projection === 'full' && opts.includeCustomFields === true) {
867
877
  if (entityIdToSource.size > 0) {
868
878
  const entityIdList = Array.from(entityIdToSource.keys())
@@ -1202,7 +1212,13 @@ export class BasicQueryEngine implements QueryEngine {
1202
1212
  cfSelectedAliases.push(alias)
1203
1213
  }
1204
1214
  }
1205
- if (!requiresPlaintextSort) q = q.orderBy(alias, (s.dir ?? 'asc') as any)
1215
+ // Only order by an alias the projection actually carries. A key that
1216
+ // resolved to no expression is dropped rather than emitted as an
1217
+ // unselected alias, which is what the base-column branch above already
1218
+ // does when `resolveBaseColumn` returns null.
1219
+ if (!requiresPlaintextSort && cfSelectedAliases.includes(alias)) {
1220
+ q = q.orderBy(alias, (s.dir ?? 'asc') as any)
1221
+ }
1206
1222
  } else {
1207
1223
  if (!requiresPlaintextSort) q = q.orderBy(qualify(s.field), (s.dir ?? 'asc') as any)
1208
1224
  }