@open-mercato/core 0.6.6-develop.6317.1.b3be10ab84 → 0.6.6-develop.6331.1.a33b8e99b7

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 (304) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +3 -0
  3. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  4. package/dist/modules/auth/api/roles/acl/route.js +2 -2
  5. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  6. package/dist/modules/auth/api/users/acl/route.js +2 -2
  7. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  8. package/dist/modules/auth/backend/roles/[id]/edit/page.js +12 -0
  9. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  10. package/dist/modules/auth/backend/users/[id]/edit/page.js +12 -0
  11. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  12. package/dist/modules/auth/data/entities.js +2 -2
  13. package/dist/modules/auth/data/entities.js.map +2 -2
  14. package/dist/modules/business_rules/api/rules/route.js +3 -3
  15. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  16. package/dist/modules/business_rules/api/sets/route.js +3 -3
  17. package/dist/modules/business_rules/api/sets/route.js.map +2 -2
  18. package/dist/modules/business_rules/backend/rules/[id]/page.js +12 -1
  19. package/dist/modules/business_rules/backend/rules/[id]/page.js.map +2 -2
  20. package/dist/modules/business_rules/backend/sets/[id]/page.js +12 -1
  21. package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
  22. package/dist/modules/catalog/api/product-media/route.js.map +2 -2
  23. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +12 -0
  24. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  25. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +15 -0
  26. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  27. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +12 -1
  28. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  29. package/dist/modules/catalog/components/PriceKindSettings.js +13 -3
  30. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  31. package/dist/modules/catalog/lib/bulkDelete.js.map +2 -2
  32. package/dist/modules/currencies/backend/currencies/[id]/page.js +15 -3
  33. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  34. package/dist/modules/currencies/backend/currencies/page.js +2 -1
  35. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  36. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +12 -1
  37. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
  38. package/dist/modules/currencies/backend/exchange-rates/page.js +3 -2
  39. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  40. package/dist/modules/customer_accounts/api/admin/roles/[id].js +3 -3
  41. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  42. package/dist/modules/customer_accounts/api/admin/users/[id].js +3 -3
  43. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  44. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +7 -12
  45. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  46. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +5 -2
  47. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  48. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -1
  49. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  50. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +2 -1
  51. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +2 -2
  52. package/dist/modules/customers/api/addresses/route.js +4 -2
  53. package/dist/modules/customers/api/addresses/route.js.map +2 -2
  54. package/dist/modules/customers/api/tags/route.js +8 -3
  55. package/dist/modules/customers/api/tags/route.js.map +2 -2
  56. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +13 -2
  57. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  58. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +24 -9
  59. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
  60. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +53 -23
  61. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +2 -2
  62. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js +13 -3
  63. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js.map +2 -2
  64. package/dist/modules/customers/backend/customers/deals/[id]/page.js +19 -2
  65. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  66. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +13 -2
  67. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  68. package/dist/modules/customers/commands/interactions.js +5 -5
  69. package/dist/modules/customers/commands/interactions.js.map +2 -2
  70. package/dist/modules/customers/commands/pipeline-stages.js +24 -2
  71. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  72. package/dist/modules/customers/commands/pipelines.js +24 -2
  73. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  74. package/dist/modules/customers/components/detail/AddressesSection.js +46 -29
  75. package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
  76. package/dist/modules/customers/components/detail/DealForm.js +5 -1
  77. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  78. package/dist/modules/customers/components/detail/EntityTagsDialog.js +76 -67
  79. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +3 -3
  80. package/dist/modules/customers/components/detail/notesAdapter.js +25 -18
  81. package/dist/modules/customers/components/detail/notesAdapter.js.map +2 -2
  82. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +4 -3
  83. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  84. package/dist/modules/data_sync/api/runs.js +1 -0
  85. package/dist/modules/data_sync/api/runs.js.map +2 -2
  86. package/dist/modules/data_sync/api/schedules/[id]/route.js +38 -19
  87. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  88. package/dist/modules/data_sync/api/schedules/route.js +1 -1
  89. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  90. package/dist/modules/data_sync/backend/data-sync/page.js +35 -16
  91. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  92. package/dist/modules/data_sync/data/validators.js +1 -0
  93. package/dist/modules/data_sync/data/validators.js.map +2 -2
  94. package/dist/modules/data_sync/lib/sync-run-service.js +20 -0
  95. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  96. package/dist/modules/data_sync/lib/sync-schedule-service.js +19 -5
  97. package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
  98. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +3 -3
  99. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  100. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +3 -3
  101. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  102. package/dist/modules/dictionaries/components/DictionariesManager.js +12 -1
  103. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  104. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  105. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +12 -0
  106. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  107. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +12 -0
  108. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  109. package/dist/modules/entities/api/entities.js +2 -2
  110. package/dist/modules/entities/api/entities.js.map +2 -2
  111. package/dist/modules/entities/api/records.js +18 -6
  112. package/dist/modules/entities/api/records.js.map +2 -2
  113. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +17 -10
  114. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  115. package/dist/modules/feature_toggles/api/overrides/route.js +2 -2
  116. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  117. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
  118. package/dist/modules/feature_toggles/data/entities.js +1 -1
  119. package/dist/modules/feature_toggles/data/entities.js.map +2 -2
  120. package/dist/modules/feature_toggles/data/validators.js +2 -1
  121. package/dist/modules/feature_toggles/data/validators.js.map +2 -2
  122. package/dist/modules/feature_toggles/lib/queries.js +2 -1
  123. package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
  124. package/dist/modules/inbox_ops/api/settings/route.js +2 -2
  125. package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
  126. package/dist/modules/messages/api/unread-count/route.js +32 -0
  127. package/dist/modules/messages/api/unread-count/route.js.map +2 -2
  128. package/dist/modules/query_index/components/QueryIndexesTable.js +13 -5
  129. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  130. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +12 -1
  131. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
  132. package/dist/modules/resources/backend/resources/resources/[id]/page.js +12 -1
  133. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  134. package/dist/modules/resources/components/detail/notesAdapter.js +25 -18
  135. package/dist/modules/resources/components/detail/notesAdapter.js.map +2 -2
  136. package/dist/modules/sales/backend/sales/documents/[id]/page.js +14 -1
  137. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  138. package/dist/modules/sales/commands/documents.js +11 -10
  139. package/dist/modules/sales/commands/documents.js.map +2 -2
  140. package/dist/modules/sales/commands/payments.js +6 -1
  141. package/dist/modules/sales/commands/payments.js.map +2 -2
  142. package/dist/modules/sales/commands/returns.js +3 -3
  143. package/dist/modules/sales/commands/returns.js.map +2 -2
  144. package/dist/modules/sales/commands/shared.js +3 -3
  145. package/dist/modules/sales/commands/shared.js.map +2 -2
  146. package/dist/modules/sales/commands/shipments.js +6 -1
  147. package/dist/modules/sales/commands/shipments.js.map +2 -2
  148. package/dist/modules/sales/components/documents/PaymentDialog.js +6 -3
  149. package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
  150. package/dist/modules/sales/components/documents/PaymentsSection.js +7 -3
  151. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  152. package/dist/modules/sales/components/documents/ShipmentDialog.js +6 -3
  153. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  154. package/dist/modules/sales/components/documents/ShipmentsSection.js +7 -3
  155. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  156. package/dist/modules/sales/di.js +39 -3
  157. package/dist/modules/sales/di.js.map +2 -2
  158. package/dist/modules/sales/setup.js +2 -0
  159. package/dist/modules/sales/setup.js.map +2 -2
  160. package/dist/modules/shipping_carriers/i18n/en.js +11 -1
  161. package/dist/modules/shipping_carriers/i18n/en.js.map +2 -2
  162. package/dist/modules/shipping_carriers/i18n/pl.js +11 -1
  163. package/dist/modules/shipping_carriers/i18n/pl.js.map +2 -2
  164. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js +25 -0
  165. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js.map +7 -0
  166. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +3 -17
  167. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +2 -2
  168. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +12 -1
  169. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  170. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +12 -1
  171. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  172. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +12 -1
  173. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  174. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -1
  175. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  176. package/dist/modules/staff/commands/job-histories.js +3 -3
  177. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  178. package/dist/modules/staff/components/detail/addressesAdapter.js +33 -24
  179. package/dist/modules/staff/components/detail/addressesAdapter.js.map +2 -2
  180. package/dist/modules/staff/components/detail/notesAdapter.js +25 -18
  181. package/dist/modules/staff/components/detail/notesAdapter.js.map +2 -2
  182. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +37 -0
  183. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  184. package/dist/modules/translations/components/TranslationManager.js +7 -1
  185. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  186. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +19 -14
  187. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
  188. package/dist/modules/workflows/api/definitions/[id]/route.js +3 -3
  189. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  190. package/dist/modules/workflows/backend/definitions/[id]/page.js +12 -1
  191. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  192. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +11 -1
  193. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  194. package/package.json +7 -7
  195. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +3 -0
  196. package/src/modules/auth/api/roles/acl/route.ts +2 -2
  197. package/src/modules/auth/api/users/acl/route.ts +2 -2
  198. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +17 -0
  199. package/src/modules/auth/backend/users/[id]/edit/page.tsx +17 -0
  200. package/src/modules/auth/data/entities.ts +2 -2
  201. package/src/modules/business_rules/api/rules/route.ts +3 -3
  202. package/src/modules/business_rules/api/sets/route.ts +3 -3
  203. package/src/modules/business_rules/backend/rules/[id]/page.tsx +17 -1
  204. package/src/modules/business_rules/backend/sets/[id]/page.tsx +18 -1
  205. package/src/modules/catalog/api/product-media/route.ts +4 -0
  206. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +17 -0
  207. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +21 -0
  208. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +16 -1
  209. package/src/modules/catalog/components/PriceKindSettings.tsx +15 -7
  210. package/src/modules/catalog/lib/bulkDelete.ts +6 -0
  211. package/src/modules/currencies/backend/currencies/[id]/page.tsx +20 -3
  212. package/src/modules/currencies/backend/currencies/page.tsx +2 -1
  213. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +17 -1
  214. package/src/modules/currencies/backend/exchange-rates/page.tsx +3 -2
  215. package/src/modules/customer_accounts/api/admin/roles/[id].ts +3 -3
  216. package/src/modules/customer_accounts/api/admin/users/[id].ts +3 -3
  217. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +7 -10
  218. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +6 -7
  219. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +3 -2
  220. package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +3 -2
  221. package/src/modules/customers/api/addresses/route.ts +2 -0
  222. package/src/modules/customers/api/tags/route.ts +6 -1
  223. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +19 -2
  224. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +29 -8
  225. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +47 -20
  226. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.ts +13 -3
  227. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +21 -1
  228. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -2
  229. package/src/modules/customers/commands/interactions.ts +5 -5
  230. package/src/modules/customers/commands/pipeline-stages.ts +26 -2
  231. package/src/modules/customers/commands/pipelines.ts +26 -2
  232. package/src/modules/customers/components/detail/AddressesSection.tsx +50 -28
  233. package/src/modules/customers/components/detail/DealForm.tsx +12 -0
  234. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +89 -71
  235. package/src/modules/customers/components/detail/notesAdapter.ts +29 -18
  236. package/src/modules/customers/i18n/de.json +2 -0
  237. package/src/modules/customers/i18n/en.json +2 -0
  238. package/src/modules/customers/i18n/es.json +2 -0
  239. package/src/modules/customers/i18n/pl.json +2 -0
  240. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +6 -3
  241. package/src/modules/data_sync/api/runs.ts +1 -0
  242. package/src/modules/data_sync/api/schedules/[id]/route.ts +38 -20
  243. package/src/modules/data_sync/api/schedules/route.ts +1 -1
  244. package/src/modules/data_sync/backend/data-sync/page.tsx +35 -16
  245. package/src/modules/data_sync/data/validators.ts +1 -0
  246. package/src/modules/data_sync/i18n/de.json +1 -0
  247. package/src/modules/data_sync/i18n/en.json +1 -0
  248. package/src/modules/data_sync/i18n/es.json +1 -0
  249. package/src/modules/data_sync/i18n/pl.json +1 -0
  250. package/src/modules/data_sync/lib/sync-run-service.ts +23 -0
  251. package/src/modules/data_sync/lib/sync-schedule-service.ts +30 -5
  252. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +3 -3
  253. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +3 -3
  254. package/src/modules/dictionaries/components/DictionariesManager.tsx +21 -1
  255. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +2 -0
  256. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +17 -0
  257. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +17 -0
  258. package/src/modules/entities/api/entities.ts +2 -2
  259. package/src/modules/entities/api/records.ts +24 -6
  260. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +25 -12
  261. package/src/modules/feature_toggles/api/overrides/route.ts +2 -2
  262. package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +12 -0
  263. package/src/modules/feature_toggles/data/entities.ts +1 -1
  264. package/src/modules/feature_toggles/data/validators.ts +1 -0
  265. package/src/modules/feature_toggles/lib/queries.ts +1 -0
  266. package/src/modules/inbox_ops/api/settings/route.ts +2 -2
  267. package/src/modules/messages/api/unread-count/route.ts +40 -0
  268. package/src/modules/query_index/components/QueryIndexesTable.tsx +17 -15
  269. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +17 -1
  270. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +17 -1
  271. package/src/modules/resources/components/detail/notesAdapter.ts +29 -18
  272. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +17 -1
  273. package/src/modules/sales/commands/documents.ts +11 -10
  274. package/src/modules/sales/commands/payments.ts +11 -0
  275. package/src/modules/sales/commands/returns.ts +3 -3
  276. package/src/modules/sales/commands/shared.ts +10 -4
  277. package/src/modules/sales/commands/shipments.ts +14 -0
  278. package/src/modules/sales/components/documents/PaymentDialog.tsx +7 -3
  279. package/src/modules/sales/components/documents/PaymentsSection.tsx +8 -3
  280. package/src/modules/sales/components/documents/ShipmentDialog.tsx +7 -3
  281. package/src/modules/sales/components/documents/ShipmentsSection.tsx +8 -3
  282. package/src/modules/sales/di.ts +68 -9
  283. package/src/modules/sales/setup.ts +2 -0
  284. package/src/modules/shipping_carriers/i18n/de.json +11 -1
  285. package/src/modules/shipping_carriers/i18n/en.json +11 -1
  286. package/src/modules/shipping_carriers/i18n/en.ts +10 -0
  287. package/src/modules/shipping_carriers/i18n/es.json +11 -1
  288. package/src/modules/shipping_carriers/i18n/pl.json +11 -1
  289. package/src/modules/shipping_carriers/i18n/pl.ts +10 -0
  290. package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.tsx +35 -0
  291. package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +3 -22
  292. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +17 -1
  293. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +17 -1
  294. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +17 -1
  295. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +17 -1
  296. package/src/modules/staff/commands/job-histories.ts +3 -3
  297. package/src/modules/staff/components/detail/addressesAdapter.ts +40 -23
  298. package/src/modules/staff/components/detail/notesAdapter.ts +28 -18
  299. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +74 -0
  300. package/src/modules/translations/components/TranslationManager.tsx +14 -1
  301. package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +30 -13
  302. package/src/modules/workflows/api/definitions/[id]/route.ts +3 -3
  303. package/src/modules/workflows/backend/definitions/[id]/page.tsx +18 -1
  304. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +19 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/feature_toggles/api/overrides/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { getOverrides } from '../../lib/queries'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { FeatureToggleOverride } from '../../data/entities'\nimport { buildContext } from '../../lib/utils'\nimport { resolveFeatureCheckContext } from \"@open-mercato/core/modules/directory/utils/organizationScope\"\nimport { ProcessedChangeOverrideStateInput } from '../../data/validators'\nimport {\n changeOverrideStateBaseSchema,\n overrideListQuerySchema,\n featureTogglesTag,\n featureToggleOverrideListResponseSchema,\n featureToggleErrorSchema,\n changeOverrideStateResponseSchema,\n} from '../openapi'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\n\nexport async function GET(req: Request) {\n const { auth, ctx, scope } = await buildContext(req)\n\n const url = new URL(req.url)\n const parsed = overrideListQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries()))\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n const em = ctx.container.resolve('em') as EntityManager\n\n const tenant = await em.findOne(Tenant, {\n id: scope.tenantId,\n })\n\n if (!tenant) {\n return NextResponse.json({\n error: 'Tenant context required. Please select a tenant.'\n }, { status: 400 })\n }\n\n const result = await getOverrides(em, tenant, parsed.data)\n\n await logCrudAccess({\n container: ctx.container,\n auth,\n request: req,\n items: [...result.raw.toggles, ...result.raw.overrides],\n idField: 'id',\n resourceKind: 'feature_toggles.override_list',\n tenantId: scope.tenantId ?? null,\n query: parsed.data,\n accessType: 'read:list',\n fields: ['id', 'toggleId', 'tenantId', 'identifier', 'name', 'category']\n })\n\n return NextResponse.json({\n items: result.items,\n total: result.total,\n totalPages: result.totalPages,\n page: result.page,\n pageSize: result.pageSize,\n isSuperAdmin: auth.isSuperAdmin ?? false\n })\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx } = await buildContext(req)\n\n const parsed = changeOverrideStateBaseSchema.safeParse(await req.json())\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const { scope, organizationId } = await resolveFeatureCheckContext({\n container: ctx.container,\n auth: ctx.auth,\n request: req\n })\n\n if (!scope.tenantId) {\n return NextResponse.json({\n error: 'Tenant context required. Please select a tenant.'\n }, { status: 400 })\n }\n\n // Optimistic lock: refuse a stale override overwrite when two admins edit the\n // same global toggle override in parallel. Only enforced when an override row\n // already exists (first set has no prior version). Strictly additive.\n const em = ctx.container.resolve('em') as EntityManager\n const existingOverride = await em.findOne(FeatureToggleOverride, {\n tenantId: scope.tenantId,\n toggle: { id: parsed.data.toggleId },\n })\n if (existingOverride) {\n enforceCommandOptimisticLock({\n resourceKind: 'feature_toggles.feature_toggle_override',\n resourceId: existingOverride.id,\n current: existingOverride.updatedAt ?? null,\n request: req,\n })\n }\n\n // Run the CRUD mutation guard lifecycle so this custom write route honors\n // module-level guard injections (and after-success hooks) like every other\n // mutating endpoint. The override either updates an existing row or creates a\n // new one, so the resource id falls back to the toggle id when no row exists.\n const guardUserId = ctx.auth?.userId ?? ctx.auth?.sub ?? null\n if (!guardUserId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const guardResourceKind = 'feature_toggles.feature_toggle_override'\n const guardResourceId = existingOverride?.id ?? parsed.data.toggleId\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: scope.tenantId,\n organizationId: organizationId ?? null,\n userId: guardUserId,\n resourceKind: guardResourceKind,\n resourceId: guardResourceId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<ProcessedChangeOverrideStateInput, { overrideToggleId: string | null }>('feature_toggles.overrides.changeState', {\n input: {\n toggleId: parsed.data.toggleId,\n tenantId: scope.tenantId,\n isOverride: parsed.data.isOverride,\n overrideValue: parsed.data.overrideValue,\n },\n ctx,\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: scope.tenantId,\n organizationId: organizationId ?? null,\n userId: guardUserId,\n resourceKind: guardResourceKind,\n resourceId: result?.overrideToggleId ?? guardResourceId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({\n ok: true,\n overrideToggleId: result?.overrideToggleId ?? null,\n })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'feature_toggles.override',\n resourceId: logEntry.resourceId ?? result?.overrideToggleId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n const message = error instanceof Error ? error.message : 'Unknown error'\n if (message === 'NOT_FOUND') return NextResponse.json({ error: 'Not found' }, { status: 404 })\n if (message === 'INVALID_STATE' || message === 'INVALID_TOGGLE_ID' || message === 'INVALID_TENANT_ID') {\n return NextResponse.json({ error: message }, { status: 400 })\n }\n console.error('feature_toggles.overrides.changeState failed', error)\n return NextResponse.json({ error: 'Failed to update override' }, { status: 500 })\n }\n}\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['feature_toggles.view'] },\n PUT: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst overrideGetDoc: OpenApiMethodDoc = {\n summary: 'List overrides',\n description: 'Returns list of feature toggle overrides.',\n tags: [featureTogglesTag],\n query: overrideListQuerySchema,\n responses: [\n { status: 200, description: 'List of overrides', schema: featureToggleOverrideListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: featureToggleErrorSchema },\n { status: 401, description: 'Authentication required', schema: featureToggleErrorSchema },\n ],\n}\n\nconst overridePutDoc: OpenApiMethodDoc = {\n summary: 'Change override state',\n description: 'Enable, disable or inherit a feature toggle for a specific tenant.',\n tags: [featureTogglesTag],\n requestBody: {\n contentType: 'application/json',\n schema: changeOverrideStateBaseSchema,\n description: 'Override details.',\n },\n responses: [\n { status: 200, description: 'Override updated', schema: changeOverrideStateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: featureToggleErrorSchema },\n { status: 401, description: 'Authentication required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Not found', schema: featureToggleErrorSchema },\n { status: 500, description: 'Internal server error', schema: featureToggleErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: featureTogglesTag,\n summary: 'Manage feature toggle overrides',\n methods: {\n GET: overrideGetDoc,\n PUT: overridePutDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { getOverrides } from '../../lib/queries'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { FeatureToggleOverride } from '../../data/entities'\nimport { buildContext } from '../../lib/utils'\nimport { resolveFeatureCheckContext } from \"@open-mercato/core/modules/directory/utils/organizationScope\"\nimport { ProcessedChangeOverrideStateInput } from '../../data/validators'\nimport {\n changeOverrideStateBaseSchema,\n overrideListQuerySchema,\n featureTogglesTag,\n featureToggleOverrideListResponseSchema,\n featureToggleErrorSchema,\n changeOverrideStateResponseSchema,\n} from '../openapi'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\n\nexport async function GET(req: Request) {\n const { auth, ctx, scope } = await buildContext(req)\n\n const url = new URL(req.url)\n const parsed = overrideListQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries()))\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n const em = ctx.container.resolve('em') as EntityManager\n\n const tenant = await em.findOne(Tenant, {\n id: scope.tenantId,\n })\n\n if (!tenant) {\n return NextResponse.json({\n error: 'Tenant context required. Please select a tenant.'\n }, { status: 400 })\n }\n\n const result = await getOverrides(em, tenant, parsed.data)\n\n await logCrudAccess({\n container: ctx.container,\n auth,\n request: req,\n items: [...result.raw.toggles, ...result.raw.overrides],\n idField: 'id',\n resourceKind: 'feature_toggles.override_list',\n tenantId: scope.tenantId ?? null,\n query: parsed.data,\n accessType: 'read:list',\n fields: ['id', 'toggleId', 'tenantId', 'identifier', 'name', 'category']\n })\n\n return NextResponse.json({\n items: result.items,\n total: result.total,\n totalPages: result.totalPages,\n page: result.page,\n pageSize: result.pageSize,\n isSuperAdmin: auth.isSuperAdmin ?? false\n })\n}\n\nexport async function PUT(req: Request) {\n try {\n const { ctx } = await buildContext(req)\n\n const parsed = changeOverrideStateBaseSchema.safeParse(await req.json())\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const { scope, organizationId } = await resolveFeatureCheckContext({\n container: ctx.container,\n auth: ctx.auth,\n request: req\n })\n\n if (!scope.tenantId) {\n return NextResponse.json({\n error: 'Tenant context required. Please select a tenant.'\n }, { status: 400 })\n }\n\n // Optimistic lock: refuse a stale override overwrite when two admins edit the\n // same global toggle override in parallel. Only enforced when an override row\n // already exists (first set has no prior version). Strictly additive.\n const em = ctx.container.resolve('em') as EntityManager\n const existingOverride = await em.findOne(FeatureToggleOverride, {\n tenantId: scope.tenantId,\n toggle: { id: parsed.data.toggleId },\n })\n if (existingOverride) {\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'feature_toggles.feature_toggle_override',\n resourceId: existingOverride.id,\n current: existingOverride.updatedAt ?? null,\n request: req,\n })\n }\n\n // Run the CRUD mutation guard lifecycle so this custom write route honors\n // module-level guard injections (and after-success hooks) like every other\n // mutating endpoint. The override either updates an existing row or creates a\n // new one, so the resource id falls back to the toggle id when no row exists.\n const guardUserId = ctx.auth?.userId ?? ctx.auth?.sub ?? null\n if (!guardUserId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const guardResourceKind = 'feature_toggles.feature_toggle_override'\n const guardResourceId = existingOverride?.id ?? parsed.data.toggleId\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: scope.tenantId,\n organizationId: organizationId ?? null,\n userId: guardUserId,\n resourceKind: guardResourceKind,\n resourceId: guardResourceId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<ProcessedChangeOverrideStateInput, { overrideToggleId: string | null }>('feature_toggles.overrides.changeState', {\n input: {\n toggleId: parsed.data.toggleId,\n tenantId: scope.tenantId,\n isOverride: parsed.data.isOverride,\n overrideValue: parsed.data.overrideValue,\n },\n ctx,\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: scope.tenantId,\n organizationId: organizationId ?? null,\n userId: guardUserId,\n resourceKind: guardResourceKind,\n resourceId: result?.overrideToggleId ?? guardResourceId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = NextResponse.json({\n ok: true,\n overrideToggleId: result?.overrideToggleId ?? null,\n })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'feature_toggles.override',\n resourceId: logEntry.resourceId ?? result?.overrideToggleId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n\n return response\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n const message = error instanceof Error ? error.message : 'Unknown error'\n if (message === 'NOT_FOUND') return NextResponse.json({ error: 'Not found' }, { status: 404 })\n if (message === 'INVALID_STATE' || message === 'INVALID_TOGGLE_ID' || message === 'INVALID_TENANT_ID') {\n return NextResponse.json({ error: message }, { status: 400 })\n }\n console.error('feature_toggles.overrides.changeState failed', error)\n return NextResponse.json({ error: 'Failed to update override' }, { status: 500 })\n }\n}\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['feature_toggles.view'] },\n PUT: { requireAuth: true, requireFeatures: ['feature_toggles.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst overrideGetDoc: OpenApiMethodDoc = {\n summary: 'List overrides',\n description: 'Returns list of feature toggle overrides.',\n tags: [featureTogglesTag],\n query: overrideListQuerySchema,\n responses: [\n { status: 200, description: 'List of overrides', schema: featureToggleOverrideListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: featureToggleErrorSchema },\n { status: 401, description: 'Authentication required', schema: featureToggleErrorSchema },\n ],\n}\n\nconst overridePutDoc: OpenApiMethodDoc = {\n summary: 'Change override state',\n description: 'Enable, disable or inherit a feature toggle for a specific tenant.',\n tags: [featureTogglesTag],\n requestBody: {\n contentType: 'application/json',\n schema: changeOverrideStateBaseSchema,\n description: 'Override details.',\n },\n responses: [\n { status: 200, description: 'Override updated', schema: changeOverrideStateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: featureToggleErrorSchema },\n { status: 401, description: 'Authentication required', schema: featureToggleErrorSchema },\n { status: 404, description: 'Not found', schema: featureToggleErrorSchema },\n { status: 500, description: 'Internal server error', schema: featureToggleErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: featureTogglesTag,\n summary: 'Manage feature toggle overrides',\n methods: {\n GET: overrideGetDoc,\n PUT: overridePutDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/feature_toggles/backend/feature-toggles/global/%5Bid%5D/edit/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { Page, PageBody } from \"@open-mercato/ui/backend/Page\";\nimport { CrudForm } from \"@open-mercato/ui/backend/CrudForm\";\nimport { ErrorMessage, LoadingMessage } from \"@open-mercato/ui/backend/detail\";\nimport { E } from \"#generated/entities.ids.generated\";\nimport { useT } from \"@open-mercato/shared/lib/i18n/context\";\nimport * as React from 'react'\nimport { updateCrud } from \"@open-mercato/ui/backend/utils/crud\";\nimport { useFeatureToggleItem } from \"@open-mercato/core/modules/feature_toggles/components/hooks/useFeatureToggleItem\";\nimport { createFieldDefinitions, createFormGroups } from \"@open-mercato/core/modules/feature_toggles/components/formConfig\";\n\n\nexport default function EditFeatureTogglePage({ params }: { params?: { id?: string } }) {\n const { id } = params ?? {}\n const t = useT()\n const fields = createFieldDefinitions(t);\n const formGroups = createFormGroups(t);\n\n const { data: featureToggleItem, isLoading, isError } = useFeatureToggleItem(id)\n\n // Derive the form's initial values synchronously from the loaded record. Using\n // a useState+useEffect here caused #2452: `isLoading` flips to false one render\n // before the effect set `initialValues`, so CrudForm mounted with `{}` and the\n // `type` SELECT captured an empty value (text inputs re-sync from the prop, the\n // select does not). Deriving here means the value is present in the same render\n // the data arrives; the `key` below also forces a clean remount on load.\n const initialValues = React.useMemo(() => {\n if (!featureToggleItem) return null\n return {\n id: featureToggleItem.id ?? (id ? String(id) : ''),\n identifier: featureToggleItem.identifier,\n name: featureToggleItem.name,\n description: featureToggleItem.description,\n category: featureToggleItem.category,\n type: featureToggleItem.type,\n defaultValue: featureToggleItem.defaultValue,\n }\n }, [featureToggleItem, id])\n\n // Gate the form on load. Mounting CrudForm with placeholder `{}` before the\n // record arrives makes the required `type` Select mount controlled with `''`,\n // then flip empty\u2192value asynchronously \u2014 a transition the Radix trigger fails\n // to re-derive, leaving Type blank and blocking save. Rendering CrudForm only\n // once the record is present mirrors the synchronous create flow, so the\n // Select mounts a single time with the stored value already set.\n if (!initialValues) {\n return (\n <Page>\n <PageBody>\n {isError && !isLoading ? (\n <ErrorMessage label={t('feature_toggles.form.errors.load', 'Failed to load feature toggle')} />\n ) : (\n <LoadingMessage label={t('feature_toggles.form.loading', 'Loading feature toggles')} />\n )}\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n key={initialValues ? 'ft-edit-loaded' : 'ft-edit-loading'}\n title={t('feature_toggles.form.title.edit', 'Edit Feature Toggle')}\n backHref=\"/backend/feature-toggles/global\"\n versionHistory={{ resourceKind: 'feature_toggles.global', resourceId: id ? String(id) : '' }}\n fields={fields}\n entityId={E.feature_toggles.feature_toggle}\n initialValues={initialValues}\n optimisticLockUpdatedAt={featureToggleItem?.updatedAt ?? null}\n isLoading={isLoading}\n groups={formGroups}\n loadingMessage={t('feature_toggles.form.loading', 'Loading feature toggles')}\n submitLabel={t('feature_toggles.form.action.save', 'Save')}\n cancelHref=\"/backend/feature-toggles/global\"\n successRedirect={`/backend/feature-toggles/global`}\n onSubmit={async (values) => {\n if (!id) return\n const payload = {\n id: id ? String(id) : '',\n identifier: values.identifier,\n name: values.name,\n description: values.description,\n category: values.category,\n\n type: values.type,\n defaultValue: values.defaultValue,\n }\n await updateCrud('feature_toggles/global', payload)\n }}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": ";AAkDY;AAjDZ,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAgB;AACzB,SAAS,cAAc,sBAAsB;AAC7C,SAAS,SAAS;AAClB,SAAS,YAAY;AACrB,YAAY,WAAW;AACvB,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AACrC,SAAS,wBAAwB,wBAAwB;AAG1C,SAAR,sBAAuC,EAAE,OAAO,GAAiC;AACtF,QAAM,EAAE,GAAG,IAAI,UAAU,CAAC;AAC1B,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,uBAAuB,CAAC;AACvC,QAAM,aAAa,iBAAiB,CAAC;AAErC,QAAM,EAAE,MAAM,mBAAmB,WAAW,QAAQ,IAAI,qBAAqB,EAAE;AAQ/E,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AACxC,QAAI,CAAC,kBAAmB,QAAO;AAC/B,WAAO;AAAA,MACL,IAAI,kBAAkB,OAAO,KAAK,OAAO,EAAE,IAAI;AAAA,MAC/C,YAAY,kBAAkB;AAAA,MAC9B,MAAM,kBAAkB;AAAA,MACxB,aAAa,kBAAkB;AAAA,MAC/B,UAAU,kBAAkB;AAAA,MAC5B,MAAM,kBAAkB;AAAA,MACxB,cAAc,kBAAkB;AAAA,IAClC;AAAA,EACF,GAAG,CAAC,mBAAmB,EAAE,CAAC;AAQ1B,MAAI,CAAC,eAAe;AAClB,WACE,oBAAC,QACC,8BAAC,YACE,qBAAW,CAAC,YACX,oBAAC,gBAAa,OAAO,EAAE,oCAAoC,+BAA+B,GAAG,IAE7F,oBAAC,kBAAe,OAAO,EAAE,gCAAgC,yBAAyB,GAAG,GAEzF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,QACC,8BAAC,YACC;AAAA,IAAC;AAAA;AAAA,MAEC,OAAO,EAAE,mCAAmC,qBAAqB;AAAA,MACjE,UAAS;AAAA,MACT,gBAAgB,EAAE,cAAc,0BAA0B,YAAY,KAAK,OAAO,EAAE,IAAI,GAAG;AAAA,MAC3F;AAAA,MACA,UAAU,EAAE,gBAAgB;AAAA,MAC5B;AAAA,MACA,yBAAyB,mBAAmB,aAAa;AAAA,MACzD;AAAA,MACA,QAAQ;AAAA,MACR,gBAAgB,EAAE,gCAAgC,yBAAyB;AAAA,MAC3E,aAAa,EAAE,oCAAoC,MAAM;AAAA,MACzD,YAAW;AAAA,MACX,iBAAiB;AAAA,MACjB,UAAU,OAAO,WAAW;AAC1B,YAAI,CAAC,GAAI;AACT,cAAM,UAAU;AAAA,UACd,IAAI,KAAK,OAAO,EAAE,IAAI;AAAA,UACtB,YAAY,OAAO;AAAA,UACnB,MAAM,OAAO;AAAA,UACb,aAAa,OAAO;AAAA,UACpB,UAAU,OAAO;AAAA,UAEjB,MAAM,OAAO;AAAA,UACb,cAAc,OAAO;AAAA,QACvB;AACA,cAAM,WAAW,0BAA0B,OAAO;AAAA,MACpD;AAAA;AAAA,IA3BK,gBAAgB,mBAAmB;AAAA,EA4B1C,GACF,GACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\nimport { Page, PageBody } from \"@open-mercato/ui/backend/Page\";\nimport { CrudForm } from \"@open-mercato/ui/backend/CrudForm\";\nimport { ErrorMessage, LoadingMessage } from \"@open-mercato/ui/backend/detail\";\nimport { E } from \"#generated/entities.ids.generated\";\nimport { useT } from \"@open-mercato/shared/lib/i18n/context\";\nimport * as React from 'react'\nimport { updateCrud } from \"@open-mercato/ui/backend/utils/crud\";\nimport { useFeatureToggleItem } from \"@open-mercato/core/modules/feature_toggles/components/hooks/useFeatureToggleItem\";\nimport { createFieldDefinitions, createFormGroups } from \"@open-mercato/core/modules/feature_toggles/components/formConfig\";\n\n\nexport default function EditFeatureTogglePage({ params }: { params?: { id?: string } }) {\n const { id } = params ?? {}\n const t = useT()\n const fields = createFieldDefinitions(t);\n const formGroups = createFormGroups(t);\n\n const { data: featureToggleItem, isLoading, isError } = useFeatureToggleItem(id)\n\n // Derive the form's initial values synchronously from the loaded record. Using\n // a useState+useEffect here caused #2452: `isLoading` flips to false one render\n // before the effect set `initialValues`, so CrudForm mounted with `{}` and the\n // `type` SELECT captured an empty value (text inputs re-sync from the prop, the\n // select does not). Deriving here means the value is present in the same render\n // the data arrives; the `key` below also forces a clean remount on load.\n const initialValues = React.useMemo(() => {\n if (!featureToggleItem) return null\n return {\n id: featureToggleItem.id ?? (id ? String(id) : ''),\n identifier: featureToggleItem.identifier,\n name: featureToggleItem.name,\n description: featureToggleItem.description,\n category: featureToggleItem.category,\n type: featureToggleItem.type,\n defaultValue: featureToggleItem.defaultValue,\n }\n }, [featureToggleItem, id])\n\n // Gate the form on load. Mounting CrudForm with placeholder `{}` before the\n // record arrives makes the required `type` Select mount controlled with `''`,\n // then flip empty\u2192value asynchronously \u2014 a transition the Radix trigger fails\n // to re-derive, leaving Type blank and blocking save. Rendering CrudForm only\n // once the record is present mirrors the synchronous create flow, so the\n // Select mounts a single time with the stored value already set.\n if (!initialValues) {\n return (\n <Page>\n <PageBody>\n {isError && !isLoading ? (\n <ErrorMessage label={t('feature_toggles.form.errors.load', 'Failed to load feature toggle')} />\n ) : (\n <LoadingMessage label={t('feature_toggles.form.loading', 'Loading feature toggles')} />\n )}\n </PageBody>\n </Page>\n )\n }\n\n // record_locks scope decision (Phase 6): the global feature toggle is a\n // NON-TENANT, superadmin-only entity. record_locks enrichment (presence,\n // acquire/heartbeat, conflict upgrade) is tenant-scoped \u2014 its decorator skips\n // enrichment when no tenant/resource resolves \u2014 so a global (null-tenant) record\n // cannot participate in record_locks. This surface therefore stays OSS-floor\n // guarded ONLY: the CrudForm auto-derives the optimistic-lock header from\n // `optimisticLockUpdatedAt`, the route enforces it via the OSS floor, and a stale\n // save surfaces the unified conflict bar. No `backend:record:current` presence is\n // mounted: it would never resolve a tenant-scoped lock, and this is a\n // single-superadmin surface with no concurrent-editor expectation. Per-tenant\n // override values (`api/global/[id]/override`) ARE tenant-scoped and are enabled\n // in Phase 6b.\n return (\n <Page>\n <PageBody>\n <CrudForm\n key={initialValues ? 'ft-edit-loaded' : 'ft-edit-loading'}\n title={t('feature_toggles.form.title.edit', 'Edit Feature Toggle')}\n backHref=\"/backend/feature-toggles/global\"\n versionHistory={{ resourceKind: 'feature_toggles.global', resourceId: id ? String(id) : '' }}\n fields={fields}\n entityId={E.feature_toggles.feature_toggle}\n initialValues={initialValues}\n optimisticLockUpdatedAt={featureToggleItem?.updatedAt ?? null}\n isLoading={isLoading}\n groups={formGroups}\n loadingMessage={t('feature_toggles.form.loading', 'Loading feature toggles')}\n submitLabel={t('feature_toggles.form.action.save', 'Save')}\n cancelHref=\"/backend/feature-toggles/global\"\n successRedirect={`/backend/feature-toggles/global`}\n onSubmit={async (values) => {\n if (!id) return\n const payload = {\n id: id ? String(id) : '',\n identifier: values.identifier,\n name: values.name,\n description: values.description,\n category: values.category,\n\n type: values.type,\n defaultValue: values.defaultValue,\n }\n await updateCrud('feature_toggles/global', payload)\n }}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": ";AAkDY;AAjDZ,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAgB;AACzB,SAAS,cAAc,sBAAsB;AAC7C,SAAS,SAAS;AAClB,SAAS,YAAY;AACrB,YAAY,WAAW;AACvB,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AACrC,SAAS,wBAAwB,wBAAwB;AAG1C,SAAR,sBAAuC,EAAE,OAAO,GAAiC;AACtF,QAAM,EAAE,GAAG,IAAI,UAAU,CAAC;AAC1B,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,uBAAuB,CAAC;AACvC,QAAM,aAAa,iBAAiB,CAAC;AAErC,QAAM,EAAE,MAAM,mBAAmB,WAAW,QAAQ,IAAI,qBAAqB,EAAE;AAQ/E,QAAM,gBAAgB,MAAM,QAAQ,MAAM;AACxC,QAAI,CAAC,kBAAmB,QAAO;AAC/B,WAAO;AAAA,MACL,IAAI,kBAAkB,OAAO,KAAK,OAAO,EAAE,IAAI;AAAA,MAC/C,YAAY,kBAAkB;AAAA,MAC9B,MAAM,kBAAkB;AAAA,MACxB,aAAa,kBAAkB;AAAA,MAC/B,UAAU,kBAAkB;AAAA,MAC5B,MAAM,kBAAkB;AAAA,MACxB,cAAc,kBAAkB;AAAA,IAClC;AAAA,EACF,GAAG,CAAC,mBAAmB,EAAE,CAAC;AAQ1B,MAAI,CAAC,eAAe;AAClB,WACE,oBAAC,QACC,8BAAC,YACE,qBAAW,CAAC,YACX,oBAAC,gBAAa,OAAO,EAAE,oCAAoC,+BAA+B,GAAG,IAE7F,oBAAC,kBAAe,OAAO,EAAE,gCAAgC,yBAAyB,GAAG,GAEzF,GACF;AAAA,EAEJ;AAcA,SACE,oBAAC,QACC,8BAAC,YACC;AAAA,IAAC;AAAA;AAAA,MAEC,OAAO,EAAE,mCAAmC,qBAAqB;AAAA,MACjE,UAAS;AAAA,MACT,gBAAgB,EAAE,cAAc,0BAA0B,YAAY,KAAK,OAAO,EAAE,IAAI,GAAG;AAAA,MAC3F;AAAA,MACA,UAAU,EAAE,gBAAgB;AAAA,MAC5B;AAAA,MACA,yBAAyB,mBAAmB,aAAa;AAAA,MACzD;AAAA,MACA,QAAQ;AAAA,MACR,gBAAgB,EAAE,gCAAgC,yBAAyB;AAAA,MAC3E,aAAa,EAAE,oCAAoC,MAAM;AAAA,MACzD,YAAW;AAAA,MACX,iBAAiB;AAAA,MACjB,UAAU,OAAO,WAAW;AAC1B,YAAI,CAAC,GAAI;AACT,cAAM,UAAU;AAAA,UACd,IAAI,KAAK,OAAO,EAAE,IAAI;AAAA,UACtB,YAAY,OAAO;AAAA,UACnB,MAAM,OAAO;AAAA,UACb,aAAa,OAAO;AAAA,UACpB,UAAU,OAAO;AAAA,UAEjB,MAAM,OAAO;AAAA,UACb,cAAc,OAAO;AAAA,QACvB;AACA,cAAM,WAAW,0BAA0B,OAAO;AAAA,MACpD;AAAA;AAAA,IA3BK,gBAAgB,mBAAmB;AAAA,EA4B1C,GACF,GACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -73,7 +73,7 @@ __decorateClass([
