@open-mercato/core 0.6.7-develop.6788.1.c2a1520a30 → 0.6.7-develop.6814.1.0627c7e9f1

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 (394) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/customer_deal_person_link/index.js +2 -0
  3. package/dist/generated/entities/customer_deal_person_link/index.js.map +2 -2
  4. package/dist/generated/entities/customer_user_invitation/index.js +2 -0
  5. package/dist/generated/entities/customer_user_invitation/index.js.map +2 -2
  6. package/dist/generated/entities.ids.generated.js +1 -0
  7. package/dist/generated/entities.ids.generated.js.map +2 -2
  8. package/dist/generated/entity-fields-registry.js +2 -0
  9. package/dist/generated/entity-fields-registry.js.map +2 -2
  10. package/dist/modules/business_rules/api/openmercato-call-options/route.js +85 -0
  11. package/dist/modules/business_rules/api/openmercato-call-options/route.js.map +7 -0
  12. package/dist/modules/business_rules/api/rules/route.js +71 -4
  13. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  14. package/dist/modules/business_rules/components/ActionBuilder.js +47 -4
  15. package/dist/modules/business_rules/components/ActionBuilder.js.map +3 -3
  16. package/dist/modules/business_rules/components/ActionRow.js +106 -16
  17. package/dist/modules/business_rules/components/ActionRow.js.map +2 -2
  18. package/dist/modules/business_rules/components/utils/actionValidation.js +6 -0
  19. package/dist/modules/business_rules/components/utils/actionValidation.js.map +2 -2
  20. package/dist/modules/business_rules/lib/action-executor.js +148 -0
  21. package/dist/modules/business_rules/lib/action-executor.js.map +2 -2
  22. package/dist/modules/business_rules/lib/openmercato-call-options-types.js +5 -0
  23. package/dist/modules/business_rules/lib/openmercato-call-options-types.js.map +7 -0
  24. package/dist/modules/business_rules/lib/openmercato-call-options.js +231 -0
  25. package/dist/modules/business_rules/lib/openmercato-call-options.js.map +7 -0
  26. package/dist/modules/business_rules/lib/rule-engine.js +5 -1
  27. package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
  28. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +11 -1
  29. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +2 -2
  30. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +11 -6
  31. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +2 -2
  32. package/dist/modules/configs/lib/upgrade-actions.js +12 -0
  33. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  34. package/dist/modules/customer_accounts/api/admin/users/[id].js +10 -1
  35. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  36. package/dist/modules/customer_accounts/api/admin/users-invite.js +61 -4
  37. package/dist/modules/customer_accounts/api/admin/users-invite.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/portal/users-invite.js +20 -2
  39. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  40. package/dist/modules/customer_accounts/data/entities.js +3 -0
  41. package/dist/modules/customer_accounts/data/entities.js.map +2 -2
  42. package/dist/modules/customer_accounts/data/validators.js +1 -0
  43. package/dist/modules/customer_accounts/data/validators.js.map +2 -2
  44. package/dist/modules/customer_accounts/emails/CustomerInvitationEmail.js +26 -0
  45. package/dist/modules/customer_accounts/emails/CustomerInvitationEmail.js.map +7 -0
  46. package/dist/modules/customer_accounts/lib/customerEntityOwnership.js +29 -3
  47. package/dist/modules/customer_accounts/lib/customerEntityOwnership.js.map +2 -2
  48. package/dist/modules/customer_accounts/lib/customerUrl.js +19 -10
  49. package/dist/modules/customer_accounts/lib/customerUrl.js.map +2 -2
  50. package/dist/modules/customer_accounts/lib/invitationEmail.js +35 -0
  51. package/dist/modules/customer_accounts/lib/invitationEmail.js.map +7 -0
  52. package/dist/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.js +13 -0
  53. package/dist/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.js.map +7 -0
  54. package/dist/modules/customer_accounts/services/customerInvitationService.js +32 -2
  55. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  56. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js +47 -7
  57. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js.map +2 -2
  58. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
  59. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  60. package/dist/modules/customer_accounts/widgets/injection-table.js +1 -1
  61. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  62. package/dist/modules/customers/api/deals/[id]/people/route.js +4 -2
  63. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  64. package/dist/modules/customers/api/deals/[id]/route.js +7 -3
  65. package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
  66. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +11 -1
  67. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  68. package/dist/modules/customers/backend/customers/deals/[id]/page.js +14 -8
  69. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  70. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +21 -6
  71. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  72. package/dist/modules/customers/cli.js +17 -7
  73. package/dist/modules/customers/cli.js.map +2 -2
  74. package/dist/modules/customers/commands/deals.js +48 -10
  75. package/dist/modules/customers/commands/deals.js.map +2 -2
  76. package/dist/modules/customers/commands/people.js +2 -0
  77. package/dist/modules/customers/commands/people.js.map +2 -2
  78. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +26 -6
  79. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  80. package/dist/modules/customers/components/detail/DealDetailTabs.js +30 -8
  81. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  82. package/dist/modules/customers/components/detail/PersonDetailTabs.js +30 -8
  83. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  84. package/dist/modules/customers/data/entities.js +8 -0
  85. package/dist/modules/customers/data/entities.js.map +2 -2
  86. package/dist/modules/customers/data/validators.js +2 -1
  87. package/dist/modules/customers/data/validators.js.map +2 -2
  88. package/dist/modules/customers/migrations/Migration20260723210000_customers.js +15 -0
  89. package/dist/modules/customers/migrations/Migration20260723210000_customers.js.map +7 -0
  90. package/dist/modules/customers/search.js +50 -29
  91. package/dist/modules/customers/search.js.map +2 -2
  92. package/dist/modules/data_sync/api/mappings/[id]/route.js +22 -9
  93. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
  94. package/dist/modules/data_sync/api/mappings/route.js +13 -4
  95. package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
  96. package/dist/modules/data_sync/api/options.js +7 -2
  97. package/dist/modules/data_sync/api/options.js.map +2 -2
  98. package/dist/modules/data_sync/api/run.js +7 -2
  99. package/dist/modules/data_sync/api/run.js.map +2 -2
  100. package/dist/modules/data_sync/api/runs/[id]/cancel.js +8 -3
  101. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  102. package/dist/modules/data_sync/api/runs/[id]/retry.js +7 -2
  103. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
  104. package/dist/modules/data_sync/api/runs/[id]/route.js +8 -3
  105. package/dist/modules/data_sync/api/runs/[id]/route.js.map +2 -2
  106. package/dist/modules/data_sync/api/runs.js +7 -2
  107. package/dist/modules/data_sync/api/runs.js.map +2 -2
  108. package/dist/modules/data_sync/api/schedules/[id]/route.js +19 -6
  109. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  110. package/dist/modules/data_sync/api/schedules/route.js +13 -4
  111. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  112. package/dist/modules/data_sync/api/validate.js +9 -4
  113. package/dist/modules/data_sync/api/validate.js.map +2 -2
  114. package/dist/modules/design_system/acl.js +9 -0
  115. package/dist/modules/design_system/acl.js.map +7 -0
  116. package/dist/modules/design_system/backend/design-system/page.js +11 -0
  117. package/dist/modules/design_system/backend/design-system/page.js.map +7 -0
  118. package/dist/modules/design_system/backend/design-system/page.meta.js +15 -0
  119. package/dist/modules/design_system/backend/design-system/page.meta.js.map +7 -0
  120. package/dist/modules/design_system/gallery/components/CodeSnippet.js +52 -0
  121. package/dist/modules/design_system/gallery/components/CodeSnippet.js.map +7 -0
  122. package/dist/modules/design_system/gallery/components/EntryCard.js +63 -0
  123. package/dist/modules/design_system/gallery/components/EntryCard.js.map +7 -0
  124. package/dist/modules/design_system/gallery/components/GalleryShell.js +163 -0
  125. package/dist/modules/design_system/gallery/components/GalleryShell.js.map +7 -0
  126. package/dist/modules/design_system/gallery/components/VariantPreview.js +9 -0
  127. package/dist/modules/design_system/gallery/components/VariantPreview.js.map +7 -0
  128. package/dist/modules/design_system/gallery/entries/banners.js +186 -0
  129. package/dist/modules/design_system/gallery/entries/banners.js.map +7 -0
  130. package/dist/modules/design_system/gallery/entries/buttons.js +361 -0
  131. package/dist/modules/design_system/gallery/entries/buttons.js.map +7 -0
  132. package/dist/modules/design_system/gallery/entries/charts.js +457 -0
  133. package/dist/modules/design_system/gallery/entries/charts.js.map +7 -0
  134. package/dist/modules/design_system/gallery/entries/dates.js +306 -0
  135. package/dist/modules/design_system/gallery/entries/dates.js.map +7 -0
  136. package/dist/modules/design_system/gallery/entries/detail.js +358 -0
  137. package/dist/modules/design_system/gallery/entries/detail.js.map +7 -0
  138. package/dist/modules/design_system/gallery/entries/display.js +785 -0
  139. package/dist/modules/design_system/gallery/entries/display.js.map +7 -0
  140. package/dist/modules/design_system/gallery/entries/feedback.js +658 -0
  141. package/dist/modules/design_system/gallery/entries/feedback.js.map +7 -0
  142. package/dist/modules/design_system/gallery/entries/filters.js +505 -0
  143. package/dist/modules/design_system/gallery/entries/filters.js.map +7 -0
  144. package/dist/modules/design_system/gallery/entries/foundations.js +453 -0
  145. package/dist/modules/design_system/gallery/entries/foundations.js.map +7 -0
  146. package/dist/modules/design_system/gallery/entries/icons.js +140 -0
  147. package/dist/modules/design_system/gallery/entries/icons.js.map +7 -0
  148. package/dist/modules/design_system/gallery/entries/inputs.js +1217 -0
  149. package/dist/modules/design_system/gallery/entries/inputs.js.map +7 -0
  150. package/dist/modules/design_system/gallery/entries/messages.js +334 -0
  151. package/dist/modules/design_system/gallery/entries/messages.js.map +7 -0
  152. package/dist/modules/design_system/gallery/entries/navigation.js +475 -0
  153. package/dist/modules/design_system/gallery/entries/navigation.js.map +7 -0
  154. package/dist/modules/design_system/gallery/entries/notifications.js +257 -0
  155. package/dist/modules/design_system/gallery/entries/notifications.js.map +7 -0
  156. package/dist/modules/design_system/gallery/entries/overlays.js +576 -0
  157. package/dist/modules/design_system/gallery/entries/overlays.js.map +7 -0
  158. package/dist/modules/design_system/gallery/entries/scaffolding.js +378 -0
  159. package/dist/modules/design_system/gallery/entries/scaffolding.js.map +7 -0
  160. package/dist/modules/design_system/gallery/entries/schedule.js +297 -0
  161. package/dist/modules/design_system/gallery/entries/schedule.js.map +7 -0
  162. package/dist/modules/design_system/gallery/registry.js +139 -0
  163. package/dist/modules/design_system/gallery/registry.js.map +7 -0
  164. package/dist/modules/design_system/gallery/types.js +1 -0
  165. package/dist/modules/design_system/gallery/types.js.map +7 -0
  166. package/dist/modules/design_system/index.js +9 -0
  167. package/dist/modules/design_system/index.js.map +7 -0
  168. package/dist/modules/design_system/setup.js +14 -0
  169. package/dist/modules/design_system/setup.js.map +7 -0
  170. package/dist/modules/entities/api/entity-settings.js +59 -9
  171. package/dist/modules/entities/api/entity-settings.js.map +2 -2
  172. package/dist/modules/entities/backend/config/settings/page.js +10 -0
  173. package/dist/modules/entities/backend/config/settings/page.js.map +7 -0
  174. package/dist/modules/entities/backend/config/settings/page.meta.js +36 -0
  175. package/dist/modules/entities/backend/config/settings/page.meta.js.map +7 -0
  176. package/dist/modules/entities/backend/entities/user/create/page.js +16 -7
  177. package/dist/modules/entities/backend/entities/user/create/page.js.map +2 -2
  178. package/dist/modules/entities/components/EntitySettingsManager.js +136 -0
  179. package/dist/modules/entities/components/EntitySettingsManager.js.map +7 -0
  180. package/dist/modules/inbox_ops/api/webhook/inbound.js +14 -10
  181. package/dist/modules/inbox_ops/api/webhook/inbound.js.map +2 -2
  182. package/dist/modules/inbox_ops/search.js +15 -4
  183. package/dist/modules/inbox_ops/search.js.map +2 -2
  184. package/dist/modules/integrations/api/[id]/credentials/route.js +11 -5
  185. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  186. package/dist/modules/integrations/api/[id]/health/route.js +6 -3
  187. package/dist/modules/integrations/api/[id]/health/route.js.map +2 -2
  188. package/dist/modules/integrations/api/[id]/route.js +6 -3
  189. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  190. package/dist/modules/integrations/api/[id]/state/route.js +6 -3
  191. package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
  192. package/dist/modules/integrations/api/[id]/version/route.js +6 -3
  193. package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
  194. package/dist/modules/integrations/api/logs/route.js +6 -3
  195. package/dist/modules/integrations/api/logs/route.js.map +2 -2
  196. package/dist/modules/integrations/api/route.js +6 -3
  197. package/dist/modules/integrations/api/route.js.map +2 -2
  198. package/dist/modules/integrations/api/umes-read.js +2 -2
  199. package/dist/modules/integrations/api/umes-read.js.map +2 -2
  200. package/dist/modules/integrations/lib/organization-scope.js +2 -9
  201. package/dist/modules/integrations/lib/organization-scope.js.map +2 -2
  202. package/dist/modules/messages/search.js +10 -5
  203. package/dist/modules/messages/search.js.map +2 -2
  204. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +11 -1
  205. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
  206. package/dist/modules/payment_gateways/data/entities.js +4 -0
  207. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  208. package/dist/modules/payment_gateways/lib/gateway-service.js +12 -2
  209. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  210. package/dist/modules/payment_gateways/lib/session-idempotency.js +33 -0
  211. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +2 -2
  212. package/dist/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.js +18 -0
  213. package/dist/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.js.map +7 -0
  214. package/dist/modules/payment_gateways/setup.js +47 -0
  215. package/dist/modules/payment_gateways/setup.js.map +2 -2
  216. package/dist/modules/payment_gateways/workers/session-initialization-prune.js +50 -0
  217. package/dist/modules/payment_gateways/workers/session-initialization-prune.js.map +7 -0
  218. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +134 -0
  219. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +7 -0
  220. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.js +11 -0
  221. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.js.map +7 -0
  222. package/dist/modules/portal/lib/navigation.js +7 -0
  223. package/dist/modules/portal/lib/navigation.js.map +7 -0
  224. package/dist/modules/query_index/data/entities.js +2 -1
  225. package/dist/modules/query_index/data/entities.js.map +2 -2
  226. package/dist/modules/query_index/lib/engine.js +75 -50
  227. package/dist/modules/query_index/lib/engine.js.map +2 -2
  228. package/dist/modules/query_index/lib/search-tokens.js +15 -5
  229. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  230. package/dist/modules/query_index/migrations/Migration20260731105052_query_index.js +17 -0
  231. package/dist/modules/query_index/migrations/Migration20260731105052_query_index.js.map +7 -0
  232. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +11 -1
  233. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +2 -2
  234. package/generated/entities/customer_deal_person_link/index.ts +1 -0
  235. package/generated/entities/customer_user_invitation/index.ts +1 -0
  236. package/generated/entities.ids.generated.ts +1 -0
  237. package/generated/entity-fields-registry.ts +2 -0
  238. package/package.json +16 -16
  239. package/src/modules/business_rules/api/openmercato-call-options/route.ts +94 -0
  240. package/src/modules/business_rules/api/rules/route.ts +92 -4
  241. package/src/modules/business_rules/components/ActionBuilder.tsx +54 -5
  242. package/src/modules/business_rules/components/ActionRow.tsx +156 -18
  243. package/src/modules/business_rules/components/utils/actionValidation.ts +6 -0
  244. package/src/modules/business_rules/i18n/de.json +12 -0
  245. package/src/modules/business_rules/i18n/en.json +12 -0
  246. package/src/modules/business_rules/i18n/es.json +12 -0
  247. package/src/modules/business_rules/i18n/pl.json +12 -0
  248. package/src/modules/business_rules/lib/action-executor.ts +198 -0
  249. package/src/modules/business_rules/lib/openmercato-call-options-types.ts +31 -0
  250. package/src/modules/business_rules/lib/openmercato-call-options.ts +297 -0
  251. package/src/modules/business_rules/lib/rule-engine.ts +4 -0
  252. package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +11 -1
  253. package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +17 -11
  254. package/src/modules/configs/lib/upgrade-actions.ts +14 -0
  255. package/src/modules/customer_accounts/api/admin/users/[id].ts +16 -1
  256. package/src/modules/customer_accounts/api/admin/users-invite.ts +78 -3
  257. package/src/modules/customer_accounts/api/portal/users-invite.ts +22 -1
  258. package/src/modules/customer_accounts/data/entities.ts +3 -0
  259. package/src/modules/customer_accounts/data/validators.ts +1 -0
  260. package/src/modules/customer_accounts/emails/CustomerInvitationEmail.tsx +45 -0
  261. package/src/modules/customer_accounts/i18n/de.json +6 -0
  262. package/src/modules/customer_accounts/i18n/en.json +6 -0
  263. package/src/modules/customer_accounts/i18n/es.json +6 -0
  264. package/src/modules/customer_accounts/i18n/pl.json +6 -0
  265. package/src/modules/customer_accounts/lib/customerEntityOwnership.ts +68 -2
  266. package/src/modules/customer_accounts/lib/customerUrl.ts +27 -11
  267. package/src/modules/customer_accounts/lib/invitationEmail.ts +42 -0
  268. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +16 -0
  269. package/src/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.ts +13 -0
  270. package/src/modules/customer_accounts/services/customerInvitationService.ts +56 -3
  271. package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +75 -8
  272. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
  273. package/src/modules/customer_accounts/widgets/injection-table.ts +4 -1
  274. package/src/modules/customers/api/deals/[id]/people/route.ts +3 -0
  275. package/src/modules/customers/api/deals/[id]/route.ts +10 -2
  276. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +12 -1
  277. package/src/modules/customers/backend/customers/deals/[id]/hooks/types.ts +1 -0
  278. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -9
  279. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +24 -6
  280. package/src/modules/customers/cli.ts +17 -7
  281. package/src/modules/customers/commands/deals.ts +50 -9
  282. package/src/modules/customers/commands/people.ts +3 -0
  283. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +30 -7
  284. package/src/modules/customers/components/detail/DealDetailTabs.tsx +34 -9
  285. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +34 -9
  286. package/src/modules/customers/data/entities.ts +9 -1
  287. package/src/modules/customers/data/validators.ts +1 -0
  288. package/src/modules/customers/i18n/de.json +18 -0
  289. package/src/modules/customers/i18n/en.json +18 -0
  290. package/src/modules/customers/i18n/es.json +18 -0
  291. package/src/modules/customers/i18n/pl.json +18 -0
  292. package/src/modules/customers/migrations/.snapshot-open-mercato.json +27 -1
  293. package/src/modules/customers/migrations/Migration20260723210000_customers.ts +15 -0
  294. package/src/modules/customers/search.ts +59 -27
  295. package/src/modules/data_sync/AGENTS.md +1 -0
  296. package/src/modules/data_sync/api/mappings/[id]/route.ts +22 -9
  297. package/src/modules/data_sync/api/mappings/route.ts +13 -4
  298. package/src/modules/data_sync/api/options.ts +7 -2
  299. package/src/modules/data_sync/api/run.ts +7 -2
  300. package/src/modules/data_sync/api/runs/[id]/cancel.ts +8 -3
  301. package/src/modules/data_sync/api/runs/[id]/retry.ts +7 -2
  302. package/src/modules/data_sync/api/runs/[id]/route.ts +8 -3
  303. package/src/modules/data_sync/api/runs.ts +7 -2
  304. package/src/modules/data_sync/api/schedules/[id]/route.ts +19 -6
  305. package/src/modules/data_sync/api/schedules/route.ts +13 -4
  306. package/src/modules/data_sync/api/validate.ts +9 -4
  307. package/src/modules/design_system/AGENTS.md +34 -0
  308. package/src/modules/design_system/acl.ts +5 -0
  309. package/src/modules/design_system/backend/design-system/page.meta.ts +11 -0
  310. package/src/modules/design_system/backend/design-system/page.tsx +14 -0
  311. package/src/modules/design_system/gallery/components/CodeSnippet.tsx +55 -0
  312. package/src/modules/design_system/gallery/components/EntryCard.tsx +95 -0
  313. package/src/modules/design_system/gallery/components/GalleryShell.tsx +206 -0
  314. package/src/modules/design_system/gallery/components/VariantPreview.tsx +15 -0
  315. package/src/modules/design_system/gallery/entries/banners.tsx +226 -0
  316. package/src/modules/design_system/gallery/entries/buttons.tsx +422 -0
  317. package/src/modules/design_system/gallery/entries/charts.tsx +518 -0
  318. package/src/modules/design_system/gallery/entries/dates.tsx +325 -0
  319. package/src/modules/design_system/gallery/entries/detail.tsx +403 -0
  320. package/src/modules/design_system/gallery/entries/display.tsx +893 -0
  321. package/src/modules/design_system/gallery/entries/feedback.tsx +746 -0
  322. package/src/modules/design_system/gallery/entries/filters.tsx +570 -0
  323. package/src/modules/design_system/gallery/entries/foundations.tsx +529 -0
  324. package/src/modules/design_system/gallery/entries/icons.tsx +168 -0
  325. package/src/modules/design_system/gallery/entries/inputs.tsx +1516 -0
  326. package/src/modules/design_system/gallery/entries/messages.tsx +339 -0
  327. package/src/modules/design_system/gallery/entries/navigation.tsx +573 -0
  328. package/src/modules/design_system/gallery/entries/notifications.tsx +285 -0
  329. package/src/modules/design_system/gallery/entries/overlays.tsx +677 -0
  330. package/src/modules/design_system/gallery/entries/scaffolding.tsx +430 -0
  331. package/src/modules/design_system/gallery/entries/schedule.tsx +307 -0
  332. package/src/modules/design_system/gallery/registry.ts +152 -0
  333. package/src/modules/design_system/gallery/types.ts +32 -0
  334. package/src/modules/design_system/i18n/.hardcoded-allowlist.json +71 -0
  335. package/src/modules/design_system/i18n/de.json +46 -0
  336. package/src/modules/design_system/i18n/en.json +46 -0
  337. package/src/modules/design_system/i18n/es.json +46 -0
  338. package/src/modules/design_system/i18n/pl.json +46 -0
  339. package/src/modules/design_system/index.ts +7 -0
  340. package/src/modules/design_system/setup.ts +12 -0
  341. package/src/modules/entities/api/entity-settings.ts +66 -22
  342. package/src/modules/entities/backend/config/settings/page.meta.ts +34 -0
  343. package/src/modules/entities/backend/config/settings/page.tsx +12 -0
  344. package/src/modules/entities/backend/entities/user/create/page.tsx +16 -7
  345. package/src/modules/entities/components/EntitySettingsManager.tsx +160 -0
  346. package/src/modules/entities/i18n/de.json +9 -0
  347. package/src/modules/entities/i18n/en.json +9 -0
  348. package/src/modules/entities/i18n/es.json +9 -0
  349. package/src/modules/entities/i18n/pl.json +9 -0
  350. package/src/modules/inbox_ops/api/webhook/inbound.ts +14 -12
  351. package/src/modules/inbox_ops/i18n/de.json +3 -0
  352. package/src/modules/inbox_ops/i18n/en.json +3 -0
  353. package/src/modules/inbox_ops/i18n/es.json +3 -0
  354. package/src/modules/inbox_ops/i18n/pl.json +3 -0
  355. package/src/modules/inbox_ops/search.ts +20 -4
  356. package/src/modules/integrations/api/[id]/credentials/route.ts +11 -5
  357. package/src/modules/integrations/api/[id]/health/route.ts +6 -3
  358. package/src/modules/integrations/api/[id]/route.ts +6 -3
  359. package/src/modules/integrations/api/[id]/state/route.ts +6 -3
  360. package/src/modules/integrations/api/[id]/version/route.ts +6 -3
  361. package/src/modules/integrations/api/logs/route.ts +6 -3
  362. package/src/modules/integrations/api/route.ts +6 -3
  363. package/src/modules/integrations/api/umes-read.ts +2 -2
  364. package/src/modules/integrations/lib/organization-scope.ts +5 -25
  365. package/src/modules/messages/i18n/de.json +2 -0
  366. package/src/modules/messages/i18n/en.json +2 -0
  367. package/src/modules/messages/i18n/es.json +2 -0
  368. package/src/modules/messages/i18n/pl.json +2 -0
  369. package/src/modules/messages/search.ts +11 -5
  370. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +13 -1
  371. package/src/modules/payment_gateways/data/entities.ts +5 -0
  372. package/src/modules/payment_gateways/i18n/de.json +5 -1
  373. package/src/modules/payment_gateways/i18n/en.json +5 -1
  374. package/src/modules/payment_gateways/i18n/es.json +5 -1
  375. package/src/modules/payment_gateways/i18n/pl.json +5 -1
  376. package/src/modules/payment_gateways/lib/gateway-service.ts +12 -2
  377. package/src/modules/payment_gateways/lib/session-idempotency.ts +36 -0
  378. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +10 -1
  379. package/src/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.ts +17 -0
  380. package/src/modules/payment_gateways/setup.ts +62 -0
  381. package/src/modules/payment_gateways/workers/session-initialization-prune.ts +63 -0
  382. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.ts +9 -0
  383. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +164 -0
  384. package/src/modules/portal/i18n/de.json +18 -0
  385. package/src/modules/portal/i18n/en.json +18 -0
  386. package/src/modules/portal/i18n/es.json +18 -0
  387. package/src/modules/portal/i18n/pl.json +18 -0
  388. package/src/modules/portal/lib/navigation.ts +3 -0
  389. package/src/modules/query_index/data/entities.ts +7 -0
  390. package/src/modules/query_index/lib/engine.ts +92 -63
  391. package/src/modules/query_index/lib/search-tokens.ts +16 -4
  392. package/src/modules/query_index/migrations/.snapshot-open-mercato.json +12 -0
  393. package/src/modules/query_index/migrations/Migration20260731105052_query_index.ts +35 -0
  394. package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +11 -1
