@reventlessdev/reventless-local 3.0.0-alpha.113

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 (334) hide show
  1. package/CHANGELOG.md +1197 -0
  2. package/LICENSE +202 -0
  3. package/__mocks__/emptyModule.js +2 -0
  4. package/__mocks__/nodeSqlite.mjs +6 -0
  5. package/package.json +67 -0
  6. package/rescript.json +39 -0
  7. package/src/Platform.res +2162 -0
  8. package/src/Platform.res.mjs +3389 -0
  9. package/src/adapter/Api/LocalGraphQL_Adapter.res +29 -0
  10. package/src/adapter/Api/LocalGraphQL_Adapter.res.mjs +26 -0
  11. package/src/adapter/Api/LocalGraphQL_SubscriptionResolvers.res +146 -0
  12. package/src/adapter/Api/LocalGraphQL_SubscriptionResolvers.res.mjs +88 -0
  13. package/src/adapter/Auth/Auth_GraphqlContext.res +83 -0
  14. package/src/adapter/Auth/Auth_GraphqlContext.res.mjs +76 -0
  15. package/src/adapter/Auth/LocalAuth.res +239 -0
  16. package/src/adapter/Auth/LocalAuth.res.mjs +247 -0
  17. package/src/adapter/Auth/UserStore.res +134 -0
  18. package/src/adapter/Auth/UserStore.res.mjs +146 -0
  19. package/src/adapter/Backend.res +46 -0
  20. package/src/adapter/Backend.res.mjs +66 -0
  21. package/src/adapter/BackendState.res +34 -0
  22. package/src/adapter/BackendState.res.mjs +42 -0
  23. package/src/adapter/Cloner/LocalClonerRunner.res +13 -0
  24. package/src/adapter/Cloner/LocalClonerRunner.res.mjs +12 -0
  25. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +329 -0
  26. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res.mjs +301 -0
  27. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +76 -0
  28. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res.mjs +57 -0
  29. package/src/adapter/CommandGenerator/LocalCommandGeneratorResolvers.res +19 -0
  30. package/src/adapter/CommandGenerator/LocalCommandGeneratorResolvers.res.mjs +19 -0
  31. package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +104 -0
  32. package/src/adapter/CommandTopic/LocalCommandTopicChannel.res.mjs +82 -0
  33. package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +32 -0
  34. package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res.mjs +38 -0
  35. package/src/adapter/Counter/LocalCounterHandler.res +42 -0
  36. package/src/adapter/Counter/LocalCounterHandler.res.mjs +45 -0
  37. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +131 -0
  38. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res.mjs +140 -0
  39. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +334 -0
  40. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs +299 -0
  41. package/src/adapter/DomainGraphQL_Server.res +565 -0
  42. package/src/adapter/DomainGraphQL_Server.res.mjs +576 -0
  43. package/src/adapter/DomainMCP_Server.res +369 -0
  44. package/src/adapter/DomainMCP_Server.res.mjs +358 -0
  45. package/src/adapter/EventCollector/LocalEventCollectorChannel.res +87 -0
  46. package/src/adapter/EventCollector/LocalEventCollectorChannel.res.mjs +45 -0
  47. package/src/adapter/EventLog/EventLogStorage_InMemory.res +105 -0
  48. package/src/adapter/EventLog/EventLogStorage_InMemory.res.mjs +117 -0
  49. package/src/adapter/EventLog/EventLogStorage_Sqlite.res +145 -0
  50. package/src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs +155 -0
  51. package/src/adapter/EventTopic/LocalEventTopicPublisher.res +36 -0
  52. package/src/adapter/EventTopic/LocalEventTopicPublisher.res.mjs +31 -0
  53. package/src/adapter/GraphQL_Server.res +6 -0
  54. package/src/adapter/GraphQL_Server.res.mjs +154 -0
  55. package/src/adapter/GraphQL_ServerInstance.res +310 -0
  56. package/src/adapter/GraphQL_ServerInstance.res.mjs +257 -0
  57. package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +57 -0
  58. package/src/adapter/Heartbeat/LocalHeartbeatRunner.res.mjs +34 -0
  59. package/src/adapter/LocalBus.res +498 -0
  60. package/src/adapter/LocalBus.res.mjs +1011 -0
  61. package/src/adapter/LocalPluginSpec.res +16 -0
  62. package/src/adapter/LocalPluginSpec.res.mjs +33 -0
  63. package/src/adapter/LocalSideEffectHandler.res +44 -0
  64. package/src/adapter/LocalSideEffectHandler.res.mjs +34 -0
  65. package/src/adapter/MCP_Server.res +4 -0
  66. package/src/adapter/MCP_Server.res.mjs +67 -0
  67. package/src/adapter/MCP_ServerInstance.res +362 -0
  68. package/src/adapter/MCP_ServerInstance.res.mjs +313 -0
  69. package/src/adapter/PlatformGraphQL_Server.res +75 -0
  70. package/src/adapter/PlatformGraphQL_Server.res.mjs +84 -0
  71. package/src/adapter/PlatformMCP_Server.res +23 -0
  72. package/src/adapter/PlatformMCP_Server.res.mjs +46 -0
  73. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +793 -0
  74. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +569 -0
  75. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +242 -0
  76. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res.mjs +241 -0
  77. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +339 -0
  78. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs +318 -0
  79. package/src/adapter/QueryDb/SortKey_Filter.res +48 -0
  80. package/src/adapter/QueryDb/SortKey_Filter.res.mjs +46 -0
  81. package/src/adapter/QueryEngine/LocalQueryEngine.res +57 -0
  82. package/src/adapter/QueryEngine/LocalQueryEngine.res.mjs +56 -0
  83. package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +91 -0
  84. package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res.mjs +50 -0
  85. package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +47 -0
  86. package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res.mjs +37 -0
  87. package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +11 -0
  88. package/src/adapter/Runtime/LocalPluginRuntime_Builder.res.mjs +30 -0
  89. package/src/adapter/Runtime/LocalRuntimeEnvironment.res +54 -0
  90. package/src/adapter/Runtime/LocalRuntimeEnvironment.res.mjs +40 -0
  91. package/src/adapter/Scheduler/LocalScheduledPublisher.res +102 -0
  92. package/src/adapter/Scheduler/LocalScheduledPublisher.res.mjs +84 -0
  93. package/src/adapter/SqliteDriver.res +52 -0
  94. package/src/adapter/SqliteDriver.res.mjs +76 -0
  95. package/src/adapter/SqliteDriver.resi +31 -0
  96. package/src/adapter/Task/LocalTaskBucket.res +75 -0
  97. package/src/adapter/Task/LocalTaskBucket.res.mjs +48 -0
  98. package/src/adapter/Task/TaskBucket_Sqlite.res +47 -0
  99. package/src/adapter/Task/TaskBucket_Sqlite.res.mjs +40 -0
  100. package/src/components/Aggregate_Builder.res +55 -0
  101. package/src/components/Aggregate_Builder.res.mjs +140 -0
  102. package/src/components/AutomationSlice_Builder.res +41 -0
  103. package/src/components/AutomationSlice_Builder.res.mjs +57 -0
  104. package/src/components/Counter_Builder.res +14 -0
  105. package/src/components/Counter_Builder.res.mjs +32 -0
  106. package/src/components/DcbEventLog_Builder.res +18 -0
  107. package/src/components/DcbEventLog_Builder.res.mjs +24 -0
  108. package/src/components/ExtensionPoint_Builder.res +25 -0
  109. package/src/components/ExtensionPoint_Builder.res.mjs +44 -0
  110. package/src/components/InboundTranslationSlice_Builder.res +34 -0
  111. package/src/components/InboundTranslationSlice_Builder.res.mjs +45 -0
  112. package/src/components/OutboundTranslationSlice_Builder.res +43 -0
  113. package/src/components/OutboundTranslationSlice_Builder.res.mjs +59 -0
  114. package/src/components/Plugin_Builder.res +37 -0
  115. package/src/components/Plugin_Builder.res.mjs +67 -0
  116. package/src/components/ReadModel_Builder.res +47 -0
  117. package/src/components/ReadModel_Builder.res.mjs +54 -0
  118. package/src/components/StateChangeSlice_Builder.res +19 -0
  119. package/src/components/StateChangeSlice_Builder.res.mjs +21 -0
  120. package/src/components/StateViewSlice_Builder.res +42 -0
  121. package/src/components/StateViewSlice_Builder.res.mjs +58 -0
  122. package/src/components/Task_Builder.res +28 -0
  123. package/src/components/Task_Builder.res.mjs +41 -0
  124. package/src/reexport/CommandTopic.res +3 -0
  125. package/src/reexport/CommandTopic.res.mjs +55 -0
  126. package/src/test/Mocks/MockDcbEventLogStorage.res +136 -0
  127. package/src/test/Mocks/MockDcbEventLogStorage.res.mjs +146 -0
  128. package/src/test/Mocks/MockEventLogStorage.res +57 -0
  129. package/src/test/Mocks/MockEventLogStorage.res.mjs +65 -0
  130. package/src/test/Mocks/MockPublisher.res +29 -0
  131. package/src/test/Mocks/MockPublisher.res.mjs +28 -0
  132. package/src/test/Mocks/MockQueryDbStorage.res +104 -0
  133. package/src/test/Mocks/MockQueryDbStorage.res.mjs +136 -0
  134. package/src/test/TestRunner.res +96 -0
  135. package/src/test/TestRunner.res.mjs +71 -0
  136. package/tests/SplitApiFixtures.res +84 -0
  137. package/tests/SplitApiFixtures.res.mjs +88 -0
  138. package/tests/SplitApiTest.res +102 -0
  139. package/tests/SplitApiTest.res.mjs +82 -0
  140. package/tests/TestFixtures.res +54 -0
  141. package/tests/TestFixtures.res.mjs +65 -0
  142. package/tests/adapter/BackendParityTest.res +94 -0
  143. package/tests/adapter/BackendParityTest.res.mjs +96 -0
  144. package/tests/adapter/CollectNEventsTest.res +82 -0
  145. package/tests/adapter/CollectNEventsTest.res.mjs +83 -0
  146. package/tests/adapter/CommandAuthorizationTest.res +200 -0
  147. package/tests/adapter/CommandAuthorizationTest.res.mjs +190 -0
  148. package/tests/adapter/CommandTopicChannelTest.res +174 -0
  149. package/tests/adapter/CommandTopicChannelTest.res.mjs +170 -0
  150. package/tests/adapter/CounterHandlerTest.res +113 -0
  151. package/tests/adapter/CounterHandlerTest.res.mjs +96 -0
  152. package/tests/adapter/DcbEventLogStorageSqliteTest.res +157 -0
  153. package/tests/adapter/DcbEventLogStorageSqliteTest.res.mjs +165 -0
  154. package/tests/adapter/DcbEventLogStorageTest.res +187 -0
  155. package/tests/adapter/DcbEventLogStorageTest.res.mjs +195 -0
  156. package/tests/adapter/EventCollectorChannelTest.res +166 -0
  157. package/tests/adapter/EventCollectorChannelTest.res.mjs +178 -0
  158. package/tests/adapter/EventLogStorageSqliteTest.res +105 -0
  159. package/tests/adapter/EventLogStorageSqliteTest.res.mjs +116 -0
  160. package/tests/adapter/EventTapTest.res +102 -0
  161. package/tests/adapter/EventTapTest.res.mjs +94 -0
  162. package/tests/adapter/EventTopicPublisherTest.res +76 -0
  163. package/tests/adapter/EventTopicPublisherTest.res.mjs +86 -0
  164. package/tests/adapter/GraphQL_SchemaInspectorTest.res +763 -0
  165. package/tests/adapter/GraphQL_SchemaInspectorTest.res.mjs +578 -0
  166. package/tests/adapter/GraphQL_SubscriptionResolversTest.res +161 -0
  167. package/tests/adapter/GraphQL_SubscriptionResolversTest.res.mjs +134 -0
  168. package/tests/adapter/HeartbeatRunnerTest.res +136 -0
  169. package/tests/adapter/HeartbeatRunnerTest.res.mjs +96 -0
  170. package/tests/adapter/LocalAuthHttpTest.res +224 -0
  171. package/tests/adapter/LocalAuthHttpTest.res.mjs +237 -0
  172. package/tests/adapter/LocalAuthLoginTest.res +180 -0
  173. package/tests/adapter/LocalAuthLoginTest.res.mjs +207 -0
  174. package/tests/adapter/LocalAuthTest.res +106 -0
  175. package/tests/adapter/LocalAuthTest.res.mjs +145 -0
  176. package/tests/adapter/LocalAuthUserStoreTest.res +172 -0
  177. package/tests/adapter/LocalAuthUserStoreTest.res.mjs +208 -0
  178. package/tests/adapter/LocalBusBoundedTest.res +157 -0
  179. package/tests/adapter/LocalBusBoundedTest.res.mjs +168 -0
  180. package/tests/adapter/LocalBusPubSubTest.res +81 -0
  181. package/tests/adapter/LocalBusPubSubTest.res.mjs +81 -0
  182. package/tests/adapter/QueryDbGsiTtlTest.res +268 -0
  183. package/tests/adapter/QueryDbGsiTtlTest.res.mjs +209 -0
  184. package/tests/adapter/QueryDbListResolverTest.res +361 -0
  185. package/tests/adapter/QueryDbListResolverTest.res.mjs +426 -0
  186. package/tests/adapter/QueryDbStorageSqliteTest.res +159 -0
  187. package/tests/adapter/QueryDbStorageSqliteTest.res.mjs +126 -0
  188. package/tests/adapter/QueryDbStorageTest.res +171 -0
  189. package/tests/adapter/QueryDbStorageTest.res.mjs +170 -0
  190. package/tests/adapter/QueryEngineTest.res +152 -0
  191. package/tests/adapter/QueryEngineTest.res.mjs +148 -0
  192. package/tests/adapter/ScheduledPublisherTest.res +199 -0
  193. package/tests/adapter/ScheduledPublisherTest.res.mjs +203 -0
  194. package/tests/adapter/SortKey_FilterTest.res +103 -0
  195. package/tests/adapter/SortKey_FilterTest.res.mjs +135 -0
  196. package/tests/adapter/SqliteDriverTest.res +86 -0
  197. package/tests/adapter/SqliteDriverTest.res.mjs +101 -0
  198. package/tests/adapter/TaskBucketSqliteTest.res +44 -0
  199. package/tests/adapter/TaskBucketSqliteTest.res.mjs +40 -0
  200. package/tests/adapter/TaskBucketTest.res +106 -0
  201. package/tests/adapter/TaskBucketTest.res.mjs +118 -0
  202. package/tests/components/aggregate/AggregateFixtures.res +78 -0
  203. package/tests/components/aggregate/AggregateFixtures.res.mjs +109 -0
  204. package/tests/components/aggregate/AggregateTest.res +129 -0
  205. package/tests/components/aggregate/AggregateTest.res.mjs +129 -0
  206. package/tests/components/automationslice/AutomationSliceCallbackTest.res +212 -0
  207. package/tests/components/automationslice/AutomationSliceCallbackTest.res.mjs +233 -0
  208. package/tests/components/automationslice/AutomationSliceFixtures.res +189 -0
  209. package/tests/components/automationslice/AutomationSliceFixtures.res.mjs +276 -0
  210. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +289 -0
  211. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +453 -0
  212. package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res +47 -0
  213. package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res.mjs +31 -0
  214. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +213 -0
  215. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res.mjs +268 -0
  216. package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +118 -0
  217. package/tests/components/automationslice/MixedSourceAutomationSliceTest.res.mjs +99 -0
  218. package/tests/components/commandgenerator/CommandGeneratorFixtures.res +73 -0
  219. package/tests/components/commandgenerator/CommandGeneratorFixtures.res.mjs +109 -0
  220. package/tests/components/commandgenerator/CommandGeneratorTest.res +43 -0
  221. package/tests/components/commandgenerator/CommandGeneratorTest.res.mjs +46 -0
  222. package/tests/components/commandtopic/CommandTopicFixtures.res +37 -0
  223. package/tests/components/commandtopic/CommandTopicFixtures.res.mjs +68 -0
  224. package/tests/components/commandtopic/CommandTopicStreamFixtures.res +43 -0
  225. package/tests/components/commandtopic/CommandTopicStreamFixtures.res.mjs +91 -0
  226. package/tests/components/commandtopic/CommandTopicStreamTest.res +30 -0
  227. package/tests/components/commandtopic/CommandTopicStreamTest.res.mjs +35 -0
  228. package/tests/components/commandtopic/CommandTopicTest.res +55 -0
  229. package/tests/components/commandtopic/CommandTopicTest.res.mjs +62 -0
  230. package/tests/components/counter/CounterFixtures.res +42 -0
  231. package/tests/components/counter/CounterFixtures.res.mjs +34 -0
  232. package/tests/components/counter/CounterTest.res +46 -0
  233. package/tests/components/counter/CounterTest.res.mjs +61 -0
  234. package/tests/components/dcb/DcbCrossPartitionFixtures.res +170 -0
  235. package/tests/components/dcb/DcbCrossPartitionFixtures.res.mjs +276 -0
  236. package/tests/components/dcb/DcbCrossPartitionReadTest.res +68 -0
  237. package/tests/components/dcb/DcbCrossPartitionReadTest.res.mjs +78 -0
  238. package/tests/components/dcb/DcbEventLogAppendStreamTest.res +72 -0
  239. package/tests/components/dcb/DcbEventLogAppendStreamTest.res.mjs +82 -0
  240. package/tests/components/dcb/DcbEventLogStreamTest.res +103 -0
  241. package/tests/components/dcb/DcbEventLogStreamTest.res.mjs +91 -0
  242. package/tests/components/dcb/DcbFixtures.res +145 -0
  243. package/tests/components/dcb/DcbFixtures.res.mjs +221 -0
  244. package/tests/components/dcb/DcbTest.res +55 -0
  245. package/tests/components/dcb/DcbTest.res.mjs +55 -0
  246. package/tests/components/eventcollector/EventCollectorFixtures.res +42 -0
  247. package/tests/components/eventcollector/EventCollectorFixtures.res.mjs +49 -0
  248. package/tests/components/eventcollector/EventCollectorTest.res +37 -0
  249. package/tests/components/eventcollector/EventCollectorTest.res.mjs +36 -0
  250. package/tests/components/eventlog/EventLogAppendStreamTest.res +93 -0
  251. package/tests/components/eventlog/EventLogAppendStreamTest.res.mjs +113 -0
  252. package/tests/components/eventlog/EventLogConflictTest.res +86 -0
  253. package/tests/components/eventlog/EventLogConflictTest.res.mjs +81 -0
  254. package/tests/components/eventlog/EventLogFixtures.res +52 -0
  255. package/tests/components/eventlog/EventLogFixtures.res.mjs +80 -0
  256. package/tests/components/eventlog/EventLogStreamTest.res +98 -0
  257. package/tests/components/eventlog/EventLogStreamTest.res.mjs +142 -0
  258. package/tests/components/eventlog/EventLogTest.res +53 -0
  259. package/tests/components/eventlog/EventLogTest.res.mjs +74 -0
  260. package/tests/components/eventtopic/EventTopicFixtures.res +47 -0
  261. package/tests/components/eventtopic/EventTopicFixtures.res.mjs +77 -0
  262. package/tests/components/eventtopic/EventTopicStreamFixtures.res +27 -0
  263. package/tests/components/eventtopic/EventTopicStreamFixtures.res.mjs +62 -0
  264. package/tests/components/eventtopic/EventTopicStreamTest.res +35 -0
  265. package/tests/components/eventtopic/EventTopicStreamTest.res.mjs +44 -0
  266. package/tests/components/eventtopic/EventTopicTest.res +35 -0
  267. package/tests/components/eventtopic/EventTopicTest.res.mjs +43 -0
  268. package/tests/components/extensionpoint/ExtensionPointFixtures.res +145 -0
  269. package/tests/components/extensionpoint/ExtensionPointFixtures.res.mjs +188 -0
  270. package/tests/components/extensionpoint/ExtensionPointTest.res +62 -0
  271. package/tests/components/extensionpoint/ExtensionPointTest.res.mjs +55 -0
  272. package/tests/components/heartbeat/HeartbeatFixtures.res +48 -0
  273. package/tests/components/heartbeat/HeartbeatFixtures.res.mjs +46 -0
  274. package/tests/components/heartbeat/HeartbeatTest.res +67 -0
  275. package/tests/components/heartbeat/HeartbeatTest.res.mjs +65 -0
  276. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +241 -0
  277. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +239 -0
  278. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +36 -0
  279. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +73 -0
  280. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +191 -0
  281. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +297 -0
  282. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +78 -0
  283. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res.mjs +137 -0
  284. package/tests/components/querydb/QueryDbFixtures.res +39 -0
  285. package/tests/components/querydb/QueryDbFixtures.res.mjs +73 -0
  286. package/tests/components/querydb/QueryDbSubIdFixtures.res +44 -0
  287. package/tests/components/querydb/QueryDbSubIdFixtures.res.mjs +80 -0
  288. package/tests/components/querydb/QueryDbSubIdTest.res +104 -0
  289. package/tests/components/querydb/QueryDbSubIdTest.res.mjs +104 -0
  290. package/tests/components/querydb/QueryDbTest.res +81 -0
  291. package/tests/components/querydb/QueryDbTest.res.mjs +126 -0
  292. package/tests/components/readmodel/DcbReadModelE2EFixtures.res +215 -0
  293. package/tests/components/readmodel/DcbReadModelE2EFixtures.res.mjs +308 -0
  294. package/tests/components/readmodel/DcbReadModelE2ETest.res +45 -0
  295. package/tests/components/readmodel/DcbReadModelE2ETest.res.mjs +37 -0
  296. package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res +88 -0
  297. package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res.mjs +152 -0
  298. package/tests/components/readmodel/MixedSourceReadModelFixtures.res +181 -0
  299. package/tests/components/readmodel/MixedSourceReadModelFixtures.res.mjs +277 -0
  300. package/tests/components/readmodel/MixedSourceReadModelTest.res +61 -0
  301. package/tests/components/readmodel/MixedSourceReadModelTest.res.mjs +68 -0
  302. package/tests/components/readmodel/ReadModelFixtures.res +138 -0
  303. package/tests/components/readmodel/ReadModelFixtures.res.mjs +169 -0
  304. package/tests/components/readmodel/ReadModelTest.res +46 -0
  305. package/tests/components/readmodel/ReadModelTest.res.mjs +36 -0
  306. package/tests/components/scheduler/SchedulerFixtures.res +59 -0
  307. package/tests/components/scheduler/SchedulerFixtures.res.mjs +63 -0
  308. package/tests/components/scheduler/SchedulerTest.res +76 -0
  309. package/tests/components/scheduler/SchedulerTest.res.mjs +86 -0
  310. package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +133 -0
  311. package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +149 -0
  312. package/tests/components/sideeffecthandler/SideEffectHandlerTest.res +39 -0
  313. package/tests/components/sideeffecthandler/SideEffectHandlerTest.res.mjs +28 -0
  314. package/tests/components/stateviewslice/StateViewSliceFixtures.res +117 -0
  315. package/tests/components/stateviewslice/StateViewSliceFixtures.res.mjs +188 -0
  316. package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +117 -0
  317. package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res.mjs +183 -0
  318. package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +78 -0
  319. package/tests/components/stateviewslice/StateViewSliceSubIdTest.res.mjs +107 -0
  320. package/tests/components/stateviewslice/StateViewSliceTest.res +59 -0
  321. package/tests/components/stateviewslice/StateViewSliceTest.res.mjs +72 -0
  322. package/tests/components/task/TaskFixtures.res +87 -0
  323. package/tests/components/task/TaskFixtures.res.mjs +82 -0
  324. package/tests/components/task/TaskTest.res +98 -0
  325. package/tests/components/task/TaskTest.res.mjs +46 -0
  326. package/tests/plugin/PluginBehavior_GWT.res +146 -0
  327. package/tests/plugin/PluginBehavior_GWT.res.mjs +281 -0
  328. package/tests/plugin/Plugin_Fixtures.res +61 -0
  329. package/tests/plugin/Plugin_Fixtures.res.mjs +105 -0
  330. package/tests/plugin/PluginsProjection_GWT.res +95 -0
  331. package/tests/plugin/PluginsProjection_GWT.res.mjs +120 -0
  332. package/tests/plugin/UIFragmentRegistryProjection_GWT.res +72 -0
  333. package/tests/plugin/UIFragmentRegistryProjection_GWT.res.mjs +117 -0
  334. package/tests/setup/sqliteGlobal.cjs +12 -0
