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

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 (32) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/package.json +7 -7
  3. package/src/Projection.res +5 -2
  4. package/src/Projection.res.mjs +2 -1
  5. package/src/admin/Platform_AdminApi.res +2 -0
  6. package/src/admin/Platform_AdminApi.res.mjs +3 -1
  7. package/src/admin/Platform_ComponentDefinitionsApi.res +26 -5
  8. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +21 -5
  9. package/src/admin/Platform_PluginStructuresApi.res +132 -0
  10. package/src/admin/Platform_PluginStructuresApi.res.mjs +117 -0
  11. package/src/components/AutomationSlice/AutomationSlice_Callback.res +30 -0
  12. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +12 -0
  13. package/src/components/Dcb/Dcb_Builder.res +6 -2
  14. package/src/components/Dcb/Dcb_Builder.res.mjs +2 -2
  15. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res +32 -0
  16. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs +13 -0
  17. package/src/plugin/component/Plugin_Builder.res +6 -13
  18. package/src/plugin/component/Plugin_Builder.res.mjs +2 -16
  19. package/src/plugin/component/Plugin_Helpers.res +21 -3
  20. package/src/plugin/component/Plugin_Helpers.res.mjs +16 -0
  21. package/src/util/LogFormat.res +52 -1
  22. package/src/util/LogFormat.res.mjs +21 -2
  23. package/src/util/StoreLayout.res +38 -0
  24. package/src/util/StoreLayout.res.mjs +22 -0
  25. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +12 -7
  26. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +2 -2
  27. package/tests/admin/Platform_PluginStructuresApiTest.res +144 -0
  28. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +197 -0
  29. package/tests/api/GraphQL_FragmentGeneratorTest.res +40 -0
  30. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +35 -0
  31. package/tests/logger/LogFormatTest.res +42 -0
  32. package/tests/logger/LogFormatTest.res.mjs +37 -0
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.213 (2026-08-05)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **core:** describe slice TODO items by their own schema in the API ([d863056](https://github.com/ReventlessDev/reventless-core/commit/d863056ba734ba3dc76721c32bfb766d777ceb9a))
11
+ ### Features
12
+
13
+ * **core:** cap rendered values in command, event and action logs ([8e07752](https://github.com/ReventlessDev/reventless-core/commit/8e077527ad3c8258582951544ee53d2722876191))
14
+ * **local:** offload large pluginDefinition fields to the object store ([84c5fab](https://github.com/ReventlessDev/reventless-core/commit/84c5fab339856b5d64f0c05110b1fa7f38646f5e))
15
+ * **local:** scoped seed:reset for the local platform ([a4dd003](https://github.com/ReventlessDev/reventless-core/commit/a4dd0033b7d01be23be91bac3f01d38ebeab7d45))
16
+
17
+
18
+ # 3.0.0-alpha.212 (2026-08-04)
19
+
20
+ ### Bug Fixes
21
+
22
+ * **admin:** declare the component fields the resolver already returned ([879fdf3](https://github.com/ReventlessDev/reventless-core/commit/879fdf3e7ac68be23f915db6bdd0fdc68294de5d))
23
+ ### Features
24
+
25
+ * **admin:** serve the complete plugin structure to developer tooling ([e99cda0](https://github.com/ReventlessDev/reventless-core/commit/e99cda02a2f862b36bcfdb018a0fb34bb7426a2b))
26
+
27
+
6
28
  # 3.0.0-alpha.211 (2026-08-04)
7
29
 
8
30
  ### 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.213",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -28,17 +28,17 @@
28
28
  "dependencies": {
29
29
  "sury": "11.0.0-alpha.4",
30
30
  "uuid": "^13.0.0",
31
- "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.1",
32
31
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
32
+ "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.2",
33
33
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
34
34
  "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
35
- "@reventlessdev/rescript-node": "2.0.0-alpha.1",
35
+ "@reventlessdev/rescript-node": "2.0.0-alpha.2",
36
36
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
37
- "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
38
- "@reventlessdev/rescript-ssh2": "2.0.0-alpha.1",
39
- "@reventlessdev/reventless-infra": "3.0.0-alpha.126",
37
+ "@reventlessdev/rescript-ssh2": "2.0.0-alpha.2",
40
38
  "@reventlessdev/reventless-interop": "3.0.0-alpha.30",
41
- "@reventlessdev/reventless-spec": "3.0.0-alpha.100"
39
+ "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
40
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.127",
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.101"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "12.3.0",
@@ -126,9 +126,12 @@ let applyChanges = async (
126
126
  }
127
127
 
128
128
  // Best-effort: a state that can't be JSON-serialized (e.g. carries a function)
129
- // must not crash the projection just to produce a debug line.
129
+ // must not crash the projection just to produce a debug line. Capped at the
130
+ // shared log budget — an action line names which row changed, and a read model
131
+ // holding a large field (a plugin structure, an inline offload payload) would
132
+ // otherwise print the whole row twice, once per side of the `=>`.
130
133
  let stateToString: 'a => string = state =>
131
- state->JSON.stringifyAny->Option.getOr("<unserializable>")
134
+ state->JSON.stringifyAny->Option.getOr("<unserializable>")->LogFormat.truncate
132
135
  let statesToString: array<'a> => string = states =>
133
136
  states->Array.map(stateToString)->Array.joinUnsafe(", ")
134
137
 
@@ -16,6 +16,7 @@ import * as DisplayName$Reventless from "@reventlessdev/reventless-spec/src/comp
16
16
  import * as Message$ReventlessCore from "./Message.res.mjs";
17
17
  import * as QueryDb$ReventlessCore from "./components/QueryDb/QueryDb.res.mjs";
18
18
  import * as QueryDb$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/QueryDb.res.mjs";
19
+ import * as LogFormat$ReventlessCore from "./util/LogFormat.res.mjs";
19
20
 
20
21
  let log = Logger$ReventlessCore.fromEnv();
21
22
 
@@ -195,7 +196,7 @@ async function applyChanges(action, id, beforeStates, afterStates, param, param$
195
196
  }
196
197
 
197
198
  function stateToString(state) {
198
- return Stdlib_Option.getOr(JSON.stringify(state), "<unserializable>");
199
+ return LogFormat$ReventlessCore.truncate(Stdlib_Option.getOr(JSON.stringify(state), "<unserializable>"));
199
200
  }
200
201
 
201
202
  function statesToString(states) {
@@ -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 */
@@ -31,6 +31,36 @@ type todoRow = {
31
31
  retryCount: int,
32
32
  }
33
33
 
34
+ /** The item schema seen through the `JSON.t` the row actually stores.
35
+
36
+ `item` holds `Spec.todoItemSchema`-encoded JSON, so the item schema already
37
+ describes the stored value exactly — only the ReScript types disagree. */
38
+ external itemAsJson: S.t<'item> => S.t<JSON.t> = "%identity"
39
+
40
+ /**
41
+ The row schema as the API describes it: `item` carried by the slice's own item
42
+ schema instead of as opaque JSON.
43
+
44
+ `todoRowSchema` types `item` as `JSON.t`, which the schema→GraphQL IR can only
45
+ classify as `Unknown` — and `Unknown` renders as `String!`. The field then
46
+ serves a stored object through a String, which fails at execution ("String
47
+ cannot represent value: { … }") the moment a client selects it. Describing the
48
+ item shape gives the field a real object type instead.
49
+
50
+ Shape only: never encode or parse a row with this. The runtime field is `JSON.t`
51
+ and the identity cast above would have `S.parseOrThrow` hand back an item where
52
+ the row's own code expects JSON.
53
+ */
54
+ let todoRowSchemaFor = (itemSchema: S.t<'item>): S.t<todoRow> =>
55
+ S.schema(s => {
56
+ item: s.matches(itemSchema->itemAsJson),
57
+ status: s.matches(todoStatusSchema),
58
+ createdAt: s.matches(S.string),
59
+ processedAt: ?s.matches(S.option(S.string)),
60
+ completedAt: ?s.matches(S.option(S.string)),
61
+ retryCount: s.matches(S.int),
62
+ })
63
+
34
64
  /**
35
65
  The runtime protocol between a built callback and whoever drives it — the same
36
66
  arrangement as `OutboundTranslationSlice_Callback`, and for the same reason. The
@@ -30,6 +30,17 @@ let todoRowSchema = S.schema(s => ({
30
30
  retryCount: s.m(S.int)
31
31
  }));
32
32
 
33
+ function todoRowSchemaFor(itemSchema) {
34
+ return S.schema(s => ({
35
+ item: s.m(itemSchema),
36
+ status: s.m(todoStatusSchema),
37
+ createdAt: s.m(S.string),
38
+ processedAt: s.m(S.option(S.string)),
39
+ completedAt: s.m(S.option(S.string)),
40
+ retryCount: s.m(S.int)
41
+ }));
42
+ }
43
+
33
44
  function Make(Spec) {
34
45
  return Automation => {
35
46
  let todoItems = {};
@@ -184,6 +195,7 @@ function Make(Spec) {
184
195
  export {
185
196
  todoStatusSchema,
186
197
  todoRowSchema,
198
+ todoRowSchemaFor,
187
199
  Make,
188
200
  }
189
201
  /* Not a pure module */
@@ -1009,7 +1009,9 @@ module Make = (
1009
1009
  ReventlessInfra.Api.singleFieldName: qn.singleFieldName,
1010
1010
  listFieldName: qn.listFieldName,
1011
1011
  returnTypeName: qn.returnTypeName,
1012
- stateSchema: AutomationSlice_Callback.todoRowSchema->S.castToUnknown,
1012
+ stateSchema: AutomationSlice_Callback.todoRowSchemaFor(
1013
+ A.Spec.todoItemSchema,
1014
+ )->S.castToUnknown,
1013
1015
  authorization: None,
1014
1016
  connectionSpec: true,
1015
1017
  }
@@ -1023,7 +1025,9 @@ module Make = (
1023
1025
  ReventlessInfra.Api.singleFieldName: qn.singleFieldName,
1024
1026
  listFieldName: qn.listFieldName,
1025
1027
  returnTypeName: qn.returnTypeName,
1026
- stateSchema: OutboundTranslationSlice_Callback.todoRowSchema->S.castToUnknown,
1028
+ stateSchema: OutboundTranslationSlice_Callback.todoRowSchemaFor(
1029
+ O.Spec.outboundItemSchema,
1030
+ )->S.castToUnknown,
1027
1031
  authorization: None,
1028
1032
  connectionSpec: true,
1029
1033
  }
@@ -531,7 +531,7 @@ function Make(DcbEventLogStorage) {
531
531
  singleFieldName: qn.singleFieldName,
532
532
  listFieldName: qn.listFieldName,
533
533
  returnTypeName: qn.returnTypeName,
534
- stateSchema: AutomationSlice_Callback$ReventlessCore.todoRowSchema,
534
+ stateSchema: AutomationSlice_Callback$ReventlessCore.todoRowSchemaFor(A.Spec.todoItemSchema),
535
535
  authorization: undefined,
536
536
  connectionSpec: true
537
537
  };
@@ -542,7 +542,7 @@ function Make(DcbEventLogStorage) {
542
542
  singleFieldName: qn.singleFieldName,
543
543
  listFieldName: qn.listFieldName,
544
544
  returnTypeName: qn.returnTypeName,
545
- stateSchema: OutboundTranslationSlice_Callback$ReventlessCore.todoRowSchema,
545
+ stateSchema: OutboundTranslationSlice_Callback$ReventlessCore.todoRowSchemaFor(O.Spec.outboundItemSchema),
546
546
  authorization: undefined,
547
547
  connectionSpec: true
548
548
  };