@open-mercato/shared 0.9.1-develop.7474.1.51db10479d → 0.9.1-develop.7476.1.342138d35e

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 (36) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -1
  3. package/dist/lib/catalog-visibility/canonicalizeEffectiveScope.js +33 -0
  4. package/dist/lib/catalog-visibility/canonicalizeEffectiveScope.js.map +7 -0
  5. package/dist/lib/catalog-visibility/hashEffectiveScope.js +9 -0
  6. package/dist/lib/catalog-visibility/hashEffectiveScope.js.map +7 -0
  7. package/dist/lib/catalog-visibility/index.js +4 -0
  8. package/dist/lib/catalog-visibility/index.js.map +2 -2
  9. package/dist/lib/localization/index.js +9 -1
  10. package/dist/lib/localization/index.js.map +2 -2
  11. package/dist/lib/localization/translatable-fields.js +26 -0
  12. package/dist/lib/localization/translatable-fields.js.map +2 -2
  13. package/dist/lib/query/engine.js +29 -2
  14. package/dist/lib/query/engine.js.map +2 -2
  15. package/dist/lib/query/join-utils.js +1 -1
  16. package/dist/lib/query/join-utils.js.map +2 -2
  17. package/dist/lib/query/overlap.js +58 -0
  18. package/dist/lib/query/overlap.js.map +7 -0
  19. package/dist/lib/query/types.js.map +2 -2
  20. package/dist/lib/version.js +1 -1
  21. package/dist/lib/version.js.map +1 -1
  22. package/dist/modules/search.js.map +2 -2
  23. package/package.json +2 -2
  24. package/src/lib/catalog-visibility/__tests__/hashEffectiveScope.test.ts +121 -0
  25. package/src/lib/catalog-visibility/canonicalizeEffectiveScope.ts +41 -0
  26. package/src/lib/catalog-visibility/hashEffectiveScope.ts +8 -0
  27. package/src/lib/catalog-visibility/index.ts +2 -0
  28. package/src/lib/localization/__tests__/translatable-fields.test.ts +17 -0
  29. package/src/lib/localization/index.ts +8 -1
  30. package/src/lib/localization/translatable-fields.ts +46 -0
  31. package/src/lib/query/__tests__/overlap.test.ts +219 -0
  32. package/src/lib/query/engine.ts +33 -2
  33. package/src/lib/query/join-utils.ts +1 -1
  34. package/src/lib/query/overlap.ts +106 -0
  35. package/src/lib/query/types.ts +13 -1
  36. package/src/modules/search.ts +39 -0
@@ -1,3 +1,3 @@
1
1
  [generate-countries] up to date (259 countries)
2
- [build:shared] found 314 entry points
2
+ [build:shared] found 317 entry points
3
3
  [build:shared] built successfully
package/AGENTS.md CHANGED
@@ -38,7 +38,7 @@ yarn workspace @open-mercato/shared build
38
38
  | `auth/organizationScope` | When an organization-scoped API route must resolve the caller's organization — falls back to `actorOrgId` for an "all organizations" selection, but only while the effective tenant is still the actor's tenant. On `null` for an authenticated caller answer with `organizationScopeRequiredResponse()` (400, code `organization_scope_required`) — never 401 | `@open-mercato/shared/lib/auth/organizationScope` — `resolveActiveOrganizationId(auth)`, `organizationScopeRequiredResponse()` |
39
39
  | `boolean/` | When parsing boolean strings from env/query params | `@open-mercato/shared/lib/boolean` |
40
40
  | `browser/` | When persisting client UI state to `localStorage` — use the safe wrappers and the versioned-envelope helper instead of raw `localStorage` reads/writes | `@open-mercato/shared/lib/browser/safeLocalStorage`, `@open-mercato/shared/lib/browser/versionedPreference` |