73
73
  Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
74
74
  ], FeatureToggleOverride.prototype, "createdAt", 2);
75
75
  __decorateClass([
76
- Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date() })
76
+ Property({ name: "updated_at", type: Date, onCreate: () => /* @__PURE__ */ new Date(), onUpdate: () => /* @__PURE__ */ new Date() })
77
77
  ], FeatureToggleOverride.prototype, "updatedAt", 2);
78
78
  __decorateClass([
79
79
  Property({ name: "value", type: "jsonb" })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/feature_toggles/data/entities.ts"],
4
- "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, ManyToOne, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\nimport type { JsonValue } from '@open-mercato/shared/lib/json'\n\n\nexport type FeatureToggleType = 'boolean' | 'string' | 'number' | 'json'\n\n@Entity({ tableName: 'feature_toggles' })\n@Unique({ name: 'feature_toggles_identifier_unique', properties: ['identifier'] })\n@Index({ name: 'feature_toggles_category_idx', properties: ['category'] })\n@Index({ name: 'feature_toggles_name_idx', properties: ['name'] })\nexport class FeatureToggle {\n [OptionalProps]?: 'description' | 'category' | 'createdAt' | 'updatedAt' | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n identifier!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ type: 'text', nullable: true })\n description?: string | null\n\n @Property({ type: 'text', nullable: true })\n category?: string | null\n\n @Property({ name: 'default_value', type: 'jsonb' })\n defaultValue!: JsonValue\n\n @Property({ name: 'type', type: 'text' })\n type!: FeatureToggleType\n\n\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'feature_toggle_overrides' })\n@Unique({\n name: 'feature_toggle_overrides_toggle_tenant_unique',\n properties: ['toggle', 'tenantId'],\n})\n@Index({ name: 'feature_toggle_overrides_tenant_idx', properties: ['tenantId'] })\n@Index({ name: 'feature_toggle_overrides_toggle_idx', properties: ['toggle'] })\nexport class FeatureToggleOverride {\n [OptionalProps]?: 'createdAt' | 'updatedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => FeatureToggle, { fieldName: 'toggle_id' })\n toggle!: FeatureToggle\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'value', type: 'jsonb' })\n value!: JsonValue\n}\n"],
5
- "mappings": ";;;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAAS,QAAQ,OAAO,WAAW,YAAY,UAAU,cAAc;AAWpE;AADI,IAAM,gBAAN,MAAoB;AAAA,EAApB;AA2BL,qBAAkB,oBAAI,KAAK;AAG3B,qBAAkB,oBAAI,KAAK;AAAA;AAI7B;AA9BE;AAAA,EADC,WAAW,EAAE,MAAM,QAAQ,YAAY,oBAAoB,CAAC;AAAA,GAHlD,cAIX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,GANf,cAOX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,GATf,cAUX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAZ/B,cAaX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAf/B,cAgBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,iBAAiB,MAAM,QAAQ,CAAC;AAAA,GAlBvC,cAmBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,MAAM,OAAO,CAAC;AAAA,GArB7B,cAsBX;AAKA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GA1B7D,cA2BX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GA7B7D,cA8BX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,KAAK,CAAC;AAAA,GAhCjD,cAiCX;AAjCW,gBAAN;AAAA,EAJN,OAAO,EAAE,WAAW,kBAAkB,CAAC;AAAA,EACvC,OAAO,EAAE,MAAM,qCAAqC,YAAY,CAAC,YAAY,EAAE,CAAC;AAAA,EAChF,MAAM,EAAE,MAAM,gCAAgC,YAAY,CAAC,UAAU,EAAE,CAAC;AAAA,EACxE,MAAM,EAAE,MAAM,4BAA4B,YAAY,CAAC,MAAM,EAAE,CAAC;AAAA,GACpD;AA4CV;AADI,IAAM,wBAAN,MAA4B;AAAA,EAA5B;AAaL,qBAAkB,oBAAI,KAAK;AAG3B,qBAAkB,oBAAI,KAAK;AAAA;AAI7B;AAhBE;AAAA,EADC,WAAW,EAAE,MAAM,QAAQ,YAAY,oBAAoB,CAAC;AAAA,GAHlD,sBAIX;AAGA;AAAA,EADC,UAAU,MAAM,eAAe,EAAE,WAAW,YAAY,CAAC;AAAA,GAN/C,sBAOX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,GATlC,sBAUX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GAZ7D,sBAaX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GAf7D,sBAgBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,GAlB/B,sBAmBX;AAnBW,wBAAN;AAAA,EAPN,OAAO,EAAE,WAAW,2BAA2B,CAAC;AAAA,EAChD,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,CAAC,UAAU,UAAU;AAAA,EACnC,CAAC;AAAA,EACA,MAAM,EAAE,MAAM,uCAAuC,YAAY,CAAC,UAAU,EAAE,CAAC;AAAA,EAC/E,MAAM,EAAE,MAAM,uCAAuC,YAAY,CAAC,QAAQ,EAAE,CAAC;AAAA,GACjE;",
4
+ "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, ManyToOne, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'\nimport type { JsonValue } from '@open-mercato/shared/lib/json'\n\n\nexport type FeatureToggleType = 'boolean' | 'string' | 'number' | 'json'\n\n@Entity({ tableName: 'feature_toggles' })\n@Unique({ name: 'feature_toggles_identifier_unique', properties: ['identifier'] })\n@Index({ name: 'feature_toggles_category_idx', properties: ['category'] })\n@Index({ name: 'feature_toggles_name_idx', properties: ['name'] })\nexport class FeatureToggle {\n [OptionalProps]?: 'description' | 'category' | 'createdAt' | 'updatedAt' | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ type: 'text' })\n identifier!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ type: 'text', nullable: true })\n description?: string | null\n\n @Property({ type: 'text', nullable: true })\n category?: string | null\n\n @Property({ name: 'default_value', type: 'jsonb' })\n defaultValue!: JsonValue\n\n @Property({ name: 'type', type: 'text' })\n type!: FeatureToggleType\n\n\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n@Entity({ tableName: 'feature_toggle_overrides' })\n@Unique({\n name: 'feature_toggle_overrides_toggle_tenant_unique',\n properties: ['toggle', 'tenantId'],\n})\n@Index({ name: 'feature_toggle_overrides_tenant_idx', properties: ['tenantId'] })\n@Index({ name: 'feature_toggle_overrides_toggle_idx', properties: ['toggle'] })\nexport class FeatureToggleOverride {\n [OptionalProps]?: 'createdAt' | 'updatedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @ManyToOne(() => FeatureToggle, { fieldName: 'toggle_id' })\n toggle!: FeatureToggle\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'value', type: 'jsonb' })\n value!: JsonValue\n}\n"],
5
+ "mappings": ";;;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAAS,QAAQ,OAAO,WAAW,YAAY,UAAU,cAAc;AAWpE;AADI,IAAM,gBAAN,MAAoB;AAAA,EAApB;AA2BL,qBAAkB,oBAAI,KAAK;AAG3B,qBAAkB,oBAAI,KAAK;AAAA;AAI7B;AA9BE;AAAA,EADC,WAAW,EAAE,MAAM,QAAQ,YAAY,oBAAoB,CAAC;AAAA,GAHlD,cAIX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,GANf,cAOX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,GATf,cAUX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAZ/B,cAaX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,UAAU,KAAK,CAAC;AAAA,GAf/B,cAgBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,iBAAiB,MAAM,QAAQ,CAAC;AAAA,GAlBvC,cAmBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,QAAQ,MAAM,OAAO,CAAC;AAAA,GArB7B,cAsBX;AAKA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GA1B7D,cA2BX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GA7B7D,cA8BX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,KAAK,CAAC;AAAA,GAhCjD,cAiCX;AAjCW,gBAAN;AAAA,EAJN,OAAO,EAAE,WAAW,kBAAkB,CAAC;AAAA,EACvC,OAAO,EAAE,MAAM,qCAAqC,YAAY,CAAC,YAAY,EAAE,CAAC;AAAA,EAChF,MAAM,EAAE,MAAM,gCAAgC,YAAY,CAAC,UAAU,EAAE,CAAC;AAAA,EACxE,MAAM,EAAE,MAAM,4BAA4B,YAAY,CAAC,MAAM,EAAE,CAAC;AAAA,GACpD;AA4CV;AADI,IAAM,wBAAN,MAA4B;AAAA,EAA5B;AAaL,qBAAkB,oBAAI,KAAK;AAG3B,qBAAkB,oBAAI,KAAK;AAAA;AAI7B;AAhBE;AAAA,EADC,WAAW,EAAE,MAAM,QAAQ,YAAY,oBAAoB,CAAC;AAAA,GAHlD,sBAIX;AAGA;AAAA,EADC,UAAU,MAAM,eAAe,EAAE,WAAW,YAAY,CAAC;AAAA,GAN/C,sBAOX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,GATlC,sBAUX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GAZ7D,sBAaX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,cAAc,MAAM,MAAM,UAAU,MAAM,oBAAI,KAAK,GAAG,UAAU,MAAM,oBAAI,KAAK,EAAE,CAAC;AAAA,GAfzF,sBAgBX;AAGA;AAAA,EADC,SAAS,EAAE,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,GAlB/B,sBAmBX;AAnBW,wBAAN;AAAA,EAPN,OAAO,EAAE,WAAW,2BAA2B,CAAC;AAAA,EAChD,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,CAAC,UAAU,UAAU;AAAA,EACnC,CAAC;AAAA,EACA,MAAM,EAAE,MAAM,uCAAuC,YAAY,CAAC,UAAU,EAAE,CAAC;AAAA,EAC/E,MAAM,EAAE,MAAM,uCAAuC,YAAY,CAAC,QAAQ,EAAE,CAAC;AAAA,GACjE;",
6
6
  "names": []
