@open-mercato/core 0.10.1-develop.7736.1.88e07906bf → 0.10.1-develop.7748.1.d745ba61aa

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 (198) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/api_key/index.js +2 -0
  3. package/dist/generated/entities/api_key/index.js.map +2 -2
  4. package/dist/generated/entities/attachment_partition/index.js +2 -0
  5. package/dist/generated/entities/attachment_partition/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +2 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/helpers/integration/attachmentAccessFixtures.js +144 -0
  9. package/dist/helpers/integration/attachmentAccessFixtures.js.map +7 -0
  10. package/dist/modules/api_keys/data/entities.js +7 -0
  11. package/dist/modules/api_keys/data/entities.js.map +2 -2
  12. package/dist/modules/api_keys/migrations/Migration20261012202110_api_keys.js +30 -0
  13. package/dist/modules/api_keys/migrations/Migration20261012202110_api_keys.js.map +7 -0
  14. package/dist/modules/api_keys/services/apiKeyService.js +31 -9
  15. package/dist/modules/api_keys/services/apiKeyService.js.map +2 -2
  16. package/dist/modules/attachments/api/file/[id]/route.js +16 -10
  17. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  18. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +15 -9
  19. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  20. package/dist/modules/attachments/api/library/[id]/route.js +100 -56
  21. package/dist/modules/attachments/api/library/[id]/route.js.map +3 -3
  22. package/dist/modules/attachments/api/library/route.js +56 -17
  23. package/dist/modules/attachments/api/library/route.js.map +2 -2
  24. package/dist/modules/attachments/api/partitions/route.js +2 -0
  25. package/dist/modules/attachments/api/partitions/route.js.map +2 -2
  26. package/dist/modules/attachments/api/route.js +74 -34
  27. package/dist/modules/attachments/api/route.js.map +3 -3
  28. package/dist/modules/attachments/api/transfer/route.js +63 -37
  29. package/dist/modules/attachments/api/transfer/route.js.map +2 -2
  30. package/dist/modules/attachments/data/entities.js +3 -0
  31. package/dist/modules/attachments/data/entities.js.map +2 -2
  32. package/dist/modules/attachments/data/validators.js +41 -0
  33. package/dist/modules/attachments/data/validators.js.map +7 -0
  34. package/dist/modules/attachments/di.js +5 -2
  35. package/dist/modules/attachments/di.js.map +2 -2
  36. package/dist/modules/attachments/generators.js +29 -0
  37. package/dist/modules/attachments/generators.js.map +7 -0
  38. package/dist/modules/attachments/index.js +8 -1
  39. package/dist/modules/attachments/index.js.map +2 -2
  40. package/dist/modules/attachments/lib/access-errors.js +35 -0
  41. package/dist/modules/attachments/lib/access-errors.js.map +7 -0
  42. package/dist/modules/attachments/lib/access-mutation.js +23 -0
  43. package/dist/modules/attachments/lib/access-mutation.js.map +7 -0
  44. package/dist/modules/attachments/lib/access-protection.js +73 -0
  45. package/dist/modules/attachments/lib/access-protection.js.map +7 -0
  46. package/dist/modules/attachments/lib/access-query.js +74 -0
  47. package/dist/modules/attachments/lib/access-query.js.map +7 -0
  48. package/dist/modules/attachments/lib/access-registry.js +87 -0
  49. package/dist/modules/attachments/lib/access-registry.js.map +7 -0
  50. package/dist/modules/attachments/lib/access-runner.js +161 -0
  51. package/dist/modules/attachments/lib/access-runner.js.map +7 -0
  52. package/dist/modules/attachments/lib/access-types.js +1 -0
  53. package/dist/modules/attachments/lib/access-types.js.map +7 -0
  54. package/dist/modules/attachments/lib/attachment-service.js +14 -6
  55. package/dist/modules/attachments/lib/attachment-service.js.map +2 -2
  56. package/dist/modules/attachments/lib/createFromBuffer.js +2 -0
  57. package/dist/modules/attachments/lib/createFromBuffer.js.map +2 -2
  58. package/dist/modules/attachments/lib/partitions.js +3 -2
  59. package/dist/modules/attachments/lib/partitions.js.map +2 -2
  60. package/dist/modules/attachments/lib/scoped-upload-service.js +2 -0
  61. package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
  62. package/dist/modules/attachments/lib/target-access-service.js +12 -3
  63. package/dist/modules/attachments/lib/target-access-service.js.map +2 -2
  64. package/dist/modules/attachments/migrations/Migration20261001120000_attachments.js +38 -0
  65. package/dist/modules/attachments/migrations/Migration20261001120000_attachments.js.map +7 -0
  66. package/dist/modules/attachments/setup.js +10 -0
  67. package/dist/modules/attachments/setup.js.map +2 -2
  68. package/dist/modules/notifications/api/[id]/action/route.js +8 -0
  69. package/dist/modules/notifications/api/[id]/action/route.js.map +2 -2
  70. package/dist/modules/notifications/api/route.js +17 -0
  71. package/dist/modules/notifications/api/route.js.map +2 -2
  72. package/dist/modules/notifications/data/validators.js +1 -1
  73. package/dist/modules/notifications/data/validators.js.map +2 -2
  74. package/dist/modules/notifications/lib/notificationService.js +45 -3
  75. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  76. package/dist/modules/notifications/lib/routeHelpers.js +27 -0
  77. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  78. package/dist/modules/payment_gateways/lib/gateway-service.js +50 -34
  79. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  80. package/dist/modules/payment_gateways/lib/status-machine.js +10 -1
  81. package/dist/modules/payment_gateways/lib/status-machine.js.map +2 -2
  82. package/dist/modules/payment_gateways/lib/webhook-processor.js +1 -1
  83. package/dist/modules/payment_gateways/lib/webhook-processor.js.map +2 -2
  84. package/dist/modules/staff/notifications.js +4 -2
  85. package/dist/modules/staff/notifications.js.map +2 -2
  86. package/dist/modules/workflows/api/portal/tasks/[id]/complete/route.js +1 -1
  87. package/dist/modules/workflows/api/portal/tasks/[id]/complete/route.js.map +2 -2
  88. package/dist/modules/workflows/api/tasks/[id]/complete/route.js +7 -1
  89. package/dist/modules/workflows/api/tasks/[id]/complete/route.js.map +2 -2
  90. package/dist/modules/workflows/backend/tasks/[id]/page.js +2 -1
  91. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  92. package/dist/modules/workflows/data/entities.js.map +2 -2
  93. package/dist/modules/workflows/lib/activity-executor.js +18 -4
  94. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  95. package/dist/modules/workflows/lib/activity-types.js +2 -2
  96. package/dist/modules/workflows/lib/activity-types.js.map +2 -2
  97. package/dist/modules/workflows/lib/condition-handler.js +7 -1
  98. package/dist/modules/workflows/lib/condition-handler.js.map +2 -2
  99. package/dist/modules/workflows/lib/duration.js +13 -0
  100. package/dist/modules/workflows/lib/duration.js.map +2 -2
  101. package/dist/modules/workflows/lib/instance-status.js +16 -0
  102. package/dist/modules/workflows/lib/instance-status.js.map +7 -0
  103. package/dist/modules/workflows/lib/step-handler.js +9 -2
  104. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  105. package/dist/modules/workflows/lib/task-handler.js +16 -8
  106. package/dist/modules/workflows/lib/task-handler.js.map +2 -2
  107. package/dist/modules/workflows/lib/task-quick-action.js +13 -10
  108. package/dist/modules/workflows/lib/task-quick-action.js.map +2 -2
  109. package/dist/modules/workflows/lib/task-sla.js +10 -0
  110. package/dist/modules/workflows/lib/task-sla.js.map +2 -2
  111. package/dist/modules/workflows/lib/transition-handler.js +2 -1
  112. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  113. package/dist/modules/workflows/lib/workflow-executor.js +29 -1
  114. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  115. package/dist/modules/workflows/notifications.js +4 -0
  116. package/dist/modules/workflows/notifications.js.map +2 -2
  117. package/generated/entities/api_key/index.ts +1 -0
  118. package/generated/entities/attachment_partition/index.ts +1 -0
  119. package/generated/entity-fields-registry.ts +2 -0
  120. package/package.json +7 -7
  121. package/src/helpers/integration/attachmentAccessFixtures.ts +141 -0
  122. package/src/modules/api_keys/data/entities.ts +25 -1
  123. package/src/modules/api_keys/migrations/.snapshot-open-mercato.json +234 -68
  124. package/src/modules/api_keys/migrations/Migration20261012202110_api_keys.ts +47 -0
  125. package/src/modules/api_keys/services/apiKeyService.ts +55 -9
  126. package/src/modules/attachments/api/file/[id]/route.ts +12 -9
  127. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +10 -8
  128. package/src/modules/attachments/api/library/[id]/route.ts +87 -58
  129. package/src/modules/attachments/api/library/route.ts +40 -11
  130. package/src/modules/attachments/api/partitions/route.ts +2 -0
  131. package/src/modules/attachments/api/route.ts +68 -35
  132. package/src/modules/attachments/api/transfer/route.ts +65 -42
  133. package/src/modules/attachments/data/entities.ts +4 -0
  134. package/src/modules/attachments/data/validators.ts +36 -0
  135. package/src/modules/attachments/di.ts +4 -1
  136. package/src/modules/attachments/generators.ts +27 -0
  137. package/src/modules/attachments/i18n/de.json +4 -0
  138. package/src/modules/attachments/i18n/en.json +4 -0
  139. package/src/modules/attachments/i18n/es.json +4 -0
  140. package/src/modules/attachments/i18n/ko.json +4 -0
  141. package/src/modules/attachments/i18n/pl.json +4 -0
  142. package/src/modules/attachments/index.ts +18 -0
  143. package/src/modules/attachments/lib/access-errors.ts +32 -0
  144. package/src/modules/attachments/lib/access-mutation.ts +26 -0
  145. package/src/modules/attachments/lib/access-protection.ts +83 -0
  146. package/src/modules/attachments/lib/access-query.ts +86 -0
  147. package/src/modules/attachments/lib/access-registry.ts +117 -0
  148. package/src/modules/attachments/lib/access-runner.ts +183 -0
  149. package/src/modules/attachments/lib/access-types.ts +78 -0
  150. package/src/modules/attachments/lib/attachment-service.ts +8 -4
  151. package/src/modules/attachments/lib/createFromBuffer.ts +3 -0
  152. package/src/modules/attachments/lib/partitions.ts +3 -2
  153. package/src/modules/attachments/lib/scoped-upload-service.ts +2 -0
  154. package/src/modules/attachments/lib/target-access-service.ts +10 -3
  155. package/src/modules/attachments/migrations/.snapshot-open-mercato.json +17 -0
  156. package/src/modules/attachments/migrations/Migration20261001120000_attachments.ts +35 -0
  157. package/src/modules/attachments/setup.ts +10 -0
  158. package/src/modules/auth/i18n/de.json +3 -0
  159. package/src/modules/auth/i18n/en.json +3 -0
  160. package/src/modules/auth/i18n/es.json +3 -0
  161. package/src/modules/auth/i18n/ko.json +3 -0
  162. package/src/modules/auth/i18n/pl.json +3 -0
  163. package/src/modules/notifications/api/[id]/action/route.ts +8 -0
  164. package/src/modules/notifications/api/route.ts +17 -0
  165. package/src/modules/notifications/data/validators.ts +1 -1
  166. package/src/modules/notifications/i18n/de.json +2 -0
  167. package/src/modules/notifications/i18n/en.json +2 -0
  168. package/src/modules/notifications/i18n/es.json +2 -0
  169. package/src/modules/notifications/i18n/ko.json +2 -0
  170. package/src/modules/notifications/i18n/pl.json +2 -0
  171. package/src/modules/notifications/lib/notificationService.ts +67 -1
  172. package/src/modules/notifications/lib/routeHelpers.ts +38 -0
  173. package/src/modules/payment_gateways/lib/gateway-service.ts +50 -33
  174. package/src/modules/payment_gateways/lib/status-machine.ts +19 -0
  175. package/src/modules/payment_gateways/lib/webhook-processor.ts +1 -1
  176. package/src/modules/staff/notifications.ts +2 -0
  177. package/src/modules/workflows/AGENTS.md +2 -2
  178. package/src/modules/workflows/api/portal/tasks/[id]/complete/route.ts +5 -1
  179. package/src/modules/workflows/api/tasks/[id]/complete/route.ts +7 -1
  180. package/src/modules/workflows/backend/tasks/[id]/page.tsx +6 -2
  181. package/src/modules/workflows/data/entities.ts +6 -0
  182. package/src/modules/workflows/i18n/de.json +2 -0
  183. package/src/modules/workflows/i18n/en.json +2 -0
  184. package/src/modules/workflows/i18n/es.json +2 -0
  185. package/src/modules/workflows/i18n/ko.json +2 -0
  186. package/src/modules/workflows/i18n/pl.json +2 -0
  187. package/src/modules/workflows/lib/activity-executor.ts +36 -4
  188. package/src/modules/workflows/lib/activity-types.ts +2 -2
  189. package/src/modules/workflows/lib/condition-handler.ts +16 -1
  190. package/src/modules/workflows/lib/duration.ts +25 -0
  191. package/src/modules/workflows/lib/instance-status.ts +29 -0
  192. package/src/modules/workflows/lib/step-handler.ts +16 -1
  193. package/src/modules/workflows/lib/task-handler.ts +18 -10
  194. package/src/modules/workflows/lib/task-quick-action.ts +19 -10
  195. package/src/modules/workflows/lib/task-sla.ts +13 -0
  196. package/src/modules/workflows/lib/transition-handler.ts +2 -1
  197. package/src/modules/workflows/lib/workflow-executor.ts +44 -1
  198. package/src/modules/workflows/notifications.ts +4 -0