41
- | `catalog-visibility/` | When combining buyer-facing catalog assortment grants (group-level, channel-level) into one effective visibility check — `AssortmentScope`/`EffectiveAssortmentScope` types plus the pure `matchesOne`/`matchesScope`/`unionScopes`/`intersectScopes` algebra. Imports nothing from `catalog`, `customer_groups`, `ecommerce`, or `cart` by construction; `customer_groups`'s `resolveAssortmentScope()` and `ecommerce`'s `BuyerContext.assortmentScope` composition build on top of it | `@open-mercato/shared/lib/catalog-visibility` |
41
+ | `catalog-visibility/` | When combining buyer-facing catalog assortment grants (group-level, channel-level) into one effective visibility check — `AssortmentScope`/`EffectiveAssortmentScope` types plus the pure `matchesOne`/`matchesScope`/`unionScopes`/`intersectScopes` algebra, plus `canonicalizeEffectiveScope`/`hashEffectiveScope` (16-hex digest for cache keys; the hash uses `node:crypto`, server-only). Imports nothing from `catalog`, `customer_groups`, `ecommerce`, or `cart` by construction; `customer_groups`'s `resolveAssortmentScope()` and `ecommerce`'s `BuyerContext.assortmentScope` composition build on top of it | `@open-mercato/shared/lib/catalog-visibility` |
42
42
  | `commands/` | When implementing undo/redo command pattern | `@open-mercato/shared/lib/commands` |
43
43
  | `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? })` |
44
44
  | `commands/runCrudCommandWrite` | When a command writes an entity + custom fields + CRUD/index side effects in one logical operation — composes fork → atomic flush → custom-field write → side-effect queue in the only correct order. **Prefer this over composing the primitives by hand for new commands.** | `@open-mercato/shared/lib/commands/runCrudCommandWrite` — `runCrudCommandWrite({ ctx, entityId, action, scope, phases, customFields?, events?, indexer?, sideEffect })` |