7
7
  }
@@ -70,7 +70,8 @@ const overrideListResponseSchema = z.object({
70
70
  identifier: z.string(),
71
71
  name: z.string(),
72
72
  category: z.string(),
73
- isOverride: z.boolean()
73
+ isOverride: z.boolean(),
74
+ updatedAt: z.string().nullable().optional()
74
75
  });
75
76
  export {
76
77
  IDENTIFIER_PATTERN,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/feature_toggles/data/validators.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\n\n// Namespaced feature-flag identifiers use dots and dashes as separators\n// (e.g. the seeded `customers.interactions.legacy-adapters`), so the validator\n// accepts lowercase letters, digits, `_`, `.`, and `-` after the leading\n// lowercase letter (#2055 QA round-6).\nexport const IDENTIFIER_PATTERN = /^[a-z][a-z0-9_.-]*$/\n\nexport const IDENTIFIER_PATTERN_MESSAGE =\n 'identifier must start with a lowercase letter and contain only lowercase letters, numbers, dots, dashes, and underscores'\n\nexport const toggleTypeSchema = z.enum(['boolean', 'string', 'number', 'json'])\n\nexport const toggleCreateSchema = z.object({\n identifier: z.string().min(1).regex(\n IDENTIFIER_PATTERN,\n IDENTIFIER_PATTERN_MESSAGE,\n ),\n name: z.string().min(1),\n description: z.string().nullable().optional(),\n category: z.string().nullable().optional(),\n\n type: toggleTypeSchema,\n defaultValue: z.any().nullable().optional(),\n})\n\nexport const toggleCreateSchemaList = z.array(toggleCreateSchema)\n\nexport const toggleUpdateSchema = z\n .object({\n id: z.string().uuid(),\n })\n .merge(toggleCreateSchema.partial())\n\nexport const changeOverrideStateBaseSchema = z.object({\n toggleId: z.string().uuid(),\n isOverride: z.boolean(),\n overrideValue: z.any().optional(),\n})\n\n\nexport const processedChangeOverrideStateSchema = changeOverrideStateBaseSchema.extend({\n tenantId: z.string().uuid(),\n})\n\nexport const featureToggleOverrideResponseSchema = z.object({\n id: z.string().uuid(),\n value: z.any().optional(),\n tenantName: z.string(),\n tenantId: z.string().uuid(),\n toggleType: toggleTypeSchema,\n updatedAt: z.string().nullable().optional(),\n})\n\nexport type FeatureToggleOverrideResponse = z.infer<typeof featureToggleOverrideResponseSchema>\n\nexport const featureToggleOverrideWithValueSchema = featureToggleOverrideResponseSchema\nexport type FeatureToggleOverrideWithValue = FeatureToggleOverrideResponse\n\nexport const getToggleOverrideQuerySchema = z.object({\n toggleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n})\n\nexport const overrideListQuerySchema = z\n .object({\n category: z.string().optional(),\n name: z.string().optional(),\n identifier: z.string().optional(),\n sortField: z.enum(['identifier', 'name', 'category']).optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n page: z.coerce.number().min(1).optional().default(1),\n pageSize: z.coerce.number().min(1).max(100).optional().default(25),\n })\n .passthrough()\n\nexport const featureToggleSchema = z.object({\n id: z.string().uuid().optional(),\n identifier: z.string().min(1).regex(\n IDENTIFIER_PATTERN,\n IDENTIFIER_PATTERN_MESSAGE,\n ),\n name: z.string().min(1),\n description: z.string().nullable().optional(),\n category: z.string().nullable().optional(),\n\n type: z.enum(['boolean', 'string', 'number', 'json']),\n defaultValue: z.any().nullable().optional(),\n createdAt: z.string().optional(),\n updatedAt: z.string().optional(),\n})\n\n// Inherited rows (no per-tenant override) intentionally carry an empty-string\n// `id` sentinel instead of a UUID, so the row id must accept both a UUID (real\n// override rows) and the empty inherited sentinel. See lib/queries.ts.\nexport const overrideRowIdSchema = z.union([z.string().uuid(), z.literal('')])\n\nexport const overrideListResponseSchema = z.object({\n id: overrideRowIdSchema,\n toggleId: z.string().uuid(),\n tenantName: z.string(),\n tenantId: z.string().uuid(),\n identifier: z.string(),\n name: z.string(),\n category: z.string(),\n isOverride: z.boolean(),\n});\n\nexport type OverrideListResponse = z.infer<typeof overrideListResponseSchema>\n\nexport type ToggleCreateInput = z.infer<typeof toggleCreateSchema>\nexport type ToggleUpdateInput = z.infer<typeof toggleUpdateSchema>\nexport type ChangeOverrideStateBaseInput = z.infer<typeof changeOverrideStateBaseSchema>\nexport type ProcessedChangeOverrideStateInput = z.infer<typeof processedChangeOverrideStateSchema>\nexport type GetToggleOverrideQuery = z.infer<typeof getToggleOverrideQuerySchema>\nexport type GetOverridesQuery = z.infer<typeof overrideListQuerySchema>\nexport type FeatureToggle = z.infer<typeof featureToggleSchema>\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAMX,MAAM,qBAAqB;AAE3B,MAAM,6BACX;AAEK,MAAM,mBAAmB,EAAE,KAAK,CAAC,WAAW,UAAU,UAAU,MAAM,CAAC;AAEvE,MAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;AAAA,IAC5B;AAAA,IACA;AAAA,EACF;AAAA,EACA,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC5C,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAEzC,MAAM;AAAA,EACN,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;AAC5C,CAAC;AAEM,MAAM,yBAAyB,EAAE,MAAM,kBAAkB;AAEzD,MAAM,qBAAqB,EAC/B,OAAO;AAAA,EACN,IAAI,EAAE,OAAO,EAAE,KAAK;AACtB,CAAC,EACA,MAAM,mBAAmB,QAAQ,CAAC;AAE9B,MAAM,gCAAgC,EAAE,OAAO;AAAA,EACpD,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,YAAY,EAAE,QAAQ;AAAA,EACtB,eAAe,EAAE,IAAI,EAAE,SAAS;AAClC,CAAC;AAGM,MAAM,qCAAqC,8BAA8B,OAAO;AAAA,EACrF,UAAU,EAAE,OAAO,EAAE,KAAK;AAC5B,CAAC;AAEM,MAAM,sCAAsC,EAAE,OAAO;AAAA,EAC1D,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACxB,YAAY,EAAE,OAAO;AAAA,EACrB,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,YAAY;AAAA,EACZ,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAC5C,CAAC;AAIM,MAAM,uCAAuC;AAG7C,MAAM,+BAA+B,EAAE,OAAO;AAAA,EACnD,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AACvC,CAAC;AAEM,MAAM,0BAA0B,EACpC,OAAO;AAAA,EACN,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,WAAW,EAAE,KAAK,CAAC,cAAc,QAAQ,UAAU,CAAC,EAAE,SAAS;AAAA,EAC/D,SAAS,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS;AAAA,EAC1C,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EACnD,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE;AACnE,CAAC,EACA,YAAY;AAER,MAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC/B,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;AAAA,IAC5B;AAAA,IACA;AAAA,EACF;AAAA,EACA,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC5C,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAEzC,MAAM,EAAE,KAAK,CAAC,WAAW,UAAU,UAAU,MAAM,CAAC;AAAA,EACpD,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;AAAA,EAC1C,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,WAAW,EAAE,OAAO,EAAE,SAAS;AACjC,CAAC;AAKM,MAAM,sBAAsB,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;AAEtE,MAAM,6BAA6B,EAAE,OAAO;AAAA,EACjD,IAAI;AAAA,EACJ,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,YAAY,EAAE,OAAO;AAAA,EACrB,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,EAC1B,YAAY,EAAE,OAAO;AAAA,EACrB,MAAM,EAAE,OAAO;AAAA,EACf,UAAU,EAAE,OAAO;AAAA,EACnB,YAAY,EAAE,QAAQ;AACxB,CAAC;",
4
+ "sourcesContent": ["import { z } from 'zod'\n\n// Namespaced feature-flag identifiers use dots and dashes as separators\n// (e.g. the seeded `customers.interactions.legacy-adapters`), so the validator\n// accepts lowercase letters, digits, `_`, `.`, and `-` after the leading\n// lowercase letter (#2055 QA round-6).\nexport const IDENTIFIER_PATTERN = /^[a-z][a-z0-9_.-]*$/\n\nexport const IDENTIFIER_PATTERN_MESSAGE =\n 'identifier must start with a lowercase letter and contain only lowercase letters, numbers, dots, dashes, and underscores'\n\nexport const toggleTypeSchema = z.enum(['boolean', 'string', 'number', 'json'])\n\nexport const toggleCreateSchema = z.object({\n identifier: z.string().min(1).regex(\n IDENTIFIER_PATTERN,\n IDENTIFIER_PATTERN_MESSAGE,\n ),\n name: z.string().min(1),\n description: z.string().nullable().optional(),\n category: z.string().nullable().optional(),\n\n type: toggleTypeSchema,\n defaultValue: z.any().nullable().optional(),\n})\n\nexport const toggleCreateSchemaList = z.array(toggleCreateSchema)\n\nexport const toggleUpdateSchema = z\n .object({\n id: z.string().uuid(),\n })\n .merge(toggleCreateSchema.partial())\n\nexport const changeOverrideStateBaseSchema = z.object({\n toggleId: z.string().uuid(),\n isOverride: z.boolean(),\n overrideValue: z.any().optional(),\n})\n\n\nexport const processedChangeOverrideStateSchema = changeOverrideStateBaseSchema.extend({\n tenantId: z.string().uuid(),\n})\n\nexport const featureToggleOverrideResponseSchema = z.object({\n id: z.string().uuid(),\n value: z.any().optional(),\n tenantName: z.string(),\n tenantId: z.string().uuid(),\n toggleType: toggleTypeSchema,\n updatedAt: z.string().nullable().optional(),\n})\n\nexport type FeatureToggleOverrideResponse = z.infer<typeof featureToggleOverrideResponseSchema>\n\nexport const featureToggleOverrideWithValueSchema = featureToggleOverrideResponseSchema\nexport type FeatureToggleOverrideWithValue = FeatureToggleOverrideResponse\n\nexport const getToggleOverrideQuerySchema = z.object({\n toggleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n})\n\nexport const overrideListQuerySchema = z\n .object({\n category: z.string().optional(),\n name: z.string().optional(),\n identifier: z.string().optional(),\n sortField: z.enum(['identifier', 'name', 'category']).optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n page: z.coerce.number().min(1).optional().default(1),\n pageSize: z.coerce.number().min(1).max(100).optional().default(25),\n })\n .passthrough()\n\nexport const featureToggleSchema = z.object({\n id: z.string().uuid().optional(),\n identifier: z.string().min(1).regex(\n IDENTIFIER_PATTERN,\n IDENTIFIER_PATTERN_MESSAGE,\n ),\n name: z.string().min(1),\n description: z.string().nullable().optional(),\n category: z.string().nullable().optional(),\n\n type: z.enum(['boolean', 'string', 'number', 'json']),\n defaultValue: z.any().nullable().optional(),\n createdAt: z.string().optional(),\n updatedAt: z.string().optional(),\n})\n\n// Inherited rows (no per-tenant override) intentionally carry an empty-string\n// `id` sentinel instead of a UUID, so the row id must accept both a UUID (real\n// override rows) and the empty inherited sentinel. See lib/queries.ts.\nexport const overrideRowIdSchema = z.union([z.string().uuid(), z.literal('')])\n\nexport const overrideListResponseSchema = z.object({\n id: overrideRowIdSchema,\n toggleId: z.string().uuid(),\n tenantName: z.string(),\n tenantId: z.string().uuid(),\n identifier: z.string(),\n name: z.string(),\n category: z.string(),\n isOverride: z.boolean(),\n updatedAt: z.string().nullable().optional(),\n});\n\nexport type OverrideListResponse = z.infer<typeof overrideListResponseSchema>\n\nexport type ToggleCreateInput = z.infer<typeof toggleCreateSchema>\nexport type ToggleUpdateInput = z.infer<typeof toggleUpdateSchema>\nexport type ChangeOverrideStateBaseInput = z.infer<typeof changeOverrideStateBaseSchema>\nexport type ProcessedChangeOverrideStateInput = z.infer<typeof processedChangeOverrideStateSchema>\nexport type GetToggleOverrideQuery = z.infer<typeof getToggleOverrideQuerySchema>\nexport type GetOverridesQuery = z.infer<typeof overrideListQuerySchema>\nexport type FeatureToggle = z.infer<typeof featureToggleSchema>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -46,7 +46,8 @@ async function getOverrides(em, tenant, query) {
46
46
  identifier: toggle.identifier,
47
47
  name: toggle.name,
48
48
  category: toggle.category ?? "",
49
- isOverride: Boolean(override)
49
+ isOverride: Boolean(override),
50
+ updatedAt: override?.updatedAt instanceof Date ? override.updatedAt.toISOString() : null
50
51
  };
51
52
  });
