@open-mercato/shared 0.6.5-develop.5337.1.534b781eac → 0.6.5-develop.5382.1.f542de69af

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.
Files changed (47) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -1
  3. package/dist/lib/ai/llm-provider-registry.js.map +1 -1
  4. package/dist/lib/crud/custom-fields.js +23 -15
  5. package/dist/lib/crud/custom-fields.js.map +2 -2
  6. package/dist/lib/crud/factory.js.map +1 -1
  7. package/dist/lib/crud/optimistic-lock-command.js.map +1 -1
  8. package/dist/lib/crud/optimistic-lock-headers.js.map +1 -1
  9. package/dist/lib/crud/optimistic-lock-store.js.map +1 -1
  10. package/dist/lib/crud/optimistic-lock.js.map +1 -1
  11. package/dist/lib/db/buildIlikeTerm.js +17 -0
  12. package/dist/lib/db/buildIlikeTerm.js.map +7 -0
  13. package/dist/lib/di/container.js +1 -1
  14. package/dist/lib/di/container.js.map +1 -1
  15. package/dist/lib/query/advanced-filter-tree.js +5 -5
  16. package/dist/lib/query/advanced-filter-tree.js.map +2 -2
  17. package/dist/lib/query/advanced-filter.js +5 -5
  18. package/dist/lib/query/advanced-filter.js.map +2 -2
  19. package/dist/lib/query/engine.js +3 -1
  20. package/dist/lib/query/engine.js.map +2 -2
  21. package/dist/lib/version.js +1 -1
  22. package/dist/lib/version.js.map +1 -1
  23. package/dist/modules/overrides.js +1 -1
  24. package/dist/modules/overrides.js.map +1 -1
  25. package/dist/modules/search.js.map +1 -1
  26. package/package.json +2 -2
  27. package/src/lib/ai/llm-provider-registry.ts +1 -1
  28. package/src/lib/ai/llm-provider.ts +1 -1
  29. package/src/lib/crud/__tests__/custom-fields.test.ts +91 -0
  30. package/src/lib/crud/custom-fields.ts +30 -17
  31. package/src/lib/crud/factory.ts +1 -1
  32. package/src/lib/crud/optimistic-lock-command.ts +1 -1
  33. package/src/lib/crud/optimistic-lock-headers.ts +1 -1
  34. package/src/lib/crud/optimistic-lock-store.ts +1 -1
  35. package/src/lib/crud/optimistic-lock.ts +1 -1
  36. package/src/lib/db/__tests__/buildIlikeTerm.test.ts +40 -0
  37. package/src/lib/db/__tests__/escapeLikePattern.test.ts +123 -0
  38. package/src/lib/db/buildIlikeTerm.ts +16 -0
  39. package/src/lib/di/container.ts +1 -1
  40. package/src/lib/query/__tests__/engine.count-distinct.test.ts +229 -0
  41. package/src/lib/query/advanced-filter-tree.ts +5 -5
  42. package/src/lib/query/advanced-filter.ts +5 -5
  43. package/src/lib/query/engine.ts +13 -2
  44. package/src/modules/__tests__/overrides.test.ts +1 -1
  45. package/src/modules/__tests__/route-overrides.test.ts +1 -1
  46. package/src/modules/overrides.ts +3 -3
  47. package/src/modules/search.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { escapeLikePattern } from '../db/escapeLikePattern'
1
+ import { buildIlikeTerm } from '../db/buildIlikeTerm'
2
2
 
3
3
  export type FilterOperator =
4
4
  | 'is' | 'is_not' | 'contains' | 'does_not_contain' | 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty'
@@ -190,19 +190,19 @@ function buildConditionFilter(condition: FilterCondition): Record<string, unknow
190
190
  break
191
191
  case 'contains':
192
192
  if (normalizeSingleValue(condition.value) === null) return null
193
- filter[condition.field] = { $ilike: `%${escapeLikePattern(String(normalizeSingleValue(condition.value)))}%` }
193
+ filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value))) }
194
194
  break
195
195
  case 'does_not_contain':
196
196
  if (normalizeSingleValue(condition.value) === null) return null
197
- filter[condition.field] = { $not: { $ilike: `%${escapeLikePattern(String(normalizeSingleValue(condition.value)))}%` } }
197
+ filter[condition.field] = { $not: { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value))) } }
198
198
  break
199
199
  case 'starts_with':
200
200
  if (normalizeSingleValue(condition.value) === null) return null
201
- filter[condition.field] = { $ilike: `${escapeLikePattern(String(normalizeSingleValue(condition.value)))}%` }
201
+ filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value)), 'startsWith') }
202
202
  break
203
203
  case 'ends_with':
204
204
  if (normalizeSingleValue(condition.value) === null) return null
205
- filter[condition.field] = { $ilike: `%${escapeLikePattern(String(normalizeSingleValue(condition.value)))}` }
205
+ filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value)), 'endsWith') }
206
206
  break
