@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.
- package/dist/lib/query/engine.js +101 -3
- package/dist/lib/query/engine.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/query/__tests__/engine.test.ts +35 -0
- package/src/lib/query/engine.ts +122 -3
- package/src/modules/entities.ts +2 -0
- package/src/modules/widgets/injection.ts +7 -1
package/dist/lib/query/engine.js
CHANGED
|
@@ -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,
|
|
238
|
-
if (!column)
|
|
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,
|
|
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
|
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -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-
|
|
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
|
@@ -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: [],
|
package/src/lib/query/engine.ts
CHANGED
|
@@ -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,
|
|
275
|
-
if (!column)
|
|
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,
|
|
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,
|
package/src/modules/entities.ts
CHANGED
|
@@ -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 | {
|
|
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 = {
|