@@ -20,16 +20,60 @@ import {
20
20
  type ResolvedJoin,
21
21
  } from '@open-mercato/shared/lib/query/join-utils'
22
22
  import { resolveSearchConfig, type SearchConfig } from '@open-mercato/shared/lib/search/config'
23
+ import {
24
+ createSearchTokenAvailability,
25
+ isSearchFilterOp,
26
+ hasSearchFilter,
27
+ type SearchTokenAvailability,
28
+ type SearchTokenProbeDb,
29
+ type SearchTokenProbeQueryBuilder,
30
+ } from '@open-mercato/shared/lib/search/availability'
23
31
  import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
24
32
  import { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from '@open-mercato/shared/lib/query/query-extension-runner'
25
33
  import { warnOnCiphertextLikeFallback } from '@open-mercato/shared/lib/query/ciphertext-search-warning'
26
34
  import { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from '@open-mercato/shared/lib/query/encrypted-sort'
27
35
  import { mapWithConcurrency } from '@open-mercato/shared/lib/query/bounded-decrypt'
28
36
  import { createLogger } from '@open-mercato/shared/lib/logger'
37
+ import { parseNumberWithDefault } from '@open-mercato/shared/lib/number'
29
38
 
30
39
  const logger = createLogger('query_index').child({ component: 'engine' })
31
40
 
32
41
  const DECRYPT_CONCURRENCY = 8
42
+ const AUTO_REINDEX_DEBOUNCE_DEFAULT_MS = 30_000
43
+ const AUTO_REINDEX_DEBOUNCE_MAX_SCOPES = 10_000
44
+ const AUTO_REINDEX_SCHEDULED_AT_KEY = Symbol.for('@open-mercato/query-index/auto-reindex-scheduled-at')
45
+
46
+ type GlobalWithAutoReindexSchedule = typeof globalThis & {
47
+ [AUTO_REINDEX_SCHEDULED_AT_KEY]?: Map<string, number>
48
+ }
49
+
50
+ function getAutoReindexScheduledAt(): Map<string, number> {
51
+ const globalScope = globalThis as GlobalWithAutoReindexSchedule
52
+ if (!globalScope[AUTO_REINDEX_SCHEDULED_AT_KEY]) {
53
+ globalScope[AUTO_REINDEX_SCHEDULED_AT_KEY] = new Map<string, number>()
54
+ }
55
+ return globalScope[AUTO_REINDEX_SCHEDULED_AT_KEY]
56
+ }
57
+
58
+ function markAutoReindexScheduled(key: string, debounceMs: number, now: number): boolean {
59
+ const autoReindexScheduledAt = getAutoReindexScheduledAt()
60
+ const previous = autoReindexScheduledAt.get(key)
61
+ if (previous !== undefined && now - previous < debounceMs) return false
62
+
63
+ if (autoReindexScheduledAt.size >= AUTO_REINDEX_DEBOUNCE_MAX_SCOPES) {
64
+ for (const [scopeKey, scheduledAt] of autoReindexScheduledAt) {
65
+ if (now - scheduledAt >= debounceMs) autoReindexScheduledAt.delete(scopeKey)
66
+ }
67
+ if (autoReindexScheduledAt.size >= AUTO_REINDEX_DEBOUNCE_MAX_SCOPES) {
68
+ const oldestKey = autoReindexScheduledAt.keys().next().value
69
+ if (oldestKey !== undefined) autoReindexScheduledAt.delete(oldestKey)
70
+ }
71
+ }
72
+
73
+ autoReindexScheduledAt.delete(key)
74
+ autoReindexScheduledAt.set(key, now)
75
+ return true
76
+ }
33
77
 
34
78
  function buildFilterableCustomFieldJoins(
35
79
  sources: QueryCustomFieldSource[] | undefined,
@@ -144,8 +188,10 @@ export class HybridQueryEngine implements QueryEngine {
144
188
  private sqlDebugEnabled: boolean | null = null
145
189
  private forcePartialIndexEnabled: boolean | null = null
146
190
  private autoReindexEnabled: boolean | null = null
191
+ private autoReindexDebounceMs: number | null = null
147
192
  private coverageOptimizationEnabled: boolean | null = null
148
193
  private pendingCoverageRefreshKeys = new Set<string>()
194
+ private searchAvailabilityInstance: SearchTokenAvailability | null = null
149
195
 
150
196
  constructor(
151
197
  private em: EntityManager,
@@ -174,6 +220,22 @@ export class HybridQueryEngine implements QueryEngine {
174
220
  throw new Error('HybridQueryEngine requires an EntityManager exposing getKysely() (MikroORM v7)')
175
221
  }
176
222
 
223
+ private searchAvailability(): SearchTokenAvailability {
224
+ if (!this.searchAvailabilityInstance) {
225
+ this.searchAvailabilityInstance = createSearchTokenAvailability({
226
+ getDb: () => this.getDb() as unknown as SearchTokenProbeDb,
227
+ getConfig: resolveSearchConfig,
228
+ applyOrganizationScope: (query, column, scope) => this.applyOrganizationScope(
229
+ query as unknown as AnyBuilder,
230
+ column,
231
+ scope,
232
+ ) as unknown as SearchTokenProbeQueryBuilder,
233
+ logDebug: (event, payload) => this.logSearchDebug(event, payload),
234
+ })
235
+ }
236
+ return this.searchAvailabilityInstance
237
+ }
238
+
177
239
  async query<T = unknown>(entity: EntityId, opts: QueryOptions = {}): Promise<QueryResult<T>> {
178
240
  const ext: QueryExtensionsConfig | undefined = opts.extensions
179
241
  let hybridExtCtx: QueryExtensionContext | null = null
@@ -251,7 +313,7 @@ export class HybridQueryEngine implements QueryEngine {
251
313
  profiler.mark('query:base_table_resolved')
252
314
  const searchConfig = resolveSearchConfig()
253
315
  const orgScope = this.resolveOrganizationScope(opts)
254
- const searchEnabled = searchConfig.enabled && await this.tableExists('search_tokens')
316
+ const searchEnabled = await this.searchAvailability().staticEnabled()
255
317
 
256
318
  const baseExists = await profiler.measure('base_table_exists', () => this.tableExists(baseTable))
257
319
  if (!baseExists) {
@@ -416,16 +478,19 @@ export class HybridQueryEngine implements QueryEngine {
416
478
  const searchSources: SearchTokenSource[] = indexSources
417
479
  .map((src) => ({ entity: String(src.entityId), recordIdColumn: src.recordIdColumn }))
418
480
  .filter((src) => src.recordIdColumn && src.entity)
419
- const searchFilters = normalizedFilters.filter((filter) => filter.op === 'like' || filter.op === 'ilike')
420
- // Probe `search_tokens` only when this query actually searches. Every consumer of
481
+ const searchFilters = normalizedFilters.filter((filter) => isSearchFilterOp(filter.op))
482
+ const sourceSearchFilters = [
483
+ ...baseFilters,
484
+ ...normalizedFilters.filter((filter) => String(filter.field).startsWith('cf:')),
485
+ ].filter((filter) => isSearchFilterOp(filter.op))
486
+ // Probe `search_tokens` only when this query actually searches (#4723). Every consumer of
421
487
  // `searchRuntime.enabled` already sits behind a like/ilike guard, so on a plain list load the
422
488
  // answer is unused — and the probe is a `LIMIT 1` the planner can resolve as a seq scan over a
423
489
  // large `search_tokens`. The join path below already probes lazily for the same reason.
424
- const hasSearchTokens = searchEnabled && searchSources.length && searchFilters.length
425
- ? await this.searchSourcesHaveTokens(searchSources, opts.tenantId ?? null, orgScope)
490
+ const hasSearchTokens = searchEnabled && searchSources.length && sourceSearchFilters.length
491
+ ? await this.searchAvailability().anySourceHasTokens(searchSources, opts.tenantId ?? null, orgScope)
426
492
  : false
427
493
  const searchRuntime: SearchRuntime = { ...searchRuntimeBase, searchSources, enabled: searchEnabled && hasSearchTokens }
428
- const joinSearchAvailability = new Map<string, boolean>()
429
494
  if (searchFilters.length) {
430
495
  this.logSearchDebug('search:init', {
431
496
  entity,
@@ -476,9 +541,8 @@ export class HybridQueryEngine implements QueryEngine {
476
541
  const join = joinMap.get(alias)
477
542
  if (!join?.entityId) continue
478
543
  const hasJoinedTokens = searchEnabled
479
- ? await this.hasSearchTokens(String(join.entityId), opts.tenantId ?? null, orgScope)
544
+ ? await this.searchAvailability().hasTokens(String(join.entityId), opts.tenantId ?? null, orgScope)
480
545
  : false
481
- joinSearchAvailability.set(String(join.entityId), hasJoinedTokens)
482
546
  const fallbackFields = filters
483
547
  .filter((filter) => !hasJoinedTokens || typeof filter.value !== 'string' || tokenizeText(filter.value, searchConfig).hashes.length === 0)
484
548
  .map((filter) => filter.column)
@@ -784,11 +848,7 @@ export class HybridQueryEngine implements QueryEngine {
784
848
  if (!['like', 'ilike'].includes(filter.op)) return false
785
849
  if (typeof filter.value !== 'string' || filter.value.trim().length === 0) return false
786
850
 
787
- let searchAvailable = joinSearchAvailability.get(join.entityId)
788
- if (searchAvailable === undefined) {
789
- searchAvailable = await this.hasSearchTokens(String(join.entityId), opts.tenantId ?? null, orgScope)
790
- joinSearchAvailability.set(join.entityId, searchAvailable)
791
- }
851
+ const searchAvailable = await this.searchAvailability().hasTokens(String(join.entityId), opts.tenantId ?? null, orgScope)
792
852
  if (!searchAvailable) return false
793
853
 
794
854
  const tokens = tokenizeText(String(filter.value), searchConfig)
@@ -1683,13 +1743,11 @@ export class HybridQueryEngine implements QueryEngine {
1683
1743
  if (!opts.tenantId) throw new Error('QueryEngine: tenantId is required')
1684
1744
 
1685
1745
  const normalizedFilters = normalizeFilters(opts.filters)
1686
- const hasSearchFilter = normalizedFilters.some((filter) => filter.op === 'like' || filter.op === 'ilike')
1687
-
1688
1746
  const searchConfig = resolveSearchConfig()
1689
- const searchEnabled = searchConfig.enabled && await this.tableExists('search_tokens')
1690
- // Same gate as `query()`: without a like/ilike filter the probe's answer is never read.
1691
- const hasSearchTokens = searchEnabled && hasSearchFilter
1692
- ? await this.hasSearchTokens(entity, opts.tenantId ?? null, orgScope)
1747
+ const searchEnabled = await this.searchAvailability().staticEnabled()
1748
+ // Same gate as `query()` (#4723): without a like/ilike filter the probe's answer is never read.
1749
+ const hasSearchTokens = searchEnabled && hasSearchFilter(normalizedFilters)
1750
+ ? await this.searchAvailability().hasTokens(entity, opts.tenantId ?? null, orgScope)
1693
1751
  : false
1694
1752
  const searchRuntime: SearchRuntime = {
1695
1753
  enabled: searchEnabled && hasSearchTokens,
@@ -1829,50 +1887,6 @@ export class HybridQueryEngine implements QueryEngine {
1829
1887
  return !!exists
1830
1888
  }
1831
1889
 
1832
- private async hasSearchTokens(
1833
- entity: string,
1834
- tenantId: string | null,
1835
- orgScope?: { ids: string[]; includeNull: boolean } | null
1836
- ): Promise<boolean> {
1837
- try {
1838
- const db = this.getDb() as any
1839
- let query = db
1840
- .selectFrom('search_tokens')
1841
- .select(sql<number>`1`.as('one'))
1842
- .where('entity_type', '=', entity)
1843
- if (tenantId !== undefined) {
1844
- query = query.where(sql<boolean>`tenant_id is not distinct from ${tenantId}`)
1845
- }
1846
- if (orgScope) {
1847
- query = this.applyOrganizationScope(query, 'search_tokens.organization_id', orgScope)
1848
- }
1849
- const row = await query.limit(1).executeTakeFirst()
1850
- return !!row
1851
- } catch (err) {
1852
- this.logSearchDebug('search:has-tokens-error', {
1853
- entity, tenantId, organizationScope: orgScope,
1854
- error: err instanceof Error ? err.message : String(err),
1855
- })
1856
- return false
1857
- }
1858
- }
1859
-
1860
- private async searchSourcesHaveTokens(
1861
- sources: SearchTokenSource[],
1862
- tenantId: string | null,
1863
- orgScope?: { ids: string[]; includeNull: boolean } | null
1864
- ): Promise<boolean> {
1865
- for (const source of sources) {
1866
- const ok = await this.hasSearchTokens(source.entity, tenantId, orgScope)
1867
- this.logSearchDebug('search:source-has-tokens', {
1868
- entity: source.entity, recordIdColumn: source.recordIdColumn,
1869
- tenantId, organizationScope: orgScope, hasTokens: ok,
1870
- })
1871
- if (ok) return true
1872
- }
1873
- return false
1874
- }
1875
-
1876
1890
  private async resolveAvailableCustomFieldKeys(entityIds: string[], tenantId: string | null): Promise<string[]> {
1877
1891
  if (!entityIds.length) return []
1878
1892
  const cacheKey = this.customFieldKeysCacheKey(entityIds, tenantId)
@@ -2021,6 +2035,11 @@ export class HybridQueryEngine implements QueryEngine {
2021
2035
  organizationId: organizationIdOverride ?? opts.organizationId ?? null,
2022
2036
  force: false,
2023
2037
  }
2038
+ const debounceMs = this.getAutoReindexDebounceMs()
2039
+ if (debounceMs > 0) {
2040
+ const key = [payload.entityType, payload.tenantId ?? '__tenant__', payload.organizationId ?? '__org__'].join('|')
2041
+ if (!markAutoReindexScheduled(key, debounceMs, Date.now())) return
2042
+ }
2024
2043
  const context = stats
2025
2044
  ? { entity, tenantId: payload.tenantId, organizationId: payload.organizationId, baseCount: stats.baseCount, indexedCount: stats.indexedCount }
2026
2045
  : { entity, tenantId: payload.tenantId, organizationId: payload.organizationId }
@@ -2090,6 +2109,16 @@ export class HybridQueryEngine implements QueryEngine {
2090
2109
  return this.autoReindexEnabled
2091
2110
  }
2092
2111
 
2112
+ private getAutoReindexDebounceMs(): number {
2113
+ if (this.autoReindexDebounceMs != null) return this.autoReindexDebounceMs
2114
+ this.autoReindexDebounceMs = parseNumberWithDefault(
2115
+ process.env.OM_QUERY_INDEX_AUTO_REINDEX_DEBOUNCE_MS,
2116
+ AUTO_REINDEX_DEBOUNCE_DEFAULT_MS,
2117
+ { integer: true, min: 0 },
2118
+ )
2119
+ return this.autoReindexDebounceMs
2120
+ }
2121
+
2093
2122
  private isCoverageOptimizationEnabled(): boolean {
2094
2123
  if (this.coverageOptimizationEnabled != null) return this.coverageOptimizationEnabled
2095
2124
  const raw = (process.env.OPTIMIZE_INDEX_COVERAGE_STATS ?? '').trim().toLowerCase()
@@ -2,6 +2,7 @@ import { type Kysely, type Transaction, sql } from 'kysely'
2
2
  import {
3
3
  isSearchFieldBlocklisted,
4
4
  resolveSearchConfig,
5
+ resolveSearchTokenLimits,
5
6
  type SearchConfig,
6
7
  } from '@open-mercato/shared/lib/search/config'
7
8
  import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
@@ -79,9 +80,7 @@ function shouldIndexField(
79
80
  if (lower.endsWith('_at')) return false
80
81
  if (['created_at', 'updated_at', 'deleted_at', 'tenant_id', 'organization_id'].includes(lower)) return false
81
82
  if (isSearchFieldBlocklisted(field, entityType, config)) return false
82
- const values = collectTextValues(value)
83
- if (!values.length) return false
84
- return values.some((text) => tokenizeText(text, config).tokens.length > 0)
83
+ return collectTextValues(value).some((text) => text.length > 0)
85
84
  }
86
85
 
87
86
  export function buildSearchTokenRows(params: BuildTokenOptions): SearchTokenRow[] {
@@ -95,19 +94,32 @@ export function buildSearchTokenRows(params: BuildTokenOptions): SearchTokenRow[
95
94
  organizationId: params.organizationId ?? DEFAULT_SCOPE.organizationId,
96
95
  tenantId: params.tenantId ?? DEFAULT_SCOPE.tenantId,
97
96
  }
97
+ const limits = resolveSearchTokenLimits(config)
98
+ const recordLimit = limits.maxTokensPerRecord > 0 ? limits.maxTokensPerRecord : Number.POSITIVE_INFINITY
99
+ const fieldLimit = limits.maxTokensPerField > 0 ? limits.maxTokensPerField : Number.POSITIVE_INFINITY
98
100
 
99
101
  for (const [field, rawValue] of Object.entries(params.doc)) {
102
+ if (tokens.length >= recordLimit) break
100
103
  if (!shouldIndexField(field, rawValue, config, params.entityType)) continue
101
104
  const values = collectTextValues(rawValue)
102
105
  const seen = new Set<string>()
106
+ let fieldTokenCount = 0
103
107
  for (const text of values) {
104
- const { tokens: textTokens, hashes } = tokenizeText(text, config)
108
+ if (tokens.length >= recordLimit || fieldTokenCount >= fieldLimit) break
109
+ const remainingLimit = Math.min(recordLimit - tokens.length, fieldLimit - fieldTokenCount)
110
+ const candidateLimit = fieldTokenCount + remainingLimit
111
+ const tokenConfig = Number.isFinite(candidateLimit)
112
+ ? { ...config, maxTokensPerField: candidateLimit }
113
+ : config
114
+ const { tokens: textTokens, hashes } = tokenizeText(text, tokenConfig)
105
115
  for (let i = 0; i < textTokens.length; i += 1) {
116
+ if (tokens.length >= recordLimit || fieldTokenCount >= fieldLimit) break
106
117
  const token = textTokens[i]
107
118
  const hash = hashes[i]
108
119
  const dedupeKey = `${field}|${hash}`
109
120
  if (seen.has(dedupeKey)) continue
110
121
  seen.add(dedupeKey)
122
+ fieldTokenCount += 1
111
123
  debug('token.generated', { entityType: params.entityType, recordId: params.recordId, field, hash })
112
124
  tokens.push({
113
125
  entity_type: params.entityType,
@@ -1365,6 +1365,18 @@
1365
1365
  "primary": false,
1366
1366
  "unique": false
1367
1367
  },
1368
+ {
1369
+ "columnNames": [
1370
+ "entity_type",
1371
+ "tenant_id",
1372
+ "organization_id"
1373
+ ],
1374
+ "composite": true,
1375
+ "constraint": false,
1376
+ "keyName": "search_tokens_presence_idx",
1377
+ "primary": false,
1378
+ "unique": false
1379
+ },
1368
1380
  {
1369
1381
  "columnNames": [
1370
1382
  "id"
@@ -0,0 +1,35 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ // Availability-probe index (.ai/specs/2026-07-31-search-token-probe-index.md).
4
+ // The query engines gate like/ilike routing on "does (entity_type, tenant, org)
5
+ // have any search tokens?". Neither existing index serves that probe as an
6
+ // access path — lookup_idx buries tenant_id behind field/token_hash — so on a
7
+ // large search_tokens the planner degraded the probe to a seq scan and the MISS
8
+ // answer had to exhaust every entry for the entity type (p95 12.5 s in
9
+ // production, #4723). With this (entity_type, tenant_id, organization_id)
10
+ // prefix and the index-usable `= / IS NULL` predicates the availability
11
+ // resolver emits, both the hit AND the miss become a B-tree prefix seek.
12
+ //
13
+ // search_tokens is high-churn, so the index is built CONCURRENTLY to avoid
14
+ // blocking writes during the build. CREATE INDEX CONCURRENTLY cannot run
15
+ // inside a transaction, hence isTransactional() => false; the migration runner
16
+ // applies migrations one-by-one, so this opt-out is safe (same pattern as
17
+ // Migration20260611103000_query_index). Drop first so retrying a failed
18
+ // concurrent build removes PostgreSQL's invalid index stub instead of letting
19
+ // IF NOT EXISTS silently accept it.
20
+ export class Migration20260731105052_query_index extends Migration {
21
+
22
+ override isTransactional(): boolean {
23
+ return false;
24
+ }
25
+
26
+ override up(): void | Promise<void> {
27
+ this.addSql(`drop index concurrently if exists "search_tokens_presence_idx";`);
28
+ this.addSql(`create index concurrently "search_tokens_presence_idx" on "search_tokens" ("entity_type", "tenant_id", "organization_id");`);
29
+ }
30
+
31
+ override down(): void | Promise<void> {
32
+ this.addSql(`drop index concurrently if exists "search_tokens_presence_idx";`);
33
+ }
34
+
35
+ }
@@ -11,6 +11,7 @@ import { getShippingAdapter } from '../../../lib/adapter-registry'
11
11
  import { getShippingCarrierQueue } from '../../../lib/queue'
12
12
  import { shippingCarriersTag } from '../../openapi'
13
13
  import { createLogger } from '@open-mercato/shared/lib/logger'
14
+ import { readBoundedRequestBody, WebhookBodyTooLargeError } from '@open-mercato/shared/lib/webhooks'
14
15
 
15
16
  const logger = createLogger('shipping_carriers').child({ component: 'webhook' })
16
17
 
@@ -51,7 +52,15 @@ export async function POST(req: Request, { params }: { params: Promise<{ provide
51
52
  const rateLimitResponse = await checkProviderWebhookRateLimit(container, req, providerKey)
52
53
  if (rateLimitResponse) return rateLimitResponse
53
54
 
54
- const rawBody = await req.text()
55
+ let rawBody: string
56
+ try {
57
+ rawBody = await readBoundedRequestBody(req)
58
+ } catch (error) {
59
+ if (error instanceof WebhookBodyTooLargeError) {
60
+ return NextResponse.json({ error: 'Webhook payload too large' }, { status: 413 })
61
+ }
62
+ throw error
63
+ }
55
64
  const headers: Record<string, string> = {}
56
65
  req.headers.forEach((value, key) => {
57
66
  headers[key] = value
@@ -156,6 +165,7 @@ export const openApi = {
156
165
  responses: [
157
166
  { status: 202, description: 'Webhook accepted for async processing' },
158
167
  { status: 401, description: 'Signature verification failed' },
168
+ { status: 413, description: 'Webhook payload too large' },
159
169
  { status: 404, description: 'Unknown provider' },
160
170
  ],
161
171
  },