@@ -1,3 +1,4 @@
1
+ import { createAttachmentAccessContext } from "./lib/access-runner.js";
1
2
  import { asFunction, asValue } from "awilix";
2
3
  import { StorageDriverFactory } from "./lib/drivers/driverFactory.js";
3
4
  import { DefaultAttachmentService } from "./lib/attachment-service.js";
@@ -6,13 +7,14 @@ import { scheduleAttachmentQuotaRecovery } from "./lib/quota-recovery-queue.js";
6
7
  import { AttachmentTargetAccessService } from "./lib/target-access-service.js";
7
8
  import { ScopedAttachmentUploadService } from "./lib/scoped-upload-service.js";
8
9
  function register(container) {
10
+ const accessContext = () => createAttachmentAccessContext(container);
9
11
  container.register({
10
12
  attachmentQuotaRecoveryScheduler: asValue(scheduleAttachmentQuotaRecovery),
11
13
  attachmentQuotaService: asFunction(
12
14
  ({ em }) => createAttachmentQuotaService(em)
13
15
  ).scoped().proxy(),
14
16
  attachmentTargetAccessService: asFunction(
15
- ({ em }) => new AttachmentTargetAccessService(em)
17
+ ({ em }) => new AttachmentTargetAccessService(em, accessContext)
16
18
  ).scoped().proxy(),
17
19
  attachmentScopedUploadService: asFunction(({
18
20
  em,
@@ -37,7 +39,8 @@ function register(container) {
37
39
  attachmentService: asFunction((cradle) => new DefaultAttachmentService(
38
40
  cradle.em,
39
41
  cradle.storageDriverFactory,
40
- () => cradle.attachmentScopedUploadService ?? null
42
+ () => cradle.attachmentScopedUploadService ?? null,
43
+ accessContext
41
44
  )).scoped().proxy()
42
45
  });
43
46
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/attachments/di.ts"],
4
- "sourcesContent": ["import { asFunction, asValue } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { StorageDriverFactory } from './lib/drivers/driverFactory'\nimport { DefaultAttachmentService } from './lib/attachment-service'\nimport { createAttachmentQuotaService, type AttachmentQuotaService } from './lib/quota-service'\nimport { scheduleAttachmentQuotaRecovery } from './lib/quota-recovery-queue'\nimport { AttachmentTargetAccessService } from './lib/target-access-service'\nimport { ScopedAttachmentUploadService } from './lib/scoped-upload-service'\n\nexport function register(container: AppContainer) {\n container.register({\n attachmentQuotaRecoveryScheduler: asValue(scheduleAttachmentQuotaRecovery),\n attachmentQuotaService: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n createAttachmentQuotaService(em),\n )\n .scoped()\n .proxy(),\n attachmentTargetAccessService: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n new AttachmentTargetAccessService(em),\n )\n .scoped()\n .proxy(),\n attachmentScopedUploadService: asFunction(({\n em,\n dataEngine,\n storageDriverFactory,\n attachmentQuotaService,\n attachmentQuotaRecoveryScheduler,\n }: {\n em: ConstructorParameters<typeof StorageDriverFactory>[0]\n dataEngine: DataEngine\n storageDriverFactory: StorageDriverFactory\n attachmentQuotaService: AttachmentQuotaService\n attachmentQuotaRecoveryScheduler: typeof scheduleAttachmentQuotaRecovery\n }) => new ScopedAttachmentUploadService({\n em,\n dataEngine,\n storageDriverFactory,\n attachmentQuotaService,\n attachmentQuotaRecoveryScheduler,\n }))\n .scoped()\n .proxy(),\n storageDriverFactory: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n new StorageDriverFactory(em),\n )\n .singleton()\n .proxy(),\n // The scoped upload service is handed over as a lazy resolver so module\n // uploads share the public attachment route's single fenced reservation\n // ledger, without dragging that service's own dependencies into every\n // container that merely constructs an `attachmentService`.\n attachmentService: asFunction((cradle: {\n em: ConstructorParameters<typeof DefaultAttachmentService>[0]\n storageDriverFactory: ConstructorParameters<typeof DefaultAttachmentService>[1]\n attachmentScopedUploadService: ScopedAttachmentUploadService\n }) => new DefaultAttachmentService(\n cradle.em,\n cradle.storageDriverFactory,\n () => cradle.attachmentScopedUploadService ?? null,\n ))\n .scoped()\n .proxy(),\n })\n}\n"],
5
- "mappings": "AAAA,SAAS,YAAY,eAAe;AAGpC,SAAS,4BAA4B;AACrC,SAAS,gCAAgC;AACzC,SAAS,oCAAiE;AAC1E,SAAS,uCAAuC;AAChD,SAAS,qCAAqC;AAC9C,SAAS,qCAAqC;AAEvC,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS;AAAA,IACjB,kCAAkC,QAAQ,+BAA+B;AAAA,IACzE,wBAAwB;AAAA,MAAW,CAAC,EAAE,GAAG,MACvC,6BAA6B,EAAE;AAAA,IACjC,EACG,OAAO,EACP,MAAM;AAAA,IACT,+BAA+B;AAAA,MAAW,CAAC,EAAE,GAAG,MAC9C,IAAI,8BAA8B,EAAE;AAAA,IACtC,EACG,OAAO,EACP,MAAM;AAAA,IACT,+BAA+B,WAAW,CAAC;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAMM,IAAI,8BAA8B;AAAA,MACtC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,CAAC,EACC,OAAO,EACP,MAAM;AAAA,IACT,sBAAsB;AAAA,MAAW,CAAC,EAAE,GAAG,MACrC,IAAI,qBAAqB,EAAE;AAAA,IAC7B,EACG,UAAU,EACV,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,IAKT,mBAAmB,WAAW,CAAC,WAIzB,IAAI;AAAA,MACR,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM,OAAO,iCAAiC;AAAA,IAChD,CAAC,EACE,OAAO,EACP,MAAM;AAAA,EACX,CAAC;AACH;",
4
+ "sourcesContent": ["import { createAttachmentAccessContext } from './lib/access-runner'\nimport { asFunction, asValue } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { StorageDriverFactory } from './lib/drivers/driverFactory'\nimport { DefaultAttachmentService } from './lib/attachment-service'\nimport { createAttachmentQuotaService, type AttachmentQuotaService } from './lib/quota-service'\nimport { scheduleAttachmentQuotaRecovery } from './lib/quota-recovery-queue'\nimport { AttachmentTargetAccessService } from './lib/target-access-service'\nimport { ScopedAttachmentUploadService } from './lib/scoped-upload-service'\n\nexport function register(container: AppContainer) {\n const accessContext = () => createAttachmentAccessContext(container)\n container.register({\n attachmentQuotaRecoveryScheduler: asValue(scheduleAttachmentQuotaRecovery),\n attachmentQuotaService: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n createAttachmentQuotaService(em),\n )\n .scoped()\n .proxy(),\n attachmentTargetAccessService: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n new AttachmentTargetAccessService(em, accessContext),\n )\n .scoped()\n .proxy(),\n attachmentScopedUploadService: asFunction(({\n em,\n dataEngine,\n storageDriverFactory,\n attachmentQuotaService,\n attachmentQuotaRecoveryScheduler,\n }: {\n em: ConstructorParameters<typeof StorageDriverFactory>[0]\n dataEngine: DataEngine\n storageDriverFactory: StorageDriverFactory\n attachmentQuotaService: AttachmentQuotaService\n attachmentQuotaRecoveryScheduler: typeof scheduleAttachmentQuotaRecovery\n }) => new ScopedAttachmentUploadService({\n em,\n dataEngine,\n storageDriverFactory,\n attachmentQuotaService,\n attachmentQuotaRecoveryScheduler,\n }))\n .scoped()\n .proxy(),\n storageDriverFactory: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n new StorageDriverFactory(em),\n )\n .singleton()\n .proxy(),\n // The scoped upload service is handed over as a lazy resolver so module\n // uploads share the public attachment route's single fenced reservation\n // ledger, without dragging that service's own dependencies into every\n // container that merely constructs an `attachmentService`.\n attachmentService: asFunction((cradle: {\n em: ConstructorParameters<typeof DefaultAttachmentService>[0]\n storageDriverFactory: ConstructorParameters<typeof DefaultAttachmentService>[1]\n attachmentScopedUploadService: ScopedAttachmentUploadService\n }) => new DefaultAttachmentService(\n cradle.em,\n cradle.storageDriverFactory,\n () => cradle.attachmentScopedUploadService ?? null,\n accessContext,\n ))\n .scoped()\n .proxy(),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,qCAAqC;AAC9C,SAAS,YAAY,eAAe;AAGpC,SAAS,4BAA4B;AACrC,SAAS,gCAAgC;AACzC,SAAS,oCAAiE;AAC1E,SAAS,uCAAuC;AAChD,SAAS,qCAAqC;AAC9C,SAAS,qCAAqC;AAEvC,SAAS,SAAS,WAAyB;AAChD,QAAM,gBAAgB,MAAM,8BAA8B,SAAS;AACnE,YAAU,SAAS;AAAA,IACjB,kCAAkC,QAAQ,+BAA+B;AAAA,IACzE,wBAAwB;AAAA,MAAW,CAAC,EAAE,GAAG,MACvC,6BAA6B,EAAE;AAAA,IACjC,EACG,OAAO,EACP,MAAM;AAAA,IACT,+BAA+B;AAAA,MAAW,CAAC,EAAE,GAAG,MAC9C,IAAI,8BAA8B,IAAI,aAAa;AAAA,IACrD,EACG,OAAO,EACP,MAAM;AAAA,IACT,+BAA+B,WAAW,CAAC;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAMM,IAAI,8BAA8B;AAAA,MACtC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,CAAC,EACC,OAAO,EACP,MAAM;AAAA,IACT,sBAAsB;AAAA,MAAW,CAAC,EAAE,GAAG,MACrC,IAAI,qBAAqB,EAAE;AAAA,IAC7B,EACG,UAAU,EACV,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,IAKT,mBAAmB,WAAW,CAAC,WAIzB,IAAI;AAAA,MACR,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM,OAAO,iCAAiC;AAAA,MAC9C;AAAA,IACF,CAAC,EACE,OAAO,EACP,MAAM;AAAA,EACX,CAAC;AACH;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,29 @@
1
+ const accessPlugin = {
2
+ id: "attachments.access-resolvers",
3
+ conventionFile: "data/attachment-access.ts",
4
+ importPrefix: "ATTACHMENT_ACCESS",
5
+ configExpr: (importName, moduleId) => `{ moduleId: '${moduleId}', resolvers: (${importName}.attachmentAccessResolvers ?? []), protectedTargets: (${importName}.protectedAttachmentTargets ?? []) }`,
6
+ outputFileName: "attachment-access.generated.ts",
7
+ buildOutput: ({ importSection, entriesLiteral }) => `// AUTO-GENERATED by mercato generate registry
8
+ import type { AttachmentAccessResolverEntry } from '@open-mercato/core/modules/attachments'
9
+ ${importSection}
10
+
11
+ export const attachmentAccessEntries: AttachmentAccessResolverEntry[] = [
12
+ ${entriesLiteral ? ` ${entriesLiteral}
13
+ ` : ""}]
14
+ `,
15
+ bootstrapRegistration: {
16
+ entriesExportName: "attachmentAccessEntries",
17
+ registrationImports: [
18
+ "import { registerAttachmentAccessResolvers } from '@open-mercato/core/modules/attachments/lib/access-registry'"
19
+ ],
20
+ buildCall: (entries) => `registerAttachmentAccessResolvers(${entries})`
21
+ }
22
+ };
23
+ const generatorPlugins = [accessPlugin];
24
+ var generators_default = generatorPlugins;
25
+ export {
26
+ generators_default as default,
27
+ generatorPlugins
28
+ };
29
+ //# sourceMappingURL=generators.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/attachments/generators.ts"],
4
+ "sourcesContent": ["import type { GeneratorPlugin } from '@open-mercato/shared/modules/generators'\n\nconst accessPlugin: GeneratorPlugin = {\n id: 'attachments.access-resolvers',\n conventionFile: 'data/attachment-access.ts',\n importPrefix: 'ATTACHMENT_ACCESS',\n configExpr: (importName, moduleId) =>\n `{ moduleId: '${moduleId}', resolvers: (${importName}.attachmentAccessResolvers ?? []), protectedTargets: (${importName}.protectedAttachmentTargets ?? []) }`,\n outputFileName: 'attachment-access.generated.ts',\n buildOutput: ({ importSection, entriesLiteral }) => `// AUTO-GENERATED by mercato generate registry\nimport type { AttachmentAccessResolverEntry } from '@open-mercato/core/modules/attachments'\n${importSection}\n\nexport const attachmentAccessEntries: AttachmentAccessResolverEntry[] = [\n${entriesLiteral ? ` ${entriesLiteral}\\n` : ''}]\n`,\n bootstrapRegistration: {\n entriesExportName: 'attachmentAccessEntries',\n registrationImports: [\n \"import { registerAttachmentAccessResolvers } from '@open-mercato/core/modules/attachments/lib/access-registry'\",\n ],\n buildCall: (entries) => `registerAttachmentAccessResolvers(${entries})`,\n },\n}\n\nexport const generatorPlugins: GeneratorPlugin[] = [accessPlugin]\nexport default generatorPlugins\n"],
5
+ "mappings": "AAEA,MAAM,eAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,YAAY,CAAC,YAAY,aACvB,gBAAgB,QAAQ,kBAAkB,UAAU,yDAAyD,UAAU;AAAA,EACzH,gBAAgB;AAAA,EAChB,aAAa,CAAC,EAAE,eAAe,eAAe,MAAM;AAAA;AAAA,EAEpD,aAAa;AAAA;AAAA;AAAA,EAGb,iBAAiB,KAAK,cAAc;AAAA,IAAO,EAAE;AAAA;AAAA,EAE7C,uBAAuB;AAAA,IACrB,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,MACnB;AAAA,IACF;AAAA,IACA,WAAW,CAAC,YAAY,qCAAqC,OAAO;AAAA,EACtE;AACF;AAEO,MAAM,mBAAsC,CAAC,YAAY;AAChE,IAAO,qBAAQ;",
6
+ "names": []
7
+ }
@@ -6,7 +6,14 @@ const metadata = {
6
6
  author: "Open Mercato Team",
7
7
  license: "MIT"
8
8
  };
9
+ import { registerAttachmentAccessResolvers } from "./lib/access-registry.js";
10
+ import { syncAttachmentAccessProtection } from "./lib/access-protection.js";
11
+ import { createAttachmentAccessContext, evaluateAttachmentAccess } from "./lib/access-runner.js";
9
12
  export {
10
- metadata
13
+ createAttachmentAccessContext,
14
+ evaluateAttachmentAccess,
15
+ metadata,
16
+ registerAttachmentAccessResolvers,
17
+ syncAttachmentAccessProtection
11
18
  };
12
19
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/attachments/index.ts"],
4
- "sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'attachments',\n title: 'Attachments',\n version: '0.1.0',\n description: 'File attachments and media management.',\n author: 'Open Mercato Team',\n license: 'MIT',\n}\n\nexport {\n type AttachmentOwner,\n type AttachmentProviderCleanup,\n type AttachmentService,\n type CreatedScopedAttachment,\n type CreateScopedAttachmentInput,\n type ReadScopedAttachmentInput,\n type ReadScopedAttachmentResult,\n type ReleaseScopedAttachmentInput,\n} from './lib/attachment-service'\n"],
5
- "mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AACX;",
4
+ "sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'attachments',\n title: 'Attachments',\n version: '0.1.0',\n description: 'File attachments and media management.',\n author: 'Open Mercato Team',\n license: 'MIT',\n}\n\nexport {\n type AttachmentOwner,\n type AttachmentProviderCleanup,\n type AttachmentService,\n type CreatedScopedAttachment,\n type CreateScopedAttachmentInput,\n type ReadScopedAttachmentInput,\n type ReadScopedAttachmentResult,\n type ReleaseScopedAttachmentInput,\n} from './lib/attachment-service'\n\nexport type {\n AttachmentAccessAction,\n AttachmentAccessDecision,\n AttachmentAccessInput,\n AttachmentAccessPartition,\n AttachmentAccessRecord,\n AttachmentAccessRequirement,\n AttachmentAccessResolver,\n AttachmentAccessResolverEntry,\n AttachmentAccessSubject,\n AttachmentAccessTarget,\n ProtectedAttachmentTarget,\n} from './lib/access-types'\nexport { registerAttachmentAccessResolvers } from './lib/access-registry'\nexport { syncAttachmentAccessProtection } from './lib/access-protection'\nexport { createAttachmentAccessContext, evaluateAttachmentAccess } from './lib/access-runner'\nexport type { AttachmentAccessContext, AttachmentAccessResult } from './lib/access-runner'\n"],
5
+ "mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AACX;AA0BA,SAAS,yCAAyC;AAClD,SAAS,sCAAsC;AAC/C,SAAS,+BAA+B,gCAAgC;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,35 @@
1
+ import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
2
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
3
+ async function attachmentAccessErrorBody(status) {
4
+ const { t } = await resolveTranslations();
5
+ const key = status === 401 ? "unauthorized" : status === 404 ? "notFound" : status === 504 ? "timeout" : "forbidden";
6
+ return { error: t(`attachments.access.${key}`) };
7
+ }
8
+ async function attachmentAccessErrorResponse(decision) {
9
+ return Response.json(await attachmentAccessErrorBody(decision.status), {
10
+ status: decision.status,
11
+ headers: { "Cache-Control": "private, no-store" }
12
+ });
13
+ }
14
+ async function throwAttachmentAccessError(status) {
15
+ throw new CrudHttpError(status, await attachmentAccessErrorBody(status));
16
+ }
17
+ function withAttachmentAccessErrors(handler) {
18
+ return async (...args) => {
19
+ try {
20
+ return await handler(...args);
21
+ } catch (error) {
22
+ if (isCrudHttpError(error)) {
23
+ return Response.json(error.body, { status: error.status, headers: { "Cache-Control": "private, no-store" } });
24
+ }
25
+ throw error;
26
+ }
27
+ };
28
+ }
29
+ export {
30
+ attachmentAccessErrorBody,
31
+ attachmentAccessErrorResponse,
32
+ throwAttachmentAccessError,
33
+ withAttachmentAccessErrors
34
+ };
35
+ //# sourceMappingURL=access-errors.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/attachments/lib/access-errors.ts"],
4
+ "sourcesContent": ["import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { AttachmentAccessDecision } from './access-types'\n\nexport async function attachmentAccessErrorBody(status: number): Promise<{ error: string }> {\n const { t } = await resolveTranslations()\n const key = status === 401 ? 'unauthorized' : status === 404 ? 'notFound' : status === 504 ? 'timeout' : 'forbidden'\n return { error: t(`attachments.access.${key}`) }\n}\n\nexport async function attachmentAccessErrorResponse(decision: Extract<AttachmentAccessDecision, { ok: false }>): Promise<Response> {\n return Response.json(await attachmentAccessErrorBody(decision.status), {\n status: decision.status, headers: { 'Cache-Control': 'private, no-store' },\n })\n}\n\nexport async function throwAttachmentAccessError(status: number): Promise<never> {\n throw new CrudHttpError(status, await attachmentAccessErrorBody(status))\n}\n\nexport function withAttachmentAccessErrors<Arguments extends unknown[]>(handler: (...args: Arguments) => Promise<Response>) {\n return async (...args: Arguments): Promise<Response> => {\n try {\n return await handler(...args)\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status, headers: { 'Cache-Control': 'private, no-store' } })\n }\n throw error\n }\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AAGpC,eAAsB,0BAA0B,QAA4C;AAC1F,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,QAAM,MAAM,WAAW,MAAM,iBAAiB,WAAW,MAAM,aAAa,WAAW,MAAM,YAAY;AACzG,SAAO,EAAE,OAAO,EAAE,sBAAsB,GAAG,EAAE,EAAE;AACjD;AAEA,eAAsB,8BAA8B,UAA+E;AACjI,SAAO,SAAS,KAAK,MAAM,0BAA0B,SAAS,MAAM,GAAG;AAAA,IACrE,QAAQ,SAAS;AAAA,IAAQ,SAAS,EAAE,iBAAiB,oBAAoB;AAAA,EAC3E,CAAC;AACH;AAEA,eAAsB,2BAA2B,QAAgC;AAC/E,QAAM,IAAI,cAAc,QAAQ,MAAM,0BAA0B,MAAM,CAAC;AACzE;AAEO,SAAS,2BAAwD,SAAoD;AAC1H,SAAO,UAAU,SAAuC;AACtD,QAAI;AACF,aAAO,MAAM,QAAQ,GAAG,IAAI;AAAA,IAC9B,SAAS,OAAO;AACd,UAAI,gBAAgB,KAAK,GAAG;AAC1B,eAAO,SAAS,KAAK,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ,SAAS,EAAE,iBAAiB,oBAAoB,EAAE,CAAC;AAAA,MAC9G;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,23 @@
1
+ import { throwAttachmentAccessError } from "./access-errors.js";
2
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
3
+ import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
4
+ async function prepareAttachmentMutation(input) {
5
+ if (!input.auth.tenantId) return throwAttachmentAccessError(401);
6
+ const guard = await runRouteMutationGuards({
7
+ container: input.container,
8
+ req: input.req,
9
+ auth: { tenantId: input.auth.tenantId, organizationId: input.auth.orgId ?? null, userId: input.auth.sub },
10
+ input: {
11
+ resourceKind: "attachments:attachment",
12
+ resourceId: input.recordId,
13
+ operation: input.operation,
14
+ mutationPayload: input.payload
15
+ }
16
+ });
17
+ if (!guard.ok) throw new CrudHttpError(guard.errorStatus, guard.errorBody);
18
+ return guard;
19
+ }
20
+ export {
21
+ prepareAttachmentMutation
22
+ };
23
+ //# sourceMappingURL=access-mutation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/attachments/lib/access-mutation.ts"],
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { throwAttachmentAccessError } from './access-errors'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'\n\nexport async function prepareAttachmentMutation(input: {\n container: AwilixContainer\n req: Request\n auth: NonNullable<AuthContext>\n recordId?: string\n operation: 'update' | 'delete'\n payload?: Record<string, unknown>\n}) {\n if (!input.auth.tenantId) return throwAttachmentAccessError(401)\n const guard = await runRouteMutationGuards({\n container: input.container, req: input.req,\n auth: { tenantId: input.auth.tenantId, organizationId: input.auth.orgId ?? null, userId: input.auth.sub },\n input: {\n resourceKind: 'attachments:attachment', resourceId: input.recordId,\n operation: input.operation, mutationPayload: input.payload,\n },\n })\n if (!guard.ok) throw new CrudHttpError(guard.errorStatus, guard.errorBody)\n return guard\n}\n"],
5
+ "mappings": "AAEA,SAAS,kCAAkC;AAC3C,SAAS,qBAAqB;AAC9B,SAAS,8BAA8B;AAEvC,eAAsB,0BAA0B,OAO7C;AACD,MAAI,CAAC,MAAM,KAAK,SAAU,QAAO,2BAA2B,GAAG;AAC/D,QAAM,QAAQ,MAAM,uBAAuB;AAAA,IACzC,WAAW,MAAM;AAAA,IAAW,KAAK,MAAM;AAAA,IACvC,MAAM,EAAE,UAAU,MAAM,KAAK,UAAU,gBAAgB,MAAM,KAAK,SAAS,MAAM,QAAQ,MAAM,KAAK,IAAI;AAAA,IACxG,OAAO;AAAA,MACL,cAAc;AAAA,MAA0B,YAAY,MAAM;AAAA,MAC1D,WAAW,MAAM;AAAA,MAAW,iBAAiB,MAAM;AAAA,IACrD;AAAA,EACF,CAAC;AACD,MAAI,CAAC,MAAM,GAAI,OAAM,IAAI,cAAc,MAAM,aAAa,MAAM,SAAS;AACzE,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,73 @@
1
+ import { LockMode } from "@mikro-orm/core";
2
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
+ import { AttachmentPartition } from "../data/entities.js";
4
+ import { attachmentAccessRequirementsSchema } from "../data/validators.js";
5
+ import { getAttachmentAccessRegistry, matchesAttachmentAccessSelector } from "./access-registry.js";
6
+ function getDeclaredAttachmentAccessRequirements(partitionCode) {
7
+ const registry = getAttachmentAccessRegistry();
8
+ if (registry.invalid) throw new Error("[internal] Attachment access registry is invalid");
9
+ return registry.protectedTargets.filter((target) => matchesAttachmentAccessSelector(target.targetPartition, partitionCode)).map(({ resolverId, targetEntity }) => ({ resolverId, targetEntity }));
10
+ }
11
+ function mergeAttachmentAccessRequirements(current, required) {
12
+ const existing = attachmentAccessRequirementsSchema.parse(current ?? []);
13
+ const union = /* @__PURE__ */ new Map();
14
+ for (const requirement of [...existing, ...required]) {
15
+ union.set(JSON.stringify([requirement.resolverId, requirement.targetEntity]), { ...requirement });
16
+ }
17
+ return [...union.values()];
18
+ }
19
+ async function syncAttachmentAccessProtection(em, options = {}) {
20
+ const registry = getAttachmentAccessRegistry();
21
+ if (registry.invalid) throw new Error("[internal] Attachment access registry is invalid");
22
+ if (!registry.protectedTargets.length) return 0;
23
+ const isolated = em.fork({ clear: true, useContext: false });
24
+ const candidates = await findWithDecryption(
25
+ isolated,
26
+ AttachmentPartition,
27
+ options.partitionCode ? { code: options.partitionCode } : {},
28
+ { orderBy: { id: "asc" } },
29
+ { tenantId: null, organizationId: null }
30
+ );
31
+ if (options.partitionCode && getDeclaredAttachmentAccessRequirements(options.partitionCode).length && !candidates.length) {
32
+ throw new Error("[internal] Protected attachment partition is not durably configured");
33
+ }
34
+ const pending = candidates.filter((partition) => {
35
+ const required = getDeclaredAttachmentAccessRequirements(partition.code);
36
+ if (!required.length) return false;
37
+ const existing = attachmentAccessRequirementsSchema.parse(partition.accessResolverRequirements ?? []);
38
+ return JSON.stringify(mergeAttachmentAccessRequirements(existing, required)) !== JSON.stringify(existing);
39
+ });
40
+ if (!pending.length) return 0;
41
+ return isolated.transactional(async (transaction) => {
42
+ let changed = 0;
43
+ for (const candidate of pending) {
44
+ const partition = await findOneWithDecryption(transaction, AttachmentPartition, { id: candidate.id }, {
45
+ lockMode: LockMode.PESSIMISTIC_WRITE,
46
+ refresh: true
47
+ }, { tenantId: null, organizationId: null });
48
+ if (!partition) throw new Error("[internal] Attachment partition disappeared during access protection synchronization");
49
+ const existing = attachmentAccessRequirementsSchema.parse(partition.accessResolverRequirements ?? []);
50
+ const merged = mergeAttachmentAccessRequirements(existing, getDeclaredAttachmentAccessRequirements(partition.code));
51
+ if (JSON.stringify(merged) === JSON.stringify(existing)) continue;
52
+ partition.accessResolverRequirements = merged;
53
+ changed += 1;
54
+ }
55
+ if (changed) await transaction.flush();
56
+ return changed;
57
+ });
58
+ }
59
+ async function ensureAttachmentPartitionProtection(em, partition) {
60
+ const required = getDeclaredAttachmentAccessRequirements(partition.code);
61
+ if (!required.length) return;
62
+ const existing = attachmentAccessRequirementsSchema.parse(partition.accessResolverRequirements ?? []);
63
+ const merged = mergeAttachmentAccessRequirements(existing, required);
64
+ if (JSON.stringify(existing) === JSON.stringify(merged)) return;
65
+ await syncAttachmentAccessProtection(em, { partitionCode: partition.code });
66
+ }
67
+ export {
68
+ ensureAttachmentPartitionProtection,
69
+ getDeclaredAttachmentAccessRequirements,
70
+ mergeAttachmentAccessRequirements,
71
+ syncAttachmentAccessProtection
72
+ };
73
+ //# sourceMappingURL=access-protection.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/attachments/lib/access-protection.ts"],
4
+ "sourcesContent": ["import { LockMode } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { AttachmentPartition } from '../data/entities'\nimport { attachmentAccessRequirementsSchema } from '../data/validators'\nimport { getAttachmentAccessRegistry, matchesAttachmentAccessSelector } from './access-registry'\nimport type { AttachmentAccessRequirement } from './access-types'\n\nexport function getDeclaredAttachmentAccessRequirements(partitionCode: string): AttachmentAccessRequirement[] {\n const registry = getAttachmentAccessRegistry()\n if (registry.invalid) throw new Error('[internal] Attachment access registry is invalid')\n return registry.protectedTargets\n .filter((target) => matchesAttachmentAccessSelector(target.targetPartition, partitionCode))\n .map(({ resolverId, targetEntity }) => ({ resolverId, targetEntity }))\n}\n\nexport function mergeAttachmentAccessRequirements(\n current: unknown,\n required: readonly AttachmentAccessRequirement[],\n): AttachmentAccessRequirement[] {\n const existing = attachmentAccessRequirementsSchema.parse(current ?? [])\n const union = new Map<string, AttachmentAccessRequirement>()\n for (const requirement of [...existing, ...required]) {\n union.set(JSON.stringify([requirement.resolverId, requirement.targetEntity]), { ...requirement })\n }\n return [...union.values()]\n}\n\nexport async function syncAttachmentAccessProtection(\n em: EntityManager,\n options: { partitionCode?: string } = {},\n): Promise<number> {\n const registry = getAttachmentAccessRegistry()\n if (registry.invalid) throw new Error('[internal] Attachment access registry is invalid')\n if (!registry.protectedTargets.length) return 0\n const isolated = em.fork({ clear: true, useContext: false })\n const candidates = await findWithDecryption(\n isolated,\n AttachmentPartition,\n options.partitionCode ? { code: options.partitionCode } : {},\n { orderBy: { id: 'asc' } },\n { tenantId: null, organizationId: null },\n )\n if (options.partitionCode && getDeclaredAttachmentAccessRequirements(options.partitionCode).length && !candidates.length) {\n throw new Error('[internal] Protected attachment partition is not durably configured')\n }\n const pending = candidates.filter((partition) => {\n const required = getDeclaredAttachmentAccessRequirements(partition.code)\n if (!required.length) return false\n const existing = attachmentAccessRequirementsSchema.parse(partition.accessResolverRequirements ?? [])\n return JSON.stringify(mergeAttachmentAccessRequirements(existing, required)) !== JSON.stringify(existing)\n })\n if (!pending.length) return 0\n return isolated.transactional(async (transaction) => {\n let changed = 0\n for (const candidate of pending) {\n const partition = await findOneWithDecryption(transaction, AttachmentPartition, { id: candidate.id }, {\n lockMode: LockMode.PESSIMISTIC_WRITE,\n refresh: true,\n }, { tenantId: null, organizationId: null })\n if (!partition) throw new Error('[internal] Attachment partition disappeared during access protection synchronization')\n const existing = attachmentAccessRequirementsSchema.parse(partition.accessResolverRequirements ?? [])\n const merged = mergeAttachmentAccessRequirements(existing, getDeclaredAttachmentAccessRequirements(partition.code))\n if (JSON.stringify(merged) === JSON.stringify(existing)) continue\n partition.accessResolverRequirements = merged\n changed += 1\n }\n if (changed) await transaction.flush()\n return changed\n })\n}\n\nexport async function ensureAttachmentPartitionProtection(\n em: EntityManager,\n partition: AttachmentPartition,\n): Promise<void> {\n const required = getDeclaredAttachmentAccessRequirements(partition.code)\n if (!required.length) return\n const existing = attachmentAccessRequirementsSchema.parse(partition.accessResolverRequirements ?? [])\n const merged = mergeAttachmentAccessRequirements(existing, required)\n if (JSON.stringify(existing) === JSON.stringify(merged)) return\n await syncAttachmentAccessProtection(em, { partitionCode: partition.code })\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,74 @@
1
+ import { ensureAttachmentPartitionProtection } from "./access-protection.js";
2
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
+ import { AttachmentPartition } from "../data/entities.js";
4
+ import { attachmentAccessRequirementsSchema } from "../data/validators.js";
5
+ import { getAttachmentAccessRegistry } from "./access-registry.js";
6
+ import { evaluateAttachmentAccess } from "./access-runner.js";
7
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
8
+ import { attachmentAccessErrorBody, throwAttachmentAccessError } from "./access-errors.js";
9
+ import { readAttachmentMetadata } from "./metadata.js";
10
+ function requiresAttachmentAccessScan(partitions) {
11
+ const registry = getAttachmentAccessRegistry();
12
+ return registry.invalid || registry.resolvers.length > 0 || registry.protectedTargets.length > 0 || partitions.some((partition) => {
13
+ const parsed = attachmentAccessRequirementsSchema.safeParse(partition.accessResolverRequirements ?? []);
14
+ return !parsed.success || parsed.data.length > 0;
15
+ });
16
+ }
17
+ async function assertAttachmentOwnerAccess(input) {
18
+ const partition = await findOneWithDecryption(input.em, AttachmentPartition, { code: input.attachment.partitionCode }, void 0, {
19
+ tenantId: input.attachment.tenantId,
20
+ organizationId: input.attachment.organizationId
21
+ });
22
+ if (!partition) await throwAttachmentAccessError(404);
23
+ if (input.persistProtection) await ensureAttachmentPartitionProtection(input.em, partition);
24
+ const result = await evaluateAttachmentAccess({ ...input, partition, requireAuthForPublic: true });
25
+ if (!result.ok) await throwAttachmentAccessError(result.status);
26
+ }
27
+ async function scanAuthorizedAttachments(input) {
28
+ const deadline = input.deadline ?? Date.now() + 15e3;
29
+ const timeoutBody = await attachmentAccessErrorBody(504);
30
+ let timer;
31
+ const scan = async () => {
32
+ const partitions = new Map(input.partitions.map((partition) => [partition.code, partition]));
33
+ const records = [];
34
+ const tags = /* @__PURE__ */ new Set();
35
+ let total = 0;
36
+ for (let batchOffset = 0; ; batchOffset += 100) {
37
+ if (Date.now() >= deadline) await throwAttachmentAccessError(504);
38
+ const batch = await input.loadBatch(batchOffset, 100);
39
+ for (const attachment of batch) {
40
+ if (Date.now() >= deadline) await throwAttachmentAccessError(504);
41
+ const partition = partitions.get(attachment.partitionCode);
42
+ if (!partition) continue;
43
+ const access = await evaluateAttachmentAccess({
44
+ auth: input.auth,
45
+ attachment,
46
+ partition,
47
+ action: "metadata",
48
+ context: input.context,
49
+ requireAuthForPublic: true
50
+ });
51
+ if (Date.now() >= deadline || !access.ok && access.status === 504) await throwAttachmentAccessError(504);
52
+ if (!access.ok) continue;
53
+ if (input.collectTags) for (const tag of readAttachmentMetadata(attachment.storageMetadata).tags ?? []) tags.add(tag);
54
+ if (total >= (input.offset ?? 0) && (input.limit === void 0 || records.length < input.limit)) records.push(attachment);
55
+ total += 1;
56
+ }
57
+ if (batch.length < 100) break;
58
+ }
59
+ return { records, total, tags: [...tags].sort((left, right) => left < right ? -1 : left > right ? 1 : 0) };
60
+ };
61
+ try {
62
+ return await Promise.race([scan(), new Promise((_resolve, reject) => {
63
+ timer = setTimeout(() => reject(new CrudHttpError(504, timeoutBody)), Math.max(1, deadline - Date.now()));
64
+ })]);
65
+ } finally {
66
+ if (timer) clearTimeout(timer);
67
+ }
68
+ }
69
+ export {
70
+ assertAttachmentOwnerAccess,
71
+ requiresAttachmentAccessScan,
72
+ scanAuthorizedAttachments
73
+ };
74
+ //# sourceMappingURL=access-query.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/attachments/lib/access-query.ts"],
4
+ "sourcesContent": ["import { ensureAttachmentPartitionProtection } from './access-protection'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { AttachmentPartition, type Attachment } from '../data/entities'\nimport { attachmentAccessRequirementsSchema } from '../data/validators'\nimport { getAttachmentAccessRegistry } from './access-registry'\nimport { evaluateAttachmentAccess, type AttachmentAccessContext } from './access-runner'\nimport type { AttachmentAccessAction, AttachmentAccessRecord } from './access-types'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { attachmentAccessErrorBody, throwAttachmentAccessError } from './access-errors'\nimport { readAttachmentMetadata } from './metadata'\n\nexport function requiresAttachmentAccessScan(partitions: readonly AttachmentPartition[]): boolean {\n const registry = getAttachmentAccessRegistry()\n return registry.invalid || registry.resolvers.length > 0 || registry.protectedTargets.length > 0\n || partitions.some((partition) => {\n const parsed = attachmentAccessRequirementsSchema.safeParse(partition.accessResolverRequirements ?? [])\n return !parsed.success || parsed.data.length > 0\n })\n}\n\nexport async function assertAttachmentOwnerAccess(input: {\n em: EntityManager\n context: AttachmentAccessContext\n auth: NonNullable<AuthContext>\n attachment: AttachmentAccessRecord\n action: AttachmentAccessAction\n persistProtection?: boolean\n}): Promise<void> {\n const partition = await findOneWithDecryption(input.em, AttachmentPartition, { code: input.attachment.partitionCode }, undefined, {\n tenantId: input.attachment.tenantId, organizationId: input.attachment.organizationId,\n })\n if (!partition) await throwAttachmentAccessError(404)\n if (input.persistProtection) await ensureAttachmentPartitionProtection(input.em, partition!)\n const result = await evaluateAttachmentAccess({ ...input, partition: partition!, requireAuthForPublic: true })\n if (!result.ok) await throwAttachmentAccessError(result.status)\n}\n\nexport async function scanAuthorizedAttachments(input: {\n auth: NonNullable<AuthContext>\n context: AttachmentAccessContext\n partitions: readonly AttachmentPartition[]\n loadBatch(offset: number, limit: number): Promise<Attachment[]>\n offset?: number\n limit?: number\n collectTags?: boolean\n deadline?: number\n}): Promise<{ records: Attachment[]; total: number; tags: string[] }> {\n const deadline = input.deadline ?? Date.now() + 15_000\n const timeoutBody = await attachmentAccessErrorBody(504)\n let timer: ReturnType<typeof setTimeout> | undefined\n const scan = async () => {\n const partitions = new Map(input.partitions.map((partition) => [partition.code, partition]))\n const records: Attachment[] = []\n const tags = new Set<string>()\n let total = 0\n for (let batchOffset = 0; ; batchOffset += 100) {\n if (Date.now() >= deadline) await throwAttachmentAccessError(504)\n const batch = await input.loadBatch(batchOffset, 100)\n for (const attachment of batch) {\n if (Date.now() >= deadline) await throwAttachmentAccessError(504)\n const partition = partitions.get(attachment.partitionCode)\n if (!partition) continue\n const access = await evaluateAttachmentAccess({\n auth: input.auth, attachment, partition, action: 'metadata', context: input.context,\n requireAuthForPublic: true,\n })\n if (Date.now() >= deadline || (!access.ok && access.status === 504)) await throwAttachmentAccessError(504)\n if (!access.ok) continue\n if (input.collectTags) for (const tag of readAttachmentMetadata(attachment.storageMetadata).tags ?? []) tags.add(tag)\n if (total >= (input.offset ?? 0) && (input.limit === undefined || records.length < input.limit)) records.push(attachment)\n total += 1\n }\n if (batch.length < 100) break\n }\n return { records, total, tags: [...tags].sort((left, right) => left < right ? -1 : left > right ? 1 : 0) }\n }\n try {\n return await Promise.race([scan(), new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new CrudHttpError(504, timeoutBody)), Math.max(1, deadline - Date.now()))\n })])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,87 @@
1
+ import { matchesEntity } from "@open-mercato/shared/lib/crud/mutation-guard-registry";
2
+ import { attachmentAccessResolverSchema, protectedAttachmentTargetSchema } from "../data/validators.js";
3
+ const GLOBAL_KEY = "__openMercatoAttachmentAccessRegistry__";
4
+ const emptyRegistry = Object.freeze({ invalid: false, resolvers: [], protectedTargets: [] });
5
+ function getAttachmentAccessRegistry() {
6
+ return globalThis[GLOBAL_KEY] ?? emptyRegistry;
7
+ }
8
+ function registerAttachmentAccessResolvers(entries) {
9
+ const globalState = globalThis;
10
+ globalState[GLOBAL_KEY] = { invalid: true, resolvers: [], protectedTargets: [] };
11
+ const resolvers = [];
12
+ const protectedTargets = [];
13
+ const ids = /* @__PURE__ */ new Set();
14
+ for (const [moduleOrder, entry] of entries.entries()) {
15
+ if (typeof entry.moduleId !== "string" || !entry.moduleId || !Array.isArray(entry.resolvers)) {
16
+ throw new Error("[internal] Invalid attachment access registry entry");
17
+ }
18
+ for (const [declarationOrder, raw] of entry.resolvers.entries()) {
19
+ const resolver = attachmentAccessResolverSchema.parse(raw);
20
+ if (!resolver.id.startsWith(`${entry.moduleId}.`) || ids.has(resolver.id)) {
21
+ throw new Error("[internal] Attachment access resolver IDs must be unique and owned by their module");
22
+ }
23
+ ids.add(resolver.id);
24
+ resolvers.push(Object.freeze({
25
+ ...resolver,
26
+ actions: resolver.actions ? Object.freeze([...resolver.actions]) : void 0,
27
+ moduleOrder,
28
+ declarationOrder
29
+ }));
30
+ }
31
+ for (const raw of entry.protectedTargets ?? []) {
32
+ const target = protectedAttachmentTargetSchema.parse(raw);
33
+ if (!target.resolverId.startsWith(`${entry.moduleId}.`)) {
34
+ throw new Error("[internal] Attachment access requirements must name their module resolver");
35
+ }
36
+ protectedTargets.push(Object.freeze(target));
37
+ }
38
+ }
39
+ resolvers.sort((left, right) => (left.priority ?? 50) - (right.priority ?? 50) || left.moduleOrder - right.moduleOrder || left.declarationOrder - right.declarationOrder);
40
+ globalState[GLOBAL_KEY] = Object.freeze({
41
+ invalid: false,
42
+ resolvers: Object.freeze(resolvers),
43
+ protectedTargets: Object.freeze(protectedTargets)
44
+ });
45
+ }
46
+ function matchesAttachmentAccessSelector(selector, value) {
47
+ return matchesEntity(selector, value);
48
+ }
49
+ function matchesAttachmentAccessTargets(selector, targets) {
50
+ return selector === "*" || targets.some((target) => matchesAttachmentAccessSelector(selector, target.entityId));
51
+ }
52
+ function matchesAttachmentAccessResolver(resolver, partitionCode, targets, action) {
53
+ return matchesAttachmentAccessSelector(resolver.targetPartition, partitionCode) && matchesAttachmentAccessTargets(resolver.targetEntity ?? "*", targets) && (!resolver.actions || resolver.actions.includes(action));
54
+ }
55
+ function isRecord(value) {
56
+ return value !== null && typeof value === "object" && !Array.isArray(value);
57
+ }
58
+ function collectAttachmentAccessTargets(attachment) {
59
+ const targets = [];
60
+ function append(entityId, recordId, origin) {
61
+ if (typeof entityId !== "string" || !entityId.trim()) return;
62
+ const target = Object.freeze({
63
+ entityId: entityId.trim(),
64
+ recordId: typeof recordId === "string" ? recordId.trim() : "",
65
+ origin
66
+ });
67
+ if (!targets.some((candidate) => candidate.entityId === target.entityId && candidate.recordId === target.recordId)) {
68
+ targets.push(target);
69
+ }
70
+ }
71
+ append(attachment.entityId, attachment.recordId, "primary");
72
+ const assignments = isRecord(attachment.storageMetadata) ? attachment.storageMetadata.assignments : void 0;
73
+ const entries = Array.isArray(assignments) ? assignments : isRecord(assignments) ? [assignments] : [];
74
+ for (const assignment of entries) {
75
+ if (isRecord(assignment)) append(assignment.type, assignment.id, "assignment");
76
+ }
77
+ return Object.freeze(targets);
78
+ }
79
+ export {
80
+ collectAttachmentAccessTargets,
81
+ getAttachmentAccessRegistry,
82
+ matchesAttachmentAccessResolver,
83
+ matchesAttachmentAccessSelector,
84
+ matchesAttachmentAccessTargets,
85
+ registerAttachmentAccessResolvers
86
+ };
87
+ //# sourceMappingURL=access-registry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/attachments/lib/access-registry.ts"],
4
+ "sourcesContent": ["import { matchesEntity } from '@open-mercato/shared/lib/crud/mutation-guard-registry'\nimport { attachmentAccessResolverSchema, protectedAttachmentTargetSchema } from '../data/validators'\nimport type {\n AttachmentAccessAction,\n AttachmentAccessRecord,\n AttachmentAccessResolver,\n AttachmentAccessResolverEntry,\n AttachmentAccessTarget,\n ProtectedAttachmentTarget,\n} from './access-types'\n\nconst GLOBAL_KEY = '__openMercatoAttachmentAccessRegistry__'\n\ntype RegisteredResolver = AttachmentAccessResolver & {\n moduleOrder: number\n declarationOrder: number\n}\n\ntype Registry = {\n invalid: boolean\n resolvers: readonly RegisteredResolver[]\n protectedTargets: readonly ProtectedAttachmentTarget[]\n}\n\nconst emptyRegistry: Registry = Object.freeze({ invalid: false, resolvers: [], protectedTargets: [] })\n\nexport function getAttachmentAccessRegistry(): Registry {\n return (globalThis as Record<string, unknown>)[GLOBAL_KEY] as Registry | undefined ?? emptyRegistry\n}\n\nexport function registerAttachmentAccessResolvers(entries: readonly AttachmentAccessResolverEntry[]): void {\n const globalState = globalThis as Record<string, unknown>\n globalState[GLOBAL_KEY] = { invalid: true, resolvers: [], protectedTargets: [] } satisfies Registry\n const resolvers: RegisteredResolver[] = []\n const protectedTargets: ProtectedAttachmentTarget[] = []\n const ids = new Set<string>()\n for (const [moduleOrder, entry] of entries.entries()) {\n if (typeof entry.moduleId !== 'string' || !entry.moduleId || !Array.isArray(entry.resolvers)) {\n throw new Error('[internal] Invalid attachment access registry entry')\n }\n for (const [declarationOrder, raw] of entry.resolvers.entries()) {\n const resolver = attachmentAccessResolverSchema.parse(raw)\n if (!resolver.id.startsWith(`${entry.moduleId}.`) || ids.has(resolver.id)) {\n throw new Error('[internal] Attachment access resolver IDs must be unique and owned by their module')\n }\n ids.add(resolver.id)\n resolvers.push(Object.freeze({\n ...resolver,\n actions: resolver.actions ? Object.freeze([...resolver.actions]) : undefined,\n moduleOrder,\n declarationOrder,\n }))\n }\n for (const raw of entry.protectedTargets ?? []) {\n const target = protectedAttachmentTargetSchema.parse(raw)\n if (!target.resolverId.startsWith(`${entry.moduleId}.`)) {\n throw new Error('[internal] Attachment access requirements must name their module resolver')\n }\n protectedTargets.push(Object.freeze(target))\n }\n }\n resolvers.sort((left, right) => (\n (left.priority ?? 50) - (right.priority ?? 50)\n || left.moduleOrder - right.moduleOrder\n || left.declarationOrder - right.declarationOrder\n ))\n globalState[GLOBAL_KEY] = Object.freeze({\n invalid: false,\n resolvers: Object.freeze(resolvers),\n protectedTargets: Object.freeze(protectedTargets),\n }) satisfies Registry\n}\n\nexport function matchesAttachmentAccessSelector(selector: string, value: string): boolean {\n return matchesEntity(selector, value)\n}\n\nexport function matchesAttachmentAccessTargets(selector: string, targets: readonly AttachmentAccessTarget[]): boolean {\n return selector === '*' || targets.some((target) => matchesAttachmentAccessSelector(selector, target.entityId))\n}\n\nexport function matchesAttachmentAccessResolver(\n resolver: AttachmentAccessResolver,\n partitionCode: string,\n targets: readonly AttachmentAccessTarget[],\n action: AttachmentAccessAction,\n): boolean {\n return matchesAttachmentAccessSelector(resolver.targetPartition, partitionCode)\n && matchesAttachmentAccessTargets(resolver.targetEntity ?? '*', targets)\n && (!resolver.actions || resolver.actions.includes(action))\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nexport function collectAttachmentAccessTargets(attachment: AttachmentAccessRecord): readonly AttachmentAccessTarget[] {\n const targets: AttachmentAccessTarget[] = []\n function append(entityId: unknown, recordId: unknown, origin: AttachmentAccessTarget['origin']) {\n if (typeof entityId !== 'string' || !entityId.trim()) return\n const target = Object.freeze({\n entityId: entityId.trim(),\n recordId: typeof recordId === 'string' ? recordId.trim() : '',\n origin,\n })\n if (!targets.some((candidate) => candidate.entityId === target.entityId && candidate.recordId === target.recordId)) {\n targets.push(target)\n }\n }\n append(attachment.entityId, attachment.recordId, 'primary')\n const assignments = isRecord(attachment.storageMetadata) ? attachment.storageMetadata.assignments : undefined\n const entries = Array.isArray(assignments) ? assignments : isRecord(assignments) ? [assignments] : []\n for (const assignment of entries) {\n if (isRecord(assignment)) append(assignment.type, assignment.id, 'assignment')\n }\n return Object.freeze(targets)\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }