@reventlessdev/reventless-local 3.0.0-alpha.199 → 3.0.0-alpha.201

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 (40) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +10 -9
  3. package/rescript.json +2 -1
  4. package/src/Platform.res +137 -86
  5. package/src/Platform.res.mjs +57 -44
  6. package/src/adapter/BackendState.res +13 -0
  7. package/src/adapter/BackendState.res.mjs +17 -1
  8. package/src/adapter/DcbEventLog/LocalDcbEventLogStorage.res.mjs +1 -1
  9. package/src/adapter/DomainGraphQL_Server.res +5 -5
  10. package/src/adapter/DomainGraphQL_Server.res.mjs +1 -1
  11. package/src/adapter/EventLog/LocalEventLogStorage.res.mjs +1 -1
  12. package/src/adapter/LocalBus.res +6 -46
  13. package/src/adapter/LocalBus.res.mjs +0 -29
  14. package/src/adapter/LocalStateChangeDescriptor.res +93 -0
  15. package/src/adapter/LocalStateChangeDescriptor.res.mjs +61 -0
  16. package/src/adapter/LocalUploadResolvers.res +16 -3
  17. package/src/adapter/LocalUploadResolvers.res.mjs +6 -4
  18. package/src/adapter/ObjectStore/LocalObjectStore.res +157 -0
  19. package/src/adapter/ObjectStore/LocalObjectStore.res.mjs +129 -0
  20. package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +215 -0
  21. package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res.mjs +286 -0
  22. package/src/adapter/ObjectStore/ObjectStoreStorage_InMemory.res +33 -0
  23. package/src/adapter/ObjectStore/ObjectStoreStorage_InMemory.res.mjs +47 -0
  24. package/src/adapter/QueryDb/LocalQueryDbStorage.res.mjs +1 -1
  25. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +4 -2
  26. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res.mjs +3 -3
  27. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +4 -2
  28. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs +3 -3
  29. package/src/reset/LocalSeedReset.res +552 -0
  30. package/src/reset/LocalSeedReset.res.mjs +533 -0
  31. package/tests/adapter/BackendParityTest.res +51 -0
  32. package/tests/adapter/BackendParityTest.res.mjs +44 -0
  33. package/tests/adapter/GraphQL_SubscriptionResolversTest.res +6 -4
  34. package/tests/adapter/GraphQL_SubscriptionResolversTest.res.mjs +4 -3
  35. package/tests/adapter/ObjectStorePersistenceTest.res +243 -0
  36. package/tests/adapter/ObjectStorePersistenceTest.res.mjs +159 -0
  37. package/tests/reset/LocalSeedResetTest.res +252 -0
  38. package/tests/reset/LocalSeedResetTest.res.mjs +280 -0
  39. package/src/adapter/LocalObjectStore.res +0 -70
  40. package/src/adapter/LocalObjectStore.res.mjs +0 -60
