@open-mercato/shared 0.9.1-develop.7553.1.8dbab05b5a → 0.9.1-develop.7566.1.b9de0a5eb7

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.
package/AGENTS.md CHANGED
@@ -53,7 +53,7 @@ yarn workspace @open-mercato/shared build
53
53
  | `indexers/` | When building query index helpers | `@open-mercato/shared/lib/indexers` |
54
54
  | `logger/` | When emitting diagnostics — `createLogger(namespace)` instead of raw `console.*` (migrate incrementally, Boy Scout rule). Message-first with structured fields (`logger.warn('Payload too large', { event, maxBytes })`), errors under `err`, `child(bindings)` for context, `getLogLevel()`/`isLevelEnabled()` to gate expensive fields; level via `OM_LOG_LEVEL`. Never log credentials, PII, or payload bodies | `@open-mercato/shared/lib/logger` |
55
55
  | `modules/` | When registering or listing modules; `onModulesRegistered(listener)` subscribes to (re-)registrations so a cache derived from the module list can drop what it built from an incomplete one — bootstrap may register an i18n-only set before the full module list merges in, and listeners fire only when the registered set actually changed, so nothing is added to the request path. Its governing contract — notification timing, fail-soft handling of a throwing or rejecting listener, snapshot-based change detection, listener lifetime under HMR, and the globals a test MUST clear — is [`.ai/specs/2026-08-12-module-registry-registration-listeners.md`](../../.ai/specs/2026-08-12-module-registry-registration-listeners.md); `surfaceFingerprint` gives a deploy-time hash of the enabled modules, their declared ACL features, and the backend route manifest — mix it into any cache key whose payload is derived from those (no DB write exists to tag-invalidate on, so an omitted fingerprint serves the pre-deploy payload forever). It cannot see React-element fields such as a route `icon`, so callers MUST still pass a `ttl` | `@open-mercato/shared/lib/modules/registry`, `@open-mercato/shared/lib/modules/surfaceFingerprint` |
56
- | `number.ts` | When parsing numeric strings from env/query params with a fallback and optional min/integer constraint (`parseNumberWithDefault`), or when parsing a number a USER TYPED, which carries the application locale's decimal/group separators (`parseLocaleNumber`, returns `null` — never a silent `0` — on unparseable input). MUST NOT run API/DB values through `parseLocaleNumber`; those are already numbers | `@open-mercato/shared/lib/number` |
56
+ | `number.ts` | When parsing numeric strings from env/query params with a fallback and optional min/integer constraint (`parseNumberWithDefault`), or when parsing a number a USER TYPED, which carries the application locale's decimal/group separators (`parseLocaleNumber`, returns `null` — never a silent `0` — on unparseable input). MUST NOT run API/DB values through `parseLocaleNumber`; those are already numbers. When an input REDISPLAYS a stored number (blur, reload, post-save), render it with `formatLocaleNumberInput(value, locale)`, never `String(value)`, which always emits a dot | `@open-mercato/shared/lib/number` |
57
57
  | `openapi/` | When generating CRUD OpenAPI specs | `@open-mercato/shared/lib/openapi/crud` |
58
58
  | `profiler/` | When profiling with `OM_PROFILE` env flag | `@open-mercato/shared/lib/profiler` |
59
59
  | `search/` | When resolving record ids from the `search_tokens` index — MUST use instead of hand-rolling the Kysely lookup, and MUST be unioned into (or replace) any `$ilike` filter on a column an encryption map covers | `@open-mercato/shared/lib/search/tokenLookup` |
@@ -62,6 +62,11 @@ function parseLocaleNumber(input, locale) {
62
62
  const parsed = Number(normalized);
63
63
  return Number.isFinite(parsed) ? parsed : null;
64
64
  }