207
207
  case 'is_empty':
208
208
  filter[condition.field] = { $exists: false }
@@ -844,9 +844,20 @@ export class BasicQueryEngine implements QueryEngine {
844
844
  if (hasJoinedAggregates) {
845
845
  q = q.groupBy(`${table}.id`)
846
846
  }
847
+ // `count(distinct base.id)` is only required when a join can multiply base rows
848
+ // (CF/extension aggregates, explicit relation joins, or custom-field sources).
849
+ // Without such joins base.id is the unique PK, so `count(*)` is equivalent and
850
+ // lets Postgres skip the redundant DISTINCT sort/hash for an index-only count (#2227).
851
+ const mayMultiplyBaseRows =
852
+ hasJoinedAggregates ||
853
+ (Array.isArray(opts.joins) && opts.joins.length > 0) ||
854
+ (Array.isArray(opts.customFieldSources) && opts.customFieldSources.length > 0)
855
+ const countExpr = mayMultiplyBaseRows
856
+ ? sql<string>`count(distinct ${sql.ref(`${table}.id`)})`
857
+ : sql<string>`count(*)`
847
858
  const countBuilder = hasJoinedAggregates
848
- ? q.clearSelect().clearOrderBy().clearGroupBy().select(sql<string>`count(distinct ${sql.ref(`${table}.id`)})`.as('count'))
849
- : q.clearSelect().clearOrderBy().select(sql<string>`count(distinct ${sql.ref(`${table}.id`)})`.as('count'))
859
+ ? q.clearSelect().clearOrderBy().clearGroupBy().select(countExpr.as('count'))
860
+ : q.clearSelect().clearOrderBy().select(countExpr.as('count'))
850
861
  const countRow = await countBuilder.executeTakeFirst() as { count: unknown } | undefined
851
862
  const total = Number((countRow as any)?.count ?? 0)
852
863
  const dataQuery = requiresPlaintextSort
@@ -6,7 +6,7 @@
6
6
  * by shared's default appliers; custom/future domains still warn if no
7
7
  * applier is registered.
8
8
  *
9
- * Spec: `.ai/specs/2026-05-04-modules-ts-unified-overrides.md`.
9
+ * Spec: `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md`.
10
10
  */
11
11
  import {
12
12
  applyModuleOverridesFromEnabledModules,
@@ -12,7 +12,7 @@
12
12
  * - `registerApiRouteManifests` consults the override composer.
13
13
  * - Stale override keys emit a warning so operators notice.
14
14
  *
15
- * Spec: `.ai/specs/2026-05-04-modules-ts-unified-overrides.md`.
15
+ * Spec: `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md`.
16
16
  */
17
17
  import {
18
18
  applyApiOverridesToManifests,
@@ -2,7 +2,7 @@
2
2
  * Unified `modules.ts` override surface — one place for downstream apps to
3
3
  * replace or disable any contract a module presents.
4
4
  *
5
- * Spec: `.ai/specs/2026-05-04-modules-ts-unified-overrides.md`
5
+ * Spec: `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md`
6
6
  *
7
7
  * Each `ModuleEntry` in `apps/<app>/src/modules.ts` may carry an
8
8
  * `overrides` field whose sub-keys address one domain at a time:
@@ -229,7 +229,7 @@ const DOMAIN_KEYS: ModuleOverrideDomain[] = [
229
229
  ]
230
230
 
231
231
  const TRACKING_ISSUE_HINT =
232
- 'See `.ai/specs/2026-05-04-modules-ts-unified-overrides.md` and tracking issue https://github.com/open-mercato/open-mercato/issues/1787.'
232
+ 'See `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md` and tracking issue https://github.com/open-mercato/open-mercato/issues/1787.'
233
233
 
234
234
  /**
235
235
  * Walk every `ModuleEntry` and dispatch its `overrides.<domain>` shape
@@ -312,7 +312,7 @@ export function applyModuleOverridesFromEnabledModules(
312
312
  // runtime override scenarios call the dispatcher / programmatic API
313
313
  // before `bootstrap.ts` triggers manifest registration.
314
314
  //
315
- // Spec: `.ai/specs/2026-05-04-modules-ts-unified-overrides.md` (Phases 2/3).
315
+ // Spec: `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md` (Phases 2/3).
316
316
 
317
317
  // ApiRouteManifestEntry / ApiHandler / HttpMethod live in ./registry. The
318
318
  // type-only import is erased at runtime, so there is no cycle even though
@@ -160,7 +160,7 @@ export interface SearchStrategy {
160
160
  bulkIndex?(records: IndexableRecord[]): Promise<void>
161
161
 
162
162
  /** Purge all records for an entity type (optional) */
163
- purge?(entityId: EntityId, tenantId: string): Promise<void>
163
+ purge?(entityId: EntityId, tenantId: string, organizationId?: string | null): Promise<void>
164
164
  }
165
165
 
166
166
  // =============================================================================