@reventlessdev/reventless-local 3.0.0-alpha.241 → 3.0.0-alpha.243

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,28 @@
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.243 (2026-09-02)
7
+
8
+ * feat(aws)!: the messaging sender is configuration, and a stack can choose to only log ([23b8b4b](https://github.com/ReventlessDev/reventless-core/commit/23b8b4bfe9c70555de4d74266ca686cb427485ca))
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ * `Capability_Messaging_Ses.make` is replaced by
13
+ `Capability_Messaging.make(~name)`, which reads the transport and the address
14
+ from config; the SES module keeps only `emailSender`. A deployment that named its
15
+ sender in code must move it to `platform:messagingEmailSender` or the deploy is
16
+ refused.
17
+
18
+
19
+
20
+ # 3.0.0-alpha.242 (2026-09-01)
21
+
22
+ **Note:** Version bump only for package @reventlessdev/reventless-local
23
+
24
+
25
+
26
+
27
+
6
28
  # 3.0.0-alpha.241 (2026-09-01)
7
29
 
8
30
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-local",
3
- "version": "3.0.0-alpha.241",
3
+ "version": "3.0.0-alpha.243",
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,23 +35,23 @@
35
35
  "sury": "11.0.0-rc.2",
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
38
  "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.27",
40
- "@reventlessdev/rescript-node": "2.0.0-alpha.8",
41
39
  "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
40
+ "@reventlessdev/rescript-node": "2.0.0-alpha.8",
41
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
42
+ "@reventlessdev/reventless-core": "3.0.0-alpha.254",
42
43
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
43
- "@reventlessdev/reventless-core": "3.0.0-alpha.252",
44
- "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.100",
45
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.199",
46
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.116",
44
+ "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.102",
45
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.155",
46
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.201",
47
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.118",
47
48
  "@reventlessdev/reventless-seed": "1.0.0-alpha.18",
48
- "@reventlessdev/reventless-infra": "3.0.0-alpha.153",
49
- "@reventlessdev/reventless-spec": "3.0.0-alpha.125"
49
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.127"
50
50
  },
51
51
  "devDependencies": {
52
52
  "rescript": "12.3.0",
53
53
  "sury-ppx": "11.0.0-rc.2",
54
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.75"
54
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.76"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "rescript": "12.3.0"
package/src/Platform.res CHANGED
@@ -1491,6 +1491,20 @@ module MakeWithConfig = (
1491
1491
  `backend: sqlite (${path}${resetOnStart ? ", resetOnStart" : ""})`
1492
1492
  },
1493
1493
  )
1494
+ // The infrastructure half of `~hostUiBundle` that this platform does NOT
1495
+ // ignore. A capability is the one kind of deployment declaration that has a
1496
+ // local counterpart — the slice reaching it runs in this process, so there is
1497
+ // something real to provide — and the seam is the AWS one: a handle from a
1498
+ // `Capability_*` helper, registered before a slice can ask for it. `Input.t`
1499
+ // is the identity function off Pulumi, so what comes back out is the string.
1500
+ switch hostUiBundle->Option.flatMap(cfg => cfg.messagingSender) {
1501
+ | Some({emailSender: ?Some(sender)}) =>
1502
+ let address = sender->Pulumi.Output.fromInput->Pulumi.Output.unwrap
1503
+ LocalCapabilities.registerMessagingSender(address)
1504
+ log.info(~comp="Platform", `messaging: log transport as ${address} (nothing is sent)`)
1505
+ | _ => ()
1506
+ }
1507
+
1494
1508
  // Runtime extension seam. Fired before anything is built, so an extension's
1495
1509
  // interception and publish hooks are registered before the first command or
1496
1510
  // query can reach them.
@@ -52,6 +52,7 @@ import * as Platform_AdminApi$ReventlessCore from "@reventlessdev/reventless-cor
52
52
  import * as PluginsProjection$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsProjection.res.mjs";
53
53
  import * as Scheduler_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/Scheduler/Scheduler_Builder.res.mjs";
54
54
  import * as Aggregate_Builder$ReventlessLocal from "./components/Aggregate_Builder.res.mjs";
55
+ import * as LocalCapabilities$ReventlessLocal from "./adapter/LocalCapabilities.res.mjs";
55
56
  import * as LocalClonerRunner$ReventlessLocal from "./adapter/Cloner/LocalClonerRunner.res.mjs";
56
57
  import * as PluginBaseFragment$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/api/PluginBaseFragment.res.mjs";
57
58
  import * as ProjectionPending$ReventlessLocal from "./adapter/ProjectionPending.res.mjs";
@@ -1230,6 +1231,14 @@ function MakeWithConfig(Config) {
1230
1231
  ) + `)` : `backend: postgres (` + match.connection + `, event-logs only)`
1231
1232
  );