@@ -0,0 +1,33 @@
1
+ const ID_LIST_KEYS = [
2
+ "categoryIds",
3
+ "excludeCategoryIds",
4
+ "excludeProductIds",
5
+ "excludeTagIds",
6
+ "tagIds"
7
+ ];
8
+ function compareStrings(left, right) {
9
+ if (left < right) return -1;
10
+ if (left > right) return 1;
11
+ return 0;
12
+ }
13
+ function sortedUnique(values) {
14
+ return Array.from(new Set(values)).sort(compareStrings);
15
+ }
16
+ function canonicalizeBranch(scope) {
17
+ const members = [];
18
+ const nested = (scope.allOf ?? []).map(canonicalizeBranch);
19
+ if (nested.length > 0) members.push(`"allOf":[${sortedUnique(nested).join(",")}]`);
20
+ for (const key of ID_LIST_KEYS) {
21
+ const ids = scope[key] ?? [];
22
+ if (ids.length > 0) members.push(`${JSON.stringify(key)}:${JSON.stringify(sortedUnique(ids))}`);
23
+ }
24
+ return `{${members.join(",")}}`;
25
+ }
26
+ function canonicalizeEffectiveScope(scope) {
27
+ if (scope === null) return "null";
28
+ return `[${sortedUnique(scope.map(canonicalizeBranch)).join(",")}]`;
29
+ }
30
+ export {
31
+ canonicalizeEffectiveScope
32
+ };
33
+ //# sourceMappingURL=canonicalizeEffectiveScope.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/catalog-visibility/canonicalizeEffectiveScope.ts"],
4
+ "sourcesContent": ["import type { AssortmentScope, EffectiveAssortmentScope } from './types'\n\nconst ID_LIST_KEYS = [\n 'categoryIds',\n 'excludeCategoryIds',\n 'excludeProductIds',\n 'excludeTagIds',\n 'tagIds',\n] as const\n\nfunction compareStrings(left: string, right: string): number {\n if (left < right) return -1\n if (left > right) return 1\n return 0\n}\n\nfunction sortedUnique(values: string[]): string[] {\n return Array.from(new Set(values)).sort(compareStrings)\n}\n\nfunction canonicalizeBranch(scope: AssortmentScope): string {\n const members: string[] = []\n const nested = (scope.allOf ?? []).map(canonicalizeBranch)\n if (nested.length > 0) members.push(`\"allOf\":[${sortedUnique(nested).join(',')}]`)\n for (const key of ID_LIST_KEYS) {\n const ids = scope[key] ?? []\n if (ids.length > 0) members.push(`${JSON.stringify(key)}:${JSON.stringify(sortedUnique(ids))}`)\n }\n return `{${members.join(',')}}`\n}\n\n/**\n * Deterministic serialization of a resolved scope. `null` (unrestricted) serializes as `null`\n * and `[]` (deny-all) as `[]`, so the two never collide. Empty and absent arrays are\n * equivalent, id lists are sorted and deduplicated, and OR-branches and `allOf` entries are\n * ordered by their own canonical form with identical ones collapsed.\n */\nexport function canonicalizeEffectiveScope(scope: EffectiveAssortmentScope): string {\n if (scope === null) return 'null'\n return `[${sortedUnique(scope.map(canonicalizeBranch)).join(',')}]`\n}\n"],
5
+ "mappings": "AAEA,MAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,eAAe,MAAc,OAAuB;AAC3D,MAAI,OAAO,MAAO,QAAO;AACzB,MAAI,OAAO,MAAO,QAAO;AACzB,SAAO;AACT;AAEA,SAAS,aAAa,QAA4B;AAChD,SAAO,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,EAAE,KAAK,cAAc;AACxD;AAEA,SAAS,mBAAmB,OAAgC;AAC1D,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAU,MAAM,SAAS,CAAC,GAAG,IAAI,kBAAkB;AACzD,MAAI,OAAO,SAAS,EAAG,SAAQ,KAAK,YAAY,aAAa,MAAM,EAAE,KAAK,GAAG,CAAC,GAAG;AACjF,aAAW,OAAO,cAAc;AAC9B,UAAM,MAAM,MAAM,GAAG,KAAK,CAAC;AAC3B,QAAI,IAAI,SAAS,EAAG,SAAQ,KAAK,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,KAAK,UAAU,aAAa,GAAG,CAAC,CAAC,EAAE;AAAA,EAChG;AACA,SAAO,IAAI,QAAQ,KAAK,GAAG,CAAC;AAC9B;AAQO,SAAS,2BAA2B,OAAyC;AAClF,MAAI,UAAU,KAAM,QAAO;AAC3B,SAAO,IAAI,aAAa,MAAM,IAAI,kBAAkB,CAAC,EAAE,KAAK,GAAG,CAAC;AAClE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,9 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonicalizeEffectiveScope } from "./canonicalizeEffectiveScope.js";
3
+ function hashEffectiveScope(scope) {
4
+ return createHash("sha256").update(canonicalizeEffectiveScope(scope)).digest("hex").slice(0, 16);
5
+ }
6
+ export {
7
+ hashEffectiveScope
8
+ };
9
+ //# sourceMappingURL=hashEffectiveScope.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/catalog-visibility/hashEffectiveScope.ts"],
4
+ "sourcesContent": ["import { createHash } from 'node:crypto'\nimport { canonicalizeEffectiveScope } from './canonicalizeEffectiveScope'\nimport type { EffectiveAssortmentScope } from './types'\n\n/** 16-hex-character sha256 digest of the canonical scope (SPEC-029 \u00A76.1 truncation). Server-only: uses node:crypto. */\nexport function hashEffectiveScope(scope: EffectiveAssortmentScope): string {\n return createHash('sha256').update(canonicalizeEffectiveScope(scope)).digest('hex').slice(0, 16)\n}\n"],
5
+ "mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,kCAAkC;AAIpC,SAAS,mBAAmB,OAAyC;AAC1E,SAAO,WAAW,QAAQ,EAAE,OAAO,2BAA2B,KAAK,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACjG;",
6
+ "names": []
7
+ }
@@ -2,7 +2,11 @@ import { matchesOne } from "./matchesOne.js";
2
2
  import { matchesScope } from "./matchesScope.js";
3
3
  import { unionScopes } from "./unionScopes.js";
4
4
  import { intersectScopes } from "./intersectScopes.js";