52
53
  const totalPages = Math.ceil(totalItems / pageSize);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/feature_toggles/lib/queries.ts"],
4
- "sourcesContent": ["import { EntityManager, FilterQuery, QueryOrder } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { OverrideListResponse } from '../data/validators'\n\nexport type FeatureToggleOverrideResponse = {\n id: string\n toggleId: string\n overrideState: 'enabled' | 'disabled' | 'inherit'\n tenantName: string\n tenantId: string\n identifier: string\n name: string\n category: string\n defaultState: boolean\n}\n\nexport type GetOverridesQuery = {\n category?: string\n name?: string\n identifier?: string\n defaultState?: 'enabled' | 'disabled'\n overrideState?: 'enabled' | 'disabled' | 'inherit'\n sortField?: 'identifier' | 'name' | 'category' | 'defaultState' | 'overrideState'\n sortDir?: 'asc' | 'desc'\n page?: number\n pageSize?: number\n}\n\nconst SORTABLE_FIELDS = new Set<keyof FeatureToggle>(['identifier', 'name', 'category'])\n\ntype GetOverridesResult = {\n items: OverrideListResponse[]\n total: number\n totalPages: number\n page: number\n pageSize: number\n raw: {\n toggles: FeatureToggle[]\n overrides: FeatureToggleOverride[]\n }\n}\n\nexport async function getOverrides(\n em: EntityManager,\n tenant: Tenant,\n query: GetOverridesQuery\n): Promise<GetOverridesResult> {\n const filters: FilterQuery<FeatureToggle> = { deletedAt: null }\n if (query.category) {\n filters.category = { $ilike: `%${escapeLikePattern(query.category)}%` }\n }\n if (query.name) {\n filters.name = { $ilike: `%${escapeLikePattern(query.name)}%` }\n }\n if (query.identifier) {\n filters.identifier = { $ilike: `%${escapeLikePattern(query.identifier)}%` }\n }\n\n const page = query.page ?? 1\n const pageSize = query.pageSize ?? 25\n const offset = (page - 1) * pageSize\n\n const direction = query.sortDir === 'desc' ? QueryOrder.DESC : QueryOrder.ASC\n const orderBy: Partial<Record<keyof FeatureToggle, QueryOrder>> = {}\n if (query.sortField && SORTABLE_FIELDS.has(query.sortField as keyof FeatureToggle)) {\n orderBy[query.sortField as keyof FeatureToggle] = direction\n }\n orderBy.id = QueryOrder.ASC\n\n const totalItems = await em.count(FeatureToggle, filters)\n\n const globalToggles = await em.find(FeatureToggle, filters, {\n orderBy,\n limit: pageSize,\n offset,\n })\n\n const overrides = globalToggles.length\n ? await em.find(FeatureToggleOverride, {\n tenantId: tenant.id,\n toggle: { id: { $in: globalToggles.map((toggle) => toggle.id) } },\n })\n : []\n\n const overridesByToggleId = new Map<string, FeatureToggleOverride>()\n for (const override of overrides) {\n overridesByToggleId.set(override.toggle.id, override)\n }\n\n const items: OverrideListResponse[] = globalToggles.map((toggle) => {\n const override = overridesByToggleId.get(toggle.id)\n return {\n id: override?.id ?? '',\n toggleId: toggle.id,\n tenantName: tenant.name,\n tenantId: tenant.id,\n identifier: toggle.identifier,\n name: toggle.name,\n category: toggle.category ?? '',\n isOverride: Boolean(override),\n }\n })\n\n const totalPages = Math.ceil(totalItems / pageSize)\n\n return {\n items,\n total: totalItems,\n totalPages,\n page,\n pageSize,\n raw: {\n toggles: globalToggles,\n overrides,\n },\n }\n}\n"],
5
- "mappings": "AAAA,SAAqC,kBAAkB;AACvD,SAAS,eAAe,6BAA6B;AAErD,SAAS,yBAAyB;AA2BlC,MAAM,kBAAkB,oBAAI,IAAyB,CAAC,cAAc,QAAQ,UAAU,CAAC;AAcvF,eAAsB,aAClB,IACA,QACA,OAC2B;AAC3B,QAAM,UAAsC,EAAE,WAAW,KAAK;AAC9D,MAAI,MAAM,UAAU;AAChB,YAAQ,WAAW,EAAE,QAAQ,IAAI,kBAAkB,MAAM,QAAQ,CAAC,IAAI;AAAA,EAC1E;AACA,MAAI,MAAM,MAAM;AACZ,YAAQ,OAAO,EAAE,QAAQ,IAAI,kBAAkB,MAAM,IAAI,CAAC,IAAI;AAAA,EAClE;AACA,MAAI,MAAM,YAAY;AAClB,YAAQ,aAAa,EAAE,QAAQ,IAAI,kBAAkB,MAAM,UAAU,CAAC,IAAI;AAAA,EAC9E;AAEA,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,UAAU,OAAO,KAAK;AAE5B,QAAM,YAAY,MAAM,YAAY,SAAS,WAAW,OAAO,WAAW;AAC1E,QAAM,UAA4D,CAAC;AACnE,MAAI,MAAM,aAAa,gBAAgB,IAAI,MAAM,SAAgC,GAAG;AAChF,YAAQ,MAAM,SAAgC,IAAI;AAAA,EACtD;AACA,UAAQ,KAAK,WAAW;AAExB,QAAM,aAAa,MAAM,GAAG,MAAM,eAAe,OAAO;AAExD,QAAM,gBAAgB,MAAM,GAAG,KAAK,eAAe,SAAS;AAAA,IACxD;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACJ,CAAC;AAED,QAAM,YAAY,cAAc,SAC1B,MAAM,GAAG,KAAK,uBAAuB;AAAA,IACjC,UAAU,OAAO;AAAA,IACjB,QAAQ,EAAE,IAAI,EAAE,KAAK,cAAc,IAAI,CAAC,WAAW,OAAO,EAAE,EAAE,EAAE;AAAA,EACpE,CAAC,IACD,CAAC;AAEP,QAAM,sBAAsB,oBAAI,IAAmC;AACnE,aAAW,YAAY,WAAW;AAC9B,wBAAoB,IAAI,SAAS,OAAO,IAAI,QAAQ;AAAA,EACxD;AAEA,QAAM,QAAgC,cAAc,IAAI,CAAC,WAAW;AAChE,UAAM,WAAW,oBAAoB,IAAI,OAAO,EAAE;AAClD,WAAO;AAAA,MACH,IAAI,UAAU,MAAM;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,MAAM,OAAO;AAAA,MACb,UAAU,OAAO,YAAY;AAAA,MAC7B,YAAY,QAAQ,QAAQ;AAAA,IAChC;AAAA,EACJ,CAAC;AAED,QAAM,aAAa,KAAK,KAAK,aAAa,QAAQ;AAElD,SAAO;AAAA,IACH;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK;AAAA,MACD,SAAS;AAAA,MACT;AAAA,IACJ;AAAA,EACJ;AACJ;",
4
+ "sourcesContent": ["import { EntityManager, FilterQuery, QueryOrder } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { OverrideListResponse } from '../data/validators'\n\nexport type FeatureToggleOverrideResponse = {\n id: string\n toggleId: string\n overrideState: 'enabled' | 'disabled' | 'inherit'\n tenantName: string\n tenantId: string\n identifier: string\n name: string\n category: string\n defaultState: boolean\n}\n\nexport type GetOverridesQuery = {\n category?: string\n name?: string\n identifier?: string\n defaultState?: 'enabled' | 'disabled'\n overrideState?: 'enabled' | 'disabled' | 'inherit'\n sortField?: 'identifier' | 'name' | 'category' | 'defaultState' | 'overrideState'\n sortDir?: 'asc' | 'desc'\n page?: number\n pageSize?: number\n}\n\nconst SORTABLE_FIELDS = new Set<keyof FeatureToggle>(['identifier', 'name', 'category'])\n\ntype GetOverridesResult = {\n items: OverrideListResponse[]\n total: number\n totalPages: number\n page: number\n pageSize: number\n raw: {\n toggles: FeatureToggle[]\n overrides: FeatureToggleOverride[]\n }\n}\n\nexport async function getOverrides(\n em: EntityManager,\n tenant: Tenant,\n query: GetOverridesQuery\n): Promise<GetOverridesResult> {\n const filters: FilterQuery<FeatureToggle> = { deletedAt: null }\n if (query.category) {\n filters.category = { $ilike: `%${escapeLikePattern(query.category)}%` }\n }\n if (query.name) {\n filters.name = { $ilike: `%${escapeLikePattern(query.name)}%` }\n }\n if (query.identifier) {\n filters.identifier = { $ilike: `%${escapeLikePattern(query.identifier)}%` }\n }\n\n const page = query.page ?? 1\n const pageSize = query.pageSize ?? 25\n const offset = (page - 1) * pageSize\n\n const direction = query.sortDir === 'desc' ? QueryOrder.DESC : QueryOrder.ASC\n const orderBy: Partial<Record<keyof FeatureToggle, QueryOrder>> = {}\n if (query.sortField && SORTABLE_FIELDS.has(query.sortField as keyof FeatureToggle)) {\n orderBy[query.sortField as keyof FeatureToggle] = direction\n }\n orderBy.id = QueryOrder.ASC\n\n const totalItems = await em.count(FeatureToggle, filters)\n\n const globalToggles = await em.find(FeatureToggle, filters, {\n orderBy,\n limit: pageSize,\n offset,\n })\n\n const overrides = globalToggles.length\n ? await em.find(FeatureToggleOverride, {\n tenantId: tenant.id,\n toggle: { id: { $in: globalToggles.map((toggle) => toggle.id) } },\n })\n : []\n\n const overridesByToggleId = new Map<string, FeatureToggleOverride>()\n for (const override of overrides) {\n overridesByToggleId.set(override.toggle.id, override)\n }\n\n const items: OverrideListResponse[] = globalToggles.map((toggle) => {\n const override = overridesByToggleId.get(toggle.id)\n return {\n id: override?.id ?? '',\n toggleId: toggle.id,\n tenantName: tenant.name,\n tenantId: tenant.id,\n identifier: toggle.identifier,\n name: toggle.name,\n category: toggle.category ?? '',\n isOverride: Boolean(override),\n updatedAt: override?.updatedAt instanceof Date ? override.updatedAt.toISOString() : null,\n }\n })\n\n const totalPages = Math.ceil(totalItems / pageSize)\n\n return {\n items,\n total: totalItems,\n totalPages,\n page,\n pageSize,\n raw: {\n toggles: globalToggles,\n overrides,\n },\n }\n}\n"],
5
+ "mappings": "AAAA,SAAqC,kBAAkB;AACvD,SAAS,eAAe,6BAA6B;AAErD,SAAS,yBAAyB;AA2BlC,MAAM,kBAAkB,oBAAI,IAAyB,CAAC,cAAc,QAAQ,UAAU,CAAC;AAcvF,eAAsB,aAClB,IACA,QACA,OAC2B;AAC3B,QAAM,UAAsC,EAAE,WAAW,KAAK;AAC9D,MAAI,MAAM,UAAU;AAChB,YAAQ,WAAW,EAAE,QAAQ,IAAI,kBAAkB,MAAM,QAAQ,CAAC,IAAI;AAAA,EAC1E;AACA,MAAI,MAAM,MAAM;AACZ,YAAQ,OAAO,EAAE,QAAQ,IAAI,kBAAkB,MAAM,IAAI,CAAC,IAAI;AAAA,EAClE;AACA,MAAI,MAAM,YAAY;AAClB,YAAQ,aAAa,EAAE,QAAQ,IAAI,kBAAkB,MAAM,UAAU,CAAC,IAAI;AAAA,EAC9E;AAEA,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,UAAU,OAAO,KAAK;AAE5B,QAAM,YAAY,MAAM,YAAY,SAAS,WAAW,OAAO,WAAW;AAC1E,QAAM,UAA4D,CAAC;AACnE,MAAI,MAAM,aAAa,gBAAgB,IAAI,MAAM,SAAgC,GAAG;AAChF,YAAQ,MAAM,SAAgC,IAAI;AAAA,EACtD;AACA,UAAQ,KAAK,WAAW;AAExB,QAAM,aAAa,MAAM,GAAG,MAAM,eAAe,OAAO;AAExD,QAAM,gBAAgB,MAAM,GAAG,KAAK,eAAe,SAAS;AAAA,IACxD;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACJ,CAAC;AAED,QAAM,YAAY,cAAc,SAC1B,MAAM,GAAG,KAAK,uBAAuB;AAAA,IACjC,UAAU,OAAO;AAAA,IACjB,QAAQ,EAAE,IAAI,EAAE,KAAK,cAAc,IAAI,CAAC,WAAW,OAAO,EAAE,EAAE,EAAE;AAAA,EACpE,CAAC,IACD,CAAC;AAEP,QAAM,sBAAsB,oBAAI,IAAmC;AACnE,aAAW,YAAY,WAAW;AAC9B,wBAAoB,IAAI,SAAS,OAAO,IAAI,QAAQ;AAAA,EACxD;AAEA,QAAM,QAAgC,cAAc,IAAI,CAAC,WAAW;AAChE,UAAM,WAAW,oBAAoB,IAAI,OAAO,EAAE;AAClD,WAAO;AAAA,MACH,IAAI,UAAU,MAAM;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,MAAM,OAAO;AAAA,MACb,UAAU,OAAO,YAAY;AAAA,MAC7B,YAAY,QAAQ,QAAQ;AAAA,MAC5B,WAAW,UAAU,qBAAqB,OAAO,SAAS,UAAU,YAAY,IAAI;AAAA,IACxF;AAAA,EACJ,CAAC;AAED,QAAM,aAAa,KAAK,KAAK,aAAa,QAAQ;AAElD,SAAO;AAAA,IACH;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK;AAAA,MACD,SAAS;AAAA,MACT;AAAA,IACJ;AAAA,EACJ;AACJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { runWithCacheTenant } from "@open-mercato/cache";
3
3
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
- import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
4
+ import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
5
5
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
6
6
  import { InboxSettings } from "../../data/entities.js";
