@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,763 @@
1
+ // Tests for GraphQL_SchemaInspector — verifies schema introspection at granular,
2
+ // plugin-fragment, and platform levels.
3
+
4
+ open JestGlobals
5
+
6
+ let _ = TestRunner.setup()
7
+
8
+ // ── Test schemas ────────────────────────────────────────────────────────────
9
+
10
+ let stringSchema = S.string
11
+ let taggedSchema = Reventless.DcbTag.string
12
+ let numberSchema = S.float
13
+ let boolSchema = S.bool
14
+
15
+ @schema
16
+ type testState = {
17
+ id: @s.matches(Reventless.DcbTag.string) string,
18
+ name: string,
19
+ price: float,
20
+ active: bool,
21
+ }
22
+
23
+ @schema
24
+ type svState = {
25
+ productId: @s.matches(Reventless.DcbTag.string) string,
26
+ name: string,
27
+ price: float,
28
+ }
29
+
30
+ @schema
31
+ type addCommand = {
32
+ productId: @s.matches(Reventless.DcbTag.string) string,
33
+ name: string,
34
+ }
35
+
36
+ @schema
37
+ type unionCommand =
38
+ | Create({itemId: @s.matches(Reventless.DcbTag.string) string, name: string})
39
+ | Rename({itemId: @s.matches(Reventless.DcbTag.string) string, newName: string})
40
+
41
+ // State schemas exercising structural annotations (Phase 1 server capability)
42
+ @schema
43
+ type plainState = {
44
+ productId: @s.matches(Reventless.DcbTag.string) string,
45
+ name: string,
46
+ }
47
+
48
+ @schema
49
+ type indexedState = {
50
+ productId: @s.matches(Reventless.DcbTag.string) string,
51
+ ownerId: string,
52
+ status: string,
53
+ name: string,
54
+ }
55
+ let indexedStateSchemaWithAnnotations = indexedStateSchema->S.Metadata.set(
56
+ ~id=Reventless.StateAnnotations.stateAnnotationsId,
57
+ {
58
+ ids: ["productId"],
59
+ compositeIds: [],
60
+ subIds: [],
61
+ compositeSubIds: [],
62
+ indexes: [("ownerId", "byOwner")],
63
+ hidden: [],
64
+ summary: [],
65
+ drillTargets: [],
66
+ drillTargetKeys: [],
67
+ collapsed: [],
68
+ scan: [],
69
+ scanSort: [],
70
+ status: None,
71
+ visibility: None,
72
+ },
73
+ )
74
+
75
+ @schema
76
+ type orderedState = {
77
+ productId: @s.matches(Reventless.DcbTag.string) string,
78
+ createdAt: string,
79
+ name: string,
80
+ }
81
+ let orderedStateSchemaWithAnnotations = orderedStateSchema->S.Metadata.set(
82
+ ~id=Reventless.StateAnnotations.stateAnnotationsId,
83
+ {
84
+ ids: ["productId"],
85
+ compositeIds: [],
86
+ subIds: ["createdAt"],
87
+ compositeSubIds: [],
88
+ indexes: [],
89
+ hidden: [],
90
+ summary: [],
91
+ drillTargets: [],
92
+ drillTargetKeys: [],
93
+ collapsed: [],
94
+ scan: [],
95
+ scanSort: [],
96
+ status: None,
97
+ visibility: None,
98
+ },
99
+ )
100
+
101
+ @schema
102
+ type scanState = {
103
+ productId: @s.matches(Reventless.DcbTag.string) string,
104
+ status: string,
105
+ name: string,
106
+ description: string,
107
+ }
108
+ let scanStateSchemaWithAnnotations = scanStateSchema->S.Metadata.set(
109
+ ~id=Reventless.StateAnnotations.stateAnnotationsId,
110
+ {
111
+ ids: ["productId"],
112
+ compositeIds: [],
113
+ subIds: [],
114
+ compositeSubIds: [],
115
+ indexes: [],
116
+ hidden: [],
117
+ summary: [],
118
+ drillTargets: [],
119
+ drillTargetKeys: [],
120
+ collapsed: [],
121
+ scan: ["status"],
122
+ scanSort: ["name"],
123
+ status: None,
124
+ visibility: None,
125
+ },
126
+ )
127
+
128
+ // ── Granular Level Tests ────────────────────────────────────────────────────
129
+
130
+ describe("GraphQL_SchemaInspector", () => {
131
+ describe("inspectScalar", () => {
132
+ testPromise("plain string derives to String", async () => {
133
+ expect(ReventlessCore.GraphQL_SchemaInspector.inspectScalar(stringSchema))->toBe("String")
134
+ })
135
+
136
+ testPromise("tagged string derives to ID", async () => {
137
+ expect(ReventlessCore.GraphQL_SchemaInspector.inspectScalar(taggedSchema))->toBe("ID")
138
+ })
139
+
140
+ testPromise("float derives to Float", async () => {
141
+ expect(ReventlessCore.GraphQL_SchemaInspector.inspectScalar(numberSchema))->toBe("Float")
142
+ })
143
+
144
+ testPromise("bool derives to Boolean", async () => {
145
+ expect(ReventlessCore.GraphQL_SchemaInspector.inspectScalar(boolSchema))->toBe("Boolean")
146
+ })
147
+ })
148
+
149
+ describe("inspectObjectType", () => {
150
+ testPromise("derives type definition from object schema", async () => {
151
+ let result = ReventlessCore.GraphQL_SchemaInspector.inspectObjectType(
152
+ ~typeName="TestState",
153
+ testStateSchema,
154
+ )
155
+ expect(result->Option.isSome)->toBe(true)
156
+ let sdl = result->Option.getOrThrow
157
+ expect(sdl->String.includes("type TestState"))->toBe(true)
158
+ expect(sdl->String.includes("id: ID!"))->toBe(true)
159
+ expect(sdl->String.includes("name: String"))->toBe(true)
160
+ expect(sdl->String.includes("price: Float"))->toBe(true)
161
+ expect(sdl->String.includes("active: Boolean"))->toBe(true)
162
+ })
163
+
164
+ testPromise("returns None for non-object schema", async () => {
165
+ let result = ReventlessCore.GraphQL_SchemaInspector.inspectObjectType(
166
+ ~typeName="Foo",
167
+ stringSchema,
168
+ )
169
+ expect(result->Option.isNone)->toBe(true)
170
+ })
171
+ })
172
+
173
+ describe("inspectMutationFields", () => {
174
+ testPromise("derives fields from single-variant (DCB slice) command", async () => {
175
+ let fields = ReventlessCore.GraphQL_SchemaInspector.inspectMutationFields(
176
+ ~fieldPrefix="Catalog_AddProduct",
177
+ addCommandSchema,
178
+ )
179
+ expect(fields->Array.length)->toBe(1)
180
+ let field = fields->Array.getUnsafe(0)
181
+ expect(field->String.includes("Catalog_AddProduct"))->toBe(true)
182
+ expect(field->String.includes("productId: ID!"))->toBe(true)
183
+ expect(field->String.includes("name: String"))->toBe(true)
184
+ })
185
+
186
+ testPromise("derives fields from union (aggregate) command", async () => {
187
+ let fields = ReventlessCore.GraphQL_SchemaInspector.inspectMutationFields(
188
+ ~fieldPrefix="App_Item",
189
+ unionCommandSchema,
190
+ )
191
+ expect(fields->Array.length)->toBe(2)
192
+ let createField = fields->Array.getUnsafe(0)
193
+ expect(createField->String.includes("App_Item_Create"))->toBe(true)
194
+ expect(createField->String.includes("itemId: ID!"))->toBe(true)
195
+ let renameField = fields->Array.getUnsafe(1)
196
+ expect(renameField->String.includes("App_Item_Rename"))->toBe(true)
197
+ expect(renameField->String.includes("newName: String"))->toBe(true)
198
+ })
199
+
200
+ testPromise("returns empty for non-object/non-union schema", async () => {
201
+ let fields = ReventlessCore.GraphQL_SchemaInspector.inspectMutationFields(
202
+ ~fieldPrefix="Foo",
203
+ stringSchema,
204
+ )
205
+ expect(fields->Array.length)->toBe(0)
206
+ })
207
+ })
208
+
209
+ describe("inspectQueryFields", () => {
210
+ testPromise("derives type def and query fields from state schema", async () => {
211
+ let result = ReventlessCore.GraphQL_SchemaInspector.inspectQueryFields(
212
+ ~name="Catalog_Product",
213
+ ~typeName="CatalogProduct",
214
+ testStateSchema,
215
+ )
216
+ expect(result.typeDef->Option.isSome)->toBe(true)
217
+ let typeDef = result.typeDef->Option.getOrThrow
218
+ expect(typeDef->String.includes("type CatalogProduct"))->toBe(true)
219
+ expect(result.singleQuery->String.includes("Catalog_Product(id: ID!)"))->toBe(true)
220
+ expect(result.singleQuery->String.includes("CatalogProduct"))->toBe(true)
221
+ expect(result.listQuery->Option.isSome)->toBe(true)
222
+ let listQ = result.listQuery->Option.getOrThrow
223
+ expect(listQ->String.includes("Catalog_Products"))->toBe(true)
224
+ })
225
+ })
226
+
227
+ // ── includeIdParam Tests ──────────────────────────────────────────────────
228
+
229
+ describe("includeIdParam — ReadModel vs StateViewSlice", () => {
230
+ testPromise("ReadModel fragment: query has (id: ID!) and type has injected id: ID!", async () => {
231
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
232
+ ~mutationEntries=[],
233
+ ~queryEntries=[
234
+ {
235
+ singleFieldName: "RM_Product",
236
+ listFieldName: "RM_Products",
237
+ returnTypeName: "RMProduct",
238
+ stateSchema: testStateSchema->S.castToUnknown,
239
+ authorization: None,
240
+ includeIdParam: true,
241
+ },
242
+ ],
243
+ )
244
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
245
+ let sdl = inspection.sdlPreview
246
+ expect(sdl->String.includes("RM_Product(id: ID!): RMProduct"))->toBe(true)
247
+ expect(sdl->String.includes("type RMProduct"))->toBe(true)
248
+ // The type should have an injected id: ID! field (first field in the type)
249
+ let typeLines = sdl->String.split("\n")
250
+ let idFieldInType = typeLines->Array.some(line =>
251
+ line->String.trim == "id: ID!" &&
252
+ !(line->String.includes("("))
253
+ )
254
+ expect(idFieldInType)->toBe(true)
255
+ })
256
+
257
+ testPromise("StateViewSlice fragment: query has no (id: ID!) and type has no injected id", async () => {
258
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
259
+ ~mutationEntries=[],
260
+ ~queryEntries=[
261
+ {
262
+ singleFieldName: "SV_Item",
263
+ listFieldName: "SV_Items",
264
+ returnTypeName: "SVItem",
265
+ stateSchema: svStateSchema->S.castToUnknown,
266
+ authorization: None,
267
+ includeIdParam: false,
268
+ },
269
+ ],
270
+ )
271
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
272
+ let sdl = inspection.sdlPreview
273
+ // Single query should NOT have (id: ID!) parameter
274
+ expect(sdl->String.includes("SV_Item: SVItem"))->toBe(true)
275
+ expect(sdl->String.includes("SV_Item(id: ID!)"))->toBe(false)
276
+ // The type should NOT have an injected id: ID! field
277
+ expect(sdl->String.includes("type SVItem"))->toBe(true)
278
+ expect(sdl->String.includes("productId: ID!"))->toBe(true)
279
+ // Count id: ID! occurrences — should be zero (productId uses ID! but "id: ID!" standalone should not appear)
280
+ let typeSection = sdl->String.split("type SVItem")->Array.get(1)->Option.getOr("")
281
+ let typeEnd = typeSection->String.indexOf("}")
282
+ let typeEnd = typeEnd >= 0 ? typeEnd : typeSection->String.length
283
+ let typeBody = typeSection->String.slice(~start=0, ~end=typeEnd)
284
+ let hasInjectedId = typeBody->String.split("\n")->Array.some(line =>
285
+ line->String.trim == "id: ID!"
286
+ )
287
+ expect(hasInjectedId)->toBe(false)
288
+ })
289
+
290
+ testPromise("default includeIdParam (omitted) behaves like ReadModel", async () => {
291
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
292
+ ~mutationEntries=[],
293
+ ~queryEntries=[
294
+ {
295
+ singleFieldName: "Default_Thing",
296
+ listFieldName: "Default_Things",
297
+ returnTypeName: "DefaultThing",
298
+ stateSchema: testStateSchema->S.castToUnknown,
299
+ authorization: None,
300
+ },
301
+ ],
302
+ )
303
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
304
+ let sdl = inspection.sdlPreview
305
+ expect(sdl->String.includes("Default_Thing(id: ID!): DefaultThing"))->toBe(true)
306
+ })
307
+ })
308
+
309
+ // ── Relay compliance — implements Node + Connection types ──────────────
310
+
311
+ describe("Relay compliance", () => {
312
+ testPromise("entity type includes 'implements Node' when includeIdParam is true", async () => {
313
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
314
+ ~mutationEntries=[],
315
+ ~queryEntries=[
316
+ {
317
+ singleFieldName: "Relay_Product",
318
+ listFieldName: "Relay_Products",
319
+ returnTypeName: "RelayProduct",
320
+ stateSchema: testStateSchema->S.castToUnknown,
321
+ authorization: None,
322
+ includeIdParam: true,
323
+ },
324
+ ],
325
+ )
326
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
327
+ let sdl = inspection.sdlPreview
328
+ expect(sdl->String.includes("type RelayProduct implements Node"))->toBe(true)
329
+ expect(sdl->String.includes("id: ID!"))->toBe(true)
330
+ })
331
+
332
+ testPromise("non-entity type does not include 'implements Node'", async () => {
333
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
334
+ ~mutationEntries=[],
335
+ ~queryEntries=[
336
+ {
337
+ singleFieldName: "Relay_View",
338
+ listFieldName: "Relay_Views",
339
+ returnTypeName: "RelayView",
340
+ stateSchema: svStateSchema->S.castToUnknown,
341
+ authorization: None,
342
+ includeIdParam: false,
343
+ },
344
+ ],
345
+ )
346
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
347
+ let sdl = inspection.sdlPreview
348
+ expect(sdl->String.includes("type RelayView implements Node"))->toBe(false)
349
+ expect(sdl->String.includes("type RelayView {"))->toBe(true)
350
+ })
351
+
352
+ testPromise("connectionSpec generates Edge and Connection types instead of plural wrapper", async () => {
353
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
354
+ ~mutationEntries=[],
355
+ ~queryEntries=[
356
+ {
357
+ singleFieldName: "Relay_Product",
358
+ listFieldName: "Relay_Products",
359
+ returnTypeName: "RelayProduct",
360
+ stateSchema: testStateSchema->S.castToUnknown,
361
+ authorization: None,
362
+ includeIdParam: true,
363
+ connectionSpec: true,
364
+ },
365
+ ],
366
+ )
367
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
368
+ let sdl = inspection.sdlPreview
369
+ // Should have Edge and Connection types
370
+ expect(sdl->String.includes("type RelayProductEdge"))->toBe(true)
371
+ expect(sdl->String.includes("node: RelayProduct!"))->toBe(true)
372
+ expect(sdl->String.includes("cursor: String!"))->toBe(true)
373
+ expect(sdl->String.includes("type RelayProductConnection"))->toBe(true)
374
+ expect(sdl->String.includes("edges: [RelayProductEdge!]!"))->toBe(true)
375
+ expect(sdl->String.includes("pageInfo: PageInfo!"))->toBe(true)
376
+ expect(sdl->String.includes("totalCount"))->toBe(false)
377
+ // Query field should use filter + first/after/last/before args (Phase 4)
378
+ expect(
379
+ sdl->String.includes(
380
+ "Relay_Products(filter: RelayProductFilter, first: Int, after: String, last: Int, before: String): RelayProductConnection!",
381
+ ),
382
+ )->toBe(true)
383
+ // Connection filter input with search/searchPrefix/ids
384
+ expect(sdl->String.includes("input RelayProductFilter"))->toBe(true)
385
+ expect(sdl->String.includes("search: String"))->toBe(true)
386
+ expect(sdl->String.includes("searchPrefix: String"))->toBe(true)
387
+ expect(sdl->String.includes("ids: [ID!]"))->toBe(true)
388
+ // Should NOT have legacy plural wrapper type
389
+ expect(sdl->String.includes("items: [RelayProduct!]!"))->toBe(false)
390
+ expect(sdl->String.includes("nextToken:"))->toBe(false)
391
+ })
392
+
393
+ testPromise("subIdField generates ItemsFilter (distinct from connection Filter)", async () => {
394
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
395
+ ~mutationEntries=[],
396
+ ~queryEntries=[
397
+ {
398
+ singleFieldName: "Items_Product",
399
+ listFieldName: "Items_Products",
400
+ returnTypeName: "ItemsProduct",
401
+ stateSchema: testStateSchema->S.castToUnknown,
402
+ authorization: None,
403
+ includeIdParam: true,
404
+ connectionSpec: true,
405
+ subIdField: "sku",
406
+ },
407
+ ],
408
+ )
409
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
410
+ let sdl = inspection.sdlPreview
411
+ // Connection-level filter (search/searchPrefix/ids)
412
+ expect(sdl->String.includes("input ItemsProductFilter"))->toBe(true)
413
+ // Items query gets its own filter type (prefix/from/to/eq/order) — renamed
414
+ // to ItemsFilter to disambiguate from the connection-level Filter.
415
+ expect(sdl->String.includes("input ItemsProductItemsFilter"))->toBe(true)
416
+ expect(
417
+ sdl->String.includes(
418
+ "Items_ProductItems(id: ID!, filter: ItemsProductItemsFilter,",
419
+ ),
420
+ )->toBe(true)
421
+ })
422
+
423
+ testPromise("explicit connectionSpec=false generates legacy plural wrapper (opt-out)", async () => {
424
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
425
+ ~mutationEntries=[],
426
+ ~queryEntries=[
427
+ {
428
+ singleFieldName: "Legacy_Product",
429
+ listFieldName: "Legacy_Products",
430
+ returnTypeName: "LegacyProduct",
431
+ stateSchema: testStateSchema->S.castToUnknown,
432
+ authorization: None,
433
+ includeIdParam: true,
434
+ connectionSpec: false,
435
+ },
436
+ ],
437
+ )
438
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
439
+ let sdl = inspection.sdlPreview
440
+ expect(sdl->String.includes("items: [LegacyProduct!]!"))->toBe(true)
441
+ expect(sdl->String.includes("nextToken: String"))->toBe(true)
442
+ expect(sdl->String.includes("LegacyProductEdge"))->toBe(false)
443
+ expect(sdl->String.includes("LegacyProductConnection"))->toBe(false)
444
+ })
445
+
446
+ testPromise("stitcher injects Node interface and node query", async () => {
447
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
448
+ ~mutationEntries=[],
449
+ ~queryEntries=[
450
+ {
451
+ singleFieldName: "Stitch_Product",
452
+ listFieldName: "Stitch_Products",
453
+ returnTypeName: "StitchProduct",
454
+ stateSchema: testStateSchema->S.castToUnknown,
455
+ authorization: None,
456
+ includeIdParam: true,
457
+ },
458
+ ],
459
+ )
460
+ let sdl = ReventlessCore.GraphQL_Stitcher.stitch(~baseFragment=fragment, ~pluginFragments=[])
461
+ expect(sdl->String.includes("interface Node"))->toBe(true)
462
+ expect(sdl->String.includes("node(id: ID!): Node"))->toBe(true)
463
+ expect(sdl->String.includes("type PageInfo"))->toBe(true)
464
+ expect(sdl->String.includes("hasNextPage: Boolean!"))->toBe(true)
465
+ })
466
+ })
467
+
468
+ // ── Plugin Level — Fragment Inspector ───────────────────────────────────
469
+
470
+ describe("inspectFragment", () => {
471
+ testPromise("decodes and previews a fragment", async () => {
472
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
473
+ ~mutationEntries=[
474
+ {
475
+ fieldNames: ["Test_Add"],
476
+ commandSchema: addCommandSchema->S.castToUnknown,
477
+ },
478
+ ],
479
+ ~queryEntries=[
480
+ {
481
+ singleFieldName: "Test_State",
482
+ listFieldName: "Test_States",
483
+ returnTypeName: "TestState",
484
+ stateSchema: testStateSchema->S.castToUnknown,
485
+ authorization: None,
486
+ },
487
+ ],
488
+ )
489
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
490
+ // TestState, TestStateEdge, TestStateConnection, TestStateFilter (Phase 4 connection filter)
491
+ // + CommandResult union + CommandAccepted + CommandRejected + CommandPending
492
+ // (auto-injected whenever the fragment emits any mutation field).
493
+ expect(inspection.types->Array.length)->toBe(8)
494
+ expect(inspection.mutations->Array.length)->toBe(1)
495
+ // single Test_State(id), list Test_States(...), and Test_StatesByIds(ids: [String!]!)
496
+ expect(inspection.queries->Array.length)->toBe(3)
497
+ expect(inspection.sdlPreview->String.includes("type TestState"))->toBe(true)
498
+ expect(inspection.sdlPreview->String.includes("type TestStateEdge"))->toBe(true)
499
+ expect(inspection.sdlPreview->String.includes("type TestStateConnection"))->toBe(true)
500
+ expect(inspection.sdlPreview->String.includes("input TestStateFilter"))->toBe(true)
501
+ expect(inspection.sdlPreview->String.includes("edges: [TestStateEdge!]!"))->toBe(true)
502
+ expect(inspection.sdlPreview->String.includes("Test_Add"))->toBe(true)
503
+ expect(inspection.sdlPreview->String.includes("Test_State"))->toBe(true)
504
+ expect(inspection.sdlPreview->String.includes("TestStateConnection!"))->toBe(true)
505
+ expect(inspection.sdlPreview->String.includes("union CommandResult"))->toBe(true)
506
+ })
507
+ })
508
+
509
+ describe("inspectPluginEntries", () => {
510
+ testPromise("formats mutation and query entries", async () => {
511
+ let summary = ReventlessCore.GraphQL_SchemaInspector.inspectPluginEntries(
512
+ ~mutationEntries=[
513
+ {
514
+ fieldNames: ["Plugin_Add", "Plugin_Remove"],
515
+ commandSchema: unionCommandSchema->S.castToUnknown,
516
+ },
517
+ ],
518
+ ~queryEntries=[
519
+ {
520
+ singleFieldName: "Plugin_Item",
521
+ listFieldName: "Plugin_Items",
522
+ returnTypeName: "PluginItem",
523
+ stateSchema: testStateSchema->S.castToUnknown,
524
+ authorization: None,
525
+ },
526
+ ],
527
+ )
528
+ expect(summary->String.includes("Mutations (2)"))->toBe(true)
529
+ expect(summary->String.includes("Plugin_Add"))->toBe(true)
530
+ expect(summary->String.includes("Plugin_Remove"))->toBe(true)
531
+ expect(summary->String.includes("Queries (1)"))->toBe(true)
532
+ expect(summary->String.includes("Plugin_Item(id) -> PluginItem"))->toBe(true)
533
+ })
534
+ })
535
+
536
+ // ── Platform Level — GraphQL_Server diagnostics ─────────────────────────
537
+
538
+ // ── Phase 1: server capability — auto-derived Filter / OrderBy ─────────
539
+
540
+ describe("server capability — auto-derived Filter / OrderBy", () => {
541
+ testPromise("read model with no annotations emits the unchanged search-only Filter", async () => {
542
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
543
+ ~mutationEntries=[],
544
+ ~queryEntries=[
545
+ {
546
+ singleFieldName: "Plain_View",
547
+ listFieldName: "Plain_Views",
548
+ returnTypeName: "PlainView",
549
+ stateSchema: plainStateSchema->S.castToUnknown,
550
+ authorization: None,
551
+ includeIdParam: false,
552
+ connectionSpec: true,
553
+ },
554
+ ],
555
+ )
556
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
557
+ let sdl = inspection.sdlPreview
558
+ // Filter has only the legacy block
559
+ expect(sdl->String.includes("input PlainViewFilter"))->toBe(true)
560
+ expect(sdl->String.includes("search: String"))->toBe(true)
561
+ expect(sdl->String.includes("searchPrefix: String"))->toBe(true)
562
+ expect(sdl->String.includes("ids: [ID!]"))->toBe(true)
563
+ // No per-field eq inputs
564
+ expect(sdl->String.includes("Eq:"))->toBe(false)
565
+ // No OrderBy / OrderField types or arg
566
+ expect(sdl->String.includes("PlainViewOrderBy"))->toBe(false)
567
+ expect(sdl->String.includes("PlainViewOrderField"))->toBe(false)
568
+ expect(sdl->String.includes("orderBy:"))->toBe(false)
569
+ })
570
+
571
+ testPromise("read model with @id + @index emits Eq filters + OrderBy + orderBy arg", async () => {
572
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
573
+ ~mutationEntries=[],
574
+ ~queryEntries=[
575
+ {
576
+ singleFieldName: "Indexed_Product",
577
+ listFieldName: "Indexed_Products",
578
+ returnTypeName: "IndexedProduct",
579
+ stateSchema: indexedStateSchemaWithAnnotations->S.castToUnknown,
580
+ authorization: None,
581
+ includeIdParam: true,
582
+ connectionSpec: true,
583
+ },
584
+ ],
585
+ )
586
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
587
+ let sdl = inspection.sdlPreview
588
+ // Per-field eq inputs derived from @id and @index
589
+ expect(sdl->String.includes("input IndexedProductFilter"))->toBe(true)
590
+ expect(sdl->String.includes("productIdEq: ID"))->toBe(true)
591
+ // ownerId is detected as an entity reference → ID (matches SchemaType.fromSury behaviour)
592
+ expect(sdl->String.includes("ownerIdEq: ID"))->toBe(true)
593
+ // OrderField enum lists the same fields
594
+ expect(sdl->String.includes("enum IndexedProductOrderField"))->toBe(true)
595
+ expect(sdl->String.includes("input IndexedProductOrderBy"))->toBe(true)
596
+ expect(sdl->String.includes("direction: SortOrder!"))->toBe(true)
597
+ // Connection field arg list includes orderBy
598
+ expect(sdl->String.includes("orderBy: IndexedProductOrderBy"))->toBe(true)
599
+ })
600
+
601
+ testPromise(
602
+ "read model with @scan / @scanSort folds opt-in fields into Filter / OrderBy",
603
+ async () => {
604
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
605
+ ~mutationEntries=[],
606
+ ~queryEntries=[
607
+ {
608
+ singleFieldName: "Scan_Item",
609
+ listFieldName: "Scan_Items",
610
+ returnTypeName: "ScanItem",
611
+ stateSchema: scanStateSchemaWithAnnotations->S.castToUnknown,
612
+ authorization: None,
613
+ includeIdParam: true,
614
+ connectionSpec: true,
615
+ },
616
+ ],
617
+ )
618
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
619
+ let sdl = inspection.sdlPreview
620
+ // @scan field becomes filterable via <field>Eq
621
+ expect(sdl->String.includes("statusEq: String"))->toBe(true)
622
+ // @scan does not promote the field to sortable
623
+ expect(sdl->String.includes("enum ScanItemOrderField"))->toBe(true)
624
+ // @scanSort field appears in OrderField enum
625
+ expect(sdl->String.includes("name"))->toBe(true)
626
+ expect(sdl->String.includes("orderBy: ScanItemOrderBy"))->toBe(true)
627
+ // @scanSort alone does not auto-add an Eq filter for that field
628
+ expect(sdl->String.includes("nameEq:"))->toBe(false)
629
+ // @scan range form is not emitted (eq only)
630
+ expect(sdl->String.includes("statusFrom:"))->toBe(false)
631
+ expect(sdl->String.includes("statusTo:"))->toBe(false)
632
+ },
633
+ )
634
+
635
+ testPromise(
636
+ "validateScanSortAlignment warns when @scanSort field is not a sort key",
637
+ async () => {
638
+ // scanStateSchemaWithAnnotations has @scanSort on "name". With no table
639
+ // sort key and no GSIs, "name" is not aligned with any sort key.
640
+ let warnings = ReventlessCore.GraphQL_FragmentGenerator.validateScanSortAlignment(
641
+ ~schema=scanStateSchemaWithAnnotations->S.castToUnknown,
642
+ ~readModelName="ScanItem",
643
+ ~knownSortFields=[],
644
+ )
645
+ expect(warnings->Array.length)->toBe(1)
646
+ let msg = warnings->Array.getUnsafe(0)
647
+ expect(msg->String.includes("ScanItem"))->toBe(true)
648
+ expect(msg->String.includes("name"))->toBe(true)
649
+ expect(msg->String.includes("per-page"))->toBe(true)
650
+ },
651
+ )
652
+ testPromise(
653
+ "validateScanSortAlignment is silent when @scanSort field aligns with a known sort key",
654
+ async () => {
655
+ let warnings = ReventlessCore.GraphQL_FragmentGenerator.validateScanSortAlignment(
656
+ ~schema=scanStateSchemaWithAnnotations->S.castToUnknown,
657
+ ~readModelName="ScanItem",
658
+ ~knownSortFields=["name"],
659
+ )
660
+ expect(warnings)->toEqual([])
661
+ },
662
+ )
663
+ testPromise(
664
+ "validateScanSortAlignment is a no-op when there are no @scanSort fields",
665
+ async () => {
666
+ let warnings = ReventlessCore.GraphQL_FragmentGenerator.validateScanSortAlignment(
667
+ ~schema=indexedStateSchemaWithAnnotations->S.castToUnknown,
668
+ ~readModelName="IndexedProduct",
669
+ ~knownSortFields=[],
670
+ )
671
+ expect(warnings)->toEqual([])
672
+ },
673
+ )
674
+ testPromise("read model with @subId emits range filters + OrderBy on the sort key", async () => {
675
+ let fragment = ReventlessCore.GraphQL_FragmentGenerator.generate(
676
+ ~mutationEntries=[],
677
+ ~queryEntries=[
678
+ {
679
+ singleFieldName: "Ordered_Item",
680
+ listFieldName: "Ordered_Items",
681
+ returnTypeName: "OrderedItem",
682
+ stateSchema: orderedStateSchemaWithAnnotations->S.castToUnknown,
683
+ authorization: None,
684
+ includeIdParam: true,
685
+ connectionSpec: true,
686
+ },
687
+ ],
688
+ )
689
+ let inspection = ReventlessCore.GraphQL_SchemaInspector.inspectFragment(fragment)
690
+ let sdl = inspection.sdlPreview
691
+ expect(sdl->String.includes("createdAtEq: String"))->toBe(true)
692
+ expect(sdl->String.includes("createdAtFrom: String"))->toBe(true)
693
+ expect(sdl->String.includes("createdAtTo: String"))->toBe(true)
694
+ expect(sdl->String.includes("enum OrderedItemOrderField"))->toBe(true)
695
+ expect(sdl->String.includes("orderBy: OrderedItemOrderBy"))->toBe(true)
696
+ })
697
+ })
698
+
699
+ describe("GraphQL_Server diagnostics", () => {
700
+ testPromise("diagnostics detects resolver without SDL field", async () => {
701
+ GraphQL_Server.reset()
702
+ let resolvers = Dict.make()
703
+ resolvers->Dict.set("orphanResolver", async (_root, _args, _ctx) => JSON.Encode.string("ok"))
704
+ GraphQL_Server.registerMutations(~sdlFields=[], ~resolvers)
705
+ let d = GraphQL_Server.diagnostics()
706
+ expect(d.resolverMutationCount)->toBe(1)
707
+ expect(d.sdlMutationCount)->toBe(0)
708
+ expect(d.mismatches->Array.length)->toBe(1)
709
+ expect(
710
+ (d.mismatches->Array.getUnsafe(0))->String.includes("resolver but no SDL field"),
711
+ )->toBe(true)
712
+ GraphQL_Server.reset()
713
+ })
714
+
715
+ testPromise("diagnostics detects SDL field without resolver", async () => {
716
+ GraphQL_Server.reset()
717
+ GraphQL_Server.registerQueries(
718
+ ~sdlFields=[" orphanField(id: ID!): String"],
719
+ ~resolvers=Dict.make(),
720
+ )
721
+ let d = GraphQL_Server.diagnostics()
722
+ expect(d.sdlQueryCount)->toBe(1)
723
+ expect(d.resolverQueryCount)->toBe(0)
724
+ expect(d.mismatches->Array.length)->toBe(1)
725
+ expect(
726
+ (d.mismatches->Array.getUnsafe(0))->String.includes("SDL field but no resolver"),
727
+ )->toBe(true)
728
+ GraphQL_Server.reset()
729
+ })
730
+
731
+ testPromise("diagnostics reports no mismatches when fields and resolvers match", async () => {
732
+ GraphQL_Server.reset()
733
+ let resolvers = Dict.make()
734
+ resolvers->Dict.set("myQuery", async (_root, _args, _ctx) => JSON.Encode.string("ok"))
735
+ GraphQL_Server.registerQueries(
736
+ ~sdlFields=[" myQuery(id: ID!): String"],
737
+ ~resolvers,
738
+ )
739
+ let d = GraphQL_Server.diagnostics()
740
+ expect(d.mismatches->Array.length)->toBe(0)
741
+ expect(d.sdlQueryCount)->toBe(1)
742
+ expect(d.resolverQueryCount)->toBe(1)
743
+ GraphQL_Server.reset()
744
+ })
745
+
746
+ testPromise("getFullSdl returns None before start", async () => {
747
+ GraphQL_Server.reset()
748
+ expect(GraphQL_Server.getFullSdl()->Option.isNone)->toBe(true)
749
+ })
750
+
751
+ testPromise("getRegisteredSdl returns Query/Mutation SDL", async () => {
752
+ GraphQL_Server.reset()
753
+ GraphQL_Server.registerQueries(
754
+ ~sdlFields=[" hello: String"],
755
+ ~resolvers=Dict.make(),
756
+ )
757
+ let sdl = GraphQL_Server.getRegisteredSdl()
758
+ expect(sdl->String.includes("hello: String"))->toBe(true)
759
+ expect(sdl->String.includes("type Query"))->toBe(true)
760
+ GraphQL_Server.reset()
761
+ })
762
+ })
763
+ })