@open-mercato/shared 0.4.7-develop-4edba96002 → 0.4.7-develop-c89cca0193

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.
@@ -232,13 +232,28 @@ class BasicQueryEngine {
232
232
  return builder;
233
233
  };
234
234
  for (const filter of baseFilters) {
235
+ const fieldName = String(filter.field);
235
236
  let qualified = filter.qualified ?? null;
236
237
  if (!qualified) {
237
- const column = await this.resolveBaseColumn(table, String(filter.field));
238
- if (!column) continue;
238
+ const column = await this.resolveBaseColumn(table, fieldName);
239
+ if (!column) {
240
+ q = this.applyIndexDocFilter(q, {
241
+ entity: String(entity),
242
+ field: fieldName,
243
+ op: filter.op,
244
+ value: filter.value,
245
+ recordIdColumn,
246
+ tenantId: opts.tenantId ?? null,
247
+ organizationScope: orgScope,
248
+ withDeleted: opts.withDeleted === true,
249
+ searchActive,
250
+ searchConfig
251
+ });
252
+ continue;
253
+ }
239
254
  qualified = qualify(column);
240
255
  }
241
- applyFilterOp(q, qualified, filter.op, filter.value, String(filter.field));
256
+ applyFilterOp(q, qualified, filter.op, filter.value, fieldName);
242
257
  }
243
258
  const applyAliasScopes = async (builder, aliasName) => {
244
259
  const targetTable = aliasTables.get(aliasName);
@@ -658,6 +673,89 @@ class BasicQueryEngine {
658
673
  });
659
674
  return true;
660
675
  }
