@open-mercato/core 0.6.6-develop.6330.1.a261878aa8 → 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 (244) hide show
  1. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +3 -0
  2. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  3. package/dist/modules/auth/api/roles/acl/route.js +2 -2
  4. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  5. package/dist/modules/auth/api/users/acl/route.js +2 -2
  6. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  7. package/dist/modules/auth/backend/roles/[id]/edit/page.js +12 -0
  8. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  9. package/dist/modules/auth/backend/users/[id]/edit/page.js +12 -0
  10. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  11. package/dist/modules/auth/data/entities.js +2 -2
  12. package/dist/modules/auth/data/entities.js.map +2 -2
  13. package/dist/modules/business_rules/api/rules/route.js +3 -3
  14. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  15. package/dist/modules/business_rules/api/sets/route.js +3 -3
  16. package/dist/modules/business_rules/api/sets/route.js.map +2 -2
  17. package/dist/modules/business_rules/backend/rules/[id]/page.js +12 -1
  18. package/dist/modules/business_rules/backend/rules/[id]/page.js.map +2 -2
  19. package/dist/modules/business_rules/backend/sets/[id]/page.js +12 -1
  20. package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
  21. package/dist/modules/catalog/api/product-media/route.js.map +2 -2
  22. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +12 -0
  23. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  24. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +15 -0
  25. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  26. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +12 -1
  27. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  28. package/dist/modules/catalog/components/PriceKindSettings.js +13 -3
  29. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  30. package/dist/modules/catalog/lib/bulkDelete.js.map +2 -2
  31. package/dist/modules/currencies/backend/currencies/[id]/page.js +15 -3
  32. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  33. package/dist/modules/currencies/backend/currencies/page.js +2 -1
  34. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  35. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +12 -1
  36. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
  37. package/dist/modules/currencies/backend/exchange-rates/page.js +3 -2
  38. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  39. package/dist/modules/customer_accounts/api/admin/roles/[id].js +3 -3
  40. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  41. package/dist/modules/customer_accounts/api/admin/users/[id].js +3 -3
  42. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  43. package/dist/modules/customers/api/addresses/route.js +4 -2
  44. package/dist/modules/customers/api/addresses/route.js.map +2 -2
  45. package/dist/modules/customers/api/tags/route.js +8 -3
  46. package/dist/modules/customers/api/tags/route.js.map +2 -2
  47. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +13 -2
  48. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  49. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +24 -9
  50. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
  51. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +53 -23
  52. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +2 -2
  53. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js +13 -3
  54. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js.map +2 -2
  55. package/dist/modules/customers/backend/customers/deals/[id]/page.js +19 -2
  56. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  57. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +13 -2
  58. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  59. package/dist/modules/customers/commands/interactions.js +5 -5
  60. package/dist/modules/customers/commands/interactions.js.map +2 -2
  61. package/dist/modules/customers/commands/pipeline-stages.js +24 -2
  62. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  63. package/dist/modules/customers/commands/pipelines.js +24 -2
  64. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  65. package/dist/modules/customers/components/detail/AddressesSection.js +46 -29
  66. package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
  67. package/dist/modules/customers/components/detail/DealForm.js +5 -1
  68. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  69. package/dist/modules/customers/components/detail/notesAdapter.js +25 -18
  70. package/dist/modules/customers/components/detail/notesAdapter.js.map +2 -2
  71. package/dist/modules/data_sync/api/schedules/[id]/route.js +38 -19
  72. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  73. package/dist/modules/data_sync/api/schedules/route.js +1 -1
  74. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  75. package/dist/modules/data_sync/backend/data-sync/page.js +32 -15
  76. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  77. package/dist/modules/data_sync/lib/sync-schedule-service.js +19 -5
  78. package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
  79. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +3 -3
  80. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  81. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +3 -3
  82. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  83. package/dist/modules/dictionaries/components/DictionariesManager.js +12 -1
  84. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  85. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  86. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +12 -0
  87. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  88. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +12 -0
  89. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  90. package/dist/modules/entities/api/entities.js +2 -2
  91. package/dist/modules/entities/api/entities.js.map +2 -2
  92. package/dist/modules/entities/api/records.js +7 -5
  93. package/dist/modules/entities/api/records.js.map +2 -2
  94. package/dist/modules/feature_toggles/api/overrides/route.js +2 -2
  95. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  96. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
  97. package/dist/modules/feature_toggles/data/entities.js +1 -1
  98. package/dist/modules/feature_toggles/data/entities.js.map +2 -2
  99. package/dist/modules/feature_toggles/data/validators.js +2 -1
  100. package/dist/modules/feature_toggles/data/validators.js.map +2 -2
  101. package/dist/modules/feature_toggles/lib/queries.js +2 -1
  102. package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
  103. package/dist/modules/inbox_ops/api/settings/route.js +2 -2
  104. package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
  105. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +12 -1
  106. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
  107. package/dist/modules/resources/backend/resources/resources/[id]/page.js +12 -1
  108. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  109. package/dist/modules/resources/components/detail/notesAdapter.js +25 -18
  110. package/dist/modules/resources/components/detail/notesAdapter.js.map +2 -2
  111. package/dist/modules/sales/backend/sales/documents/[id]/page.js +14 -1
  112. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  113. package/dist/modules/sales/commands/documents.js +11 -10
  114. package/dist/modules/sales/commands/documents.js.map +2 -2
  115. package/dist/modules/sales/commands/payments.js +6 -1
  116. package/dist/modules/sales/commands/payments.js.map +2 -2
  117. package/dist/modules/sales/commands/returns.js +3 -3
  118. package/dist/modules/sales/commands/returns.js.map +2 -2
  119. package/dist/modules/sales/commands/shared.js +3 -3
  120. package/dist/modules/sales/commands/shared.js.map +2 -2
  121. package/dist/modules/sales/commands/shipments.js +6 -1
  122. package/dist/modules/sales/commands/shipments.js.map +2 -2
  123. package/dist/modules/sales/components/documents/PaymentDialog.js +6 -3
  124. package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
  125. package/dist/modules/sales/components/documents/PaymentsSection.js +7 -3
  126. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  127. package/dist/modules/sales/components/documents/ShipmentDialog.js +6 -3
  128. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  129. package/dist/modules/sales/components/documents/ShipmentsSection.js +7 -3
  130. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  131. package/dist/modules/sales/di.js +39 -3
  132. package/dist/modules/sales/di.js.map +2 -2
  133. package/dist/modules/sales/setup.js +2 -0
  134. package/dist/modules/sales/setup.js.map +2 -2
  135. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +12 -1
  136. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  137. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +12 -1
  138. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  139. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +12 -1
  140. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  141. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -1
  142. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  143. package/dist/modules/staff/commands/job-histories.js +3 -3
  144. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  145. package/dist/modules/staff/components/detail/addressesAdapter.js +33 -24
  146. package/dist/modules/staff/components/detail/addressesAdapter.js.map +2 -2
  147. package/dist/modules/staff/components/detail/notesAdapter.js +25 -18
  148. package/dist/modules/staff/components/detail/notesAdapter.js.map +2 -2
  149. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +37 -0
  150. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  151. package/dist/modules/translations/components/TranslationManager.js +7 -1
  152. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  153. package/dist/modules/workflows/api/definitions/[id]/route.js +3 -3
  154. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  155. package/dist/modules/workflows/backend/definitions/[id]/page.js +12 -1
  156. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  157. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +11 -1
  158. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  159. package/package.json +7 -7
  160. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +3 -0
  161. package/src/modules/auth/api/roles/acl/route.ts +2 -2
  162. package/src/modules/auth/api/users/acl/route.ts +2 -2
  163. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +17 -0
  164. package/src/modules/auth/backend/users/[id]/edit/page.tsx +17 -0
  165. package/src/modules/auth/data/entities.ts +2 -2
  166. package/src/modules/business_rules/api/rules/route.ts +3 -3
  167. package/src/modules/business_rules/api/sets/route.ts +3 -3
  168. package/src/modules/business_rules/backend/rules/[id]/page.tsx +17 -1
  169. package/src/modules/business_rules/backend/sets/[id]/page.tsx +18 -1
  170. package/src/modules/catalog/api/product-media/route.ts +4 -0
  171. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +17 -0
  172. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +21 -0
  173. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +16 -1
  174. package/src/modules/catalog/components/PriceKindSettings.tsx +15 -7
  175. package/src/modules/catalog/lib/bulkDelete.ts +6 -0
  176. package/src/modules/currencies/backend/currencies/[id]/page.tsx +20 -3
  177. package/src/modules/currencies/backend/currencies/page.tsx +2 -1
  178. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +17 -1
  179. package/src/modules/currencies/backend/exchange-rates/page.tsx +3 -2
  180. package/src/modules/customer_accounts/api/admin/roles/[id].ts +3 -3
  181. package/src/modules/customer_accounts/api/admin/users/[id].ts +3 -3
  182. package/src/modules/customers/api/addresses/route.ts +2 -0
  183. package/src/modules/customers/api/tags/route.ts +6 -1
  184. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +19 -2
  185. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +29 -8
  186. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +47 -20
  187. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.ts +13 -3
  188. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +21 -1
  189. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -2
  190. package/src/modules/customers/commands/interactions.ts +5 -5
  191. package/src/modules/customers/commands/pipeline-stages.ts +26 -2
  192. package/src/modules/customers/commands/pipelines.ts +26 -2
  193. package/src/modules/customers/components/detail/AddressesSection.tsx +50 -28
  194. package/src/modules/customers/components/detail/DealForm.tsx +12 -0
  195. package/src/modules/customers/components/detail/notesAdapter.ts +29 -18
  196. package/src/modules/customers/i18n/de.json +2 -0
  197. package/src/modules/customers/i18n/en.json +2 -0
  198. package/src/modules/customers/i18n/es.json +2 -0
  199. package/src/modules/customers/i18n/pl.json +2 -0
  200. package/src/modules/data_sync/api/schedules/[id]/route.ts +38 -20
  201. package/src/modules/data_sync/api/schedules/route.ts +1 -1
  202. package/src/modules/data_sync/backend/data-sync/page.tsx +32 -15
  203. package/src/modules/data_sync/lib/sync-schedule-service.ts +30 -5
  204. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +3 -3
  205. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +3 -3
  206. package/src/modules/dictionaries/components/DictionariesManager.tsx +21 -1
  207. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +2 -0
  208. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +17 -0
  209. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +17 -0
  210. package/src/modules/entities/api/entities.ts +2 -2
  211. package/src/modules/entities/api/records.ts +7 -5
  212. package/src/modules/feature_toggles/api/overrides/route.ts +2 -2
  213. package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +12 -0
  214. package/src/modules/feature_toggles/data/entities.ts +1 -1
  215. package/src/modules/feature_toggles/data/validators.ts +1 -0
  216. package/src/modules/feature_toggles/lib/queries.ts +1 -0
  217. package/src/modules/inbox_ops/api/settings/route.ts +2 -2
  218. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +17 -1
  219. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +17 -1
  220. package/src/modules/resources/components/detail/notesAdapter.ts +29 -18
  221. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +17 -1
  222. package/src/modules/sales/commands/documents.ts +11 -10
  223. package/src/modules/sales/commands/payments.ts +11 -0
  224. package/src/modules/sales/commands/returns.ts +3 -3
  225. package/src/modules/sales/commands/shared.ts +10 -4
  226. package/src/modules/sales/commands/shipments.ts +14 -0
  227. package/src/modules/sales/components/documents/PaymentDialog.tsx +7 -3
  228. package/src/modules/sales/components/documents/PaymentsSection.tsx +8 -3
  229. package/src/modules/sales/components/documents/ShipmentDialog.tsx +7 -3
  230. package/src/modules/sales/components/documents/ShipmentsSection.tsx +8 -3
  231. package/src/modules/sales/di.ts +68 -9
  232. package/src/modules/sales/setup.ts +2 -0
  233. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +17 -1
  234. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +17 -1
  235. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +17 -1
  236. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +17 -1
  237. package/src/modules/staff/commands/job-histories.ts +3 -3
  238. package/src/modules/staff/components/detail/addressesAdapter.ts +40 -23
  239. package/src/modules/staff/components/detail/notesAdapter.ts +28 -18
  240. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +74 -0
  241. package/src/modules/translations/components/TranslationManager.tsx +14 -1
  242. package/src/modules/workflows/api/definitions/[id]/route.ts +3 -3
  243. package/src/modules/workflows/backend/definitions/[id]/page.tsx +18 -1
  244. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +19 -1