7
7
  import { updateSettingsSchema } from "../../data/validators.js";
@@ -90,7 +90,7 @@ async function PATCH(req) {
90
90
  return NextResponse.json({ error: "Settings not found" }, { status: 404 });
91
91
  }
92
92
  try {
93
- enforceCommandOptimisticLock({
93
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
94
94
  resourceKind: "inbox_ops.settings",
95
95
  resourceId: settings.id,
96
96
  current: settings.updatedAt ?? null,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/inbox_ops/api/settings/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { InboxSettings } from '../../data/entities'\nimport { updateSettingsSchema } from '../../data/validators'\nimport { resolveRequestContext, handleRouteError } from '../routeHelpers'\nimport {\n resolveCache,\n createSettingsCacheKey,\n createSettingsCacheTag,\n invalidateSettingsCache,\n SETTINGS_CACHE_TTL_MS,\n} from '../../lib/cache'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['inbox_ops.settings.manage'] },\n PATCH: { requireAuth: true, requireFeatures: ['inbox_ops.settings.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveRequestContext(req)\n const cache = resolveCache(ctx.container)\n\n if (cache) {\n const cacheKey = createSettingsCacheKey(ctx.tenantId)\n const cached = await runWithCacheTenant(ctx.tenantId, () => cache.get(cacheKey))\n if (cached) {\n return NextResponse.json(cached)\n }\n }\n\n const settings = await findOneWithDecryption(\n ctx.em,\n InboxSettings,\n {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n undefined,\n ctx.scope,\n )\n\n const responseBody = {\n settings: settings ? {\n id: settings.id,\n inboxAddress: settings.inboxAddress,\n isActive: settings.isActive,\n workingLanguage: settings.workingLanguage,\n // Surface only whether a per-tenant secret exists \u2014 never the value.\n webhookSecretSet: Boolean(settings.webhookSecret),\n updatedAt: settings.updatedAt instanceof Date ? settings.updatedAt.toISOString() : (settings.updatedAt ?? null),\n } : null,\n }\n\n if (cache) {\n const cacheKey = createSettingsCacheKey(ctx.tenantId)\n const tag = createSettingsCacheTag(ctx.tenantId)\n try {\n await runWithCacheTenant(ctx.tenantId, () =>\n cache.set(cacheKey, responseBody, { ttl: SETTINGS_CACHE_TTL_MS, tags: [tag] }),\n )\n } catch (err) {\n console.warn('[inbox_ops:settings] Failed to set cache', err)\n }\n }\n\n return NextResponse.json(responseBody)\n } catch (err) {\n return handleRouteError(err, 'load settings')\n }\n}\n\nexport async function PATCH(req: Request) {\n try {\n const ctx = await resolveRequestContext(req)\n\n const body = await req.json()\n const parsed = updateSettingsSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid request', details: parsed.error.issues }, { status: 400 })\n }\n\n const settings = await findOneWithDecryption(\n ctx.em,\n InboxSettings,\n {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n undefined,\n ctx.scope,\n )\n\n if (!settings) {\n return NextResponse.json({ error: 'Settings not found' }, { status: 404 })\n }\n\n // Optimistic lock: refuse a stale overwrite when two tabs edit the same inbox\n // settings record. Strictly additive \u2014 a no-op without the expected-version header.\n try {\n enforceCommandOptimisticLock({\n resourceKind: 'inbox_ops.settings',\n resourceId: settings.id,\n current: settings.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n if (parsed.data.workingLanguage !== undefined) {\n settings.workingLanguage = parsed.data.workingLanguage\n }\n if (parsed.data.isActive !== undefined) {\n settings.isActive = parsed.data.isActive\n }\n if (parsed.data.webhookSecret !== undefined) {\n // Empty/null clears the per-tenant secret (reverts to the global key).\n settings.webhookSecret = parsed.data.webhookSecret ? parsed.data.webhookSecret : null\n }\n\n await ctx.em.flush()\n\n const cache = resolveCache(ctx.container)\n await runWithCacheTenant(ctx.tenantId, () => invalidateSettingsCache(cache, ctx.tenantId))\n\n return NextResponse.json({\n ok: true,\n settings: {\n id: settings.id,\n inboxAddress: settings.inboxAddress,\n isActive: settings.isActive,\n workingLanguage: settings.workingLanguage,\n webhookSecretSet: Boolean(settings.webhookSecret),\n updatedAt: settings.updatedAt instanceof Date ? settings.updatedAt.toISOString() : (settings.updatedAt ?? null),\n },\n })\n } catch (err) {\n return handleRouteError(err, 'update settings')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Settings',\n methods: {\n GET: {\n summary: 'Get tenant inbox configuration',\n description: 'Returns the forwarding address and configuration for this tenant',\n responses: [\n { status: 200, description: 'Inbox settings' },\n ],\n },\n PATCH: {\n summary: 'Update tenant inbox configuration',\n description: 'Updates working language and/or active status',\n responses: [\n { status: 200, description: 'Updated settings' },\n { status: 404, description: 'Settings not found' },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { InboxSettings } from '../../data/entities'\nimport { updateSettingsSchema } from '../../data/validators'\nimport { resolveRequestContext, handleRouteError } from '../routeHelpers'\nimport {\n resolveCache,\n createSettingsCacheKey,\n createSettingsCacheTag,\n invalidateSettingsCache,\n SETTINGS_CACHE_TTL_MS,\n} from '../../lib/cache'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['inbox_ops.settings.manage'] },\n PATCH: { requireAuth: true, requireFeatures: ['inbox_ops.settings.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveRequestContext(req)\n const cache = resolveCache(ctx.container)\n\n if (cache) {\n const cacheKey = createSettingsCacheKey(ctx.tenantId)\n const cached = await runWithCacheTenant(ctx.tenantId, () => cache.get(cacheKey))\n if (cached) {\n return NextResponse.json(cached)\n }\n }\n\n const settings = await findOneWithDecryption(\n ctx.em,\n InboxSettings,\n {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n undefined,\n ctx.scope,\n )\n\n const responseBody = {\n settings: settings ? {\n id: settings.id,\n inboxAddress: settings.inboxAddress,\n isActive: settings.isActive,\n workingLanguage: settings.workingLanguage,\n // Surface only whether a per-tenant secret exists \u2014 never the value.\n webhookSecretSet: Boolean(settings.webhookSecret),\n updatedAt: settings.updatedAt instanceof Date ? settings.updatedAt.toISOString() : (settings.updatedAt ?? null),\n } : null,\n }\n\n if (cache) {\n const cacheKey = createSettingsCacheKey(ctx.tenantId)\n const tag = createSettingsCacheTag(ctx.tenantId)\n try {\n await runWithCacheTenant(ctx.tenantId, () =>\n cache.set(cacheKey, responseBody, { ttl: SETTINGS_CACHE_TTL_MS, tags: [tag] }),\n )\n } catch (err) {\n console.warn('[inbox_ops:settings] Failed to set cache', err)\n }\n }\n\n return NextResponse.json(responseBody)\n } catch (err) {\n return handleRouteError(err, 'load settings')\n }\n}\n\nexport async function PATCH(req: Request) {\n try {\n const ctx = await resolveRequestContext(req)\n\n const body = await req.json()\n const parsed = updateSettingsSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid request', details: parsed.error.issues }, { status: 400 })\n }\n\n const settings = await findOneWithDecryption(\n ctx.em,\n InboxSettings,\n {\n organizationId: ctx.organizationId,\n tenantId: ctx.tenantId,\n deletedAt: null,\n },\n undefined,\n ctx.scope,\n )\n\n if (!settings) {\n return NextResponse.json({ error: 'Settings not found' }, { status: 404 })\n }\n\n // Optimistic lock: refuse a stale overwrite when two tabs edit the same inbox\n // settings record. Strictly additive \u2014 a no-op without the expected-version header.\n try {\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'inbox_ops.settings',\n resourceId: settings.id,\n current: settings.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n if (parsed.data.workingLanguage !== undefined) {\n settings.workingLanguage = parsed.data.workingLanguage\n }\n if (parsed.data.isActive !== undefined) {\n settings.isActive = parsed.data.isActive\n }\n if (parsed.data.webhookSecret !== undefined) {\n // Empty/null clears the per-tenant secret (reverts to the global key).\n settings.webhookSecret = parsed.data.webhookSecret ? parsed.data.webhookSecret : null\n }\n\n await ctx.em.flush()\n\n const cache = resolveCache(ctx.container)\n await runWithCacheTenant(ctx.tenantId, () => invalidateSettingsCache(cache, ctx.tenantId))\n\n return NextResponse.json({\n ok: true,\n settings: {\n id: settings.id,\n inboxAddress: settings.inboxAddress,\n isActive: settings.isActive,\n workingLanguage: settings.workingLanguage,\n webhookSecretSet: Boolean(settings.webhookSecret),\n updatedAt: settings.updatedAt instanceof Date ? settings.updatedAt.toISOString() : (settings.updatedAt ?? null),\n },\n })\n } catch (err) {\n return handleRouteError(err, 'update settings')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Settings',\n methods: {\n GET: {\n summary: 'Get tenant inbox configuration',\n description: 'Returns the forwarding address and configuration for this tenant',\n responses: [\n { status: 200, description: 'Inbox settings' },\n ],\n },\n PATCH: {\n summary: 'Update tenant inbox configuration',\n description: 'Updates working language and/or active status',\n responses: [\n { status: 200, description: 'Updated settings' },\n { status: 404, description: 'Settings not found' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,20 @@
1
1
  import { sql } from "kysely";
2
+ import { runWithCacheTenant } from "@open-mercato/cache";
3
+ import {
4
+ buildCollectionTags,
5
+ isCrudCacheEnabled,
6
+ resolveCrudCache
7
+ } from "@open-mercato/shared/lib/crud/cache";
2
8
  import { resolveMessageContext } from "../../lib/routeHelpers.js";
3
9
  import { unreadCountResponseSchema } from "../openapi.js";
4
10
  const metadata = {
5
11
  GET: { requireAuth: true }
6
12
  };
13
+ const UNREAD_COUNT_RESOURCE = "messages.message";
14
+ const UNREAD_COUNT_TTL_MS = 1e4;
15
+ function buildUnreadCountCacheKey(params) {
16
+ return `messages:unread-count:u=${params.userId}:org=${params.orgId ?? "null"}`;
17
+ }
7
18
  function getDb(em) {
8
19
  return em.getKysely();
9
20
  }
@@ -11,6 +22,15 @@ async function GET(req) {
11
22
  const { ctx, scope } = await resolveMessageContext(req);
12
23
  const em = ctx.container.resolve("em");
13
24
  const db = getDb(em);
25
+ const orgId = scope.organizationId ?? null;
26
+ const cache = isCrudCacheEnabled() ? resolveCrudCache(ctx.container) : null;
27
+ const cacheKey = cache ? buildUnreadCountCacheKey({ userId: scope.userId, orgId }) : null;
28
+ if (cache && cacheKey) {
29
+ const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey));
30
+ if (typeof cached === "number") {
31
+ return Response.json({ unreadCount: cached });
32
+ }
33
+ }
14
34
  let query = db.selectFrom("message_recipients as r").innerJoin("messages as m", "m.id", "r.message_id").where("r.recipient_user_id", "=", scope.userId).where("r.status", "=", "unread").where("r.deleted_at", "is", null).where("r.archived_at", "is", null).where("m.tenant_id", "=", scope.tenantId).where("m.deleted_at", "is", null);
15
35
  if (scope.organizationId) {
16
36
  query = query.where("m.organization_id", "=", scope.organizationId);
@@ -19,6 +39,18 @@ async function GET(req) {
19
39
  }
20
40
  const row = await query.select(sql`count(*)`.as("count")).executeTakeFirst();
21
41
  const count = Number(row?.count ?? 0);
42
+ if (cache && cacheKey) {
43
+ try {
44
+ await runWithCacheTenant(
45
+ scope.tenantId,
46
+ () => cache.set(cacheKey, count, {
47
+ ttl: UNREAD_COUNT_TTL_MS,
48
+ tags: buildCollectionTags(UNREAD_COUNT_RESOURCE, scope.tenantId, [orgId])
49
+ })
50
+ );
51
+ } catch {
52
+ }
53
+ }
22
54
  return Response.json({ unreadCount: count });
23
55
  }
24
56
  const openApi = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/messages/api/unread-count/route.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { resolveMessageContext } from '../../lib/routeHelpers'\nimport { unreadCountResponseSchema } from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nfunction getDb(em: EntityManager): Kysely<any> {\n return em.getKysely<any>()\n}\n\nexport async function GET(req: Request) {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = ctx.container.resolve('em') as EntityManager\n const db = getDb(em) as any\n\n let query = db\n .selectFrom('message_recipients as r')\n .innerJoin('messages as m', 'm.id', 'r.message_id')\n .where('r.recipient_user_id', '=', scope.userId)\n .where('r.status', '=', 'unread')\n .where('r.deleted_at', 'is', null)\n .where('r.archived_at', 'is', null)\n .where('m.tenant_id', '=', scope.tenantId)\n .where('m.deleted_at', 'is', null)\n\n if (scope.organizationId) {\n query = query.where('m.organization_id', '=', scope.organizationId)\n } else {\n query = query.where('m.organization_id', 'is', null)\n }\n\n const row = await query\n .select(sql<number>`count(*)`.as('count'))\n .executeTakeFirst() as { count: string | number } | undefined\n const count = Number(row?.count ?? 0)\n\n return Response.json({ unreadCount: count })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n GET: {\n summary: 'Get unread message count',\n responses: [\n {\n status: 200,\n description: 'Unread count',\n schema: unreadCountResponseSchema,\n },\n ],\n },\n },\n}\n"],
