@reventlessdev/reventless-core 3.0.0-alpha.251 → 3.0.0-alpha.252

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 (66) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +7 -7
  3. package/src/admin/Platform_Admin_Structure.res +4 -1
  4. package/src/admin/Platform_Admin_Structure.res.mjs +4 -2
  5. package/src/admin/Platform_BakedManifest.res.mjs +3 -1
  6. package/src/admin/Platform_PluginStructuresApi.res +43 -7
  7. package/src/admin/Platform_PluginStructuresApi.res.mjs +47 -1
  8. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +8 -0
  9. package/src/components/Api/ApiNoApiHelpers.res +37 -3
  10. package/src/components/Api/ApiNoApiHelpers.res.mjs +29 -2
  11. package/src/components/AutomationSlice/AutomationSlice_Callback.res +4 -4
  12. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +3 -3
  13. package/src/plugin/api/PluginBaseFragment.res +3 -3
  14. package/src/plugin/component/Plugin_Structure.res +175 -22
  15. package/src/plugin/component/Plugin_Structure.res.mjs +89 -17
  16. package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +3 -1
  17. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +3 -1
  18. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +3 -1
  19. package/src/plugin/lifecycle/PluginBehavior.res +11 -11
  20. package/src/plugin/lifecycle/PluginBehavior.res.mjs +11 -11
  21. package/src/plugin/lifecycle/PluginSpec.res +11 -3
  22. package/src/plugin/lifecycle/PluginSpec.res.mjs +11 -3
  23. package/tests/admin/AdminApiSchemaDriftTest.res +27 -1
  24. package/tests/admin/AdminApiSchemaDriftTest.res.mjs +22 -2
  25. package/tests/admin/Platform_Admin_StructureTest.res +8 -3
  26. package/tests/admin/Platform_Admin_StructureTest.res.mjs +8 -2
  27. package/tests/admin/Platform_BakedManifestTest.res +2 -0
  28. package/tests/admin/Platform_BakedManifestTest.res.mjs +3 -1
  29. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +4 -0
  30. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +21 -7
  31. package/tests/admin/Platform_PluginStructuresApiTest.res +16 -0
  32. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +17 -3
  33. package/tests/aggregate/AggregateCacheTest.res.mjs +15 -3
  34. package/tests/aggregate/AggregateConflictTest.res.mjs +15 -3
  35. package/tests/aggregate/AggregateFixtures.res.mjs +15 -3
  36. package/tests/aggregate/AggregateSnapshotTest.res.mjs +21 -5
  37. package/tests/api/ApiNoApiTest.res +46 -0
  38. package/tests/api/ApiNoApiTest.res.mjs +49 -1
  39. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +27 -3
  40. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +12 -2
  41. package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +2 -0
  42. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +12 -2
  43. package/tests/dcb/DcbFixtures.res.mjs +8 -0
  44. package/tests/dcb/DcbStateChangeSliceTest.res.mjs +18 -0
  45. package/tests/eventmapper/EventMapperFixtures.res.mjs +15 -3
  46. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +9 -1
  47. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +9 -1
  48. package/tests/message/MessageTest.res +2 -0
  49. package/tests/message/MessageTest.res.mjs +3 -1
  50. package/tests/plugin/PluginStructureAccessTest.res.mjs +2 -0
  51. package/tests/plugin/PluginStructureTest.res +185 -1
  52. package/tests/plugin/PluginStructureTest.res.mjs +246 -3
  53. package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +8 -0
  54. package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +8 -0
  55. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +8 -0
  56. package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +8 -0
  57. package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +8 -0
  58. package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +8 -0
  59. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +8 -0
  60. package/tests/plugin/StateChangeSlice/PsTransitionSwitch.res +59 -0
  61. package/tests/plugin/StateChangeSlice/PsTransitionSwitch.res.mjs +115 -0
  62. package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +8 -0
  63. package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +8 -0
  64. package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +8 -0
  65. package/tests/plugin/pluginDefinitionRequiredScalars.txt +18 -0
  66. package/tests/util/CommandPublisherTest.res.mjs +9 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
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.252 (2026-09-01)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **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))
11
+ * **automation:** a mapping is handed the envelope's id ([9ee482c](https://github.com/ReventlessDev/reventless-core/commit/9ee482c5ad0aae09efd1259eeb7f39b781867b91))
12
+ * **capabilities:** a plugin's geocoding need was declared nowhere and failed silently ([67917dd](https://github.com/ReventlessDev/reventless-core/commit/67917dd504b43fa78b7c6a51644c9eae656b7f6b))
13
+ ### Features
14
+
15
+ * **plugin:** refuse two DCB slices that declare one command name ([8c6ec39](https://github.com/ReventlessDev/reventless-core/commit/8c6ec39cb3c6768446f46d0fc6dc04c256e2e9d9))
16
+ * **plugin:** the admin lifecycle commands name their argument ([ea552ec](https://github.com/ReventlessDev/reventless-core/commit/ea552ec64e5db0b3dc465c0d9a80cac89627f727))
17
+ * **spec:** a command declares its lifecycle edge as a value ([40eee9f](https://github.com/ReventlessDev/reventless-core/commit/40eee9f7723dc05e418be680528f01967d074da4))
18
+ * **spec:** a graft leaves a trace the deployed plugin can read ([c08ff6c](https://github.com/ReventlessDev/reventless-core/commit/c08ff6c0f6177d58603e7ae1e5cec392d9bac16a))
19
+
20
+
6
21
  # 3.0.0-alpha.251 (2026-08-27)
7
22
 
8
23
  * 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-core",
3
- "version": "3.0.0-alpha.251",
3
+ "version": "3.0.0-alpha.252",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -28,22 +28,22 @@
28
28
  "dependencies": {
29
29
  "sury": "11.0.0-rc.2",
30
30
  "uuid": "^13.0.0",
31
+ "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.8",
31
32
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
32
- "@reventlessdev/rescript-node": "2.0.0-alpha.8",
33
33
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
34
34
  "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
35
- "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.8",
36
- "@reventlessdev/rescript-ssh2": "2.0.0-alpha.8",
37
35
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
36
+ "@reventlessdev/rescript-node": "2.0.0-alpha.8",
37
+ "@reventlessdev/rescript-ssh2": "2.0.0-alpha.8",
38
38
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
39
- "@reventlessdev/reventless-infra": "3.0.0-alpha.152",
39
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.153",
40
40
  "@reventlessdev/reventless-interop": "3.0.0-alpha.34",
41
- "@reventlessdev/reventless-spec": "3.0.0-alpha.124"
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.125"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "12.3.0",
45
45
  "sury-ppx": "11.0.0-rc.2",
46
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.74"
46
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.75"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "rescript": "12.3.0"
@@ -24,7 +24,7 @@ let commandSchema = PluginSpec.commandSchema->S.castToUnknown
24
24
  // Every variant of `PluginSpec.command`, walked by the same `extractCommandDefs`
25
25
  // an ordinary aggregate goes through. The hand-written alternative described the
26
26
  // arguments with a local `{id}` schema while the generated SDL declared
27
- // `Platform_Plugin_Activate(_0: String!, id: ID!)`, so AutoUI built a mutation
27
+ // `Platform_Plugin_Activate(id: ID!, version: String!)`, so AutoUI built a mutation
28
28
  // missing a required argument and every admin row action was rejected before it
29
29
  // reached the aggregate. Deriving from `commandSchema` — the same schema the SDL
30
30
  // is generated from — cannot reproduce that class of fault.
@@ -38,6 +38,7 @@ let pluginCommands: array<commandDef> = Plugin_Structure.extractCommandDefs(
38
38
  ~mutationFieldFor=variantName =>
39
39
  Api_Naming.adminField(~name=PluginSpec.name ++ "_" ++ variantName),
40
40
  ~commandAuthorization=PluginSpec.commandAuthorization->Obj.magic,
41
+ ~commandTransition=PluginSpec.commandTransition->Obj.magic,
41
42
  commandSchema,
42
43
  )
43
44
 
@@ -76,6 +77,8 @@ let structure: pluginStructure = {
76
77
  extensionPoints: None,
77
78
  requiredStores: None,
78
79
  requiredStoreDeclarations: None,
80
+ requiredCapabilities: None,
81
+ traitDeclarations: None,
79
82
  }
80
83
 
81
84
  /** The definition the platform's own EventCollector ships as its
@@ -7,7 +7,7 @@ import * as PluginsReadModelSpec$ReventlessCore from "../plugin/lifecycle/Plugin
7
7
 
8
8
  let pluginId = "Platform";
9
9
 
10
- let pluginCommands = Plugin_Structure$ReventlessCore.extractCommandDefs(true, variantName => Api_Naming$ReventlessCore.adminField(PluginSpec$ReventlessCore.name + "_" + variantName), PluginSpec$ReventlessCore.commandAuthorization, PluginSpec$ReventlessCore.commandSchema);
10
+ let pluginCommands = Plugin_Structure$ReventlessCore.extractCommandDefs(true, variantName => Api_Naming$ReventlessCore.adminField(PluginSpec$ReventlessCore.name + "_" + variantName), PluginSpec$ReventlessCore.commandAuthorization, PluginSpec$ReventlessCore.commandTransition, PluginSpec$ReventlessCore.commandSchema);
11
11
 
12
12
  let pluginAggregate_producedEventTypes = [];
13
13
 
@@ -60,7 +60,9 @@ let structure = {
60
60
  extensions: structure_extensions,
61
61
  extensionPoints: undefined,
62
62
  requiredStores: undefined,
63
- requiredStoreDeclarations: undefined
63
+ requiredStoreDeclarations: undefined,
64
+ requiredCapabilities: undefined,
65
+ traitDeclarations: undefined
64
66
  };
65
67
 
66
68
  let internalPluginDefinition = {
@@ -134,7 +134,9 @@ function curateStructure(pluginId, def, sel) {
134
134
  extensions: def.extensions,
135
135
  extensionPoints: def.extensionPoints,
136
136
  requiredStores: def.requiredStores,
137
- requiredStoreDeclarations: def.requiredStoreDeclarations
137
+ requiredStoreDeclarations: def.requiredStoreDeclarations,
138
+ requiredCapabilities: def.requiredCapabilities,
139
+ traitDeclarations: def.traitDeclarations
138
140
  };
139
141
  });
140
142
  }
@@ -36,17 +36,35 @@ let sdlTypes: array<string> = [
36
36
  // notification-only (`command = unit`) extension point — see `extensionPointDef`.
37
37
  `type Platform_ExtensionPointDef {\n name: String!\n delegateNames: [String!]!\n sourceEventTypes: [String!]!\n commandTypes: [String!]\n publishedEvents: [Platform_PublishedEventDef!]\n acceptedCommands: [Platform_AcceptedCommandDef!]\n}`,
38
38
  `type Platform_RequiredStoreDeclaration {\n store: String!\n component: String!\n field: String!\n annotation: String\n}`,
39
- // `extensionPoints` / `requiredStores` / `requiredStoreDeclarations` are nullable
40
- // lists, not `[T!]!`: all three are optional on `pluginStructure` so that plugin
41
- // definitions persisted before the field existed still decode. A structure written
42
- // by an older deploy sends null, and null is the honest answer — an empty list
43
- // would claim the plugin has no extension points when the truth is that the
44
- // deployment cannot say.
45
- `type Platform_PluginStructureEntry {\n pluginId: String!\n readModels: [Platform_ReadSideDef!]!\n stateViewSlices: [Platform_ReadSideDef!]!\n stateChangeSlices: [Platform_WriteSideDef!]!\n aggregates: [Platform_WriteSideDef!]!\n automationSlices: [Platform_AutomationSliceDef!]!\n outboundTranslationSlices: [Platform_OutboundTranslationSliceDef!]!\n inboundTranslationSlices: [Platform_InboundTranslationSliceDef!]!\n extensions: [Platform_ExtensionDef!]!\n extensionPoints: [Platform_ExtensionPointDef!]\n requiredStores: [String!]\n requiredStoreDeclarations: [Platform_RequiredStoreDeclaration!]\n}`,
39
+ // The capability a component declares its translate reaches for. No `field`:
40
+ // unlike a store, this need is not expressible as an annotation on one.
41
+ `type Platform_RequiredCapabilityDeclaration {\n capability: String!\n component: String!\n}`,
42
+ // One graft's provenance. `posture` is a String rather than an enum for the
43
+ // reason the capability above is: a plugin built against a newer framework,
44
+ // naming a posture this schema has never heard of, still decodes.
45
+ //
46
+ // It records ORIGIN, never behaviour — a grafted file is the host's to edit
47
+ // afterwards — so a reader must not paint a declared graft as a verified one.
48
+ `type Platform_TraitDeclaration {\n trait: String!\n version: String!\n posture: String!\n component: String!\n}`,
49
+ // `extensionPoints` / `requiredStores` / `requiredStoreDeclarations` /
50
+ // `requiredCapabilities` / `traitDeclarations` are nullable lists, not `[T!]!`: all five are optional on
51
+ // `pluginStructure` so that plugin definitions persisted before the field existed
52
+ // still decode. A structure written by an older deploy sends null, and null is the
53
+ // honest answer — an empty list would claim the plugin has no extension points when
54
+ // the truth is that the deployment cannot say.
55
+ `type Platform_PluginStructureEntry {\n pluginId: String!\n readModels: [Platform_ReadSideDef!]!\n stateViewSlices: [Platform_ReadSideDef!]!\n stateChangeSlices: [Platform_WriteSideDef!]!\n aggregates: [Platform_WriteSideDef!]!\n automationSlices: [Platform_AutomationSliceDef!]!\n outboundTranslationSlices: [Platform_OutboundTranslationSliceDef!]!\n inboundTranslationSlices: [Platform_InboundTranslationSliceDef!]!\n extensions: [Platform_ExtensionDef!]!\n extensionPoints: [Platform_ExtensionPointDef!]\n requiredStores: [String!]\n requiredStoreDeclarations: [Platform_RequiredStoreDeclaration!]\n requiredCapabilities: [Platform_RequiredCapabilityDeclaration!]\n traitDeclarations: [Platform_TraitDeclaration!]\n}`,
46
56
  ]
47
57
 
48
58
  let sdlQueryField: string = ` Platform_PluginStructures: [Platform_PluginStructureEntry!]!`
49
59
 
60
+ let encodeTraitDeclaration = (d: traitDeclaration): JSON.t =>
61
+ Dict.fromArray([
62
+ ("trait", JSON.Encode.string(d.trait)),
63
+ ("version", JSON.Encode.string(d.version)),
64
+ ("posture", JSON.Encode.string(d.posture)),
65
+ ("component", JSON.Encode.string(d.component)),
66
+ ])->JSON.Encode.object
67
+
50
68
  let encodeExtensionPointDef = (e: extensionPointDef): JSON.t =>
51
69
  Dict.fromArray([
52
70
  ("name", JSON.Encode.string(e.name)),
@@ -98,6 +116,12 @@ let encodeRequiredStoreDeclaration = (d: requiredStoreDeclaration): JSON.t =>
98
116
  ("annotation", d.annotation->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
99
117
  ])->JSON.Encode.object
100
118
 
119
+ let encodeRequiredCapabilityDeclaration = (d: requiredCapabilityDeclaration): JSON.t =>
120
+ Dict.fromArray([
121
+ ("capability", JSON.Encode.string(d.capability)),
122
+ ("component", JSON.Encode.string(d.component)),
123
+ ])->JSON.Encode.object
124
+
101
125
  // Deliberately NOT filtered by `isPublicQueryable`: that filter is the AutoUI
102
126
  // contract, and applying it here would silently drop Internal components from the
103
127
  // graph a developer tool draws.
@@ -165,4 +189,16 @@ let encodePluginStructureEntry = (~pluginId: string, def: pluginStructure): JSON
165
189
  ds->Array.map(encodeRequiredStoreDeclaration)->JSON.Encode.array
166
190
  ),
167
191
  ),
192
+ (
193
+ "requiredCapabilities",
194
+ def.requiredCapabilities->Option.mapOr(JSON.Encode.null, ds =>
195
+ ds->Array.map(encodeRequiredCapabilityDeclaration)->JSON.Encode.array
196
+ ),
197
+ ),
198
+ (
199
+ "traitDeclarations",
200
+ def.traitDeclarations->Option.mapOr(JSON.Encode.null, ds =>
201
+ ds->Array.map(encodeTraitDeclaration)->JSON.Encode.array
202
+ ),
203
+ ),
168
204
  ])->JSON.Encode.object
@@ -9,9 +9,32 @@ let sdlTypes = [
9
9
  `type Platform_AcceptedCommandDef {\n name: String!\n toCommandTypes: [String!]!\n}`,
10
10
  `type Platform_ExtensionPointDef {\n name: String!\n delegateNames: [String!]!\n sourceEventTypes: [String!]!\n commandTypes: [String!]\n publishedEvents: [Platform_PublishedEventDef!]\n acceptedCommands: [Platform_AcceptedCommandDef!]\n}`,
11
11
  `type Platform_RequiredStoreDeclaration {\n store: String!\n component: String!\n field: String!\n annotation: String\n}`,
12
- `type Platform_PluginStructureEntry {\n pluginId: String!\n readModels: [Platform_ReadSideDef!]!\n stateViewSlices: [Platform_ReadSideDef!]!\n stateChangeSlices: [Platform_WriteSideDef!]!\n aggregates: [Platform_WriteSideDef!]!\n automationSlices: [Platform_AutomationSliceDef!]!\n outboundTranslationSlices: [Platform_OutboundTranslationSliceDef!]!\n inboundTranslationSlices: [Platform_InboundTranslationSliceDef!]!\n extensions: [Platform_ExtensionDef!]!\n extensionPoints: [Platform_ExtensionPointDef!]\n requiredStores: [String!]\n requiredStoreDeclarations: [Platform_RequiredStoreDeclaration!]\n}`
12
+ `type Platform_RequiredCapabilityDeclaration {\n capability: String!\n component: String!\n}`,
13
+ `type Platform_TraitDeclaration {\n trait: String!\n version: String!\n posture: String!\n component: String!\n}`,
14
+ `type Platform_PluginStructureEntry {\n pluginId: String!\n readModels: [Platform_ReadSideDef!]!\n stateViewSlices: [Platform_ReadSideDef!]!\n stateChangeSlices: [Platform_WriteSideDef!]!\n aggregates: [Platform_WriteSideDef!]!\n automationSlices: [Platform_AutomationSliceDef!]!\n outboundTranslationSlices: [Platform_OutboundTranslationSliceDef!]!\n inboundTranslationSlices: [Platform_InboundTranslationSliceDef!]!\n extensions: [Platform_ExtensionDef!]!\n extensionPoints: [Platform_ExtensionPointDef!]\n requiredStores: [String!]\n requiredStoreDeclarations: [Platform_RequiredStoreDeclaration!]\n requiredCapabilities: [Platform_RequiredCapabilityDeclaration!]\n traitDeclarations: [Platform_TraitDeclaration!]\n}`
13
15
  ];
14
16
 
17
+ function encodeTraitDeclaration(d) {
18
+ return Object.fromEntries([
19
+ [
20
+ "trait",
21
+ d.trait
22
+ ],
23
+ [
24
+ "version",
25
+ d.version
26
+ ],
27
+ [
28
+ "posture",
29
+ d.posture
30
+ ],
31
+ [
32
+ "component",
33
+ d.component
34
+ ]
35
+ ]);
36
+ }
37
+
15
38
  function encodeExtensionPointDef(e) {
16
39
  return Object.fromEntries([
17
40
  [
@@ -80,6 +103,19 @@ function encodeRequiredStoreDeclaration(d) {
80
103
  ]);
81
104
  }
82
105
 
106
+ function encodeRequiredCapabilityDeclaration(d) {
107
+ return Object.fromEntries([
108
+ [
109
+ "capability",
110
+ d.capability
111
+ ],
112
+ [
113
+ "component",
114
+ d.component
115
+ ]
116
+ ]);
117
+ }
118
+
83
119
  function encodePluginStructureEntry(pluginId, def) {
84
120
  return Object.fromEntries([
85
121
  [
@@ -129,6 +165,14 @@ function encodePluginStructureEntry(pluginId, def) {
129
165
  [
130
166
  "requiredStoreDeclarations",
131
167
  Stdlib_Option.mapOr(def.requiredStoreDeclarations, null, ds => ds.map(encodeRequiredStoreDeclaration))
168
+ ],
169
+ [
170
+ "requiredCapabilities",
171
+ Stdlib_Option.mapOr(def.requiredCapabilities, null, ds => ds.map(encodeRequiredCapabilityDeclaration))
172
+ ],
173
+ [
174
+ "traitDeclarations",
175
+ Stdlib_Option.mapOr(def.traitDeclarations, null, ds => ds.map(encodeTraitDeclaration))
132
176
  ]
133
177
  ]);
134
178
  }
@@ -138,8 +182,10 @@ let sdlQueryField = ` Platform_PluginStructures: [Platform_PluginStructureEntry
138
182
  export {
139
183
  sdlTypes,
140
184
  sdlQueryField,
185
+ encodeTraitDeclaration,
141
186
  encodeExtensionPointDef,
142
187
  encodeRequiredStoreDeclaration,
188
+ encodeRequiredCapabilityDeclaration,
143
189
  encodePluginStructureEntry,
144
190
  }
145
191
  /* No side effect */
@@ -46,6 +46,12 @@ function commandAuthorization(param) {
46
46
  return "AllowAuthenticated";
47
47
  }
48
48
 
49
+ function commandTransition(param) {
50
+ return "Unrestricted";
51
+ }
52
+
53
+ let traits = [];
54
+
49
55
  let name = "UiFragmentRegistry";
50
56
 
51
57
  let Id;
@@ -66,5 +72,7 @@ export {
66
72
  moduleUrl,
67
73
  commandAuthorization,
68
74
  readConsistency,
75
+ commandTransition,
76
+ traits,
69
77
  }
70
78
  /* commandSchema Not a pure module */
@@ -3,11 +3,45 @@ open ReventlessInfra.Api
3
3
  let isNoApi = (commandSchema: S.t<unknown>): bool =>
4
4
  commandSchema->S.Metadata.get(~id=noApiId)->Option.getOr(false)
5
5
 
6
- let getExcludedVariants = (commandSchema: S.t<unknown>): option<Set.t<string>> =>
7
- commandSchema->S.Metadata.get(~id=noApiVariantsId)
6
+ // Variants excluded by a mark on the member itself. This is the half that
7
+ // survives a spread: splicing copies members, not the metadata of the union they
8
+ // came from, so a command a trait marked internal is only still recognisable here.
9
+ let excludedByMembers = (commandSchema: S.t<unknown>): array<string> =>
10
+ switch commandSchema {
11
+ | AnyOf({anyOf}) =>
12
+ anyOf->Array.filterMap(member =>
13
+ member->S.Metadata.get(~id=noApiVariantId)->Option.getOr(false)
14
+ ? variantNameOf(member)
15
+ : None
16
+ )
17
+ | _ => []
18
+ }
19
+
20
+ /**
21
+ Every variant excluded from the API, from both records of the fact.
22
+
23
+ The union's own set covers variants the schema's author declared; the members'
24
+ marks cover variants spliced in from another type, whose author's declaration is
25
+ in a file this one never mentions. A reader that consulted only the first
26
+ published someone else's internal commands.
27
+
28
+ `None` still means "nothing is excluded", so a caller's existing branch is
29
+ unchanged.
30
+ */
31
+ let getExcludedVariants = (commandSchema: S.t<unknown>): option<Set.t<string>> => {
32
+ let fromMembers = excludedByMembers(commandSchema)
33
+ switch (commandSchema->S.Metadata.get(~id=noApiVariantsId), fromMembers) {
34
+ | (None, []) => None
35
+ | (None, members) => Some(Set.fromArray(members))
36
+ | (Some(declared), []) => Some(declared)
37
+ | (Some(declared), members) =>
38
+ let all = declared->Set.toArray->Array.concat(members)
39
+ Some(Set.fromArray(all))
40
+ }
41
+ }
8
42
 
9
43
  let filterNoApiVariants = (fieldNames: array<string>, commandSchema: S.t<unknown>): array<string> =>
10
- switch commandSchema->S.Metadata.get(~id=noApiVariantsId) {
44
+ switch getExcludedVariants(commandSchema) {
11
45
  | None => fieldNames
12
46
  | Some(excluded) => fieldNames->Array.filter(name => !(excluded->Set.has(name)))
13
47
  }
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Sury from "sury";
4
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
6
  import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
6
7
 
@@ -8,12 +9,37 @@ function isNoApi(commandSchema) {
8
9
  return Stdlib_Option.getOr(Sury.$Metadata_get(commandSchema, Api$ReventlessInfra.noApiId), false);
9
10
  }
10
11
 
12
+ function excludedByMembers(commandSchema) {
13
+ if (commandSchema.type === "anyOf") {
14
+ return Stdlib_Array.filterMap(commandSchema.anyOf, member => {
15
+ if (Stdlib_Option.getOr(Sury.$Metadata_get(member, Api$ReventlessInfra.noApiVariantId), false)) {
16
+ return Api$ReventlessInfra.variantNameOf(member);
17
+ }
18
+ });
19
+ } else {
20
+ return [];
21
+ }
22
+ }
23
+
11
24
  function getExcludedVariants(commandSchema) {
12
- return Sury.$Metadata_get(commandSchema, Api$ReventlessInfra.noApiVariantsId);
25
+ let fromMembers = excludedByMembers(commandSchema);
26
+ let match = Sury.$Metadata_get(commandSchema, Api$ReventlessInfra.noApiVariantsId);
27
+ if (match === undefined) {
28
+ if (fromMembers.length !== 0) {
29
+ return new Set(fromMembers);
30
+ } else {
31
+ return;
32
+ }
33
+ }
34
+ if (fromMembers.length === 0) {
35
+ return match;
36
+ }
37
+ let all = Array.from(match).concat(fromMembers);
38
+ return new Set(all);
13
39
  }
14
40
 
15
41
  function filterNoApiVariants(fieldNames, commandSchema) {
16
- let excluded = Sury.$Metadata_get(commandSchema, Api$ReventlessInfra.noApiVariantsId);
42
+ let excluded = getExcludedVariants(commandSchema);
17
43
  if (excluded !== undefined) {
18
44
  return fieldNames.filter(name => !excluded.has(name));
19
45
  } else {
@@ -23,6 +49,7 @@ function filterNoApiVariants(fieldNames, commandSchema) {
23
49
 
24
50
  export {
25
51
  isNoApi,
52
+ excludedByMembers,
26
53
  getExcludedVariants,
27
54
  filterNoApiVariants,
28
55
  }
@@ -169,19 +169,19 @@ module Make = (
169
169
  // Per-source erased dispatch — pre-compile decoders once at module init.
170
170
  type dispatch = {
171
171
  sourceName: string,
172
- handle: (JSON.t, Reventless.AutomationSlice.context) => unit,
172
+ handle: (JSON.t, ~sourceId: string, Reventless.AutomationSlice.context) => unit,
173
173
  }
174
174
 
175
175
  let dispatches: array<dispatch> = Automation.mappings->Array.map((
176
176
  module(M: Automation.Mapping),
177
177
  ) => {
178
178
  let decoder = Reventless.DcbDecode.makeDecoder(M.sourceEventSchema)
179
- let handle = (json: JSON.t, ctx: Reventless.AutomationSlice.context) => {
179
+ let handle = (json: JSON.t, ~sourceId: string, ctx: Reventless.AutomationSlice.context) => {
180
180
  let (eventType, dataDict) = json->Message.splitMessage
181
181
  switch decoder.decode(~eventType, ~data=dataDict) {
182
182
  | Some(event) =>
183
183
  // Collect — append new items keyed by ID; first writer wins (idempotent).
184
- M.collect(event, ctx)->Array.forEach(((id, item)) => {
184
+ M.collect(event, ~sourceId, ctx)->Array.forEach(((id, item)) => {
185
185
  switch todoItems->Dict.get(id) {
186
186
  | Some(_) => () // Already exists — skip
187
187
  | None =>
@@ -225,7 +225,7 @@ module Make = (
225
225
  // Multiple mappings can share a sourceName; all matching dispatches run.
226
226
  dispatches->Array.forEach(d => {
227
227
  if d.sourceName == sourceName {
228
- d.handle(eventPayload, ctx)
228
+ d.handle(eventPayload, ~sourceId=context.id, ctx)
229
229
  }
230
230
  })
231
231
  })
@@ -57,14 +57,14 @@ function Make(Spec) {
57
57
  };
58
58
  let dispatches = Automation.mappings.map(M => {
59
59
  let decoder = DcbDecode$Reventless.makeDecoder(M.sourceEventSchema);
60
- let handle = (json, ctx) => {
60
+ let handle = (json, sourceId, ctx) => {
61
61
  let match = Message$ReventlessCore.splitMessage(json);
62
62
  let event = decoder.decode(match[0], match[1]);
63
63
  if (event === undefined) {
64
64
  return;
65
65
  }
66
66
  let event$1 = Primitive_option.valFromOption(event);
67
- M.collect(event$1, ctx).forEach(param => {
67
+ M.collect(event$1, sourceId, ctx).forEach(param => {
68
68
  let id = param[0];
69
69
  let match = todoItems[id];
70
70
  if (match !== undefined) {
@@ -105,7 +105,7 @@ function Make(Spec) {
105
105
  let eventPayload = dict !== undefined ? Stdlib_Option.getOr(dict["event"], json) : json;
106
106
  dispatches.forEach(d => {
107
107
  if (d.sourceName === sourceName) {
108
- return d.handle(eventPayload, ctx);
108
+ return d.handle(eventPayload, context.id, ctx);
109
109
  }
110
110
  });
111
111
  });
@@ -50,9 +50,9 @@ let queryEntries: array<querySchemaEntry> = [
50
50
  // Plugin aggregate admin mutations — derived statically from PluginSpec.command,
51
51
  // so the generated SDL contains one field per non-`@noApi` variant carrying that
52
52
  // variant's command fields as args. Since the admin commands are name-keyed and
53
- // version-scoped (`Activate(version)` / `Deactivate(version)` / `Retire(version)`),
54
- // the fields are e.g. `Platform_Plugin_Activate(id: ID!, _0: String!): CommandResult!`
55
- // (`id` = plugin name = aggregate instance; `_0` = target version). `Retire` is
53
+ // version-scoped (`Activate({version})` and the same for `Deactivate`/`Retire`),
54
+ // the fields are e.g. `Platform_Plugin_Activate(id: ID!, version: String!): CommandResult!`
55
+ // (`id` = plugin name = aggregate instance; `version` = target version). `Retire` is
56
56
  // API-exposed (manual archive, §2.5). Internal-protocol variants (`Heartbeat`,
57
57
  // `Connect`, `Disconnect`, `ReportIncompatibility`) carry `@noApi` and are filtered
58
58
  // out by `ApiNoApiHelpers.filterNoApiVariants`. The same `commandSchema`-driven