package/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.201 (2026-08-05)
7
+
8
+ ### Features
9
+
10
+ * **local,aws:** carry the changed row in the live-update descriptor ([9272e2e](https://github.com/ReventlessDev/reventless-core/commit/9272e2e18e9e1d740f9a2c5aa83eccbcb41feff7))
11
+ * **local:** offload large pluginDefinition fields to the object store ([84c5fab](https://github.com/ReventlessDev/reventless-core/commit/84c5fab339856b5d64f0c05110b1fa7f38646f5e))
12
+ * **local:** persist the object store beside the SQLite database ([f37e4a1](https://github.com/ReventlessDev/reventless-core/commit/f37e4a1ad3191a97f14f3db7e2ead0e2b27b46c2))
13
+ * **local:** scoped seed:reset for the local platform ([a4dd003](https://github.com/ReventlessDev/reventless-core/commit/a4dd0033b7d01be23be91bac3f01d38ebeab7d45))
14
+
15
+
16
+ # 3.0.0-alpha.200 (2026-08-04)
17
+
18
+ ### Features
19
+
20
+ * **admin:** serve the complete plugin structure to developer tooling ([e99cda0](https://github.com/ReventlessDev/reventless-core/commit/e99cda02a2f862b36bcfdb018a0fb34bb7426a2b))
21
+
22
+
6
23
  # 3.0.0-alpha.199 (2026-08-04)
7
24
 
8
25
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-local",
3
- "version": "3.0.0-alpha.199",
3
+ "version": "3.0.0-alpha.201",
4
4
  "description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -36,16 +36,17 @@
36
36
  "ws": "^8.18.0",
37
37
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
38
38
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
39
- "@reventlessdev/rescript-node": "2.0.0-alpha.1",
40
- "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.26",
41
39
  "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
40
+ "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.26",
41
+ "@reventlessdev/rescript-node": "2.0.0-alpha.2",
42
42
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
43
- "@reventlessdev/reventless-core": "3.0.0-alpha.211",
44
- "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.59",
45
- "@reventlessdev/reventless-infra": "3.0.0-alpha.126",
46
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.75",
47
- "@reventlessdev/reventless-spec": "3.0.0-alpha.100",
48
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.158"
43
+ "@reventlessdev/reventless-core": "3.0.0-alpha.213",
44
+ "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.61",
45
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.127",
46
+ "@reventlessdev/reventless-seed": "1.0.0-alpha.9",
47
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.160",
48
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.101",
49
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.77"
49
50
  },
50
51
  "devDependencies": {
51
52
  "rescript": "12.3.0",
package/rescript.json CHANGED
@@ -35,7 +35,8 @@
35
35
  "@reventlessdev/reventless-spec",
36
36
  "@reventlessdev/reventless-core",
37
37
  "@reventlessdev/reventless-gwt",
38
- "@reventlessdev/reventless-postgres"
38
+ "@reventlessdev/reventless-postgres",
39
+ "@reventlessdev/reventless-seed"
39
40
  ],
40
41
  "compiler-flags": [],
41
42
  "suffix": ".res.mjs"
package/src/Platform.res CHANGED
@@ -118,6 +118,14 @@ module MakeWithConfig = (
118
118
  }
119
119
  let db = SqliteDriver.openDb(~path)
120
120
  BackendState.setSqlite(~db, ~path)
121
+ // The object store persists beside the database under this backend (see
122
+ // LocalObjectStore), so a reset that wipes the events wipes the bytes they
123
+ // reference too — otherwise the session starts with orphaned uploads and
124
+ // offload payloads no event points at. Runs after setSqlite because the
125
+ // store reads the active backend to find its root.
126
+ if resetOnStart {
127
+ LocalObjectStore.reset()
128
+ }
121
129
  // Persistent stores keep their event numbering across restarts and app
122
130
  // switches: seed the in-memory event-tap counter from the rows already on
123
131
  // disk so the timeline's #N continues instead of restarting at 1. (After a
@@ -837,6 +845,20 @@ module MakeWithConfig = (
837
845
  type mcpSupported = | @as(true) McpSupported | @as(false) McpNotSupported
838
846
  let mcpSupported = McpSupported
839
847
 
848
+ // Content-address the two large pluginDefinition fields into the process-local
849
+ // object store, exactly as the AWS platform does into its offload bucket. Local
850
+ // has no bucket, but it does have an object store — and leaving the fields
851
+ // inline here meant every connect handshake carried a whole plugin structure
852
+ // through the command log, and that local never exercised the offload path the
853
+ // deployed platform takes. Registered once at platform setup, before any plugin
854
+ // is built, since the hook fires during graph construction.
855
+ ReventlessCore.Plugin_Helpers.registerOffload((~store, ~bytes) => {
856
+ let hash = NodeCrypto.sha256Hex(bytes)
857
+ let key = "sha256/" ++ hash
858
+ LocalObjectStore.putOffload(~key, ~bytes)
859
+ {Reventless.Offload.store, key, hash, bytes: bytes->String.length}
860
+ })
861
+
840
862
  // ---------------------------------------------------------------------------
841
863
  // Shared helpers — used by both makePlatform and deployPlugin.
842
864
  // ---------------------------------------------------------------------------
@@ -852,6 +874,10 @@ module MakeWithConfig = (
852
874
  // In-memory store for plugin structures, keyed by plugin ID.
853
875
  let pluginStructuresStore: ref<dict<Reventless.Plugin.pluginStructure>> = ref(Dict.make())
854
876
 
877
+ // The object stores a plugin's fields declared, as `{plugin}.{store}` keys.
878
+ let declaredStoresOf = (structure: Reventless.Plugin.pluginStructure) =>
879
+ structure.requiredStores->Option.getOr([])
880
+
855
881
  let seedPluginStructuresStore = (~pluginComponents: array<ReventlessCore.Plugin.component>) => {
856
882
  pluginComponents->Array.forEach(plugin => {
857
883
  let outputs: ReventlessInfra.Plugin.outputs = plugin->ReventlessCore.Component.outputs
@@ -860,45 +886,42 @@ module MakeWithConfig = (
860
886
  ->Pulumi.Output.all2
861
887
  ->Pulumi.Output.apply(((id, ps)) => {
862
888
  switch ps {
863
- | Some(def) => pluginStructuresStore.contents->Dict.set(id, def)
889
+ | Some(def) =>
890
+ pluginStructuresStore.contents->Dict.set(id, def)
891
+ // Inside the apply, not after the forEach: the structure only exists
892
+ // once the Output resolves, so anything reading
893
+ // `pluginStructuresStore` synchronously below reads an empty dict.
894
+ declaredStoresOf(def)->Array.forEach(qualified =>
895
+ // Registering the store is what lets an uploaded object be attributed
896
+ // to the plugin that declared it, the way an S3 key's prefix does —
897
+ // under the served `uploads/` prefix locally, for the reason
898
+ // LocalObjectStore.localPrefixFor gives.
899
+ LocalObjectStore.registerStore(
900
+ ~qualified,
901
+ ~prefix=LocalObjectStore.localPrefixFor(~qualified),
902
+ )
903
+ )
904
+ // Two plugins declaring one store name is refused by the deployed
905
+ // platform before it provisions anything. Local provisions no stores, so
906
+ // nothing here breaks — but this is the only place a developer meets the
907
+ // rule while the fix is still free: once a store has deployed, its minted
908
+ // refs are in an append-only event log and renaming it strands them.
909
+ // Hence a warning at composition, and the authoritative refusal at deploy.
910
+ //
911
+ // Checked against every store registered so far rather than this
912
+ // plugin's own, since a collision is by definition between two plugins.
913
+ ReventlessCore.StorePrefixCollision.collisionsFor(
914
+ ~stores=LocalObjectStore.declaredStoreList()->Array.map(((qualified, prefix)) => {
915
+ ReventlessCore.StorePrefixCollision.qualified,
916
+ prefix,
917
+ }),
918
+ )->Array.forEach(c =>
919
+ log.warn(~comp="Platform:plugins", ReventlessCore.StorePrefixCollision.collisionMessage(c))
920
+ )
864
921
  | None => ()
865
922
  }
866
923
  })
867
924
  })
868
-
869
- // Two plugins declaring one store name is refused by the deployed platform
870
- // before it provisions anything. Local provisions no stores, so nothing here
871
- // breaks — but this is the only place a developer meets the rule while the
872
- // fix is still free: once a store has deployed, its minted refs are in an
873
- // append-only event log and renaming it strands them. Hence a warning at
874
- // composition, and the authoritative refusal at deploy.
875
- let declared =
876
- pluginStructuresStore.contents
877
- ->Dict.valuesToArray
878
- ->Array.flatMap(structure =>
879
- structure.requiredStores
880
- ->Option.getOr([])
881
- ->Array.map(qualified => {
882
- ReventlessCore.StorePrefixCollision.qualified,
883
- // Local provisions no stores, so it has no prefix of its own — it
884
- // restates the deployed rule (`{plugin}/{store}`) so the warning it
885
- // gives matches the refusal a deploy would give. Reporting a collision
886
- // the deployed platform does not have would be worse than silence.
887
- prefix: switch qualified->String.indexOfOpt(".") {
888
- | Some(i) =>
889
- qualified->String.slice(~start=0, ~end=i) ++
890
- "/" ++
891
- qualified->String.slice(~start=i + 1, ~end=qualified->String.length)
892
- | None => qualified
893
- },
894
- })
895
- )
896
- ReventlessCore.StorePrefixCollision.collisionsFor(~stores=declared)->Array.forEach(c =>
897
- log.warn(
898
- ~comp="Platform:plugins",
899
- ReventlessCore.StorePrefixCollision.collisionMessage(c),
900
- )
901
- )
902
925
  }
903
926
 
904
927
  // Bus keys for the admin Plugin aggregate (name-keyed). ComponentType.toName maps
@@ -1113,9 +1136,19 @@ module MakeWithConfig = (
1113
1136
  },
1114
1137
  ),
1115
1138
  extensionProtocols: [],
1116
- apiSchemaFragment: apiSchemaFragment->Option.map(f => Reventless.Offload.Inline(f)),
1139
+ apiSchemaFragment: apiSchemaFragment->Option.map(f =>
1140
+ f->ReventlessCore.Plugin_Helpers.offloadPayload(
1141
+ ~schema=Reventless.Plugin.apiSchemaFragmentSchema,
1142
+ ~store="pluginApiFragments",
1143
+ )
1144
+ ),
1117
1145
  apiTarget: None,
1118
- structure: pluginStructure->Option.map(s => Reventless.Offload.Inline(s)),
1146
+ structure: pluginStructure->Option.map(s =>
1147
+ s->ReventlessCore.Plugin_Helpers.offloadPayload(
1148
+ ~schema=Reventless.Plugin.pluginStructureSchema,
1149
+ ~store="pluginStructures",
1150
+ )
1151
+ ),
1119
1152
  dcbEventLog: None,
1120
1153
  kind: Domain,
1121
1154
  }
@@ -1635,60 +1668,78 @@ module MakeWithConfig = (
1635
1668
  // whose Plugin RM row is not Connected are hidden until reactivated. The built-in
1636
1669
  // Platform_Admin entry has no Plugin RM row — include it unconditionally since
1637
1670
  // it can't be deactivated.
1638
- queryResolvers->Dict.set(
1639
- "Platform_ComponentDefinitions",
1640
- async (_root, _args, _ctx): JSON.t => {
1641
- let pluginStatusById = {
1642
- let dict = Dict.make()
1643
- switch Bus.getQueryDbScan(pluginQueryDbName) {
1644
- | Some(scanAll) =>
1645
- scanAll()->Array.forEach(json =>
1646
- switch json->S.convertOrThrow(ReventlessCore.PluginsReadModelSpec.stateSchema) {
1647
- | state =>
1648
- let id = state.name ++ "@" ++ state.version
1649
- dict->Dict.set(id, state.status)
1650
- | exception _ => ()
1651
- }
1652
- )
1653
- | None => ()
1654
- }
1655
- dict
1671
+ // The plugins both structure queries answer over: one entry per plugin name,
1672
+ // Connected only, highest version. Shared so the two fields can never disagree
1673
+ // about which plugins exist mirrors the single Lambda serving both fields on
1674
+ // the AWS side.
1675
+ let connectedLatestStructures = () => {
1676
+ let pluginStatusById = {
1677
+ let dict = Dict.make()
1678
+ switch Bus.getQueryDbScan(pluginQueryDbName) {
1679
+ | Some(scanAll) =>
1680
+ scanAll()->Array.forEach(json =>
1681
+ switch json->S.convertOrThrow(ReventlessCore.PluginsReadModelSpec.stateSchema) {
1682
+ | state =>
1683
+ let id = state.name ++ "@" ++ state.version
1684
+ dict->Dict.set(id, state.status)
1685
+ | exception _ => ()
1686
+ }
1687
+ )
1688
+ | None => ()
1656
1689
  }
1657
- let adminPluginId = ReventlessCore.Platform_Admin_Structure.pluginId
1658
- // One entry per plugin name: among Connected versions keep the highest
1659
- // version, enforcing the deploy-time one-version-per-plugin invariant.
1660
- // Without this, a lingering Connected older version (rolling deploy or a
1661
- // missed retire) surfaces a full duplicate set of AutoUI menu entries —
1662
- // mirrors the dedup in Platform_ComponentDefinitions_Lambda.res.
1663
- let latestByName = Dict.make()
1664
- pluginStructuresStore.contents
1665
- ->Dict.toArray
1666
- ->Array.forEach(((pluginId, def)) => {
1667
- let connected =
1668
- pluginId === adminPluginId ||
1669
- switch pluginStatusById->Dict.get(pluginId) {
1670
- | Some(Connected) => true
1671
- | _ => false
1672
- }
1673
- if connected {
1674
- let name = ReventlessCore.Plugin.name(pluginId)
1675
- let version = ReventlessCore.Plugin.version(pluginId)
1676
- switch latestByName->Dict.get(name) {
1677
- | Some((prevVersion, _)) =>
1678
- if ReventlessCore.Plugin.compareVersions(version, prevVersion) > 0 {
1679
- latestByName->Dict.set(name, (version, (pluginId, def)))
1680
- }
1681
- | None => latestByName->Dict.set(name, (version, (pluginId, def)))
1690
+ dict
1691
+ }
1692
+ let adminPluginId = ReventlessCore.Platform_Admin_Structure.pluginId
1693
+ // One entry per plugin name: among Connected versions keep the highest
1694
+ // version, enforcing the deploy-time one-version-per-plugin invariant.
1695
+ // Without this, a lingering Connected older version (rolling deploy or a
1696
+ // missed retire) surfaces a full duplicate set of AutoUI menu entries —
1697
+ // mirrors the dedup in Platform_ComponentDefinitions_Lambda.res.
1698
+ let latestByName = Dict.make()
1699
+ pluginStructuresStore.contents
1700
+ ->Dict.toArray
1701
+ ->Array.forEach(((pluginId, def)) => {
1702
+ let connected =
1703
+ pluginId === adminPluginId ||
1704
+ switch pluginStatusById->Dict.get(pluginId) {
1705
+ | Some(Connected) => true
1706
+ | _ => false
1707
+ }
1708
+ if connected {
1709
+ let name = ReventlessCore.Plugin.name(pluginId)
1710
+ let version = ReventlessCore.Plugin.version(pluginId)
1711
+ switch latestByName->Dict.get(name) {
1712
+ | Some((prevVersion, _)) =>
1713
+ if ReventlessCore.Plugin.compareVersions(version, prevVersion) > 0 {
1714
+ latestByName->Dict.set(name, (version, (pluginId, def)))
1682
1715
  }
1716
+ | None => latestByName->Dict.set(name, (version, (pluginId, def)))
1683
1717
  }
1684
- })
1685
- latestByName
1686
- ->Dict.valuesToArray
1687
- ->Array.map(((_, (pluginId, def))) =>
1718
+ }
1719
+ })
1720
+ latestByName->Dict.valuesToArray->Array.map(((_, pair)) => pair)
1721
+ }
1722
+
1723
+ queryResolvers->Dict.set(
1724
+ "Platform_ComponentDefinitions",
1725
+ async (_root, _args, _ctx): JSON.t =>
1726
+ connectedLatestStructures()
1727
+ ->Array.map(((pluginId, def)) =>
1688
1728
  ReventlessCore.Platform_ComponentDefinitionsApi.encodePluginStructureEntry(~pluginId, def)
1689
1729
  )
1690
- ->JSON.Encode.array
1691
- },
1730
+ ->JSON.Encode.array,
1731
+ )
1732
+
1733
+ // Platform_PluginStructures resolver — the same plugins, encoded unfiltered for
1734
+ // developer tooling (Internal components kept, extension points carried).
1735
+ queryResolvers->Dict.set(
1736
+ "Platform_PluginStructures",
1737
+ async (_root, _args, _ctx): JSON.t =>
1738
+ connectedLatestStructures()
1739
+ ->Array.map(((pluginId, def)) =>
1740
+ ReventlessCore.Platform_PluginStructuresApi.encodePluginStructureEntry(~pluginId, def)
1741
+ )
1742
+ ->JSON.Encode.array,
1692
1743
  )
1693
1744
 
1694
1745
  // Platform_UIFragments resolver — reads the UiFragments StateViewSlice
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as S from "sury/src/S.res.mjs";
4
4
  import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
5
+ import * as NodeCrypto from "@reventlessdev/rescript-node/src/NodeCrypto.res.mjs";
5
6
  import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
6
7
  import * as Stdlib_Bool from "@rescript/runtime/lib/es6/Stdlib_Bool.js";
7
8
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
@@ -40,6 +41,7 @@ import * as LocalPluginSpec$ReventlessLocal from "./adapter/LocalPluginSpec.res.
40
41
  import * as NoEventMappings$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/NoEventMappings.res.mjs";
41
42
  import * as DomainMCP_Server$ReventlessLocal from "./adapter/DomainMCP_Server.res.mjs";
42
43
  import * as ExtensionMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionMapping.res.mjs";
44
+ import * as LocalObjectStore$ReventlessLocal from "./adapter/ObjectStore/LocalObjectStore.res.mjs";
43
45
  import * as LocalQueryEngine$ReventlessLocal from "./adapter/QueryEngine/LocalQueryEngine.res.mjs";
44
46
  import * as Platform_AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_AdminApi.res.mjs";
45
47
  import * as PluginsProjection$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsProjection.res.mjs";
@@ -80,6 +82,7 @@ import * as DcbEventLogStorage_Sqlite$ReventlessLocal from "./adapter/DcbEventLo
80
82
  import * as LocalEventCollectorChannel$ReventlessLocal from "./adapter/EventCollector/LocalEventCollectorChannel.res.mjs";
81
83
  import * as PluginRuntime_Builder_Micro$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Runtime/PluginRuntime_Builder_Micro.res.mjs";
82
84
  import * as UiFragmentRegistry_Behavior$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry_Behavior.res.mjs";
85
+ import * as Platform_PluginStructuresApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_PluginStructuresApi.res.mjs";
83
86
  import * as EventHistoryResolvers_GraphQL$ReventlessLocal from "./adapter/EventHistory/EventHistoryResolvers_GraphQL.res.mjs";
84
87
  import * as InboundTranslationSlice_Builder$ReventlessLocal from "./components/InboundTranslationSlice_Builder.res.mjs";
85
88
  import * as Platform_ComponentDefinitionsApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_ComponentDefinitionsApi.res.mjs";
@@ -126,12 +129,16 @@ function MakeWithConfig(Config) {
126
129
  if (typeof match !== "object") {
127
130
  BackendState$ReventlessLocal.setMemory();
128
131
  } else if (match.TAG === "Sqlite") {
132
+ let resetOnStart = match.resetOnStart;
129
133
  let path = match.path;
130
- if (match.resetOnStart) {
134
+ if (resetOnStart) {
131
135
  Backend$ReventlessLocal.removeFileIfExists(path);
132
136
  }
133
137
  let db = SqliteDriver$ReventlessLocal.openDb(path);
134
138
  BackendState$ReventlessLocal.setSqlite(db, path);
139
+ if (resetOnStart) {
140
+ LocalObjectStore$ReventlessLocal.reset();
141
+ }
135
142
  LocalBus$ReventlessLocal.seedEventTapSeq(EventLogStorage_Sqlite$ReventlessLocal.countAll(db) + DcbEventLogStorage_Sqlite$ReventlessLocal.countAll(db) | 0);
136
143
  ProjectionPending$ReventlessLocal.enableTracking();
137
144
  EventPublish_Callback$ReventlessCore.registerAfterPublish(async publishedEvent => {
@@ -809,6 +816,17 @@ function MakeWithConfig(Config) {
809
816
  let domainMcpPort = resolvePort("REVENTLESS_DOMAIN_MCP_PORT", 3001);
810
817
  let platformPort = resolvePort("REVENTLESS_PLATFORM_PORT", 4001);
811
818
  let platformMcpPort = resolvePort("REVENTLESS_PLATFORM_MCP_PORT", 3002);
819
+ Plugin_Helpers$ReventlessCore.registerOffload((store, bytes) => {
820
+ let hash = NodeCrypto.sha256Hex(bytes);
821
+ let key = "sha256/" + hash;
822
+ LocalObjectStore$ReventlessLocal.putOffload(key, bytes);
823
+ return {
824
+ store: store,
825
+ key: key,
826
+ hash: hash,
827
+ bytes: bytes.length
828
+ };
829
+ });
812
830
  let pluginStructuresStore = {
813
831
  contents: {}
814
832
  };
@@ -822,18 +840,15 @@ function MakeWithConfig(Config) {
822
840
  let ps = param[1];
823
841
  if (ps !== undefined) {
824
842
  pluginStructuresStore.contents[param[0]] = ps;
843
+ Stdlib_Option.getOr(ps.requiredStores, []).forEach(qualified => LocalObjectStore$ReventlessLocal.registerStore(qualified, LocalObjectStore$ReventlessLocal.localPrefixFor(qualified)));
844
+ StorePrefixCollision$ReventlessCore.collisionsFor(LocalObjectStore$ReventlessLocal.declaredStoreList().map(param => ({
845
+ qualified: param[0],
846
+ prefix: param[1]
847
+ }))).forEach(c => log.warn("Platform:plugins", undefined, StorePrefixCollision$ReventlessCore.collisionMessage(c)));
825
848
  return;
826
849
  }
827
850
  });
828
851
  });
829
- let declared = Object.values(pluginStructuresStore.contents).flatMap(structure => Stdlib_Option.getOr(structure.requiredStores, []).map(qualified => {
830
- let i = Stdlib_String.indexOfOpt(qualified, ".");
831
- return {
832
- qualified: qualified,
833
- prefix: i !== undefined ? qualified.slice(0, i) + "/" + qualified.slice(i + 1 | 0, qualified.length) : qualified
834
- };
835
- }));
836
- StorePrefixCollision$ReventlessCore.collisionsFor(declared).forEach(c => log.warn("Platform:plugins", undefined, StorePrefixCollision$ReventlessCore.collisionMessage(c)));
837
852
  };
838
853
  let pluginCmdTopicKey = PluginSpec$ReventlessCore.name + "AggrCmdTopic";
839
854
  let pluginEventTopicKey = PluginSpec$ReventlessCore.name + "AggrEventTopic";
@@ -970,15 +985,9 @@ function MakeWithConfig(Config) {
970
985
  }),
971
986
  eventCollector: param[2].name,
972
987
  extensionProtocols: [],
973
- apiSchemaFragment: Stdlib_Option.map(param[5], f => ({
974
- TAG: "Inline",
975
- _0: f
976
- })),
988
+ apiSchemaFragment: Stdlib_Option.map(param[5], f => Plugin_Helpers$ReventlessCore.offloadPayload(f, Plugin$Reventless.apiSchemaFragmentSchema, "pluginApiFragments")),
977
989
  apiTarget: undefined,
978
- structure: Stdlib_Option.map(param[7], s => ({
979
- TAG: "Inline",
980
- _0: s
981
- })),
990
+ structure: Stdlib_Option.map(param[7], s => Plugin_Helpers$ReventlessCore.offloadPayload(s, Plugin$Reventless.pluginStructureSchema, "pluginStructures")),
982
991
  dcbEventLog: undefined,
983
992
  kind: "Domain"
984
993
  };
@@ -1341,7 +1350,7 @@ function MakeWithConfig(Config) {
1341
1350
  let scanAll = Bus.getQueryDbScan(PluginsReadModelSpec$ReventlessCore.name);
1342
1351
  return connectionResponse(scanAll !== undefined ? scanAll() : []);
1343
1352
  };
1344
- queryResolvers["Platform_ComponentDefinitions"] = async (_root, _args, _ctx) => {
1353
+ let connectedLatestStructures = () => {
1345
1354
  let dict = {};
1346
1355
  let scanAll = Bus.getQueryDbScan(PluginsReadModelSpec$ReventlessCore.name);
1347
1356
  if (scanAll !== undefined) {
@@ -1386,11 +1395,10 @@ function MakeWithConfig(Config) {
1386
1395
  return;
1387
1396
  }
1388
1397
  });
1389
- return Object.values(latestByName).map(param => {
1390
- let match = param[1];
1391
- return Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(match[0], match[1]);
1392
- });
1398
+ return Object.values(latestByName).map(param => param[1]);
1393
1399
  };
1400
+ queryResolvers["Platform_ComponentDefinitions"] = async (_root, _args, _ctx) => connectedLatestStructures().map(param => Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(param[0], param[1]));
1401
+ queryResolvers["Platform_PluginStructures"] = async (_root, _args, _ctx) => connectedLatestStructures().map(param => Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry(param[0], param[1]));
1394
1402
  queryResolvers["Platform_UIFragments"] = async (_root, _args, _ctx) => {
1395
1403
  let scanAll = Bus.getQueryDbScan(UiFragments$ReventlessCore.name);
1396
1404
  let items = scanAll !== undefined ? scanAll() : [];
@@ -1823,12 +1831,16 @@ function Make($star) {
1823
1831
  if (typeof backend !== "object") {
1824
1832
  BackendState$ReventlessLocal.setMemory();
1825
1833
  } else if (backend.TAG === "Sqlite") {
1834
+ let resetOnStart = backend.resetOnStart;
1826
1835
  let path = backend.path;
1827
- if (backend.resetOnStart) {
1836
+ if (resetOnStart) {
1828
1837
  Backend$ReventlessLocal.removeFileIfExists(path);
1829
1838
  }
1830
1839
  let db = SqliteDriver$ReventlessLocal.openDb(path);
1831
1840
  BackendState$ReventlessLocal.setSqlite(db, path);
1841
+ if (resetOnStart) {
1842
+ LocalObjectStore$ReventlessLocal.reset();
1843
+ }
1832
1844
  LocalBus$ReventlessLocal.seedEventTapSeq(EventLogStorage_Sqlite$ReventlessLocal.countAll(db) + DcbEventLogStorage_Sqlite$ReventlessLocal.countAll(db) | 0);
1833
1845
  ProjectionPending$ReventlessLocal.enableTracking();
1834
1846
  EventPublish_Callback$ReventlessCore.registerAfterPublish(async publishedEvent => {
@@ -2500,6 +2512,17 @@ function Make($star) {
2500
2512
  let domainMcpPort = resolvePort("REVENTLESS_DOMAIN_MCP_PORT", 3001);
2501
2513
  let platformPort = resolvePort("REVENTLESS_PLATFORM_PORT", 4001);
2502
2514
  let platformMcpPort = resolvePort("REVENTLESS_PLATFORM_MCP_PORT", 3002);
2515
+ Plugin_Helpers$ReventlessCore.registerOffload((store, bytes) => {
2516
+ let hash = NodeCrypto.sha256Hex(bytes);
2517
+ let key = "sha256/" + hash;
2518
+ LocalObjectStore$ReventlessLocal.putOffload(key, bytes);
2519
+ return {
2520
+ store: store,
2521
+ key: key,
2522
+ hash: hash,
2523
+ bytes: bytes.length
2524
+ };
2525
+ });
2503
2526
  let pluginStructuresStore = {
2504
2527
  contents: {}
2505
2528
  };
@@ -2513,18 +2536,15 @@ function Make($star) {
2513
2536
  let ps = param[1];
2514
2537
  if (ps !== undefined) {
2515
2538
  pluginStructuresStore.contents[param[0]] = ps;
2539
+ Stdlib_Option.getOr(ps.requiredStores, []).forEach(qualified => LocalObjectStore$ReventlessLocal.registerStore(qualified, LocalObjectStore$ReventlessLocal.localPrefixFor(qualified)));
2540
+ StorePrefixCollision$ReventlessCore.collisionsFor(LocalObjectStore$ReventlessLocal.declaredStoreList().map(param => ({
2541
+ qualified: param[0],
2542
+ prefix: param[1]
2543
+ }))).forEach(c => log.warn("Platform:plugins", undefined, StorePrefixCollision$ReventlessCore.collisionMessage(c)));
2516
2544
  return;
2517
2545
  }
2518
2546
  });
2519
2547
  });
2520
- let declared = Object.values(pluginStructuresStore.contents).flatMap(structure => Stdlib_Option.getOr(structure.requiredStores, []).map(qualified => {
2521
- let i = Stdlib_String.indexOfOpt(qualified, ".");
2522
- return {
2523
- qualified: qualified,
2524
- prefix: i !== undefined ? qualified.slice(0, i) + "/" + qualified.slice(i + 1 | 0, qualified.length) : qualified
2525
- };
2526
- }));
2527
- StorePrefixCollision$ReventlessCore.collisionsFor(declared).forEach(c => log.warn("Platform:plugins", undefined, StorePrefixCollision$ReventlessCore.collisionMessage(c)));
2528
2548
  };
2529
2549
  let pluginCmdTopicKey = PluginSpec$ReventlessCore.name + "AggrCmdTopic";
2530
2550
  let pluginEventTopicKey = PluginSpec$ReventlessCore.name + "AggrEventTopic";
@@ -2661,15 +2681,9 @@ function Make($star) {
2661
2681
  }),
2662
2682
  eventCollector: param[2].name,
2663
2683
  extensionProtocols: [],
2664
- apiSchemaFragment: Stdlib_Option.map(param[5], f => ({
2665
- TAG: "Inline",
2666
- _0: f
2667
- })),
2684
+ apiSchemaFragment: Stdlib_Option.map(param[5], f => Plugin_Helpers$ReventlessCore.offloadPayload(f, Plugin$Reventless.apiSchemaFragmentSchema, "pluginApiFragments")),
2668
2685
  apiTarget: undefined,
2669
- structure: Stdlib_Option.map(param[7], s => ({
2670
- TAG: "Inline",
2671
- _0: s
2672
- })),
2686
+ structure: Stdlib_Option.map(param[7], s => Plugin_Helpers$ReventlessCore.offloadPayload(s, Plugin$Reventless.pluginStructureSchema, "pluginStructures")),
2673
2687
  dcbEventLog: undefined,
2674
2688
  kind: "Domain"
2675
2689
  };
@@ -3027,7 +3041,7 @@ function Make($star) {
3027
3041
  let scanAll = Bus.getQueryDbScan(PluginsReadModelSpec$ReventlessCore.name);
3028
3042
  return connectionResponse(scanAll !== undefined ? scanAll() : []);
3029
3043
  };
3030
- queryResolvers["Platform_ComponentDefinitions"] = async (_root, _args, _ctx) => {
3044
+ let connectedLatestStructures = () => {
3031
3045
  let dict = {};
3032
3046
  let scanAll = Bus.getQueryDbScan(PluginsReadModelSpec$ReventlessCore.name);
3033
3047
  if (scanAll !== undefined) {
@@ -3072,11 +3086,10 @@ function Make($star) {
3072
3086
  return;
3073
3087
  }
3074
3088
  });
3075
- return Object.values(latestByName).map(param => {
3076
- let match = param[1];
3077
- return Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(match[0], match[1]);
3078
- });
3089
+ return Object.values(latestByName).map(param => param[1]);
3079
3090
  };
3091
+ queryResolvers["Platform_ComponentDefinitions"] = async (_root, _args, _ctx) => connectedLatestStructures().map(param => Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(param[0], param[1]));
3092
+ queryResolvers["Platform_PluginStructures"] = async (_root, _args, _ctx) => connectedLatestStructures().map(param => Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry(param[0], param[1]));
3080
3093
  queryResolvers["Platform_UIFragments"] = async (_root, _args, _ctx) => {
3081
3094
  let scanAll = Bus.getQueryDbScan(UiFragments$ReventlessCore.name);
3082
3095
  let items = scanAll !== undefined ? scanAll() : [];
@@ -37,3 +37,16 @@ let getPostgresPool = () =>
37
37
  | Postgres({pool}) => Some(pool)
38
38
  | Memory | Sqlite(_) => None
39
39
  }
40
+
41
+ // Directory the object store persists under, derived from the SQLite file's own
42
+ // directory (`./.reventless/local.db` → `./.reventless`) so uploaded bytes and
43
+ // offloaded payloads live and die with the events that reference them. None for
44
+ // the backends with nothing durable on this disk: Memory, an in-process
45
+ // `:memory:` SQLite, and Postgres — durable, but off-machine, and a connection
46
+ // string names no local directory to anchor to, so its object store stays
47
+ // in-process.
48
+ let getObjectStoreRoot = () =>
49
+ switch current.contents {
50
+ | Sqlite({path}) if path != ":memory:" => Some(NodePath.dirname(path))
51
+ | Sqlite(_) | Memory | Postgres(_) => None
52
+ }
@@ -1,5 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Nodepath from "node:path";
3
4
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
4
5
 
5
6
  let current = {
@@ -43,6 +44,20 @@ function getPostgresPool() {
43
44
  }
44
45
  }
45
46
 
47
+ function getObjectStoreRoot() {
48
+ let match = current.contents;
49
+ if (typeof match !== "object") {
50
+ return;
51
+ }
52
+ if (match.TAG !== "Sqlite") {
53
+ return;
54
+ }
55
+ let path = match.path;
56
+ if (path !== ":memory:") {
57
+ return Nodepath.dirname(path);
58
+ }
59
+ }
60
+
46
61
  export {
47
62
  current,
48
63
  setMemory,
@@ -50,5 +65,6 @@ export {
50
65
  setPostgres,
51
66
  getSqliteDb,
52
67
  getPostgresPool,
68
+ getObjectStoreRoot,
53
69
  }
54
- /* No side effect */
70
+ /* node:path Not a pure module */
@@ -32,4 +32,4 @@ function Make(Bus) {
32
32
  export {
33
33
  Make,
34
34
  }
35
- /* DcbEventLogStorage_Sqlite-ReventlessLocal Not a pure module */
35
+ /* BackendState-ReventlessLocal Not a pure module */