5
- "mappings": "AACA,SAAsB,WAAW;AAEjC,SAAS,6BAA6B;AACtC,SAAS,iCAAiC;AAEnC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,SAAS,MAAM,IAAgC;AAC7C,SAAO,GAAG,UAAe;AAC3B;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,EAAE,KAAK,MAAM,IAAI,MAAM,sBAAsB,GAAG;AACtD,QAAM,KAAK,IAAI,UAAU,QAAQ,IAAI;AACrC,QAAM,KAAK,MAAM,EAAE;AAEnB,MAAI,QAAQ,GACT,WAAW,yBAAyB,EACpC,UAAU,iBAAiB,QAAQ,cAAc,EACjD,MAAM,uBAAuB,KAAK,MAAM,MAAM,EAC9C,MAAM,YAAY,KAAK,QAAQ,EAC/B,MAAM,gBAAgB,MAAM,IAAI,EAChC,MAAM,iBAAiB,MAAM,IAAI,EACjC,MAAM,eAAe,KAAK,MAAM,QAAQ,EACxC,MAAM,gBAAgB,MAAM,IAAI;AAEnC,MAAI,MAAM,gBAAgB;AACxB,YAAQ,MAAM,MAAM,qBAAqB,KAAK,MAAM,cAAc;AAAA,EACpE,OAAO;AACL,YAAQ,MAAM,MAAM,qBAAqB,MAAM,IAAI;AAAA,EACrD;AAEA,QAAM,MAAM,MAAM,MACf,OAAO,cAAsB,GAAG,OAAO,CAAC,EACxC,iBAAiB;AACpB,QAAM,QAAQ,OAAO,KAAK,SAAS,CAAC;AAEpC,SAAO,SAAS,KAAK,EAAE,aAAa,MAAM,CAAC;AAC7C;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n buildCollectionTags,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'\nimport { resolveMessageContext } from '../../lib/routeHelpers'\nimport { unreadCountResponseSchema } from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nconst UNREAD_COUNT_RESOURCE = 'messages.message'\nconst UNREAD_COUNT_TTL_MS = 10_000\n\nfunction buildUnreadCountCacheKey(params: {\n userId: string\n orgId: string | null\n}): string {\n return `messages:unread-count:u=${params.userId}:org=${params.orgId ?? 'null'}`\n}\n\nfunction getDb(em: EntityManager): Kysely<any> {\n return em.getKysely<any>()\n}\n\nexport async function GET(req: Request) {\n const { ctx, scope } = await resolveMessageContext(req)\n const em = ctx.container.resolve('em') as EntityManager\n const db = getDb(em) as any\n\n const orgId = scope.organizationId ?? null\n const cache = isCrudCacheEnabled() ? resolveCrudCache(ctx.container) : null\n const cacheKey = cache\n ? buildUnreadCountCacheKey({ userId: scope.userId, orgId })\n : null\n\n if (cache && cacheKey) {\n const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey))\n if (typeof cached === 'number') {\n return Response.json({ unreadCount: cached })\n }\n }\n\n let query = db\n .selectFrom('message_recipients as r')\n .innerJoin('messages as m', 'm.id', 'r.message_id')\n .where('r.recipient_user_id', '=', scope.userId)\n .where('r.status', '=', 'unread')\n .where('r.deleted_at', 'is', null)\n .where('r.archived_at', 'is', null)\n .where('m.tenant_id', '=', scope.tenantId)\n .where('m.deleted_at', 'is', null)\n\n if (scope.organizationId) {\n query = query.where('m.organization_id', '=', scope.organizationId)\n } else {\n query = query.where('m.organization_id', 'is', null)\n }\n\n const row = await query\n .select(sql<number>`count(*)`.as('count'))\n .executeTakeFirst() as { count: string | number } | undefined\n const count = Number(row?.count ?? 0)\n\n if (cache && cacheKey) {\n try {\n await runWithCacheTenant(scope.tenantId, () =>\n cache.set(cacheKey, count, {\n ttl: UNREAD_COUNT_TTL_MS,\n tags: buildCollectionTags(UNREAD_COUNT_RESOURCE, scope.tenantId, [orgId]),\n }),\n )\n } catch {}\n }\n\n return Response.json({ unreadCount: count })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Messages',\n methods: {\n GET: {\n summary: 'Get unread message count',\n responses: [\n {\n status: 200,\n description: 'Unread count',\n schema: unreadCountResponseSchema,\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AACA,SAAsB,WAAW;AACjC,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,6BAA6B;AACtC,SAAS,iCAAiC;AAEnC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;AAE5B,SAAS,yBAAyB,QAGvB;AACT,SAAO,2BAA2B,OAAO,MAAM,QAAQ,OAAO,SAAS,MAAM;AAC/E;AAEA,SAAS,MAAM,IAAgC;AAC7C,SAAO,GAAG,UAAe;AAC3B;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,EAAE,KAAK,MAAM,IAAI,MAAM,sBAAsB,GAAG;AACtD,QAAM,KAAK,IAAI,UAAU,QAAQ,IAAI;AACrC,QAAM,KAAK,MAAM,EAAE;AAEnB,QAAM,QAAQ,MAAM,kBAAkB;AACtC,QAAM,QAAQ,mBAAmB,IAAI,iBAAiB,IAAI,SAAS,IAAI;AACvE,QAAM,WAAW,QACb,yBAAyB,EAAE,QAAQ,MAAM,QAAQ,MAAM,CAAC,IACxD;AAEJ,MAAI,SAAS,UAAU;AACrB,UAAM,SAAS,MAAM,mBAAmB,MAAM,UAAU,MAAM,MAAM,IAAI,QAAQ,CAAC;AACjF,QAAI,OAAO,WAAW,UAAU;AAC9B,aAAO,SAAS,KAAK,EAAE,aAAa,OAAO,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,MAAI,QAAQ,GACT,WAAW,yBAAyB,EACpC,UAAU,iBAAiB,QAAQ,cAAc,EACjD,MAAM,uBAAuB,KAAK,MAAM,MAAM,EAC9C,MAAM,YAAY,KAAK,QAAQ,EAC/B,MAAM,gBAAgB,MAAM,IAAI,EAChC,MAAM,iBAAiB,MAAM,IAAI,EACjC,MAAM,eAAe,KAAK,MAAM,QAAQ,EACxC,MAAM,gBAAgB,MAAM,IAAI;AAEnC,MAAI,MAAM,gBAAgB;AACxB,YAAQ,MAAM,MAAM,qBAAqB,KAAK,MAAM,cAAc;AAAA,EACpE,OAAO;AACL,YAAQ,MAAM,MAAM,qBAAqB,MAAM,IAAI;AAAA,EACrD;AAEA,QAAM,MAAM,MAAM,MACf,OAAO,cAAsB,GAAG,OAAO,CAAC,EACxC,iBAAiB;AACpB,QAAM,QAAQ,OAAO,KAAK,SAAS,CAAC;AAEpC,MAAI,SAAS,UAAU;AACrB,QAAI;AACF,YAAM;AAAA,QAAmB,MAAM;AAAA,QAAU,MACvC,MAAM,IAAI,UAAU,OAAO;AAAA,UACzB,KAAK;AAAA,UACL,MAAM,oBAAoB,uBAAuB,MAAM,UAAU,CAAC,KAAK,CAAC;AAAA,QAC1E,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,SAAO,SAAS,KAAK,EAAE,aAAa,MAAM,CAAC;AAC7C;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -11,6 +11,7 @@ import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/u
11
11
  import { useT } from "@open-mercato/shared/lib/i18n/context";