@@ -0,0 +1,29 @@
1
+ // LocalGraphQL_Adapter — implements ReventlessInfra.Api_Adapter.Provider for
2
+ // the in-memory graphql-yoga server.
3
+ //
4
+ // makeApiResource: no-op (server lifecycle is managed by DomainGraphQL_Server.start/rebuildSchema)
5
+ // generateFragment: delegates to ReventlessCore.GraphQL_FragmentGenerator
6
+ // updateSchema: rebuilds the stitched SDL via DomainGraphQL_Server.rebuildSchema
7
+
8
+ type api = unit
9
+ type role = unit
10
+
11
+ let makeApiResource = (
12
+ ~name as _: string,
13
+ ~opts as _: Pulumi.ComponentResource.options,
14
+ ): (Pulumi.Output.t<api>, Pulumi.Output.t<role>) => (Pulumi.Output.make(()), Pulumi.Output.make(()))
15
+
16
+ let generateFragment = (
17
+ ~mutationEntries: array<ReventlessInfra.Api.mutationSchemaEntry>,
18
+ ~queryEntries: array<ReventlessInfra.Api.querySchemaEntry>,
19
+ ): Reventless.Plugin.apiSchemaFragment =>
20
+ ReventlessCore.GraphQL_FragmentGenerator.generate(~mutationEntries, ~queryEntries)
21
+
22
+ let updateSchema = (
23
+ ~api as _: Pulumi.Output.t<api>,
24
+ ~baseFragment: Reventless.Plugin.apiSchemaFragment,
25
+ ~pluginFragments: array<Reventless.Plugin.apiSchemaFragment>,
26
+ ): promise<unit> => {
27
+ DomainGraphQL_Server.rebuildSchema(~baseFragment, ~pluginFragments)
28
+ Promise.resolve()
29
+ }
@@ -0,0 +1,26 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Pulumi from "@pulumi/pulumi";
4
+ import * as DomainGraphQL_Server$ReventlessLocal from "../DomainGraphQL_Server.res.mjs";
5
+ import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
6
+
7
+ function makeApiResource(param, param$1) {
8
+ return [
9
+ Pulumi.output(),
10
+ Pulumi.output()
11
+ ];
12
+ }
13
+
14
+ let generateFragment = GraphQL_FragmentGenerator$ReventlessCore.generate;
15
+
16
+ function updateSchema(param, baseFragment, pluginFragments) {
17
+ DomainGraphQL_Server$ReventlessLocal.rebuildSchema(baseFragment, pluginFragments);
18
+ return Promise.resolve();
19
+ }
20
+
21
+ export {
22
+ makeApiResource,
23
+ generateFragment,
24
+ updateSchema,
25
+ }
26
+ /* @pulumi/pulumi Not a pure module */
@@ -0,0 +1,146 @@
1
+ // LocalGraphQL_SubscriptionResolvers.res
2
+ // WebSocket subscription resolvers for Sources A and B (in-memory / yoga).
3
+ //
4
+ // Source A (raw event stream): subscribes to Bus.subscribeToEvents for each event log
5
+ // topic and publishes the event envelope to a yoga PubSub channel.
6
+ //
7
+ // Source B (state changes): subscribes to Bus.subscribeToStateChanges for each QueryDb
8
+ // and publishes saved state items to a yoga PubSub channel.
9
+ //
10
+ // Source C (mutation-triggered): mutation resolvers would need to publish to the PubSub
11
+ // after each successful command. This is left as future work — currently mutations do
12
+ // not cross-post to the subscription PubSub in-memory.
13
+ //
14
+ // Usage: call `setup` once per server build, then `registerAll` to wire into the GraphQL
15
+ // server instance. Both must be called before the server starts.
16
+ //
17
+ // Note: `scalar AWSJSON` is injected here so yoga accepts the AppSync scalar used in
18
+ // Source A event log types. It serialises as a plain string.
19
+
20
+ module YG = GraphqlYoga
21
+
22
+ // ── PubSub instance ───────────────────────────────────────────────────────────
23
+
24
+ let pubSub: ref<option<YG.pubSub>> = ref(None)
25
+
26
+ let getPubSub = () =>
27
+ switch pubSub.contents {
28
+ | Some(ps) => ps
29
+ | None =>
30
+ let ps = YG.createPubSub()
31
+ pubSub.contents = Some(ps)
32
+ ps
33
+ }
34
+
35
+ // ── Topic naming ──────────────────────────────────────────────────────────────
36
+ // Matches the field names generated by Plugin_SubscriptionSchema:
37
+ // Source A: on{DisplayName}EventLog_eventAppended
38
+ // Source B: on{ReturnTypeName}_stateChanged
39
+
40
+ let sourceATopic = (displayName: string) => `on${displayName}EventLog_eventAppended`
41
+ let sourceBTopic = (returnTypeName: string) => `on${returnTypeName}_stateChanged`
42
+
43
+ // ── Bridge: Bus → PubSub ──────────────────────────────────────────────────────
44
+
45
+ /** Wire Source A: subscribe to event bus topic → publish to yoga PubSub channel. */
46
+ let bridgeSourceA = (
47
+ ~subscribeToEvents: (
48
+ string,
49
+ (string, ReventlessCore.Message.meta, JSON.t) => promise<unit>,
50
+ ) => unit,
51
+ ~displayName: string,
52
+ ~busTopicName: string,
53
+ ) => {
54
+ let ps = getPubSub()
55
+ let yogaTopic = sourceATopic(displayName)
56
+ // Push the raw event JSON directly — it already contains the event data.
57
+ // In-memory events don't carry a separate position/originatorSlice envelope.
58
+ subscribeToEvents(busTopicName, async (_service, _meta, json) => {
59
+ ps->YG.pubSubPublish(yogaTopic, json)
60
+ })
61
+ }
62
+
63
+ /** Wire Source B: subscribe to state-change hook → publish to yoga PubSub channel. */
64
+ let bridgeSourceB = (
65
+ ~subscribeToStateChanges: (string, JSON.t => unit) => unit,
66
+ ~readModelName: string,
67
+ ~returnTypeName: string,
68
+ ) => {
69
+ let ps = getPubSub()
70
+ let yogaTopic = sourceBTopic(returnTypeName)
71
+ subscribeToStateChanges(readModelName, state => {
72
+ ps->YG.pubSubPublish(yogaTopic, state)
73
+ })
74
+ }
75
+
76
+ // ── Subscription resolver builder ─────────────────────────────────────────────
77
+ //
78
+ // Builds the yoga subscription resolver object for one field:
79
+ // { subscribe: (_root, _args) => pubSub.subscribe(topic), resolve: x => x }
80
+ //
81
+ // The resolver dict value is typed as JSON.t (the generic resolverFn) and cast via
82
+ // Obj.magic — yoga's createSchema accepts { subscribe, resolve } objects for the
83
+ // Subscription type, not plain functions. This is safe at runtime.
84
+
85
+ let makeFieldResolver = (topic: string): YG.resolverFn => {
86
+ let ps = getPubSub()
87
+ Obj.magic({
88
+ "subscribe": (_root: JSON.t, _args: JSON.t, _ctx: JSON.t) => ps->YG.pubSubSubscribe(topic),
89
+ "resolve": (payload: JSON.t) => payload,
90
+ })
91
+ }
92
+
93
+ // ── Public API ────────────────────────────────────────────────────────────────
94
+
95
+ type subscriptionEntry = {
96
+ fieldName: string,
97
+ topic: string,
98
+ }
99
+
100
+ /**
101
+ * Register subscriptions with the GraphQL server instance.
102
+ *
103
+ * `sourceAEntries`: list of {fieldName, topic} for Source A (event stream).
104
+ * Bridge must already be wired via bridgeSourceA before calling this.
105
+ *
106
+ * `sourceBEntries`: list of {fieldName, topic} for Source B (state changes).
107
+ * Bridge must already be wired via bridgeSourceB before calling this.
108
+ *
109
+ * `sdlFields`: the `subscriptions` SDL field strings from the plugin fragment
110
+ * (after stripping `@aws_subscribe` directives — not valid in yoga).
111
+ */
112
+ let registerAll = (
113
+ ~server: GraphQL_ServerInstance.t,
114
+ ~sdlFields: array<string>,
115
+ ~sourceAEntries: array<subscriptionEntry>,
116
+ ~sourceBEntries: array<subscriptionEntry>,
117
+ ) => {
118
+ // Strip @aws_subscribe directives — only valid in AppSync, not yoga
119
+ let cleanedFields =
120
+ sdlFields->Array.map(field =>
121
+ field->String.split("\n")->Array.filter(line => !(line->String.includes("@aws_subscribe")))->Array.join("\n")
122
+ )
123
+
124
+ let resolvers: dict<YG.resolverFn> = Dict.make()
125
+
126
+ sourceAEntries->Array.forEach(({fieldName, topic}) => {
127
+ resolvers->Dict.set(fieldName, makeFieldResolver(topic))
128
+ })
129
+ sourceBEntries->Array.forEach(({fieldName, topic}) => {
130
+ resolvers->Dict.set(fieldName, makeFieldResolver(topic))
131
+ })
132
+
133
+ server.registerSubscriptions(~sdlFields=cleanedFields, ~resolvers)
134
+
135
+ // Inject scalar AWSJSON so yoga accepts it in Source A event log types
136
+ server.registerTypes(~sdlTypes=["scalar AWSJSON"])
137
+ }
138
+
139
+ /** Publish a payload to a named PubSub topic (for Source C mutation resolvers). */
140
+ let publish = (topic: string, payload: JSON.t) => {
141
+ getPubSub()->YG.pubSubPublish(topic, payload)
142
+ }
143
+
144
+ let reset = () => {
145
+ pubSub.contents = None
146
+ }
@@ -0,0 +1,88 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as GraphqlYoga from "graphql-yoga";
4
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
+
6
+ let pubSub = {
7
+ contents: undefined
8
+ };
9
+
10
+ function getPubSub() {
11
+ let ps = pubSub.contents;
12
+ if (ps !== undefined) {
13
+ return Primitive_option.valFromOption(ps);
14
+ }
15
+ let ps$1 = GraphqlYoga.createPubSub();
16
+ pubSub.contents = Primitive_option.some(ps$1);
17
+ return ps$1;
18
+ }
19
+
20
+ function sourceATopic(displayName) {
21
+ return `on` + displayName + `EventLog_eventAppended`;
22
+ }
23
+
24
+ function sourceBTopic(returnTypeName) {
25
+ return `on` + returnTypeName + `_stateChanged`;
26
+ }
27
+
28
+ function bridgeSourceA(subscribeToEvents, displayName, busTopicName) {
29
+ let ps = getPubSub();
30
+ let yogaTopic = sourceATopic(displayName);
31
+ subscribeToEvents(busTopicName, async (_service, _meta, json) => {
32
+ ps.publish(yogaTopic, json);
33
+ });
34
+ }
35
+
36
+ function bridgeSourceB(subscribeToStateChanges, readModelName, returnTypeName) {
37
+ let ps = getPubSub();
38
+ let yogaTopic = sourceBTopic(returnTypeName);
39
+ subscribeToStateChanges(readModelName, state => {
40
+ ps.publish(yogaTopic, state);
41
+ });
42
+ }
43
+
44
+ function makeFieldResolver(topic) {
45
+ let ps = getPubSub();
46
+ return {
47
+ subscribe: (_root, _args, _ctx) => ps.subscribe(topic),
48
+ resolve: payload => payload
49
+ };
50
+ }
51
+
52
+ function registerAll(server, sdlFields, sourceAEntries, sourceBEntries) {
53
+ let cleanedFields = sdlFields.map(field => field.split("\n").filter(line => !line.includes("@aws_subscribe")).join("\n"));
54
+ let resolvers = {};
55
+ sourceAEntries.forEach(param => {
56
+ resolvers[param.fieldName] = makeFieldResolver(param.topic);
57
+ });
58
+ sourceBEntries.forEach(param => {
59
+ resolvers[param.fieldName] = makeFieldResolver(param.topic);
60
+ });
61
+ server.registerSubscriptions(cleanedFields, resolvers);
62
+ server.registerTypes(["scalar AWSJSON"]);
63
+ }
64
+
65
+ function publish(topic, payload) {
66
+ getPubSub().publish(topic, payload);
67
+ }
68
+
69
+ function reset() {
70
+ pubSub.contents = undefined;
71
+ }
72
+
73
+ let YG;
74
+
75
+ export {
76
+ YG,
77
+ pubSub,
78
+ getPubSub,
79
+ sourceATopic,
80
+ sourceBTopic,
81
+ bridgeSourceA,
82
+ bridgeSourceB,
83
+ makeFieldResolver,
84
+ registerAll,
85
+ publish,
86
+ reset,
87
+ }
88
+ /* graphql-yoga Not a pure module */
@@ -0,0 +1,83 @@
1
+ // Yoga context factory that translates the incoming Fetch Request into an
2
+ // `identity` attached to resolver context. Shared between
3
+ // `DomainGraphQL_Server` (data API) and `GraphQL_ServerInstance` (split-mode
4
+ // admin API) so both endpoints enforce identical bearer-token rules:
5
+ // - Valid `Authorization: Bearer <token>` → signed-in identity
6
+ // - Missing header → `defaultUser`
7
+ // - Invalid bearer is rejected at HTTP level before this runs (see
8
+ // `DomainGraphQL_Server._dispatch`); on the admin server there is no
9
+ // dispatch layer, so an invalid bearer falls through to `defaultUser`
10
+ // too. AppSync enforces the same `@aws_auth(cognito_groups: ["Admin"])`
11
+ // directive at the schema layer in production.
12
+
13
+ module YG = GraphqlYoga
14
+
15
+ type fetchHeaders
16
+ type fetchRequest = {headers: fetchHeaders}
17
+ type yogaInitialCtx = {request: fetchRequest}
18
+
19
+ @send
20
+ external headersForEach: (fetchHeaders, (string, string) => unit) => unit = "forEach"
21
+
22
+ let extractHeaders = (headers: fetchHeaders): dict<string> => {
23
+ let acc = Dict.make()
24
+ headers->headersForEach((value, key) => acc->Dict.set(key->String.toLowerCase, value))
25
+ acc
26
+ }
27
+
28
+ let identityFromAuthResult = (result: Reventless.Identity.authResult): Reventless.Identity.t =>
29
+ switch result {
30
+ | Authenticated(id) => id
31
+ | Anonymous => Reventless.Identity.anonymous
32
+ | AuthError(_) => Reventless.Identity.anonymous
33
+ }
34
+
35
+ let buildAuthContext = async (initial: YG.initialContext): JSON.t => {
36
+ let ctx: yogaInitialCtx = Obj.magic(initial)
37
+ let headers = extractHeaders(ctx.request.headers)
38
+ let requestContext: ReventlessCore.Auth_Adapter.requestContext = {headers: headers}
39
+ let result = await LocalAuth.authenticate(requestContext)
40
+ let identity = identityFromAuthResult(result)
41
+ Obj.magic({"identity": identity})
42
+ }
43
+
44
+ // Read the identity attached by `buildAuthContext` from the resolver context.
45
+ // Falls back to `anonymous` if the context is malformed or the server wasn't
46
+ // started with `~contextFactory`.
47
+ let extractIdentity = (ctx: JSON.t): Reventless.Identity.t =>
48
+ try {
49
+ switch (ctx->Obj.magic)["identity"]->Nullable.toOption {
50
+ | Some(id) => (id: Reventless.Identity.t)
51
+ | None => Reventless.Identity.anonymous
52
+ }
53
+ } catch {
54
+ | _ => Reventless.Identity.anonymous
55
+ }
56
+
57
+ // Throw a `GraphQLError` from the `graphql` package — yoga's `maskedErrors`
58
+ // option preserves these (only opaque thrown values get masked as
59
+ // "Unexpected error / INTERNAL_SERVER_ERROR"). Mirrors the directive-level
60
+ // `@aws_auth` rejection that AppSync surfaces in production.
61
+ @new @module("graphql")
62
+ external makeGraphqlError: (string, {"extensions": {"code": string}}) => exn = "GraphQLError"
63
+
64
+ let unauthorizedError = (~group: string): exn =>
65
+ makeGraphqlError(
66
+ `Unauthorized: requires group "${group}"`,
67
+ {"extensions": {"code": "UNAUTHORIZED"}},
68
+ )
69
+
70
+ // Wrap a resolver so it raises a GraphQL "Unauthorized" error when the
71
+ // requesting identity lacks the required group. Mirrors AppSync's
72
+ // `@aws_auth(cognito_groups: [...])` semantics for admin fields on the
73
+ // in-memory adapter. Use for fields whose corresponding read-model entry
74
+ // carries `authorization: Some({group, ...})` — pass the group string here.
75
+ let requireGroup = (~group: string, resolver: YG.resolverFn): YG.resolverFn =>
76
+ async (root, args, ctx) => {
77
+ let identity = extractIdentity(ctx)
78
+ if identity.groups->Array.includes(group) {
79
+ await resolver(root, args, ctx)
80
+ } else {
81
+ throw(unauthorizedError(~group))
82
+ }
83
+ }
@@ -0,0 +1,76 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Graphql from "graphql";
4
+ import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
5
+ import * as LocalAuth$ReventlessLocal from "./LocalAuth.res.mjs";
6
+
7
+ function extractHeaders(headers) {
8
+ let acc = {};
9
+ headers.forEach((value, key) => {
10
+ acc[key.toLowerCase()] = value;
11
+ });
12
+ return acc;
13
+ }
14
+
15
+ function identityFromAuthResult(result) {
16
+ if (typeof result !== "object" || result.TAG !== "Authenticated") {
17
+ return Identity$Reventless.anonymous;
18
+ } else {
19
+ return result._0;
20
+ }
21
+ }
22
+
23
+ async function buildAuthContext(initial) {
24
+ let headers = extractHeaders(initial.request.headers);
25
+ let requestContext = {
26
+ headers: headers
27
+ };
28
+ let result = await LocalAuth$ReventlessLocal.authenticate(requestContext);
29
+ return {
30
+ identity: identityFromAuthResult(result)
31
+ };
32
+ }
33
+
34
+ function extractIdentity(ctx) {
35
+ try {
36
+ let id = ctx.identity;
37
+ if (id == null) {
38
+ return Identity$Reventless.anonymous;
39
+ } else {
40
+ return id;
41
+ }
42
+ } catch (exn) {
43
+ return Identity$Reventless.anonymous;
44
+ }
45
+ }
46
+
47
+ function unauthorizedError(group) {
48
+ return new Graphql.GraphQLError(`Unauthorized: requires group "` + group + `"`, {
49
+ extensions: {
50
+ code: "UNAUTHORIZED"
51
+ }
52
+ });
53
+ }
54
+
55
+ function requireGroup(group, resolver) {
56
+ return async (root, args, ctx) => {
57
+ let identity = extractIdentity(ctx);
58
+ if (identity.groups.includes(group)) {
59
+ return await resolver(root, args, ctx);
60
+ }
61
+ throw unauthorizedError(group);
62
+ };
63
+ }
64
+
65
+ let YG;
66
+
67
+ export {
68
+ YG,
69
+ extractHeaders,
70
+ identityFromAuthResult,
71
+ buildAuthContext,
72
+ extractIdentity,
73
+ unauthorizedError,
74
+ requireGroup,
75
+ }
76
+ /* graphql Not a pure module */
@@ -0,0 +1,239 @@
1
+ // In-memory `Auth_Adapter.Provider` implementation. Designed for local
2
+ // development and integration tests.
3
+ //
4
+ // Header semantics (host-ui-login-core stages A2 + A4):
5
+ // Authorization: Bearer <token> → decode + HMAC-verify a Login-issued token
6
+ // X-User: <username> → resolve identity from the user store
7
+ // X-Groups: A,B,C → override the resolved identity's groups
8
+ // none of the above → Authenticated(defaultUser) (dev default)
9
+ //
10
+ // Stage A4 adds the `Login` submodule (token issuance + verification) and
11
+ // the companion `UserStore` module (YAML hydration).
12
+
13
+ open Reventless
14
+
15
+ // ── Built-in identities ────────────────────────────────────────────────────
16
+
17
+ let defaultUser: Identity.t = {
18
+ userId: "local-user",
19
+ username: "user",
20
+ groups: ["User"],
21
+ provider: InMemory,
22
+ }
23
+
24
+ let adminUser: Identity.t = {
25
+ userId: "local-admin",
26
+ username: "admin",
27
+ groups: ["Admin", "User"],
28
+ provider: InMemory,
29
+ }
30
+
31
+ // ── User registry ─────────────────────────────────────────────────────────
32
+ //
33
+ // A `dict<Identity.t>` keyed by `username`. A4 hydrates this from
34
+ // `.reventless/users.yaml`; tests inject via `registerUser`.
35
+
36
+ let initialUsers = () =>
37
+ Dict.fromArray([("user", defaultUser), ("admin", adminUser)])
38
+
39
+ let users: ref<dict<Identity.t>> = ref(initialUsers())
40
+
41
+ let registerUser = (~username: string, ~identity: Identity.t): unit =>
42
+ users.contents->Dict.set(username, identity)
43
+
44
+ let resetUsers = (): unit => users := initialUsers()
45
+
46
+ let lookupUser = (username: string): option<Identity.t> =>
47
+ users.contents->Dict.get(username)
48
+
49
+ // ── Header parsing ────────────────────────────────────────────────────────
50
+
51
+ let parseGroups = (s: string): array<string> =>
52
+ s
53
+ ->String.split(",")
54
+ ->Array.map(g => g->String.trim)
55
+ ->Array.filter(g => g !== "")
56
+
57
+ // HTTP headers are case-insensitive; the context factory in
58
+ // DomainGraphQL_Server normalises keys to lower-case before calling
59
+ // authenticate.
60
+ let getHeader = (headers: dict<string>, name: string): option<string> =>
61
+ headers->Dict.get(name->String.toLowerCase)
62
+
63
+ // ── Login (Stage A4 — token issuance) ──────────────────────────────────────
64
+ //
65
+ // Issues opaque tokens of the form `<base64url-payload>.<base64url-sig>` where
66
+ // payload = JSON(Identity.t) and sig = HMAC-SHA256(payload, secret). The
67
+ // secret is process-local (random 32-byte hex at first use) and can be pinned
68
+ // for tests via `Login.setTokenSecret`.
69
+ //
70
+ // Not security-grade — local dev only. AWS deployments verify Cognito-issued
71
+ // JWTs at AppSync (Stage D) and never see these tokens.
72
+
73
+ module Login = {
74
+ type credentials = {password: string, identity: Identity.t}
75
+
76
+ // Hydrated by `UserStore` at platform start; tests can inject directly.
77
+ let store: ref<dict<credentials>> = ref(Dict.make())
78
+
79
+ let setCredentials = (~username: string, ~password: string, ~identity: Identity.t): unit => {
80
+ store.contents->Dict.set(username, {password, identity})
81
+ // Mirror into the authenticate-side user registry so X-User flows still
82
+ // resolve a Login-registered user.
83
+ users.contents->Dict.set(username, identity)
84
+ }
85
+
86
+ let resetStore = (): unit => {
87
+ store := Dict.make()
88
+ }
89
+
90
+ // ── HMAC secret (lazy) ──
91
+ //
92
+ // Reads `REVENTLESS_INMEMORY_TOKEN_SECRET` once on first use. When the env
93
+ // var is set (≥16 chars) the issued tokens survive process restarts, which
94
+ // matches the dev loop's expectation that a logged-in tab keeps working
95
+ // after a backend reload. When unset, falls back to a random per-process
96
+ // secret — secure for ephemeral test runs but invalidates every previously
97
+ // issued token on every restart.
98
+ type buf
99
+ @module("node:crypto") external _randomBytes: int => buf = "randomBytes"
100
+ @send external _bufToString: (buf, string) => string = "toString"
101
+ type hmac
102
+ @module("node:crypto") external _createHmac: (string, string) => hmac = "createHmac"
103
+ @send external _update: (hmac, string) => hmac = "update"
104
+ @send external _digest: (hmac, string) => string = "digest"
105
+
106
+ @val external _processEnv: Dict.t<string> = "process.env"
107
+
108
+ let _secret: ref<option<string>> = ref(None)
109
+ let _getSecret = (): string =>
110
+ switch _secret.contents {
111
+ | Some(s) => s
112
+ | None =>
113
+ let s = switch _processEnv->Dict.get("REVENTLESS_INMEMORY_TOKEN_SECRET") {
114
+ | Some(envSecret) if String.length(envSecret) >= 16 => envSecret
115
+ | _ => _randomBytes(32)->_bufToString("hex")
116
+ }
117
+ _secret := Some(s)
118
+ s
119
+ }
120
+
121
+ let setTokenSecret = (s: string): unit => _secret := Some(s)
122
+
123
+ let _sign = (payload: string): string =>
124
+ _createHmac("sha256", _getSecret())->_update(payload)->_digest("base64url")
125
+
126
+ // base64url over arbitrary JSON strings: btoa-encode + URL-safe alphabet,
127
+ // strip `=` padding. Avoids pulling in Buffer for a single-line conversion.
128
+ @val external _btoa: string => string = "btoa"
129
+ @val external _atob: string => string = "atob"
130
+
131
+ let _b64urlEncode = (s: string): string =>
132
+ _btoa(s)
133
+ ->String.replaceAll("+", "-")
134
+ ->String.replaceAll("/", "_")
135
+ ->String.replaceAll("=", "")
136
+
137
+ let _b64urlDecode = (s: string): option<string> => {
138
+ let padLen = mod(4 - mod(s->String.length, 4), 4)
139
+ let padded = s ++ String.repeat("=", padLen)
140
+ let std = padded->String.replaceAll("-", "+")->String.replaceAll("_", "/")
141
+ try {
142
+ Some(_atob(std))
143
+ } catch {
144
+ | _ => None
145
+ }
146
+ }
147
+
148
+ /** Verifies credentials and returns a signed token (or an error string). */
149
+ let issue = async (~username: string, ~password: string): result<string, string> =>
150
+ switch store.contents->Dict.get(username) {
151
+ | Some({password: stored, identity}) if stored === password =>
152
+ let json = identity->S.reverseConvertToJsonOrThrow(Identity.schema)->JSON.stringify
153
+ let payload = _b64urlEncode(json)
154
+ let sig = _sign(payload)
155
+ Ok(`${payload}.${sig}`)
156
+ | _ => Error("Invalid credentials")
157
+ }
158
+
159
+ /**
160
+ * Returns the embedded Identity if signature verifies and the payload
161
+ * decodes; `None` for any tampered, malformed, or unsigned token.
162
+ */
163
+ let verifyAndDecode = (token: string): option<Identity.t> => {
164
+ let parts = token->String.split(".")
165
+ switch (parts->Array.get(0), parts->Array.get(1)) {
166
+ | (Some(payload), Some(sig)) if _sign(payload) === sig =>
167
+ _b64urlDecode(payload)->Option.flatMap(json =>
168
+ try {
169
+ Some(json->JSON.parseOrThrow->S.parseOrThrow(Identity.schema))
170
+ } catch {
171
+ | _ => None
172
+ }
173
+ )
174
+ | _ => None
175
+ }
176
+ }
177
+ }
178
+
179
+ // ── Provider implementation ───────────────────────────────────────────────
180
+
181
+ type authConfig = unit
182
+
183
+ let _bearerToken = (header: string): option<string> =>
184
+ if header->String.startsWith("Bearer ") {
185
+ Some(header->String.slice(~start=7, ~end=header->String.length)->String.trim)
186
+ } else {
187
+ None
188
+ }
189
+
190
+ let authenticate = async (
191
+ ctx: ReventlessCore.Auth_Adapter.requestContext,
192
+ ): Identity.authResult => {
193
+ // Decoding order: Bearer (when signature valid) → X-User/X-Groups → default.
194
+ // A *present* Bearer that fails verification is rejected (AuthError) rather
195
+ // than falling through to defaultUser — otherwise a client whose token was
196
+ // invalidated (e.g. process-local HMAC secret rotated on restart) would
197
+ // silently keep working as defaultUser, and the host-shell `on401` logout
198
+ // path would never fire. No-Authorization stays permissive (defaultUser)
199
+ // so anonymous dev flows still work.
200
+ let bearerHeader = getHeader(ctx.headers, "Authorization")->Option.flatMap(_bearerToken)
201
+ switch bearerHeader {
202
+ | Some(token) =>
203
+ switch Login.verifyAndDecode(token) {
204
+ | Some(identity) => Authenticated(identity)
205
+ | None => AuthError("Invalid bearer token")
206
+ }
207
+ | None =>
208
+ let userHeader = getHeader(ctx.headers, "X-User")
209
+ let groupsHeader = getHeader(ctx.headers, "X-Groups")
210
+ switch userHeader {
211
+ | None =>
212
+ switch groupsHeader {
213
+ | None =>
214
+ // No headers: in-memory mode defaults to defaultUser so local dev
215
+ // against an AllowAuthenticated-defaulted backend "just works" without
216
+ // setting headers. Anonymous behaviour is reachable only by building
217
+ // an Identity.anonymous explicitly in code.
218
+ Authenticated(defaultUser)
219
+ | Some(g) =>
220
+ // X-Groups without X-User: defaultUser identity with the requested
221
+ // groups. Useful for tests that exercise group-only authorization paths.
222
+ Authenticated({...defaultUser, groups: parseGroups(g)})
223
+ }
224
+ | Some(username) =>
225
+ switch lookupUser(username) {
226
+ | None => Anonymous
227
+ | Some(identity) =>
228
+ let withGroups = switch groupsHeader {
229
+ | Some(g) => {...identity, groups: parseGroups(g)}
230
+ | None => identity
231
+ }
232
+ Authenticated(withGroups)
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ let make = (~name as _: string, ~opts as _: option<Pulumi.ComponentResource.options>=?): Pulumi.Output.t<authConfig> =>
239
+ Pulumi.Output.make()