5
+ import { canonicalizeEffectiveScope } from "./canonicalizeEffectiveScope.js";
6
+ import { hashEffectiveScope } from "./hashEffectiveScope.js";
5
7
  export {
8
+ canonicalizeEffectiveScope,
9
+ hashEffectiveScope,
6
10
  intersectScopes,
7
11
  matchesOne,
8
12
  matchesScope,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/catalog-visibility/index.ts"],
4
- "sourcesContent": ["export type { AssortmentScope, EffectiveAssortmentScope, ScopedProduct } from './types'\nexport { matchesOne } from './matchesOne'\nexport { matchesScope } from './matchesScope'\nexport { unionScopes } from './unionScopes'\nexport { intersectScopes } from './intersectScopes'\n"],
5
- "mappings": "AACA,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;",
4
+ "sourcesContent": ["export type { AssortmentScope, EffectiveAssortmentScope, ScopedProduct } from './types'\nexport { matchesOne } from './matchesOne'\nexport { matchesScope } from './matchesScope'\nexport { unionScopes } from './unionScopes'\nexport { intersectScopes } from './intersectScopes'\nexport { canonicalizeEffectiveScope } from './canonicalizeEffectiveScope'\nexport { hashEffectiveScope } from './hashEffectiveScope'\n"],
5
+ "mappings": "AACA,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAChC,SAAS,kCAAkC;AAC3C,SAAS,0BAA0B;",
6
6
  "names": []
7
7
  }
@@ -1,11 +1,19 @@
1
1
  import { applyLocalizedContent } from "./resolver.js";
2
- import { registerTranslatableFields, getTranslatableFields, getTranslatableFieldsRegistry } from "./translatable-fields.js";
2
+ import {
3
+ registerTranslatableFields,
4
+ getTranslatableFields,
5
+ getTranslatableFieldsRegistry,
6
+ registerTranslatableFieldExpander,
7
+ getTranslatableFieldExpander
8
+ } from "./translatable-fields.js";
3
9
  import { registerTranslationOverlayPlugin, getTranslationOverlayPlugin } from "./overlay-plugin.js";
4
10
  export {
5
11
  applyLocalizedContent,
12
+ getTranslatableFieldExpander,
6
13
  getTranslatableFields,
7
14
  getTranslatableFieldsRegistry,
8
15
  getTranslationOverlayPlugin,
16
+ registerTranslatableFieldExpander,
9
17
  registerTranslatableFields,
10
18
  registerTranslationOverlayPlugin
11
19
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/localization/index.ts"],
4
- "sourcesContent": ["export type { TranslationRecord, LocaleCode } from './types'\nexport { applyLocalizedContent } from './resolver'\nexport { registerTranslatableFields, getTranslatableFields, getTranslatableFieldsRegistry } from './translatable-fields'\nexport { registerTranslationOverlayPlugin, getTranslationOverlayPlugin } from './overlay-plugin'\nexport type { TranslationOverlayFn, ResolveLocaleFromRequestFn } from './overlay-plugin'\n"],
5
- "mappings": "AACA,SAAS,6BAA6B;AACtC,SAAS,4BAA4B,uBAAuB,qCAAqC;AACjG,SAAS,kCAAkC,mCAAmC;",
4
+ "sourcesContent": ["export type { TranslationRecord, LocaleCode } from './types'\nexport { applyLocalizedContent } from './resolver'\nexport {\n registerTranslatableFields,\n getTranslatableFields,\n getTranslatableFieldsRegistry,\n registerTranslatableFieldExpander,\n getTranslatableFieldExpander,\n} from './translatable-fields'\nexport type { TranslatableFieldExpansion, TranslatableFieldExpander } from './translatable-fields'\nexport { registerTranslationOverlayPlugin, getTranslationOverlayPlugin } from './overlay-plugin'\nexport type { TranslationOverlayFn, ResolveLocaleFromRequestFn } from './overlay-plugin'\n"],
5
+ "mappings": "AACA,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,kCAAkC,mCAAmC;",
6
6
  "names": []
7
7
  }
