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

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
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.203 (2026-08-08)
7
+
8
+ ### Features
9
+
10
+ * **core,aws,local:** add the RuntimeExtension cold-start seam ([143e30e](https://github.com/ReventlessDev/reventless-core/commit/143e30eeded6232ce7f0fcc18328939b2917bb31))
11
+
12
+
13
+ # 3.0.0-alpha.202 (2026-08-07)
14
+
15
+ ### Features
16
+
17
+ * **core,aws:** add the EventLogProvisioning seam for out-of-tree consumers ([82477b3](https://github.com/ReventlessDev/reventless-core/commit/82477b38c1ef90fbe3a6f4a114a9798770329470))
18
+
19
+
6
20
  # 3.0.0-alpha.201 (2026-08-05)
7
21
 
8
22
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-local",
3
- "version": "3.0.0-alpha.201",
3
+ "version": "3.0.0-alpha.203",
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": {
@@ -35,18 +35,18 @@
35
35
  "sury": "11.0.0-alpha.4",
36
36
  "ws": "^8.18.0",
37
37
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
38
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
39
- "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
40
38
  "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.26",
39
+ "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
41
40
  "@reventlessdev/rescript-node": "2.0.0-alpha.2",
41
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
42
42
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
43
- "@reventlessdev/reventless-core": "3.0.0-alpha.213",
44
- "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.61",
43
+ "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.63",
44
+ "@reventlessdev/reventless-core": "3.0.0-alpha.215",
45
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.162",
45
46
  "@reventlessdev/reventless-infra": "3.0.0-alpha.127",
47
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.79",
46
48
  "@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
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.101"
50
50
  },
51
51
  "devDependencies": {
52
52
  "rescript": "12.3.0",
package/src/Platform.res CHANGED
@@ -1414,6 +1414,24 @@ module MakeWithConfig = (
1414
1414
  `backend: sqlite (${path}${resetOnStart ? ", resetOnStart" : ""})`
1415
1415
  },
1416
1416
  )
1417
+ // Runtime extension seam. Fired before anything is built, so an extension's
1418
+ // interception and publish hooks are registered before the first command or
1419
+ // query can reach them.
1420
+ //
1421
+ // Off Lambda the seam means something slightly different, and deliberately
1422
+ // so: every component runs in THIS process, so there is one cold start, not
1423
+ // one per runtime. Firing per hosted component would hand an extension N
1424
+ // identities for a single process and re-run registrations that are
1425
+ // module-level refs anyway. `Platform` is the honest kind for a runtime that
1426
+ // hosts all of them; an extension that routes on kind should treat this as
1427
+ // "everything", which is what it is.
1428
+ ReventlessCore.RuntimeExtension.notifyColdStart(
1429
+ ~runtimeKind=ReventlessCore.ComponentType.Platform,
1430
+ ~component="LocalPlatform",
1431
+ ~plugin=ReventlessCore.ResourceAttribution.current.contents.plugin,
1432
+ ~platform=ReventlessCore.ResourceAttribution.current.contents.platform,
1433
+ )
1434
+
1417
1435
  // Projection catch-up bound (plan B5): the highest persisted event position
1418
1436
  // BEFORE this session appends anything. Catch-up replays only (checkpoint,
1419
1437
  // bound] — this session's own events (Connect dispatches, user commands)
@@ -39,6 +39,7 @@ import * as Counter_Builder$ReventlessLocal from "./components/Counter_Builder.r
39
39
  import * as GraphQL_Stitcher$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_Stitcher.res.mjs";
40
40
  import * as LocalPluginSpec$ReventlessLocal from "./adapter/LocalPluginSpec.res.mjs";
41
41
  import * as NoEventMappings$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/NoEventMappings.res.mjs";
42
+ import * as RuntimeExtension$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/RuntimeExtension/RuntimeExtension.res.mjs";
42
43
  import * as DomainMCP_Server$ReventlessLocal from "./adapter/DomainMCP_Server.res.mjs";
43
44
  import * as ExtensionMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionMapping.res.mjs";
44
45
  import * as LocalObjectStore$ReventlessLocal from "./adapter/ObjectStore/LocalObjectStore.res.mjs";
@@ -54,6 +55,7 @@ import * as ReadModel_Builder$ReventlessLocal from "./components/ReadModel_Build
54
55
  import * as UiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs";
55
56
  import * as LocalEvents_Server$ReventlessLocal from "./adapter/Api/LocalEvents_Server.res.mjs";
56
57
  import * as PlatformMCP_Server$ReventlessLocal from "./adapter/PlatformMCP_Server.res.mjs";
58
+ import * as ResourceAttribution$ReventlessCore from "@reventlessdev/reventless-core/src/ResourceAttribution.res.mjs";
57
59
  import * as Auth_GraphqlContext$ReventlessLocal from "./adapter/Auth/Auth_GraphqlContext.res.mjs";
58
60
  import * as PgProjectionCatchup$ReventlessLocal from "./adapter/PgProjectionCatchup.res.mjs";
59
61
  import * as PluginsReadModelSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs";
@@ -1192,6 +1194,7 @@ function MakeWithConfig(Config) {
1192
1194
  ) + `)` : `backend: postgres (` + match.connection + `, event-logs only)`
1193
1195
  );
1194
1196
  log.info("Platform", undefined, tmp);
1197
+ RuntimeExtension$ReventlessCore.notifyColdStart("Platform", "LocalPlatform", ResourceAttribution$ReventlessCore.current.contents.plugin, ResourceAttribution$ReventlessCore.current.contents.platform);
1195
1198
  let projectionCatchup = Stdlib_Option.map(BackendState$ReventlessLocal.getSqliteDb(), db => [
1196
1199
  db,
1197
1200
  ProjectionCheckpoint$ReventlessLocal.maxPosition(db, "Aggregate"),
@@ -2883,6 +2886,7 @@ function Make($star) {
2883
2886
  ) + `)` : `backend: postgres (` + backend.connection + `, event-logs only)`
2884
2887
  );
2885
2888
  log.info("Platform", undefined, tmp);
2889
+ RuntimeExtension$ReventlessCore.notifyColdStart("Platform", "LocalPlatform", ResourceAttribution$ReventlessCore.current.contents.plugin, ResourceAttribution$ReventlessCore.current.contents.platform);
2886
2890
  let projectionCatchup = Stdlib_Option.map(BackendState$ReventlessLocal.getSqliteDb(), db => [
2887
2891
  db,
2888
2892
  ProjectionCheckpoint$ReventlessLocal.maxPosition(db, "Aggregate"),
@@ -0,0 +1,110 @@
1
+ // End-to-end check that the EventLogProvisioning seam fires from the real
2
+ // builders, not just from its own registry: a backend registered before the
3
+ // build sees both log styles as they are provisioned, through the local
4
+ // in-memory storage adapters.
5
+ //
6
+ // Registration and construction happen at module load — the seam fires during
7
+ // `Component.make`'s construct, so a backend registered inside a `test` body
8
+ // would arrive too late. The assertions then read what was recorded.
9
+
10
+ open JestGlobals
11
+
12
+ type record = {
13
+ logStyle: ReventlessCore.EventLogProvisioning.logStyle,
14
+ name: string,
15
+ owner: option<ReventlessCore.ResourceAttribution.owner>,
16
+ plugin: option<string>,
17
+ resourceCount: int,
18
+ }
19
+
20
+ let provisioned: array<record> = []
21
+
22
+ module Recorder: ReventlessCore.EventLogProvisioning.Backend = {
23
+ let onProvisioned = (~logStyle, ~name, ~owner, ~plugin, ~platform as _, ~resources, ~opts as _) =>
24
+ provisioned->Array.push({
25
+ logStyle,
26
+ name,
27
+ owner,
28
+ plugin,
29
+ resourceCount: resources->Array.length,
30
+ })
31
+ }
32
+
33
+ ReventlessCore.EventLogProvisioning.use(module(Recorder: ReventlessCore.EventLogProvisioning.Backend))
34
+
35
+ module Bus = LocalBus.Make()
36
+
37
+ let _ = TestRunner.setup()
38
+
39
+ // ── Classic log ──────────────────────────────────────────────
40
+ module SeamItemSpec = {
41
+ module Id = Reventless.Id.StringPure
42
+ let name = "SeamItemEventLog"
43
+
44
+ @schema
45
+ type event = | SeamItemCreated({name: string})
46
+ }
47
+
48
+ module ClassicMaker = ReventlessCore.EventLog_Builder.Make(
49
+ SeamItemSpec,
50
+ EventLogStorage_InMemory,
51
+ LocalEventTopicPublisher.Make(Bus),
52
+ )
53
+
54
+ // The plugin builder publishes this context around a plugin's construct; entering
55
+ // it here is what a real plugin build does, so the seam should carry the names.
56
+ let previousContext = ReventlessCore.ResourceAttribution.enter(~platform="seam-platform", ~plugin="SeamPlugin")
57
+
58
+ let classicLog = ClassicMaker.make(
59
+ ~name="SeamItem",
60
+ ~owner={kind: ReventlessCore.ComponentType.Aggregate, name: "SeamItem"},
61
+ )
62
+
63
+ // ── DCB log ──────────────────────────────────────────────────
64
+ module DcbMaker = DcbEventLog_Builder.Make(Bus)
65
+
66
+ let dcbLog = DcbMaker.make(
67
+ ~name="SeamCatalog",
68
+ ~partitionTag=Reventless.DcbTag.Simple({key: "id"}),
69
+ )
70
+
71
+ ReventlessCore.ResourceAttribution.restore(previousContext)
72
+
73
+ describe("EventLogProvisioning seam, driven by the real builders", () => {
74
+ testSync("fires once per log, for both styles", () => {
75
+ expect(provisioned->Array.map(r => (r.logStyle, r.name)))->toEqual([
76
+ (ReventlessCore.EventLogProvisioning.Classic, "SeamItemEventLog"),
77
+ (ReventlessCore.EventLogProvisioning.Dcb, "SeamCatalogDcbEventLog"),
78
+ ])
79
+ })
80
+
81
+ testSync("carries the owning element: the aggregate for classic, the plugin for DCB", () => {
82
+ expect(provisioned->Array.map(r => r.owner))->toEqual([
83
+ Some(({kind: ReventlessCore.ComponentType.Aggregate, name: "SeamItem"}: ReventlessCore.ResourceAttribution.owner)),
84
+ Some(({kind: ReventlessCore.ComponentType.Plugin, name: "SeamCatalog"}: ReventlessCore.ResourceAttribution.owner)),
85
+ ])
86
+ })
87
+
88
+ testSync("carries the ambient plugin attribution the builder published", () => {
89
+ expect(provisioned->Array.map(r => r.plugin))->toEqual([
90
+ Some("SeamPlugin"),
91
+ Some("SeamPlugin"),
92
+ ])
93
+ })
94
+
95
+ testSync("carries the adapter's resources — empty for in-memory storage", () => {
96
+ // In-memory logs have nothing attachable; the seam still fires, and a backend
97
+ // that only implements a provider arm ignores them.
98
+ expect(provisioned->Array.map(r => r.resourceCount))->toEqual([0, 0])
99
+ })
100
+
101
+ test("the components still build normally with a backend registered", async () => {
102
+ let classicOps = await classicLog->ReventlessCore.Component.operations->TestRunner.resolve
103
+ let dcbOps = await dcbLog->DcbMaker.operations->TestRunner.resolve
104
+ // Resolving both operation records proves construction completed with a
105
+ // backend registered — the seam fires mid-construct, so a throwing backend
106
+ // would take the build down here.
107
+ expect(classicOps)->toBeTruthy
108
+ expect(dcbOps)->toBeTruthy
109
+ })
110
+ })
@@ -0,0 +1,141 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
5
+ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
6
+ import * as LocalBus$ReventlessLocal from "../../../src/adapter/LocalBus.res.mjs";
7
+ import * as TestRunner$ReventlessLocal from "../../../src/test/TestRunner.res.mjs";
8
+ import * as EventLog_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventLog/EventLog_Builder.res.mjs";
9
+ import * as ResourceAttribution$ReventlessCore from "@reventlessdev/reventless-core/src/ResourceAttribution.res.mjs";
10
+ import * as DcbEventLog_Builder$ReventlessLocal from "../../../src/components/DcbEventLog_Builder.res.mjs";
11
+ import * as EventLogProvisioning$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/EventLogProvisioning/EventLogProvisioning.res.mjs";
12
+ import * as EventLogStorage_InMemory$ReventlessLocal from "../../../src/adapter/EventLog/EventLogStorage_InMemory.res.mjs";
13
+ import * as LocalEventTopicPublisher$ReventlessLocal from "../../../src/adapter/EventTopic/LocalEventTopicPublisher.res.mjs";
14
+
15
+ let provisioned = [];
16
+
17
+ function onProvisioned(logStyle, name, owner, plugin, param, resources, param$1) {
18
+ provisioned.push({
19
+ logStyle: logStyle,
20
+ name: name,
21
+ owner: owner,
22
+ plugin: plugin,
23
+ resourceCount: resources.length
24
+ });
25
+ }
26
+
27
+ let Recorder = {
28
+ onProvisioned: onProvisioned
29
+ };
30
+
31
+ EventLogProvisioning$ReventlessCore.use(Recorder);
32
+
33
+ let Bus = LocalBus$ReventlessLocal.Make({});
34
+
35
+ TestRunner$ReventlessLocal.setup();
36
+
37
+ let name = "SeamItemEventLog";
38
+
39
+ let eventSchema = S.schema(s => ({
40
+ TAG: "SeamItemCreated",
41
+ name: s.m(S.string)
42
+ }));
43
+
44
+ let SeamItemSpec = {
45
+ Id: undefined,
46
+ name: name,
47
+ eventSchema: eventSchema
48
+ };
49
+
50
+ let ClassicMaker = EventLog_Builder$ReventlessCore.Make({
51
+ Id: {
52
+ schema: Id$Reventless.StringPure.schema,
53
+ make: prim => prim,
54
+ makeFromString: prim => prim,
55
+ toString: prim => prim,
56
+ cmp: Id$Reventless.StringPure.cmp
57
+ },
58
+ name: name,
59
+ eventSchema: eventSchema
60
+ })({
61
+ make: EventLogStorage_InMemory$ReventlessLocal.make
62
+ })(LocalEventTopicPublisher$ReventlessLocal.Make(Bus));
63
+
64
+ let previousContext = ResourceAttribution$ReventlessCore.enter("seam-platform", "SeamPlugin");
65
+
66
+ let classicLog = ClassicMaker.make("SeamItem", {
67
+ kind: "Aggregate",
68
+ name: "SeamItem"
69
+ }, undefined);
70
+
71
+ let DcbMaker = DcbEventLog_Builder$ReventlessLocal.Make(Bus);
72
+
73
+ let dcbLog = DcbMaker.make("SeamCatalog", undefined, {
74
+ TAG: "Simple",
75
+ _0: {
76
+ key: "id"
77
+ }
78
+ }, undefined);
79
+
80
+ ResourceAttribution$ReventlessCore.restore(previousContext);
81
+
82
+ globalThis.describe("EventLogProvisioning seam, driven by the real builders", () => {
83
+ globalThis.test("fires once per log, for both styles", () => {
84
+ globalThis.expect(provisioned.map(r => [
85
+ r.logStyle,
86
+ r.name
87
+ ])).toEqual([
88
+ [
89
+ "Classic",
90
+ "SeamItemEventLog"
91
+ ],
92
+ [
93
+ "Dcb",
94
+ "SeamCatalogDcbEventLog"
95
+ ]
96
+ ]);
97
+ });
98
+ globalThis.test("carries the owning element: the aggregate for classic, the plugin for DCB", () => {
99
+ globalThis.expect(provisioned.map(r => r.owner)).toEqual([
100
+ {
101
+ kind: "Aggregate",
102
+ name: "SeamItem"
103
+ },
104
+ {
105
+ kind: "Plugin",
106
+ name: "SeamCatalog"
107
+ }
108
+ ]);
109
+ });
110
+ globalThis.test("carries the ambient plugin attribution the builder published", () => {
111
+ globalThis.expect(provisioned.map(r => r.plugin)).toEqual([
112
+ "SeamPlugin",
113
+ "SeamPlugin"
114
+ ]);
115
+ });
116
+ globalThis.test("carries the adapter's resources — empty for in-memory storage", () => {
117
+ globalThis.expect(provisioned.map(r => r.resourceCount)).toEqual([
118
+ 0,
119
+ 0
120
+ ]);
121
+ });
122
+ globalThis.test("the components still build normally with a backend registered", async () => {
123
+ let classicOps = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(classicLog));
124
+ let dcbOps = await TestRunner$ReventlessLocal.resolve(DcbMaker.operations(dcbLog));
125
+ globalThis.expect(classicOps).toBeTruthy();
126
+ globalThis.expect(dcbOps).toBeTruthy();
127
+ });
128
+ });
129
+
130
+ export {
131
+ provisioned,
132
+ Recorder,
133
+ Bus,
134
+ SeamItemSpec,
135
+ ClassicMaker,
136
+ previousContext,
137
+ classicLog,
138
+ DcbMaker,
139
+ dcbLog,
140
+ }
141
+ /* Not a pure module */