@open-mercato/shared 0.5.1-develop.2663.2c29774b5b → 0.5.1-develop.2681.c559bb2bc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- Found 199 entry points
1
+ Found 201 entry points
2
2
  shared built successfully
package/AGENTS.md CHANGED
@@ -18,6 +18,7 @@ Use `@open-mercato/shared` for cross-cutting utilities, types, DSL helpers, and
18
18
  | `auth/` | When you need wildcard-aware feature matching or shared auth helpers | `@open-mercato/shared/lib/auth/featureMatch` |
19
19
  | `boolean/` | When parsing boolean strings from env/query params | `@open-mercato/shared/lib/boolean` |
20
20
  | `commands/` | When implementing undo/redo command pattern | `@open-mercato/shared/lib/commands` |
21
+ | `commands/flush` | When a command mutates entities across multiple phases (scalar + relation syncs) — wraps phases in a single atomic flush | `@open-mercato/shared/lib/commands/flush` — `withAtomicFlush(em, phases, { transaction? })` |
21
22
  | `crud/` | When building CRUD routes | `@open-mercato/shared/lib/crud` |
22
23
  | `custom-fields/` | When handling custom field payloads | `@open-mercato/shared/lib/custom-fields` |
23
24
  | `data/` | When you need `DataEngine` or `QueryEngine` types | `@open-mercato/shared/lib/data/engine` |
@@ -0,0 +1,30 @@
1
+ function readJsonFromLocalStorage(key, fallback) {
2
+ if (typeof window === "undefined") return fallback;
3
+ try {
4
+ const raw = window.localStorage.getItem(key);
5
+ if (!raw) return fallback;
6
+ return JSON.parse(raw);
7
+ } catch {
8
+ return fallback;
9
+ }
10
+ }
11
+ function writeJsonToLocalStorage(key, value) {
12
+ if (typeof window === "undefined") return;
13
+ try {
14
+ window.localStorage.setItem(key, JSON.stringify(value));
15
+ } catch {
16
+ }
17
+ }
18
+ function removeLocalStorageKey(key) {
19
+ if (typeof window === "undefined") return;
20
+ try {
21
+ window.localStorage.removeItem(key);
22
+ } catch {
23
+ }
24
+ }
25
+ export {
26
+ readJsonFromLocalStorage,
27
+ removeLocalStorageKey,
28
+ writeJsonToLocalStorage
29
+ };
30
+ //# sourceMappingURL=safeLocalStorage.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/browser/safeLocalStorage.ts"],
4
+ "sourcesContent": ["export type JsonSerializable =\n | string\n | number\n | boolean\n | null\n | JsonSerializable[]\n | { [key: string]: JsonSerializable | undefined }\n\nexport function readJsonFromLocalStorage<T extends JsonSerializable>(\n key: string,\n fallback: T,\n): T {\n if (typeof window === 'undefined') return fallback\n try {\n const raw = window.localStorage.getItem(key)\n if (!raw) return fallback\n return JSON.parse(raw) as T\n } catch {\n return fallback\n }\n}\n\nexport function writeJsonToLocalStorage(key: string, value: unknown): void {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.setItem(key, JSON.stringify(value))\n } catch {\n /* quota/serialization errors are intentionally swallowed */\n }\n}\n\nexport function removeLocalStorageKey(key: string): void {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.removeItem(key)\n } catch {\n /* intentionally swallowed */\n }\n}\n"],
5
+ "mappings": "AAQO,SAAS,yBACd,KACA,UACG;AACH,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,UAAM,MAAM,OAAO,aAAa,QAAQ,GAAG;AAC3C,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,wBAAwB,KAAa,OAAsB;AACzE,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,WAAO,aAAa,QAAQ,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EACxD,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,sBAAsB,KAAmB;AACvD,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,WAAO,aAAa,WAAW,GAAG;AAAA,EACpC,QAAQ;AAAA,EAER;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,28 @@
1
+ async function withAtomicFlush(em, phases, options) {
2
+ if (phases.length === 0) return;
3
+ const runPhasesAndFlush = async () => {
4
+ for (const phase of phases) {
5
+ await phase();
6
+ }
7
+ await em.flush();
8
+ };
9
+ if (options?.transaction) {
10
+ await em.begin();
11
+ try {
12
+ await runPhasesAndFlush();
13
+ await em.commit();
14
+ } catch (err) {
15
+ try {
16
+ await em.rollback();
17
+ } catch {
18
+ }
19
+ throw err;
20
+ }
21
+ return;
22
+ }
23
+ await runPhasesAndFlush();
24
+ }
25
+ export {
26
+ withAtomicFlush
27
+ };
28
+ //# sourceMappingURL=flush.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/commands/flush.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\n\n/**\n * Wraps multiple mutation phases in a single atomic flush.\n *\n * Prevents partial commits when a command mutates entities across\n * multiple phases (e.g., scalar mutations + relation syncs).\n * Each phase function runs sequentially; a single `em.flush()`\n * commits all changes at the end on the same `EntityManager` the\n * phases mutate, so closures over `em` stay valid.\n *\n * When `options.transaction` is true, the whole sequence runs\n * inside a database transaction (`em.begin()` / `em.commit()` /\n * `em.rollback()`) for all-or-nothing semantics. The outer `em`\n * stays bound to the transaction, so phases that close over `em`\n * participate in the same transaction.\n *\n * When `phases` is empty the call is a true no-op \u2014 no flush,\n * no transaction. Callers that need an explicit commit should\n * pass at least one phase.\n *\n * Keep side-effect emissions (`emitCrudSideEffects` etc.) OUTSIDE\n * the `withAtomicFlush` block \u2014 they should only fire after commit.\n */\nexport async function withAtomicFlush(\n em: EntityManager,\n phases: Array<() => void | Promise<void>>,\n options?: { transaction?: boolean },\n): Promise<void> {\n if (phases.length === 0) return\n\n const runPhasesAndFlush = async () => {\n for (const phase of phases) {\n await phase()\n }\n await em.flush()\n }\n\n if (options?.transaction) {\n await em.begin()\n try {\n await runPhasesAndFlush()\n await em.commit()\n } catch (err) {\n try {\n await em.rollback()\n } catch {\n // rollback failure should not mask the original error; intentionally swallowed\n }\n throw err\n }\n return\n }\n\n await runPhasesAndFlush()\n}\n"],
5
+ "mappings": "AAwBA,eAAsB,gBACpB,IACA,QACA,SACe;AACf,MAAI,OAAO,WAAW,EAAG;AAEzB,QAAM,oBAAoB,YAAY;AACpC,eAAW,SAAS,QAAQ;AAC1B,YAAM,MAAM;AAAA,IACd;AACA,UAAM,GAAG,MAAM;AAAA,EACjB;AAEA,MAAI,SAAS,aAAa;AACxB,UAAM,GAAG,MAAM;AACf,QAAI;AACF,YAAM,kBAAkB;AACxB,YAAM,GAAG,OAAO;AAAA,IAClB,SAAS,KAAK;AACZ,UAAI;AACF,cAAM,GAAG,SAAS;AAAA,MACpB,QAAQ;AAAA,MAER;AACA,YAAM;AAAA,IACR;AACA;AAAA,EACF;AAEA,QAAM,kBAAkB;AAC1B;",
6
+ "names": []
7
+ }
@@ -84,6 +84,34 @@ function buildFilterableCustomFieldJoins(sources) {
84
84
  }];
