@reventlessdev/reventless-core 3.0.0-alpha.211 → 3.0.0-alpha.212

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,16 @@
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.212 (2026-08-04)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **admin:** declare the component fields the resolver already returned ([879fdf3](https://github.com/ReventlessDev/reventless-core/commit/879fdf3e7ac68be23f915db6bdd0fdc68294de5d))
11
+ ### Features
12
+
13
+ * **admin:** serve the complete plugin structure to developer tooling ([e99cda0](https://github.com/ReventlessDev/reventless-core/commit/e99cda02a2f862b36bcfdb018a0fb34bb7426a2b))
14
+
15
+
6
16
  # 3.0.0-alpha.211 (2026-08-04)
7
17
 
8
18
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.211",
3
+ "version": "3.0.0-alpha.212",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -30,12 +30,12 @@
30
30
  "uuid": "^13.0.0",
31
31
  "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.1",
32
32
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
33
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
34
33
  "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
35
34
  "@reventlessdev/rescript-node": "2.0.0-alpha.1",
35
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
36
36
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
37
- "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
38
37
  "@reventlessdev/rescript-ssh2": "2.0.0-alpha.1",
38
+ "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
39
39
  "@reventlessdev/reventless-infra": "3.0.0-alpha.126",
40
40
  "@reventlessdev/reventless-interop": "3.0.0-alpha.30",
41
41
  "@reventlessdev/reventless-spec": "3.0.0-alpha.100"
@@ -136,11 +136,13 @@ let baseFragment = (~cloner: bool) => {
136
136
  ->Array.concat(uiFragmentSubscriptionTypes)
137
137
  ->Array.concat(pluginStatusSubscriptionTypes)
138
138
  ->Array.concat(Platform_ComponentDefinitionsApi.sdlTypes)
139
+ ->Array.concat(Platform_PluginStructuresApi.sdlTypes)
139
140
  ->Array.concat(Platform_UIFragmentsApi.sdlTypes),
140
141
  queries: Array.concat(
141
142
  parts.queries,
142
143
  [
143
144
  Platform_ComponentDefinitionsApi.sdlQueryField,
145
+ Platform_PluginStructuresApi.sdlQueryField,
144
146
  Platform_UIFragmentsApi.sdlQueryField,
145
147
  ],
146
148
  ),
@@ -7,6 +7,7 @@ import * as PluginBaseFragment$ReventlessCore from "../plugin/api/PluginBaseFrag
7
7
  import * as Platform_UIFragmentsApi$ReventlessCore from "./Platform_UIFragmentsApi.res.mjs";
8
8
  import * as GraphQL_FragmentGenerator$ReventlessCore from "../components/Api/GraphQL_FragmentGenerator.res.mjs";
9
9
  import * as Plugin_SubscriptionSchema$ReventlessCore from "../plugin/component/Plugin_SubscriptionSchema.res.mjs";
10
+ import * as Platform_PluginStructuresApi$ReventlessCore from "./Platform_PluginStructuresApi.res.mjs";
10
11
  import * as Platform_ComponentDefinitionsApi$ReventlessCore from "./Platform_ComponentDefinitionsApi.res.mjs";
11
12
 
12
13
  let cloneArgsSchema = S.schema(s => ({
@@ -90,10 +91,11 @@ function baseFragment(cloner) {
90
91
  let parts = GraphQL_Stitcher$ReventlessCore.decode(base);
91
92
  let match = Plugin_SubscriptionSchema$ReventlessCore.sourceCFields(PluginBaseFragment$ReventlessCore.pluginAggregateMutationEntries);
92
93
  return GraphQL_Stitcher$ReventlessCore.encode({
93
- types: parts.types.concat(uiFragmentSubscriptionTypes).concat(pluginStatusSubscriptionTypes).concat(Platform_ComponentDefinitionsApi$ReventlessCore.sdlTypes).concat(Platform_UIFragmentsApi$ReventlessCore.sdlTypes),
94
+ types: parts.types.concat(uiFragmentSubscriptionTypes).concat(pluginStatusSubscriptionTypes).concat(Platform_ComponentDefinitionsApi$ReventlessCore.sdlTypes).concat(Platform_PluginStructuresApi$ReventlessCore.sdlTypes).concat(Platform_UIFragmentsApi$ReventlessCore.sdlTypes),
94
95
  mutations: parts.mutations.concat(uiFragmentMutationFields).concat(pluginStatusMutationFields),
95
96
  queries: parts.queries.concat([
96
97
  Platform_ComponentDefinitionsApi$ReventlessCore.sdlQueryField,
98
+ Platform_PluginStructuresApi$ReventlessCore.sdlQueryField,
97
99
  Platform_UIFragmentsApi$ReventlessCore.sdlQueryField
98
100
  ]),
99
101
  subscriptions: [
@@ -3,18 +3,28 @@
3
3
  // and the AWS adapter (which resolves from the Plugin read model). Keeping both
4
4
  // adapters byte-identical guarantees a single client query string works against
5
5
  // both.
6
+ //
7
+ // The component-level types below are shared with `Platform_PluginStructuresApi`,
8
+ // which serves the same components unfiltered to developer tooling. They are
9
+ // declared here because this is where their encoders live; the two queries differ
10
+ // in WHICH components they return and what the entry carries around them, never in
11
+ // how one component is shaped.
6
12
 
7
13
  open Reventless.Plugin
8
14
 
15
+ // Component-level SDL. Every field a leaf encoder below emits is declared here:
16
+ // a field the resolver returns but the SDL omits is unreachable (GraphQL rejects
17
+ // selecting it), which is how `chapter` and `externalSystem` stayed invisible to
18
+ // consumers long after they were being encoded.
9
19
  let sdlTypes: array<string> = [
10
20
  `type Platform_FieldReference {\n fieldName: String!\n entity: String!\n plugin: String\n}`,
11
21
  `type Platform_CommandDef {\n name: String!\n schema: String!\n level: String!\n aggregateIdField: String\n mutationField: String!\n references: [Platform_FieldReference!]!\n allowedStates: [String!]\n targetState: String\n apiExposed: Boolean\n}`,
12
22
  `type Platform_EventDef {\n name: String!\n schema: String!\n references: [Platform_FieldReference!]!\n}`,
13
- `type Platform_WriteSideDef {\n name: String!\n commands: [Platform_CommandDef!]!\n linkedViews: [String!]!\n consistencyRead: String\n producedEventTypes: [String!]!\n consumedEventTypes: [String!]!\n events: [Platform_EventDef!]!\n}`,
14
- `type Platform_ReadSideDef {\n name: String!\n queryField: String!\n schema: String!\n consumedEventTypes: [String!]!\n linkedWriteSide: [String!]!\n labelField: String!\n searchableFields: [String!]!\n labelFieldSource: String\n statusField: String\n}`,
15
- `type Platform_AutomationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n producedCommandTypes: [String!]!\n}`,
16
- `type Platform_OutboundTranslationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n inboundCommandTypes: [String!]!\n}`,
17
- `type Platform_InboundTranslationSliceDef {\n name: String!\n commandTypes: [String!]!\n}`,
23
+ `type Platform_WriteSideDef {\n name: String!\n commands: [Platform_CommandDef!]!\n linkedViews: [String!]!\n consistencyRead: String\n producedEventTypes: [String!]!\n consumedEventTypes: [String!]!\n events: [Platform_EventDef!]!\n chapter: String\n}`,
24
+ `type Platform_ReadSideDef {\n name: String!\n queryField: String!\n schema: String!\n consumedEventTypes: [String!]!\n linkedWriteSide: [String!]!\n labelField: String!\n searchableFields: [String!]!\n labelFieldSource: String\n statusField: String\n visibility: String\n chapter: String\n}`,
25
+ `type Platform_AutomationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n producedCommandTypes: [String!]!\n targetName: String\n chapter: String\n}`,
26
+ `type Platform_OutboundTranslationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n inboundCommandTypes: [String!]!\n targetName: String\n externalSystem: String\n chapter: String\n}`,
27
+ `type Platform_InboundTranslationSliceDef {\n name: String!\n commandTypes: [String!]!\n targetName: String\n externalSystem: String\n chapter: String\n}`,
18
28
  `type Platform_ExtensionDef {\n name: String!\n delegateNames: [String!]!\n eventTypes: [String!]!\n commandTypes: [String!]!\n}`,
19
29
  `type Platform_ComponentDefinitionEntry {\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}`,
20
30
  ]
@@ -77,6 +87,11 @@ let encodeQueryableDef = (r: queryableDef): JSON.t =>
77
87
  r.labelFieldSource->Option.mapOr(JSON.Encode.null, JSON.Encode.string),
78
88
  ),
79
89
  ("statusField", r.statusField->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
90
+ // Carried rather than only filtered on: a tooling consumer reading the
91
+ // unfiltered structure needs to know WHICH components are Internal, and a
92
+ // consumer of the filtered query reads null here because nothing Internal
93
+ // survives the filter anyway.
94
+ ("visibility", r.visibility->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
80
95
  ("chapter", r.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
81
96
  ])->JSON.Encode.object
82
97
 
@@ -108,6 +123,10 @@ let encodeAutomationSliceDef = (a: automationSliceDef): JSON.t =>
108
123
  ("name", JSON.Encode.string(a.name)),
109
124
  ("consumedEventTypes", encodeStrings(a.consumedEventTypes)),
110
125
  ("producedCommandTypes", encodeStrings(a.producedCommandTypes)),
126
+ // Routing target — the plugin this slice's commands are dispatched to. It is
127
+ // what turns a slice into a cross-plugin edge, so a graph consumer needs it
128
+ // from the structure rather than from the deploy-time inspector sync.
129
+ ("targetName", JSON.Encode.string(a.targetName)),
111
130
  ("chapter", a.chapter->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
112
131
  ])->JSON.Encode.object
113
132
 
@@ -116,6 +135,7 @@ let encodeOutboundTranslationSliceDef = (o: outboundTranslationSliceDef): JSON.t
116
135
  ("name", JSON.Encode.string(o.name)),
117
136
  ("consumedEventTypes", encodeStrings(o.consumedEventTypes)),
118
137
  ("inboundCommandTypes", encodeStrings(o.inboundCommandTypes)),
138
+ ("targetName", o.targetName->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
119
139
  // Foreign system this slice publishes to — lets a deployed-graph consumer draw
120
140
  // the external-system boundary box without workspace access. None → null.
121
141
  ("externalSystem", o.externalSystem->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
@@ -126,6 +146,7 @@ let encodeInboundTranslationSliceDef = (i: inboundTranslationSliceDef): JSON.t =
126
146
  Dict.fromArray([
127
147
  ("name", JSON.Encode.string(i.name)),
128
148
  ("commandTypes", encodeStrings(i.commandTypes)),
149
+ ("targetName", JSON.Encode.string(i.targetName)),
129
150
  // Foreign system this slice receives from — lets a deployed-graph consumer draw
130
151
  // the external-system boundary box without workspace access. None → null.
131
152
  ("externalSystem", i.externalSystem->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
@@ -8,11 +8,11 @@ let sdlTypes = [
8
8
  `type Platform_FieldReference {\n fieldName: String!\n entity: String!\n plugin: String\n}`,
9
9
  `type Platform_CommandDef {\n name: String!\n schema: String!\n level: String!\n aggregateIdField: String\n mutationField: String!\n references: [Platform_FieldReference!]!\n allowedStates: [String!]\n targetState: String\n apiExposed: Boolean\n}`,
10
10
  `type Platform_EventDef {\n name: String!\n schema: String!\n references: [Platform_FieldReference!]!\n}`,
11
- `type Platform_WriteSideDef {\n name: String!\n commands: [Platform_CommandDef!]!\n linkedViews: [String!]!\n consistencyRead: String\n producedEventTypes: [String!]!\n consumedEventTypes: [String!]!\n events: [Platform_EventDef!]!\n}`,
12
- `type Platform_ReadSideDef {\n name: String!\n queryField: String!\n schema: String!\n consumedEventTypes: [String!]!\n linkedWriteSide: [String!]!\n labelField: String!\n searchableFields: [String!]!\n labelFieldSource: String\n statusField: String\n}`,
13
- `type Platform_AutomationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n producedCommandTypes: [String!]!\n}`,
14
- `type Platform_OutboundTranslationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n inboundCommandTypes: [String!]!\n}`,
15
- `type Platform_InboundTranslationSliceDef {\n name: String!\n commandTypes: [String!]!\n}`,
11
+ `type Platform_WriteSideDef {\n name: String!\n commands: [Platform_CommandDef!]!\n linkedViews: [String!]!\n consistencyRead: String\n producedEventTypes: [String!]!\n consumedEventTypes: [String!]!\n events: [Platform_EventDef!]!\n chapter: String\n}`,
12
+ `type Platform_ReadSideDef {\n name: String!\n queryField: String!\n schema: String!\n consumedEventTypes: [String!]!\n linkedWriteSide: [String!]!\n labelField: String!\n searchableFields: [String!]!\n labelFieldSource: String\n statusField: String\n visibility: String\n chapter: String\n}`,
13
+ `type Platform_AutomationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n producedCommandTypes: [String!]!\n targetName: String\n chapter: String\n}`,
14
+ `type Platform_OutboundTranslationSliceDef {\n name: String!\n consumedEventTypes: [String!]!\n inboundCommandTypes: [String!]!\n targetName: String\n externalSystem: String\n chapter: String\n}`,
15
+ `type Platform_InboundTranslationSliceDef {\n name: String!\n commandTypes: [String!]!\n targetName: String\n externalSystem: String\n chapter: String\n}`,
16
16
  `type Platform_ExtensionDef {\n name: String!\n delegateNames: [String!]!\n eventTypes: [String!]!\n commandTypes: [String!]!\n}`,
17
17
  `type Platform_ComponentDefinitionEntry {\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}`
18
18
  ];
@@ -124,6 +124,10 @@ function encodeQueryableDef(r) {
124
124
  "statusField",
125
125
  Stdlib_Option.mapOr(r.statusField, null, prim => prim)
126
126
  ],
127
+ [
128
+ "visibility",
129
+ Stdlib_Option.mapOr(r.visibility, null, prim => prim)
130
+ ],
127
131
  [
128
132
  "chapter",
129
133
  Stdlib_Option.mapOr(r.chapter, null, prim => prim)
@@ -199,6 +203,10 @@ function encodeAutomationSliceDef(a) {
199
203
  "producedCommandTypes",
200
204
  a.producedCommandTypes.map(prim => prim)
201
205
  ],
206
+ [
207
+ "targetName",
208
+ a.targetName
209
+ ],
202
210
  [
203
211
  "chapter",
204
212
  Stdlib_Option.mapOr(a.chapter, null, prim => prim)
@@ -220,6 +228,10 @@ function encodeOutboundTranslationSliceDef(o) {
220
228
  "inboundCommandTypes",
221
229
  o.inboundCommandTypes.map(prim => prim)
222
230
  ],
231
+ [
232
+ "targetName",
233
+ Stdlib_Option.mapOr(o.targetName, null, prim => prim)
234
+ ],
223
235
  [
224
236
  "externalSystem",
225
237
  Stdlib_Option.mapOr(o.externalSystem, null, prim => prim)
@@ -241,6 +253,10 @@ function encodeInboundTranslationSliceDef(i) {
241
253
  "commandTypes",
242
254
  i.commandTypes.map(prim => prim)
243
255
  ],
256
+ [
257
+ "targetName",
258
+ i.targetName
259
+ ],
244
260
  [
245
261
  "externalSystem",
246
262
  Stdlib_Option.mapOr(i.externalSystem, null, prim => prim)
@@ -0,0 +1,132 @@
1
+ // SDL types and JSON encoder for the `Platform_PluginStructures` GraphQL query —
2
+ // the COMPLETE plugin structure, for developer tooling.
3
+ //
4
+ // Why this is a separate query rather than more fields on
5
+ // `Platform_ComponentDefinitions`: the two have different contracts, and the
6
+ // difference is not cosmetic.
7
+ //
8
+ // Platform_ComponentDefinitions the deployed AutoUI's menu, drill-down pages and
9
+ // queryable defs. Internal ReadModels /
10
+ // StateViewSlices are filtered OUT — showing them
11
+ // would put components in an end user's menu that
12
+ // the author marked as not-for-display.
13
+ // Platform_PluginStructures what the plugin actually IS. Nothing is filtered,
14
+ // and the producer-side `extensionPoints` are
15
+ // carried, because a tool drawing the domain graph
16
+ // has to see every component and every cross-plugin
17
+ // bridge or the graph it draws is a lie.
18
+ //
19
+ // Folding both into one field would mean either leaking Internal components into
20
+ // AutoUI or hiding them from tooling; there is no filter setting that is right for
21
+ // both. The component-level types are shared with `Platform_ComponentDefinitionsApi`
22
+ // (one component has one shape), so only the entry type and the two structure-level
23
+ // collections live here.
24
+ //
25
+ // Both adapters resolve this from the same persisted `pluginStructure`, so a single
26
+ // client query string works against a deployed platform and an in-memory one.
27
+
28
+ open Reventless.Plugin
29
+
30
+ let sdlTypes: array<string> = [
31
+ // Producer side of an extension point. `commandTypes` is null for a
32
+ // notification-only (`command = unit`) extension point — see `extensionPointDef`.
33
+ `type Platform_ExtensionPointDef {\n name: String!\n delegateNames: [String!]!\n sourceEventTypes: [String!]!\n commandTypes: [String!]\n}`,
34
+ `type Platform_RequiredStoreDeclaration {\n store: String!\n component: String!\n field: String!\n annotation: String\n}`,
35
+ // `extensionPoints` / `requiredStores` / `requiredStoreDeclarations` are nullable
36
+ // lists, not `[T!]!`: all three are optional on `pluginStructure` so that plugin
37
+ // definitions persisted before the field existed still decode. A structure written
38
+ // by an older deploy sends null, and null is the honest answer — an empty list
39
+ // would claim the plugin has no extension points when the truth is that the
40
+ // deployment cannot say.
41
+ `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}`,
42
+ ]
43
+
44
+ let sdlQueryField: string = ` Platform_PluginStructures: [Platform_PluginStructureEntry!]!`
45
+
46
+ let encodeExtensionPointDef = (e: extensionPointDef): JSON.t =>
47
+ Dict.fromArray([
48
+ ("name", JSON.Encode.string(e.name)),
49
+ ("delegateNames", Platform_ComponentDefinitionsApi.encodeStrings(e.delegateNames)),
50
+ ("sourceEventTypes", Platform_ComponentDefinitionsApi.encodeStrings(e.sourceEventTypes)),
51
+ (
52
+ "commandTypes",
53
+ e.commandTypes->Option.mapOr(JSON.Encode.null, Platform_ComponentDefinitionsApi.encodeStrings),
54
+ ),
55
+ ])->JSON.Encode.object
56
+
57
+ let encodeRequiredStoreDeclaration = (d: requiredStoreDeclaration): JSON.t =>
58
+ Dict.fromArray([
59
+ ("store", JSON.Encode.string(d.store)),
60
+ ("component", JSON.Encode.string(d.component)),
61
+ ("field", JSON.Encode.string(d.field)),
62
+ ("annotation", d.annotation->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
63
+ ])->JSON.Encode.object
64
+
65
+ // Deliberately NOT filtered by `isPublicQueryable`: that filter is the AutoUI
66
+ // contract, and applying it here would silently drop Internal components from the
67
+ // graph a developer tool draws.
68
+ let encodePluginStructureEntry = (~pluginId: string, def: pluginStructure): JSON.t =>
69
+ Dict.fromArray([
70
+ ("pluginId", JSON.Encode.string(Plugin.name(pluginId))),
71
+ (
72
+ "readModels",
73
+ def.readModels->Array.map(Platform_ComponentDefinitionsApi.encodeQueryableDef)->JSON.Encode.array,
74
+ ),
75
+ (
76
+ "stateViewSlices",
77
+ def.stateViewSlices
78
+ ->Array.map(Platform_ComponentDefinitionsApi.encodeQueryableDef)
79
+ ->JSON.Encode.array,
80
+ ),
81
+ (
82
+ "stateChangeSlices",
83
+ def.stateChangeSlices
84
+ ->Array.map(Platform_ComponentDefinitionsApi.encodeWritableDef)
85
+ ->JSON.Encode.array,
86
+ ),
87
+ (
88
+ "aggregates",
89
+ def.aggregates->Array.map(Platform_ComponentDefinitionsApi.encodeWritableDef)->JSON.Encode.array,
90
+ ),
91
+ (
92
+ "automationSlices",
93
+ def.automationSlices
94
+ ->Array.map(Platform_ComponentDefinitionsApi.encodeAutomationSliceDef)
95
+ ->JSON.Encode.array,
96
+ ),
97
+ (
98
+ "outboundTranslationSlices",
99
+ def.outboundTranslationSlices
100
+ ->Array.map(Platform_ComponentDefinitionsApi.encodeOutboundTranslationSliceDef)
101
+ ->JSON.Encode.array,
102
+ ),
103
+ (
104
+ "inboundTranslationSlices",
105
+ def.inboundTranslationSlices
106
+ ->Array.map(Platform_ComponentDefinitionsApi.encodeInboundTranslationSliceDef)
107
+ ->JSON.Encode.array,
108
+ ),
109
+ (
110
+ "extensions",
111
+ def.extensions->Array.map(Platform_ComponentDefinitionsApi.encodeExtensionDef)->JSON.Encode.array,
112
+ ),
113
+ (
114
+ "extensionPoints",
115
+ def.extensionPoints->Option.mapOr(JSON.Encode.null, eps =>
116
+ eps->Array.map(encodeExtensionPointDef)->JSON.Encode.array
117
+ ),
118
+ ),
119
+ (
120
+ "requiredStores",
121
+ def.requiredStores->Option.mapOr(
122
+ JSON.Encode.null,
123
+ Platform_ComponentDefinitionsApi.encodeStrings,
124
+ ),
125
+ ),
126
+ (
127
+ "requiredStoreDeclarations",
128
+ def.requiredStoreDeclarations->Option.mapOr(JSON.Encode.null, ds =>
129
+ ds->Array.map(encodeRequiredStoreDeclaration)->JSON.Encode.array
130
+ ),
131
+ ),
132
+ ])->JSON.Encode.object
@@ -0,0 +1,117 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Plugin$ReventlessCore from "../plugin/component/Plugin.res.mjs";
5
+ import * as Platform_ComponentDefinitionsApi$ReventlessCore from "./Platform_ComponentDefinitionsApi.res.mjs";
6
+
7
+ let sdlTypes = [
8
+ `type Platform_ExtensionPointDef {\n name: String!\n delegateNames: [String!]!\n sourceEventTypes: [String!]!\n commandTypes: [String!]\n}`,
9
+ `type Platform_RequiredStoreDeclaration {\n store: String!\n component: String!\n field: String!\n annotation: String\n}`,
10
+ `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}`
11
+ ];
12
+
13
+ function encodeExtensionPointDef(e) {
14
+ return Object.fromEntries([
15
+ [
16
+ "name",
17
+ e.name
18
+ ],
19
+ [
20
+ "delegateNames",
21
+ Platform_ComponentDefinitionsApi$ReventlessCore.encodeStrings(e.delegateNames)
22
+ ],
23
+ [
24
+ "sourceEventTypes",
25
+ Platform_ComponentDefinitionsApi$ReventlessCore.encodeStrings(e.sourceEventTypes)
26
+ ],
27
+ [
28
+ "commandTypes",
29
+ Stdlib_Option.mapOr(e.commandTypes, null, Platform_ComponentDefinitionsApi$ReventlessCore.encodeStrings)
30
+ ]
31
+ ]);
32
+ }
33
+
34
+ function encodeRequiredStoreDeclaration(d) {
35
+ return Object.fromEntries([
36
+ [
37
+ "store",
38
+ d.store
39
+ ],
40
+ [
41
+ "component",
42
+ d.component
43
+ ],
44
+ [
45
+ "field",
46
+ d.field
47
+ ],
48
+ [
49
+ "annotation",
50
+ Stdlib_Option.mapOr(d.annotation, null, prim => prim)
51
+ ]
52
+ ]);
53
+ }
54
+
55
+ function encodePluginStructureEntry(pluginId, def) {
56
+ return Object.fromEntries([
57
+ [
58
+ "pluginId",
59
+ Plugin$ReventlessCore.name(pluginId)
60
+ ],
61
+ [
62
+ "readModels",
63
+ def.readModels.map(Platform_ComponentDefinitionsApi$ReventlessCore.encodeQueryableDef)
64
+ ],
65
+ [
66
+ "stateViewSlices",
67
+ def.stateViewSlices.map(Platform_ComponentDefinitionsApi$ReventlessCore.encodeQueryableDef)
68
+ ],
69
+ [
70
+ "stateChangeSlices",
71
+ def.stateChangeSlices.map(Platform_ComponentDefinitionsApi$ReventlessCore.encodeWritableDef)
72
+ ],
73
+ [
74
+ "aggregates",
75
+ def.aggregates.map(Platform_ComponentDefinitionsApi$ReventlessCore.encodeWritableDef)
76
+ ],
77
+ [
78
+ "automationSlices",
79
+ def.automationSlices.map(Platform_ComponentDefinitionsApi$ReventlessCore.encodeAutomationSliceDef)
80
+ ],
81
+ [
82
+ "outboundTranslationSlices",
83
+ def.outboundTranslationSlices.map(Platform_ComponentDefinitionsApi$ReventlessCore.encodeOutboundTranslationSliceDef)
84
+ ],
85
+ [
86
+ "inboundTranslationSlices",
87
+ def.inboundTranslationSlices.map(Platform_ComponentDefinitionsApi$ReventlessCore.encodeInboundTranslationSliceDef)
88
+ ],
89
+ [
90
+ "extensions",
91
+ def.extensions.map(Platform_ComponentDefinitionsApi$ReventlessCore.encodeExtensionDef)
92
+ ],
93
+ [
94
+ "extensionPoints",
95
+ Stdlib_Option.mapOr(def.extensionPoints, null, eps => eps.map(encodeExtensionPointDef))
96
+ ],
97
+ [
98
+ "requiredStores",
99
+ Stdlib_Option.mapOr(def.requiredStores, null, Platform_ComponentDefinitionsApi$ReventlessCore.encodeStrings)
100
+ ],
101
+ [
102
+ "requiredStoreDeclarations",
103
+ Stdlib_Option.mapOr(def.requiredStoreDeclarations, null, ds => ds.map(encodeRequiredStoreDeclaration))
104
+ ]
105
+ ]);
106
+ }
107
+
108
+ let sdlQueryField = ` Platform_PluginStructures: [Platform_PluginStructureEntry!]!`;
109
+
110
+ export {
111
+ sdlTypes,
112
+ sdlQueryField,
113
+ encodeExtensionPointDef,
114
+ encodeRequiredStoreDeclaration,
115
+ encodePluginStructureEntry,
116
+ }
117
+ /* No side effect */
@@ -163,17 +163,22 @@ describe("encodePluginStructureEntry", () => {
163
163
  )
164
164
 
165
165
  // Translation-slice externalSystem: absent on the fixture above (both None), so it
166
- // must serialize as null — the "no external box" case.
166
+ // must serialize as null — the "no external box" case. The leading field keeps the
167
+ // two directions apart; `targetName` sits between them and is pinned here too.
167
168
  testSync("encodes None outbound externalSystem as null", () =>
168
- expect(json->String.includes("\"inboundCommandTypes\":[\"Ship\"],\"externalSystem\":null"))->toEqual(
169
- true,
170
- )
169
+ expect(
170
+ json->String.includes(
171
+ "\"inboundCommandTypes\":[\"Ship\"],\"targetName\":null,\"externalSystem\":null",
172
+ ),
173
+ )->toEqual(true)
171
174
  )
172
175
 
173
176
  testSync("encodes None inbound externalSystem as null", () =>
174
- expect(json->String.includes("\"commandTypes\":[\"RecordPayment\"],\"externalSystem\":null"))->toEqual(
175
- true,
176
- )
177
+ expect(
178
+ json->String.includes(
179
+ "\"commandTypes\":[\"RecordPayment\"],\"targetName\":\"Order\",\"externalSystem\":null",
180
+ ),
181
+ )->toEqual(true)
177
182
  )
178
183
  })
179
184
 
@@ -183,10 +183,10 @@ globalThis.describe("encodePluginStructureEntry", () => {
183
183
  globalThis.expect(json.includes("\"events\":[{\"name\":\"ProductAdded\"")).toEqual(true);
184
184
  });
185
185
  globalThis.test("encodes None outbound externalSystem as null", () => {
186
- globalThis.expect(json.includes("\"inboundCommandTypes\":[\"Ship\"],\"externalSystem\":null")).toEqual(true);
186
+ globalThis.expect(json.includes("\"inboundCommandTypes\":[\"Ship\"],\"targetName\":null,\"externalSystem\":null")).toEqual(true);
187
187
  });
188
188
  globalThis.test("encodes None inbound externalSystem as null", () => {
189
- globalThis.expect(json.includes("\"commandTypes\":[\"RecordPayment\"],\"externalSystem\":null")).toEqual(true);
189
+ globalThis.expect(json.includes("\"commandTypes\":[\"RecordPayment\"],\"targetName\":\"Order\",\"externalSystem\":null")).toEqual(true);
190
190
  });
191
191
  });
192
192
 
@@ -0,0 +1,144 @@
1
+ // Asserts what makes `Platform_PluginStructures` different from
2
+ // `Platform_ComponentDefinitions`: nothing is filtered out, and the structure-level
3
+ // collections the AutoUI entry does not carry are present. A developer tool draws
4
+ // its graph from this response, so a component or a bridge missing here is a graph
5
+ // that silently disagrees with the source.
6
+
7
+ open JestGlobals
8
+ open Reventless.Plugin
9
+
10
+ let publicRm: queryableDef = {
11
+ name: "Products",
12
+ queryField: "Catalog_Products",
13
+ schema: "{}",
14
+ consumedEventTypes: ["ProductAdded"],
15
+ linkedWriteSide: ["Product"],
16
+ labelField: "displayName",
17
+ searchableFields: ["displayName"],
18
+ labelFieldSource: Some("annotation"),
19
+ statusField: None,
20
+ visibility: None,
21
+ chapter: Some("Inventory"),
22
+ }
23
+
24
+ // The component `Platform_ComponentDefinitions` drops and this query must keep.
25
+ let internalRm: queryableDef = {
26
+ ...publicRm,
27
+ name: "ProductAudit",
28
+ queryField: "Catalog_ProductAudit",
29
+ visibility: Some("Internal"),
30
+ }
31
+
32
+ let structure: pluginStructure = {
33
+ readModels: [publicRm, internalRm],
34
+ stateViewSlices: [internalRm],
35
+ stateChangeSlices: [],
36
+ aggregates: [],
37
+ automationSlices: [],
38
+ outboundTranslationSlices: [],
39
+ inboundTranslationSlices: [],
40
+ extensions: [],
41
+ extensionPoints: Some([
42
+ {
43
+ name: "Catalog.Products",
44
+ delegateNames: ["onAdded"],
45
+ sourceEventTypes: ["Catalog.ProductAdded"],
46
+ commandTypes: None,
47
+ },
48
+ ]),
49
+ requiredStores: Some(["Catalog.images"]),
50
+ requiredStoreDeclarations: Some([
51
+ {store: "Catalog.images", component: "Product", field: "image", annotation: Some("images")},
52
+ ]),
53
+ }
54
+
55
+ let encoded = Platform_PluginStructuresApi.encodePluginStructureEntry(
56
+ ~pluginId="Catalog",
57
+ structure,
58
+ )
59
+ let json = encoded->JSON.stringify
60
+
61
+ let namesOf = (key): array<string> =>
62
+ encoded
63
+ ->JSON.Decode.object
64
+ ->Option.flatMap(d => d->Dict.get(key))
65
+ ->Option.flatMap(JSON.Decode.array)
66
+ ->Option.getOr([])
67
+ ->Array.filterMap(v =>
68
+ v->JSON.Decode.object->Option.flatMap(o => o->Dict.get("name"))->Option.flatMap(JSON.Decode.string)
69
+ )
70
+
71
+ describe("Platform_PluginStructures entry", () => {
72
+ testSync("carries the structure-level keys the AutoUI entry omits", () => {
73
+ let dict = encoded->JSON.Decode.object->Option.getOr(Dict.make())
74
+ let keys = dict->Dict.keysToArray->Array.toSorted(String.compare)
75
+ expect(keys)->toEqual([
76
+ "aggregates",
77
+ "automationSlices",
78
+ "extensionPoints",
79
+ "extensions",
80
+ "inboundTranslationSlices",
81
+ "outboundTranslationSlices",
82
+ "pluginId",
83
+ "readModels",
84
+ "requiredStoreDeclarations",
85
+ "requiredStores",
86
+ "stateChangeSlices",
87
+ "stateViewSlices",
88
+ ])
89
+ })
90
+
91
+ // The whole reason this query exists as a separate field.
92
+ testSync("keeps Internal read models", () =>
93
+ expect(namesOf("readModels"))->toEqual(["Products", "ProductAudit"])
94
+ )
95
+
96
+ testSync("keeps Internal state-view slices", () =>
97
+ expect(namesOf("stateViewSlices"))->toEqual(["ProductAudit"])
98
+ )
99
+
100
+ testSync("carries visibility so a consumer can tell which are Internal", () =>
101
+ expect(json->String.includes("\"visibility\":\"Internal\""))->toEqual(true)
102
+ )
103
+
104
+ testSync("encodes the chapter band", () =>
105
+ expect(json->String.includes("\"chapter\":\"Inventory\""))->toEqual(true)
106
+ )
107
+
108
+ testSync("encodes extension points with their source event types", () =>
109
+ expect(
110
+ json->String.includes(
111
+ "\"extensionPoints\":[{\"name\":\"Catalog.Products\",\"delegateNames\":[\"onAdded\"],\"sourceEventTypes\":[\"Catalog.ProductAdded\"],\"commandTypes\":null}]",
112
+ ),
113
+ )->toEqual(true)
114
+ )
115
+
116
+ testSync("encodes required stores with their provenance", () =>
117
+ expect(json->String.includes("\"requiredStores\":[\"Catalog.images\"]"))->toEqual(true)
118
+ )
119
+ })
120
+
121
+ // A structure persisted before these fields existed decodes as None, and null is the
122
+ // honest wire value — an empty list would claim the plugin has no extension points
123
+ // when the truth is that the deployment cannot say.
124
+ describe("absent optional collections", () => {
125
+ let bare: pluginStructure = {
126
+ ...structure,
127
+ extensionPoints: None,
128
+ requiredStores: None,
129
+ requiredStoreDeclarations: None,
130
+ }
131
+ let bareJson =
132
+ Platform_PluginStructuresApi.encodePluginStructureEntry(
133
+ ~pluginId="Catalog",
134
+ bare,
135
+ )->JSON.stringify
136
+
137
+ testSync("encodes absent extensionPoints as null", () =>
138
+ expect(bareJson->String.includes("\"extensionPoints\":null"))->toEqual(true)
139
+ )
140
+
141
+ testSync("encodes absent requiredStores as null", () =>
142
+ expect(bareJson->String.includes("\"requiredStores\":null"))->toEqual(true)
143
+ )
144
+ })
@@ -0,0 +1,197 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
4
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
7
+ import * as Platform_PluginStructuresApi$ReventlessCore from "../../src/admin/Platform_PluginStructuresApi.res.mjs";
8
+
9
+ let publicRm_consumedEventTypes = ["ProductAdded"];
10
+
11
+ let publicRm_linkedWriteSide = ["Product"];
12
+
13
+ let publicRm_searchableFields = ["displayName"];
14
+
15
+ let publicRm_labelFieldSource = "annotation";
16
+
17
+ let publicRm_chapter = "Inventory";
18
+
19
+ let publicRm = {
20
+ name: "Products",
21
+ queryField: "Catalog_Products",
22
+ schema: "{}",
23
+ consumedEventTypes: publicRm_consumedEventTypes,
24
+ linkedWriteSide: publicRm_linkedWriteSide,
25
+ labelField: "displayName",
26
+ searchableFields: publicRm_searchableFields,
27
+ labelFieldSource: publicRm_labelFieldSource,
28
+ statusField: undefined,
29
+ visibility: undefined,
30
+ chapter: publicRm_chapter
31
+ };
32
+
33
+ let internalRm_consumedEventTypes = publicRm_consumedEventTypes;
34
+
35
+ let internalRm_linkedWriteSide = publicRm_linkedWriteSide;
36
+
37
+ let internalRm_searchableFields = publicRm_searchableFields;
38
+
39
+ let internalRm_labelFieldSource = "annotation";
40
+
41
+ let internalRm_visibility = "Internal";
42
+
43
+ let internalRm_chapter = "Inventory";
44
+
45
+ let internalRm = {
46
+ name: "ProductAudit",
47
+ queryField: "Catalog_ProductAudit",
48
+ schema: "{}",
49
+ consumedEventTypes: internalRm_consumedEventTypes,
50
+ linkedWriteSide: internalRm_linkedWriteSide,
51
+ labelField: "displayName",
52
+ searchableFields: internalRm_searchableFields,
53
+ labelFieldSource: internalRm_labelFieldSource,
54
+ statusField: undefined,
55
+ visibility: internalRm_visibility,
56
+ chapter: internalRm_chapter
57
+ };
58
+
59
+ let structure_readModels = [
60
+ publicRm,
61
+ internalRm
62
+ ];
63
+
64
+ let structure_stateViewSlices = [internalRm];
65
+
66
+ let structure_stateChangeSlices = [];
67
+
68
+ let structure_aggregates = [];
69
+
70
+ let structure_automationSlices = [];
71
+
72
+ let structure_outboundTranslationSlices = [];
73
+
74
+ let structure_inboundTranslationSlices = [];
75
+
76
+ let structure_extensions = [];
77
+
78
+ let structure_extensionPoints = [{
79
+ name: "Catalog.Products",
80
+ delegateNames: ["onAdded"],
81
+ sourceEventTypes: ["Catalog.ProductAdded"],
82
+ commandTypes: undefined
83
+ }];
84
+
85
+ let structure_requiredStores = ["Catalog.images"];
86
+
87
+ let structure_requiredStoreDeclarations = [{
88
+ store: "Catalog.images",
89
+ component: "Product",
90
+ field: "image",
91
+ annotation: "images"
92
+ }];
93
+
94
+ let structure = {
95
+ readModels: structure_readModels,
96
+ stateViewSlices: structure_stateViewSlices,
97
+ stateChangeSlices: structure_stateChangeSlices,
98
+ aggregates: structure_aggregates,
99
+ automationSlices: structure_automationSlices,
100
+ outboundTranslationSlices: structure_outboundTranslationSlices,
101
+ inboundTranslationSlices: structure_inboundTranslationSlices,
102
+ extensions: structure_extensions,
103
+ extensionPoints: structure_extensionPoints,
104
+ requiredStores: structure_requiredStores,
105
+ requiredStoreDeclarations: structure_requiredStoreDeclarations
106
+ };
107
+
108
+ let encoded = Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry("Catalog", structure);
109
+
110
+ let json = JSON.stringify(encoded);
111
+
112
+ function namesOf(key) {
113
+ return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(encoded), d => d[key]), Stdlib_JSON.Decode.array), []), v => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(v), o => o["name"]), Stdlib_JSON.Decode.string));
114
+ }
115
+
116
+ globalThis.describe("Platform_PluginStructures entry", () => {
117
+ globalThis.test("carries the structure-level keys the AutoUI entry omits", () => {
118
+ let dict = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(encoded), {});
119
+ let keys = Object.keys(dict).toSorted(Primitive_string.compare);
120
+ globalThis.expect(keys).toEqual([
121
+ "aggregates",
122
+ "automationSlices",
123
+ "extensionPoints",
124
+ "extensions",
125
+ "inboundTranslationSlices",
126
+ "outboundTranslationSlices",
127
+ "pluginId",
128
+ "readModels",
129
+ "requiredStoreDeclarations",
130
+ "requiredStores",
131
+ "stateChangeSlices",
132
+ "stateViewSlices"
133
+ ]);
134
+ });
135
+ globalThis.test("keeps Internal read models", () => {
136
+ globalThis.expect(namesOf("readModels")).toEqual([
137
+ "Products",
138
+ "ProductAudit"
139
+ ]);
140
+ });
141
+ globalThis.test("keeps Internal state-view slices", () => {
142
+ globalThis.expect(namesOf("stateViewSlices")).toEqual(["ProductAudit"]);
143
+ });
144
+ globalThis.test("carries visibility so a consumer can tell which are Internal", () => {
145
+ globalThis.expect(json.includes("\"visibility\":\"Internal\"")).toEqual(true);
146
+ });
147
+ globalThis.test("encodes the chapter band", () => {
148
+ globalThis.expect(json.includes("\"chapter\":\"Inventory\"")).toEqual(true);
149
+ });
150
+ globalThis.test("encodes extension points with their source event types", () => {
151
+ globalThis.expect(json.includes("\"extensionPoints\":[{\"name\":\"Catalog.Products\",\"delegateNames\":[\"onAdded\"],\"sourceEventTypes\":[\"Catalog.ProductAdded\"],\"commandTypes\":null}]")).toEqual(true);
152
+ });
153
+ globalThis.test("encodes required stores with their provenance", () => {
154
+ globalThis.expect(json.includes("\"requiredStores\":[\"Catalog.images\"]")).toEqual(true);
155
+ });
156
+ });
157
+
158
+ globalThis.describe("absent optional collections", () => {
159
+ let bare_readModels = structure_readModels;
160
+ let bare_stateViewSlices = structure_stateViewSlices;
161
+ let bare_stateChangeSlices = structure_stateChangeSlices;
162
+ let bare_aggregates = structure_aggregates;
163
+ let bare_automationSlices = structure_automationSlices;
164
+ let bare_outboundTranslationSlices = structure_outboundTranslationSlices;
165
+ let bare_inboundTranslationSlices = structure_inboundTranslationSlices;
166
+ let bare_extensions = structure_extensions;
167
+ let bare = {
168
+ readModels: bare_readModels,
169
+ stateViewSlices: bare_stateViewSlices,
170
+ stateChangeSlices: bare_stateChangeSlices,
171
+ aggregates: bare_aggregates,
172
+ automationSlices: bare_automationSlices,
173
+ outboundTranslationSlices: bare_outboundTranslationSlices,
174
+ inboundTranslationSlices: bare_inboundTranslationSlices,
175
+ extensions: bare_extensions,
176
+ extensionPoints: undefined,
177
+ requiredStores: undefined,
178
+ requiredStoreDeclarations: undefined
179
+ };
180
+ let bareJson = JSON.stringify(Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry("Catalog", bare));
181
+ globalThis.test("encodes absent extensionPoints as null", () => {
182
+ globalThis.expect(bareJson.includes("\"extensionPoints\":null")).toEqual(true);
183
+ });
184
+ globalThis.test("encodes absent requiredStores as null", () => {
185
+ globalThis.expect(bareJson.includes("\"requiredStores\":null")).toEqual(true);
186
+ });
187
+ });
188
+
189
+ export {
190
+ publicRm,
191
+ internalRm,
192
+ structure,
193
+ encoded,
194
+ json,
195
+ namesOf,
196
+ }
197
+ /* encoded Not a pure module */