@@ -14,9 +14,35 @@ function getTranslatableFields(entityType) {
14
14
  function getTranslatableFieldsRegistry() {
15
15
  return { ...getGlobal() };
16
16
  }
17
+ const EXPANDER_GLOBAL_KEY = "__openMercatoTranslatableFieldExpanders__";
18
+ function getExpanderGlobal() {
19
+ return globalThis[EXPANDER_GLOBAL_KEY] ?? {};
20
+ }
21
+ function registerTranslatableFieldExpander(entityType, expander) {
22
+ const host = globalThis;
23
+ host[EXPANDER_GLOBAL_KEY] = { ...getExpanderGlobal(), [entityType]: expander };
24
+ }
25
+ function getTranslatableFieldExpander(entityType) {
26
+ return getExpanderGlobal()[entityType];
27
+ }
28
+ const LIST_PATH_GLOBAL_KEY = "__openMercatoTranslatableEntityListPaths__";
29
+ function getListPathGlobal() {
30
+ return globalThis[LIST_PATH_GLOBAL_KEY] ?? {};
31
+ }
32
+ function registerTranslatableEntityListPath(entityType, listPath) {
33
+ const host = globalThis;
34
+ host[LIST_PATH_GLOBAL_KEY] = { ...getListPathGlobal(), [entityType]: listPath };
35
+ }
36
+ function getTranslatableEntityListPath(entityType) {
37
+ return getListPathGlobal()[entityType];
38
+ }
17
39
  export {
40
+ getTranslatableEntityListPath,
41
+ getTranslatableFieldExpander,
18
42
  getTranslatableFields,
19
43
  getTranslatableFieldsRegistry,
44
+ registerTranslatableEntityListPath,
45
+ registerTranslatableFieldExpander,
20
46
  registerTranslatableFields
21
47
  };
22
48
  //# sourceMappingURL=translatable-fields.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/localization/translatable-fields.ts"],
4
- "sourcesContent": ["type TranslatableFieldsRegistry = Record<string, string[]>\n\n// Use globalThis to survive Turbopack/esbuild module duplication where the same\n// file can be loaded as multiple module instances when mixing dynamic and static imports\nconst GLOBAL_KEY = '__openMercatoTranslatableFields__'\n\nfunction getGlobal(): TranslatableFieldsRegistry {\n return (globalThis as any)[GLOBAL_KEY] ?? {}\n}\n\nfunction setGlobal(registry: TranslatableFieldsRegistry): void {\n (globalThis as any)[GLOBAL_KEY] = registry\n}\n\nexport function registerTranslatableFields(fields: TranslatableFieldsRegistry): void {\n setGlobal({ ...getGlobal(), ...fields })\n}\n\nexport function getTranslatableFields(entityType: string): string[] | undefined {\n return getGlobal()[entityType]\n}\n\nexport function getTranslatableFieldsRegistry(): TranslatableFieldsRegistry {\n return { ...getGlobal() }\n}\n"],
5
- "mappings": "AAIA,MAAM,aAAa;AAEnB,SAAS,YAAwC;AAC/C,SAAQ,WAAmB,UAAU,KAAK,CAAC;AAC7C;AAEA,SAAS,UAAU,UAA4C;AAC7D,EAAC,WAAmB,UAAU,IAAI;AACpC;AAEO,SAAS,2BAA2B,QAA0C;AACnF,YAAU,EAAE,GAAG,UAAU,GAAG,GAAG,OAAO,CAAC;AACzC;AAEO,SAAS,sBAAsB,YAA0C;AAC9E,SAAO,UAAU,EAAE,UAAU;AAC/B;AAEO,SAAS,gCAA4D;AAC1E,SAAO,EAAE,GAAG,UAAU,EAAE;AAC1B;",
4
+ "sourcesContent": ["type TranslatableFieldsRegistry = Record<string, string[]>\n\n// Use globalThis to survive Turbopack/esbuild module duplication where the same\n// file can be loaded as multiple module instances when mixing dynamic and static imports\nconst GLOBAL_KEY = '__openMercatoTranslatableFields__'\n\nfunction getGlobal(): TranslatableFieldsRegistry {\n return (globalThis as any)[GLOBAL_KEY] ?? {}\n}\n\nfunction setGlobal(registry: TranslatableFieldsRegistry): void {\n (globalThis as any)[GLOBAL_KEY] = registry\n}\n\nexport function registerTranslatableFields(fields: TranslatableFieldsRegistry): void {\n setGlobal({ ...getGlobal(), ...fields })\n}\n\nexport function getTranslatableFields(entityType: string): string[] | undefined {\n return getGlobal()[entityType]\n}\n\nexport function getTranslatableFieldsRegistry(): TranslatableFieldsRegistry {\n return { ...getGlobal() }\n}\n\nexport type TranslatableFieldExpansion = {\n key: string\n label?: string\n baseValue?: string\n}\n\nexport type TranslatableFieldExpander = (record: Record<string, unknown>) => TranslatableFieldExpansion[]\n\ntype TranslatableFieldExpanderRegistry = Record<string, TranslatableFieldExpander>\n\nconst EXPANDER_GLOBAL_KEY = '__openMercatoTranslatableFieldExpanders__'\n\ntype ExpanderGlobalHost = { [EXPANDER_GLOBAL_KEY]?: TranslatableFieldExpanderRegistry }\n\nfunction getExpanderGlobal(): TranslatableFieldExpanderRegistry {\n return (globalThis as ExpanderGlobalHost)[EXPANDER_GLOBAL_KEY] ?? {}\n}\n\nexport function registerTranslatableFieldExpander(entityType: string, expander: TranslatableFieldExpander): void {\n const host = globalThis as ExpanderGlobalHost\n host[EXPANDER_GLOBAL_KEY] = { ...getExpanderGlobal(), [entityType]: expander }\n}\n\nexport function getTranslatableFieldExpander(entityType: string): TranslatableFieldExpander | undefined {\n return getExpanderGlobal()[entityType]\n}\n\ntype TranslatableEntityListPathRegistry = Record<string, string>\n\nconst LIST_PATH_GLOBAL_KEY = '__openMercatoTranslatableEntityListPaths__'\n\ntype ListPathGlobalHost = { [LIST_PATH_GLOBAL_KEY]?: TranslatableEntityListPathRegistry }\n\nfunction getListPathGlobal(): TranslatableEntityListPathRegistry {\n return (globalThis as ListPathGlobalHost)[LIST_PATH_GLOBAL_KEY] ?? {}\n}\n\nexport function registerTranslatableEntityListPath(entityType: string, listPath: string): void {\n const host = globalThis as ListPathGlobalHost\n host[LIST_PATH_GLOBAL_KEY] = { ...getListPathGlobal(), [entityType]: listPath }\n}\n\nexport function getTranslatableEntityListPath(entityType: string): string | undefined {\n return getListPathGlobal()[entityType]\n}\n"],
5
+ "mappings": "AAIA,MAAM,aAAa;AAEnB,SAAS,YAAwC;AAC/C,SAAQ,WAAmB,UAAU,KAAK,CAAC;AAC7C;AAEA,SAAS,UAAU,UAA4C;AAC7D,EAAC,WAAmB,UAAU,IAAI;AACpC;AAEO,SAAS,2BAA2B,QAA0C;AACnF,YAAU,EAAE,GAAG,UAAU,GAAG,GAAG,OAAO,CAAC;AACzC;AAEO,SAAS,sBAAsB,YAA0C;AAC9E,SAAO,UAAU,EAAE,UAAU;AAC/B;AAEO,SAAS,gCAA4D;AAC1E,SAAO,EAAE,GAAG,UAAU,EAAE;AAC1B;AAYA,MAAM,sBAAsB;AAI5B,SAAS,oBAAuD;AAC9D,SAAQ,WAAkC,mBAAmB,KAAK,CAAC;AACrE;AAEO,SAAS,kCAAkC,YAAoB,UAA2C;AAC/G,QAAM,OAAO;AACb,OAAK,mBAAmB,IAAI,EAAE,GAAG,kBAAkB,GAAG,CAAC,UAAU,GAAG,SAAS;AAC/E;AAEO,SAAS,6BAA6B,YAA2D;AACtG,SAAO,kBAAkB,EAAE,UAAU;AACvC;AAIA,MAAM,uBAAuB;AAI7B,SAAS,oBAAwD;AAC/D,SAAQ,WAAkC,oBAAoB,KAAK,CAAC;AACtE;AAEO,SAAS,mCAAmC,YAAoB,UAAwB;AAC7F,QAAM,OAAO;AACb,OAAK,oBAAoB,IAAI,EAAE,GAAG,kBAAkB,GAAG,CAAC,UAAU,GAAG,SAAS;AAChF;AAEO,SAAS,8BAA8B,YAAwC;AACpF,SAAO,kBAAkB,EAAE,UAAU;AACvC;",
6
6
  "names": []
7
7
  }
