@open-mercato/shared 0.6.8-develop.6984.1.f0cf23f0f6 → 0.6.8-develop.6986.1.3adb0d0df6

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
- [build:shared] found 259 entry points
1
+ [build:shared] found 260 entry points
2
2
  [build:shared] built successfully
package/AGENTS.md CHANGED
@@ -45,6 +45,7 @@ yarn workspace @open-mercato/shared build
45
45
  | `custom-fields/` | When handling custom field payloads | `@open-mercato/shared/lib/custom-fields` |
46
46
  | `data/` | When you need `DataEngine` or `QueryEngine` types | `@open-mercato/shared/lib/data/engine` |
47
47
  | `db/` | When resolving the ORM/connection-pool config (`resolvePoolConfig`, pool/timeout env knobs) | `@open-mercato/shared/lib/db/mikro` |
48
+ | `delivery/` | When scheduling delivery/retry attempts — exponential backoff with jitter for delivery pipelines (currently the push delivery worker) | `@open-mercato/shared/lib/delivery/retry` (`calculateBackoffDelayMs`) |
48
49
  | `di/` | When setting up dependency injection (Awilix). The app-level hook (`src/di.ts` → `register`) is wired explicitly in BOTH bootstrap paths — `src/bootstrap.ts` for the Next.js runtime, `bootstrapFromAppRoot()` for worker/scheduler/CLI processes. Never rely on the legacy `import('@/di')` fallback: the alias does not exist outside the app bundler | `@open-mercato/shared/lib/di` |
49
50
  | `encryption/` | When querying encrypted entities (MUST use instead of raw `em.find`) | `@open-mercato/shared/lib/encryption/find` |
50
51
  | `i18n/` | When translating strings — `useT()` client-side, `resolveTranslations()` server-side | `@open-mercato/shared/lib/i18n/context` or `/server` |
@@ -0,0 +1,11 @@
1
+ function calculateBackoffDelayMs(attemptNumber, options = {}) {
2
+ const baseDelayMs = options.baseDelayMs ?? 1e3;
3
+ const maxJitterMs = options.maxJitterMs ?? 1e3;
4
+ const factor = options.factor ?? 2;
5
+ const jitterMs = maxJitterMs > 0 ? Math.floor(Math.random() * maxJitterMs) : 0;
6
+ return baseDelayMs * Math.pow(factor, Math.max(attemptNumber - 1, 0)) + jitterMs;
7
+ }
8
+ export {
9
+ calculateBackoffDelayMs
10
+ };
11
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/delivery/retry.ts"],
4
+ "sourcesContent": ["export type BackoffOptions = {\n baseDelayMs?: number\n maxJitterMs?: number\n factor?: number\n}\n\n/**\n * Exponential backoff with jitter for delivery-retry scheduling.\n *\n * `delay = baseDelayMs * factor^(attemptNumber - 1) + random(0, maxJitterMs)`\n *\n * `attemptNumber` is 1-based (the number of the attempt that just failed). The jitter spreads\n * simultaneous retries so a provider outage does not make every failed delivery re-fire at the\n * same instant (thundering herd). Reusable across delivery pipelines that need identical backoff\n * semantics instead of hand-rolling their own (currently the push delivery worker).\n */\nexport function calculateBackoffDelayMs(attemptNumber: number, options: BackoffOptions = {}): number {\n const baseDelayMs = options.baseDelayMs ?? 1000\n const maxJitterMs = options.maxJitterMs ?? 1000\n const factor = options.factor ?? 2\n const jitterMs = maxJitterMs > 0 ? Math.floor(Math.random() * maxJitterMs) : 0\n return baseDelayMs * Math.pow(factor, Math.max(attemptNumber - 1, 0)) + jitterMs\n}\n"],
5
+ "mappings": "AAgBO,SAAS,wBAAwB,eAAuB,UAA0B,CAAC,GAAW;AACnG,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,WAAW,cAAc,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,WAAW,IAAI;AAC7E,SAAO,cAAc,KAAK,IAAI,QAAQ,KAAK,IAAI,gBAAgB,GAAG,CAAC,CAAC,IAAI;AAC1E;",
6
+ "names": []
7
+ }
@@ -106,6 +106,9 @@ function serializeAdvancedFilter(state) {
106
106
  });
107
107
  return params;
108
108
  }