@@ -6,7 +6,7 @@ import { normalizeExportFormat, serializeExport, defaultExportFilename, ensureCo
6
6
  import { resolveOrganizationScope, getSelectedOrganizationFromRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
7
7
  import { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from "@open-mercato/shared/lib/data/engine";
8
8
  import { parseBooleanToken, parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
9
- import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
9
+ import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
10
10
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
11
11
  import { getModules } from "@open-mercato/shared/lib/i18n/server";
12
12
  import { assertEntityAclForRequest } from "../lib/entityAcl.js";
@@ -384,7 +384,8 @@ async function PUT(req) {
384
384
  if (!parsed.ok) return NextResponse.json({ error: parsed.error }, { status: 400 });
385
385
  const { entityId, recordId, values } = parsed.data;
386
386
  try {
387
- const { resolve } = await createRequestContainer();
387
+ const container = await createRequestContainer();
388
+ const { resolve } = container;
388
389
  const de = resolve("dataEngine");
389
390
  const em = resolve("em");
390
391
  const rbac = resolve("rbacService");
@@ -426,7 +427,7 @@ async function PUT(req) {
426
427
  entityId: rid,
427
428
  organizationId: targetOrgId
428
429
  });
429
- enforceCommandOptimisticLock({
430
+ await enforceCommandOptimisticLockWithGuards(container, {
430
431
  resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,
431
432
  resourceId: rid,
432
433
  current: currentUpdatedAt,
@@ -473,7 +474,8 @@ async function DELETE(req) {
473
474
  if (!parsed.success) return NextResponse.json({ error: "Validation failed", details: parsed.error.flatten() }, { status: 400 });
474
475
  const { entityId, recordId } = parsed.data;
475
476
  try {
476
- const { resolve } = await createRequestContainer();
477
+ const container = await createRequestContainer();
478
+ const { resolve } = container;
477
479
  const de = resolve("dataEngine");
478
480
  const em = resolve("em");
479
481
  const rbac = resolve("rbacService");
@@ -490,7 +492,7 @@ async function DELETE(req) {
490
492
  entityId: recordId,
491
493
  organizationId: targetOrgId
492
494
  });
493
- enforceCommandOptimisticLock({
495
+ await enforceCommandOptimisticLockWithGuards(container, {
494
496
  resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,
495
497
  resourceId: recordId,
496
498
  current: currentUpdatedAt,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/entities/api/records.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport type { QueryEngine, QueryOptions, Where, Sort } from '@open-mercato/shared/lib/query/types'\nimport { normalizeExportFormat, serializeExport, defaultExportFilename, ensureColumns } from '@open-mercato/shared/lib/crud/exporters'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { resolveOrganizationScope, getSelectedOrganizationFromRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'\nimport { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { setRecordCustomFields } from '../lib/helpers'\nimport { CustomFieldValue } from '../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getModules } from '@open-mercato/shared/lib/i18n/server'\nimport { assertEntityAclForRequest } from '../lib/entityAcl'\n\nlet declaredCustomEntityIds: Set<string> | null = null\nfunction isDeclaredCustomEntity(entityId: string): boolean {\n if (declaredCustomEntityIds === null) {\n try {\n const mods = getModules() as Array<{ customEntities?: Array<{ id?: string }> }>\n if (Array.isArray(mods) && mods.length) {\n const ids = new Set<string>()\n for (const mod of mods) {\n for (const spec of mod?.customEntities ?? []) {\n if (spec?.id) ids.add(spec.id)\n }\n }\n declaredCustomEntityIds = ids\n }\n } catch {}\n }\n return declaredCustomEntityIds?.has(entityId) ?? false\n}\n\nconst CUSTOM_ENTITY_RECORD_RESOURCE_KIND = 'entities.record'\n\ntype RecordsEntityKind = 'system' | 'custom' | 'unknown'\n\n// This surface manages doc-storage records, which exist for CUSTOM entities only.\n// Module-declared ids backed by a registered ORM table are system entities \u2014 their\n// records live in their own module tables/APIs, and stray doc rows for them poisoned\n// read-path classification platform-wide (#2939) \u2014 so they are rejected outright. The\n// previous fallback that classified an entity by the mere presence of\n// `custom_entities_storage` rows is gone: within the allowed set, declaration (ce.ts)\n// or an active `custom_entities` registration is authoritative.\nasync function classifyRecordsEntity(em: any, entityId: string): Promise<RecordsEntityKind> {\n if (isOrmBackedSystemEntityId(em, entityId)) return 'system'\n if (isDeclaredCustomEntity(entityId)) return 'custom'\n try {\n const { CustomEntity } = await import('../data/entities')\n // Any registration row \u2014 active or soft-deleted \u2014 proves the id is a custom\n // entity. Records persist beyond the definition's soft delete (TC-ENTITIES-006)\n // and must stay readable/deletable, e.g. for the restore flow and cleanup.\n const found = await em.findOne(CustomEntity as any, { entityId })\n if (found) return 'custom'\n } catch {}\n return 'unknown'\n}\n\nfunction systemEntityRecordsRejection(entityId: string) {\n return NextResponse.json(\n { error: 'Records are available for custom entities only', code: SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, entityId },\n { status: 400 },\n )\n}\n\nasync function readCustomEntityRecordUpdatedAt(\n em: any,\n input: { entityType: string; entityId: string; organizationId: string | null },\n): Promise<string | null> {\n try {\n const db = em.getKysely()\n let query = db\n .selectFrom('custom_entities_storage' as any)\n .select(['updated_at' as any])\n .where('entity_type' as any, '=', input.entityType)\n .where('entity_id' as any, '=', input.entityId)\n query = input.organizationId === null\n ? query.where('organization_id' as any, 'is', null as any)\n : query.where('organization_id' as any, '=', input.organizationId)\n const row = await query.executeTakeFirst()\n const value = (row as any)?.updated_at\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string' && value.length > 0) return value\n return null\n } catch {\n return null\n }\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['entities.records.view'] },\n POST: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n}\n\nconst DEFAULT_EXPORT_PAGE_SIZE = 1000\n\nconst listRecordsQuerySchema = z\n .object({\n entityId: z.string().min(1),\n page: z.coerce.number().int().min(1).optional(),\n pageSize: z.coerce.number().int().min(1).max(100).optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n search: z.string().optional(),\n searchFields: z.string().optional(),\n withDeleted: z.coerce.boolean().optional(),\n format: z.enum(['csv', 'json', 'xml', 'markdown']).optional(),\n exportScope: z.enum(['full']).optional(),\n export_scope: z.enum(['full']).optional(),\n all: z.coerce.boolean().optional(),\n full: z.coerce.boolean().optional(),\n })\n .passthrough()\n\nconst recordItemSchema = z.record(z.string(), z.any())\n\nconst listRecordsResponseSchema = z.object({\n items: z.array(recordItemSchema),\n total: z.number(),\n page: z.number(),\n pageSize: z.number(),\n totalPages: z.number(),\n})\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const entityId = url.searchParams.get('entityId') || ''\n if (!entityId) return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const requestedExport = normalizeExportFormat(url.searchParams.get('format'))\n const exportScopeRaw = (url.searchParams.get('exportScope') || url.searchParams.get('export_scope') || '').toLowerCase()\n const exportFullRequested = requestedExport != null && (exportScopeRaw === 'full' || parseBooleanWithDefault(url.searchParams.get('full'), false))\n const exportAll = parseBooleanWithDefault(url.searchParams.get('all'), false)\n const noPagination = exportAll || requestedExport != null\n const page = noPagination ? 1 : Math.max(parseInt(url.searchParams.get('page') || '1', 10) || 1, 1)\n const basePageSize = Math.min(Math.max(parseInt(url.searchParams.get('pageSize') || '50', 10) || 50, 1), 100)\n const pageSize = noPagination ? Math.max(basePageSize, DEFAULT_EXPORT_PAGE_SIZE) : basePageSize\n const sortField = url.searchParams.get('sortField') || 'id'\n const sortDir = (url.searchParams.get('sortDir') || 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc'\n const withDeleted = parseBooleanWithDefault(url.searchParams.get('withDeleted'), false)\n const searchTerm = (url.searchParams.get('search') || '').trim()\n const searchFields = (url.searchParams.get('searchFields') || '')\n .split(',')\n .map((field) => field.trim())\n .filter(Boolean)\n\n const qpEntries: Array<[string, string]> = []\n for (const [key, val] of url.searchParams.entries()) {\n if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full','search','searchFields'].includes(key)) continue\n qpEntries.push([key, val])\n }\n\n try {\n const { resolve } = await createRequestContainer()\n const qe = resolve('queryEngine') as QueryEngine\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n let organizationIds: string[] | null = scope.filterIds\n // Module-declared custom entities (ce.ts) carry frozen system-style ids and are never\n // registered in `custom_entities`, so classification checks the declared registry plus\n // active registrations. System (table-backed) ids are rejected above; for the allowed\n // set `isCustomEntity` drives mapRow's cf_ stripping so the edit form reads back values.\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'view', isCustomEntity, rbac })\n if (organizationIds && organizationIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 0 })\n }\n const normalizeCustomEntityValue = (value: unknown) => {\n if (Array.isArray(value)) {\n return value.map((entry) => {\n if (typeof entry !== 'string') return entry\n const parsed = parseBooleanToken(entry)\n return parsed === null ? entry : parsed\n })\n }\n if (typeof value !== 'string') return value\n const parsed = parseBooleanToken(value)\n return parsed === null ? value : parsed\n }\n const mapRow = (row: any) => {\n if (!isCustomEntity || !row || typeof row !== 'object') return row\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(row)) {\n if (k.startsWith('cf_')) out[k.replace(/^cf_/, '')] = normalizeCustomEntityValue(v)\n else out[k] = v\n }\n return out\n }\n const mapFullRow = (row: any) => {\n if (!row || typeof row !== 'object') return row\n return { ...(row as Record<string, unknown>) }\n }\n // Build filters with awareness of custom-entity mode\n const filtersObj: Where<any> = {}\n const buildFilter = (key: string, val: string, allowAnyKey: boolean) => {\n if (key.startsWith('cf_')) {\n if (key.endsWith('In')) {\n const base = key.slice(0, -2)\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[base] = { $in: values }\n } else {\n if (val.includes(',')) {\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[key] = { $in: values }\n } else {\n const parsed = parseBooleanToken(val)\n ;(filtersObj as any)[key] = parsed === null ? val : parsed\n }\n }\n } else if (allowAnyKey) {\n if (val.includes(',')) {\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[key] = { $in: values }\n } else {\n const parsed = parseBooleanToken(val)\n ;(filtersObj as any)[key] = parsed === null ? val : parsed\n }\n } else {\n if (['id', 'created_at', 'updated_at', 'deleted_at', 'name', 'title', 'email'].includes(key)) {\n ;(filtersObj as any)[key] = val\n }\n }\n }\n\n if (organizationIds && organizationIds.length) {\n (filtersObj as any).organization_id = { $in: organizationIds }\n }\n const qopts: QueryOptions = {\n tenantId: auth.tenantId!,\n includeCustomFields: true,\n page: { page, pageSize },\n sort: [{ field: sortField as any, dir: sortDir as any }] as Sort[],\n filters: filtersObj as any,\n withDeleted,\n }\n if (organizationIds && organizationIds.length) {\n qopts.organizationIds = organizationIds\n }\n // Allowed entities are doc-storage-backed by definition (system ids were rejected\n // above) \u2014 direct the engine to doc storage explicitly so reads stay deterministic\n // even before the first record exists.\n if (isCustomEntity) qopts.forceCustomEntityStorage = true\n for (const [k, v] of qpEntries) buildFilter(k, v, isCustomEntity)\n // Server-side full-result search: match the term against the requested fields\n // (defaults to `id`) before pagination so totals/exports stay consistent with\n // the active search instead of filtering only the current client page (#3229).\n if (searchTerm) {\n const fields = searchFields.length ? searchFields : ['id']\n const pattern = `%${searchTerm}%`\n const orClauses = fields.map((field) => ({ [field]: { $ilike: pattern } }))\n ;(filtersObj as any).$or = orClauses\n }\n const res = await qe.query(entityId as any, qopts)\n const rawItems = res.items || []\n const viewPageItems = rawItems.map(mapRow)\n const fullPageItems = rawItems.map(mapFullRow)\n\n // Expose `updated_at` on custom-entity records. The query engine returns only\n // the `doc` fields + `id`, dropping the base `updated_at` column \u2014 which made\n // optimistic locking impossible end-to-end (no version for the edit page to\n // round-trip as the lock header). Batch-read it from storage and merge it in.\n if (isCustomEntity && viewPageItems.length) {\n try {\n const recordIds = viewPageItems\n .map((it: any) => it?.id)\n .filter((v: any): v is string => typeof v === 'string' && v.length > 0)\n if (recordIds.length) {\n const db = em.getKysely()\n const rows = await db\n .selectFrom('custom_entities_storage' as any)\n .select(['entity_id' as any, 'updated_at' as any])\n .where('entity_type' as any, '=', entityId)\n .where('entity_id' as any, 'in', recordIds as any)\n .execute()\n const updatedById = new Map<string, string>()\n for (const row of rows as any[]) {\n const value = row?.updated_at\n const iso = value instanceof Date ? value.toISOString() : (typeof value === 'string' && value.length > 0 ? value : null)\n if (iso && row?.entity_id) updatedById.set(String(row.entity_id), iso)\n }\n for (const item of viewPageItems as any[]) {\n const iso = updatedById.get(String(item?.id))\n if (iso) {\n item.updated_at = iso\n item.updatedAt = iso\n }\n }\n }\n } catch { /* best-effort: locking simply will not engage if storage is unavailable */ }\n }\n\n const total = typeof res.total === 'number' ? res.total : rawItems.length\n const effectivePageSize = res.pageSize || pageSize\n const payload = {\n items: viewPageItems,\n total,\n page: res.page || page,\n pageSize: effectivePageSize,\n totalPages: Math.ceil(total / (effectivePageSize || 1)),\n }\n\n if (requestedExport) {\n let exportItems: any[] = exportFullRequested ? [...fullPageItems] : [...viewPageItems]\n if (total > exportItems.length) {\n let nextPage = 2\n while (exportItems.length < total) {\n const nextRes = await qe.query(entityId as any, {\n ...qopts,\n page: { page: nextPage, pageSize },\n })\n const nextRawItems = nextRes.items || []\n if (!nextRawItems.length) break\n const nextViewItems = nextRawItems.map(mapRow)\n const nextFullItems = nextRawItems.map(mapFullRow)\n const nextBatch = exportFullRequested ? nextFullItems : nextViewItems\n exportItems.push(...nextBatch)\n if (nextBatch.length < pageSize) break\n nextPage += 1\n }\n }\n const prepared = {\n columns: ensureColumns(exportItems),\n rows: exportItems,\n }\n const filenameBase = exportFullRequested ? `${entityId || 'records'}_full` : entityId || 'records'\n const serialized = serializeExport(prepared, requestedExport)\n const filename = defaultExportFilename(filenameBase, requestedExport)\n return new Response(serialized.body, {\n headers: {\n 'content-type': serialized.contentType,\n 'content-disposition': `attachment; filename=\"${filename}\"`,\n },\n })\n }\n\n return NextResponse.json(payload)\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n try { console.error('[entities.records.GET] Error', e) } catch {}\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst postBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1).optional(),\n values: z.record(z.string(), z.any()).default({}),\n})\n\nconst putBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n values: z.record(z.string(), z.any()).default({}),\n})\n\nconst mutationResponseSchema = z.object({\n ok: z.literal(true),\n item: z\n .object({\n entityId: z.string(),\n recordId: z.string(),\n })\n .optional(),\n})\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let json: unknown\n try { json = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = postBodySchema.safeParse(json)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const { entityId } = parsed.data\n let { recordId, values } = parsed.data as { recordId?: string; values: Record<string, any> }\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n // Strip reserved record/system columns the edit form echoes back from the loaded record\n // (`id`, plus `updated_at`/`updatedAt` used for optimistic locking). They are not custom\n // fields; without this they validate as cf_id / cf_updated_at / cf_updatedAt and are\n // rejected as \"Unknown custom field\", which fails EVERY custom-entity edit-form save.\n for (const reservedKey of ['id', 'created_at', 'createdAt', 'updated_at', 'updatedAt', 'deleted_at', 'deletedAt']) {\n delete (values as any)[reservedKey]\n }\n const norm = normalizeValues(values)\n\n // Validate against custom field definitions\n try {\n const { validateCustomFieldValuesServer } = await import('../lib/validation')\n const check = await validateCustomFieldValuesServer(em, { entityId, organizationId: targetOrgId, tenantId: auth.tenantId!, values: norm, rejectUndeclaredKeys: true })\n if (!check.ok) return NextResponse.json({ error: 'Validation failed', fields: check.fieldErrors }, { status: 400 })\n } catch { /* ignore if helper missing */ }\n\n const normalizedRecordId = (() => {\n const raw = String(recordId || '').trim()\n if (!raw) return undefined\n const low = raw.toLowerCase()\n if (low === 'create' || low === 'new' || low === 'null' || low === 'undefined') return undefined\n // Enforce UUID only; any non-uuid is ignored so we generate one in the DE\n const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n return uuid.test(raw) ? raw : undefined\n })()\n const { id } = await de.createCustomEntityRecord({\n entityId,\n recordId: normalizedRecordId,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n\n return NextResponse.json({ ok: true, item: { entityId, recordId: id } })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n try { console.error('[entities.records.POST] Error', e) } catch {}\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\n// Avoid zod here to prevent runtime import issues in some environments\nfunction parsePutBody(json: any): { ok: true; data: { entityId: string; recordId: string; values: Record<string, any> } } | { ok: false; error: string } {\n if (!json || typeof json !== 'object') return { ok: false, error: 'Invalid JSON' }\n const entityId = typeof json.entityId === 'string' && json.entityId.length ? json.entityId : ''\n const recordId = typeof json.recordId === 'string' && json.recordId.length ? json.recordId : ''\n const values = (json.values && typeof json.values === 'object') ? json.values as Record<string, any> : {}\n if (!entityId || !recordId) return { ok: false, error: 'entityId and recordId are required' }\n return { ok: true, data: { entityId, recordId, values } }\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let json: any\n try { json = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = parsePutBody(json)\n if (!parsed.ok) return NextResponse.json({ error: parsed.error }, { status: 400 })\n const { entityId, recordId, values } = parsed.data\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n // Strip reserved record/system columns the edit form echoes back from the loaded record\n // (`id`, plus `updated_at`/`updatedAt` used for optimistic locking). They are not custom\n // fields; without this they validate as cf_id / cf_updated_at / cf_updatedAt and are\n // rejected as \"Unknown custom field\", which fails EVERY custom-entity edit-form save.\n for (const reservedKey of ['id', 'created_at', 'createdAt', 'updated_at', 'updatedAt', 'deleted_at', 'deletedAt']) {\n delete (values as any)[reservedKey]\n }\n const norm = normalizeValues(values)\n\n // Validate against custom field definitions\n try {\n const { validateCustomFieldValuesServer } = await import('../lib/validation')\n const check = await validateCustomFieldValuesServer(em, { entityId, organizationId: targetOrgId, tenantId: auth.tenantId!, values: norm, rejectUndeclaredKeys: true })\n if (!check.ok) return NextResponse.json({ error: 'Validation failed', fields: check.fieldErrors }, { status: 400 })\n } catch { /* ignore if helper missing */ }\n\n // Normalize recordId: if blank/sentinel/non-uuid => create instead of update\n const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n const rid = String(recordId || '').trim()\n const low = rid.toLowerCase()\n const isSentinel = !rid || low === 'create' || low === 'new' || low === 'null' || low === 'undefined'\n const isUuid = uuidRe.test(rid)\n if (isSentinel || !isUuid) {\n const created = await de.createCustomEntityRecord({\n entityId,\n recordId: undefined,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n return NextResponse.json({ ok: true, item: { entityId, recordId: created.id } })\n }\n\n try {\n const currentUpdatedAt = await readCustomEntityRecordUpdatedAt(em, {\n entityType: entityId,\n entityId: rid,\n organizationId: targetOrgId,\n })\n enforceCommandOptimisticLock({\n resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,\n resourceId: rid,\n current: currentUpdatedAt,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n\n await de.updateCustomEntityRecord({\n entityId,\n recordId: rid,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n return NextResponse.json({ ok: true, item: { entityId, recordId: rid } })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst deleteBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n})\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const qpEntityId = url.searchParams.get('entityId')\n const qpRecordId = url.searchParams.get('recordId')\n let payload: any = qpEntityId && qpRecordId ? { entityId: qpEntityId, recordId: qpRecordId } : null\n if (!payload) {\n try { payload = await req.json() } catch { payload = null }\n }\n const parsed = deleteBodySchema.safeParse(payload)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const { entityId, recordId } = parsed.data\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n\n try {\n const currentUpdatedAt = await readCustomEntityRecordUpdatedAt(em, {\n entityType: entityId,\n entityId: recordId,\n organizationId: targetOrgId,\n })\n enforceCommandOptimisticLock({\n resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,\n resourceId: recordId,\n current: currentUpdatedAt,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n\n await de.deleteCustomEntityRecord({ entityId, recordId, organizationId: targetOrgId, tenantId: auth.tenantId!, soft: true })\n return NextResponse.json({ ok: true })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nfunction normalizeValues(input: Record<string, any>): Record<string, any> {\n const out: Record<string, any> = {}\n for (const [k, v] of Object.entries(input || {})) {\n const key = k.startsWith('cf_') ? k.replace(/^cf_/, '') : k\n out[key] = v\n }\n return out\n}\n\nconst deleteResponseSchema = z.object({\n ok: z.literal(true),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n}).passthrough()\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'CRUD operations on entity records',\n methods: {\n GET: {\n summary: 'List records',\n description:\n 'Returns paginated records for the supplied entity. Supports custom field filters, exports, and soft-delete toggles.',\n query: listRecordsQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Paginated records',\n schema: listRecordsResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n POST: {\n summary: 'Create record',\n description:\n 'Creates a record for the given entity. When `recordId` is omitted or not a UUID the data engine will generate one automatically.',\n requestBody: {\n contentType: 'application/json',\n schema: postBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record created',\n schema: mutationResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failure',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n PUT: {\n summary: 'Update record',\n description:\n 'Updates an existing record. If the provided recordId is not a UUID the record will be created instead to support optimistic flows.',\n requestBody: {\n contentType: 'application/json',\n schema: putBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record updated',\n schema: mutationResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failure',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n DELETE: {\n summary: 'Delete record',\n description: 'Soft deletes the specified record within the current tenant/org scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record deleted',\n schema: deleteResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id or record id',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 404,\n description: 'Record not found',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport type { QueryEngine, QueryOptions, Where, Sort } from '@open-mercato/shared/lib/query/types'\nimport { normalizeExportFormat, serializeExport, defaultExportFilename, ensureColumns } from '@open-mercato/shared/lib/crud/exporters'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { resolveOrganizationScope, getSelectedOrganizationFromRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'\nimport { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { setRecordCustomFields } from '../lib/helpers'\nimport { CustomFieldValue } from '../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getModules } from '@open-mercato/shared/lib/i18n/server'\nimport { assertEntityAclForRequest } from '../lib/entityAcl'\n\nlet declaredCustomEntityIds: Set<string> | null = null\nfunction isDeclaredCustomEntity(entityId: string): boolean {\n if (declaredCustomEntityIds === null) {\n try {\n const mods = getModules() as Array<{ customEntities?: Array<{ id?: string }> }>\n if (Array.isArray(mods) && mods.length) {\n const ids = new Set<string>()\n for (const mod of mods) {\n for (const spec of mod?.customEntities ?? []) {\n if (spec?.id) ids.add(spec.id)\n }\n }\n declaredCustomEntityIds = ids\n }\n } catch {}\n }\n return declaredCustomEntityIds?.has(entityId) ?? false\n}\n\nconst CUSTOM_ENTITY_RECORD_RESOURCE_KIND = 'entities.record'\n\ntype RecordsEntityKind = 'system' | 'custom' | 'unknown'\n\n// This surface manages doc-storage records, which exist for CUSTOM entities only.\n// Module-declared ids backed by a registered ORM table are system entities \u2014 their\n// records live in their own module tables/APIs, and stray doc rows for them poisoned\n// read-path classification platform-wide (#2939) \u2014 so they are rejected outright. The\n// previous fallback that classified an entity by the mere presence of\n// `custom_entities_storage` rows is gone: within the allowed set, declaration (ce.ts)\n// or an active `custom_entities` registration is authoritative.\nasync function classifyRecordsEntity(em: any, entityId: string): Promise<RecordsEntityKind> {\n if (isOrmBackedSystemEntityId(em, entityId)) return 'system'\n if (isDeclaredCustomEntity(entityId)) return 'custom'\n try {\n const { CustomEntity } = await import('../data/entities')\n // Any registration row \u2014 active or soft-deleted \u2014 proves the id is a custom\n // entity. Records persist beyond the definition's soft delete (TC-ENTITIES-006)\n // and must stay readable/deletable, e.g. for the restore flow and cleanup.\n const found = await em.findOne(CustomEntity as any, { entityId })\n if (found) return 'custom'\n } catch {}\n return 'unknown'\n}\n\nfunction systemEntityRecordsRejection(entityId: string) {\n return NextResponse.json(\n { error: 'Records are available for custom entities only', code: SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, entityId },\n { status: 400 },\n )\n}\n\nasync function readCustomEntityRecordUpdatedAt(\n em: any,\n input: { entityType: string; entityId: string; organizationId: string | null },\n): Promise<string | null> {\n try {\n const db = em.getKysely()\n let query = db\n .selectFrom('custom_entities_storage' as any)\n .select(['updated_at' as any])\n .where('entity_type' as any, '=', input.entityType)\n .where('entity_id' as any, '=', input.entityId)\n query = input.organizationId === null\n ? query.where('organization_id' as any, 'is', null as any)\n : query.where('organization_id' as any, '=', input.organizationId)\n const row = await query.executeTakeFirst()\n const value = (row as any)?.updated_at\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string' && value.length > 0) return value\n return null\n } catch {\n return null\n }\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['entities.records.view'] },\n POST: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['entities.records.manage'] },\n}\n\nconst DEFAULT_EXPORT_PAGE_SIZE = 1000\n\nconst listRecordsQuerySchema = z\n .object({\n entityId: z.string().min(1),\n page: z.coerce.number().int().min(1).optional(),\n pageSize: z.coerce.number().int().min(1).max(100).optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n search: z.string().optional(),\n searchFields: z.string().optional(),\n withDeleted: z.coerce.boolean().optional(),\n format: z.enum(['csv', 'json', 'xml', 'markdown']).optional(),\n exportScope: z.enum(['full']).optional(),\n export_scope: z.enum(['full']).optional(),\n all: z.coerce.boolean().optional(),\n full: z.coerce.boolean().optional(),\n })\n .passthrough()\n\nconst recordItemSchema = z.record(z.string(), z.any())\n\nconst listRecordsResponseSchema = z.object({\n items: z.array(recordItemSchema),\n total: z.number(),\n page: z.number(),\n pageSize: z.number(),\n totalPages: z.number(),\n})\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const entityId = url.searchParams.get('entityId') || ''\n if (!entityId) return NextResponse.json({ error: 'entityId is required' }, { status: 400 })\n\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const requestedExport = normalizeExportFormat(url.searchParams.get('format'))\n const exportScopeRaw = (url.searchParams.get('exportScope') || url.searchParams.get('export_scope') || '').toLowerCase()\n const exportFullRequested = requestedExport != null && (exportScopeRaw === 'full' || parseBooleanWithDefault(url.searchParams.get('full'), false))\n const exportAll = parseBooleanWithDefault(url.searchParams.get('all'), false)\n const noPagination = exportAll || requestedExport != null\n const page = noPagination ? 1 : Math.max(parseInt(url.searchParams.get('page') || '1', 10) || 1, 1)\n const basePageSize = Math.min(Math.max(parseInt(url.searchParams.get('pageSize') || '50', 10) || 50, 1), 100)\n const pageSize = noPagination ? Math.max(basePageSize, DEFAULT_EXPORT_PAGE_SIZE) : basePageSize\n const sortField = url.searchParams.get('sortField') || 'id'\n const sortDir = (url.searchParams.get('sortDir') || 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc'\n const withDeleted = parseBooleanWithDefault(url.searchParams.get('withDeleted'), false)\n const searchTerm = (url.searchParams.get('search') || '').trim()\n const searchFields = (url.searchParams.get('searchFields') || '')\n .split(',')\n .map((field) => field.trim())\n .filter(Boolean)\n\n const qpEntries: Array<[string, string]> = []\n for (const [key, val] of url.searchParams.entries()) {\n if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full','search','searchFields'].includes(key)) continue\n qpEntries.push([key, val])\n }\n\n try {\n const { resolve } = await createRequestContainer()\n const qe = resolve('queryEngine') as QueryEngine\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n let organizationIds: string[] | null = scope.filterIds\n // Module-declared custom entities (ce.ts) carry frozen system-style ids and are never\n // registered in `custom_entities`, so classification checks the declared registry plus\n // active registrations. System (table-backed) ids are rejected above; for the allowed\n // set `isCustomEntity` drives mapRow's cf_ stripping so the edit form reads back values.\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'view', isCustomEntity, rbac })\n if (organizationIds && organizationIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 0 })\n }\n const normalizeCustomEntityValue = (value: unknown) => {\n if (Array.isArray(value)) {\n return value.map((entry) => {\n if (typeof entry !== 'string') return entry\n const parsed = parseBooleanToken(entry)\n return parsed === null ? entry : parsed\n })\n }\n if (typeof value !== 'string') return value\n const parsed = parseBooleanToken(value)\n return parsed === null ? value : parsed\n }\n const mapRow = (row: any) => {\n if (!isCustomEntity || !row || typeof row !== 'object') return row\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(row)) {\n if (k.startsWith('cf_')) out[k.replace(/^cf_/, '')] = normalizeCustomEntityValue(v)\n else out[k] = v\n }\n return out\n }\n const mapFullRow = (row: any) => {\n if (!row || typeof row !== 'object') return row\n return { ...(row as Record<string, unknown>) }\n }\n // Build filters with awareness of custom-entity mode\n const filtersObj: Where<any> = {}\n const buildFilter = (key: string, val: string, allowAnyKey: boolean) => {\n if (key.startsWith('cf_')) {\n if (key.endsWith('In')) {\n const base = key.slice(0, -2)\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[base] = { $in: values }\n } else {\n if (val.includes(',')) {\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[key] = { $in: values }\n } else {\n const parsed = parseBooleanToken(val)\n ;(filtersObj as any)[key] = parsed === null ? val : parsed\n }\n }\n } else if (allowAnyKey) {\n if (val.includes(',')) {\n const values = val.split(',').map((s) => s.trim()).filter(Boolean)\n ;(filtersObj as any)[key] = { $in: values }\n } else {\n const parsed = parseBooleanToken(val)\n ;(filtersObj as any)[key] = parsed === null ? val : parsed\n }\n } else {\n if (['id', 'created_at', 'updated_at', 'deleted_at', 'name', 'title', 'email'].includes(key)) {\n ;(filtersObj as any)[key] = val\n }\n }\n }\n\n if (organizationIds && organizationIds.length) {\n (filtersObj as any).organization_id = { $in: organizationIds }\n }\n const qopts: QueryOptions = {\n tenantId: auth.tenantId!,\n includeCustomFields: true,\n page: { page, pageSize },\n sort: [{ field: sortField as any, dir: sortDir as any }] as Sort[],\n filters: filtersObj as any,\n withDeleted,\n }\n if (organizationIds && organizationIds.length) {\n qopts.organizationIds = organizationIds\n }\n // Allowed entities are doc-storage-backed by definition (system ids were rejected\n // above) \u2014 direct the engine to doc storage explicitly so reads stay deterministic\n // even before the first record exists.\n if (isCustomEntity) qopts.forceCustomEntityStorage = true\n for (const [k, v] of qpEntries) buildFilter(k, v, isCustomEntity)\n // Server-side full-result search: match the term against the requested fields\n // (defaults to `id`) before pagination so totals/exports stay consistent with\n // the active search instead of filtering only the current client page (#3229).\n if (searchTerm) {\n const fields = searchFields.length ? searchFields : ['id']\n const pattern = `%${searchTerm}%`\n const orClauses = fields.map((field) => ({ [field]: { $ilike: pattern } }))\n ;(filtersObj as any).$or = orClauses\n }\n const res = await qe.query(entityId as any, qopts)\n const rawItems = res.items || []\n const viewPageItems = rawItems.map(mapRow)\n const fullPageItems = rawItems.map(mapFullRow)\n\n // Expose `updated_at` on custom-entity records. The query engine returns only\n // the `doc` fields + `id`, dropping the base `updated_at` column \u2014 which made\n // optimistic locking impossible end-to-end (no version for the edit page to\n // round-trip as the lock header). Batch-read it from storage and merge it in.\n if (isCustomEntity && viewPageItems.length) {\n try {\n const recordIds = viewPageItems\n .map((it: any) => it?.id)\n .filter((v: any): v is string => typeof v === 'string' && v.length > 0)\n if (recordIds.length) {\n const db = em.getKysely()\n const rows = await db\n .selectFrom('custom_entities_storage' as any)\n .select(['entity_id' as any, 'updated_at' as any])\n .where('entity_type' as any, '=', entityId)\n .where('entity_id' as any, 'in', recordIds as any)\n .execute()\n const updatedById = new Map<string, string>()\n for (const row of rows as any[]) {\n const value = row?.updated_at\n const iso = value instanceof Date ? value.toISOString() : (typeof value === 'string' && value.length > 0 ? value : null)\n if (iso && row?.entity_id) updatedById.set(String(row.entity_id), iso)\n }\n for (const item of viewPageItems as any[]) {\n const iso = updatedById.get(String(item?.id))\n if (iso) {\n item.updated_at = iso\n item.updatedAt = iso\n }\n }\n }\n } catch { /* best-effort: locking simply will not engage if storage is unavailable */ }\n }\n\n const total = typeof res.total === 'number' ? res.total : rawItems.length\n const effectivePageSize = res.pageSize || pageSize\n const payload = {\n items: viewPageItems,\n total,\n page: res.page || page,\n pageSize: effectivePageSize,\n totalPages: Math.ceil(total / (effectivePageSize || 1)),\n }\n\n if (requestedExport) {\n let exportItems: any[] = exportFullRequested ? [...fullPageItems] : [...viewPageItems]\n if (total > exportItems.length) {\n let nextPage = 2\n while (exportItems.length < total) {\n const nextRes = await qe.query(entityId as any, {\n ...qopts,\n page: { page: nextPage, pageSize },\n })\n const nextRawItems = nextRes.items || []\n if (!nextRawItems.length) break\n const nextViewItems = nextRawItems.map(mapRow)\n const nextFullItems = nextRawItems.map(mapFullRow)\n const nextBatch = exportFullRequested ? nextFullItems : nextViewItems\n exportItems.push(...nextBatch)\n if (nextBatch.length < pageSize) break\n nextPage += 1\n }\n }\n const prepared = {\n columns: ensureColumns(exportItems),\n rows: exportItems,\n }\n const filenameBase = exportFullRequested ? `${entityId || 'records'}_full` : entityId || 'records'\n const serialized = serializeExport(prepared, requestedExport)\n const filename = defaultExportFilename(filenameBase, requestedExport)\n return new Response(serialized.body, {\n headers: {\n 'content-type': serialized.contentType,\n 'content-disposition': `attachment; filename=\"${filename}\"`,\n },\n })\n }\n\n return NextResponse.json(payload)\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n try { console.error('[entities.records.GET] Error', e) } catch {}\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst postBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1).optional(),\n values: z.record(z.string(), z.any()).default({}),\n})\n\nconst putBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n values: z.record(z.string(), z.any()).default({}),\n})\n\nconst mutationResponseSchema = z.object({\n ok: z.literal(true),\n item: z\n .object({\n entityId: z.string(),\n recordId: z.string(),\n })\n .optional(),\n})\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let json: unknown\n try { json = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = postBodySchema.safeParse(json)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const { entityId } = parsed.data\n let { recordId, values } = parsed.data as { recordId?: string; values: Record<string, any> }\n\n try {\n const { resolve } = await createRequestContainer()\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n // Strip reserved record/system columns the edit form echoes back from the loaded record\n // (`id`, plus `updated_at`/`updatedAt` used for optimistic locking). They are not custom\n // fields; without this they validate as cf_id / cf_updated_at / cf_updatedAt and are\n // rejected as \"Unknown custom field\", which fails EVERY custom-entity edit-form save.\n for (const reservedKey of ['id', 'created_at', 'createdAt', 'updated_at', 'updatedAt', 'deleted_at', 'deletedAt']) {\n delete (values as any)[reservedKey]\n }\n const norm = normalizeValues(values)\n\n // Validate against custom field definitions\n try {\n const { validateCustomFieldValuesServer } = await import('../lib/validation')\n const check = await validateCustomFieldValuesServer(em, { entityId, organizationId: targetOrgId, tenantId: auth.tenantId!, values: norm, rejectUndeclaredKeys: true })\n if (!check.ok) return NextResponse.json({ error: 'Validation failed', fields: check.fieldErrors }, { status: 400 })\n } catch { /* ignore if helper missing */ }\n\n const normalizedRecordId = (() => {\n const raw = String(recordId || '').trim()\n if (!raw) return undefined\n const low = raw.toLowerCase()\n if (low === 'create' || low === 'new' || low === 'null' || low === 'undefined') return undefined\n // Enforce UUID only; any non-uuid is ignored so we generate one in the DE\n const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n return uuid.test(raw) ? raw : undefined\n })()\n const { id } = await de.createCustomEntityRecord({\n entityId,\n recordId: normalizedRecordId,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n\n return NextResponse.json({ ok: true, item: { entityId, recordId: id } })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n try { console.error('[entities.records.POST] Error', e) } catch {}\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\n// Avoid zod here to prevent runtime import issues in some environments\nfunction parsePutBody(json: any): { ok: true; data: { entityId: string; recordId: string; values: Record<string, any> } } | { ok: false; error: string } {\n if (!json || typeof json !== 'object') return { ok: false, error: 'Invalid JSON' }\n const entityId = typeof json.entityId === 'string' && json.entityId.length ? json.entityId : ''\n const recordId = typeof json.recordId === 'string' && json.recordId.length ? json.recordId : ''\n const values = (json.values && typeof json.values === 'object') ? json.values as Record<string, any> : {}\n if (!entityId || !recordId) return { ok: false, error: 'entityId and recordId are required' }\n return { ok: true, data: { entityId, recordId, values } }\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let json: any\n try { json = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = parsePutBody(json)\n if (!parsed.ok) return NextResponse.json({ error: parsed.error }, { status: 400 })\n const { entityId, recordId, values } = parsed.data\n\n try {\n const container = await createRequestContainer()\n const { resolve } = container\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n // Strip reserved record/system columns the edit form echoes back from the loaded record\n // (`id`, plus `updated_at`/`updatedAt` used for optimistic locking). They are not custom\n // fields; without this they validate as cf_id / cf_updated_at / cf_updatedAt and are\n // rejected as \"Unknown custom field\", which fails EVERY custom-entity edit-form save.\n for (const reservedKey of ['id', 'created_at', 'createdAt', 'updated_at', 'updatedAt', 'deleted_at', 'deletedAt']) {\n delete (values as any)[reservedKey]\n }\n const norm = normalizeValues(values)\n\n // Validate against custom field definitions\n try {\n const { validateCustomFieldValuesServer } = await import('../lib/validation')\n const check = await validateCustomFieldValuesServer(em, { entityId, organizationId: targetOrgId, tenantId: auth.tenantId!, values: norm, rejectUndeclaredKeys: true })\n if (!check.ok) return NextResponse.json({ error: 'Validation failed', fields: check.fieldErrors }, { status: 400 })\n } catch { /* ignore if helper missing */ }\n\n // Normalize recordId: if blank/sentinel/non-uuid => create instead of update\n const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n const rid = String(recordId || '').trim()\n const low = rid.toLowerCase()\n const isSentinel = !rid || low === 'create' || low === 'new' || low === 'null' || low === 'undefined'\n const isUuid = uuidRe.test(rid)\n if (isSentinel || !isUuid) {\n const created = await de.createCustomEntityRecord({\n entityId,\n recordId: undefined,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n return NextResponse.json({ ok: true, item: { entityId, recordId: created.id } })\n }\n\n try {\n const currentUpdatedAt = await readCustomEntityRecordUpdatedAt(em, {\n entityType: entityId,\n entityId: rid,\n organizationId: targetOrgId,\n })\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,\n resourceId: rid,\n current: currentUpdatedAt,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n\n await de.updateCustomEntityRecord({\n entityId,\n recordId: rid,\n organizationId: targetOrgId,\n tenantId: auth.tenantId!,\n values: norm,\n })\n return NextResponse.json({ ok: true, item: { entityId, recordId: rid } })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst deleteBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n})\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const qpEntityId = url.searchParams.get('entityId')\n const qpRecordId = url.searchParams.get('recordId')\n let payload: any = qpEntityId && qpRecordId ? { entityId: qpEntityId, recordId: qpRecordId } : null\n if (!payload) {\n try { payload = await req.json() } catch { payload = null }\n }\n const parsed = deleteBodySchema.safeParse(payload)\n if (!parsed.success) return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n const { entityId, recordId } = parsed.data\n\n try {\n const container = await createRequestContainer()\n const { resolve } = container\n const de = resolve('dataEngine') as any\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as RbacService\n const scope = await resolveOrganizationScope({ em, rbac, auth, selectedId: getSelectedOrganizationFromRequest(req) })\n const targetOrgId = scope.selectedId ?? auth.orgId\n if (!targetOrgId) return NextResponse.json({ error: 'Organization context is required' }, { status: 400 })\n const entityKind = await classifyRecordsEntity(em, entityId)\n if (entityKind === 'system') return systemEntityRecordsRejection(entityId)\n const isCustomEntity = entityKind === 'custom'\n await assertEntityAclForRequest({ auth, entityId, action: 'manage', isCustomEntity, rbac })\n\n try {\n const currentUpdatedAt = await readCustomEntityRecordUpdatedAt(em, {\n entityType: entityId,\n entityId: recordId,\n organizationId: targetOrgId,\n })\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,\n resourceId: recordId,\n current: currentUpdatedAt,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n\n await de.deleteCustomEntityRecord({ entityId, recordId, organizationId: targetOrgId, tenantId: auth.tenantId!, soft: true })\n return NextResponse.json({ ok: true })\n } catch (e) {\n if (isCrudHttpError(e)) return NextResponse.json(e.body, { status: e.status })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nfunction normalizeValues(input: Record<string, any>): Record<string, any> {\n const out: Record<string, any> = {}\n for (const [k, v] of Object.entries(input || {})) {\n const key = k.startsWith('cf_') ? k.replace(/^cf_/, '') : k\n out[key] = v\n }\n return out\n}\n\nconst deleteResponseSchema = z.object({\n ok: z.literal(true),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n}).passthrough()\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'CRUD operations on entity records',\n methods: {\n GET: {\n summary: 'List records',\n description:\n 'Returns paginated records for the supplied entity. Supports custom field filters, exports, and soft-delete toggles.',\n query: listRecordsQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Paginated records',\n schema: listRecordsResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n POST: {\n summary: 'Create record',\n description:\n 'Creates a record for the given entity. When `recordId` is omitted or not a UUID the data engine will generate one automatically.',\n requestBody: {\n contentType: 'application/json',\n schema: postBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record created',\n schema: mutationResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failure',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n PUT: {\n summary: 'Update record',\n description:\n 'Updates an existing record. If the provided recordId is not a UUID the record will be created instead to support optimistic flows.',\n requestBody: {\n contentType: 'application/json',\n schema: putBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record updated',\n schema: mutationResponseSchema,\n },\n {\n status: 400,\n description: 'Validation failure',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n DELETE: {\n summary: 'Delete record',\n description: 'Soft deletes the specified record within the current tenant/org scope.',\n requestBody: {\n contentType: 'application/json',\n schema: deleteBodySchema,\n },\n responses: [\n {\n status: 200,\n description: 'Record deleted',\n schema: deleteResponseSchema,\n },\n {\n status: 400,\n description: 'Missing entity id or record id',\n schema: errorSchema,\n },\n {\n status: 401,\n description: 'Missing authentication',\n schema: errorSchema,\n },\n {\n status: 404,\n description: 'Record not found',\n schema: errorSchema,\n },\n {\n status: 500,\n description: 'Unexpected failure',\n schema: errorSchema,\n },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["parsed"]
7
7
  }
@@ -3,7 +3,7 @@ import { Tenant } from "@open-mercato/core/modules/directory/data/entities";
3
3
  import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
4
4
  import { getOverrides } from "../../lib/queries.js";
5
5
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
6
- import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
6
+ import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
7
7
  import {
8
8
  runCrudMutationGuardAfterSuccess,
9
9
  validateCrudMutationGuard
@@ -81,7 +81,7 @@ async function PUT(req) {
81
81
  toggle: { id: parsed.data.toggleId }
82
82
  });
83
83
  if (existingOverride) {
84
- enforceCommandOptimisticLock({
84
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
85
85
  resourceKind: "feature_toggles.feature_toggle_override",
86
86
  resourceId: existingOverride.id,
87
87
  current: existingOverride.updatedAt ?? null,
@@ -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": "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",
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
  }