@reventlessdev/reventless-aws 3.0.0-alpha.167

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/CHANGELOG.md +1564 -0
  2. package/README.md +62 -0
  3. package/__mocks__/emptyModule.js +1 -0
  4. package/all-licenses.csv +565 -0
  5. package/docker-compose.dynamodb-local.yml +12 -0
  6. package/jest.setup.cjs +5 -0
  7. package/licenses-analysis.ods +0 -0
  8. package/package.json +73 -0
  9. package/rescript.json +39 -0
  10. package/reventless-licenses-analysis.xls +0 -0
  11. package/scripts/run-integration-tests.sh +43 -0
  12. package/src/Platform.res +1940 -0
  13. package/src/Platform.res.mjs +2281 -0
  14. package/src/Platform_Casts.res +106 -0
  15. package/src/Platform_Casts.res.mjs +13 -0
  16. package/src/Platform_Stack.res +153 -0
  17. package/src/Platform_Stack.res.mjs +121 -0
  18. package/src/Types.res +39 -0
  19. package/src/Types.res.mjs +21 -0
  20. package/src/adapter/AWS.res +96 -0
  21. package/src/adapter/AWS.res.mjs +186 -0
  22. package/src/adapter/AWS_Tags.res +16 -0
  23. package/src/adapter/AWS_Tags.res.mjs +48 -0
  24. package/src/adapter/Adapter_Helpers.res +19 -0
  25. package/src/adapter/Adapter_Helpers.res.mjs +37 -0
  26. package/src/adapter/Api/AppSync_EventsApi.res +172 -0
  27. package/src/adapter/Api/AppSync_EventsApi.res.mjs +96 -0
  28. package/src/adapter/Api/AppSync_Resolver_Native.res +93 -0
  29. package/src/adapter/Api/AppSync_Resolver_Native.res.mjs +67 -0
  30. package/src/adapter/Api/AppSync_Resolver_Retrying.res +653 -0
  31. package/src/adapter/Api/AppSync_Resolver_Retrying.res.mjs +462 -0
  32. package/src/adapter/Api/CommandSubscriptionResolvers_AppSync.res +29 -0
  33. package/src/adapter/Api/CommandSubscriptionResolvers_AppSync.res.mjs +15 -0
  34. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +238 -0
  35. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +179 -0
  36. package/src/adapter/Api/Platform_UIFragments_Lambda.res +229 -0
  37. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +245 -0
  38. package/src/adapter/Auth/Auth_Cognito.res +209 -0
  39. package/src/adapter/Auth/Auth_Cognito.res.mjs +135 -0
  40. package/src/adapter/CommandGenerator/CommandGeneratorResolvers.res +2 -0
  41. package/src/adapter/CommandGenerator/CommandGeneratorResolvers.res.mjs +12 -0
  42. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +251 -0
  43. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res.mjs +126 -0
  44. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync_NoOp.res +20 -0
  45. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync_NoOp.res.mjs +23 -0
  46. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync_Runtime.res +1 -0
  47. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync_Runtime.res.mjs +11 -0
  48. package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res +95 -0
  49. package/src/adapter/CommandGenerator/InboundTranslationResolvers_AppSync.res.mjs +50 -0
  50. package/src/adapter/CommandTopic/CommandTopicChannel.res +6 -0
  51. package/src/adapter/CommandTopic/CommandTopicChannel.res.mjs +21 -0
  52. package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res +196 -0
  53. package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs +145 -0
  54. package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res +91 -0
  55. package/src/adapter/CommandTopic/CommandTopicChannel_SQS.res.mjs +74 -0
  56. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res +69 -0
  57. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Async.res.mjs +62 -0
  58. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res +60 -0
  59. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_FIFO.res.mjs +56 -0
  60. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res +73 -0
  61. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs +65 -0
  62. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res +88 -0
  63. package/src/adapter/CommandTopic/CommandTopicChannel_SQS_Sync.res.mjs +76 -0
  64. package/src/adapter/CommandTopic/CommandTopicRemoteChannel.res +1 -0
  65. package/src/adapter/CommandTopic/CommandTopicRemoteChannel.res.mjs +9 -0
  66. package/src/adapter/CommandTopic/CommandTopicRemoteChannel_SQS.res +7 -0
  67. package/src/adapter/CommandTopic/CommandTopicRemoteChannel_SQS.res.mjs +17 -0
  68. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +103 -0
  69. package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +68 -0
  70. package/src/adapter/Counter/CounterHandler_DynamoDbStream_Runtime.res +107 -0
  71. package/src/adapter/Counter/CounterHandler_DynamoDbStream_Runtime.res.mjs +145 -0
  72. package/src/adapter/DcbEventLog/DcbEventLogStorage.res +1 -0
  73. package/src/adapter/DcbEventLog/DcbEventLogStorage.res.mjs +9 -0
  74. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res +84 -0
  75. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb.res.mjs +52 -0
  76. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +1436 -0
  77. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +1143 -0
  78. package/src/adapter/EventCollector/EventCollectorChannel.res +6 -0
  79. package/src/adapter/EventCollector/EventCollectorChannel.res.mjs +21 -0
  80. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res +63 -0
  81. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs +56 -0
  82. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res +20 -0
  83. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res.mjs +32 -0
  84. package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +266 -0
  85. package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res.mjs +166 -0
  86. package/src/adapter/EventCollector/EventCollectorChannel_SQS.res +81 -0
  87. package/src/adapter/EventCollector/EventCollectorChannel_SQS.res.mjs +54 -0
  88. package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res +59 -0
  89. package/src/adapter/EventCollector/EventCollectorChannel_SQS_FIFO.res.mjs +45 -0
  90. package/src/adapter/EventCollector/EventCollectorChannel_SQS_Runtime.res +91 -0
  91. package/src/adapter/EventCollector/EventCollectorChannel_SQS_Runtime.res.mjs +89 -0
  92. package/src/adapter/EventLog/EventLogStorage.res +3 -0
  93. package/src/adapter/EventLog/EventLogStorage.res.mjs +15 -0
  94. package/src/adapter/EventLog/EventLogStorage_DynamoDb.res +25 -0
  95. package/src/adapter/EventLog/EventLogStorage_DynamoDb.res.mjs +34 -0
  96. package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res +26 -0
  97. package/src/adapter/EventLog/EventLogStorage_DynamoDbStream.res.mjs +35 -0
  98. package/src/adapter/EventLog/EventLogStorage_DynamoDb_Runtime.res +121 -0
  99. package/src/adapter/EventLog/EventLogStorage_DynamoDb_Runtime.res.mjs +137 -0
  100. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +265 -0
  101. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +185 -0
  102. package/src/adapter/EventTopic/EventTopicPublisher.res +6 -0
  103. package/src/adapter/EventTopic/EventTopicPublisher.res.mjs +21 -0
  104. package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res +25 -0
  105. package/src/adapter/EventTopic/EventTopicPublisher_DynamoDbStream.res.mjs +28 -0
  106. package/src/adapter/EventTopic/EventTopicPublisher_SNS.res +40 -0
  107. package/src/adapter/EventTopic/EventTopicPublisher_SNS.res.mjs +39 -0
  108. package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res +39 -0
  109. package/src/adapter/EventTopic/EventTopicPublisher_SNS_FIFO.res.mjs +38 -0
  110. package/src/adapter/EventTopic/EventTopicPublisher_SNS_Runtime.res +5 -0
  111. package/src/adapter/EventTopic/EventTopicPublisher_SNS_Runtime.res.mjs +18 -0
  112. package/src/adapter/Mcp/MCP_Lambda.res +502 -0
  113. package/src/adapter/Mcp/MCP_Lambda.res.mjs +289 -0
  114. package/src/adapter/QueryDb/NodeResolver_AppSync.res +71 -0
  115. package/src/adapter/QueryDb/NodeResolver_AppSync.res.mjs +44 -0
  116. package/src/adapter/QueryDb/QueryDbResolvers.res +2 -0
  117. package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +12 -0
  118. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +434 -0
  119. package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +243 -0
  120. package/src/adapter/QueryDb/QueryDbResolvers_AppSync_NoOp.res +21 -0
  121. package/src/adapter/QueryDb/QueryDbResolvers_AppSync_NoOp.res.mjs +14 -0
  122. package/src/adapter/QueryDb/QueryDbResolvers_NoOp.res +21 -0
  123. package/src/adapter/QueryDb/QueryDbResolvers_NoOp.res.mjs +14 -0
  124. package/src/adapter/QueryDb/QueryDbStorage.res +5 -0
  125. package/src/adapter/QueryDb/QueryDbStorage.res.mjs +18 -0
  126. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res +115 -0
  127. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs +100 -0
  128. package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res +52 -0
  129. package/src/adapter/QueryDb/QueryDbStorage_DynamoDbStream.res.mjs +38 -0
  130. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb_Runtime.res +271 -0
  131. package/src/adapter/QueryDb/QueryDbStorage_DynamoDb_Runtime.res.mjs +306 -0
  132. package/src/adapter/QueryDb/QueryInterceptor_Lambda.res +20 -0
  133. package/src/adapter/QueryDb/QueryInterceptor_Lambda.res.mjs +22 -0
  134. package/src/adapter/QueryEngine/QueryEngine.res +3 -0
  135. package/src/adapter/QueryEngine/QueryEngine.res.mjs +12 -0
  136. package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res +184 -0
  137. package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res.mjs +221 -0
  138. package/src/adapter/Runtime/AdminEventCollectorEntryPoint.mjs +879 -0
  139. package/src/adapter/Runtime/AggregateEntryPoint.mjs +313 -0
  140. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res +327 -0
  141. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res.mjs +239 -0
  142. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res +331 -0
  143. package/src/adapter/Runtime/AggregateRuntime_Builder_Micro_Async.res.mjs +240 -0
  144. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res +247 -0
  145. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate.res.mjs +185 -0
  146. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res +244 -0
  147. package/src/adapter/Runtime/AggregateRuntime_Builder_PerAggregate_Async.res.mjs +185 -0
  148. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +347 -0
  149. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +244 -0
  150. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +285 -0
  151. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +212 -0
  152. package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +124 -0
  153. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +187 -0
  154. package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +147 -0
  155. package/src/adapter/Runtime/CounterEntryPoint.mjs +91 -0
  156. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +265 -0
  157. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res +122 -0
  158. package/src/adapter/Runtime/EventCollectorRuntime_Builder_PerEventCollector.res.mjs +89 -0
  159. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +193 -0
  160. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +137 -0
  161. package/src/adapter/Runtime/EventMapperEntryPoint.mjs +97 -0
  162. package/src/adapter/Runtime/ExtensionPointEntryPoint.mjs +101 -0
  163. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res +83 -0
  164. package/src/adapter/Runtime/ExtensionPointRuntime_Builder_PerExtensionPoint.res.mjs +59 -0
  165. package/src/adapter/Runtime/HandlerFactoryHelpers.mjs +142 -0
  166. package/src/adapter/Runtime/HeartbeatEntryPoint.mjs +38 -0
  167. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.mjs +137 -0
  168. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +143 -0
  169. package/src/adapter/Runtime/RuntimeEnvironment.res +1 -0
  170. package/src/adapter/Runtime/RuntimeEnvironment.res.mjs +9 -0
  171. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +254 -0
  172. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +169 -0
  173. package/src/adapter/Runtime/SideEffectEntryPoint.mjs +81 -0
  174. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +178 -0
  175. package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +129 -0
  176. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +166 -0
  177. package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +87 -0
  178. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +136 -0
  179. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +344 -0
  180. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +242 -0
  181. package/src/adapter/Runtime/TaskBucketEntryPoint.mjs +150 -0
  182. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res +123 -0
  183. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +74 -0
  184. package/src/adapter/ScheduledPublisher/ScheduledPublisher.res +2 -0
  185. package/src/adapter/ScheduledPublisher/ScheduledPublisher.res.mjs +12 -0
  186. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res +54 -0
  187. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res.mjs +41 -0
  188. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res +97 -0
  189. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res.mjs +85 -0
  190. package/src/adapter/StateTopic/StateTopic_AppSync.res +418 -0
  191. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +295 -0
  192. package/src/adapter/Task/TaskBucket.res +1 -0
  193. package/src/adapter/Task/TaskBucket.res.mjs +9 -0
  194. package/src/adapter/Task/TaskBucket_S3.res +157 -0
  195. package/src/adapter/Task/TaskBucket_S3.res.mjs +108 -0
  196. package/src/adapter/Task/TaskBucket_S3_Runtime.res +11 -0
  197. package/src/adapter/Task/TaskBucket_S3_Runtime.res.mjs +15 -0
  198. package/src/components/Aggregate_Builder.res +1 -0
  199. package/src/components/Aggregate_Builder.res.mjs +25 -0
  200. package/src/components/Aggregate_Builder_Micro.res +63 -0
  201. package/src/components/Aggregate_Builder_Micro.res.mjs +80 -0
  202. package/src/components/Aggregate_Builder_NoResolver.res +61 -0
  203. package/src/components/Aggregate_Builder_NoResolver.res.mjs +80 -0
  204. package/src/components/Aggregate_Builder_PerAggregate.res +57 -0
  205. package/src/components/Aggregate_Builder_PerAggregate.res.mjs +80 -0
  206. package/src/components/Aggregate_Builder_Single.res +59 -0
  207. package/src/components/Aggregate_Builder_Single.res.mjs +80 -0
  208. package/src/components/Aggregate_Builder_Single_Async.res +59 -0
  209. package/src/components/Aggregate_Builder_Single_Async.res.mjs +80 -0
  210. package/src/components/Api/AppSync_Adapter.res +500 -0
  211. package/src/components/Api/AppSync_Adapter.res.mjs +396 -0
  212. package/src/components/AutomationSlice_Builder.res +52 -0
  213. package/src/components/AutomationSlice_Builder.res.mjs +72 -0
  214. package/src/components/AutomationSlice_Builder_Bundled.res.mjs +70 -0
  215. package/src/components/Counter_Builder.res +10 -0
  216. package/src/components/Counter_Builder.res.mjs +16 -0
  217. package/src/components/DataCleaner.res +170 -0
  218. package/src/components/DataCleaner.res.mjs +2 -0
  219. package/src/components/DcbEventLog_Builder.res +8 -0
  220. package/src/components/DcbEventLog_Builder.res.mjs +14 -0
  221. package/src/components/ExtensionPoint_Builder.res +16 -0
  222. package/src/components/ExtensionPoint_Builder.res.mjs +37 -0
  223. package/src/components/ForeignReadModel_Builder.res +19 -0
  224. package/src/components/ForeignReadModel_Builder.res.mjs +48 -0
  225. package/src/components/InboundTranslationSlice_Builder.res +25 -0
  226. package/src/components/InboundTranslationSlice_Builder.res.mjs +31 -0
  227. package/src/components/OutboundTranslationSlice_Builder.res +60 -0
  228. package/src/components/OutboundTranslationSlice_Builder.res.mjs +76 -0
  229. package/src/components/OutboundTranslationSlice_Builder_Bundled.res.mjs +75 -0
  230. package/src/components/Plugin.res +29 -0
  231. package/src/components/Plugin.res.mjs +63 -0
  232. package/src/components/ReadModel_Builder.res +1 -0
  233. package/src/components/ReadModel_Builder.res.mjs +19 -0
  234. package/src/components/ReadModel_Builder_NoResolver.res +55 -0
  235. package/src/components/ReadModel_Builder_NoResolver.res.mjs +64 -0
  236. package/src/components/ReadModel_Builder_NoResolver_Stream.res +60 -0
  237. package/src/components/ReadModel_Builder_NoResolver_Stream.res.mjs +64 -0
  238. package/src/components/ReadModel_Builder_PerReadModel.res +53 -0
  239. package/src/components/ReadModel_Builder_PerReadModel.res.mjs +66 -0
  240. package/src/components/ReadModel_Builder_Single.res +52 -0
  241. package/src/components/ReadModel_Builder_Single.res.mjs +66 -0
  242. package/src/components/ReadModel_Builder_Single_Stream.res +60 -0
  243. package/src/components/ReadModel_Builder_Single_Stream.res.mjs +66 -0
  244. package/src/components/Scheduler.res +1 -0
  245. package/src/components/Scheduler.res.mjs +13 -0
  246. package/src/components/SideEffectHandler.res +1 -0
  247. package/src/components/SideEffectHandler.res.mjs +19 -0
  248. package/src/components/SideEffectHandler_PerSideEffectHandler.res +13 -0
  249. package/src/components/SideEffectHandler_PerSideEffectHandler.res.mjs +52 -0
  250. package/src/components/SideEffectHandler_Single.res +54 -0
  251. package/src/components/SideEffectHandler_Single.res.mjs +60 -0
  252. package/src/components/StateChangeSlice_Builder.res +17 -0
  253. package/src/components/StateChangeSlice_Builder.res.mjs +23 -0
  254. package/src/components/StateViewSlice_Builder.res +46 -0
  255. package/src/components/StateViewSlice_Builder.res.mjs +68 -0
  256. package/src/components/StateViewSlice_Builder_Bundled.res.mjs +69 -0
  257. package/src/components/StateViewSlice_Builder_Stream.res +50 -0
  258. package/src/components/StateViewSlice_Builder_Stream.res.mjs +68 -0
  259. package/src/components/Task_Builder.res +1 -0
  260. package/src/components/Task_Builder.res.mjs +28 -0
  261. package/src/components/Task_Builder_PerBucket.res +22 -0
  262. package/src/components/Task_Builder_PerBucket.res.mjs +63 -0
  263. package/src/components/Vpc.res +214 -0
  264. package/src/components/Vpc.res.mjs +117 -0
  265. package/src/components/Vpc.resi +22 -0
  266. package/src/errors/AppSync_Error.res +36 -0
  267. package/src/errors/AppSync_Error.res.mjs +38 -0
  268. package/src/errors/Cognito_Error.res +36 -0
  269. package/src/errors/Cognito_Error.res.mjs +38 -0
  270. package/src/errors/DynamoDb_Error.res +75 -0
  271. package/src/errors/DynamoDb_Error.res.mjs +86 -0
  272. package/src/errors/Kinesis_Error.res +36 -0
  273. package/src/errors/Kinesis_Error.res.mjs +38 -0
  274. package/src/errors/SNS_Error.res +34 -0
  275. package/src/errors/SNS_Error.res.mjs +38 -0
  276. package/src/errors/SQS_Error.res +40 -0
  277. package/src/errors/SQS_Error.res.mjs +41 -0
  278. package/src/plugin/cloner/ClonerRunner.res +1 -0
  279. package/src/plugin/cloner/ClonerRunner.res.mjs +9 -0
  280. package/src/plugin/cloner/ClonerRunner_Fargate.res +362 -0
  281. package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +246 -0
  282. package/src/plugin/cloner/ClonerRunner_Fargate_Runtime.res +42 -0
  283. package/src/plugin/cloner/ClonerRunner_Fargate_Runtime.res.mjs +45 -0
  284. package/src/plugin/heartbeat/HeartbeatRunner.res +1 -0
  285. package/src/plugin/heartbeat/HeartbeatRunner.res.mjs +9 -0
  286. package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res +96 -0
  287. package/src/plugin/heartbeat/HeartbeatRunner_CloudWatchEvents.res.mjs +69 -0
  288. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res +115 -0
  289. package/src/plugin/runtime/PluginExtensionPointRuntime_Builder.res.mjs +88 -0
  290. package/src/plugin/runtime/PluginRuntimeOperations.res +5 -0
  291. package/src/plugin/runtime/PluginRuntimeOperations.res.mjs +14 -0
  292. package/src/plugin/runtime/PluginRuntime_Builder.res +769 -0
  293. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +464 -0
  294. package/src/plugin/runtime/Util_PluginMessage_Runtime.res +13 -0
  295. package/src/plugin/runtime/Util_PluginMessage_Runtime.res.mjs +19 -0
  296. package/src/plugin/stack/Plugin_ExtensionPoint_Builder.res +28 -0
  297. package/src/plugin/stack/Plugin_ExtensionPoint_Builder.res.mjs +67 -0
  298. package/src/plugin/stack/Plugin_Stack.res +439 -0
  299. package/src/plugin/stack/Plugin_Stack.res.mjs +263 -0
  300. package/src/util/Util.res +29 -0
  301. package/src/util/Util.res.mjs +93 -0
  302. package/src/util/Util_AppSync.res +36 -0
  303. package/src/util/Util_AppSync.res.mjs +35 -0
  304. package/src/util/Util_AppSync_Caller.res +213 -0
  305. package/src/util/Util_AppSync_Caller.res.mjs +170 -0
  306. package/src/util/Util_Bundle.res +255 -0
  307. package/src/util/Util_Bundle.res.mjs +179 -0
  308. package/src/util/Util_Cloudwatch.res +14 -0
  309. package/src/util/Util_Cloudwatch.res.mjs +19 -0
  310. package/src/util/Util_CognitoGroupUser.res +39 -0
  311. package/src/util/Util_CognitoGroupUser.res.mjs +32 -0
  312. package/src/util/Util_CognitoGroupUser_Runtime.res +65 -0
  313. package/src/util/Util_CognitoGroupUser_Runtime.res.mjs +50 -0
  314. package/src/util/Util_Cognito_Runtime.res +52 -0
  315. package/src/util/Util_Cognito_Runtime.res.mjs +35 -0
  316. package/src/util/Util_DeadLetterQueue.res +154 -0
  317. package/src/util/Util_DeadLetterQueue.res.mjs +158 -0
  318. package/src/util/Util_DynamoDb.res +210 -0
  319. package/src/util/Util_DynamoDb.res.mjs +218 -0
  320. package/src/util/Util_DynamoDbStream.res +138 -0
  321. package/src/util/Util_DynamoDbStream.res.mjs +134 -0
  322. package/src/util/Util_DynamoDbStream_Runtime.res +94 -0
  323. package/src/util/Util_DynamoDbStream_Runtime.res.mjs +131 -0
  324. package/src/util/Util_DynamoDb_Runtime.res +216 -0
  325. package/src/util/Util_DynamoDb_Runtime.res.mjs +257 -0
  326. package/src/util/Util_DynamoDb_TableManager.res +23 -0
  327. package/src/util/Util_DynamoDb_TableManager.res.mjs +35 -0
  328. package/src/util/Util_DynamoDb_TableManager.resi +3 -0
  329. package/src/util/Util_Env.res +5 -0
  330. package/src/util/Util_Env.res.mjs +2 -0
  331. package/src/util/Util_EventSourceMapping.res +49 -0
  332. package/src/util/Util_EventSourceMapping.res.mjs +32 -0
  333. package/src/util/Util_HostUiDomain.res +28 -0
  334. package/src/util/Util_HostUiDomain.res.mjs +26 -0
  335. package/src/util/Util_IAM_Role.res +14 -0
  336. package/src/util/Util_IAM_Role.res.mjs +28 -0
  337. package/src/util/Util_Kinesis_Runtime.res +20 -0
  338. package/src/util/Util_Kinesis_Runtime.res.mjs +24 -0
  339. package/src/util/Util_Lambda.res +39 -0
  340. package/src/util/Util_Lambda.res.mjs +45 -0
  341. package/src/util/Util_LocalConfig.res +119 -0
  342. package/src/util/Util_LocalConfig.res.mjs +112 -0
  343. package/src/util/Util_ResourceNaming.res +15 -0
  344. package/src/util/Util_ResourceNaming.res.mjs +26 -0
  345. package/src/util/Util_S3.res +17 -0
  346. package/src/util/Util_S3.res.mjs +30 -0
  347. package/src/util/Util_S3_Runtime.res +11 -0
  348. package/src/util/Util_S3_Runtime.res.mjs +15 -0
  349. package/src/util/Util_SNS.res +33 -0
  350. package/src/util/Util_SNS.res.mjs +50 -0
  351. package/src/util/Util_SNS_FIFO.res +21 -0
  352. package/src/util/Util_SNS_FIFO.res.mjs +27 -0
  353. package/src/util/Util_SNS_Runtime.res +33 -0
  354. package/src/util/Util_SNS_Runtime.res.mjs +27 -0
  355. package/src/util/Util_SQS.res +70 -0
  356. package/src/util/Util_SQS.res.mjs +83 -0
  357. package/src/util/Util_SQS_FIFO.res +16 -0
  358. package/src/util/Util_SQS_FIFO.res.mjs +28 -0
  359. package/src/util/Util_SQS_Runtime.res +169 -0
  360. package/src/util/Util_SQS_Runtime.res.mjs +150 -0
  361. package/src/util/Util_SesPolicy_Runtime.res +54 -0
  362. package/src/util/Util_SesPolicy_Runtime.res.mjs +55 -0
  363. package/src/util/Util_StaticBundle.res +124 -0
  364. package/src/util/Util_StaticBundle.res.mjs +132 -0
  365. package/src/util/Util_Vpc.res +22 -0
  366. package/src/util/Util_Vpc.res.mjs +27 -0
  367. package/tests/AppSync_AdapterTest.res +396 -0
  368. package/tests/AppSync_AdapterTest.res.mjs +326 -0
  369. package/tests/AppSync_Resolver_RetryingTest.res +59 -0
  370. package/tests/AppSync_Resolver_RetryingTest.res.mjs +66 -0
  371. package/tests/Auth_CognitoTest.res +203 -0
  372. package/tests/Auth_CognitoTest.res.mjs +248 -0
  373. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +602 -0
  374. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +742 -0
  375. package/tests/EventLogStorage_DynamoDb_RuntimeTest.res +57 -0
  376. package/tests/EventLogStorage_DynamoDb_RuntimeTest.res.mjs +68 -0
  377. package/tests/MCP_LambdaTest.res +101 -0
  378. package/tests/MCP_LambdaTest.res.mjs +56 -0
  379. package/tests/Util_HostUiDomainTest.res +90 -0
  380. package/tests/Util_HostUiDomainTest.res.mjs +50 -0
  381. package/tests/Util_SQS_RuntimeTest.res +46 -0
  382. package/tests/Util_SQS_RuntimeTest.res.mjs +45 -0
  383. package/tests/Util_StaticBundleTest.res +136 -0
  384. package/tests/Util_StaticBundleTest.res.mjs +149 -0
  385. package/tests/integration/DcbCommandTopicEntryPoint_IntegrationTest.res +101 -0
  386. package/tests/integration/DcbCommandTopicEntryPoint_IntegrationTest.res.mjs +91 -0
  387. package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res +315 -0
  388. package/tests/integration/DcbEventLogStorage_DynamoDb_IntegrationTest.res.mjs +435 -0
  389. package/tests/integration/DcbIntegrationHarness.res +102 -0
  390. package/tests/integration/DcbIntegrationHarness.res.mjs +163 -0
  391. package/tests/integration/EpTestSlice.res +28 -0
  392. package/tests/integration/EpTestSlice.res.mjs +43 -0
  393. package/tests/integration/EpTestSliceBehavior.res +18 -0
  394. package/tests/integration/EpTestSliceBehavior.res.mjs +35 -0