109
+ function isUsableFilterField(field) {
110
+ return field.length > 0 && !field.startsWith("$");
111
+ }
109
112
  function deserializeAdvancedFilter(query) {
110
113
  const logic = normalizeJoinOperator(query["filter[logic]"]);
111
114
  const conditions = [];
@@ -113,7 +116,7 @@ function deserializeAdvancedFilter(query) {
113
116
  const field = query[`filter[conditions][${i}][field]`];
114
117
  const op = query[`filter[conditions][${i}][op]`];
115
118
  if (typeof field !== "string" || typeof op !== "string") break;
116
- if (!isValidOperator(op)) continue;
119
+ if (!isValidOperator(op) || !isUsableFilterField(field)) continue;
117
120
  const value = query[`filter[conditions][${i}][value]`];
118
121
  const join = i === 0 ? "and" : normalizeJoinOperator(query[`filter[conditions][${i}][join]`], logic);
119
122
  conditions.push({
@@ -304,6 +307,7 @@ function readTreeGroup(prefix, query) {
304
307
  const field = query[`${childPrefix}[field]`];
305
308
  const op = query[`${childPrefix}[op]`];
306
309
  if (typeof field !== "string" || typeof op !== "string" || !isValidOperator(op)) continue;
310
+ if (!isUsableFilterField(field)) continue;
307
311
  const rawVal = query[`${childPrefix}[value]`];
308
312
  children.push({
309
313
  id: crypto.randomUUID(),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/query/advanced-filter.ts"],
4
- "sourcesContent": ["import { buildIlikeTerm } from '../db/buildIlikeTerm'\n\nexport type FilterOperator =\n | 'is' | 'is_not' | 'contains' | 'does_not_contain' | 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty'\n | 'equals' | 'not_equals' | 'greater_than' | 'less_than' | 'greater_or_equal' | 'less_or_equal' | 'between'\n | 'is_before' | 'is_after'\n | 'is_any_of' | 'is_none_of'\n | 'is_true' | 'is_false'\n | 'has_any_of' | 'has_all_of' | 'has_none_of'\n\nexport type FilterFieldType = 'text' | 'number' | 'date' | 'select' | 'boolean' | 'tags'\n\nexport type FilterOptionTone = 'success' | 'error' | 'warning' | 'info' | 'neutral' | 'brand' | 'pink'\n\nexport type FilterOption = {\n value: string\n label: string\n tone?: FilterOptionTone\n}\n\nexport type FilterFieldDef = {\n key: string\n label: string\n type: FilterFieldType\n group?: string\n iconName?: string\n loadOptions?: (query?: string) => Promise<FilterOption[]>\n options?: FilterOption[]\n}\n\nexport type FilterJoinOperator = 'and' | 'or'\n\nexport type FilterCondition = {\n id: string\n field: string\n operator: FilterOperator\n value: unknown\n join?: FilterJoinOperator\n}\n\nexport type AdvancedFilterState = {\n logic: FilterJoinOperator\n conditions: FilterCondition[]\n}\n\nexport const OPERATORS_BY_FIELD_TYPE: Record<FilterFieldType, FilterOperator[]> = {\n text: ['is', 'is_not', 'contains', 'does_not_contain', 'starts_with', 'ends_with', 'is_empty', 'is_not_empty'],\n number: ['equals', 'not_equals', 'greater_than', 'less_than', 'greater_or_equal', 'less_or_equal', 'between', 'is_empty'],\n date: ['is', 'is_before', 'is_after', 'between', 'is_empty', 'is_not_empty'],\n select: ['is', 'is_not', 'is_any_of', 'is_none_of', 'is_empty'],\n boolean: ['is_true', 'is_false'],\n tags: ['has_any_of', 'has_all_of', 'has_none_of', 'is_empty'],\n}\n\nexport function getDefaultOperator(fieldType: FilterFieldType): FilterOperator {\n switch (fieldType) {\n case 'text': return 'contains'\n case 'number': return 'equals'\n case 'date': return 'is_after'\n case 'select': return 'is'\n case 'boolean': return 'is_true'\n case 'tags': return 'has_any_of'\n }\n}\n\nconst VALID_OPERATORS = new Set<string>([\n 'is', 'is_not', 'contains', 'does_not_contain', 'starts_with', 'ends_with', 'is_empty', 'is_not_empty',\n 'equals', 'not_equals', 'greater_than', 'less_than', 'greater_or_equal', 'less_or_equal', 'between',\n 'is_before', 'is_after',\n 'is_any_of', 'is_none_of',\n 'is_true', 'is_false',\n 'has_any_of', 'has_all_of', 'has_none_of',\n])\n\nexport function isValidOperator(op: string): op is FilterOperator {\n return VALID_OPERATORS.has(op)\n}\n\nexport function isValuelessOperator(operator: FilterOperator): boolean {\n return operator === 'is_empty' || operator === 'is_not_empty' || operator === 'is_true' || operator === 'is_false'\n}\n\nexport function createEmptyCondition(): FilterCondition {\n return {\n id: crypto.randomUUID(),\n field: '',\n operator: 'contains',\n value: '',\n join: 'and',\n }\n}\n\nfunction normalizeJoinOperator(value: unknown, fallback: FilterJoinOperator = 'and'): FilterJoinOperator {\n return value === 'or' ? 'or' : value === 'and' ? 'and' : fallback\n}\n\nfunction parseSerializedFilterValue(value: unknown): unknown {\n if (typeof value !== 'string') return value ?? null\n const trimmed = value.trim()\n if (!trimmed) return value\n if (!trimmed.startsWith('[') && !trimmed.startsWith('{')) return value\n try {\n return JSON.parse(trimmed)\n } catch {\n return value\n }\n}\n\nexport function normalizeAdvancedFilterState(state: AdvancedFilterState): AdvancedFilterState {\n const logic = normalizeJoinOperator(state.logic)\n return {\n logic,\n conditions: state.conditions.map((condition, index) => ({\n ...condition,\n join: index === 0 ? 'and' : normalizeJoinOperator(condition.join, logic),\n })),\n }\n}\n\nexport function serializeAdvancedFilter(state: AdvancedFilterState): Record<string, string> {\n const params: Record<string, string> = {}\n const normalized = normalizeAdvancedFilterState(state)\n if (!normalized.conditions.length) return params\n params['filter[logic]'] = normalized.logic\n normalized.conditions.forEach((condition, index) => {\n const prefix = `filter[conditions][${index}]`\n params[`${prefix}[field]`] = condition.field\n params[`${prefix}[op]`] = condition.operator\n if (index > 0) {\n params[`${prefix}[join]`] = condition.join ?? normalized.logic\n }\n if (!isValuelessOperator(condition.operator) && condition.value != null) {\n params[`${prefix}[value]`] = typeof condition.value === 'object'\n ? JSON.stringify(condition.value)\n : String(condition.value)\n }\n })\n return params\n}\n\nexport function deserializeAdvancedFilter(query: Record<string, unknown>): AdvancedFilterState | null {\n const logic = normalizeJoinOperator(query['filter[logic]'])\n const conditions: FilterCondition[] = []\n for (let i = 0; i < 20; i++) {\n const field = query[`filter[conditions][${i}][field]`]\n const op = query[`filter[conditions][${i}][op]`]\n if (typeof field !== 'string' || typeof op !== 'string') break\n if (!isValidOperator(op)) continue\n const value = query[`filter[conditions][${i}][value]`]\n const join = i === 0\n ? 'and'\n : normalizeJoinOperator(query[`filter[conditions][${i}][join]`], logic)\n conditions.push({\n id: String(i),\n field,\n operator: op,\n value: parseSerializedFilterValue(value),\n join,\n })\n }\n\n if (!conditions.length) return null\n return normalizeAdvancedFilterState({ logic, conditions })\n}\n\nfunction buildConditionFilter(condition: FilterCondition): Record<string, unknown> | null {\n if (!condition.field || !condition.operator) return null\n const normalizeSingleValue = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n }\n const normalizeListValue = (value: unknown): unknown[] => {\n const list = Array.isArray(value) ? value : [value]\n return list\n .map((entry) => normalizeSingleValue(entry))\n .filter((entry) => entry !== null)\n }\n const filter: Record<string, unknown> = {}\n switch (condition.operator) {\n case 'is':\n case 'equals':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $eq: normalizeSingleValue(condition.value) }\n break\n case 'is_not':\n case 'not_equals':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $ne: normalizeSingleValue(condition.value) }\n break\n case 'contains':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value))) }\n break\n case 'does_not_contain':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $not: { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value))) } }\n break\n case 'starts_with':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value)), 'startsWith') }\n break\n case 'ends_with':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value)), 'endsWith') }\n break\n case 'is_empty':\n filter[condition.field] = { $exists: false }\n break\n case 'is_not_empty':\n filter[condition.field] = { $exists: true }\n break\n case 'greater_than':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $gt: normalizeSingleValue(condition.value) }\n break\n case 'less_than':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $lt: normalizeSingleValue(condition.value) }\n break\n case 'greater_or_equal':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $gte: normalizeSingleValue(condition.value) }\n break\n case 'less_or_equal':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $lte: normalizeSingleValue(condition.value) }\n break\n case 'between':\n if (Array.isArray(condition.value) && condition.value.length === 2) {\n const start = normalizeSingleValue(condition.value[0])\n const end = normalizeSingleValue(condition.value[1])\n if (start === null && end === null) return null\n if (start !== null && end !== null) {\n filter[condition.field] = { $gte: start, $lte: end }\n } else if (start !== null) {\n filter[condition.field] = { $gte: start }\n } else if (end !== null) {\n filter[condition.field] = { $lte: end }\n }\n }\n break\n case 'is_before':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $lt: normalizeSingleValue(condition.value) }\n break\n case 'is_after':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $gt: normalizeSingleValue(condition.value) }\n break\n case 'is_any_of':\n case 'has_any_of':\n if (normalizeListValue(condition.value).length === 0) return null\n filter[condition.field] = { $in: normalizeListValue(condition.value) }\n break\n case 'is_none_of':\n case 'has_none_of':\n if (normalizeListValue(condition.value).length === 0) return null\n filter[condition.field] = { $nin: normalizeListValue(condition.value) }\n break\n case 'has_all_of': {\n const allOfValues = normalizeListValue(condition.value)\n if (allOfValues.length === 0) return null\n filter[condition.field] = { $contains: allOfValues }\n break\n }\n case 'is_true':\n filter[condition.field] = { $eq: true }\n break\n case 'is_false':\n filter[condition.field] = { $eq: false }\n break\n }\n return Object.keys(filter).length > 0 ? filter : null\n}\n\nexport function convertAdvancedFilterToWhere(state: AdvancedFilterState): Record<string, unknown> {\n const normalized = normalizeAdvancedFilterState(state)\n if (!normalized.conditions.length) return {}\n\n const conditionEntries = normalized.conditions\n .map((condition) => {\n const filter = buildConditionFilter(condition)\n return filter\n ? {\n join: normalizeJoinOperator(condition.join, normalized.logic),\n filter,\n }\n : null\n })\n .filter((entry): entry is { join: FilterJoinOperator; filter: Record<string, unknown> } => entry !== null)\n\n if (!conditionEntries.length) return {}\n\n let clauses: Record<string, unknown>[] = [conditionEntries[0].filter]\n for (const entry of conditionEntries.slice(1)) {\n if (entry.join === 'or') {\n clauses = [...clauses, entry.filter]\n continue\n }\n clauses = clauses.map((clause) => ({\n ...clause,\n ...entry.filter,\n }))\n }\n\n return clauses.length > 1 ? { $or: clauses } : clauses[0]\n}\n\n// -----------------------------------------------------------------------------\n// v2 tree serialization (advanced-filter-tree)\n// -----------------------------------------------------------------------------\n\nimport type {\n AdvancedFilterTree,\n FilterRule as TreeFilterRule,\n FilterGroup as TreeFilterGroup,\n FilterCombinator as TreeFilterCombinator,\n} from './advanced-filter-tree'\n\nexport function serializeTree(tree: AdvancedFilterTree): Record<string, string> {\n if (!treeHasRules(tree.root)) return {}\n const out: Record<string, string> = { 'filter[v]': '2' }\n serializeTreeGroup(tree.root, 'filter[root]', out)\n return out\n}\n\nfunction treeHasRules(group: TreeFilterGroup): boolean {\n return group.children.some((child) => child.type === 'rule' || treeHasRules(child))\n}\n\nfunction serializeTreeGroup(group: TreeFilterGroup, prefix: string, out: Record<string, string>): void {\n out[`${prefix}[combinator]`] = group.combinator\n group.children.forEach((child, idx) => {\n const childPrefix = `${prefix}[children][${idx}]`\n if (child.type === 'rule') serializeTreeRule(child, childPrefix, out)\n else { out[`${childPrefix}[type]`] = 'group'; serializeTreeGroup(child, childPrefix, out) }\n })\n}\n\nfunction serializeTreeRule(rule: TreeFilterRule, prefix: string, out: Record<string, string>): void {\n out[`${prefix}[type]`] = 'rule'\n out[`${prefix}[field]`] = rule.field\n out[`${prefix}[op]`] = rule.operator\n if (!isValuelessOperator(rule.operator) && rule.value != null) {\n out[`${prefix}[value]`] = typeof rule.value === 'object'\n ? JSON.stringify(rule.value)\n : String(rule.value)\n }\n}\n\nexport function deserializeTree(query: Record<string, unknown>): AdvancedFilterTree | null {\n if (query['filter[v]'] !== '2') return null\n const root = readTreeGroup('filter[root]', query)\n if (!root) return null\n return { root }\n}\n\nfunction readTreeGroup(prefix: string, query: Record<string, unknown>): TreeFilterGroup | null {\n const combRaw = query[`${prefix}[combinator]`]\n if (combRaw !== 'and' && combRaw !== 'or') return null\n const children: Array<TreeFilterRule | TreeFilterGroup> = []\n for (let i = 0; i < 64; i++) {\n const childPrefix = `${prefix}[children][${i}]`\n const type = query[`${childPrefix}[type]`]\n if (type === 'rule') {\n const field = query[`${childPrefix}[field]`]\n const op = query[`${childPrefix}[op]`]\n if (typeof field !== 'string' || typeof op !== 'string' || !isValidOperator(op)) continue\n const rawVal = query[`${childPrefix}[value]`]\n children.push({\n id: crypto.randomUUID(),\n type: 'rule',\n field,\n operator: op as FilterOperator,\n value: parseSerializedFilterValue(rawVal),\n })\n } else if (type === 'group') {\n const sub = readTreeGroup(childPrefix, query)\n if (sub) children.push(sub)\n } else {\n break\n }\n }\n return {\n id: crypto.randomUUID(),\n type: 'group',\n combinator: combRaw as TreeFilterCombinator,\n children,\n }\n}\n\n/**\n * Runtime discriminator: is this value an `AdvancedFilterState` (legacy flat\n * shape with `logic` + `conditions`) rather than an `AdvancedFilterTree`\n * (`{root: FilterGroup}`)?\n *\n * Used at the DataTable boundary to bridge legacy callers onto the new tree\n * model without forcing third-party module developers to migrate immediately.\n * See `BACKWARD_COMPATIBILITY.md` \u00A73 and the spec's \"Migration & Backward\n * Compatibility\" section.\n */\nexport function isAdvancedFilterState(value: unknown): value is AdvancedFilterState {\n if (!value || typeof value !== 'object') return false\n const record = value as Record<string, unknown>\n return Array.isArray(record.conditions) && (record.logic === 'and' || record.logic === 'or')\n}\n\n/**\n * Convert legacy flat AdvancedFilterState into a tree under standard SQL precedence\n * (AND binds tighter than OR). Runs of consecutive AND-joined conditions become\n * AND-subgroups, and the OR connectors join those subgroups in a root OR-group.\n */\nexport function flatToTree(flat: AdvancedFilterState): AdvancedFilterTree {\n if (flat.conditions.length === 0) {\n return { root: { id: crypto.randomUUID(), type: 'group', combinator: 'and', children: [] } }\n }\n\n // Step A: split into AND-runs separated by OR connectors. The first row's `join`\n // is logically \"and\" (no left neighbor); we never use it as a separator.\n const andRuns: FilterCondition[][] = [[flat.conditions[0]]]\n for (let i = 1; i < flat.conditions.length; i++) {\n const c = flat.conditions[i]\n if (c.join === 'or') andRuns.push([c])\n else andRuns[andRuns.length - 1].push(c)\n }\n\n const ruleFromCondition = (c: FilterCondition): TreeFilterRule => ({\n id: crypto.randomUUID(),\n type: 'rule',\n field: c.field,\n operator: c.operator,\n value: c.value,\n })\n\n // Step B: each AND-run becomes either a rule (length 1) or an AND-group.\n const orChildren: Array<TreeFilterRule | TreeFilterGroup> = andRuns.map((run) => {\n if (run.length === 1) return ruleFromCondition(run[0])\n return {\n id: crypto.randomUUID(),\n type: 'group',\n combinator: 'and',\n children: run.map(ruleFromCondition),\n }\n })\n\n // Step C: zero or one OR-disjunct -> root combinator stays \"and\"; otherwise OR.\n if (orChildren.length === 1) {\n const only = orChildren[0]\n if (only.type === 'group') return { root: only }\n return { root: { id: crypto.randomUUID(), type: 'group', combinator: 'and', children: [only] } }\n }\n return {\n root: { id: crypto.randomUUID(), type: 'group', combinator: 'or', children: orChildren },\n }\n}\n\n/**\n * Map a dictionary entry's display color (named like 'red'/'green' or a hex like '#ef4444')\n * to a `FilterOptionTone`, so dictionary-backed select options can render with the\n * correct status dot in chips and value pills. Unknown / undefined inputs return\n * `undefined` so the consumer falls back to plain (no tone) rendering.\n */\nexport function mapDictionaryColorToTone(color: string | null | undefined): FilterOptionTone | undefined {\n if (!color || typeof color !== 'string') return undefined\n const trimmed = color.trim().toLowerCase()\n if (!trimmed) return undefined\n const named: Record<string, FilterOptionTone> = {\n red: 'error',\n crimson: 'error',\n pink: 'pink',\n rose: 'pink',\n fuchsia: 'pink',\n magenta: 'pink',\n green: 'success',\n emerald: 'success',\n lime: 'success',\n teal: 'success',\n amber: 'warning',\n yellow: 'warning',\n orange: 'warning',\n blue: 'info',\n cyan: 'info',\n sky: 'info',\n indigo: 'info',\n gray: 'neutral',\n grey: 'neutral',\n slate: 'neutral',\n zinc: 'neutral',\n stone: 'neutral',\n violet: 'brand',\n purple: 'brand',\n }\n if (named[trimmed]) return named[trimmed]\n const hexMatch = /^#?([0-9a-f]{6})$/i.exec(trimmed)\n if (!hexMatch) return undefined\n const hex = hexMatch[1]\n const r = parseInt(hex.slice(0, 2), 16)\n const g = parseInt(hex.slice(2, 4), 16)\n const b = parseInt(hex.slice(4, 6), 16)\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const delta = max - min\n if (delta < 24) return 'neutral'\n let hue = 0\n if (max === r) hue = ((g - b) / delta) % 6\n else if (max === g) hue = (b - r) / delta + 2\n else hue = (r - g) / delta + 4\n hue = (hue * 60 + 360) % 360\n // Hue ranges (degrees): 0-20 red(error), 20-50 orange(warning), 50-80 yellow(warning),\n // 80-170 green(success), 170-260 blue/indigo(info), 260-320 violet/purple(brand),\n // 320-340 pink/magenta(pink), 340-360 red(error).\n if (hue < 20) return 'error'\n if (hue < 50) return 'warning'\n if (hue < 80) return 'warning'\n if (hue < 170) return 'success'\n if (hue < 260) return 'info'\n if (hue < 320) return 'brand'\n if (hue < 340) return 'pink'\n return 'error'\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { buildIlikeTerm } from '../db/buildIlikeTerm'\n\nexport type FilterOperator =\n | 'is' | 'is_not' | 'contains' | 'does_not_contain' | 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty'\n | 'equals' | 'not_equals' | 'greater_than' | 'less_than' | 'greater_or_equal' | 'less_or_equal' | 'between'\n | 'is_before' | 'is_after'\n | 'is_any_of' | 'is_none_of'\n | 'is_true' | 'is_false'\n | 'has_any_of' | 'has_all_of' | 'has_none_of'\n\nexport type FilterFieldType = 'text' | 'number' | 'date' | 'select' | 'boolean' | 'tags'\n\nexport type FilterOptionTone = 'success' | 'error' | 'warning' | 'info' | 'neutral' | 'brand' | 'pink'\n\nexport type FilterOption = {\n value: string\n label: string\n tone?: FilterOptionTone\n}\n\nexport type FilterFieldDef = {\n key: string\n label: string\n type: FilterFieldType\n group?: string\n iconName?: string\n loadOptions?: (query?: string) => Promise<FilterOption[]>\n options?: FilterOption[]\n}\n\nexport type FilterJoinOperator = 'and' | 'or'\n\nexport type FilterCondition = {\n id: string\n field: string\n operator: FilterOperator\n value: unknown\n join?: FilterJoinOperator\n}\n\nexport type AdvancedFilterState = {\n logic: FilterJoinOperator\n conditions: FilterCondition[]\n}\n\nexport const OPERATORS_BY_FIELD_TYPE: Record<FilterFieldType, FilterOperator[]> = {\n text: ['is', 'is_not', 'contains', 'does_not_contain', 'starts_with', 'ends_with', 'is_empty', 'is_not_empty'],\n number: ['equals', 'not_equals', 'greater_than', 'less_than', 'greater_or_equal', 'less_or_equal', 'between', 'is_empty'],\n date: ['is', 'is_before', 'is_after', 'between', 'is_empty', 'is_not_empty'],\n select: ['is', 'is_not', 'is_any_of', 'is_none_of', 'is_empty'],\n boolean: ['is_true', 'is_false'],\n tags: ['has_any_of', 'has_all_of', 'has_none_of', 'is_empty'],\n}\n\nexport function getDefaultOperator(fieldType: FilterFieldType): FilterOperator {\n switch (fieldType) {\n case 'text': return 'contains'\n case 'number': return 'equals'\n case 'date': return 'is_after'\n case 'select': return 'is'\n case 'boolean': return 'is_true'\n case 'tags': return 'has_any_of'\n }\n}\n\nconst VALID_OPERATORS = new Set<string>([\n 'is', 'is_not', 'contains', 'does_not_contain', 'starts_with', 'ends_with', 'is_empty', 'is_not_empty',\n 'equals', 'not_equals', 'greater_than', 'less_than', 'greater_or_equal', 'less_or_equal', 'between',\n 'is_before', 'is_after',\n 'is_any_of', 'is_none_of',\n 'is_true', 'is_false',\n 'has_any_of', 'has_all_of', 'has_none_of',\n])\n\nexport function isValidOperator(op: string): op is FilterOperator {\n return VALID_OPERATORS.has(op)\n}\n\nexport function isValuelessOperator(operator: FilterOperator): boolean {\n return operator === 'is_empty' || operator === 'is_not_empty' || operator === 'is_true' || operator === 'is_false'\n}\n\nexport function createEmptyCondition(): FilterCondition {\n return {\n id: crypto.randomUUID(),\n field: '',\n operator: 'contains',\n value: '',\n join: 'and',\n }\n}\n\nfunction normalizeJoinOperator(value: unknown, fallback: FilterJoinOperator = 'and'): FilterJoinOperator {\n return value === 'or' ? 'or' : value === 'and' ? 'and' : fallback\n}\n\nfunction parseSerializedFilterValue(value: unknown): unknown {\n if (typeof value !== 'string') return value ?? null\n const trimmed = value.trim()\n if (!trimmed) return value\n if (!trimmed.startsWith('[') && !trimmed.startsWith('{')) return value\n try {\n return JSON.parse(trimmed)\n } catch {\n return value\n }\n}\n\nexport function normalizeAdvancedFilterState(state: AdvancedFilterState): AdvancedFilterState {\n const logic = normalizeJoinOperator(state.logic)\n return {\n logic,\n conditions: state.conditions.map((condition, index) => ({\n ...condition,\n join: index === 0 ? 'and' : normalizeJoinOperator(condition.join, logic),\n })),\n }\n}\n\nexport function serializeAdvancedFilter(state: AdvancedFilterState): Record<string, string> {\n const params: Record<string, string> = {}\n const normalized = normalizeAdvancedFilterState(state)\n if (!normalized.conditions.length) return params\n params['filter[logic]'] = normalized.logic\n normalized.conditions.forEach((condition, index) => {\n const prefix = `filter[conditions][${index}]`\n params[`${prefix}[field]`] = condition.field\n params[`${prefix}[op]`] = condition.operator\n if (index > 0) {\n params[`${prefix}[join]`] = condition.join ?? normalized.logic\n }\n if (!isValuelessOperator(condition.operator) && condition.value != null) {\n params[`${prefix}[value]`] = typeof condition.value === 'object'\n ? JSON.stringify(condition.value)\n : String(condition.value)\n }\n })\n return params\n}\n\n/**\n * A condition field must name a column, never a Where combinator. `$and`/`$or`/`$not` share the\n * filter namespace with column names, so a client-supplied `filter[...][field]=$and` would compile\n * to a combinator key: at best it reaches the engine as a filter on a column that does not exist,\n * at worst it collides with a key the route itself emitted. Unlike the operator, the field name is\n * route-specific, so this is the one check that can be made centrally \u2014 dropped silently, the same\n * way an unrecognized operator is.\n */\nfunction isUsableFilterField(field: string): boolean {\n return field.length > 0 && !field.startsWith('$')\n}\n\nexport function deserializeAdvancedFilter(query: Record<string, unknown>): AdvancedFilterState | null {\n const logic = normalizeJoinOperator(query['filter[logic]'])\n const conditions: FilterCondition[] = []\n for (let i = 0; i < 20; i++) {\n const field = query[`filter[conditions][${i}][field]`]\n const op = query[`filter[conditions][${i}][op]`]\n if (typeof field !== 'string' || typeof op !== 'string') break\n if (!isValidOperator(op) || !isUsableFilterField(field)) continue\n const value = query[`filter[conditions][${i}][value]`]\n const join = i === 0\n ? 'and'\n : normalizeJoinOperator(query[`filter[conditions][${i}][join]`], logic)\n conditions.push({\n id: String(i),\n field,\n operator: op,\n value: parseSerializedFilterValue(value),\n join,\n })\n }\n\n if (!conditions.length) return null\n return normalizeAdvancedFilterState({ logic, conditions })\n}\n\nfunction buildConditionFilter(condition: FilterCondition): Record<string, unknown> | null {\n if (!condition.field || !condition.operator) return null\n const normalizeSingleValue = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n }\n const normalizeListValue = (value: unknown): unknown[] => {\n const list = Array.isArray(value) ? value : [value]\n return list\n .map((entry) => normalizeSingleValue(entry))\n .filter((entry) => entry !== null)\n }\n const filter: Record<string, unknown> = {}\n switch (condition.operator) {\n case 'is':\n case 'equals':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $eq: normalizeSingleValue(condition.value) }\n break\n case 'is_not':\n case 'not_equals':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $ne: normalizeSingleValue(condition.value) }\n break\n case 'contains':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value))) }\n break\n case 'does_not_contain':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $not: { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value))) } }\n break\n case 'starts_with':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value)), 'startsWith') }\n break\n case 'ends_with':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value)), 'endsWith') }\n break\n case 'is_empty':\n filter[condition.field] = { $exists: false }\n break\n case 'is_not_empty':\n filter[condition.field] = { $exists: true }\n break\n case 'greater_than':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $gt: normalizeSingleValue(condition.value) }\n break\n case 'less_than':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $lt: normalizeSingleValue(condition.value) }\n break\n case 'greater_or_equal':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $gte: normalizeSingleValue(condition.value) }\n break\n case 'less_or_equal':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $lte: normalizeSingleValue(condition.value) }\n break\n case 'between':\n if (Array.isArray(condition.value) && condition.value.length === 2) {\n const start = normalizeSingleValue(condition.value[0])\n const end = normalizeSingleValue(condition.value[1])\n if (start === null && end === null) return null\n if (start !== null && end !== null) {\n filter[condition.field] = { $gte: start, $lte: end }\n } else if (start !== null) {\n filter[condition.field] = { $gte: start }\n } else if (end !== null) {\n filter[condition.field] = { $lte: end }\n }\n }\n break\n case 'is_before':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $lt: normalizeSingleValue(condition.value) }\n break\n case 'is_after':\n if (normalizeSingleValue(condition.value) === null) return null\n filter[condition.field] = { $gt: normalizeSingleValue(condition.value) }\n break\n case 'is_any_of':\n case 'has_any_of':\n if (normalizeListValue(condition.value).length === 0) return null\n filter[condition.field] = { $in: normalizeListValue(condition.value) }\n break\n case 'is_none_of':\n case 'has_none_of':\n if (normalizeListValue(condition.value).length === 0) return null\n filter[condition.field] = { $nin: normalizeListValue(condition.value) }\n break\n case 'has_all_of': {\n const allOfValues = normalizeListValue(condition.value)\n if (allOfValues.length === 0) return null\n filter[condition.field] = { $contains: allOfValues }\n break\n }\n case 'is_true':\n filter[condition.field] = { $eq: true }\n break\n case 'is_false':\n filter[condition.field] = { $eq: false }\n break\n }\n return Object.keys(filter).length > 0 ? filter : null\n}\n\nexport function convertAdvancedFilterToWhere(state: AdvancedFilterState): Record<string, unknown> {\n const normalized = normalizeAdvancedFilterState(state)\n if (!normalized.conditions.length) return {}\n\n const conditionEntries = normalized.conditions\n .map((condition) => {\n const filter = buildConditionFilter(condition)\n return filter\n ? {\n join: normalizeJoinOperator(condition.join, normalized.logic),\n filter,\n }\n : null\n })\n .filter((entry): entry is { join: FilterJoinOperator; filter: Record<string, unknown> } => entry !== null)\n\n if (!conditionEntries.length) return {}\n\n let clauses: Record<string, unknown>[] = [conditionEntries[0].filter]\n for (const entry of conditionEntries.slice(1)) {\n if (entry.join === 'or') {\n clauses = [...clauses, entry.filter]\n continue\n }\n clauses = clauses.map((clause) => ({\n ...clause,\n ...entry.filter,\n }))\n }\n\n return clauses.length > 1 ? { $or: clauses } : clauses[0]\n}\n\n// -----------------------------------------------------------------------------\n// v2 tree serialization (advanced-filter-tree)\n// -----------------------------------------------------------------------------\n\nimport type {\n AdvancedFilterTree,\n FilterRule as TreeFilterRule,\n FilterGroup as TreeFilterGroup,\n FilterCombinator as TreeFilterCombinator,\n} from './advanced-filter-tree'\n\nexport function serializeTree(tree: AdvancedFilterTree): Record<string, string> {\n if (!treeHasRules(tree.root)) return {}\n const out: Record<string, string> = { 'filter[v]': '2' }\n serializeTreeGroup(tree.root, 'filter[root]', out)\n return out\n}\n\nfunction treeHasRules(group: TreeFilterGroup): boolean {\n return group.children.some((child) => child.type === 'rule' || treeHasRules(child))\n}\n\nfunction serializeTreeGroup(group: TreeFilterGroup, prefix: string, out: Record<string, string>): void {\n out[`${prefix}[combinator]`] = group.combinator\n group.children.forEach((child, idx) => {\n const childPrefix = `${prefix}[children][${idx}]`\n if (child.type === 'rule') serializeTreeRule(child, childPrefix, out)\n else { out[`${childPrefix}[type]`] = 'group'; serializeTreeGroup(child, childPrefix, out) }\n })\n}\n\nfunction serializeTreeRule(rule: TreeFilterRule, prefix: string, out: Record<string, string>): void {\n out[`${prefix}[type]`] = 'rule'\n out[`${prefix}[field]`] = rule.field\n out[`${prefix}[op]`] = rule.operator\n if (!isValuelessOperator(rule.operator) && rule.value != null) {\n out[`${prefix}[value]`] = typeof rule.value === 'object'\n ? JSON.stringify(rule.value)\n : String(rule.value)\n }\n}\n\nexport function deserializeTree(query: Record<string, unknown>): AdvancedFilterTree | null {\n if (query['filter[v]'] !== '2') return null\n const root = readTreeGroup('filter[root]', query)\n if (!root) return null\n return { root }\n}\n\nfunction readTreeGroup(prefix: string, query: Record<string, unknown>): TreeFilterGroup | null {\n const combRaw = query[`${prefix}[combinator]`]\n if (combRaw !== 'and' && combRaw !== 'or') return null\n const children: Array<TreeFilterRule | TreeFilterGroup> = []\n for (let i = 0; i < 64; i++) {\n const childPrefix = `${prefix}[children][${i}]`\n const type = query[`${childPrefix}[type]`]\n if (type === 'rule') {\n const field = query[`${childPrefix}[field]`]\n const op = query[`${childPrefix}[op]`]\n if (typeof field !== 'string' || typeof op !== 'string' || !isValidOperator(op)) continue\n if (!isUsableFilterField(field)) continue\n const rawVal = query[`${childPrefix}[value]`]\n children.push({\n id: crypto.randomUUID(),\n type: 'rule',\n field,\n operator: op as FilterOperator,\n value: parseSerializedFilterValue(rawVal),\n })\n } else if (type === 'group') {\n const sub = readTreeGroup(childPrefix, query)\n if (sub) children.push(sub)\n } else {\n break\n }\n }\n return {\n id: crypto.randomUUID(),\n type: 'group',\n combinator: combRaw as TreeFilterCombinator,\n children,\n }\n}\n\n/**\n * Runtime discriminator: is this value an `AdvancedFilterState` (legacy flat\n * shape with `logic` + `conditions`) rather than an `AdvancedFilterTree`\n * (`{root: FilterGroup}`)?\n *\n * Used at the DataTable boundary to bridge legacy callers onto the new tree\n * model without forcing third-party module developers to migrate immediately.\n * See `BACKWARD_COMPATIBILITY.md` \u00A73 and the spec's \"Migration & Backward\n * Compatibility\" section.\n */\nexport function isAdvancedFilterState(value: unknown): value is AdvancedFilterState {\n if (!value || typeof value !== 'object') return false\n const record = value as Record<string, unknown>\n return Array.isArray(record.conditions) && (record.logic === 'and' || record.logic === 'or')\n}\n\n/**\n * Convert legacy flat AdvancedFilterState into a tree under standard SQL precedence\n * (AND binds tighter than OR). Runs of consecutive AND-joined conditions become\n * AND-subgroups, and the OR connectors join those subgroups in a root OR-group.\n */\nexport function flatToTree(flat: AdvancedFilterState): AdvancedFilterTree {\n if (flat.conditions.length === 0) {\n return { root: { id: crypto.randomUUID(), type: 'group', combinator: 'and', children: [] } }\n }\n\n // Step A: split into AND-runs separated by OR connectors. The first row's `join`\n // is logically \"and\" (no left neighbor); we never use it as a separator.\n const andRuns: FilterCondition[][] = [[flat.conditions[0]]]\n for (let i = 1; i < flat.conditions.length; i++) {\n const c = flat.conditions[i]\n if (c.join === 'or') andRuns.push([c])\n else andRuns[andRuns.length - 1].push(c)\n }\n\n const ruleFromCondition = (c: FilterCondition): TreeFilterRule => ({\n id: crypto.randomUUID(),\n type: 'rule',\n field: c.field,\n operator: c.operator,\n value: c.value,\n })\n\n // Step B: each AND-run becomes either a rule (length 1) or an AND-group.\n const orChildren: Array<TreeFilterRule | TreeFilterGroup> = andRuns.map((run) => {\n if (run.length === 1) return ruleFromCondition(run[0])\n return {\n id: crypto.randomUUID(),\n type: 'group',\n combinator: 'and',\n children: run.map(ruleFromCondition),\n }\n })\n\n // Step C: zero or one OR-disjunct -> root combinator stays \"and\"; otherwise OR.\n if (orChildren.length === 1) {\n const only = orChildren[0]\n if (only.type === 'group') return { root: only }\n return { root: { id: crypto.randomUUID(), type: 'group', combinator: 'and', children: [only] } }\n }\n return {\n root: { id: crypto.randomUUID(), type: 'group', combinator: 'or', children: orChildren },\n }\n}\n\n/**\n * Map a dictionary entry's display color (named like 'red'/'green' or a hex like '#ef4444')\n * to a `FilterOptionTone`, so dictionary-backed select options can render with the\n * correct status dot in chips and value pills. Unknown / undefined inputs return\n * `undefined` so the consumer falls back to plain (no tone) rendering.\n */\nexport function mapDictionaryColorToTone(color: string | null | undefined): FilterOptionTone | undefined {\n if (!color || typeof color !== 'string') return undefined\n const trimmed = color.trim().toLowerCase()\n if (!trimmed) return undefined\n const named: Record<string, FilterOptionTone> = {\n red: 'error',\n crimson: 'error',\n pink: 'pink',\n rose: 'pink',\n fuchsia: 'pink',\n magenta: 'pink',\n green: 'success',\n emerald: 'success',\n lime: 'success',\n teal: 'success',\n amber: 'warning',\n yellow: 'warning',\n orange: 'warning',\n blue: 'info',\n cyan: 'info',\n sky: 'info',\n indigo: 'info',\n gray: 'neutral',\n grey: 'neutral',\n slate: 'neutral',\n zinc: 'neutral',\n stone: 'neutral',\n violet: 'brand',\n purple: 'brand',\n }\n if (named[trimmed]) return named[trimmed]\n const hexMatch = /^#?([0-9a-f]{6})$/i.exec(trimmed)\n if (!hexMatch) return undefined\n const hex = hexMatch[1]\n const r = parseInt(hex.slice(0, 2), 16)\n const g = parseInt(hex.slice(2, 4), 16)\n const b = parseInt(hex.slice(4, 6), 16)\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const delta = max - min\n if (delta < 24) return 'neutral'\n let hue = 0\n if (max === r) hue = ((g - b) / delta) % 6\n else if (max === g) hue = (b - r) / delta + 2\n else hue = (r - g) / delta + 4\n hue = (hue * 60 + 360) % 360\n // Hue ranges (degrees): 0-20 red(error), 20-50 orange(warning), 50-80 yellow(warning),\n // 80-170 green(success), 170-260 blue/indigo(info), 260-320 violet/purple(brand),\n // 320-340 pink/magenta(pink), 340-360 red(error).\n if (hue < 20) return 'error'\n if (hue < 50) return 'warning'\n if (hue < 80) return 'warning'\n if (hue < 170) return 'success'\n if (hue < 260) return 'info'\n if (hue < 320) return 'brand'\n if (hue < 340) return 'pink'\n return 'error'\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.6.8-develop.6984.1.f0cf23f0f6";
1
+ const APP_VERSION = "0.6.8-develop.6986.1.3adb0d0df6";
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.6.8-develop.6984.1.f0cf23f0f6';\nexport const appVersion = APP_VERSION;\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.8-develop.6986.1.3adb0d0df6';\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.6.8-develop.6984.1.f0cf23f0f6",
3
+ "version": "0.6.8-develop.6986.1.3adb0d0df6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -105,7 +105,7 @@
105
105
  "@mikro-orm/core": "^7.1.8",
106
106
  "@mikro-orm/decorators": "^7.1.8",
107
107
  "@mikro-orm/postgresql": "^7.1.8",
108
- "@open-mercato/cache": "0.6.8-develop.6984.1.f0cf23f0f6",
108
+ "@open-mercato/cache": "0.6.8-develop.6986.1.3adb0d0df6",
109
109
  "@types/sanitize-html": "^2.16.1",
110
110
  "dotenv": "^17.4.2",
111
111
  "pino": "^10.3.1",
@@ -0,0 +1,54 @@
1
+ import { calculateBackoffDelayMs } from '../retry'
2
+
3
+ describe('calculateBackoffDelayMs', () => {
4
+ const originalRandom = Math.random
5
+
6
+ afterEach(() => {
7
+ Math.random = originalRandom
8
+ })
9
+
10
+ it('grows exponentially by the default factor of 2 from a 1000ms base (jitter pinned to 0)', () => {
11
+ Math.random = () => 0
12
+ expect(calculateBackoffDelayMs(1)).toBe(1000)
13
+ expect(calculateBackoffDelayMs(2)).toBe(2000)
14
+ expect(calculateBackoffDelayMs(3)).toBe(4000)
15
+ expect(calculateBackoffDelayMs(4)).toBe(8000)
16
+ })
17
+
18
+ it('adds jitter drawn from [0, maxJitterMs) on top of the exponential term', () => {
19
+ // Math.floor(0.5 * 1000) = 500 added to the 2000ms exponential term for attempt 2.
20
+ Math.random = () => 0.5
21
+ expect(calculateBackoffDelayMs(2)).toBe(2500)
22
+ })
23
+
24
+ it('clamps non-positive attempt numbers so the exponent never goes negative', () => {
25
+ Math.random = () => 0
26
+ // attemptNumber 1, 0 and -5 all collapse to factor^0 = the base delay.
27
+ expect(calculateBackoffDelayMs(0)).toBe(1000)
28
+ expect(calculateBackoffDelayMs(-5)).toBe(1000)
29
+ })
30
+
31
+ it('is fully deterministic when maxJitterMs is 0 (no Math.random call)', () => {
32
+ const randomSpy = jest.fn(() => 0.999)
33
+ Math.random = randomSpy
34
+ expect(calculateBackoffDelayMs(3, { maxJitterMs: 0 })).toBe(4000)
35
+ expect(randomSpy).not.toHaveBeenCalled()
36
+ })
37
+
38
+ it('honours custom base delay and factor', () => {
39
+ Math.random = () => 0
40
+ expect(calculateBackoffDelayMs(1, { baseDelayMs: 250, factor: 3 })).toBe(250)
41
+ expect(calculateBackoffDelayMs(2, { baseDelayMs: 250, factor: 3 })).toBe(750)
42
+ expect(calculateBackoffDelayMs(3, { baseDelayMs: 250, factor: 3 })).toBe(2250)
43
+ })
44
+
45
+ it('keeps the jitter bound below maxJitterMs across the random range', () => {
46
+ for (const sample of [0, 0.25, 0.5, 0.9999999]) {
47
+ Math.random = () => sample
48
+ const delay = calculateBackoffDelayMs(1, { maxJitterMs: 1000 })
49
+ // Base (1000) + jitter in [0, 1000): never reaches base + maxJitterMs.
50
+ expect(delay).toBeGreaterThanOrEqual(1000)
51
+ expect(delay).toBeLessThan(2000)
52
+ }
53
+ })
54
+ })
@@ -0,0 +1,23 @@
1
+ export type BackoffOptions = {
2
+ baseDelayMs?: number
3
+ maxJitterMs?: number
4
+ factor?: number
5
+ }
6
+
7
+ /**
8
+ * Exponential backoff with jitter for delivery-retry scheduling.
9
+ *
10
+ * `delay = baseDelayMs * factor^(attemptNumber - 1) + random(0, maxJitterMs)`
11
+ *
12
+ * `attemptNumber` is 1-based (the number of the attempt that just failed). The jitter spreads
13
+ * simultaneous retries so a provider outage does not make every failed delivery re-fire at the
14
+ * same instant (thundering herd). Reusable across delivery pipelines that need identical backoff
15
+ * semantics instead of hand-rolling their own (currently the push delivery worker).
16
+ */
17
+ export function calculateBackoffDelayMs(attemptNumber: number, options: BackoffOptions = {}): number {
18
+ const baseDelayMs = options.baseDelayMs ?? 1000
19
+ const maxJitterMs = options.maxJitterMs ?? 1000
20
+ const factor = options.factor ?? 2
21
+ const jitterMs = maxJitterMs > 0 ? Math.floor(Math.random() * maxJitterMs) : 0
22
+ return baseDelayMs * Math.pow(factor, Math.max(attemptNumber - 1, 0)) + jitterMs
23
+ }
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  convertAdvancedFilterToWhere,
5
5
  deserializeAdvancedFilter,
6
+ deserializeTree,
6
7
  serializeAdvancedFilter,
7
8
  type AdvancedFilterState,
8
9
  } from '../advanced-filter'
@@ -58,6 +59,41 @@ describe('advanced filter', () => {
58
59
  })
59
60
  })
60
61
 
62
+ it('drops conditions whose field names a Where combinator instead of a column', () => {
63
+ // `$and`/`$or`/`$not` live in the same key namespace as column names, so a condition naming one
64
+ // would compile to a combinator key and collide with whatever the route put there — including a
65
+ // scope predicate. Dropped like an unrecognized operator, leaving the remaining conditions.
66
+ expect(
67
+ deserializeAdvancedFilter({
68
+ 'filter[conditions][0][field]': '$and',
69
+ 'filter[conditions][0][op]': 'is_empty',
70
+ 'filter[conditions][1][field]': 'platform',
71
+ 'filter[conditions][1][op]': 'is',
72
+ 'filter[conditions][1][value]': 'ios',
73
+ }),
74
+ ).toEqual({
75
+ logic: 'and',
76
+ conditions: [{ id: '1', field: 'platform', operator: 'is', value: 'ios', join: 'and' }],
77
+ })
78
+
79
+ expect(
80
+ deserializeAdvancedFilter({
81
+ 'filter[conditions][0][field]': '$or',
82
+ 'filter[conditions][0][op]': 'is_empty',
83
+ }),
84
+ ).toBeNull()
85
+
86
+ expect(
87
+ deserializeTree({
88
+ 'filter[v]': '2',
89
+ 'filter[root][combinator]': 'and',
90
+ 'filter[root][children][0][type]': 'rule',
91
+ 'filter[root][children][0][field]': '$and',
92
+ 'filter[root][children][0][op]': 'is_empty',
93
+ })?.root.children,
94
+ ).toEqual([])
95
+ })
96
+
61
97
  it('ignores empty values for operators that require a concrete value', () => {
62
98
  expect(
63
99
  convertAdvancedFilterToWhere({
@@ -138,6 +138,18 @@ export function serializeAdvancedFilter(state: AdvancedFilterState): Record<stri
138
138
  return params
139
139
  }
140
140
 
141
+ /**
142
+ * A condition field must name a column, never a Where combinator. `$and`/`$or`/`$not` share the
143
+ * filter namespace with column names, so a client-supplied `filter[...][field]=$and` would compile
144
+ * to a combinator key: at best it reaches the engine as a filter on a column that does not exist,
145
+ * at worst it collides with a key the route itself emitted. Unlike the operator, the field name is
146
+ * route-specific, so this is the one check that can be made centrally — dropped silently, the same
147
+ * way an unrecognized operator is.
148
+ */
149
+ function isUsableFilterField(field: string): boolean {
150
+ return field.length > 0 && !field.startsWith('$')
151
+ }
152
+
141
153
  export function deserializeAdvancedFilter(query: Record<string, unknown>): AdvancedFilterState | null {
142
154
  const logic = normalizeJoinOperator(query['filter[logic]'])
143
155
  const conditions: FilterCondition[] = []
@@ -145,7 +157,7 @@ export function deserializeAdvancedFilter(query: Record<string, unknown>): Advan
145
157
  const field = query[`filter[conditions][${i}][field]`]
146
158
  const op = query[`filter[conditions][${i}][op]`]
147
159
  if (typeof field !== 'string' || typeof op !== 'string') break
148
- if (!isValidOperator(op)) continue
160
+ if (!isValidOperator(op) || !isUsableFilterField(field)) continue
149
161
  const value = query[`filter[conditions][${i}][value]`]
150
162
  const join = i === 0
151
163
  ? 'and'
@@ -367,6 +379,7 @@ function readTreeGroup(prefix: string, query: Record<string, unknown>): TreeFilt
367
379
  const field = query[`${childPrefix}[field]`]
368
380
  const op = query[`${childPrefix}[op]`]
369
381
  if (typeof field !== 'string' || typeof op !== 'string' || !isValidOperator(op)) continue
382
+ if (!isUsableFilterField(field)) continue
370
383
  const rawVal = query[`${childPrefix}[value]`]
371
384
  children.push({
372
385
  id: crypto.randomUUID(),
@@ -67,6 +67,84 @@ export type NotificationTypeDefinition = {
67
67
  linkHref?: string
68
68
  Renderer?: ComponentType<NotificationRendererProps>
69
69
  expiresAfterHours?: number
70
+ /**
71
+ * Optional i18n key for the short type name shown in a per-channel
72
+ * preferences UI (e.g. "Order created"). Distinct from `titleKey`, which is
73
+ * the per-instance message title. Falls back to `titleKey` when omitted.
74
+ */
75
+ labelKey?: string
76
+ /** Optional i18n key for helper text shown beside the type in a preferences UI. */
77
+ descriptionKey?: string
78
+ /**
79
+ * Optional free-form grouping label (e.g. `security`, `orders`, `marketing`) so a
80
+ * client — typically a mobile app — can list/group notification types under a heading.
81
+ * Plain string, not an enum; mirrored to the `notification_types` table and returned by
82
+ * `GET /api/notifications/types`.
83
+ *
84
+ * Defaults to the prefix before the first dot in `type` (`sales.order.created` →
85
+ * `sales`), resolved once during `syncNotificationTypes`. Declare it only to override
86
+ * that default. Localize the heading by adding `notifications.categories.<key>` to YOUR
87
+ * OWN module's `i18n/*.json` — the `notifications` module deliberately ships no category
88
+ * labels and knows nothing about which categories exist. `GET /api/notifications/types`
89
+ * resolves the key into `categoryLabel`, falling back to the raw key when absent.
90
+ *
91
+ * Clients group on `category` (stable across locales) and display `categoryLabel`.
92
+ */
93
+ category?: string
94
+ /**
95
+ * When `true`, this type's push is delivered as a silent / content-available (data-only)
96
+ * wake-up instead of a visible alert. `silent` selects the delivery STYLE only — the
97
+ * notification still flows through the normal `notificationService.create()` path (in-app
98
+ * row created, per-channel preferences respected unless the type is `nonOptOut`).
99
+ */
100
+ silent?: boolean
101
+ /**
102
+ * When `true`, the recipient cannot opt out of this type: delivery ignores any
103
+ * stored per-channel preference (security/account alerts that must always be
104
+ * delivered). A preferences UI should render it as locked/forced-on, and
105
+ * `setPreferences` refuses to store an opt-out row for it.
106
+ */
107
+ nonOptOut?: boolean
108
+ /**
109
+ * When `true`, this type is hidden from the client-facing catalogue: it is NOT mirrored to the
110
+ * `notification_types` table and therefore not returned by `GET /api/notifications/types`, so a
111
+ * mobile app's preferences screen never lists it. Used for internal/admin-only types (e.g. one-off
112
+ * admin custom pushes) that are dispatched by the server, not toggled by users. The type still
113
+ * lives in the in-memory registry for delivery logic (`getNotificationType`).
114
+ */
115
+ hiddenFromSettings?: boolean
116
+ /**
117
+ * Optional per-type channel eligibility: the delivery channels this type may EVER use
118
+ * (e.g. a marketing type restricted to `['push']` so it never lands in the in-app bell).
119
+ * The dispatcher intersects it with the per-send target, the registered strategies, and the
120
+ * recipient's preferences via `shouldDeliver`. Omit to make the type eligible for every
121
+ * registered channel (pre-Phase-7 behavior).
122
+ *
123
+ * Operators can override this set per tenant via the `notification_type_overrides` table
124
+ * (`PATCH /api/notifications/types` / the Notification Delivery settings page): a stored
125
+ * array replaces the code-declared one, an absent row (or `NULL`) inherits it. A channel
126
+ * outside the effective set never delivers for the type in that tenant — the check runs
127
+ * before both the `nonOptOut` bypass and user preferences, and the preference UIs render
128
+ * that cell locked off.
129
+ */
130
+ channels?: string[]
131
+ }
132
+
133
+ /**
134
+ * A delivery channel in the module-registered channel catalogue. Any module can contribute channels
135
+ * by exporting `notificationChannels: NotificationChannelDefinition[]` from a `notification-channels.ts`
136
+ * file (generator-discovered), so a preferences UI and the `/api/notifications/channels` endpoint stay
137
+ * in sync with the actual delivery paths without a hardcoded list. `id` matches the delivery-strategy id
138
+ * (`in_app`, `email`, `push`, …) — treat it as a FROZEN contract once shipped.
139
+ */
140
+ export type NotificationChannelDefinition = {
141
+ id: string
142
+ /** i18n key for the channel's display name in a preferences UI. */
143
+ labelKey: string
144
+ /** Optional i18n key for helper text shown beside the channel. */
145
+ descriptionKey?: string
146
+ /** Ascending display order; entries without an order sort after ordered ones, then by id. */
147
+ order?: number
70
148
  }
71
149
 
72
150
  export type NotificationDto = {
@@ -93,6 +171,10 @@ export type NotificationDto = {
93
171
  sourceEntityType?: string | null
94
172
  sourceEntityId?: string | null
95
173
  linkHref?: string | null
174
+ /** Arbitrary app-readable key/values attached at create time (also delivered with the push). */
175
+ data?: Record<string, string> | null
176
+ /** Resolved delivery channels for this notification. `null` = all channels (legacy/untargeted). */
177
+ channels?: string[] | null
96
178
  createdAt: string
97
179
  readAt?: string | null
98
180
  actionTaken?: string | null