676
+ applyIndexDocFilter(q, opts) {
677
+ if ((opts.op === "like" || opts.op === "ilike") && opts.searchActive && typeof opts.value === "string") {
678
+ const tokens = tokenizeText(String(opts.value), opts.searchConfig);
679
+ const hashes = tokens.hashes;
680
+ if (hashes.length) {
681
+ const applied = this.applySearchTokens(q, {
682
+ entity: opts.entity,
683
+ field: opts.field,
684
+ hashes,
685
+ recordIdColumn: opts.recordIdColumn,
686
+ tenantId: opts.tenantId ?? null,
687
+ organizationScope: opts.organizationScope,
688
+ tokens: tokens.tokens
689
+ });
690
+ this.logSearchDebug("search:index-doc-filter", {
691
+ entity: opts.entity,
692
+ field: opts.field,
693
+ tokens: tokens.tokens,
694
+ hashes,
695
+ applied,
696
+ tenantId: opts.tenantId ?? null,
697
+ organizationScope: opts.organizationScope
698
+ });
699
+ if (applied) return q;
700
+ } else {
701
+ this.logSearchDebug("search:index-doc-skip-empty-hashes", {
702
+ entity: opts.entity,
703
+ field: opts.field,
704
+ value: opts.value
705
+ });
706
+ }
707
+ return q;
708
+ }
709
+ const knex = this.getKnexFn ? this.getKnexFn() : this.em.getConnection().getKnex();
710
+ const alias = `ei_${this.searchAliasSeq++}`;
711
+ const engine = this;
712
+ return q.whereExists(function() {
713
+ this.select(1).from({ [alias]: "entity_indexes" }).where(`${alias}.entity_type`, opts.entity).andWhereRaw("?? = ??::text", [`${alias}.entity_id`, opts.recordIdColumn]);
714
+ if (opts.tenantId !== void 0) {
715
+ this.andWhereRaw(`${alias}.tenant_id is not distinct from ?`, [opts.tenantId ?? null]);
716
+ }
717
+ if (opts.organizationScope) {
718
+ engine.applyOrganizationScope(this, `${alias}.organization_id`, opts.organizationScope);
719
+ }
720
+ if (!opts.withDeleted) {
721
+ this.whereNull(`${alias}.deleted_at`);
722
+ }
723
+ const text = knex.raw(`(${alias}.doc ->> ?)`, [opts.field]);
724
+ switch (opts.op) {
725
+ case "eq":
726
+ this.where(text, "=", opts.value);
727
+ break;
728
+ case "ne":
729
+ this.where(text, "!=", opts.value);
730
+ break;
731
+ case "gt":
732
+ case "gte":
733
+ case "lt":
734
+ case "lte": {
735
+ const operator = opts.op === "gt" ? ">" : opts.op === "gte" ? ">=" : opts.op === "lt" ? "<" : "<=";
736
+ this.where(text, operator, opts.value);
737
+ break;
738
+ }
739
+ case "in":
740
+ this.whereIn(text, Array.isArray(opts.value) ? opts.value : [opts.value]);
741
+ break;
742
+ case "nin":
743
+ this.whereNotIn(text, Array.isArray(opts.value) ? opts.value : [opts.value]);
744
+ break;
745
+ case "like":
746
+ this.where(text, "like", opts.value);
747
+ break;
748
+ case "ilike":
749
+ this.where(text, "ilike", opts.value);
750
+ break;
751
+ case "exists":
752
+ opts.value ? this.whereNotNull(text) : this.whereNull(text);
753
+ break;
754
+ default:
755
+ break;
756
+ }
757
+ });
758
+ }
661
759
  configureCustomFieldSources(q, baseTable, baseEntity, knex, opts, qualify) {
662
760
  const sources = [
663
761
  {
@@ -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, QueryCustomFieldSource, QueryExtensionsConfig } from './types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { Knex } from 'knex'\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 { tokenizeText } from '../search/tokenize'\nimport { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from './query-extension-runner'\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 isEnabled?: () => boolean\n} | null\n\ntype ResolvedCustomFieldSource = {\n entityId: EntityId\n alias: string\n table: string\n recordIdExpr: any\n}\n\ntype ResultRow = Record<string, unknown>\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\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\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 const metadata = (em as any)?.getMetadata?.()\n\n if (metadata && rawName) {\n const candidates = candidateClassNames(rawName)\n for (const candidate of candidates) {\n try {\n const meta = metadata.find?.(candidate)\n if (meta?.tableName) {\n const tableName = String(meta.tableName)\n entityTableCache.set(entity, tableName)\n return tableName\n }\n } catch {}\n }\n }\n\n const fallback = pluralizeBaseName(rawName || '')\n entityTableCache.set(entity, fallback)\n return fallback\n}\n\n\n// Minimal default implementation placeholder.\n// For now, only supports basic base-entity querying by table name inferred from EntityId ('<module>:<entity>' -> '<entities>') via convention.\n// Extensions and custom fields will be added iteratively.\n\nexport class BasicQueryEngine implements QueryEngine {\n private columnCache = new Map<string, boolean>()\n private tableCache = new Map<string, boolean>()\n private searchAliasSeq = 0\n\n constructor(\n private em: EntityManager,\n private getKnexFn?: () => any,\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 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 knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()\n\n let q = knex(table)\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 // Optional organization filter (when present in schema)\n if (orgScope && await this.columnExists(table, 'organization_id')) {\n q = this.applyOrganizationScope(q, qualify('organization_id'), orgScope)\n }\n // Tenant guard (required) when present in schema\n if (await this.columnExists(table, 'tenant_id')) {\n q = q.where(qualify('tenant_id'), opts.tenantId)\n }\n // Default soft-delete guard: exclude rows with deleted_at when column exists\n if (!opts.withDeleted && await this.columnExists(table, 'deleted_at')) {\n q = q.whereNull(qualify('deleted_at'))\n }\n\n const normalizedFilters = normalizeFilters(opts.filters)\n const resolvedJoins = resolveJoins(table, opts.joins, (entityId) => resolveEntityTableName(this.em, entityId as any))\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 const searchConfig = resolveSearchConfig()\n const searchEnabled = searchConfig.enabled && await this.tableExists('search_tokens')\n const hasSearchTokens = searchEnabled\n ? await this.hasSearchTokens(String(entity), opts.tenantId ?? null, orgScope)\n : false\n const searchActive = searchEnabled && hasSearchTokens\n const searchFilters = [...baseFilters, ...cfFilters].filter((filter) => filter.op === 'like' || filter.op === 'ilike')\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 }\n const recordIdColumn = qualify('id')\n\n const applyFilterOp = (builder: any, column: string, op: any, value: any, fieldName?: string) => {\n if (\n (op === 'like' || op === 'ilike') &&\n searchActive &&\n typeof value === 'string' &&\n fieldName\n ) {\n const tokens = tokenizeText(String(value), searchConfig)\n const hashes = tokens.hashes\n if (hashes.length) {\n const applied = 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,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n if (applied) return builder\n } else {\n this.logSearchDebug('search:skip-empty-hashes', {\n entity: String(entity),\n field: fieldName,\n value,\n })\n }\n }\n switch (op) {\n case 'eq': builder.where(column, value); break\n case 'ne': builder.whereNot(column, value); break\n case 'gt': builder.where(column, '>', value); break\n case 'gte': builder.where(column, '>=', value); break\n case 'lt': builder.where(column, '<', value); break\n case 'lte': builder.where(column, '<=', value); break\n case 'in': builder.whereIn(column, Array.isArray(value) ? value : [value]); break\n case 'nin': builder.whereNotIn(column, Array.isArray(value) ? value : [value]); break\n case 'like': builder.where(column, 'like', value); break\n case 'ilike': builder.where(column, 'ilike', value); break\n case 'exists': value ? builder.whereNotNull(column) : builder.whereNull(column); break\n default: break\n }\n return builder\n }\n\n for (const filter of baseFilters) {\n let qualified = filter.qualified ?? null\n if (!qualified) {\n const column = await this.resolveBaseColumn(table, String(filter.field))\n if (!column) continue\n qualified = qualify(column)\n }\n applyFilterOp(q, qualified, filter.op, filter.value, String(filter.field))\n }\n\n const applyAliasScopes = async (builder: any, aliasName: string) => {\n const targetTable = aliasTables.get(aliasName)\n if (!targetTable) return\n if (orgScope && await this.columnExists(targetTable, 'organization_id')) {\n this.applyOrganizationScope(builder, `${aliasName}.organization_id`, orgScope)\n }\n if (opts.tenantId && await this.columnExists(targetTable, 'tenant_id')) {\n builder.where(`${aliasName}.tenant_id`, opts.tenantId)\n }\n }\n await applyJoinFilters({\n knex,\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,\n columnExists: (tbl, column) => this.columnExists(tbl, column),\n })\n // Selection (base columns only here; cf:* handled later)\n if (opts.fields && opts.fields.length) {\n const cols = opts.fields.filter((f) => !f.startsWith('cf:'))\n if (cols.length) {\n // Qualify and alias to base names to avoid ambiguity\n const baseSelects = cols.map((c) => knex.raw('?? as ??', [qualify(c), c]))\n q = q.select(baseSelects)\n }\n } else {\n // Default to selecting only base table columns to avoid ambiguity when joining\n q = q.select(knex.raw('??.*', [table]))\n }\n\n // Resolve which custom fields to include\n const tenantId = opts.tenantId\n const sanitize = (s: string) => s.replace(/[^a-zA-Z0-9_]/g, '_')\n const cfSources = this.configureCustomFieldSources(q, table, entity, knex, opts, qualify)\n const entityIdToSource = new Map<string, ResolvedCustomFieldSource>()\n for (const source of cfSources) {\n entityIdToSource.set(String(source.entityId), source)\n }\n const requestedCustomFieldKeys = 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 // Explicit in fields/filters\n for (const f of (opts.fields || [])) {\n if (typeof f === 'string' && f.startsWith('cf:')) cfKeys.add(f.slice(3))\n }\n for (const f of cfFilters) {\n if (typeof f.field === 'string' && f.field.startsWith('cf:')) cfKeys.add(f.field.slice(3))\n }\n if (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 knex('custom_field_defs')\n .select('key', 'entity_id', 'config_json', 'kind')\n .whereIn('entity_id', entityIdList)\n .andWhere('is_active', true)\n .modify((qb: any) => {\n qb.andWhere((inner: any) => {\n inner.where({ tenant_id: tenantId }).orWhereNull('tenant_id')\n })\n })\n type CustomFieldDefinitionRow = {\n key: string\n entityId: string\n kind: string\n config: Record<string, unknown>\n }\n const sorted: CustomFieldDefinitionRow[] = rows.map((row: any) => {\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\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: CustomFieldDefinitionRow, b: CustomFieldDefinitionRow) => {\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 = computeScore(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 (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 knex('custom_field_defs')\n .select('key', 'entity_id')\n .whereIn('entity_id', Array.from(entityIdToSource.keys()))\n .whereIn('key', unresolvedKeys)\n .andWhere('is_active', true)\n .modify((qb: any) => {\n qb.andWhere((inner: any) => {\n inner.where({ tenant_id: tenantId }).orWhereNull('tenant_id')\n })\n })\n for (const row of rows) {\n const source = entityIdToSource.get(String(row.entity_id))\n if (!source) continue\n if (!keySource.has(row.key)) keySource.set(row.key, source)\n }\n }\n\n const cfValueExprByKey: Record<string, any> = {}\n const cfSelectedAliases: string[] = []\n const cfJsonAliases = new Set<string>()\n const cfMultiAliasByAlias = new Map<string, string>()\n for (const key of cfKeys) {\n const source = keySource.get(key)\n if (!source) continue\n 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({ [defAlias]: 'custom_field_defs' }, function (this: any) {\n this.on(`${defAlias}.entity_id`, '=', knex.raw('?', [entityIdForKey]))\n .andOn(`${defAlias}.key`, '=', knex.raw('?', [key]))\n .andOn(`${defAlias}.is_active`, '=', knex.raw('true'))\n .andOn(knex.raw(`(${defAlias}.tenant_id = ? OR ${defAlias}.tenant_id IS NULL)`, [tenantId]))\n })\n // Join values with record match\n q = q.leftJoin({ [valAlias]: 'custom_field_values' }, function (this: any) {\n this.on(`${valAlias}.entity_id`, '=', knex.raw('?', [entityIdForKey]))\n .andOn(`${valAlias}.field_key`, '=', knex.raw('?', [key]))\n .andOn(`${valAlias}.record_id`, '=', recordIdExpr)\n .andOn(knex.raw(`(${valAlias}.tenant_id = ? OR ${valAlias}.tenant_id IS NULL)`, [tenantId]))\n })\n // Force a common SQL type across branches to avoid Postgres CASE type conflicts\n const caseExpr = knex.raw(\n `CASE ${defAlias}.kind\n WHEN 'integer' THEN (${valAlias}.value_int)::text\n WHEN 'float' THEN (${valAlias}.value_float)::text\n WHEN 'boolean' THEN (${valAlias}.value_bool)::text\n WHEN 'multiline' THEN (${valAlias}.value_multiline)::text\n ELSE (${valAlias}.value_text)::text\n END`\n )\n cfValueExprByKey[key] = caseExpr\n const alias = sanitize(`cf:${key}`)\n // Project as aggregated to avoid duplicates when multi values exist\n if ((opts.fields || []).includes(`cf:${key}`) || opts.includeCustomFields === true || (requestedCustomFieldKeys.length > 0 && requestedCustomFieldKeys.includes(key))) {\n // Use bool_or over config_json->>multi so it's valid under GROUP BY\n const isMulti = knex.raw(`bool_or(coalesce((${defAlias}.config_json->>'multi')::boolean, false))`)\n const aggregatedArray = `array_remove(array_agg(DISTINCT ${caseExpr.toString()}), NULL)`\n const expr = `CASE WHEN ${isMulti.toString()}\n THEN to_jsonb(${aggregatedArray})\n ELSE to_jsonb(max(${caseExpr.toString()}))\n END`\n const multiAlias = `${alias}__is_multi`\n q = q.select(knex.raw(`${expr} as ??`, [alias]))\n q = q.select(knex.raw(`${isMulti.toString()} 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)\n for (const f of cfFilters) {\n if (!f.field.startsWith('cf:')) continue\n const key = f.field.slice(3)\n const expr = cfValueExprByKey[key]\n if (!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 applied = 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,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n if (applied) continue\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 switch (f.op) {\n case 'eq': q = q.where(expr, '=', f.value); break\n case 'ne': q = q.where(expr, '!=', f.value); break\n case 'gt': q = q.where(expr, '>', f.value); break\n case 'gte': q = q.where(expr, '>=', f.value); break\n case 'lt': q = q.where(expr, '<', f.value); break\n case 'lte': q = q.where(expr, '<=', f.value); break\n case 'in': q = q.whereIn(expr as any, f.value ?? []); break\n case 'nin': q = q.whereNotIn(expr as any, f.value ?? []); break\n case 'like': q = q.where(expr, 'like', f.value); break\n case 'ilike': q = q.where(expr, 'ilike', f.value); break\n case 'exists': f.value ? q = q.whereNotNull(expr) : q = q.whereNull(expr); break\n }\n }\n\n // Entity extensions joins (no selection yet; enables future filters/projections)\n if (opts.includeExtensions) {\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 const extTable = extName.endsWith('s') ? extName : `${extName}s`\n const alias = `ext_${sanitize(extName)}`\n q = q.leftJoin({ [alias]: extTable }, function (this: any) {\n this.on(`${alias}.${e.join.extensionKey}`, '=', knex.raw('??', [`${table}.${e.join.baseKey}`]))\n })\n }\n }\n\n // Sorting: base fields and cf:* (use aggregated alias for cf)\n for (const s of opts.sort || []) {\n if (s.field.startsWith('cf:')) {\n const key = s.field.slice(3)\n const alias = sanitize(`cf:${key}`)\n // Ensure included in projection to sort by\n if (!cfSelectedAliases.includes(alias)) {\n const expr = cfValueExprByKey[key]\n if (expr) {\n q = q.select(knex.raw(`max(${expr.toString()}) as ??`, [alias]))\n cfSelectedAliases.push(alias)\n }\n }\n q = q.orderBy(alias, s.dir ?? 'asc')\n } else {\n const column = await this.resolveBaseColumn(table, s.field)\n if (!column) continue\n q = q.orderBy(qualify(column), s.dir ?? 'asc')\n }\n }\n\n // Pagination\n const page = opts.page?.page ?? 1\n const pageSize = opts.page?.pageSize ?? 20\n // Deduplicate if we joined CFs or extensions by grouping on base id\n if ((opts.includeExtensions && (Array.isArray(opts.includeExtensions) ? (opts.includeExtensions.length > 0) : true)) || Object.keys(cfValueExprByKey).length > 0) {\n q = q.groupBy(`${table}.id`)\n }\n const countClone: any = q.clone()\n if (typeof countClone.clearSelect === 'function') countClone.clearSelect()\n if (typeof countClone.clearOrder === 'function') countClone.clearOrder()\n if (typeof countClone.clearGroup === 'function') countClone.clearGroup()\n const countRow = await countClone\n .countDistinct(`${table}.id as count`)\n .first()\n const total = Number((countRow as any)?.count ?? 0)\n const items = await q.limit(pageSize).offset((page - 1) * pageSize)\n\n if (cfJsonAliases.size > 0) {\n for (const row of items as any[]) {\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 const svc = this.getEncryptionService()\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 let decryptedItems = items\n if (decryptPayload) {\n const fallbackOrgId =\n opts.organizationId\n ?? (Array.isArray(opts.organizationIds) && opts.organizationIds.length === 1 ? opts.organizationIds[0] : null)\n decryptedItems = await Promise.all(\n (items as any[]).map(async (item) => {\n try {\n const decrypted = await decryptPayload(\n entity,\n item,\n item?.tenant_id ?? item?.tenantId ?? opts.tenantId ?? null,\n item?.organization_id ?? item?.organizationId ?? fallbackOrgId ?? null,\n )\n return { ...item, ...decrypted }\n } catch (err) {\n console.error('QueryEngine: error decrypting entity payload', err);\n return item\n }\n })\n )\n }\n\n let queryResult: QueryResult<T> = { items: decryptedItems, page, pageSize, total }\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 return queryResult\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 if (this.columnCache.has(key)) {\n const cached = this.columnCache.get(key)\n if (cached === true) return true\n this.columnCache.delete(key)\n }\n const knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()\n const exists = await knex('information_schema.columns')\n .where({ table_name: table, column_name: column })\n .first()\n const present = !!exists\n if (present) this.columnCache.set(key, true)\n else this.columnCache.delete(key)\n return present\n }\n\n private async tableExists(table: string): Promise<boolean> {\n if (this.tableCache.has(table)) return this.tableCache.get(table) ?? false\n const knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()\n const exists = await knex('information_schema.tables')\n .where({ table_name: table })\n .first()\n const present = !!exists\n this.tableCache.set(table, present)\n return present\n }\n\n private async hasSearchTokens(\n entity: string,\n tenantId: string | null,\n orgScope?: { ids: string[]; includeNull: boolean } | null\n ): Promise<boolean> {\n try {\n const knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()\n const query = knex('search_tokens').select(1).where('entity_type', entity).limit(1)\n if (tenantId !== undefined) {\n query.andWhereRaw('tenant_id is not distinct from ?', [tenantId])\n }\n if (orgScope) {\n this.applyOrganizationScope(query as any, 'search_tokens.organization_id', orgScope)\n }\n const row = await query.first()\n return !!row\n } catch (err) {\n this.logSearchDebug('search:has-tokens-error', {\n entity,\n tenantId,\n organizationScope: orgScope,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n\n private applySearchTokens<TRecord extends ResultRow, TResult>(\n q: Knex.QueryBuilder<TRecord, TResult>,\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 ): boolean {\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 false\n }\n const alias = `st_${this.searchAliasSeq++}`\n const combineWith = opts.combineWith === 'or' ? 'orWhereExists' : 'whereExists'\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 ;(q as any)[combineWith](function (this: Knex.QueryBuilder) {\n this.select(1)\n .from({ [alias]: 'search_tokens' })\n .where(`${alias}.entity_type`, opts.entity)\n .andWhere(`${alias}.field`, opts.field)\n .andWhereRaw('?? = ??::text', [`${alias}.entity_id`, opts.recordIdColumn])\n .whereIn(`${alias}.token_hash`, opts.hashes)\n .groupBy(`${alias}.entity_id`, `${alias}.field`)\n .havingRaw(`count(distinct ${alias}.token_hash) >= ?`, [opts.hashes.length])\n if (opts.tenantId !== undefined) {\n this.andWhereRaw(`${alias}.tenant_id is not distinct from ?`, [opts.tenantId ?? null])\n }\n if (opts.organizationScope) {\n engine.applyOrganizationScope(this as any, `${alias}.organization_id`, opts.organizationScope)\n }\n })\n return true\n }\n\n private configureCustomFieldSources(\n q: any,\n baseTable: string,\n baseEntity: EntityId,\n knex: any,\n opts: QueryOptions,\n qualify: (column: string) => string\n ): ResolvedCustomFieldSource[] {\n const sources: ResolvedCustomFieldSource[] = [\n {\n entityId: baseEntity,\n alias: 'base',\n table: baseTable,\n recordIdExpr: knex.raw('??::text', [`${baseTable}.id`]),\n },\n ]\n const extras: QueryCustomFieldSource[] = opts.customFieldSources ?? []\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 joinArgs = { [alias]: joinTable }\n const joinCallback = function (this: any) {\n this.on(`${alias}.${join.toField}`, '=', qualify(join.fromField))\n }\n const joinType = join.type ?? 'left'\n if (joinType === 'inner') q.join(joinArgs, joinCallback)\n else q.leftJoin(joinArgs, joinCallback)\n const recordColumn = srcOpt.recordIdColumn ?? 'id'\n sources.push({\n entityId: srcOpt.entityId,\n alias,\n table: joinTable,\n recordIdExpr: knex.raw('??::text', [`${alias}.${recordColumn}`]),\n })\n })\n return sources\n }\n\n private logSearchDebug(event: string, payload: Record<string, unknown>) {\n try {\n console.info('[query:search]', event, JSON.stringify(payload))\n } catch {\n console.info('[query:search]', event, payload)\n }\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: any, column: string, scope: { ids: string[]; includeNull: boolean }): any {\n if (!scope) return q\n if (scope.ids.length === 0 && !scope.includeNull) {\n return q.whereRaw('1 = 0')\n }\n return q.where((builder: any) => {\n let applied = false\n if (scope.ids.length > 0) {\n builder.whereIn(column as any, scope.ids)\n applied = true\n }\n if (scope.includeNull) {\n if (applied) builder.orWhereNull(column)\n else builder.whereNull(column)\n applied = true\n }\n if (!applied) builder.whereRaw('1 = 0')\n })\n }\n\n}\n const computeScore = (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':\n return 8\n case 'relation':\n return 6\n case 'select':\n return 4\n case 'multiline':\n return 3\n case 'boolean':\n case 'integer':\n case 'float':\n return 2\n default:\n 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.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"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { QueryEngine, QueryOptions, QueryResult, QueryCustomFieldSource, QueryExtensionsConfig } from './types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { Knex } from 'knex'\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 { tokenizeText } from '../search/tokenize'\nimport { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from './query-extension-runner'\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 isEnabled?: () => boolean\n} | null\n\ntype ResolvedCustomFieldSource = {\n entityId: EntityId\n alias: string\n table: string\n recordIdExpr: any\n}\n\ntype ResultRow = Record<string, unknown>\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\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\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 const metadata = (em as any)?.getMetadata?.()\n\n if (metadata && rawName) {\n const candidates = candidateClassNames(rawName)\n for (const candidate of candidates) {\n try {\n const meta = metadata.find?.(candidate)\n if (meta?.tableName) {\n const tableName = String(meta.tableName)\n entityTableCache.set(entity, tableName)\n return tableName\n }\n } catch {}\n }\n }\n\n const fallback = pluralizeBaseName(rawName || '')\n entityTableCache.set(entity, fallback)\n return fallback\n}\n\n\n// Minimal default implementation placeholder.\n// For now, only supports basic base-entity querying by table name inferred from EntityId ('<module>:<entity>' -> '<entities>') via convention.\n// Extensions and custom fields will be added iteratively.\n\nexport class BasicQueryEngine implements QueryEngine {\n private columnCache = new Map<string, boolean>()\n private tableCache = new Map<string, boolean>()\n private searchAliasSeq = 0\n\n constructor(\n private em: EntityManager,\n private getKnexFn?: () => any,\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 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 knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()\n\n let q = knex(table)\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 // Optional organization filter (when present in schema)\n if (orgScope && await this.columnExists(table, 'organization_id')) {\n q = this.applyOrganizationScope(q, qualify('organization_id'), orgScope)\n }\n // Tenant guard (required) when present in schema\n if (await this.columnExists(table, 'tenant_id')) {\n q = q.where(qualify('tenant_id'), opts.tenantId)\n }\n // Default soft-delete guard: exclude rows with deleted_at when column exists\n if (!opts.withDeleted && await this.columnExists(table, 'deleted_at')) {\n q = q.whereNull(qualify('deleted_at'))\n }\n\n const normalizedFilters = normalizeFilters(opts.filters)\n const resolvedJoins = resolveJoins(table, opts.joins, (entityId) => resolveEntityTableName(this.em, entityId as any))\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 const searchConfig = resolveSearchConfig()\n const searchEnabled = searchConfig.enabled && await this.tableExists('search_tokens')\n const hasSearchTokens = searchEnabled\n ? await this.hasSearchTokens(String(entity), opts.tenantId ?? null, orgScope)\n : false\n const searchActive = searchEnabled && hasSearchTokens\n const searchFilters = [...baseFilters, ...cfFilters].filter((filter) => filter.op === 'like' || filter.op === 'ilike')\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 }\n const recordIdColumn = qualify('id')\n\n const applyFilterOp = (builder: any, column: string, op: any, value: any, fieldName?: string) => {\n if (\n (op === 'like' || op === 'ilike') &&\n searchActive &&\n typeof value === 'string' &&\n fieldName\n ) {\n const tokens = tokenizeText(String(value), searchConfig)\n const hashes = tokens.hashes\n if (hashes.length) {\n const applied = 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,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n if (applied) return builder\n } else {\n this.logSearchDebug('search:skip-empty-hashes', {\n entity: String(entity),\n field: fieldName,\n value,\n })\n }\n }\n switch (op) {\n case 'eq': builder.where(column, value); break\n case 'ne': builder.whereNot(column, value); break\n case 'gt': builder.where(column, '>', value); break\n case 'gte': builder.where(column, '>=', value); break\n case 'lt': builder.where(column, '<', value); break\n case 'lte': builder.where(column, '<=', value); break\n case 'in': builder.whereIn(column, Array.isArray(value) ? value : [value]); break\n case 'nin': builder.whereNotIn(column, Array.isArray(value) ? value : [value]); break\n case 'like': builder.where(column, 'like', value); break\n case 'ilike': builder.where(column, 'ilike', value); break\n case 'exists': value ? builder.whereNotNull(column) : builder.whereNull(column); break\n default: break\n }\n return builder\n }\n\n for (const filter of baseFilters) {\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 applyFilterOp(q, qualified, filter.op, filter.value, fieldName)\n }\n\n const applyAliasScopes = async (builder: any, aliasName: string) => {\n const targetTable = aliasTables.get(aliasName)\n if (!targetTable) return\n if (orgScope && await this.columnExists(targetTable, 'organization_id')) {\n this.applyOrganizationScope(builder, `${aliasName}.organization_id`, orgScope)\n }\n if (opts.tenantId && await this.columnExists(targetTable, 'tenant_id')) {\n builder.where(`${aliasName}.tenant_id`, opts.tenantId)\n }\n }\n await applyJoinFilters({\n knex,\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,\n columnExists: (tbl, column) => this.columnExists(tbl, column),\n })\n // Selection (base columns only here; cf:* handled later)\n if (opts.fields && opts.fields.length) {\n const cols = opts.fields.filter((f) => !f.startsWith('cf:'))\n if (cols.length) {\n // Qualify and alias to base names to avoid ambiguity\n const baseSelects = cols.map((c) => knex.raw('?? as ??', [qualify(c), c]))\n q = q.select(baseSelects)\n }\n } else {\n // Default to selecting only base table columns to avoid ambiguity when joining\n q = q.select(knex.raw('??.*', [table]))\n }\n\n // Resolve which custom fields to include\n const tenantId = opts.tenantId\n const sanitize = (s: string) => s.replace(/[^a-zA-Z0-9_]/g, '_')\n const cfSources = this.configureCustomFieldSources(q, table, entity, knex, opts, qualify)\n const entityIdToSource = new Map<string, ResolvedCustomFieldSource>()\n for (const source of cfSources) {\n entityIdToSource.set(String(source.entityId), source)\n }\n const requestedCustomFieldKeys = 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 // Explicit in fields/filters\n for (const f of (opts.fields || [])) {\n if (typeof f === 'string' && f.startsWith('cf:')) cfKeys.add(f.slice(3))\n }\n for (const f of cfFilters) {\n if (typeof f.field === 'string' && f.field.startsWith('cf:')) cfKeys.add(f.field.slice(3))\n }\n if (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 knex('custom_field_defs')\n .select('key', 'entity_id', 'config_json', 'kind')\n .whereIn('entity_id', entityIdList)\n .andWhere('is_active', true)\n .modify((qb: any) => {\n qb.andWhere((inner: any) => {\n inner.where({ tenant_id: tenantId }).orWhereNull('tenant_id')\n })\n })\n type CustomFieldDefinitionRow = {\n key: string\n entityId: string\n kind: string\n config: Record<string, unknown>\n }\n const sorted: CustomFieldDefinitionRow[] = rows.map((row: any) => {\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\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: CustomFieldDefinitionRow, b: CustomFieldDefinitionRow) => {\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 = computeScore(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 (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 knex('custom_field_defs')\n .select('key', 'entity_id')\n .whereIn('entity_id', Array.from(entityIdToSource.keys()))\n .whereIn('key', unresolvedKeys)\n .andWhere('is_active', true)\n .modify((qb: any) => {\n qb.andWhere((inner: any) => {\n inner.where({ tenant_id: tenantId }).orWhereNull('tenant_id')\n })\n })\n for (const row of rows) {\n const source = entityIdToSource.get(String(row.entity_id))\n if (!source) continue\n if (!keySource.has(row.key)) keySource.set(row.key, source)\n }\n }\n\n const cfValueExprByKey: Record<string, any> = {}\n const cfSelectedAliases: string[] = []\n const cfJsonAliases = new Set<string>()\n const cfMultiAliasByAlias = new Map<string, string>()\n for (const key of cfKeys) {\n const source = keySource.get(key)\n if (!source) continue\n 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({ [defAlias]: 'custom_field_defs' }, function (this: any) {\n this.on(`${defAlias}.entity_id`, '=', knex.raw('?', [entityIdForKey]))\n .andOn(`${defAlias}.key`, '=', knex.raw('?', [key]))\n .andOn(`${defAlias}.is_active`, '=', knex.raw('true'))\n .andOn(knex.raw(`(${defAlias}.tenant_id = ? OR ${defAlias}.tenant_id IS NULL)`, [tenantId]))\n })\n // Join values with record match\n q = q.leftJoin({ [valAlias]: 'custom_field_values' }, function (this: any) {\n this.on(`${valAlias}.entity_id`, '=', knex.raw('?', [entityIdForKey]))\n .andOn(`${valAlias}.field_key`, '=', knex.raw('?', [key]))\n .andOn(`${valAlias}.record_id`, '=', recordIdExpr)\n .andOn(knex.raw(`(${valAlias}.tenant_id = ? OR ${valAlias}.tenant_id IS NULL)`, [tenantId]))\n })\n // Force a common SQL type across branches to avoid Postgres CASE type conflicts\n const caseExpr = knex.raw(\n `CASE ${defAlias}.kind\n WHEN 'integer' THEN (${valAlias}.value_int)::text\n WHEN 'float' THEN (${valAlias}.value_float)::text\n WHEN 'boolean' THEN (${valAlias}.value_bool)::text\n WHEN 'multiline' THEN (${valAlias}.value_multiline)::text\n ELSE (${valAlias}.value_text)::text\n END`\n )\n cfValueExprByKey[key] = caseExpr\n const alias = sanitize(`cf:${key}`)\n // Project as aggregated to avoid duplicates when multi values exist\n if ((opts.fields || []).includes(`cf:${key}`) || opts.includeCustomFields === true || (requestedCustomFieldKeys.length > 0 && requestedCustomFieldKeys.includes(key))) {\n // Use bool_or over config_json->>multi so it's valid under GROUP BY\n const isMulti = knex.raw(`bool_or(coalesce((${defAlias}.config_json->>'multi')::boolean, false))`)\n const aggregatedArray = `array_remove(array_agg(DISTINCT ${caseExpr.toString()}), NULL)`\n const expr = `CASE WHEN ${isMulti.toString()}\n THEN to_jsonb(${aggregatedArray})\n ELSE to_jsonb(max(${caseExpr.toString()}))\n END`\n const multiAlias = `${alias}__is_multi`\n q = q.select(knex.raw(`${expr} as ??`, [alias]))\n q = q.select(knex.raw(`${isMulti.toString()} 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)\n for (const f of cfFilters) {\n if (!f.field.startsWith('cf:')) continue\n const key = f.field.slice(3)\n const expr = cfValueExprByKey[key]\n if (!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 applied = 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,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n })\n if (applied) continue\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 switch (f.op) {\n case 'eq': q = q.where(expr, '=', f.value); break\n case 'ne': q = q.where(expr, '!=', f.value); break\n case 'gt': q = q.where(expr, '>', f.value); break\n case 'gte': q = q.where(expr, '>=', f.value); break\n case 'lt': q = q.where(expr, '<', f.value); break\n case 'lte': q = q.where(expr, '<=', f.value); break\n case 'in': q = q.whereIn(expr as any, f.value ?? []); break\n case 'nin': q = q.whereNotIn(expr as any, f.value ?? []); break\n case 'like': q = q.where(expr, 'like', f.value); break\n case 'ilike': q = q.where(expr, 'ilike', f.value); break\n case 'exists': f.value ? q = q.whereNotNull(expr) : q = q.whereNull(expr); break\n }\n }\n\n // Entity extensions joins (no selection yet; enables future filters/projections)\n if (opts.includeExtensions) {\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 const extTable = extName.endsWith('s') ? extName : `${extName}s`\n const alias = `ext_${sanitize(extName)}`\n q = q.leftJoin({ [alias]: extTable }, function (this: any) {\n this.on(`${alias}.${e.join.extensionKey}`, '=', knex.raw('??', [`${table}.${e.join.baseKey}`]))\n })\n }\n }\n\n // Sorting: base fields and cf:* (use aggregated alias for cf)\n for (const s of opts.sort || []) {\n if (s.field.startsWith('cf:')) {\n const key = s.field.slice(3)\n const alias = sanitize(`cf:${key}`)\n // Ensure included in projection to sort by\n if (!cfSelectedAliases.includes(alias)) {\n const expr = cfValueExprByKey[key]\n if (expr) {\n q = q.select(knex.raw(`max(${expr.toString()}) as ??`, [alias]))\n cfSelectedAliases.push(alias)\n }\n }\n q = q.orderBy(alias, s.dir ?? 'asc')\n } else {\n const column = await this.resolveBaseColumn(table, s.field)\n if (!column) continue\n q = q.orderBy(qualify(column), s.dir ?? 'asc')\n }\n }\n\n // Pagination\n const page = opts.page?.page ?? 1\n const pageSize = opts.page?.pageSize ?? 20\n // Deduplicate if we joined CFs or extensions by grouping on base id\n if ((opts.includeExtensions && (Array.isArray(opts.includeExtensions) ? (opts.includeExtensions.length > 0) : true)) || Object.keys(cfValueExprByKey).length > 0) {\n q = q.groupBy(`${table}.id`)\n }\n const countClone: any = q.clone()\n if (typeof countClone.clearSelect === 'function') countClone.clearSelect()\n if (typeof countClone.clearOrder === 'function') countClone.clearOrder()\n if (typeof countClone.clearGroup === 'function') countClone.clearGroup()\n const countRow = await countClone\n .countDistinct(`${table}.id as count`)\n .first()\n const total = Number((countRow as any)?.count ?? 0)\n const items = await q.limit(pageSize).offset((page - 1) * pageSize)\n\n if (cfJsonAliases.size > 0) {\n for (const row of items as any[]) {\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 const svc = this.getEncryptionService()\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 let decryptedItems = items\n if (decryptPayload) {\n const fallbackOrgId =\n opts.organizationId\n ?? (Array.isArray(opts.organizationIds) && opts.organizationIds.length === 1 ? opts.organizationIds[0] : null)\n decryptedItems = await Promise.all(\n (items as any[]).map(async (item) => {\n try {\n const decrypted = await decryptPayload(\n entity,\n item,\n item?.tenant_id ?? item?.tenantId ?? opts.tenantId ?? null,\n item?.organization_id ?? item?.organizationId ?? fallbackOrgId ?? null,\n )\n return { ...item, ...decrypted }\n } catch (err) {\n console.error('QueryEngine: error decrypting entity payload', err);\n return item\n }\n })\n )\n }\n\n let queryResult: QueryResult<T> = { items: decryptedItems, page, pageSize, total }\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 return queryResult\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 if (this.columnCache.has(key)) {\n const cached = this.columnCache.get(key)\n if (cached === true) return true\n this.columnCache.delete(key)\n }\n const knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()\n const exists = await knex('information_schema.columns')\n .where({ table_name: table, column_name: column })\n .first()\n const present = !!exists\n if (present) this.columnCache.set(key, true)\n else this.columnCache.delete(key)\n return present\n }\n\n private async tableExists(table: string): Promise<boolean> {\n if (this.tableCache.has(table)) return this.tableCache.get(table) ?? false\n const knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()\n const exists = await knex('information_schema.tables')\n .where({ table_name: table })\n .first()\n const present = !!exists\n this.tableCache.set(table, present)\n return present\n }\n\n private async hasSearchTokens(\n entity: string,\n tenantId: string | null,\n orgScope?: { ids: string[]; includeNull: boolean } | null\n ): Promise<boolean> {\n try {\n const knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()\n const query = knex('search_tokens').select(1).where('entity_type', entity).limit(1)\n if (tenantId !== undefined) {\n query.andWhereRaw('tenant_id is not distinct from ?', [tenantId])\n }\n if (orgScope) {\n this.applyOrganizationScope(query as any, 'search_tokens.organization_id', orgScope)\n }\n const row = await query.first()\n return !!row\n } catch (err) {\n this.logSearchDebug('search:has-tokens-error', {\n entity,\n tenantId,\n organizationScope: orgScope,\n error: err instanceof Error ? err.message : String(err),\n })\n return false\n }\n }\n\n private applySearchTokens<TRecord extends ResultRow, TResult>(\n q: Knex.QueryBuilder<TRecord, TResult>,\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 ): boolean {\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 false\n }\n const alias = `st_${this.searchAliasSeq++}`\n const combineWith = opts.combineWith === 'or' ? 'orWhereExists' : 'whereExists'\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 ;(q as any)[combineWith](function (this: Knex.QueryBuilder) {\n this.select(1)\n .from({ [alias]: 'search_tokens' })\n .where(`${alias}.entity_type`, opts.entity)\n .andWhere(`${alias}.field`, opts.field)\n .andWhereRaw('?? = ??::text', [`${alias}.entity_id`, opts.recordIdColumn])\n .whereIn(`${alias}.token_hash`, opts.hashes)\n .groupBy(`${alias}.entity_id`, `${alias}.field`)\n .havingRaw(`count(distinct ${alias}.token_hash) >= ?`, [opts.hashes.length])\n if (opts.tenantId !== undefined) {\n this.andWhereRaw(`${alias}.tenant_id is not distinct from ?`, [opts.tenantId ?? null])\n }\n if (opts.organizationScope) {\n engine.applyOrganizationScope(this as any, `${alias}.organization_id`, opts.organizationScope)\n }\n })\n return true\n }\n\n private applyIndexDocFilter<TRecord extends ResultRow, TResult>(\n q: Knex.QueryBuilder<TRecord, TResult>,\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 ): Knex.QueryBuilder<TRecord, TResult> {\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 applied = 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,\n tenantId: opts.tenantId ?? null,\n organizationScope: opts.organizationScope,\n })\n if (applied) return q\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 const knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()\n const alias = `ei_${this.searchAliasSeq++}`\n const engine = this\n return q.whereExists(function (this: Knex.QueryBuilder) {\n this.select(1)\n .from({ [alias]: 'entity_indexes' })\n .where(`${alias}.entity_type`, opts.entity)\n .andWhereRaw('?? = ??::text', [`${alias}.entity_id`, opts.recordIdColumn])\n\n if (opts.tenantId !== undefined) {\n this.andWhereRaw(`${alias}.tenant_id is not distinct from ?`, [opts.tenantId ?? null])\n }\n if (opts.organizationScope) {\n engine.applyOrganizationScope(this as any, `${alias}.organization_id`, opts.organizationScope)\n }\n if (!opts.withDeleted) {\n this.whereNull(`${alias}.deleted_at`)\n }\n\n const text = knex.raw(`(${alias}.doc ->> ?)`, [opts.field])\n switch (opts.op) {\n case 'eq':\n this.where(text, '=', opts.value as Knex.Value)\n break\n case 'ne':\n this.where(text, '!=', opts.value as Knex.Value)\n break\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const operator = opts.op === 'gt' ? '>' : opts.op === 'gte' ? '>=' : opts.op === 'lt' ? '<' : '<='\n this.where(text, operator, opts.value as Knex.Value)\n break\n }\n case 'in':\n this.whereIn(text as any, Array.isArray(opts.value) ? opts.value : [opts.value])\n break\n case 'nin':\n this.whereNotIn(text as any, Array.isArray(opts.value) ? opts.value : [opts.value])\n break\n case 'like':\n this.where(text, 'like', opts.value as Knex.Value)\n break\n case 'ilike':\n this.where(text, 'ilike', opts.value as Knex.Value)\n break\n case 'exists':\n opts.value ? this.whereNotNull(text as any) : this.whereNull(text as any)\n break\n default:\n break\n }\n })\n }\n\n private configureCustomFieldSources(\n q: any,\n baseTable: string,\n baseEntity: EntityId,\n knex: any,\n opts: QueryOptions,\n qualify: (column: string) => string\n ): ResolvedCustomFieldSource[] {\n const sources: ResolvedCustomFieldSource[] = [\n {\n entityId: baseEntity,\n alias: 'base',\n table: baseTable,\n recordIdExpr: knex.raw('??::text', [`${baseTable}.id`]),\n },\n ]\n const extras: QueryCustomFieldSource[] = opts.customFieldSources ?? []\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 joinArgs = { [alias]: joinTable }\n const joinCallback = function (this: any) {\n this.on(`${alias}.${join.toField}`, '=', qualify(join.fromField))\n }\n const joinType = join.type ?? 'left'\n if (joinType === 'inner') q.join(joinArgs, joinCallback)\n else q.leftJoin(joinArgs, joinCallback)\n const recordColumn = srcOpt.recordIdColumn ?? 'id'\n sources.push({\n entityId: srcOpt.entityId,\n alias,\n table: joinTable,\n recordIdExpr: knex.raw('??::text', [`${alias}.${recordColumn}`]),\n })\n })\n return sources\n }\n\n private logSearchDebug(event: string, payload: Record<string, unknown>) {\n try {\n console.info('[query:search]', event, JSON.stringify(payload))\n } catch {\n console.info('[query:search]', event, payload)\n }\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: any, column: string, scope: { ids: string[]; includeNull: boolean }): any {\n if (!scope) return q\n if (scope.ids.length === 0 && !scope.includeNull) {\n return q.whereRaw('1 = 0')\n }\n return q.where((builder: any) => {\n let applied = false\n if (scope.ids.length > 0) {\n builder.whereIn(column as any, scope.ids)\n applied = true\n }\n if (scope.includeNull) {\n if (applied) builder.orWhereNull(column)\n else builder.whereNull(column)\n applied = true\n }\n if (!applied) builder.whereRaw('1 = 0')\n })\n }\n\n}\n const computeScore = (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':\n return 8\n case 'relation':\n return 6\n case 'select':\n return 4\n case 'multiline':\n return 3\n case 'boolean':\n case 'integer':\n case 'float':\n return 2\n default:\n 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.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"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.4.7-develop-4edba96002";
1
+ const APP_VERSION = "0.4.7-develop-c89cca0193";
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.4.7-develop-4edba96002'\nexport const appVersion = APP_VERSION\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.7-develop-c89cca0193'\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.4.7-develop-4edba96002",
3
+ "version": "0.4.7-develop-c89cca0193",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -232,6 +232,41 @@ describe('BasicQueryEngine', () => {
232
232
  ]))
233
233
  })