@@ -26,6 +26,13 @@ import { resolveListCountCap } from "./count-cap.js";
26
26
  import { mapWithConcurrency } from "./bounded-decrypt.js";
27
27
  import { parseNumberWithDefault } from "../number.js";
28
28
  import { createLogger } from "../logger/index.js";
29
+ import {
30
+ buildArrayColumnNoOverlapPredicate,
31
+ buildArrayColumnOverlapPredicate,
32
+ buildJsonbNoOverlapPredicate,
33
+ buildJsonbOverlapPredicate,
34
+ buildScalarOverlapPredicate
35
+ } from "./overlap.js";
29
36
  const logger = createLogger("shared").child({ component: "query" });
30
37
  const DECRYPT_CONCURRENCY = 8;
31
38
  const entityTableCache = /* @__PURE__ */ new Map();
@@ -841,7 +848,7 @@ class BasicQueryEngine {
841
848
  });
842
849
  continue;
843
850
  }
844
- q = this.applyColumnOp(q, expr, f.op, f.value);
851
+ q = f.op === "overlap" ? q.where(buildScalarOverlapPredicate(expr, f.value)) : f.op === "noverlap" ? q.where(sql`false`) : this.applyColumnOp(q, expr, f.op, f.value);
845
852
  }
846
853
  const cfLeafApplicable = (key) => isCountProjection ? keySource.has(key) : Boolean(cfValueExprByKey[key]);
