@reventlessdev/reventless-infra 3.0.0-alpha.152 → 3.0.0-alpha.154

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,27 @@
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.154 (2026-09-01)
7
+
8
+ **Note:** Version bump only for package @reventlessdev/reventless-infra
9
+
10
+
11
+
12
+
13
+
14
+ # 3.0.0-alpha.153 (2026-09-01)
15
+
16
+ ### Bug Fixes
17
+
18
+ * **api:** [@no](https://github.com/no)Api was lost when a command variant was spliced into another type ([00403fb](https://github.com/ReventlessDev/reventless-core/commit/00403fb4b8873a68a69558829e598339a5012833))
19
+ * **capabilities:** a plugin's geocoding need was declared nowhere and failed silently ([67917dd](https://github.com/ReventlessDev/reventless-core/commit/67917dd504b43fa78b7c6a51644c9eae656b7f6b))
20
+ ### Features
21
+
22
+ * **capabilities:** a plugin can send a message without naming a provider ([b001a1e](https://github.com/ReventlessDev/reventless-core/commit/b001a1e9361a0f4d0affe228cb4c08c38a5a995e))
23
+ * **spec:** a command declares its lifecycle edge as a value ([40eee9f](https://github.com/ReventlessDev/reventless-core/commit/40eee9f7723dc05e418be680528f01967d074da4))
24
+ * **spec:** a graft leaves a trace the deployed plugin can read ([c08ff6c](https://github.com/ReventlessDev/reventless-core/commit/c08ff6c0f6177d58603e7ae1e5cec392d9bac16a))
25
+
26
+
6
27
  # 3.0.0-alpha.152 (2026-08-27)
7
28
 
8
29
  * feat(spec)!: reflect the command direction across a port, both halves ([f2fe258](https://github.com/ReventlessDev/reventless-core/commit/f2fe258d195b74f4a61488edee305665341020ea))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-infra",
3
- "version": "3.0.0-alpha.152",
3
+ "version": "3.0.0-alpha.154",
4
4
  "description": "Infrastructure types for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -17,9 +17,9 @@
17
17
  "sury-ppx": "11.0.0-rc.2",
18
18
  "uuid": "^13.0.0",
19
19
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
20
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
21
20
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
22
- "@reventlessdev/reventless-spec": "3.0.0-alpha.124"
21
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
22
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.126"
23
23
  },
24
24
  "devDependencies": {
25
25
  "rescript": "12.3.0",
@@ -7,37 +7,76 @@ let noApiId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(~namespace="api", ~name=
7
7
  let noApiVariantsId: S.Metadata.Id.t<Set.t<string>> =
8
8
  S.Metadata.Id.make(~namespace="api", ~name="noApiVariants")
9
9
 
10
- /** Internal sury metadata ID storing per-variant allowed-state lists for AutoUI command filtering.
11
- Maps variant name set of status values under which the command is meaningful. */
12
- let allowedStatesId: S.Metadata.Id.t<dict<array<string>>> =
13
- S.Metadata.Id.make(~namespace="api", ~name="allowedStates")
10
+ /** The same exclusion, carried by the excluded *member* rather than by the union
11
+ it sits in so it survives a variant spread.
14
12
 
15
- /** Internal sury metadata ID storing per-variant target-state values for AutoUI board
16
- transitions. Maps variant name the single status the command's handler writes. */
17
- let targetStateId: S.Metadata.Id.t<dict<string>> =
18
- S.Metadata.Id.make(~namespace="api", ~name="targetState")
13
+ A spread splices members: the generated code concatenates the source union's
14
+ `anyOf` into the target's. Metadata on the source *union* is not part of that,
15
+ so an exclusion recorded only there vanishes on the way into the host, and the
16
+ host publishes a command its author marked internal. A mark on the member
17
+ travels, because the member is what is spliced — the host's union holds the
18
+ same schema object. */
19
+ let noApiVariantId: S.Metadata.Id.t<bool> =
20
+ S.Metadata.Id.make(~namespace="api", ~name="noApiVariant")
19
21
 
20
22
  /** PPX helper: attaches the noApi flag to a command schema. Called by generated code. */
21
23
  let markNoApi = (schema: S.t<'a>): S.t<'a> =>
22
24
  schema->S.Metadata.set(~id=noApiId, true)
23
25
 
24
- /** PPX helper: attaches variant-level exclusions to a command schema. Called by generated code. */
25
- let markNoApiVariants = (schema: S.t<'a>, variants: array<string>): S.t<'a> =>
26
- schema->S.Metadata.set(~id=noApiVariantsId, Set.fromArray(variants))
27
-
28
- /** PPX helper: attaches per-variant allowedStates to a command schema. Called by generated code
29
- emitted from the from-set of an @transition attribute. Each entry is (variantName, allowedStateNames). */
30
- let markAllowedStates = (
31
- schema: S.t<'a>,
32
- entries: array<(string, array<string>)>,
33
- ): S.t<'a> => schema->S.Metadata.set(~id=allowedStatesId, Dict.fromArray(entries))
34
-
35
- /** PPX helper: attaches per-variant targetState to a command schema. Called by generated code
36
- emitted from the target of an @transition attribute. Each entry is (variantName, targetStateName). */
37
- let markTargetState = (
38
- schema: S.t<'a>,
39
- entries: array<(string, string)>,
40
- ): S.t<'a> => schema->S.Metadata.set(~id=targetStateId, Dict.fromArray(entries))
26
+ /** The constructor a union member stands for: the `TAG` literal of a
27
+ payload-bearing variant, or the bare literal a payload-less one compiles to. */
28
+ let variantNameOf = (member: S.t<unknown>): option<string> =>
29
+ switch member {
30
+ | Object({properties}) =>
31
+ properties
32
+ ->Dict.get("TAG")
33
+ ->Option.flatMap(tag =>
34
+ switch tag {
35
+ | String({const: ?Some(name)}) => Some(name)
36
+ | _ => None
37
+ }
38
+ )
39
+ | String({const: ?Some(name)}) => Some(name)
40
+ | _ => None
41
+ }
42
+
43
+ /**
44
+ PPX helper: attaches variant-level exclusions to a command schema. Called by
45
+ generated code.
46
+
47
+ Recorded twice, on purpose. The set on the union is what every reader has always
48
+ consulted; the per-member mark is what survives being spliced into another type.
49
+ Writing only the second would break any reader holding a union whose members it
50
+ did not build, and writing only the first is the defect this exists to close.
51
+
52
+ The union is rebuilt from the marked members because sury metadata is immutable —
53
+ setting it returns a new schema, so the members the original union holds would
54
+ otherwise still be the unmarked ones. Sound here because the PPX calls this
55
+ *first*, directly on the freshly built union, before any other metadata is
56
+ attached: there is nothing on the parent yet to lose. A schema that is not a
57
+ union, or that names no excluded variant, is left structurally untouched.
58
+ */
59
+ let markNoApiVariants = (schema: S.t<'a>, variants: array<string>): S.t<'a> => {
60
+ let excluded = Set.fromArray(variants)
61
+ let marked = switch (variants, schema->S.castToUnknown) {
62
+ | ([], _) => schema
63
+ | (_, AnyOf({anyOf})) =>
64
+ anyOf
65
+ ->Array.map(member =>
66
+ switch variantNameOf(member) {
67
+ | Some(name) if excluded->Set.has(name) => member->S.Metadata.set(~id=noApiVariantId, true)
68
+ | _ => member
69
+ }
70
+ )
71
+ ->S.union
72
+ // The rebuilt union describes the same values as the one handed in — same
73
+ // members, same order, only metadata added. `S.union` cannot express that in
74
+ // the type, so the equivalence is carried here rather than by the compiler.
75
+ ->(Obj.magic: S.t<unknown> => S.t<'a>)
76
+ | (_, _) => schema
77
+ }
78
+ marked->S.Metadata.set(~id=noApiVariantsId, excluded)
79
+ }
41
80
 
42
81
  /** Type alias for schema fragments (re-exports Plugin.apiSchemaFragment). */
43
82
  type schemaFragment = Reventless.Plugin.apiSchemaFragment
@@ -1,39 +1,61 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Sury from "sury";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
5
 
5
6
  let noApiId = Sury.$Metadata_Id_make("api", "noApi");
6
7
 
7
8
  let noApiVariantsId = Sury.$Metadata_Id_make("api", "noApiVariants");
8
9
 
9
- let allowedStatesId = Sury.$Metadata_Id_make("api", "allowedStates");
10
-
11
- let targetStateId = Sury.$Metadata_Id_make("api", "targetState");
10
+ let noApiVariantId = Sury.$Metadata_Id_make("api", "noApiVariant");
12
11
 
13
12
  function markNoApi(schema) {
14
13
  return Sury.$Metadata_set(schema, noApiId, true);
15
14
  }
16
15
 
17
- function markNoApiVariants(schema, variants) {
18
- return Sury.$Metadata_set(schema, noApiVariantsId, new Set(variants));
16
+ function variantNameOf(member) {
17
+ switch (member.type) {
18
+ case "string" :
19
+ let name = member.const;
20
+ if (name !== undefined) {
21
+ return name;
22
+ } else {
23
+ return;
24
+ }
25
+ case "object" :
26
+ return Stdlib_Option.flatMap(member.properties["TAG"], tag => {
27
+ if (tag.type !== "string") {
28
+ return;
29
+ }
30
+ let name = tag.const;
31
+ if (name !== undefined) {
32
+ return name;
33
+ }
34
+ });
35
+ default:
36
+ return;
37
+ }
19
38
  }
20
39
 
21
- function markAllowedStates(schema, entries) {
22
- return Sury.$Metadata_set(schema, allowedStatesId, Object.fromEntries(entries));
23
- }
24
-
25
- function markTargetState(schema, entries) {
26
- return Sury.$Metadata_set(schema, targetStateId, Object.fromEntries(entries));
40
+ function markNoApiVariants(schema, variants) {
41
+ let excluded = new Set(variants);
42
+ let marked = variants.length !== 0 && schema.type === "anyOf" ? Sury.union(schema.anyOf.map(member => {
43
+ let name = variantNameOf(member);
44
+ if (name !== undefined && excluded.has(name)) {
45
+ return Sury.$Metadata_set(member, noApiVariantId, true);
46
+ } else {
47
+ return member;
48
+ }
49
+ })) : schema;
50
+ return Sury.$Metadata_set(marked, noApiVariantsId, excluded);
27
51
  }
28
52
 
29
53
  export {
30
54
  noApiId,
31
55
  noApiVariantsId,
32
- allowedStatesId,
33
- targetStateId,
56
+ noApiVariantId,
34
57
  markNoApi,
58
+ variantNameOf,
35
59
  markNoApiVariants,
36
- markAllowedStates,
37
- markTargetState,
38
60
  }
39
61
  /* noApiId Not a pure module */
@@ -147,6 +147,9 @@ module NoDelegate = {
147
147
  let moduleUrl: string = %raw(`import.meta.url`)
148
148
  let commandAuthorization = (_: command): Reventless.Authorization.permission =>
149
149
  AllowAuthenticated
150
+ type lifecycleState = unit
151
+ let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> => Unrestricted
152
+ let traits: array<Reventless.Trait.t> = []
150
153
  }
151
154
 
152
155
 
@@ -32,6 +32,12 @@ function commandAuthorization() {
32
32
  return "AllowAuthenticated";
33
33
  }
34
34
 
35
+ function commandTransition() {
36
+ return "Unrestricted";
37
+ }
38
+
39
+ let traits = [];
40
+
35
41
  let NoDelegate = {
36
42
  name: "NoDelegate",
37
43
  Id: Id,
@@ -39,7 +45,9 @@ let NoDelegate = {
39
45
  errorSchema: errorSchema,
40
46
  commandSchema: commandSchema,
41
47
  moduleUrl: moduleUrl,
42
- commandAuthorization: commandAuthorization
48
+ commandAuthorization: commandAuthorization,
49
+ commandTransition: commandTransition,
50
+ traits: traits
43
51
  };
44
52
 
45
53
  function Make(MappingImpl) {
@@ -109,6 +109,7 @@ store, and reaching another plugin's store is the qualified form.
109
109
  type capability =
110
110
  | ObjectStore({plugin: string, store: string})
111
111
  | Geocoding
112
+ | Messaging
112
113
 
113
114
  // A provisioned geocoding place index, as returned by the framework's geocoding
114
115
  // capability helper. A record rather than a bare name so the handle can grow
@@ -181,6 +182,20 @@ type bakedManifest = {
181
182
 
182
183
  type geocoderIndex = {indexName: Pulumi.Input.t<string>}
183
184
 
185
+ /**
186
+ A provisioned messaging sender, as returned by the framework's messaging
187
+ capability helper.
188
+
189
+ `emailSender` is the verified address messages are sent *from* — a provider-side
190
+ identity, not something an application picks per message, which is why it is a
191
+ deploy-time handle rather than a field on `Messaging.message`. Absent when the
192
+ deployment provisions no email channel.
193
+
194
+ The channel set is derived from which senders are present rather than declared
195
+ beside them, so a handle carrying no sender at all cannot claim a channel.
196
+ */
197
+ type messagingSender = {emailSender?: Pulumi.Input.t<string>}
198
+
184
199
  // Options for the shell's map view mode. Written to config.json flat, beside
185
200
  // `viewModes`, because that is where the shell reads them; carried on the arm
186
201
  // here so a style set with the map off is unrepresentable.
@@ -426,6 +441,12 @@ module type T = {
426
441
  // on the platform API — and exports the index name for the unattended slice
427
442
  // path. Unset ⇒ no resolver, no export. In-memory platforms ignore this.
428
443
  geocoderPlaceIndex?: geocoderIndex,
444
+ // Optional messaging sender. When set, the deploy exports the verified
445
+ // sender address for the unattended slice path and grants those Lambdas the
446
+ // right to send from it. No client door — a browser has no business sending
447
+ // from the deployment's identity. Unset ⇒ no export, no grant. In-memory
448
+ // platforms ignore this.
449
+ messagingSender?: messagingSender,
429
450
  // Optional object store for direct-to-store uploads. When set, the deploy
430
451
  // provisions a presign service against it, threads that service's URL into
431
452
  // config.json as `uploadEndpoint`, and serves the store read-only from the
@@ -175,6 +175,14 @@ let requiredStoreDeclarationSchema = Plugin$Reventless.requiredStoreDeclarationS
175
175
 
176
176
  let requiredStoreDeclarationArrayOptionSchema = Plugin$Reventless.requiredStoreDeclarationArrayOptionSchema;
177
177
 
178
+ let requiredCapabilityDeclarationSchema = Plugin$Reventless.requiredCapabilityDeclarationSchema;
179
+
180
+ let requiredCapabilityDeclarationArrayOptionSchema = Plugin$Reventless.requiredCapabilityDeclarationArrayOptionSchema;
181
+
182
+ let traitDeclarationSchema = Plugin$Reventless.traitDeclarationSchema;
183
+
184
+ let traitDeclarationArrayOptionSchema = Plugin$Reventless.traitDeclarationArrayOptionSchema;
185
+
178
186
  let pluginStructureSchema = Plugin$Reventless.pluginStructureSchema;
179
187
 
180
188
  let pluginStructureOffloadSchema = Plugin$Reventless.pluginStructureOffloadSchema;
@@ -225,6 +233,10 @@ export {
225
233
  extensionPointDefArrayOptionSchema,
226
234
  requiredStoreDeclarationSchema,
227
235
  requiredStoreDeclarationArrayOptionSchema,
236
+ requiredCapabilityDeclarationSchema,
237
+ requiredCapabilityDeclarationArrayOptionSchema,
238
+ traitDeclarationSchema,
239
+ traitDeclarationArrayOptionSchema,
228
240
  pluginStructureSchema,
229
241
  pluginStructureOffloadSchema,
230
242
  pluginDefinitionSchema,