@open-mercato/core 0.6.4-develop.4015.1.efaafadf79 → 0.6.4-develop.4095.1.9c790dc021

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.
@@ -14,6 +14,7 @@ import {
14
14
  import { resolveSearchConfig } from "@open-mercato/shared/lib/search/config";
15
15
  import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
16
16
  import { runBeforeQueryPipeline, runAfterQueryPipeline } from "@open-mercato/shared/lib/query/query-extension-runner";
17
+ import { resolveEncryptedSortFields, sortRowsInMemory } from "@open-mercato/shared/lib/query/encrypted-sort";
17
18
  function buildFilterableCustomFieldJoins(sources) {
18
19
  if (!sources || sources.length === 0) return [];
19
20
  return sources.flatMap((source, index) => {
@@ -410,6 +411,26 @@ class HybridQueryEngine {
410
411
  if (field === "organization_id" && columns.has("id")) return "id";
411
412
  return null;
412
413
  };
414
+ const fallbackOrgId = opts.organizationId ?? (Array.isArray(opts.organizationIds) && opts.organizationIds.length === 1 ? opts.organizationIds[0] : null);
415
+ const encSvc = this.getEncryptionService();
416
+ const resolvedSorts = [];
417
+ for (const sort of opts.sort || []) {
418
+ const field = String(sort.field);
419
+ if (field.startsWith("cf:")) {
420
+ resolvedSorts.push({ ...sort, field });
421
+ } else {
422
+ const baseField = resolveBaseColumn(field);
423
+ if (baseField) resolvedSorts.push({ ...sort, field: baseField });
424
+ }
425
+ }
426
+ const encryptedSortFields = await resolveEncryptedSortFields(
427
+ encSvc,
428
+ entity,
429
+ resolvedSorts.filter((sort) => !sort.field.startsWith("cf:")).map((sort) => sort.field),
430
+ opts.tenantId ?? null,
431
+ fallbackOrgId
432
+ );
433
+ const requiresPlaintextSort = encryptedSortFields.size > 0;
413
434
  const applyBaseScope = (q) => {
414
435
  let next = q;
415
436
  if (orgScope && hasOrganizationColumn) {
@@ -617,6 +638,9 @@ class HybridQueryEngine {
617
638
  const hasCustomFieldFilters = cfFilters.length > 0;
618
639
  const canOptimizeCount = !hasCustomFieldFilters && !hasNonBaseSearchSource;
619
640
  const selectFieldSet = new Set(opts.fields && opts.fields.length ? opts.fields.map(String) : Array.from(columns.keys()));
641
+ if (requiresPlaintextSort) {
642
+ for (const field of encryptedSortFields) selectFieldSet.add(field);
643
+ }
620
644
  if (opts.includeCustomFields === true) {
621
645
  const entityIds = Array.from(new Set(indexSources.map((src) => String(src.entityId))));
622
646
  try {
@@ -647,7 +671,8 @@ class HybridQueryEngine {
647
671
  };
648
672
  const applySort = (q) => {
649
673
  let next = q;
650
- for (const s of opts.sort || []) {
674
+ if (requiresPlaintextSort) return next;
675
+ for (const s of resolvedSorts) {
651
676
  const fieldName = String(s.field);
652
677
  if (fieldName.startsWith("cf:")) {
653
678
  const textExpr = this.buildCfTextExprSql(fieldName, indexSources);
@@ -722,7 +747,9 @@ class HybridQueryEngine {
722
747
  let dataBuilder = await applyQueryShape(dataRoot);
723
748
  dataBuilder = applySelection(dataBuilder);
724
749
  dataBuilder = applySort(dataBuilder);
725
- dataBuilder = dataBuilder.limit(pageSize).offset((page - 1) * pageSize);
750
+ if (!requiresPlaintextSort) {
751
+ dataBuilder = dataBuilder.limit(pageSize).offset((page - 1) * pageSize);
752
+ }
726
753
  if (debugEnabled && sqlDebugEnabled) {
727
754
  const compiled = dataBuilder.compile();
728
755
  this.debug("query:sql:data", { entity, sql: compiled.sql, bindings: compiled.parameters, page, pageSize });
@@ -736,11 +763,9 @@ class HybridQueryEngine {
736
763
  );
737
764
  if (debugEnabled) this.debug("query:complete", { entity, total, items: Array.isArray(itemsRaw) ? itemsRaw.length : 0 });
738
765
  let items = itemsRaw;
739
- const encSvc = this.getEncryptionService();
740
766
  const dekKeyCache = /* @__PURE__ */ new Map();
741
767
  if (encSvc?.decryptEntityPayload) {
742
768
  const decrypt = encSvc.decryptEntityPayload.bind(encSvc);
743
- const fallbackOrgId = opts.organizationId ?? (Array.isArray(opts.organizationIds) && opts.organizationIds.length === 1 ? opts.organizationIds[0] : null);
744
769
  items = await Promise.all(
745
770
  items.map(async (item) => {
746
771
  try {
@@ -777,6 +802,9 @@ class HybridQueryEngine {
777
802
  })
778
803
  );
779
804
  }
805
+ if (requiresPlaintextSort) {
806
+ items = sortRowsInMemory(items, resolvedSorts).slice((page - 1) * pageSize, page * pageSize);
807
+ }
780
808
  const typedItems = items;
781
809
  let result = { items: typedItems, page, pageSize, total };
782
810
  if (partialIndexWarning) result.meta = { partialIndexWarning };