847
854
  const applicableGroupFilters = resolvedGroupFilters.map((group) => group.filter((rf) => rf.kind !== "cf" || cfLeafApplicable(rf.key))).filter((group) => group.length > 0);
@@ -861,7 +868,8 @@ class BasicQueryEngine {
861
868
  value: rf.value
862
869
  });
863
870
  }
864
- return this.buildColumnOpExpression(eb, cfValueExprByKey[rf.key], rf.op, rf.value);
871
+ if (rf.op === "noverlap") return sql`false`;
872
+ return rf.op === "overlap" ? buildScalarOverlapPredicate(cfValueExprByKey[rf.key], rf.value) : this.buildColumnOpExpression(eb, cfValueExprByKey[rf.key], rf.op, rf.value);
865
873
  }
866
874
  return this.buildIndexDocOpExpression(eb, {
867
875
  entity: String(entity),
@@ -1101,6 +1109,10 @@ class BasicQueryEngine {
1101
1109
  return builder.where(column, "ilike", value);
1102
1110
  case "exists":
1103
1111
  return value ? builder.where(column, "is not", null) : builder.where(column, "is", null);
1112
+ case "overlap":
1113
+ return builder.where(buildArrayColumnOverlapPredicate(column, value));
1114
+ case "noverlap":
1115
+ return builder.where(buildArrayColumnNoOverlapPredicate(column, value));
1104
1116
  default:
1105
1117
  return builder;
1106
1118
  }
@@ -1194,6 +1206,11 @@ class BasicQueryEngine {
1194
1206
  case "exists":
1195
1207
  predicate = sql`${caseExpr} is not null`;
1196
1208
  break;
1209
+ case "overlap":
1210
+ predicate = buildScalarOverlapPredicate(caseExpr, value);
1211
+ break;
1212
+ case "noverlap":
1213
+ return sql`false`;
1197
1214
  default:
1198
1215
  return eb.val(true);
1199
1216
  }
@@ -1224,6 +1241,10 @@ class BasicQueryEngine {
1224
1241
  return eb(column, "ilike", value);
1225
1242
  case "exists":
1226
1243
  return value ? eb(column, "is not", null) : eb(column, "is", null);
1244
+ case "overlap":
1245
+ return buildArrayColumnOverlapPredicate(column, value);
1246
+ case "noverlap":
1247
+ return buildArrayColumnNoOverlapPredicate(column, value);
1227
1248
  default:
1228
1249
  return eb.val(true);
1229
1250
  }
@@ -1372,6 +1393,12 @@ class BasicQueryEngine {
1372
1393
  case "exists":
1373
1394
  sub = opts.value ? sub.where(sql`${textExpr} is not null`) : sub.where(sql`${textExpr} is null`);
1374
1395
  break;
1396
+ case "overlap":
1397
+ sub = sub.where(buildJsonbOverlapPredicate(`${alias}.doc`, opts.field, opts.value));
1398
+ break;
1399
+ case "noverlap":
1400
+ sub = sub.where(buildJsonbNoOverlapPredicate(`${alias}.doc`, opts.field, opts.value));
1401
+ break;
1375
1402
  default:
1376
1403
  break;
1377
1404
  }