12
12
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
13
13
  import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
14
+ import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
14
15
  const MUTATION_CONTEXT_ID = "query_index.status.list:actions";
15
16
  function formatCount(value) {
16
17
  if (value == null) return "\u2014";
@@ -69,7 +70,7 @@ function createColumns(t) {
69
70
  if (!record.vectorEnabled) return /* @__PURE__ */ jsx("span", { children: "\u2014" });
70
71
  const ok = record.vectorCount != null && record.baseCount != null && record.vectorCount === record.baseCount;
71
72
  const display = formatCount(record.vectorCount);
72
- const className = ok ? "text-green-600" : "text-orange-600";
73
+ const className = ok ? "text-status-success-text" : "text-status-warning-text";
73
74
  return /* @__PURE__ */ jsx("span", { className, children: display });
74
75
  },
75
76
  meta: { priority: 2 }
@@ -83,7 +84,7 @@ function createColumns(t) {
83
84
  if (!record.fulltextEnabled) return /* @__PURE__ */ jsx("span", { children: "\u2014" });
84
85
  const ok = record.fulltextCount != null && record.baseCount != null && record.fulltextCount === record.baseCount;
85
86
  const display = formatCount(record.fulltextCount);
86
- const className = ok ? "text-green-600" : "text-orange-600";
87
+ const className = ok ? "text-status-success-text" : "text-status-warning-text";
87
88
  return /* @__PURE__ */ jsx("span", { className, children: display });
88
89
  },
89
90
  meta: { priority: 2 }
@@ -99,7 +100,14 @@ function createColumns(t) {
99
100
  const statusText = translateJobStatus(t, job?.status, ok);
100
101
  const jobProgress = job ? formatProgressLabel(job.processedCount ?? null, job.totalCount ?? null, t) : null;
101
102
  const label = jobProgress ? t("query_index.table.status.withProgress", { status: statusText, progress: jobProgress }) : statusText;
102
- const className = job ? job.status === "stalled" ? "text-red-600" : job.status === "reindexing" || job.status === "purging" ? "text-orange-600" : ok ? "text-green-600" : "text-muted-foreground" : ok ? "text-green-600" : "text-muted-foreground";
103
+ let variant = "neutral";
104
+ if (job) {
105
+ if (job.status === "stalled") variant = "error";
106
+ else if (job.status === "reindexing" || job.status === "purging") variant = "warning";
107
+ else variant = ok ? "success" : "neutral";
108
+ } else {
109
+ variant = ok ? "success" : "neutral";
110
+ }
103
111
  const lines = [];
104
112
  if (job?.scope && partitions.length <= 1) {
105
113
  const scopeStatus = translateScopeStatus(t, job.scope.status ?? null);
@@ -122,8 +130,8 @@ function createColumns(t) {
122
130
  const vectorValue = vectorTotal ? t("query_index.table.status.vectorValue", { count: vectorCount, total: vectorTotal }) : vectorCount;
123
131
  lines.push(`${vectorLabel}: ${vectorValue}`);
124
132
  }
125
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
126
- /* @__PURE__ */ jsx("span", { className, children: label }),
133
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
134
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(StatusBadge, { variant, dot: true, children: label }) }),
127
135
  lines.length > 0 && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: lines.map((line, idx) => /* @__PURE__ */ jsx("div", { children: line }, idx)) })
128
136
  ] });
129
137
  },