65
+ function formatLocaleNumberInput(value, locale) {
66
+ const serialized = String(value);
67
+ const { decimal } = resolveLocaleNumberSeparators(locale);
68
+ return decimal === "." ? serialized : serialized.replace(".", decimal);
69
+ }
65
70
  function parseNumberWithDefault(raw, fallback, options) {
66
71
  if (raw == null) return fallback;
67
72
  const trimmed = raw.trim();
@@ -73,6 +78,7 @@ function parseNumberWithDefault(raw, fallback, options) {
73
78
  return value;
74
79
  }
75
80
  export {
81
+ formatLocaleNumberInput,
76
82
  parseLocaleNumber,
77
83
  parseNumberWithDefault,
78
84
  resolveLocaleNumberSeparators
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/number.ts"],
4
- "sourcesContent": ["type LocaleNumberSeparators = { group: string; decimal: string }\n\nconst DEFAULT_SEPARATORS: LocaleNumberSeparators = { group: ',', decimal: '.' }\nconst separatorCache = new Map<string, LocaleNumberSeparators>()\n\nconst UNICODE_MINUS_SIGNS = /[\u2212\u2012\u2013\u2014]/g\nconst GROUP_LIKE_CHARACTER = /[\\s'\u2019\u02BC]/\nconst GROUP_LIKE_SEPARATOR_IN_POSITION = /(\\d)[\\s'\u2019\u02BC](?=\\d{3}(?!\\d))/g\nconst NORMALIZED_NUMBER = /^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?$/i\n\n/**\n * Group and decimal separators the given locale uses, derived from `Intl` rather than\n * assumed, so grouping characters such as the narrow no-break space (`fr-FR`) are covered.\n */\nexport function resolveLocaleNumberSeparators(locale?: string): LocaleNumberSeparators {\n const cacheKey = locale ?? ''\n const cached = separatorCache.get(cacheKey)\n if (cached) return cached\n let resolved = DEFAULT_SEPARATORS\n try {\n const parts = new Intl.NumberFormat(locale, {\n useGrouping: true,\n minimumFractionDigits: 1,\n }).formatToParts(12345.6)\n const group = parts.find((part) => part.type === 'group')?.value ?? DEFAULT_SEPARATORS.group\n const decimal = parts.find((part) => part.type === 'decimal')?.value ?? DEFAULT_SEPARATORS.decimal\n resolved = { group, decimal }\n } catch {\n resolved = DEFAULT_SEPARATORS\n }\n separatorCache.set(cacheKey, resolved)\n return resolved\n}\n\nfunction isValidGrouping(integerPart: string, separator: string): boolean {\n const digits = integerPart.replace(/^[+-]/, '')\n const segments = digits.split(separator)\n if (segments.length < 2) return true\n const [first, ...rest] = segments\n if (!/^\\d{1,3}$/.test(first)) return false\n return rest.every((segment) => /^\\d{3}$/.test(segment))\n}\n\n/**\n * Parses a user-typed number written in the conventions of `locale` \u2014 `110,70` under `pl-PL`,\n * `1 234,56` under `fr-FR`, `1,234.56` under `en-US`. Returns `null` when the input is not a\n * number, never a silent `0`, so callers can tell \"unparseable\" apart from \"zero\".\n *\n * Both `,` and `.` are accepted as the decimal separator whichever way the locale runs, because\n * users type the shape their keyboard offers. A SINGLE `,` or `.` is therefore always the decimal\n * point, in every locale: `1.500` is 1.5 under `de-DE` just as it is under `en-US`. Reading a lone\n * separator as grouping instead would turn `1.500` into 1500 with no visible cue \u2014 a silent 1000\u00D7\n * on a money field, and three- and four-decimal unit prices are ordinary here. Grouping is\n * recognized only where it is unambiguous: at least two separators (`1.234.567`), or a whitespace\n * or apostrophe separator standing in a valid group-of-three position (`1 234,56`, `1\u2019234.5`).\n * Whitespace and apostrophes anywhere else are not absorbed \u2014 `1 2` is rejected rather than read\n * as 12 \u2014 so a mistyped or pasted value surfaces as an error instead of a different number.\n *\n * Use it only on strings a user typed. Values arriving from an API or the database are already\n * numbers and MUST NOT go through it.\n */\nexport function parseLocaleNumber(input: string | null | undefined, locale?: string): number | null {\n if (input == null) return null\n const trimmed = input.trim()\n if (!trimmed) return null\n\n const { group } = resolveLocaleNumberSeparators(locale)\n let candidate = trimmed.replace(UNICODE_MINUS_SIGNS, '-')\n if (group && group !== ',' && group !== '.' && !GROUP_LIKE_CHARACTER.test(group)) {\n candidate = candidate.split(group).join(' ')\n }\n candidate = candidate.replace(GROUP_LIKE_SEPARATOR_IN_POSITION, '$1')\n if (!candidate) return null\n\n const hasComma = candidate.includes(',')\n const hasDot = candidate.includes('.')\n let decimalSeparator: string | null = null\n if (hasComma && hasDot) {\n decimalSeparator = candidate.lastIndexOf(',') > candidate.lastIndexOf('.') ? ',' : '.'\n } else if (hasComma || hasDot) {\n const separator = hasComma ? ',' : '.'\n const segments = candidate.split(separator)\n decimalSeparator = segments.length > 2 ? null : separator\n }\n\n const groupSeparator = decimalSeparator\n ? decimalSeparator === ','\n ? '.'\n : ','\n : hasComma\n ? ','\n : '.'\n const [integerPart, ...fractionParts] = decimalSeparator ? candidate.split(decimalSeparator) : [candidate]\n if (fractionParts.length > 1) return null\n if (!isValidGrouping(integerPart, groupSeparator)) return null\n if (fractionParts.length && fractionParts[0].includes(groupSeparator)) return null\n\n const normalized = `${integerPart.split(groupSeparator).join('')}${fractionParts.length ? `.${fractionParts[0]}` : ''}`\n if (!NORMALIZED_NUMBER.test(normalized)) return null\n const parsed = Number(normalized)\n return Number.isFinite(parsed) ? parsed : null\n}\n\nexport function parseNumberWithDefault(\n raw: string | null | undefined,\n fallback: number,\n options?: { min?: number; integer?: boolean },\n): number {\n if (raw == null) return fallback\n const trimmed = raw.trim()\n if (!trimmed) return fallback\n const value = options?.integer ? Number.parseInt(trimmed, 10) : Number(trimmed)\n if (!Number.isFinite(value)) return fallback\n const min = options?.min ?? -Infinity\n if (value < min) return fallback\n return value\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["type LocaleNumberSeparators = { group: string; decimal: string }\n\nconst DEFAULT_SEPARATORS: LocaleNumberSeparators = { group: ',', decimal: '.' }\nconst separatorCache = new Map<string, LocaleNumberSeparators>()\n\nconst UNICODE_MINUS_SIGNS = /[\u2212\u2012\u2013\u2014]/g\nconst GROUP_LIKE_CHARACTER = /[\\s'\u2019\u02BC]/\nconst GROUP_LIKE_SEPARATOR_IN_POSITION = /(\\d)[\\s'\u2019\u02BC](?=\\d{3}(?!\\d))/g\nconst NORMALIZED_NUMBER = /^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?$/i\n\n/**\n * Group and decimal separators the given locale uses, derived from `Intl` rather than\n * assumed, so grouping characters such as the narrow no-break space (`fr-FR`) are covered.\n */\nexport function resolveLocaleNumberSeparators(locale?: string): LocaleNumberSeparators {\n const cacheKey = locale ?? ''\n const cached = separatorCache.get(cacheKey)\n if (cached) return cached\n let resolved = DEFAULT_SEPARATORS\n try {\n const parts = new Intl.NumberFormat(locale, {\n useGrouping: true,\n minimumFractionDigits: 1,\n }).formatToParts(12345.6)\n const group = parts.find((part) => part.type === 'group')?.value ?? DEFAULT_SEPARATORS.group\n const decimal = parts.find((part) => part.type === 'decimal')?.value ?? DEFAULT_SEPARATORS.decimal\n resolved = { group, decimal }\n } catch {\n resolved = DEFAULT_SEPARATORS\n }\n separatorCache.set(cacheKey, resolved)\n return resolved\n}\n\nfunction isValidGrouping(integerPart: string, separator: string): boolean {\n const digits = integerPart.replace(/^[+-]/, '')\n const segments = digits.split(separator)\n if (segments.length < 2) return true\n const [first, ...rest] = segments\n if (!/^\\d{1,3}$/.test(first)) return false\n return rest.every((segment) => /^\\d{3}$/.test(segment))\n}\n\n/**\n * Parses a user-typed number written in the conventions of `locale` \u2014 `110,70` under `pl-PL`,\n * `1 234,56` under `fr-FR`, `1,234.56` under `en-US`. Returns `null` when the input is not a\n * number, never a silent `0`, so callers can tell \"unparseable\" apart from \"zero\".\n *\n * Both `,` and `.` are accepted as the decimal separator whichever way the locale runs, because\n * users type the shape their keyboard offers. A SINGLE `,` or `.` is therefore always the decimal\n * point, in every locale: `1.500` is 1.5 under `de-DE` just as it is under `en-US`. Reading a lone\n * separator as grouping instead would turn `1.500` into 1500 with no visible cue \u2014 a silent 1000\u00D7\n * on a money field, and three- and four-decimal unit prices are ordinary here. Grouping is\n * recognized only where it is unambiguous: at least two separators (`1.234.567`), or a whitespace\n * or apostrophe separator standing in a valid group-of-three position (`1 234,56`, `1\u2019234.5`).\n * Whitespace and apostrophes anywhere else are not absorbed \u2014 `1 2` is rejected rather than read\n * as 12 \u2014 so a mistyped or pasted value surfaces as an error instead of a different number.\n *\n * Use it only on strings a user typed. Values arriving from an API or the database are already\n * numbers and MUST NOT go through it.\n */\nexport function parseLocaleNumber(input: string | null | undefined, locale?: string): number | null {\n if (input == null) return null\n const trimmed = input.trim()\n if (!trimmed) return null\n\n const { group } = resolveLocaleNumberSeparators(locale)\n let candidate = trimmed.replace(UNICODE_MINUS_SIGNS, '-')\n if (group && group !== ',' && group !== '.' && !GROUP_LIKE_CHARACTER.test(group)) {\n candidate = candidate.split(group).join(' ')\n }\n candidate = candidate.replace(GROUP_LIKE_SEPARATOR_IN_POSITION, '$1')\n if (!candidate) return null\n\n const hasComma = candidate.includes(',')\n const hasDot = candidate.includes('.')\n let decimalSeparator: string | null = null\n if (hasComma && hasDot) {\n decimalSeparator = candidate.lastIndexOf(',') > candidate.lastIndexOf('.') ? ',' : '.'\n } else if (hasComma || hasDot) {\n const separator = hasComma ? ',' : '.'\n const segments = candidate.split(separator)\n decimalSeparator = segments.length > 2 ? null : separator\n }\n\n const groupSeparator = decimalSeparator\n ? decimalSeparator === ','\n ? '.'\n : ','\n : hasComma\n ? ','\n : '.'\n const [integerPart, ...fractionParts] = decimalSeparator ? candidate.split(decimalSeparator) : [candidate]\n if (fractionParts.length > 1) return null\n if (!isValidGrouping(integerPart, groupSeparator)) return null\n if (fractionParts.length && fractionParts[0].includes(groupSeparator)) return null\n\n const normalized = `${integerPart.split(groupSeparator).join('')}${fractionParts.length ? `.${fractionParts[0]}` : ''}`\n if (!NORMALIZED_NUMBER.test(normalized)) return null\n const parsed = Number(normalized)\n return Number.isFinite(parsed) ? parsed : null\n}\n\n/**\n * Renders a number as the text an input field should show under `locale` \u2014 `2,5` under\n * `pl-PL`, `2.5` under `en-US`. Unlike `String()`, which always emits a dot, the result uses\n * the locale's decimal separator, so a value redisplayed after blur or reload matches what the\n * user typed and what the rest of the UI shows. No grouping is applied, and the output always\n * round-trips through `parseLocaleNumber`.\n */\nexport function formatLocaleNumberInput(value: number, locale?: string): string {\n const serialized = String(value)\n const { decimal } = resolveLocaleNumberSeparators(locale)\n return decimal === '.' ? serialized : serialized.replace('.', decimal)\n}\n\nexport function parseNumberWithDefault(\n raw: string | null | undefined,\n fallback: number,\n options?: { min?: number; integer?: boolean },\n): number {\n if (raw == null) return fallback\n const trimmed = raw.trim()\n if (!trimmed) return fallback\n const value = options?.integer ? Number.parseInt(trimmed, 10) : Number(trimmed)\n if (!Number.isFinite(value)) return fallback\n const min = options?.min ?? -Infinity\n if (value < min) return fallback\n return value\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1099,10 +1099,14 @@ class BasicQueryEngine {
1099
1099
  return builder.where(column, "<", value);
1100
1100
  case "lte":
1101
1101
  return builder.where(column, "<=", value);
1102
- case "in":
1103
- return builder.where(column, "in", Array.isArray(value) ? value : [value]);
1104
- case "nin":
1105
- return builder.where(column, "not in", Array.isArray(value) ? value : [value]);
1102
+ case "in": {
1103
+ const inValues = Array.isArray(value) ? value : [value];
1104
+ return inValues.length === 0 ? builder.where(sql`false`) : builder.where(column, "in", inValues);
1105
+ }
1106
+ case "nin": {
1107
+ const ninValues = Array.isArray(value) ? value : [value];
1108
+ return ninValues.length === 0 ? builder.where(sql`true`) : builder.where(column, "not in", ninValues);
1109
+ }
1106
1110
  case "like":
1107
1111
  return builder.where(column, "like", value);
1108
1112
  case "ilike":
@@ -1231,10 +1235,14 @@ class BasicQueryEngine {
1231
1235
  return eb(column, "<", value);
1232
1236
  case "lte":
1233
1237
  return eb(column, "<=", value);
1234
- case "in":
1235
- return eb(column, "in", Array.isArray(value) ? value : [value]);
1236
- case "nin":
1237
- return eb(column, "not in", Array.isArray(value) ? value : [value]);
1238
+ case "in": {
1239
+ const inValues = Array.isArray(value) ? value : [value];
1240
+ return inValues.length === 0 ? eb.val(false) : eb(column, "in", inValues);
1241
+ }
1242
+ case "nin": {
1243
+ const ninValues = Array.isArray(value) ? value : [value];
1244
+ return ninValues.length === 0 ? eb.val(true) : eb(column, "not in", ninValues);
1245
+ }
1238
1246
  case "like":
1239
1247
  return eb(column, "like", value);
1240
1248
  case "ilike":
@@ -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 { buildContainmentPatterns } from '../search/containment'\nimport { fieldNameCandidates } from './encrypted-sort'\nimport { isTenantDataEncryptionEnabled } from '../encryption/toggles'\nimport { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from './query-extension-runner'\nimport {\n buildCustomFieldDefinitionIndexFromRows,\n normalizeDefinitionKey,\n resolveCfDefIndexOrgCandidates,\n type CustomFieldDefinitionRow,\n type CustomFieldDefinitionSummary,\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'\nimport {\n buildArrayColumnNoOverlapPredicate,\n buildArrayColumnOverlapPredicate,\n buildJsonbNoOverlapPredicate,\n buildJsonbOverlapPredicate,\n buildScalarOverlapPredicate,\n} from './overlap'\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)` reads\n// `encryption_maps`. The service caches that aggregate since #5949, but this set is per-search\n// hot and also spans the name-shape expansion below, so a short TTL keeps the whole resolved\n// set out of the request path 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\n/** Custom field kinds stored numerically (`custom_field_values.value_int`/`value_float`) \u2014 sort numerically, not as text (#5674). */\nconst NUMERIC_CF_SORT_KINDS = new Set(['integer', 'float'])\n\n/**\n * Pick the winning `kind` for a `cf:` sort key from an already-resolved\n * definition list \u2014 org-scoped beats tenant-wide beats global, same\n * precedence as the dedicated sort-kind lookup (#5674 review).\n */\nfunction pickCfSortKind(defs: CustomFieldDefinitionSummary[] | undefined): string | null {\n if (!defs || !defs.length) return null\n let winner: { kind: string; specificity: number } | null = null\n for (const def of defs) {\n if (!def.kind) continue\n const specificity = def.organizationId != null ? 2 : def.tenantId != null ? 1 : 0\n if (!winner || specificity >= winner.specificity) winner = { kind: def.kind, specificity }\n }\n return winner?.kind ?? null\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 // Gated on OM_SEARCH_USE_ILIKE_FOR_NON_ENCRYPTED_FIELDS (default false per #5383; set it to\n // true to opt into the #5803 fix ahead of that follow-up). When enabled, base-column\n // like/ilike is rerouted 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, so a field any org encrypts stays on the token path -- a wider set\n // fails safe. Passing the request's org instead would silently break encrypted-column search\n // for orgs without their own map. The service caches that union (#5949), and the TTL cache\n // above keeps even the cache lookup off the request path. `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 // A PLAINTEXT base column the gate above kept off the token path: apply the declared\n // containment once per word so the token subquery's word-order-independent matching survives\n // the reroute (#5803 / TC-RESO-009). Chained `where`s ARE the AND the token\n // `having count(distinct)` did. `fieldName` is required, so this never fires for a JOINed\n // column \u2014 `applyJoinFilters` calls `applyFilterOp` without it (below), and that predicate\n // keeps its pre-existing literal-containment behavior unchanged.\n if (\n (op === 'like' || op === 'ilike') &&\n typeof value === 'string' &&\n searchActive &&\n fieldName &&\n encryptedLikeFields !== null &&\n !isEncryptedLikeField(encryptedLikeFields, fieldName)\n ) {\n const patterns = buildContainmentPatterns(value)\n if (patterns.length > 1) {\n let next = builder\n for (const pattern of patterns) next = this.applyColumnOp(next, column, op, pattern)\n return next\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 cfSortAliases: 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 // A `cf:` sort needs to know the field's declared `kind` so the ORDER BY\n // expression can cast numeric kinds instead of ordering by jsonb text\n // (#5674). Org-scoped beats tenant-wide beats global for the same key \u2014\n // a same-key definition from an unrelated organization in the tenant\n // never gets to decide the kind (#5674 review).\n const cfSortKeys = Array.from(new Set(\n resolvedSorts.filter((sort) => sort.field.startsWith('cf:')).map((sort) => sort.field.slice(3))\n ))\n const cfSortKinds = new Map<string, string>()\n if (cfSortKeys.length > 0) {\n // `includeCustomFields === true` already resolved every definition for\n // these entities into `resolvedCustomFieldDefinitions` above \u2014 reuse it\n // instead of a second `custom_field_defs` round trip (#5674 review).\n const keysNeedingQuery: string[] = []\n for (const key of cfSortKeys) {\n const kind = resolvedCustomFieldDefinitions\n ? pickCfSortKind(resolvedCustomFieldDefinitions.index.get(normalizeDefinitionKey(key)))\n : null\n if (kind) cfSortKinds.set(key, kind)\n else keysNeedingQuery.push(key)\n }\n if (keysNeedingQuery.length > 0) {\n const sortEntityIds = Array.from(new Set(\n keysNeedingQuery\n .map((key) => keySource.get(key)?.entityId)\n .filter((id): id is EntityId => Boolean(id))\n .map((id) => String(id))\n ))\n if (sortEntityIds.length > 0) {\n const cfSortOrgCandidates = resolveCfDefIndexOrgCandidates(opts.organizationIds, opts.organizationId ?? null)\n const kindRows = await db\n .selectFrom('custom_field_defs' as any)\n .select(['key' as any, 'kind' as any, 'organization_id' as any, 'tenant_id' as any])\n .where('entity_id' as any, 'in', sortEntityIds)\n .where('key' as any, 'in', keysNeedingQuery)\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 .where((eb: any) => cfSortOrgCandidates.length\n ? eb.or([\n eb('organization_id' as any, 'is', null),\n eb('organization_id' as any, 'in', cfSortOrgCandidates),\n ])\n : eb('organization_id' as any, 'is', null))\n .execute() as Array<{ key: string; kind: string | null; organization_id: string | null; tenant_id: string | null }>\n const winners = new Map<string, { kind: string; specificity: number }>()\n for (const row of kindRows) {\n if (!row.kind) continue\n const specificity = row.organization_id != null ? 2 : row.tenant_id != null ? 1 : 0\n const existing = winners.get(row.key)\n if (!existing || specificity >= existing.specificity) winners.set(row.key, { kind: row.kind, specificity })\n }\n for (const [key, entry] of winners) cfSortKinds.set(key, entry.kind)\n }\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 const cfSortAliases: 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 = f.op === 'overlap'\n ? q.where(buildScalarOverlapPredicate(expr, f.value))\n : f.op === 'noverlap'\n ? q.where(sql<boolean>`false`)\n : 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 // This also means the #5803 plaintext-containment split (lib/search/containment) does not\n // apply here: `buildColumnOpExpression` below keeps a multi-word `like`/`ilike` leaf as one\n // literal pattern, so an OR-grouped search (e.g. `customers/api/people`'s multi-field\n // fallback) loses word-order independence on Basic while the Hybrid engine's OR groups\n // apply the split. Pre-existing divergence between the two engines; not tracked by an issue.\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 if (rf.op === 'noverlap') return sql<boolean>`false`\n return rf.op === 'overlap'\n ? buildScalarOverlapPredicate(cfValueExprByKey[rf.key], rf.value)\n : 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:* (a dedicated scalar alias, never the jsonb\n // projection alias \u2014 ordering by `to_jsonb(...)` compares arrays after every\n // scalar string regardless of contents, and a numeric kind must cast to\n // numeric instead of ordering the text CASE expression lexicographically (#5674)).\n let lastEmittedSortField: string | null = null\n for (const s of isCountProjection ? [] : resolvedSorts) {\n if (s.field.startsWith('cf:')) {\n const key = s.field.slice(3)\n const sortAlias = sanitize(`cf:${key}__sort`)\n if (!cfSortAliases.includes(sortAlias)) {\n const source = keySource.get(key)\n const kind = cfSortKinds.get(key)\n let sortExpr: RawBuilder<unknown> | null = null\n if (source && kind && NUMERIC_CF_SORT_KINDS.has(kind)) {\n const sourceAliasSafe = sanitize(source.alias || 'src')\n const keyAliasSafe = sanitize(key)\n const valAlias = `cfv_${sourceAliasSafe}_${keyAliasSafe}`\n const numericColumn = kind === 'integer' ? 'value_int' : 'value_float'\n sortExpr = sql<string | null>`max((${sql.ref(`${valAlias}.${numericColumn}`)})::numeric)`\n } else {\n const expr = cfValueExprByKey[key]\n if (expr) sortExpr = sql<string | null>`max(${expr})`\n }\n if (sortExpr) {\n q = q.select(sortExpr.as(sortAlias))\n cfSortAliases.push(sortAlias)\n }\n }\n // Only order by an alias the query actually selects. A key that resolved\n // to no expression is dropped rather than emitted as an unselected alias,\n // which is what the base-column branch below already does when\n // `resolveBaseColumn` returns null.\n if (!requiresPlaintextSort && cfSortAliases.includes(sortAlias)) {\n const direction = sql.raw((s.dir ?? 'asc') === 'desc' ? 'desc' : 'asc')\n q = q.orderBy(sql`${sql.ref(sortAlias)} ${direction} NULLS LAST`)\n lastEmittedSortField = s.field\n }\n } else {\n if (!requiresPlaintextSort) {\n q = q.orderBy(qualify(s.field), (s.dir ?? 'asc') as any)\n lastEmittedSortField = s.field\n }\n }\n }\n // Stable tiebreak so ties (or NULLs) don't reorder arbitrarily across pages\n // (#5674). Only appended when a sort term actually made it into the ORDER\n // BY \u2014 an entirely unresolved cf sort must stay a no-op, not gain an\n // incidental `id` ordering the caller never asked for.\n if (!isCountProjection && lastEmittedSortField !== null && lastEmittedSortField !== 'id') {\n q = q.orderBy(qualify('id'), 'asc' as any)\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, cfSortAliases, 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 cfSortAliases,\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 // The `cf:<key>__sort` alias exists only to give a `cf:` sort's ORDER BY\n // something to reference (and, for a numeric kind, to host the cast) \u2014 it\n // is never a requested output field. Strip it before rows leave this\n // function so it doesn't leak into API responses as a phantom custom field\n // `<key>__sort` (unlike `__is_multi`, which `normalizeCfJsonAliases`\n // already deletes) (#5674 review).\n const stripCfSortAliases = (rows: ResultRow[]) => {\n if (cfSortAliases.length === 0) return\n for (const row of rows) {\n for (const alias of cfSortAliases) delete row[alias]\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 stripCfSortAliases(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 stripCfSortAliases(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 case 'overlap':\n return builder.where(buildArrayColumnOverlapPredicate(column, value))\n case 'noverlap':\n return builder.where(buildArrayColumnNoOverlapPredicate(column, value))\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 case 'overlap':\n predicate = buildScalarOverlapPredicate(caseExpr, value)\n break\n case 'noverlap':\n return sql<boolean>`false`\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 case 'overlap': return buildArrayColumnOverlapPredicate(column, value)\n case 'noverlap': return buildArrayColumnNoOverlapPredicate(column, value)\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 case 'overlap':\n sub = sub.where(buildJsonbOverlapPredicate(`${alias}.doc`, opts.field, opts.value))\n break\n case 'noverlap':\n sub = sub.where(buildJsonbNoOverlapPredicate(`${alias}.doc`, opts.field, opts.value))\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 { buildContainmentPatterns } from '../search/containment'\nimport { fieldNameCandidates } from './encrypted-sort'\nimport { isTenantDataEncryptionEnabled } from '../encryption/toggles'\nimport { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from './query-extension-runner'\nimport {\n buildCustomFieldDefinitionIndexFromRows,\n normalizeDefinitionKey,\n resolveCfDefIndexOrgCandidates,\n type CustomFieldDefinitionRow,\n type CustomFieldDefinitionSummary,\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'\nimport {\n buildArrayColumnNoOverlapPredicate,\n buildArrayColumnOverlapPredicate,\n buildJsonbNoOverlapPredicate,\n buildJsonbOverlapPredicate,\n buildScalarOverlapPredicate,\n} from './overlap'\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)` reads\n// `encryption_maps`. The service caches that aggregate since #5949, but this set is per-search\n// hot and also spans the name-shape expansion below, so a short TTL keeps the whole resolved\n// set out of the request path 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\n/** Custom field kinds stored numerically (`custom_field_values.value_int`/`value_float`) \u2014 sort numerically, not as text (#5674). */\nconst NUMERIC_CF_SORT_KINDS = new Set(['integer', 'float'])\n\n/**\n * Pick the winning `kind` for a `cf:` sort key from an already-resolved\n * definition list \u2014 org-scoped beats tenant-wide beats global, same\n * precedence as the dedicated sort-kind lookup (#5674 review).\n */\nfunction pickCfSortKind(defs: CustomFieldDefinitionSummary[] | undefined): string | null {\n if (!defs || !defs.length) return null\n let winner: { kind: string; specificity: number } | null = null\n for (const def of defs) {\n if (!def.kind) continue\n const specificity = def.organizationId != null ? 2 : def.tenantId != null ? 1 : 0\n if (!winner || specificity >= winner.specificity) winner = { kind: def.kind, specificity }\n }\n return winner?.kind ?? null\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 // Gated on OM_SEARCH_USE_ILIKE_FOR_NON_ENCRYPTED_FIELDS (default false per #5383; set it to\n // true to opt into the #5803 fix ahead of that follow-up). When enabled, base-column\n // like/ilike is rerouted 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, so a field any org encrypts stays on the token path -- a wider set\n // fails safe. Passing the request's org instead would silently break encrypted-column search\n // for orgs without their own map. The service caches that union (#5949), and the TTL cache\n // above keeps even the cache lookup off the request path. `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 // A PLAINTEXT base column the gate above kept off the token path: apply the declared\n // containment once per word so the token subquery's word-order-independent matching survives\n // the reroute (#5803 / TC-RESO-009). Chained `where`s ARE the AND the token\n // `having count(distinct)` did. `fieldName` is required, so this never fires for a JOINed\n // column \u2014 `applyJoinFilters` calls `applyFilterOp` without it (below), and that predicate\n // keeps its pre-existing literal-containment behavior unchanged.\n if (\n (op === 'like' || op === 'ilike') &&\n typeof value === 'string' &&\n searchActive &&\n fieldName &&\n encryptedLikeFields !== null &&\n !isEncryptedLikeField(encryptedLikeFields, fieldName)\n ) {\n const patterns = buildContainmentPatterns(value)\n if (patterns.length > 1) {\n let next = builder\n for (const pattern of patterns) next = this.applyColumnOp(next, column, op, pattern)\n return next\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 cfSortAliases: 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 // A `cf:` sort needs to know the field's declared `kind` so the ORDER BY\n // expression can cast numeric kinds instead of ordering by jsonb text\n // (#5674). Org-scoped beats tenant-wide beats global for the same key \u2014\n // a same-key definition from an unrelated organization in the tenant\n // never gets to decide the kind (#5674 review).\n const cfSortKeys = Array.from(new Set(\n resolvedSorts.filter((sort) => sort.field.startsWith('cf:')).map((sort) => sort.field.slice(3))\n ))\n const cfSortKinds = new Map<string, string>()\n if (cfSortKeys.length > 0) {\n // `includeCustomFields === true` already resolved every definition for\n // these entities into `resolvedCustomFieldDefinitions` above \u2014 reuse it\n // instead of a second `custom_field_defs` round trip (#5674 review).\n const keysNeedingQuery: string[] = []\n for (const key of cfSortKeys) {\n const kind = resolvedCustomFieldDefinitions\n ? pickCfSortKind(resolvedCustomFieldDefinitions.index.get(normalizeDefinitionKey(key)))\n : null\n if (kind) cfSortKinds.set(key, kind)\n else keysNeedingQuery.push(key)\n }\n if (keysNeedingQuery.length > 0) {\n const sortEntityIds = Array.from(new Set(\n keysNeedingQuery\n .map((key) => keySource.get(key)?.entityId)\n .filter((id): id is EntityId => Boolean(id))\n .map((id) => String(id))\n ))\n if (sortEntityIds.length > 0) {\n const cfSortOrgCandidates = resolveCfDefIndexOrgCandidates(opts.organizationIds, opts.organizationId ?? null)\n const kindRows = await db\n .selectFrom('custom_field_defs' as any)\n .select(['key' as any, 'kind' as any, 'organization_id' as any, 'tenant_id' as any])\n .where('entity_id' as any, 'in', sortEntityIds)\n .where('key' as any, 'in', keysNeedingQuery)\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 .where((eb: any) => cfSortOrgCandidates.length\n ? eb.or([\n eb('organization_id' as any, 'is', null),\n eb('organization_id' as any, 'in', cfSortOrgCandidates),\n ])\n : eb('organization_id' as any, 'is', null))\n .execute() as Array<{ key: string; kind: string | null; organization_id: string | null; tenant_id: string | null }>\n const winners = new Map<string, { kind: string; specificity: number }>()\n for (const row of kindRows) {\n if (!row.kind) continue\n const specificity = row.organization_id != null ? 2 : row.tenant_id != null ? 1 : 0\n const existing = winners.get(row.key)\n if (!existing || specificity >= existing.specificity) winners.set(row.key, { kind: row.kind, specificity })\n }\n for (const [key, entry] of winners) cfSortKinds.set(key, entry.kind)\n }\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 const cfSortAliases: 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 = f.op === 'overlap'\n ? q.where(buildScalarOverlapPredicate(expr, f.value))\n : f.op === 'noverlap'\n ? q.where(sql<boolean>`false`)\n : 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 // This also means the #5803 plaintext-containment split (lib/search/containment) does not\n // apply here: `buildColumnOpExpression` below keeps a multi-word `like`/`ilike` leaf as one\n // literal pattern, so an OR-grouped search (e.g. `customers/api/people`'s multi-field\n // fallback) loses word-order independence on Basic while the Hybrid engine's OR groups\n // apply the split. Pre-existing divergence between the two engines; not tracked by an issue.\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 if (rf.op === 'noverlap') return sql<boolean>`false`\n return rf.op === 'overlap'\n ? buildScalarOverlapPredicate(cfValueExprByKey[rf.key], rf.value)\n : 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:* (a dedicated scalar alias, never the jsonb\n // projection alias \u2014 ordering by `to_jsonb(...)` compares arrays after every\n // scalar string regardless of contents, and a numeric kind must cast to\n // numeric instead of ordering the text CASE expression lexicographically (#5674)).\n let lastEmittedSortField: string | null = null\n for (const s of isCountProjection ? [] : resolvedSorts) {\n if (s.field.startsWith('cf:')) {\n const key = s.field.slice(3)\n const sortAlias = sanitize(`cf:${key}__sort`)\n if (!cfSortAliases.includes(sortAlias)) {\n const source = keySource.get(key)\n const kind = cfSortKinds.get(key)\n let sortExpr: RawBuilder<unknown> | null = null\n if (source && kind && NUMERIC_CF_SORT_KINDS.has(kind)) {\n const sourceAliasSafe = sanitize(source.alias || 'src')\n const keyAliasSafe = sanitize(key)\n const valAlias = `cfv_${sourceAliasSafe}_${keyAliasSafe}`\n const numericColumn = kind === 'integer' ? 'value_int' : 'value_float'\n sortExpr = sql<string | null>`max((${sql.ref(`${valAlias}.${numericColumn}`)})::numeric)`\n } else {\n const expr = cfValueExprByKey[key]\n if (expr) sortExpr = sql<string | null>`max(${expr})`\n }\n if (sortExpr) {\n q = q.select(sortExpr.as(sortAlias))\n cfSortAliases.push(sortAlias)\n }\n }\n // Only order by an alias the query actually selects. A key that resolved\n // to no expression is dropped rather than emitted as an unselected alias,\n // which is what the base-column branch below already does when\n // `resolveBaseColumn` returns null.\n if (!requiresPlaintextSort && cfSortAliases.includes(sortAlias)) {\n const direction = sql.raw((s.dir ?? 'asc') === 'desc' ? 'desc' : 'asc')\n q = q.orderBy(sql`${sql.ref(sortAlias)} ${direction} NULLS LAST`)\n lastEmittedSortField = s.field\n }\n } else {\n if (!requiresPlaintextSort) {\n q = q.orderBy(qualify(s.field), (s.dir ?? 'asc') as any)\n lastEmittedSortField = s.field\n }\n }\n }\n // Stable tiebreak so ties (or NULLs) don't reorder arbitrarily across pages\n // (#5674). Only appended when a sort term actually made it into the ORDER\n // BY \u2014 an entirely unresolved cf sort must stay a no-op, not gain an\n // incidental `id` ordering the caller never asked for.\n if (!isCountProjection && lastEmittedSortField !== null && lastEmittedSortField !== 'id') {\n q = q.orderBy(qualify('id'), 'asc' as any)\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, cfSortAliases, 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 cfSortAliases,\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 // The `cf:<key>__sort` alias exists only to give a `cf:` sort's ORDER BY\n // something to reference (and, for a numeric kind, to host the cast) \u2014 it\n // is never a requested output field. Strip it before rows leave this\n // function so it doesn't leak into API responses as a phantom custom field\n // `<key>__sort` (unlike `__is_multi`, which `normalizeCfJsonAliases`\n // already deletes) (#5674 review).\n const stripCfSortAliases = (rows: ResultRow[]) => {\n if (cfSortAliases.length === 0) return\n for (const row of rows) {\n for (const alias of cfSortAliases) delete row[alias]\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 stripCfSortAliases(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 stripCfSortAliases(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 // An empty list (\"no id survived validation\", #7502) compiles to invalid\n // SQL (`in ()`) under Kysely/Postgres \u2014 short-circuit to a predicate that\n // never matches instead of letting the driver reject the query.\n const inValues = Array.isArray(value) ? value : [value]\n return inValues.length === 0\n ? builder.where(sql<boolean>`false`)\n : builder.where(column as any, 'in', inValues)\n }\n case 'nin': {\n // Mirrors 'in' above: empty means \"exclude nothing\".\n const ninValues = Array.isArray(value) ? value : [value]\n return ninValues.length === 0\n ? builder.where(sql<boolean>`true`)\n : builder.where(column as any, 'not in', ninValues)\n }\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 case 'overlap':\n return builder.where(buildArrayColumnOverlapPredicate(column, value))\n case 'noverlap':\n return builder.where(buildArrayColumnNoOverlapPredicate(column, value))\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 case 'overlap':\n predicate = buildScalarOverlapPredicate(caseExpr, value)\n break\n case 'noverlap':\n return sql<boolean>`false`\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': {\n // Mirrors the guard in applyColumnOp above (#7502).\n const inValues = Array.isArray(value) ? value : [value]\n return inValues.length === 0 ? eb.val(false) : eb(column, 'in', inValues)\n }\n case 'nin': {\n const ninValues = Array.isArray(value) ? value : [value]\n return ninValues.length === 0 ? eb.val(true) : eb(column, 'not in', ninValues)\n }\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 case 'overlap': return buildArrayColumnOverlapPredicate(column, value)\n case 'noverlap': return buildArrayColumnNoOverlapPredicate(column, value)\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 case 'overlap':\n sub = sub.where(buildJsonbOverlapPredicate(`${alias}.doc`, opts.field, opts.value))\n break\n case 'noverlap':\n sub = sub.where(buildJsonbNoOverlapPredicate(`${alias}.doc`, opts.field, opts.value))\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", "cfSortAliases", "hasJoinedAggregates", "eb", "next"]
7
7
  }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.9.1-develop.7553.1.8dbab05b5a";
1
+ const APP_VERSION = "0.9.1-develop.7566.1.b9de0a5eb7";
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.9.1-develop.7553.1.8dbab05b5a';\nexport const appVersion = APP_VERSION;\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.9.1-develop.7566.1.b9de0a5eb7';\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.9.1-develop.7553.1.8dbab05b5a",
3
+ "version": "0.9.1-develop.7566.1.b9de0a5eb7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -121,7 +121,7 @@
121
121
  "@mikro-orm/core": "^7.1.14",
122
122
  "@mikro-orm/decorators": "^7.1.14",
123
123
  "@mikro-orm/postgresql": "^7.1.14",
124
- "@open-mercato/cache": "0.9.1-develop.7553.1.8dbab05b5a",
124
+ "@open-mercato/cache": "0.9.1-develop.7566.1.b9de0a5eb7",
125
125
  "@types/html-to-text": "^9.0.4",
126
126
  "@types/sanitize-html": "^2.16.1",
127
127
  "dotenv": "^17.4.2",
@@ -1,4 +1,4 @@
1
- import { parseLocaleNumber, parseNumberWithDefault, resolveLocaleNumberSeparators } from '../number'
1
+ import { formatLocaleNumberInput, parseLocaleNumber, parseNumberWithDefault, resolveLocaleNumberSeparators } from '../number'
2
2
 
3
3
  describe('parseNumberWithDefault', () => {
4
4
  it('returns the fallback when raw is missing or blank', () => {
@@ -121,3 +121,26 @@ describe('parseLocaleNumber', () => {
121
121
  expect(parseLocaleNumber('110.70', undefined)).toBe(110.7)
122
122
  })
123
123
  })
124
+
125
+ describe('formatLocaleNumberInput (issue #6309)', () => {
126
+ it('uses the locale decimal separator instead of the dot String() always emits', () => {
127
+ expect(formatLocaleNumberInput(2.5, 'pl-PL')).toBe('2,5')
128
+ expect(formatLocaleNumberInput(-2.5, 'pl-PL')).toBe('-2,5')
129
+ expect(formatLocaleNumberInput(1234.56, 'pl-PL')).toBe('1234,56')
130
+ expect(formatLocaleNumberInput(2.5, 'en-US')).toBe('2.5')
131
+ })
132
+
133
+ it('leaves integers untouched and applies no grouping', () => {
134
+ expect(formatLocaleNumberInput(1500, 'pl-PL')).toBe('1500')
135
+ expect(formatLocaleNumberInput(1500, 'en-US')).toBe('1500')
136
+ expect(formatLocaleNumberInput(0, 'pl-PL')).toBe('0')
137
+ })
138
+
139
+ it('round-trips through parseLocaleNumber', () => {
140
+ for (const value of [2.5, -0.125, 1234.56, 0.1 + 0.2, 7]) {
141
+ for (const locale of ['pl-PL', 'de-DE', 'en-US', 'fr-FR']) {
142
+ expect(parseLocaleNumber(formatLocaleNumberInput(value, locale), locale)).toBe(value)
143
+ }
144
+ }
145
+ })
146
+ })
package/src/lib/number.ts CHANGED
@@ -101,6 +101,19 @@ export function parseLocaleNumber(input: string | null | undefined, locale?: str
101
101
  return Number.isFinite(parsed) ? parsed : null
102
102
  }
103
103
 
104
+ /**
105
+ * Renders a number as the text an input field should show under `locale` — `2,5` under
106
+ * `pl-PL`, `2.5` under `en-US`. Unlike `String()`, which always emits a dot, the result uses
107
+ * the locale's decimal separator, so a value redisplayed after blur or reload matches what the
108
+ * user typed and what the rest of the UI shows. No grouping is applied, and the output always
109
+ * round-trips through `parseLocaleNumber`.
110
+ */
111
+ export function formatLocaleNumberInput(value: number, locale?: string): string {
112
+ const serialized = String(value)
113
+ const { decimal } = resolveLocaleNumberSeparators(locale)
114
+ return decimal === '.' ? serialized : serialized.replace('.', decimal)
115
+ }
116
+
104
117
  export function parseNumberWithDefault(
105
118
  raw: string | null | undefined,
106
119
  fallback: number,
@@ -0,0 +1,89 @@
1
+ /**
2
+ * End-to-end proof for #7502 against a real PostgreSQL: a `?ids=` filter that
3
+ * leaves no valid id (`{ id: { $in: [] } }`, the shape `mergeIdFilter` produces
4
+ * for an unparseable or disjoint id list) must answer an empty page, not a
5
+ * 42601 syntax error from Kysely compiling `in ()`.
6
+ *
7
+ * Gated on OM_COUNT_CAP_PG_URL (a throwaway database — the suite creates and
8
+ * drops its own table). Example:
9
+ * docker run --rm -d -p 54329:5432 -e POSTGRES_PASSWORD=t postgres:16
10
+ * OM_COUNT_CAP_PG_URL=postgres://postgres:t@localhost:54329/postgres yarn jest empty-ids-filter.pg
11
+ */
12
+ import { Kysely, PostgresDialect, sql } from 'kysely'
13
+ import { BasicQueryEngine } from '../engine'
14
+
15
+ const PG_URL = process.env.OM_COUNT_CAP_PG_URL
16
+ const maybe = PG_URL ? describe : describe.skip
17
+
18
+ const TENANT = '11111111-1111-4111-8111-111111111111'
19
+ const ORG = '22222222-2222-4222-8222-222222222222'
20
+ const ENTITY = 'capcheck:om_emptyids_order'
21
+ const TABLE = 'om_emptyids_orders'
22
+ const ID_A = '33333333-3333-4333-8333-000000000001'
23
+ const ID_B = '33333333-3333-4333-8333-000000000002'
24
+
25
+ maybe('empty $in/$nin filter against PostgreSQL', () => {
26
+ jest.setTimeout(30_000)
27
+ let db: Kysely<any>
28
+
29
+ beforeAll(async () => {
30
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
31
+ const { Pool } = require('pg')
32
+ db = new Kysely<any>({ dialect: new PostgresDialect({ pool: new Pool({ connectionString: PG_URL }) }) })
33
+ await sql`drop table if exists om_emptyids_orders`.execute(db)
34
+ await sql`create table om_emptyids_orders (
35
+ id uuid primary key,
36
+ tenant_id uuid not null,
37
+ organization_id uuid not null,
38
+ deleted_at timestamptz,
39
+ status text not null
40
+ )`.execute(db)
41
+ await sql`insert into om_emptyids_orders (id, tenant_id, organization_id, deleted_at, status) values
42
+ (${ID_A}::uuid, ${TENANT}::uuid, ${ORG}::uuid, null, 'open'),
43
+ (${ID_B}::uuid, ${TENANT}::uuid, ${ORG}::uuid, null, 'closed')`.execute(db)
44
+ })
45
+
46
+ afterAll(async () => {
47
+ await sql`drop table if exists om_emptyids_orders`.execute(db)
48
+ await db.destroy()
49
+ })
50
+
51
+ const makeEngine = () => new BasicQueryEngine({} as any, () => db as any)
52
+ const baseOpts = {
53
+ tenantId: TENANT,
54
+ organizationId: ORG,
55
+ fields: ['id'],
56
+ page: { page: 1, pageSize: 10 },
57
+ }
58
+
59
+ test('?ids= with no valid id returns an empty page instead of a 500 (AND path)', async () => {
60
+ const result = await makeEngine().query(ENTITY, {
61
+ ...baseOpts,
62
+ filters: { id: { $in: [] } },
63
+ })
64
+ expect(result.items).toEqual([])
65
+ expect(result.total).toBe(0)
66
+ })
67
+
68
+ test('a disjoint ?id=&?ids= intersection inside an $or group still matches the other disjunct', async () => {
69
+ const result = await makeEngine().query(ENTITY, {
70
+ ...baseOpts,
71
+ filters: {
72
+ $or: [
73
+ { id: { $in: [] } },
74
+ { status: { $eq: 'closed' } },
75
+ ],
76
+ },
77
+ })
78
+ expect(result.items.map((row: any) => row.id)).toEqual([ID_B])
79
+ expect(result.total).toBe(1)
80
+ })
81
+
82
+ test('an empty $nin excludes nothing, matching every row', async () => {
83
+ const result = await makeEngine().query(ENTITY, {
84
+ ...baseOpts,
85
+ filters: { id: { $nin: [] } },
86
+ })
87
+ expect(result.total).toBe(2)
88
+ })
89
+ })
@@ -0,0 +1,79 @@
1
+ /**
2
+ * #7502: `mergeIdFilter` deliberately produces `{ $in: [] }` for a `?ids=` param
3
+ * that left no valid UUID (or a disjoint `?id=`+`?ids=` intersection) — the
4
+ * correct "match nothing" semantics. Both engine where-builders used to pass
5
+ * that empty array straight to Kysely's `.where(column, 'in', [])`, which
6
+ * compiles to invalid SQL (`in ()`) and makes Postgres answer 42601/500
7
+ * instead of an empty page. These tests pin the guard directly on the two
8
+ * private builder methods — no real SQL execution needed to prove the shape
9
+ * changed; `count-cap-plan.test.ts`-style Postgres-gated tests cover the
10
+ * end-to-end compiled-SQL behavior.
11
+ */
12
+ import { BasicQueryEngine } from '../engine'
13
+
14
+ describe('applyColumnOp empty $in/$nin guard (#7502)', () => {
15
+ const engine = new BasicQueryEngine({} as any, () => ({}) as any)
16
+
17
+ function callApplyColumnOp(op: string, value: unknown) {
18
+ const calls: any[] = []
19
+ const builder: any = {
20
+ where: (...args: any[]) => {
21
+ calls.push(args)
22
+ return builder
23
+ },
24
+ }
25
+ ;(engine as any).applyColumnOp(builder, 'id', op, value)
26
+ return calls
27
+ }
28
+
29
+ test('empty $in does not reach Kysely as an empty in-list', () => {
30
+ const calls = callApplyColumnOp('in', [])
31
+ expect(calls).toHaveLength(1)
32
+ // Pre-fix this called builder.where('id', 'in', []) -- three arguments, the
33
+ // shape Kysely compiles to invalid SQL. Post-fix it is a single predicate arg.
34
+ expect(calls[0]).not.toEqual(['id', 'in', []])
35
+ expect(calls[0]).toHaveLength(1)
36
+ })
37
+
38
+ test('non-empty $in keeps the original in-list predicate', () => {
39
+ const calls = callApplyColumnOp('in', ['a'])
40
+ expect(calls[0]).toEqual(['id', 'in', ['a']])
41
+ })
42
+
43
+ test('empty $nin does not reach Kysely as an empty not-in-list', () => {
44
+ const calls = callApplyColumnOp('nin', [])
45
+ expect(calls[0]).toHaveLength(1)
46
+ expect(calls[0]).not.toEqual(['id', 'not in', []])
47
+ })
48
+
49
+ test('non-empty $nin keeps the original not-in-list predicate', () => {
50
+ const calls = callApplyColumnOp('nin', ['a'])
51
+ expect(calls[0]).toEqual(['id', 'not in', ['a']])
52
+ })
53
+ })
54
+
55
+ describe('buildColumnOpExpression empty $in/$nin guard (#7502, OR-group path)', () => {
56
+ const engine = new BasicQueryEngine({} as any, () => ({}) as any)
57
+
58
+ function callBuildColumnOpExpression(op: string, value: unknown) {
59
+ const eb: any = (...args: any[]) => ({ kind: 'cmp', args })
60
+ eb.val = (v: any) => ({ kind: 'val', value: v })
61
+ return (engine as any).buildColumnOpExpression(eb, 'id', op, value)
62
+ }
63
+
64
+ test('empty $in compiles to a never-match literal, not an empty in-list comparison', () => {
65
+ expect(callBuildColumnOpExpression('in', [])).toEqual({ kind: 'val', value: false })
66
+ })
67
+
68
+ test('non-empty $in keeps the original in-list comparison', () => {
69
+ expect(callBuildColumnOpExpression('in', ['a'])).toEqual({ kind: 'cmp', args: ['id', 'in', ['a']] })
70
+ })
71
+
72
+ test('empty $nin compiles to an always-match literal, not an empty not-in-list comparison', () => {
73
+ expect(callBuildColumnOpExpression('nin', [])).toEqual({ kind: 'val', value: true })
74
+ })
75
+
76
+ test('non-empty $nin keeps the original not-in-list comparison', () => {
77
+ expect(callBuildColumnOpExpression('nin', ['a'])).toEqual({ kind: 'cmp', args: ['id', 'not in', ['a']] })
78
+ })
79
+ })
@@ -1603,10 +1603,22 @@ export class BasicQueryEngine implements QueryEngine {
1603
1603
  return builder.where(column as any, '<', value as any)
1604
1604
  case 'lte':
1605
1605
  return builder.where(column as any, '<=', value as any)
1606
- case 'in':
1607
- return builder.where(column as any, 'in', Array.isArray(value) ? value : [value])
1608
- case 'nin':
1609
- return builder.where(column as any, 'not in', Array.isArray(value) ? value : [value])
1606
+ case 'in': {
1607
+ // An empty list ("no id survived validation", #7502) compiles to invalid
1608
+ // SQL (`in ()`) under Kysely/Postgres — short-circuit to a predicate that
1609
+ // never matches instead of letting the driver reject the query.
1610
+ const inValues = Array.isArray(value) ? value : [value]
1611
+ return inValues.length === 0
1612
+ ? builder.where(sql<boolean>`false`)
1613
+ : builder.where(column as any, 'in', inValues)
1614
+ }
1615
+ case 'nin': {
1616
+ // Mirrors 'in' above: empty means "exclude nothing".
1617
+ const ninValues = Array.isArray(value) ? value : [value]
1618
+ return ninValues.length === 0
1619
+ ? builder.where(sql<boolean>`true`)
1620
+ : builder.where(column as any, 'not in', ninValues)
1621
+ }
1610
1622
  case 'like':
1611
1623
  return builder.where(column as any, 'like', value as any)
1612
1624
  case 'ilike':
@@ -1774,8 +1786,15 @@ export class BasicQueryEngine implements QueryEngine {
1774
1786
  case 'gte': return eb(column, '>=', value)
1775
1787
  case 'lt': return eb(column, '<', value)
1776
1788
  case 'lte': return eb(column, '<=', value)
1777
- case 'in': return eb(column, 'in', Array.isArray(value) ? value : [value])
1778
- case 'nin': return eb(column, 'not in', Array.isArray(value) ? value : [value])
1789
+ case 'in': {
1790
+ // Mirrors the guard in applyColumnOp above (#7502).
1791
+ const inValues = Array.isArray(value) ? value : [value]
1792
+ return inValues.length === 0 ? eb.val(false) : eb(column, 'in', inValues)
1793
+ }
1794
+ case 'nin': {
1795
+ const ninValues = Array.isArray(value) ? value : [value]
1796
+ return ninValues.length === 0 ? eb.val(true) : eb(column, 'not in', ninValues)
1797
+ }
1779
1798
  case 'like': return eb(column, 'like', value)
1780
1799
  case 'ilike': return eb(column, 'ilike', value)
1781
1800
  case 'exists': return value ? eb(column, 'is not', null) : eb(column, 'is', null)