1232
1233
  log.info("Platform", undefined, tmp);
1234
+ let match$1 = Stdlib_Option.flatMap(hostUiBundle, cfg => cfg.messagingSender);
1235
+ if (match$1 !== undefined) {
1236
+ let sender = match$1.emailSender;
1237
+ if (sender !== undefined) {
1238
+ LocalCapabilities$ReventlessLocal.registerMessagingSender(sender);
1239
+ log.info("Platform", undefined, `messaging: log transport as ` + sender + ` (nothing is sent)`);
1240
+ }
1241
+ }
1233
1242
  RuntimeExtension$ReventlessCore.notifyColdStart("Platform", "LocalPlatform", ResourceAttribution$ReventlessCore.current.contents.plugin, ResourceAttribution$ReventlessCore.current.contents.platform);
1234
1243
  let projectionCatchup = Stdlib_Option.map(BackendState$ReventlessLocal.getSqliteDb(), db => [
1235
1244
  db,
@@ -2959,6 +2968,14 @@ function Make($star) {
2959
2968
  ) + `)` : `backend: postgres (` + backend.connection + `, event-logs only)`
2960
2969
  );
2961
2970
  log.info("Platform", undefined, tmp);
2971
+ let match = Stdlib_Option.flatMap(hostUiBundle, cfg => cfg.messagingSender);
2972
+ if (match !== undefined) {
2973
+ let sender = match.emailSender;
2974
+ if (sender !== undefined) {
2975
+ LocalCapabilities$ReventlessLocal.registerMessagingSender(sender);
2976
+ log.info("Platform", undefined, `messaging: log transport as ` + sender + ` (nothing is sent)`);
2977
+ }
2978
+ }
2962
2979
  RuntimeExtension$ReventlessCore.notifyColdStart("Platform", "LocalPlatform", ResourceAttribution$ReventlessCore.current.contents.plugin, ResourceAttribution$ReventlessCore.current.contents.platform);
2963
2980
  let projectionCatchup = Stdlib_Option.map(BackendState$ReventlessLocal.getSqliteDb(), db => [
2964
2981
  db,
@@ -1,16 +1,30 @@
1
1
  // What the local platform hands a slice's `translate`.
2
2
  //
3
- // Nothing, today and that is the honest answer rather than a placeholder. The
4
- // local platform provisions neither a geocoder nor a mailer (a place index and a
5
- // verified sender identity are each an account and a bill), so
6
- // `Capabilities.none` reports `Unavailable`, which `translate` maps to a
7
- // retryable `Error` and the sweep surfaces. A dev running a geocoding slice sees
8
- // its items stay queued, which is true, instead of seeing them written off as
9
- // unresolvable addresses, which would not be. Messaging additionally publishes an
10
- // empty channel list, so a preference surface offers nothing rather than offering
11
- // a channel that cannot deliver.
3
+ // No geocoder: the local platform provisions none (a place index is an account
4
+ // and a bill), so `Capabilities.none`'s `geocode` reports `Unavailable`, which
5
+ // `translate` maps to a retryable `Error` and the sweep surfaces. A dev running a
6
+ // geocoding slice sees its items stay queued, which is true, instead of seeing
7
+ // them written off as unresolvable addresses, which would not be.
12
8
  //
13
- // This is the one place to change when local grows a real one a fixture, a
14
- // stub keyed by address, Nominatim, a mailbox on disk. Slices reach them through
15
- // the injected record, so nothing in a plugin moves on that day.
16
- let capabilities = () => Reventless.Capabilities.none
9
+ // Messaging is provisioned when a root asks for it, exactly as on AWS: a
10
+ // `Capability_Messaging_Log.make()` handle passed as `~messagingSender`
11
+ // reaches `registerMessagingSender` and the transport becomes the provider. A root
12
+ // that passes nothing keeps the old answer — an empty sender yields no channels
13
+ // and a retryable `Unavailable`, which is what an unprovisioned platform is.
14
+ //
15
+ // The sender is a ref read per call rather than a value captured once, mirroring
16
+ // the Lambda side: there `capabilities()` re-reads `MESSAGING_EMAIL_SENDER` on
17
+ // every invocation so a reconfigured function needs no cold start. Here it means
18
+ // the order of `makePlatform`'s work is not a thing to get right.
19
+
20
+ let messagingSenderRef = ref("")
21
+
22
+ /** Provision the messaging capability. Called by `makePlatform` from the handle
23
+ a root declared — the local half of the seam whose AWS half is
24
+ `PluginRuntime_Builder.registerMessagingSender`. */
25
+ let registerMessagingSender = (sender: string) => messagingSenderRef := sender
26
+
27
+ let capabilities = (): Reventless.Capabilities.t => {
28
+ geocode: Reventless.Capabilities.none.geocode,
29
+ messaging: ReventlessCore.Messaging_Log_Backend.provider(~sender=messagingSenderRef.contents),
30
+ }
@@ -1,12 +1,26 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Capabilities$Reventless from "@reventlessdev/reventless-spec/src/semantic/Capabilities.res.mjs";
4
+ import * as Messaging_Log_Backend$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Messaging/Messaging_Log_Backend.res.mjs";
5
+
6
+ let messagingSenderRef = {
7
+ contents: ""
8
+ };
9
+
10
+ function registerMessagingSender(sender) {
11
+ messagingSenderRef.contents = sender;
12
+ }
4
13
 
5
14
  function capabilities() {
6
- return Capabilities$Reventless.none;
15
+ return {
16
+ geocode: Capabilities$Reventless.none.geocode,
17
+ messaging: Messaging_Log_Backend$ReventlessCore.provider(messagingSenderRef.contents)
18
+ };
7
19
  }
8
20
 
9
21
  export {
22
+ messagingSenderRef,
23
+ registerMessagingSender,
10
24
  capabilities,
11
25
  }
12
- /* No side effect */
26
+ /* Messaging_Log_Backend-ReventlessCore Not a pure module */
@@ -0,0 +1,39 @@
1
+ // The messaging sender a dev platform provisions — the local counterpart to
2
+ // `Capability_Messaging_Ses`, and reached the same way: a root calls `make`,
3
+ // passes the handle as `~messagingSender` in `~hostUiBundle`, and the platform
4
+ // puts a provider on the capability record every slice's `translate` receives.
5
+ // Nothing about a plugin changes between the two.
6
+ //
7
+ // **The address is hard-coded here, where on AWS it is configuration.** That is
8
+ // the one deliberate difference, and it follows from what the value is for. A
9
+ // verified identity is a per-account fact a repository cannot know, so the AWS
10
+ // helper refuses to invent one; a logged message reaches nobody, so there is
11
+ // nothing to verify, nothing that differs per developer, and nothing a setting
12
+ // would buy. `.test` is reserved for exactly this (RFC 6761) — it can never
13
+ // resolve, so an address escaping into a fixture cannot reach a real inbox.
14
+
15
+ /** The `From:` this platform's mail would carry. Named for the platform rather
16
+ than the shop: what a dev is looking at when they read it is the local
17
+ log transport, and a name that impersonated the deployment would make a screenshot
18
+ of a dev run indistinguishable from one of production. */
19
+ let senderName = "Online Shop (local)"
20
+ let senderAddress = "notifications@online-shop.test"
21
+
22
+ /**
23
+ The handle, in the shape the platform config takes.
24
+
25
+ `Pulumi.Input.t` in a platform that runs no Pulumi is not an accident: the field
26
+ belongs to the shared `Platform.messagingSender`, so both platforms carry the
27
+ sender in one type and a root moving between them changes which helper it calls
28
+ and nothing else. `Input.make` is the identity function, so what travels is the
29
+ string.
30
+
31
+ The header is built by `Messaging.fromHeader`, the same function the SES helper
32
+ applies to its verified address — one formatter, so the two platforms cannot
33
+ present the same deployment under two differently-escaped names.
34
+ */
35
+ let make = (): ReventlessInfra.Platform.messagingSender => {
36
+ emailSender: Pulumi.Input.make(
37
+ Reventless.Messaging.fromHeader(~displayName=Some(senderName), ~address=senderAddress),
38
+ ),
39
+ }
@@ -0,0 +1,20 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Messaging$Reventless from "@reventlessdev/reventless-spec/src/semantic/Messaging.res.mjs";
4
+
5
+ let senderName = "Online Shop (local)";
6
+
7
+ let senderAddress = "notifications@online-shop.test";
8
+
9
+ function make() {
10
+ return {
11
+ emailSender: Messaging$Reventless.fromHeader(senderName, senderAddress)
12
+ };
13
+ }
14
+
15
+ export {
16
+ senderName,
17
+ senderAddress,
18
+ make,
19
+ }
20
+ /* No side effect */
@@ -33,7 +33,9 @@ function Make(Bus) {
33
33
  make: QueryDbStorage.make
34
34
  })({
35
35
  make: QueryDbResolvers.make
36
- })(EventCollectorChannel)(EventCollectorRuntimeBuilder)(Api)(LocalCapabilities$ReventlessLocal);
36
+ })(EventCollectorChannel)(EventCollectorRuntimeBuilder)(Api)({
37
+ capabilities: LocalCapabilities$ReventlessLocal.capabilities
38
+ });
37
39
  let Make$1 = Spec => (Translation => {
38
40
  let Inner = CoreMaker.Make(Spec)(Translation);
39
41
  return {