234
234
 
235
+ test('uses search tokens for index document fields on base entities', async () => {
236
+ const fakeKnex = createFakeKnex({
237
+ todos: [],
238
+ })
239
+ const engine = new BasicQueryEngine({} as any, () => fakeKnex as any)
240
+ const tableExistsSpy = jest.spyOn(engine as any, 'tableExists').mockResolvedValue(true)
241
+ const hasSearchTokensSpy = jest.spyOn(engine as any, 'hasSearchTokens').mockResolvedValue(true)
242
+ const applySearchTokensSpy = jest.spyOn(engine as any, 'applySearchTokens')
243
+
244
+ await engine.query('example:todo', {
245
+ tenantId: 't1',
246
+ organizationId: 'org1',
247
+ fields: ['id'],
248
+ filters: {
249
+ search_text: { $ilike: '%avision%' },
250
+ },
251
+ page: { page: 1, pageSize: 10 },
252
+ })
253
+
254
+ expect(tableExistsSpy).toHaveBeenCalledWith('search_tokens')
255
+ expect(hasSearchTokensSpy).toHaveBeenCalledWith(
256
+ 'example:todo',
257
+ 't1',
258
+ expect.objectContaining({ ids: ['org1'] }),
259
+ )
260
+ expect(applySearchTokensSpy).toHaveBeenCalledWith(
261
+ expect.anything(),
262
+ expect.objectContaining({
263
+ entity: 'example:todo',
264
+ field: 'search_text',
265
+ recordIdColumn: 'todos.id',
266
+ }),
267
+ )
268
+ })
269
+
235
270
  test('join filters use whereExists with configured alias', async () => {
236
271
  const fakeKnex = createFakeKnex({
237
272
  customer_entities: [],
@@ -269,13 +269,28 @@ export class BasicQueryEngine implements QueryEngine {
269
269
  }
270
270
 
271
271
  for (const filter of baseFilters) {
272
+ const fieldName = String(filter.field)
272
273
  let qualified = filter.qualified ?? null
273
274
  if (!qualified) {
274
- const column = await this.resolveBaseColumn(table, String(filter.field))
275
- if (!column) continue
275
+ const column = await this.resolveBaseColumn(table, fieldName)
276
+ if (!column) {
277
+ q = this.applyIndexDocFilter(q, {
278
+ entity: String(entity),
279
+ field: fieldName,
280
+ op: filter.op,
281
+ value: filter.value,
282
+ recordIdColumn,
283
+ tenantId: opts.tenantId ?? null,
284
+ organizationScope: orgScope,
285
+ withDeleted: opts.withDeleted === true,
286
+ searchActive,
287
+ searchConfig,
288
+ })
289
+ continue
290
+ }
276
291
  qualified = qualify(column)
277
292
  }
278
- applyFilterOp(q, qualified, filter.op, filter.value, String(filter.field))
293
+ applyFilterOp(q, qualified, filter.op, filter.value, fieldName)
279
294
  }
280
295
 
281
296
  const applyAliasScopes = async (builder: any, aliasName: string) => {
@@ -765,6 +780,110 @@ export class BasicQueryEngine implements QueryEngine {
765
780
  return true
766
781
  }
767
782
 
783
+ private applyIndexDocFilter<TRecord extends ResultRow, TResult>(
784
+ q: Knex.QueryBuilder<TRecord, TResult>,
785
+ opts: {
786
+ entity: string
787
+ field: string
788
+ op: NormalizedFilter['op']
789
+ value: unknown
790
+ recordIdColumn: string
791
+ tenantId?: string | null
792
+ organizationScope?: { ids: string[]; includeNull: boolean } | null
793
+ withDeleted: boolean
794
+ searchActive: boolean
795
+ searchConfig: ReturnType<typeof resolveSearchConfig>
796
+ }
797
+ ): Knex.QueryBuilder<TRecord, TResult> {
798
+ if ((opts.op === 'like' || opts.op === 'ilike') && opts.searchActive && typeof opts.value === 'string') {
799
+ const tokens = tokenizeText(String(opts.value), opts.searchConfig)
800
+ const hashes = tokens.hashes
801
+ if (hashes.length) {
802
+ const applied = this.applySearchTokens(q, {
803
+ entity: opts.entity,
804
+ field: opts.field,
805
+ hashes,
806
+ recordIdColumn: opts.recordIdColumn,
807
+ tenantId: opts.tenantId ?? null,
808
+ organizationScope: opts.organizationScope,
809
+ tokens: tokens.tokens,
810
+ })
811
+ this.logSearchDebug('search:index-doc-filter', {
812
+ entity: opts.entity,
813
+ field: opts.field,
814
+ tokens: tokens.tokens,
815
+ hashes,
816
+ applied,
817
+ tenantId: opts.tenantId ?? null,
818
+ organizationScope: opts.organizationScope,
819
+ })
820
+ if (applied) return q
821
+ } else {
822
+ this.logSearchDebug('search:index-doc-skip-empty-hashes', {
823
+ entity: opts.entity,
824
+ field: opts.field,
825
+ value: opts.value,
826
+ })
827
+ }
828
+ return q
829
+ }
830
+
831
+ const knex = this.getKnexFn ? this.getKnexFn() : (this.em as any).getConnection().getKnex()
832
+ const alias = `ei_${this.searchAliasSeq++}`
833
+ const engine = this
834
+ return q.whereExists(function (this: Knex.QueryBuilder) {
835
+ this.select(1)
836
+ .from({ [alias]: 'entity_indexes' })
837
+ .where(`${alias}.entity_type`, opts.entity)
838
+ .andWhereRaw('?? = ??::text', [`${alias}.entity_id`, opts.recordIdColumn])
839
+
840
+ if (opts.tenantId !== undefined) {
841
+ this.andWhereRaw(`${alias}.tenant_id is not distinct from ?`, [opts.tenantId ?? null])
842
+ }
843
+ if (opts.organizationScope) {
844
+ engine.applyOrganizationScope(this as any, `${alias}.organization_id`, opts.organizationScope)
845
+ }
846
+ if (!opts.withDeleted) {
847
+ this.whereNull(`${alias}.deleted_at`)
848
+ }
849
+
850
+ const text = knex.raw(`(${alias}.doc ->> ?)`, [opts.field])
851
+ switch (opts.op) {
852
+ case 'eq':
853
+ this.where(text, '=', opts.value as Knex.Value)
854
+ break
855
+ case 'ne':
856
+ this.where(text, '!=', opts.value as Knex.Value)
857
+ break
858
+ case 'gt':
859
+ case 'gte':
860
+ case 'lt':
861
+ case 'lte': {
862
+ const operator = opts.op === 'gt' ? '>' : opts.op === 'gte' ? '>=' : opts.op === 'lt' ? '<' : '<='
863
+ this.where(text, operator, opts.value as Knex.Value)
864
+ break
865
+ }
866
+ case 'in':
867
+ this.whereIn(text as any, Array.isArray(opts.value) ? opts.value : [opts.value])
868
+ break
869
+ case 'nin':
870
+ this.whereNotIn(text as any, Array.isArray(opts.value) ? opts.value : [opts.value])
871
+ break
872
+ case 'like':
873
+ this.where(text, 'like', opts.value as Knex.Value)
874
+ break
875
+ case 'ilike':
876
+ this.where(text, 'ilike', opts.value as Knex.Value)
877
+ break
878
+ case 'exists':
879
+ opts.value ? this.whereNotNull(text as any) : this.whereNull(text as any)
880
+ break
881
+ default:
882
+ break
883
+ }
884
+ })
885
+ }
886
+
768
887
  private configureCustomFieldSources(
769
888
  q: any,
770
889
  baseTable: string,
@@ -37,6 +37,7 @@ export type CustomFieldDefinition = {
37
37
  label?: string
38
38
  description?: string
39
39
  fieldset?: string
40
+ fieldsets?: string[]
40
41
  group?: {
41
42
  code: string
42
43
  title?: string
@@ -74,6 +75,7 @@ export type CustomFieldDefinition = {
74
75
  // Attachments config passthrough (handled by attachments module)
75
76
  maxAttachmentSizeMb?: number
76
77
  acceptExtensions?: string[]
78
+ sourceMetadata?: Record<string, unknown>
77
79
  }
78
80
 
79
81
  export type CustomFieldSet = {
@@ -250,10 +250,16 @@ export type InjectionBulkActionDefinition = {
250
250
  id: string
251
251
  label: string
252
252
  icon?: string
253
+ requiresSelection?: boolean
253
254
  onExecute: (
254
255
  selectedRows: unknown[],
255
256
  context: unknown,
256
- ) => Promise<void | { ok: boolean; message?: string; affectedCount?: number }>
257
+ ) => Promise<void | {
258
+ ok: boolean
259
+ message?: string
260
+ affectedCount?: number
261
+ progressJobId?: string | null
262
+ }>
257
263
  }
258
264
 
259
265
  export type InjectionFilterDefinition = {