85
85
  });
86
86
  }
87
+ const computeScore = (cfg, kind, entityIndex) => {
88
+ const listVisibleScore = cfg.listVisible === false ? 0 : 1;
89
+ const formEditableScore = cfg.formEditable === false ? 0 : 1;
90
+ const filterableScore = cfg.filterable ? 1 : 0;
91
+ const kindScore = (() => {
92
+ switch (kind) {
93
+ case "dictionary":
94
+ return 8;
95
+ case "relation":
96
+ return 6;
97
+ case "select":
98
+ return 4;
99
+ case "multiline":
100
+ return 3;
101
+ case "boolean":
102
+ case "integer":
103
+ case "float":
104
+ return 2;
105
+ default:
106
+ return 1;
107
+ }
108
+ })();
109
+ const optionsBonus = Array.isArray(cfg.options) && cfg.options.length ? 2 : 0;
110
+ const dictionaryBonus = typeof cfg.dictionaryId === "string" && cfg.dictionaryId.trim().length ? 5 : 0;
111
+ const base = listVisibleScore * 16 + formEditableScore * 8 + filterableScore * 4 + kindScore + optionsBonus + dictionaryBonus;
112
+ const penalty = typeof cfg.priority === "number" ? cfg.priority : 0;
113
+ return { base, penalty, entityIndex };
114
+ };
87
115
  class BasicQueryEngine {
88
116
  constructor(em, getKnexFn, resolveEncryptionService) {
89
117
  this.em = em;
@@ -276,7 +304,7 @@ class BasicQueryEngine {
276
304
  };
277
305
  const applyJoinFilterOp = async (builder, filter, _qualified, join) => {
278
306
  if (!searchEnabled || !join.entityId) return false;
279
- if (!["like", "ilike"].includes(filter.op)) return false;
307
+ if (!["eq", "like", "ilike"].includes(filter.op)) return false;
280
308
  if (typeof filter.value !== "string" || filter.value.trim().length === 0) return false;
281
309
  let searchAvailable = joinSearchAvailability.get(join.entityId);
282
310
  if (searchAvailable === void 0) {
@@ -936,34 +964,6 @@ class BasicQueryEngine {
936
964
  });
937
965
  }
938
966
  }
939
- const computeScore = (cfg, kind, entityIndex) => {
940
- const listVisibleScore = cfg.listVisible === false ? 0 : 1;
941
- const formEditableScore = cfg.formEditable === false ? 0 : 1;
942
- const filterableScore = cfg.filterable ? 1 : 0;
943
- const kindScore = (() => {
944
- switch (kind) {
945
- case "dictionary":
946
- return 8;
947
- case "relation":
948
- return 6;
949
- case "select":
950
- return 4;
951
- case "multiline":
952
- return 3;
953
- case "boolean":
954
- case "integer":
955
- case "float":
956
- return 2;
957
- default:
958
- return 1;
959
- }
960
- })();
961
- const optionsBonus = Array.isArray(cfg.options) && cfg.options.length ? 2 : 0;
962
- const dictionaryBonus = typeof cfg.dictionaryId === "string" && cfg.dictionaryId.trim().length ? 5 : 0;
963
- const base = listVisibleScore * 16 + formEditableScore * 8 + filterableScore * 4 + kindScore + optionsBonus + dictionaryBonus;
964
- const penalty = typeof cfg.priority === "number" ? cfg.priority : 0;
965
- return { base, penalty, entityIndex };
966
- };
967
967
  export {
968
968
  BasicQueryEngine,
969
969
  resolveEntityTableName
@@ -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 // Secondary lookup: search ORM metadata by candidate table names\n const modulePrefix = parts[0] ?? ''\n const candidateTables = [\n `${modulePrefix}_${rawName}`,\n pluralizeBaseName(rawName),\n `${modulePrefix}_${pluralizeBaseName(rawName)}`,\n ]\n try {\n const allMeta: any[] = metadata.getAll?.() ?? []\n for (const meta of allMeta) {\n if (meta?.tableName && candidateTables.includes(String(meta.tableName))) {\n const tableName = String(meta.tableName)\n entityTableCache.set(entity, tableName)\n return tableName\n }\n }\n } catch {}\n }\n\n const fallback = pluralizeBaseName(rawName || '')\n console.warn(\n `[QueryEngine] Could not resolve entity \"${entity}\" via ORM metadata. ` +\n `Falling back to table name \"${fallback}\". ` +\n `Ensure the entity ID segment matches the class name convention.`\n )\n entityTableCache.set(entity, fallback)\n return fallback\n}\n\nfunction buildFilterableCustomFieldJoins(\n sources: QueryCustomFieldSource[] | undefined,\n): Array<{\n alias: string\n table?: string\n entityId: EntityId\n from: { field: string }\n to: { field: string }\n type: 'left' | 'inner'\n}> {\n if (!sources || sources.length === 0) return []\n return sources.flatMap((source, index) => {\n if (!source.join) return []\n const alias = typeof source.alias === 'string' && source.alias.trim().length > 0\n ? source.alias.trim()\n : `cfs_${index}`\n return [{\n alias,\n table: source.table,\n entityId: source.entityId,\n from: { field: source.join.fromField },\n to: { field: source.join.toField },\n type: source.join.type === 'inner' ? 'inner' : 'left',\n }]\n })\n}\n\n\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 const skipAutoScope = opts.omitAutomaticTenantOrgScope === true\n // Optional organization filter (when present in schema)\n if (!skipAutoScope && 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 (!skipAutoScope && 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(\n table,\n [...(opts.joins ?? []), ...buildFilterableCustomFieldJoins(opts.customFieldSources)],\n (entityId) => resolveEntityTableName(this.em, entityId as any),\n )\n const joinMap = new Map<string, ResolvedJoin>()\n const aliasTables = new Map<string, string>()\n aliasTables.set(table, table)\n aliasTables.set('base', table)\n for (const join of resolvedJoins) {\n joinMap.set(join.alias, join)\n aliasTables.set(join.alias, join.table)\n }\n const { baseFilters, joinFilters } = partitionFilters(table, normalizedFilters, joinMap)\n const cfFilters = normalizedFilters.filter((filter) => String(filter.field).startsWith('cf:'))\n 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 joinSearchAvailability = new Map<string, boolean>()\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':\n if (value === null) builder.whereNull(column)\n else builder.where(column, value)\n break\n case 'ne':\n if (value === null) builder.whereNotNull(column)\n else builder.whereNot(column, value)\n 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 const applyJoinFilterOp = async (\n builder: any,\n filter: { column: string; op: string; value?: unknown },\n _qualified: string,\n join: ResolvedJoin,\n ): Promise<boolean> => {\n if (!searchEnabled || !join.entityId) return false\n if (!['like', 'ilike'].includes(filter.op)) return false\n if (typeof filter.value !== 'string' || filter.value.trim().length === 0) return false\n\n let searchAvailable = joinSearchAvailability.get(join.entityId)\n if (searchAvailable === undefined) {\n searchAvailable = await this.hasSearchTokens(join.entityId, opts.tenantId ?? null, orgScope)\n joinSearchAvailability.set(join.entityId, searchAvailable)\n }\n if (!searchAvailable) return false\n\n const tokens = tokenizeText(String(filter.value), searchConfig)\n if (!tokens.hashes.length) return false\n\n return this.applySearchTokens(builder, {\n entity: join.entityId,\n field: filter.column,\n hashes: tokens.hashes,\n recordIdColumn: `${join.alias}.id`,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n tokens: tokens.tokens,\n })\n }\n\n const regularBaseFilters = baseFilters.filter((f) => !f.orGroup)\n const orGroupFilters = baseFilters.filter((f) => f.orGroup)\n\n for (const filter of regularBaseFilters) {\n const fieldName = String(filter.field)\n let qualified = filter.qualified ?? null\n if (!qualified) {\n const column = await this.resolveBaseColumn(table, fieldName)\n if (!column) {\n q = this.applyIndexDocFilter(q, {\n entity: String(entity),\n field: fieldName,\n op: filter.op,\n value: filter.value,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n withDeleted: opts.withDeleted === true,\n searchActive,\n searchConfig,\n })\n continue\n }\n qualified = qualify(column)\n }\n applyFilterOp(q, qualified, filter.op, filter.value, fieldName)\n }\n\n // OR-grouped filters: AND within each group (one $or disjunct), OR between groups.\n if (orGroupFilters.length > 0) {\n const groups = new Map<string, typeof orGroupFilters>()\n for (const f of orGroupFilters) {\n const group = groups.get(f.orGroup!) ?? []\n group.push(f)\n groups.set(f.orGroup!, group)\n }\n const resolvedGroupFilters: Array<Array<{ qualified: string; op: string; value: unknown; fieldName: string }>> = []\n for (const [, groupFilters] of groups) {\n const resolved: Array<{ qualified: string; op: string; value: unknown; fieldName: string }> = []\n for (const filter of groupFilters) {\n const column = await this.resolveBaseColumn(table, String(filter.field))\n if (column) {\n resolved.push({\n qualified: qualify(column),\n op: filter.op,\n value: filter.value,\n fieldName: String(filter.field),\n })\n }\n }\n if (resolved.length > 0) resolvedGroupFilters.push(resolved)\n }\n if (resolvedGroupFilters.length > 0) {\n q = q.where(function (this: any) {\n const applyConjunctiveGroup = (builder: any, conjuncts: (typeof resolvedGroupFilters)[0]) => {\n for (const rf of conjuncts) {\n applyFilterOp(builder, rf.qualified, rf.op, rf.value, rf.fieldName)\n }\n }\n applyConjunctiveGroup(this, resolvedGroupFilters[0])\n for (let gi = 1; gi < resolvedGroupFilters.length; gi++) {\n this.orWhere(function (nested: any) {\n applyConjunctiveGroup(nested, resolvedGroupFilters[gi])\n })\n }\n })\n }\n }\n\n const applyAliasScopes = async (builder: any, aliasName: string) => {\n const targetTable = aliasTables.get(aliasName)\n if (!targetTable) return\n if (!skipAutoScope && orgScope && await this.columnExists(targetTable, 'organization_id')) {\n this.applyOrganizationScope(builder, `${aliasName}.organization_id`, orgScope)\n }\n if (!skipAutoScope && 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 applyJoinFilterOp,\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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",
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 // Secondary lookup: search ORM metadata by candidate table names\n const modulePrefix = parts[0] ?? ''\n const candidateTables = [\n `${modulePrefix}_${rawName}`,\n pluralizeBaseName(rawName),\n `${modulePrefix}_${pluralizeBaseName(rawName)}`,\n ]\n try {\n const allMeta: any[] = metadata.getAll?.() ?? []\n for (const meta of allMeta) {\n if (meta?.tableName && candidateTables.includes(String(meta.tableName))) {\n const tableName = String(meta.tableName)\n entityTableCache.set(entity, tableName)\n return tableName\n }\n }\n } catch {}\n }\n\n const fallback = pluralizeBaseName(rawName || '')\n console.warn(\n `[QueryEngine] Could not resolve entity \"${entity}\" via ORM metadata. ` +\n `Falling back to table name \"${fallback}\". ` +\n `Ensure the entity ID segment matches the class name convention.`\n )\n entityTableCache.set(entity, fallback)\n return fallback\n}\n\nfunction buildFilterableCustomFieldJoins(\n sources: QueryCustomFieldSource[] | undefined,\n): Array<{\n alias: string\n table?: string\n entityId: EntityId\n from: { field: string }\n to: { field: string }\n type: 'left' | 'inner'\n}> {\n if (!sources || sources.length === 0) return []\n return sources.flatMap((source, index) => {\n if (!source.join) return []\n const alias = typeof source.alias === 'string' && source.alias.trim().length > 0\n ? source.alias.trim()\n : `cfs_${index}`\n return [{\n alias,\n table: source.table,\n entityId: source.entityId,\n from: { field: source.join.fromField },\n to: { field: source.join.toField },\n type: source.join.type === 'inner' ? 'inner' : 'left',\n }]\n })\n}\n\n\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\nconst 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\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 const skipAutoScope = opts.omitAutomaticTenantOrgScope === true\n // Optional organization filter (when present in schema)\n if (!skipAutoScope && 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 (!skipAutoScope && 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(\n table,\n [...(opts.joins ?? []), ...buildFilterableCustomFieldJoins(opts.customFieldSources)],\n (entityId) => resolveEntityTableName(this.em, entityId as any),\n )\n const joinMap = new Map<string, ResolvedJoin>()\n const aliasTables = new Map<string, string>()\n aliasTables.set(table, table)\n aliasTables.set('base', table)\n for (const join of resolvedJoins) {\n joinMap.set(join.alias, join)\n aliasTables.set(join.alias, join.table)\n }\n const { baseFilters, joinFilters } = partitionFilters(table, normalizedFilters, joinMap)\n const cfFilters = normalizedFilters.filter((filter) => String(filter.field).startsWith('cf:'))\n 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 joinSearchAvailability = new Map<string, boolean>()\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':\n if (value === null) builder.whereNull(column)\n else builder.where(column, value)\n break\n case 'ne':\n if (value === null) builder.whereNotNull(column)\n else builder.whereNot(column, value)\n 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 // `eq` is accepted alongside `like`/`ilike` so that filters against\n // encrypted joined columns (whose ciphertext cannot be compared for\n // equality in SQL) can still resolve via tokenized search. Routing\n // only applies when `searchEnabled` is true AND the joined entity has\n // search tokens installed (`searchAvailable`); for non-searchable or\n // non-encrypted columns the caller still falls through to exact SQL\n // equality via `applyFilterOp`. Note that token match is approximate \u2014\n // callers needing strict equality on encrypted fields should filter on\n // the deterministic `*_hash` column instead.\n const applyJoinFilterOp = async (\n builder: any,\n filter: { column: string; op: string; value?: unknown },\n _qualified: string,\n join: ResolvedJoin,\n ): Promise<boolean> => {\n if (!searchEnabled || !join.entityId) return false\n if (!['eq', 'like', 'ilike'].includes(filter.op)) return false\n if (typeof filter.value !== 'string' || filter.value.trim().length === 0) return false\n\n let searchAvailable = joinSearchAvailability.get(join.entityId)\n if (searchAvailable === undefined) {\n searchAvailable = await this.hasSearchTokens(join.entityId, opts.tenantId ?? null, orgScope)\n joinSearchAvailability.set(join.entityId, searchAvailable)\n }\n if (!searchAvailable) return false\n\n const tokens = tokenizeText(String(filter.value), searchConfig)\n if (!tokens.hashes.length) return false\n\n return this.applySearchTokens(builder, {\n entity: join.entityId,\n field: filter.column,\n hashes: tokens.hashes,\n recordIdColumn: `${join.alias}.id`,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n tokens: tokens.tokens,\n })\n }\n\n const regularBaseFilters = baseFilters.filter((f) => !f.orGroup)\n const orGroupFilters = baseFilters.filter((f) => f.orGroup)\n\n for (const filter of regularBaseFilters) {\n const fieldName = String(filter.field)\n let qualified = filter.qualified ?? null\n if (!qualified) {\n const column = await this.resolveBaseColumn(table, fieldName)\n if (!column) {\n q = this.applyIndexDocFilter(q, {\n entity: String(entity),\n field: fieldName,\n op: filter.op,\n value: filter.value,\n recordIdColumn,\n tenantId: opts.tenantId ?? null,\n organizationScope: orgScope,\n withDeleted: opts.withDeleted === true,\n searchActive,\n searchConfig,\n })\n continue\n }\n qualified = qualify(column)\n }\n applyFilterOp(q, qualified, filter.op, filter.value, fieldName)\n }\n\n // OR-grouped filters: AND within each group (one $or disjunct), OR between groups.\n if (orGroupFilters.length > 0) {\n const groups = new Map<string, typeof orGroupFilters>()\n for (const f of orGroupFilters) {\n const group = groups.get(f.orGroup!) ?? []\n group.push(f)\n groups.set(f.orGroup!, group)\n }\n const resolvedGroupFilters: Array<Array<{ qualified: string; op: string; value: unknown; fieldName: string }>> = []\n for (const [, groupFilters] of groups) {\n const resolved: Array<{ qualified: string; op: string; value: unknown; fieldName: string }> = []\n for (const filter of groupFilters) {\n const column = await this.resolveBaseColumn(table, String(filter.field))\n if (column) {\n resolved.push({\n qualified: qualify(column),\n op: filter.op,\n value: filter.value,\n fieldName: String(filter.field),\n })\n }\n }\n if (resolved.length > 0) resolvedGroupFilters.push(resolved)\n }\n if (resolvedGroupFilters.length > 0) {\n q = q.where(function (this: any) {\n const applyConjunctiveGroup = (builder: any, conjuncts: (typeof resolvedGroupFilters)[0]) => {\n for (const rf of conjuncts) {\n applyFilterOp(builder, rf.qualified, rf.op, rf.value, rf.fieldName)\n }\n }\n applyConjunctiveGroup(this, resolvedGroupFilters[0])\n for (let gi = 1; gi < resolvedGroupFilters.length; gi++) {\n this.orWhere(function (nested: any) {\n applyConjunctiveGroup(nested, resolvedGroupFilters[gi])\n })\n }\n })\n }\n }\n\n const applyAliasScopes = async (builder: any, aliasName: string) => {\n const targetTable = aliasTables.get(aliasName)\n if (!targetTable) return\n if (!skipAutoScope && orgScope && await this.columnExists(targetTable, 'organization_id')) {\n this.applyOrganizationScope(builder, `${aliasName}.organization_id`, orgScope)\n }\n if (!skipAutoScope && 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 applyJoinFilterOp,\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"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.5.1-develop.2663.2c29774b5b";
1
+ const APP_VERSION = "0.5.1-develop.2681.c559bb2bc3";
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.5.1-develop.2663.2c29774b5b'\nexport const appVersion = APP_VERSION\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.5.1-develop.2681.c559bb2bc3'\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.5.1-develop.2663.2c29774b5b",
3
+ "version": "0.5.1-develop.2681.c559bb2bc3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -0,0 +1,39 @@
1
+ export type JsonSerializable =
2
+ | string
3
+ | number
4
+ | boolean
5
+ | null
6
+ | JsonSerializable[]
7
+ | { [key: string]: JsonSerializable | undefined }
8
+
9
+ export function readJsonFromLocalStorage<T extends JsonSerializable>(
10
+ key: string,
11
+ fallback: T,
12
+ ): T {
13
+ if (typeof window === 'undefined') return fallback
14
+ try {
15
+ const raw = window.localStorage.getItem(key)
16
+ if (!raw) return fallback
17
+ return JSON.parse(raw) as T
18
+ } catch {
19
+ return fallback
20
+ }
21
+ }
22
+
23
+ export function writeJsonToLocalStorage(key: string, value: unknown): void {
24
+ if (typeof window === 'undefined') return
25
+ try {
26
+ window.localStorage.setItem(key, JSON.stringify(value))
27
+ } catch {
28
+ /* quota/serialization errors are intentionally swallowed */
29
+ }
30
+ }
31
+
32
+ export function removeLocalStorageKey(key: string): void {
33
+ if (typeof window === 'undefined') return
34
+ try {
35
+ window.localStorage.removeItem(key)
36
+ } catch {
37
+ /* intentionally swallowed */
38
+ }
39
+ }
@@ -0,0 +1,166 @@
1
+ import { withAtomicFlush } from '../flush'
2
+
3
+ type FakeEntityManager = {
4
+ flush: jest.Mock<Promise<void>, []>
5
+ begin: jest.Mock<Promise<void>, []>
6
+ commit: jest.Mock<Promise<void>, []>
7
+ rollback: jest.Mock<Promise<void>, []>
8
+ }
9
+
10
+ function createFakeEm(): FakeEntityManager {
11
+ return {
12
+ flush: jest.fn().mockResolvedValue(undefined),
13
+ begin: jest.fn().mockResolvedValue(undefined),
14
+ commit: jest.fn().mockResolvedValue(undefined),
15
+ rollback: jest.fn().mockResolvedValue(undefined),
16
+ }
17
+ }
18
+
19
+ describe('withAtomicFlush', () => {
20
+ it('runs phases in order and flushes once at the end', async () => {
21
+ const em = createFakeEm()
22
+ const calls: string[] = []
23
+
24
+ await withAtomicFlush(em as any, [
25
+ async () => {
26
+ calls.push('phase1-start')
27
+ await Promise.resolve()
28
+ calls.push('phase1-end')
29
+ },
30
+ () => {
31
+ calls.push('phase2')
32
+ },
33
+ async () => {
34
+ calls.push('phase3')
35
+ },
36
+ ])
37
+
38
+ expect(calls).toEqual(['phase1-start', 'phase1-end', 'phase2', 'phase3'])
39
+ expect(em.flush).toHaveBeenCalledTimes(1)
40
+ expect(em.begin).not.toHaveBeenCalled()
41
+ expect(em.commit).not.toHaveBeenCalled()
42
+ expect(em.rollback).not.toHaveBeenCalled()
43
+ })
44
+
45
+ it('lets a later phase observe state a prior phase mutated', async () => {
46
+ const em = createFakeEm()
47
+ const state: { value: number } = { value: 0 }
48
+ let observed: number | null = null
49
+
50
+ await withAtomicFlush(em as any, [
51
+ () => {
52
+ state.value = 42
53
+ },
54
+ () => {
55
+ observed = state.value
56
+ },
57
+ ])
58
+
59
+ expect(observed).toBe(42)
60
+ expect(em.flush).toHaveBeenCalledTimes(1)
61
+ })
62
+
63
+ it('wraps phases in begin/commit when transaction option is true', async () => {
64
+ const em = createFakeEm()
65
+ const phase = jest.fn()
66
+
67
+ await withAtomicFlush(em as any, [phase], { transaction: true })
68
+
69
+ expect(em.begin).toHaveBeenCalledTimes(1)
70
+ expect(phase).toHaveBeenCalledTimes(1)
71
+ expect(em.flush).toHaveBeenCalledTimes(1)
72
+ expect(em.commit).toHaveBeenCalledTimes(1)
73
+ expect(em.rollback).not.toHaveBeenCalled()
74
+ })
75
+
76
+ it('calls rollback when a transactional phase throws', async () => {
77
+ const em = createFakeEm()
78
+ const failure = new Error('transactional-failure')
79
+
80
+ await expect(
81
+ withAtomicFlush(em as any, [
82
+ () => {
83
+ throw failure
84
+ },
85
+ ], { transaction: true }),
86
+ ).rejects.toBe(failure)
87
+
88
+ expect(em.begin).toHaveBeenCalledTimes(1)
89
+ expect(em.flush).not.toHaveBeenCalled()
90
+ expect(em.commit).not.toHaveBeenCalled()
91
+ expect(em.rollback).toHaveBeenCalledTimes(1)
92
+ })
93
+
94
+ it('propagates a thrown error and does NOT flush when a phase throws (non-transactional)', async () => {
95
+ const em = createFakeEm()
96
+ const failure = new Error('phase-failure')
97
+
98
+ await expect(
99
+ withAtomicFlush(em as any, [
100
+ () => {
101
+ // ok
102
+ },
103
+ () => {
104
+ throw failure
105
+ },
106
+ () => {
107
+ throw new Error('should-not-run')
108
+ },
109
+ ]),
110
+ ).rejects.toBe(failure)
111
+
112
+ expect(em.flush).not.toHaveBeenCalled()
113
+ })
114
+
115
+ it('is a true no-op when phases is empty — no flush, no transaction', async () => {
116
+ const em = createFakeEm()
117
+
118
+ await withAtomicFlush(em as any, [])
119
+
120
+ expect(em.flush).not.toHaveBeenCalled()
121
+ expect(em.begin).not.toHaveBeenCalled()
122
+ })
123
+
124
+ it('is a true no-op when phases is empty even with transaction option', async () => {
125
+ const em = createFakeEm()
126
+
127
+ await withAtomicFlush(em as any, [], { transaction: true })
128
+
129
+ expect(em.flush).not.toHaveBeenCalled()
130
+ expect(em.begin).not.toHaveBeenCalled()
131
+ expect(em.commit).not.toHaveBeenCalled()
132
+ expect(em.rollback).not.toHaveBeenCalled()
133
+ })
134
+
135
+ it('does not swallow an async rejection from a phase', async () => {
136
+ const em = createFakeEm()
137
+ const failure = new Error('async-failure')
138
+
139
+ await expect(
140
+ withAtomicFlush(em as any, [
141
+ async () => {
142
+ await Promise.resolve()
143
+ throw failure
144
+ },
145
+ ]),
146
+ ).rejects.toBe(failure)
147
+
148
+ expect(em.flush).not.toHaveBeenCalled()
149
+ })
150
+
151
+ it('throws the original phase error even if rollback fails', async () => {
152
+ const em = createFakeEm()
153
+ em.rollback.mockRejectedValueOnce(new Error('rollback-failed'))
154
+ const failure = new Error('phase-failure')
155
+
156
+ await expect(
157
+ withAtomicFlush(em as any, [
158
+ () => {
159
+ throw failure
160
+ },
161
+ ], { transaction: true }),
162
+ ).rejects.toBe(failure)
163
+
164
+ expect(em.rollback).toHaveBeenCalledTimes(1)
165
+ })
166
+ })
@@ -0,0 +1,56 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+
3
+ /**
4
+ * Wraps multiple mutation phases in a single atomic flush.
5
+ *
6
+ * Prevents partial commits when a command mutates entities across
7
+ * multiple phases (e.g., scalar mutations + relation syncs).
8
+ * Each phase function runs sequentially; a single `em.flush()`
9
+ * commits all changes at the end on the same `EntityManager` the
10
+ * phases mutate, so closures over `em` stay valid.
11
+ *
12
+ * When `options.transaction` is true, the whole sequence runs
13
+ * inside a database transaction (`em.begin()` / `em.commit()` /
14
+ * `em.rollback()`) for all-or-nothing semantics. The outer `em`
15
+ * stays bound to the transaction, so phases that close over `em`
16
+ * participate in the same transaction.
17
+ *
18
+ * When `phases` is empty the call is a true no-op — no flush,
19
+ * no transaction. Callers that need an explicit commit should
20
+ * pass at least one phase.
21
+ *
22
+ * Keep side-effect emissions (`emitCrudSideEffects` etc.) OUTSIDE
23
+ * the `withAtomicFlush` block — they should only fire after commit.
24
+ */
25
+ export async function withAtomicFlush(
26
+ em: EntityManager,
27
+ phases: Array<() => void | Promise<void>>,
28
+ options?: { transaction?: boolean },
29
+ ): Promise<void> {
30
+ if (phases.length === 0) return
31
+
32
+ const runPhasesAndFlush = async () => {
33
+ for (const phase of phases) {
34
+ await phase()
35
+ }
36
+ await em.flush()
37
+ }
38
+
39
+ if (options?.transaction) {
40
+ await em.begin()
41
+ try {
42
+ await runPhasesAndFlush()
43
+ await em.commit()
44
+ } catch (err) {
45
+ try {
46
+ await em.rollback()
47
+ } catch {
48
+ // rollback failure should not mask the original error; intentionally swallowed
49
+ }
50
+ throw err
51
+ }
52
+ return
53
+ }
54
+
55
+ await runPhasesAndFlush()
56
+ }
@@ -271,7 +271,7 @@ describe('BasicQueryEngine', () => {
271
271
  expect(hasEqualityFilter).toBe(true)
272
272
  })
273
273
 
274
- test('customFieldSources equality filters stay exact when search tokens are available', async () => {
274
+ test('customFieldSources equality filters use search tokens when available', async () => {
275
275
  const fakeKnex = createFakeKnex({
276
276
  customer_entities: [],
277
277
  customer_people: [],
@@ -303,6 +303,65 @@ describe('BasicQueryEngine', () => {
303
303
  page: { page: 1, pageSize: 10 },
304
304
  })
305
305
 
306
+ expect(applySearchTokensSpy).toHaveBeenCalledTimes(1)
307
+ // Assert the search-tokens adapter was invoked with the expected join-scoped options
308
+ expect(applySearchTokensSpy).toHaveBeenCalledWith(
309
+ expect.anything(),
310
+ expect.objectContaining({
311
+ entity: 'customers:customer_person_profile',
312
+ field: 'id',
313
+ recordIdColumn: 'person_profile.id',
314
+ tenantId: 't1',
315
+ tokens: expect.arrayContaining(['profile']),
316
+ hashes: expect.any(Array),
317
+ }),
318
+ )
319
+ const callArgs = applySearchTokensSpy.mock.calls[0][1]
320
+ expect(callArgs.tokens.length).toBeGreaterThan(0)
321
+ expect(callArgs.hashes.length).toBe(callArgs.tokens.length)
322
+ const baseCall = fakeKnex._calls.find((b: any) => b._ops.table === 'customer_entities')
323
+ expect(baseCall).toBeTruthy()
324
+ // The join subquery that the parent whereExists wraps MUST still target customer_people
325
+ const existsFilter = baseCall._ops.wheres.find((w: any) => Array.isArray(w) && w[0] === 'exists')
326
+ expect(existsFilter).toBeTruthy()
327
+ expect(existsFilter[1]?._ops?.table).toBe('customer_people')
328
+ })
329
+
330
+ test('customFieldSources equality filters stay exact when search is disabled', async () => {
331
+ // This is the baseline row-set invariant: when the search-tokens table is
332
+ // absent (searchEnabled=false), $eq must route through the exact EXISTS
333
+ // subquery path, producing the pre-change `person_profile.id = 'profile-1'`
334
+ // filter — not the tokenized OR across search-tokens columns.
335
+ const fakeKnex = createFakeKnex({
336
+ customer_entities: [],
337
+ customer_people: [],
338
+ 'information_schema.columns': [
339
+ { table_name: 'customer_entities', column_name: 'tenant_id' },
340
+ { table_name: 'customer_people', column_name: 'id' },
341
+ { table_name: 'customer_people', column_name: 'tenant_id' },
342
+ ],
343
+ })
344
+ const engine = new BasicQueryEngine({} as any, () => fakeKnex as any)
345
+ jest.spyOn(engine as any, 'tableExists').mockResolvedValue(false)
346
+ const applySearchTokensSpy = jest.spyOn(engine as any, 'applySearchTokens')
347
+
348
+ await engine.query('customers:customer_entity', {
349
+ tenantId: 't1',
350
+ fields: ['id'],
351
+ customFieldSources: [
352
+ {
353
+ entityId: 'customers:customer_person_profile',
354
+ table: 'customer_people',
355
+ alias: 'person_profile',
356
+ join: { fromField: 'id', toField: 'entity_id' },
357
+ },
358
+ ],
359
+ filters: {
360
+ 'person_profile.id': { $eq: 'profile-1' },
361
+ },
362
+ page: { page: 1, pageSize: 10 },
363
+ })
364
+
306
365
  expect(applySearchTokensSpy).not.toHaveBeenCalled()
307
366
  const baseCall = fakeKnex._calls.find((b: any) => b._ops.table === 'customer_entities')
308
367
  expect(baseCall).toBeTruthy()
@@ -136,6 +136,35 @@ function buildFilterableCustomFieldJoins(
136
136
  // For now, only supports basic base-entity querying by table name inferred from EntityId ('<module>:<entity>' -> '<entities>') via convention.
137
137
  // Extensions and custom fields will be added iteratively.
138
138
 
139
+ const computeScore = (cfg: Record<string, unknown>, kind: string, entityIndex: number) => {
140
+ const listVisibleScore = cfg.listVisible === false ? 0 : 1
141
+ const formEditableScore = cfg.formEditable === false ? 0 : 1
142
+ const filterableScore = cfg.filterable ? 1 : 0
143
+ const kindScore = (() => {
144
+ switch (kind) {
145
+ case 'dictionary':
146
+ return 8
147
+ case 'relation':
148
+ return 6
149
+ case 'select':
150
+ return 4
151
+ case 'multiline':
152
+ return 3
153
+ case 'boolean':
154
+ case 'integer':
155
+ case 'float':
156
+ return 2
157
+ default:
158
+ return 1
159
+ }
160
+ })()
161
+ const optionsBonus = Array.isArray(cfg.options) && cfg.options.length ? 2 : 0
162
+ const dictionaryBonus = typeof cfg.dictionaryId === 'string' && cfg.dictionaryId.trim().length ? 5 : 0
163
+ const base = (listVisibleScore * 16) + (formEditableScore * 8) + (filterableScore * 4) + kindScore + optionsBonus + dictionaryBonus
164
+ const penalty = typeof cfg.priority === 'number' ? cfg.priority : 0
165
+ return { base, penalty, entityIndex }
166
+ }
167
+
139
168
  export class BasicQueryEngine implements QueryEngine {
140
169
  private columnCache = new Map<string, boolean>()
141
170
  private tableCache = new Map<string, boolean>()
@@ -330,6 +359,15 @@ export class BasicQueryEngine implements QueryEngine {
330
359
  return builder
331
360
  }
332
361
 
362
+ // `eq` is accepted alongside `like`/`ilike` so that filters against
363
+ // encrypted joined columns (whose ciphertext cannot be compared for
364
+ // equality in SQL) can still resolve via tokenized search. Routing
365
+ // only applies when `searchEnabled` is true AND the joined entity has
366
+ // search tokens installed (`searchAvailable`); for non-searchable or
367
+ // non-encrypted columns the caller still falls through to exact SQL
368
+ // equality via `applyFilterOp`. Note that token match is approximate —
369
+ // callers needing strict equality on encrypted fields should filter on
370
+ // the deterministic `*_hash` column instead.
333
371
  const applyJoinFilterOp = async (
334
372
  builder: any,
335
373
  filter: { column: string; op: string; value?: unknown },
@@ -337,7 +375,7 @@ export class BasicQueryEngine implements QueryEngine {
337
375
  join: ResolvedJoin,
338
376
  ): Promise<boolean> => {
339
377
  if (!searchEnabled || !join.entityId) return false
340
- if (!['like', 'ilike'].includes(filter.op)) return false
378
+ if (!['eq', 'like', 'ilike'].includes(filter.op)) return false
341
379
  if (typeof filter.value !== 'string' || filter.value.trim().length === 0) return false
342
380
 
343
381
  let searchAvailable = joinSearchAvailability.get(join.entityId)
@@ -1106,31 +1144,3 @@ export class BasicQueryEngine implements QueryEngine {
1106
1144
  }
1107
1145
 
1108
1146
  }
1109
- const computeScore = (cfg: Record<string, unknown>, kind: string, entityIndex: number) => {
1110
- const listVisibleScore = cfg.listVisible === false ? 0 : 1
1111
- const formEditableScore = cfg.formEditable === false ? 0 : 1
1112
- const filterableScore = cfg.filterable ? 1 : 0
1113
- const kindScore = (() => {
1114
- switch (kind) {
1115
- case 'dictionary':
1116
- return 8
1117
- case 'relation':
1118
- return 6
1119
- case 'select':
1120
- return 4
1121
- case 'multiline':
1122
- return 3
1123
- case 'boolean':
1124
- case 'integer':
1125
- case 'float':
1126
- return 2
1127
- default:
1128
- return 1
1129
- }
1130
- })()
1131
- const optionsBonus = Array.isArray(cfg.options) && cfg.options.length ? 2 : 0
1132
- const dictionaryBonus = typeof cfg.dictionaryId === 'string' && cfg.dictionaryId.trim().length ? 5 : 0
1133
- const base = (listVisibleScore * 16) + (formEditableScore * 8) + (filterableScore * 4) + kindScore + optionsBonus + dictionaryBonus
1134
- const penalty = typeof cfg.priority === 'number' ? cfg.priority : 0
1135
- return { base, penalty, entityIndex }
1136
- }