@@ -0,0 +1,602 @@
1
+ open JestGlobals
2
+
3
+ module Runtime = DcbEventLogStorage_DynamoDb_Runtime
4
+
5
+ let tag = (key, value): Reventless.DcbTag.tag => {key, value}
6
+
7
+ let testMeta = () => ReventlessCore.Message.generateMeta(~service="test")
8
+
9
+ let table: Util_DynamoDb_Runtime.resolvedTable = {
10
+ id: "test-table-id",
11
+ name: "TestTable",
12
+ arn: "arn:aws:dynamodb:eu-west-1:000000000000:table/TestTable",
13
+ hashKey: "id",
14
+ }
15
+
16
+ describe("Runtime.fencePartitionKey", () => {
17
+ testSync("formats the fence partition key", () => {
18
+ expect(Runtime.fencePartitionKey(tag("orderId", "ord-1")))->toBe("fence#orderId:ord-1")
19
+ })
20
+
21
+ testSync("does not collide with event partition keys", () => {
22
+ let fenceId = Runtime.fencePartitionKey(tag("orderId", "ord-1"))
23
+ let eventId = "orderId:ord-1"
24
+ expect(fenceId == eventId)->toBe(false)
25
+ })
26
+ })
27
+
28
+ describe("Runtime.fenceKey", () => {
29
+ testSync("returns key with id and FENCE sort key", () => {
30
+ let key = Runtime.fenceKey(tag("orderId", "ord-1"))
31
+ expect(key->Dict.get("id"))->toEqual(Some("fence#orderId:ord-1"->JSON.Encode.string))
32
+ expect(key->Dict.get("position"))->toEqual(Some("FENCE"->JSON.Encode.string))
33
+ })
34
+ })
35
+
36
+ describe("Runtime.collectQueryTags", () => {
37
+ testSync("returns empty array for empty query", () => {
38
+ expect(Runtime.collectQueryTags([]))->toEqual([])
39
+ })
40
+
41
+ testSync("returns empty array when no queryItem has tags", () => {
42
+ let q: Reventless.DcbTag.query = [{eventTypes: ["Foo"]}]
43
+ expect(Runtime.collectQueryTags(q))->toEqual([])
44
+ })
45
+
46
+ testSync("collects tags from a single queryItem", () => {
47
+ let q: Reventless.DcbTag.query = [
48
+ {tags: [tag("orderId", "o1"), tag("customerId", "c1")]},
49
+ ]
50
+ let result = Runtime.collectQueryTags(q)
51
+ expect(result->Array.length)->toBe(2)
52
+ })
53
+
54
+ testSync("dedupes same tag value across queryItems", () => {
55
+ let q: Reventless.DcbTag.query = [
56
+ {tags: [tag("productId", "p1")]},
57
+ {tags: [tag("productId", "p1")]},
58
+ {tags: [tag("productId", "p2")]},
59
+ ]
60
+ let result = Runtime.collectQueryTags(q)
61
+ expect(result->Array.length)->toBe(2)
62
+ })
63
+
64
+ testSync("ignores queryItems without tags but keeps others", () => {
65
+ let q: Reventless.DcbTag.query = [
66
+ {eventTypes: ["Foo"]},
67
+ {tags: [tag("orderId", "o1")]},
68
+ ]
69
+ let result = Runtime.collectQueryTags(q)
70
+ expect(result->Array.length)->toBe(1)
71
+ })
72
+ })
73
+
74
+ describe("Runtime.collectEventTags", () => {
75
+ testSync("returns empty array for no events", () => {
76
+ expect(Runtime.collectEventTags([]))->toEqual([])
77
+ })
78
+
79
+ testSync("dedupes tag values across events", () => {
80
+ let event1: ReventlessCore.DcbEventLog_Adapter.rawStoredEvent = {
81
+ eventType: "ProductAdded",
82
+ data: JSON.Object(Dict.make()),
83
+ tags: [tag("productId", "p1"), tag("categoryId", "c1")],
84
+ meta: testMeta(),
85
+ }
86
+ let event2: ReventlessCore.DcbEventLog_Adapter.rawStoredEvent = {
87
+ eventType: "ProductAdded",
88
+ data: JSON.Object(Dict.make()),
89
+ tags: [tag("productId", "p1"), tag("categoryId", "c2")],
90
+ meta: testMeta(),
91
+ }
92
+ let result = Runtime.collectEventTags([event1, event2])
93
+ expect(result->Array.length)->toBe(3)
94
+ })
95
+ })
96
+
97
+ describe("Runtime.buildConditionalFenceUpdate", () => {
98
+ testSync("after=Some: per-consumed-type OCC check, bumps only produced types", () => {
99
+ // ChangeProductName: reads [ProductAdded, ProductNameChanged], produces
100
+ // ProductNameChanged. The check must reference only those two types — never
101
+ // pos#ProductPriceChanged (the Issue 4 fix) — and bump only the produced type.
102
+ let update = Runtime.buildConditionalFenceUpdate(
103
+ "TestTable",
104
+ tag("productId", "p1"),
105
+ ~consumedTypes=["ProductAdded", "ProductNameChanged"],
106
+ ~producedTypes=["ProductNameChanged"],
107
+ ~newPosition="100",
108
+ ~after=Some("50"),
109
+ )
110
+ expect(update.conditionExpression)->toEqual(
111
+ Some(
112
+ "(attribute_not_exists(#c0) OR #c0 <= :after) AND (attribute_not_exists(#c1) OR #c1 <= :after)",
113
+ ),
114
+ )
115
+ expect(update.updateExpression)->toBe("SET #p0 = :new")
116
+ expect(update.expressionAttributeNames)->toEqual(
117
+ Some(
118
+ Dict.fromArray([
119
+ ("#p0", "pos#ProductNameChanged"),
120
+ ("#c0", "pos#ProductAdded"),
121
+ ("#c1", "pos#ProductNameChanged"),
122
+ ]),
123
+ ),
124
+ )
125
+ expect(update.expressionAttributeValues->Option.flatMap(v => v->Dict.get(":after")))->toEqual(
126
+ Some("50"->JSON.Encode.string),
127
+ )
128
+ expect(update.expressionAttributeValues->Option.flatMap(v => v->Dict.get(":new")))->toEqual(
129
+ Some("100"->JSON.Encode.string),
130
+ )
131
+ })
132
+
133
+ testSync("after=Some: a non-read type is never referenced in the condition", () => {
134
+ let update = Runtime.buildConditionalFenceUpdate(
135
+ "TestTable",
136
+ tag("productId", "p1"),
137
+ ~consumedTypes=["ProductAdded", "ProductNameChanged"],
138
+ ~producedTypes=["ProductNameChanged"],
139
+ ~newPosition="100",
140
+ ~after=Some("50"),
141
+ )
142
+ let referenced =
143
+ update.expressionAttributeNames->Option.getOr(Dict.make())->Dict.valuesToArray
144
+ expect(referenced->Array.includes("pos#ProductPriceChanged"))->toBe(false)
145
+ })
146
+
147
+ testSync("after=None: folded create guard over consumed ∪ produced types", () => {
148
+ let update = Runtime.buildConditionalFenceUpdate(
149
+ "TestTable",
150
+ tag("productId", "p1"),
151
+ ~consumedTypes=["ProductAdded"],
152
+ ~producedTypes=["ProductAdded"],
153
+ ~newPosition="100",
154
+ ~after=None,
155
+ )
156
+ // consumed ∪ produced dedups to [ProductAdded] → a single attribute_not_exists.
157
+ expect(update.conditionExpression)->toEqual(Some("attribute_not_exists(#c0)"))
158
+ expect(update.updateExpression)->toBe("SET #p0 = :new")
159
+ expect(update.expressionAttributeValues->Option.flatMap(v => v->Dict.get(":after")))->toEqual(
160
+ None,
161
+ )
162
+ })
163
+
164
+ testSync("targets the fence sentinel item", () => {
165
+ let update = Runtime.buildConditionalFenceUpdate(
166
+ "TestTable",
167
+ tag("orderId", "o1"),
168
+ ~consumedTypes=["OrderPlaced"],
169
+ ~producedTypes=["OrderPlaced"],
170
+ ~newPosition="100",
171
+ ~after=None,
172
+ )
173
+ expect(update.key->Dict.get("id"))->toEqual(Some("fence#orderId:o1"->JSON.Encode.string))
174
+ expect(update.key->Dict.get("position"))->toEqual(Some("FENCE"->JSON.Encode.string))
175
+ })
176
+ })
177
+
178
+ describe("Runtime.buildUnconditionalFenceUpdate", () => {
179
+ testSync("does not set a conditionExpression", () => {
180
+ let update = Runtime.buildUnconditionalFenceUpdate(
181
+ "TestTable",
182
+ tag("orderId", "o1"),
183
+ ~producedTypes=["OrderPlaced"],
184
+ ~newPosition="100",
185
+ )
186
+ expect(update.conditionExpression)->toEqual(None)
187
+ })
188
+
189
+ testSync("bumps each produced type's position attribute", () => {
190
+ let update = Runtime.buildUnconditionalFenceUpdate(
191
+ "TestTable",
192
+ tag("orderId", "o1"),
193
+ ~producedTypes=["OrderPlaced", "OrderShipped"],
194
+ ~newPosition="100",
195
+ )
196
+ expect(update.updateExpression)->toBe("SET #p0 = :new, #p1 = :new")
197
+ expect(update.expressionAttributeNames)->toEqual(
198
+ Some(Dict.fromArray([("#p0", "pos#OrderPlaced"), ("#p1", "pos#OrderShipped")])),
199
+ )
200
+ })
201
+ })
202
+
203
+ describe("Runtime.buildQueryByPartitionKeyInput", () => {
204
+ testSync("omits consistentRead by default (eventually consistent)", () => {
205
+ let input = Runtime.buildQueryByPartitionKeyInput(table, "orderId:o1")
206
+ expect(input.consistentRead)->toEqual(None)
207
+ })
208
+
209
+ testSync("omits consistentRead when ~strongConsistency=false", () => {
210
+ let input = Runtime.buildQueryByPartitionKeyInput(
211
+ table,
212
+ "orderId:o1",
213
+ ~strongConsistency=false,
214
+ )
215
+ expect(input.consistentRead)->toEqual(None)
216
+ })
217
+
218
+ testSync("sets consistentRead=true when ~strongConsistency=true", () => {
219
+ let input = Runtime.buildQueryByPartitionKeyInput(
220
+ table,
221
+ "orderId:o1",
222
+ ~strongConsistency=true,
223
+ )
224
+ expect(input.consistentRead)->toEqual(Some(true))
225
+ })
226
+
227
+ testSync("does not target a GSI (uses base table)", () => {
228
+ let input = Runtime.buildQueryByPartitionKeyInput(
229
+ table,
230
+ "orderId:o1",
231
+ ~strongConsistency=true,
232
+ )
233
+ expect(input.indexName)->toEqual(None)
234
+ })
235
+
236
+ testSync("threads ~after into the key condition without dropping consistentRead", () => {
237
+ let input = Runtime.buildQueryByPartitionKeyInput(
238
+ table,
239
+ "orderId:o1",
240
+ ~after="50",
241
+ ~strongConsistency=true,
242
+ )
243
+ expect(input.consistentRead)->toEqual(Some(true))
244
+ expect(input.keyConditionExpression)->toEqual(Some("id = :pk AND #pos > :after"))
245
+ })
246
+ })
247
+
248
+ describe("Runtime.buildScanFilter — excludes fence sentinels (Issue 12)", () => {
249
+ testSync("tagless, type-less scan still guards against fence# items", () => {
250
+ let filter = Runtime.buildScanFilter()
251
+ // No event-type disjunction, but the fence guard is always present, so
252
+ // `fence#…` sentinels (which lack the `event` attribute) are never returned.
253
+ expect(filter.filterExpression)->toEqual(Some("attribute_exists(#evt)"))
254
+ expect(filter.expressionAttributeNames)->toEqual(Some(Dict.fromArray([("#evt", "event")])))
255
+ expect(filter.expressionAttributeValues)->toEqual(None)
256
+ })
257
+
258
+ testSync("an empty event-type list yields only the fence guard, not a degenerate ()", () => {
259
+ let filter = Runtime.buildScanFilter(~eventTypes=[])
260
+ expect(filter.filterExpression)->toEqual(Some("attribute_exists(#evt)"))
261
+ expect(filter.expressionAttributeValues)->toEqual(None)
262
+ })
263
+
264
+ testSync("an event-type filter is AND-ed after the fence guard", () => {
265
+ let filter = Runtime.buildScanFilter(~eventTypes=["OrderPlaced", "OrderShipped"])
266
+ expect(filter.filterExpression)->toEqual(
267
+ Some("attribute_exists(#evt) AND (#evt = :type0 OR #evt = :type1)"),
268
+ )
269
+ expect(filter.expressionAttributeValues)->toEqual(
270
+ Some(
271
+ Dict.fromArray([
272
+ (":type0", "OrderPlaced"->JSON.Encode.string),
273
+ (":type1", "OrderShipped"->JSON.Encode.string),
274
+ ]),
275
+ ),
276
+ )
277
+ })
278
+ })
279
+
280
+ describe("Runtime.buildEventPuts", () => {
281
+ let event = (eventType, tags): ReventlessCore.DcbEventLog_Adapter.rawStoredEvent => {
282
+ eventType,
283
+ data: JSON.Object(Dict.make()),
284
+ tags,
285
+ meta: testMeta(),
286
+ }
287
+
288
+ testSync("emits one Put per event with the table name", () => {
289
+ let puts = Runtime.buildEventPuts(
290
+ table,
291
+ [event("Created", [tag("orderId", "o1")]), event("Shipped", [tag("orderId", "o1")])],
292
+ "100",
293
+ )
294
+ expect(puts->Array.length)->toBe(2)
295
+ let first = puts->Array.getUnsafe(0)
296
+ expect(first.put->Option.map(p => p.tableName))->toEqual(Some("TestTable"))
297
+ })
298
+
299
+ testSync("returns no Puts when events is empty", () => {
300
+ let puts = Runtime.buildEventPuts(table, [], "100")
301
+ expect(puts->Array.length)->toBe(0)
302
+ })
303
+ })
304
+
305
+ describe("Runtime.appendUnconditional", () => {
306
+ let event = (eventType, tags): ReventlessCore.DcbEventLog_Adapter.rawStoredEvent => {
307
+ eventType,
308
+ data: JSON.Object(Dict.make()),
309
+ tags,
310
+ meta: testMeta(),
311
+ }
312
+
313
+ testAsync("rejects appends exceeding 100 items with a clear error", async () => {
314
+ // 100 distinct tag values across events → 100 fence bumps + 1 event = 101 items.
315
+ let manyTags = Array.fromInitializer(~length=100, i => tag("k", `v${i->Int.toString}`))
316
+ let events = manyTags->Array.map(t => event("Foo", [t]))
317
+ let result = await Runtime.appendUnconditional(table, events)
318
+ switch result {
319
+ | Error(msg) => expect(msg->String.includes("limit exceeded"))->toBe(true)
320
+ | Ok(_) => expect("expected Error, got Ok")->toBe("")
321
+ }
322
+ })
323
+ })
324
+
325
+ describe("Runtime.buildConditionalTransactItems — fence-scope = read-scope", () => {
326
+ let event = (eventType, tags): ReventlessCore.DcbEventLog_Adapter.rawStoredEvent => {
327
+ eventType,
328
+ data: JSON.Object(Dict.make()),
329
+ tags,
330
+ meta: testMeta(),
331
+ }
332
+
333
+ // Locate the fence transact item (Update or ConditionCheck) targeting `fenceId`.
334
+ let findFence = (items: array<AwsSdk.DynamoDb.DocumentClient.TransactWriteCommand.transactWriteItem>, fenceId) =>
335
+ items->Array.find(it => {
336
+ let idOf = key => key->Dict.get("id") == Some(fenceId->JSON.Encode.string)
337
+ switch (it.update, it.conditionCheck) {
338
+ | (Some(u), _) => idOf(u.key)
339
+ | (_, Some(c)) => idOf(c.key)
340
+ | _ => false
341
+ }
342
+ })
343
+ let isUpdate = it => it->Option.flatMap(i => i.AwsSdk.DynamoDb.DocumentClient.TransactWriteCommand.update)->Option.isSome
344
+ let isCheck = it => it->Option.flatMap(i => i.AwsSdk.DynamoDb.DocumentClient.TransactWriteCommand.conditionCheck)->Option.isSome
345
+
346
+ describe("PlaceOrder shape: OrderPlaced partitioned by orderId, reads orderId + productId", () => {
347
+ let orderPlaced = event(
348
+ "OrderPlaced",
349
+ [tag("orderId", "o1"), tag("customerId", "c1"), tag("productId", "p5")],
350
+ )
351
+ let partitionTag = Some(Reventless.DcbTag.Simple({key: "orderId"}))
352
+ let cond: Reventless.DcbTag.appendCondition = {
353
+ query: [
354
+ {tags: [tag("orderId", "o1")], eventTypes: ["OrderPlaced"]},
355
+ {tags: [tag("productId", "p5")], eventTypes: ["OrderPlaced", "CatalogProductSynced"]},
356
+ ],
357
+ after: "50",
358
+ }
359
+ let items = Runtime.buildConditionalTransactItems(table, [orderPlaced], cond, "100", ~partitionTag?)
360
+
361
+ testSync("partition tag (orderId) is a conditional Update that bumps the fence", () => {
362
+ expect(isUpdate(findFence(items, "fence#orderId:o1")))->toBe(true)
363
+ })
364
+
365
+ testSync("non-partition read tag (productId) is a ConditionCheck, not a bump", () => {
366
+ let it = findFence(items, "fence#productId:p5")
367
+ expect(isCheck(it))->toBe(true)
368
+ expect(isUpdate(it))->toBe(false)
369
+ })
370
+
371
+ testSync("secondary event tag (customerId) is not fenced at all", () => {
372
+ expect(findFence(items, "fence#customerId:c1")->Option.isSome)->toBe(false)
373
+ })
374
+ })
375
+
376
+ describe("composite (multi-tag) read keeps check+bump on all its tags", () => {
377
+ // RecordProductDemand-style: one multi-tag clause => composite GSI read.
378
+ let demand = event("ProductDemandRecorded", [tag("productId", "p1"), tag("orderId", "o1")])
379
+ let partitionTag = Some(Reventless.DcbTag.Simple({key: "productId"}))
380
+ let cond: Reventless.DcbTag.appendCondition = {
381
+ query: [
382
+ {tags: [tag("productId", "p1"), tag("orderId", "o1")], eventTypes: ["ProductDemandRecorded"]},
383
+ ],
384
+ after: "50",
385
+ }
386
+ let items = Runtime.buildConditionalTransactItems(table, [demand], cond, "100", ~partitionTag?)
387
+
388
+ testSync("partition tag (productId) is a conditional Update", () => {
389
+ expect(isUpdate(findFence(items, "fence#productId:p1")))->toBe(true)
390
+ })
391
+
392
+ testSync("other composite tag (orderId) is also a conditional Update — OCC preserved", () => {
393
+ expect(isUpdate(findFence(items, "fence#orderId:o1")))->toBe(true)
394
+ })
395
+ })
396
+
397
+ // Cross-partition tag: fence bumped by every carrier (Issue 13 / Phase 7).
398
+ // StudentSubscribed partitions by courseId; studentId is @crossPartition, so a
399
+ // single-tag studentId read crosses partitions and its fence must move on every
400
+ // carrier — even though studentId is a *secondary* tag here. After fan-out the
401
+ // M:N command yields two single-tag clauses.
402
+ describe("cross-partition secondary tag (studentId) is check+bump, not read-only", () => {
403
+ let subscribed = event("StudentSubscribed", [tag("courseId", "C1"), tag("studentId", "S1")])
404
+ let partitionTag = Some(Reventless.DcbTag.Simple({key: "courseId"}))
405
+ let cond: Reventless.DcbTag.appendCondition = {
406
+ query: [
407
+ {tags: [tag("courseId", "C1")], eventTypes: ["StudentSubscribed"]},
408
+ {tags: [tag("studentId", "S1")], eventTypes: ["StudentSubscribed"]},
409
+ ],
410
+ after: "50",
411
+ }
412
+
413
+ testSync("with studentId cross-partition, its fence is a conditional Update (bump)", () => {
414
+ let items = Runtime.buildConditionalTransactItems(
415
+ table,
416
+ [subscribed],
417
+ cond,
418
+ "100",
419
+ ~partitionTag?,
420
+ ~crossPartitionTagKeys=["studentId"],
421
+ )
422
+ // courseId (partition) bumps as usual; studentId (cross-partition secondary) also bumps.
423
+ expect(isUpdate(findFence(items, "fence#courseId:C1")))->toBe(true)
424
+ expect(isUpdate(findFence(items, "fence#studentId:S1")))->toBe(true)
425
+ })
426
+
427
+ testSync("without the cross-partition flag, studentId is only a read-only ConditionCheck", () => {
428
+ let items = Runtime.buildConditionalTransactItems(table, [subscribed], cond, "100", ~partitionTag?)
429
+ let it = findFence(items, "fence#studentId:S1")
430
+ expect(isCheck(it))->toBe(true)
431
+ expect(isUpdate(it))->toBe(false)
432
+ })
433
+ })
434
+ })
435
+
436
+ describe("Runtime.buildConditionalTransactItems — folded create guard (after=None)", () => {
437
+ let event = (eventType, tags): ReventlessCore.DcbEventLog_Adapter.rawStoredEvent => {
438
+ eventType,
439
+ data: JSON.Object(Dict.make()),
440
+ tags,
441
+ meta: testMeta(),
442
+ }
443
+
444
+ // The partition-tag fence Update for `fenceId`, if any.
445
+ let fenceUpdate = (
446
+ items: array<AwsSdk.DynamoDb.DocumentClient.TransactWriteCommand.transactWriteItem>,
447
+ fenceId,
448
+ ) =>
449
+ items->Array.findMap(it =>
450
+ switch it.AwsSdk.DynamoDb.DocumentClient.TransactWriteCommand.update {
451
+ | Some(u) if u.key->Dict.get("id") == Some(fenceId->JSON.Encode.string) => Some(u)
452
+ | _ => None
453
+ }
454
+ )
455
+
456
+ // True if any item targets a legacy `create#…` sentinel row (must be none now).
457
+ let hasCreateRow = (items: array<AwsSdk.DynamoDb.DocumentClient.TransactWriteCommand.transactWriteItem>) =>
458
+ items->Array.some(it =>
459
+ switch it.AwsSdk.DynamoDb.DocumentClient.TransactWriteCommand.update {
460
+ | Some(u) =>
461
+ u.key
462
+ ->Dict.get("id")
463
+ ->Option.flatMap(JSON.Decode.string)
464
+ ->Option.mapOr(false, id => id->String.startsWith("create#"))
465
+ | None => false
466
+ }
467
+ )
468
+
469
+ let referencedAttrs = (u: AwsSdk.DynamoDb.DocumentClient.TransactWriteCommand.update) =>
470
+ u.expressionAttributeNames->Option.getOr(Dict.make())->Dict.valuesToArray
471
+
472
+ describe("first write of a create slice (consumes == produces)", () => {
473
+ let added = event("ProductAdded", [tag("productId", "p1")])
474
+ let partitionTag = Some(Reventless.DcbTag.Simple({key: "productId"}))
475
+ let cond: Reventless.DcbTag.appendCondition = {
476
+ query: [{tags: [tag("productId", "p1")], eventTypes: ["ProductAdded"]}],
477
+ }
478
+ let items = Runtime.buildConditionalTransactItems(table, [added], cond, "100", ~partitionTag?)
479
+
480
+ testSync("no separate create# row is emitted (guard folded into the fence)", () => {
481
+ expect(hasCreateRow(items))->toBe(false)
482
+ })
483
+
484
+ testSync("the partition fence is a conditional Update gated on attribute_not_exists", () => {
485
+ let u = fenceUpdate(items, "fence#productId:p1")->Option.getOrThrow
486
+ expect(u.conditionExpression)->toEqual(Some("attribute_not_exists(#c0)"))
487
+ expect(u.updateExpression)->toBe("SET #p0 = :new")
488
+ expect(u.expressionAttributeValues->Option.flatMap(v => v->Dict.get(":after")))->toEqual(None)
489
+ })
490
+
491
+ testSync("the guard references only the produced/consumed type", () => {
492
+ let u = fenceUpdate(items, "fence#productId:p1")->Option.getOrThrow
493
+ let referenced = referencedAttrs(u)
494
+ expect(referenced->Array.includes("pos#ProductAdded"))->toBe(true)
495
+ expect(referenced->Array.includes("pos#ProductPriceChanged"))->toBe(false)
496
+ })
497
+ })
498
+
499
+ describe("produced-not-consumed (the double-create gate — analysis con #1)", () => {
500
+ // A slice producing a type it does NOT read must still guard on the produced
501
+ // type at after=None, or two concurrent first-writers both commit.
502
+ let started = event("OrderStarted", [tag("orderId", "o1")])
503
+ let partitionTag = Some(Reventless.DcbTag.Simple({key: "orderId"}))
504
+ let cond: Reventless.DcbTag.appendCondition = {
505
+ query: [{tags: [tag("orderId", "o1")], eventTypes: ["OrderDrafted"]}],
506
+ }
507
+ let items = Runtime.buildConditionalTransactItems(table, [started], cond, "100", ~partitionTag?)
508
+
509
+ testSync("the produced type is in the guard even though it is not consumed", () => {
510
+ let u = fenceUpdate(items, "fence#orderId:o1")->Option.getOrThrow
511
+ let referenced = referencedAttrs(u)
512
+ // guard set = consumed ∪ produced = {OrderDrafted, OrderStarted}
513
+ expect(referenced->Array.includes("pos#OrderStarted"))->toBe(true)
514
+ expect(referenced->Array.includes("pos#OrderDrafted"))->toBe(true)
515
+ })
516
+
517
+ testSync("the bump advances the produced type", () => {
518
+ let u = fenceUpdate(items, "fence#orderId:o1")->Option.getOrThrow
519
+ expect(u.updateExpression)->toBe("SET #p0 = :new")
520
+ expect(u.expressionAttributeNames->Option.flatMap(n => n->Dict.get("#p0")))->toEqual(
521
+ Some("pos#OrderStarted"),
522
+ )
523
+ })
524
+ })
525
+
526
+ describe("at after=Some (entity exists)", () => {
527
+ let changed = event("ProductNameChanged", [tag("productId", "p1")])
528
+ let partitionTag = Some(Reventless.DcbTag.Simple({key: "productId"}))
529
+ let cond: Reventless.DcbTag.appendCondition = {
530
+ query: [
531
+ {tags: [tag("productId", "p1")], eventTypes: ["ProductAdded", "ProductNameChanged"]},
532
+ ],
533
+ after: "50",
534
+ }
535
+ let items = Runtime.buildConditionalTransactItems(table, [changed], cond, "100", ~partitionTag?)
536
+
537
+ testSync("emits no create# row", () => {
538
+ expect(hasCreateRow(items))->toBe(false)
539
+ })
540
+
541
+ testSync("the fence check covers consumed types but not unrelated ones (Issue 4 fix)", () => {
542
+ let u = fenceUpdate(items, "fence#productId:p1")->Option.getOrThrow
543
+ let referenced = referencedAttrs(u)
544
+ expect(referenced->Array.includes("pos#ProductAdded"))->toBe(true)
545
+ expect(referenced->Array.includes("pos#ProductNameChanged"))->toBe(true)
546
+ expect(referenced->Array.includes("pos#ProductPriceChanged"))->toBe(false)
547
+ })
548
+ })
549
+ })
550
+
551
+ describe("Runtime.appendConditional", () => {
552
+ testAsync("rejects tagless conditions with a clear error", async () => {
553
+ let cond: Reventless.DcbTag.appendCondition = {
554
+ query: [{eventTypes: ["Foo"]}],
555
+ }
556
+ let result = await Runtime.appendConditional(table, [], cond)
557
+ switch result {
558
+ | Error(msg) =>
559
+ expect(msg->String.includes("tagless"))->toBe(true)
560
+ | Ok(_) => expect("expected Error, got Ok")->toBe("")
561
+ }
562
+ })
563
+
564
+ testAsync("rejects appends exceeding 100 items with a clear error", async () => {
565
+ // 100 unique single-tag query clauses + 1 event = 101 items, over the limit.
566
+ // `after` is set so each clause yields a conditional fence item (Update for the
567
+ // partition tag, ConditionCheck for the rest) rather than the idempotent
568
+ // bump-only fallback.
569
+ let manyTags = Array.fromInitializer(~length=100, i => tag("k", `v${i->Int.toString}`))
570
+ let cond: Reventless.DcbTag.appendCondition = {
571
+ query: manyTags->Array.map(t => {Reventless.DcbTag.tags: [t], eventTypes: ["Foo"]}),
572
+ after: "0",
573
+ }
574
+ let event: ReventlessCore.DcbEventLog_Adapter.rawStoredEvent = {
575
+ eventType: "Foo",
576
+ data: JSON.Object(Dict.make()),
577
+ tags: [tag("k", "v0")],
578
+ meta: testMeta(),
579
+ }
580
+ let result = await Runtime.appendConditional(table, [event], cond)
581
+ switch result {
582
+ | Error(msg) =>
583
+ expect(msg->String.includes("limit exceeded"))->toBe(true)
584
+ | Ok(_) => expect("expected Error, got Ok")->toBe("")
585
+ }
586
+ })
587
+ })
588
+
589
+ // Phase 3: the deploy-time `make` provisions `tag_composite` with a full (ALL)
590
+ // projection and every per-tag `tag_<key>` GSI as KEYS_ONLY. This guards the
591
+ // pure predicate that drives that decision (the Pulumi wiring itself is
592
+ // type-checked by the build, not unit-tested).
593
+ describe("Runtime.indexKeepsFullProjection", () => {
594
+ testSync("tag_composite keeps a full projection", () => {
595
+ expect(Runtime.indexKeepsFullProjection("tag_composite"))->toBe(true)
596
+ })
597
+ testSync("per-tag GSIs are KEYS_ONLY (no full projection)", () => {
598
+ expect(Runtime.indexKeepsFullProjection("tag_orderId"))->toBe(false)
599
+ expect(Runtime.indexKeepsFullProjection("tag_customerId"))->toBe(false)
600
+ expect(Runtime.indexKeepsFullProjection("tag_productId"))->toBe(false)
601
+ })
602
+ })