@reventlessdev/reventless-core 3.0.0-alpha.256 → 3.0.0-alpha.258
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 +28 -0
- package/package.json +8 -8
- package/src/adapter/Monitoring/Monitoring.res +79 -5
- package/src/adapter/Monitoring/Monitoring.res.mjs +42 -2
- package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res +5 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
- package/src/admin/Platform_UiSlots.res +21 -0
- package/src/admin/Platform_UiSlots.res.mjs +15 -0
- package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +1 -1
- package/src/components/Aggregate/Aggregate_Callback.res +13 -4
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +6 -5
- package/src/components/Api/GraphQL_FragmentGenerator.res +2 -0
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +4 -0
- package/src/components/Api/MCP_SchemaGenerator.res.mjs +2 -2
- package/src/components/Api/SchemaType.res +8 -0
- package/src/components/Api/SchemaType.res.mjs +15 -5
- package/src/components/Api/SuryToJsonSchema.res +70 -12
- package/src/components/Api/SuryToJsonSchema.res.mjs +43 -16
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +6 -1
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +1 -1
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +7 -2
- package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +4 -1
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
- package/src/components/StateViewSlice/StateViewSlice_Callback.res.mjs +1 -1
- package/src/plugin/component/Plugin.res +1 -0
- package/src/plugin/component/Plugin_Builder.res +2 -0
- package/src/plugin/component/Plugin_Builder.res.mjs +3 -2
- package/src/plugin/component/Plugin_Structure.res +105 -10
- package/src/plugin/component/Plugin_Structure.res.mjs +71 -27
- package/src/util/Validation.res +1 -1
- package/src/util/Validation.res.mjs +2 -2
- package/tests/adapter/MonitoringTest.res +46 -7
- package/tests/adapter/MonitoringTest.res.mjs +47 -5
- package/tests/admin/AdminApiSchemaDriftTest.res +1 -0
- package/tests/admin/AdminApiSchemaDriftTest.res.mjs +3 -0
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +2 -14
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +1 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +2 -0
- package/tests/aggregate/AggregateCacheTest.res.mjs +1 -1
- package/tests/aggregate/AggregateConflictTest.res.mjs +1 -1
- package/tests/aggregate/AggregateFixtures.res.mjs +1 -1
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +1 -1
- package/tests/api/GraphQL_FragmentGeneratorTest.res +6 -4
- package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +5 -5
- package/tests/api/SuryToJsonSchemaTest.res.mjs +57 -57
- package/tests/api/TaggedUnionSchemaTest.res.mjs +1 -1
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +1 -1
- package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +1 -1
- package/tests/dcb/DcbFixtures.res.mjs +1 -1
- package/tests/eventmapper/EventMapperFixtures.res.mjs +1 -1
- package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +1 -1
- package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +1 -1
- package/tests/plugin/ManifestVisibilityTest.res.mjs +1 -1
- package/tests/plugin/PluginStructureAccessTest.res.mjs +1 -1
- package/tests/plugin/PluginStructureTest.res +184 -0
- package/tests/plugin/PluginStructureTest.res.mjs +142 -17
- package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsReserveStock.res +14 -1
- package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +9 -2
- package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +1 -1
- package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +1 -1
- package/tests/util/CommandPublisherTest.res.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
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.258 (2026-09-07)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **core:** a state view slice composes its [@display](https://github.com/display)Name like a read model ([851aa1d](https://github.com/ReventlessDev/reventless-core/commit/851aa1d12ddae9dd39f91084f5719cd98fe1cbd4))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.257 (2026-09-07)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **core:** hold a monitoring announcement until someone is listening ([9cc4526](https://github.com/ReventlessDev/reventless-core/commit/9cc4526d3a21ce130e219b6da88fd4f3201974bd))
|
|
18
|
+
* **logging:** log a message's identity, not its serialisation ([cd7467a](https://github.com/ReventlessDev/reventless-core/commit/cd7467abdc59609279f16f358cdd8884cc49f063))
|
|
19
|
+
* **ui:** a declared slot module is named, not only written ([de000ba](https://github.com/ReventlessDev/reventless-core/commit/de000baf428249f64cfc719ed52df3760198b82a))
|
|
20
|
+
* feat(dcb)!: a record inside an array is still a reference and a key ([bcb9b13](https://github.com/ReventlessDev/reventless-core/commit/bcb9b13958323d8319d7fcf25c055800e84150f3))
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* **core:** a command's lifecycle edge comes from its scenarios, not its claim ([1879d05](https://github.com/ReventlessDev/reventless-core/commit/1879d0570a981e7fae5bcd3808f8a36e898bd0fd))
|
|
24
|
+
|
|
25
|
+
### BREAKING CHANGES
|
|
26
|
+
|
|
27
|
+
* an `int` field is now `Int` in the generated SDL and
|
|
28
|
+
`"integer"` in JSON Schema, where it was `Float`/`"number"`. A client
|
|
29
|
+
declaring `$retryCount: Float!` against one of these fields must move to
|
|
30
|
+
`Int!`.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
6
34
|
# 3.0.0-alpha.256 (2026-09-04)
|
|
7
35
|
|
|
8
36
|
* feat(spec)!: one optional encoding on the wire, with no annotation ([320f91d](https://github.com/ReventlessDev/reventless-core/commit/320f91daa8bd90812a6e82069e7a1cb473041930))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.258",
|
|
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.9",
|
|
32
31
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
33
32
|
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
|
|
34
|
-
"@reventlessdev/rescript-
|
|
33
|
+
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.9",
|
|
35
34
|
"@reventlessdev/rescript-node": "2.0.0-alpha.9",
|
|
36
|
-
"@reventlessdev/rescript-
|
|
35
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
37
36
|
"@reventlessdev/rescript-ssh2": "2.0.0-alpha.9",
|
|
37
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.159",
|
|
38
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
38
39
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
39
|
-
"@reventlessdev/reventless-
|
|
40
|
-
"@reventlessdev/reventless-
|
|
41
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.129"
|
|
40
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.131",
|
|
41
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.35"
|
|
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.
|
|
46
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.78"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"rescript": "12.3.0"
|
|
@@ -100,17 +100,83 @@ module Noop: Backend = {
|
|
|
100
100
|
|
|
101
101
|
let backend: ref<module(Backend)> = ref(module(Noop: Backend))
|
|
102
102
|
|
|
103
|
+
/** Whether `use` has run. Distinguishes "nobody is listening yet" — which is
|
|
104
|
+
what the buffer below exists for — from "a backend that deliberately does
|
|
105
|
+
nothing with this unit", which is the backend's decision to make, not ours. */
|
|
106
|
+
let registered = ref(false)
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
An announcement made before any backend was registered, held until one is.
|
|
110
|
+
|
|
111
|
+
"Before the build" is not early enough for every provisioning site. A module that
|
|
112
|
+
creates its resources at import time announces them while the deploy program's own
|
|
113
|
+
body — including its `use` call — has not started running, because an imported
|
|
114
|
+
module is evaluated to completion first. The dead-letter sink is such a site, and
|
|
115
|
+
it was the one execution unit in a monitored estate that no alarm ever covered:
|
|
116
|
+
not because a backend declined to handle its kind, but because it was told at a
|
|
117
|
+
moment when the answer to "who is listening" was permanently no one.
|
|
118
|
+
|
|
119
|
+
Buffering makes the seam insensitive to that ordering. What a backend sees is the
|
|
120
|
+
same set of units in the same order; only the moment it hears about the early ones
|
|
121
|
+
moves, to the first instant there is anybody to hear it.
|
|
122
|
+
*/
|
|
123
|
+
type announcement = {
|
|
124
|
+
kind: unitKind,
|
|
125
|
+
name: string,
|
|
126
|
+
component: ReventlessInfra.Adapter.resource,
|
|
127
|
+
plugin: option<string>,
|
|
128
|
+
platform: option<string>,
|
|
129
|
+
logLocator: option<Pulumi.Output.t<string>>,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let pending: ref<array<announcement>> = ref([])
|
|
133
|
+
|
|
134
|
+
let deliver = (module(B: Backend), a: announcement) =>
|
|
135
|
+
B.onProvisioned(
|
|
136
|
+
~kind=a.kind,
|
|
137
|
+
~name=a.name,
|
|
138
|
+
~component=a.component,
|
|
139
|
+
~plugin=a.plugin,
|
|
140
|
+
~platform=a.platform,
|
|
141
|
+
~logLocator=a.logLocator,
|
|
142
|
+
)
|
|
143
|
+
|
|
103
144
|
/**
|
|
104
145
|
Register the monitoring backend. Must run before the platform/plugin build in the
|
|
105
146
|
deploy program (plain statement order — the registry is consulted lazily at each
|
|
106
147
|
provisioning site). Called once, by the extension, first.
|
|
148
|
+
|
|
149
|
+
Announcements from sites that ran at import time — before any statement of the
|
|
150
|
+
deploy program — are replayed here, in the order they were made, and the buffer is
|
|
151
|
+
drained so a second registration does not receive them twice.
|
|
107
152
|
*/
|
|
108
|
-
let use = (b: module(Backend)) =>
|
|
153
|
+
let use = (b: module(Backend)) => {
|
|
154
|
+
backend := b
|
|
155
|
+
registered := true
|
|
156
|
+
let replay = pending.contents
|
|
157
|
+
pending := []
|
|
158
|
+
replay->Array.forEach(a => deliver(b, a))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Restores the registry to its initial state: no backend, nothing buffered.
|
|
162
|
+
|
|
163
|
+
A deploy program never needs this — it registers once and runs once. Tests do,
|
|
164
|
+
because the registry and the buffer are module state that outlives a single
|
|
165
|
+
case, and `use(Noop)` is a *registration* rather than an undo: it would leave
|
|
166
|
+
later announcements delivered-and-dropped instead of held. */
|
|
167
|
+
let reset = () => {
|
|
168
|
+
backend := module(Noop: Backend)
|
|
169
|
+
registered := false
|
|
170
|
+
pending := []
|
|
171
|
+
}
|
|
109
172
|
|
|
110
173
|
/**
|
|
111
174
|
Announce a provisioned execution unit to the registered backend. Called by
|
|
112
|
-
runtime backends at their provisioning sites (once per unit).
|
|
113
|
-
|
|
175
|
+
runtime backends at their provisioning sites (once per unit).
|
|
176
|
+
|
|
177
|
+
Held until an extension registers a backend, rather than dropped — see
|
|
178
|
+
`announcement` above for why that distinction decides whether a unit provisioned
|
|
179
|
+
at import time is ever monitored.
|
|
114
180
|
*/
|
|
115
181
|
let notify = (
|
|
116
182
|
~kind: unitKind,
|
|
@@ -118,13 +184,21 @@ let notify = (
|
|
|
118
184
|
~component: ReventlessInfra.Adapter.resource,
|
|
119
185
|
~logLocator: option<Pulumi.Output.t<string>>=?,
|
|
120
186
|
) => {
|
|
121
|
-
module B = unpack(backend.contents)
|
|
122
187
|
// The owning plugin/platform come from the ambient context the plugin builder publishes
|
|
123
188
|
// (ResourceAttribution.enter) — populated exactly when this fires, so the call sites need not
|
|
124
189
|
// thread the names. `None` outside a plugin construct (platform substrate).
|
|
190
|
+
//
|
|
191
|
+
// Read here rather than at delivery, because a buffered announcement is replayed from a deploy
|
|
192
|
+
// program's body, where the ambient context is whatever the *next* construct left behind. The
|
|
193
|
+
// owner belongs to the moment the unit was provisioned.
|
|
125
194
|
let {ResourceAttribution.plugin: plugin, platform} = ResourceAttribution.current.contents
|
|
126
195
|
// `~logLocator`, unlike the owner, is NOT ambient: only the provisioning site knows where it
|
|
127
196
|
// pointed the unit's logs. So it is passed, and optional — a call site with nothing to say omits
|
|
128
197
|
// it rather than being forced to invent an address.
|
|
129
|
-
|
|
198
|
+
let announcement = {kind, name, component, plugin, platform, logLocator}
|
|
199
|
+
if registered.contents {
|
|
200
|
+
deliver(backend.contents, announcement)
|
|
201
|
+
} else {
|
|
202
|
+
pending := pending.contents->Array.concat([announcement])
|
|
203
|
+
}
|
|
130
204
|
}
|
|
@@ -14,20 +14,60 @@ let backend = {
|
|
|
14
14
|
contents: Noop
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
let registered = {
|
|
18
|
+
contents: false
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let pending = {
|
|
22
|
+
contents: []
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function deliver(B, a) {
|
|
26
|
+
B.onProvisioned(a.kind, a.name, a.component, a.plugin, a.platform, a.logLocator);
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
function use(b) {
|
|
18
30
|
backend.contents = b;
|
|
31
|
+
registered.contents = true;
|
|
32
|
+
let replay = pending.contents;
|
|
33
|
+
pending.contents = [];
|
|
34
|
+
replay.forEach(a => deliver(b, a));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function reset() {
|
|
38
|
+
backend.contents = Noop;
|
|
39
|
+
registered.contents = false;
|
|
40
|
+
pending.contents = [];
|
|
19
41
|
}
|
|
20
42
|
|
|
21
43
|
function notify(kind, name, component, logLocator) {
|
|
22
|
-
let B = backend.contents;
|
|
23
44
|
let match = ResourceAttribution$ReventlessCore.current.contents;
|
|
24
|
-
|
|
45
|
+
let announcement_plugin = match.plugin;
|
|
46
|
+
let announcement_platform = match.platform;
|
|
47
|
+
let announcement = {
|
|
48
|
+
kind: kind,
|
|
49
|
+
name: name,
|
|
50
|
+
component: component,
|
|
51
|
+
plugin: announcement_plugin,
|
|
52
|
+
platform: announcement_platform,
|
|
53
|
+
logLocator: logLocator
|
|
54
|
+
};
|
|
55
|
+
if (registered.contents) {
|
|
56
|
+
return deliver(backend.contents, announcement);
|
|
57
|
+
} else {
|
|
58
|
+
pending.contents = pending.contents.concat([announcement]);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
25
61
|
}
|
|
26
62
|
|
|
27
63
|
export {
|
|
28
64
|
Noop,
|
|
29
65
|
backend,
|
|
66
|
+
registered,
|
|
67
|
+
pending,
|
|
68
|
+
deliver,
|
|
30
69
|
use,
|
|
70
|
+
reset,
|
|
31
71
|
notify,
|
|
32
72
|
}
|
|
33
73
|
/* No side effect */
|
|
@@ -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.commandTransition, 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, undefined, PluginSpec$ReventlessCore.commandSchema);
|
|
11
11
|
|
|
12
12
|
let pluginAggregate_producedEventTypes = [];
|
|
13
13
|
|
|
@@ -18,7 +18,7 @@ open Reventless.Plugin
|
|
|
18
18
|
// consumers long after they were being encoded.
|
|
19
19
|
let sdlTypes: array<string> = [
|
|
20
20
|
`type Platform_FieldReference {\n fieldName: String!\n entity: String!\n plugin: String\n}`,
|
|
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 requiredAccess: [String!]\n ownerField: String\n}`,
|
|
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 allowedStatesSource: String\n apiExposed: Boolean\n requiredAccess: [String!]\n ownerField: String\n}`,
|
|
22
22
|
`type Platform_EventDef {\n name: String!\n schema: String!\n references: [Platform_FieldReference!]!\n}`,
|
|
23
23
|
// Same fields as Platform_EventDef, kept a distinct type because a refusal is not
|
|
24
24
|
// a fact: a caller selecting `errors` is asking what a command can be rejected
|
|
@@ -84,6 +84,10 @@ let encodeCommandDef = (c: commandDef): JSON.t =>
|
|
|
84
84
|
c.allowedStates->Option.mapOr(JSON.Encode.null, encodeStrings),
|
|
85
85
|
),
|
|
86
86
|
("targetState", c.targetState->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
87
|
+
(
|
|
88
|
+
"allowedStatesSource",
|
|
89
|
+
c.allowedStatesSource->Option.mapOr(JSON.Encode.null, JSON.Encode.string),
|
|
90
|
+
),
|
|
87
91
|
("apiExposed", c.apiExposed->Option.mapOr(JSON.Encode.null, JSON.Encode.bool)),
|
|
88
92
|
// Null, not `[]`: a rule asking for nothing checkable and a def written
|
|
89
93
|
// before the field existed are the same answer to a client — "no key gates
|
|
@@ -6,7 +6,7 @@ import * as Plugin$ReventlessCore from "../plugin/component/Plugin.res.mjs";
|
|
|
6
6
|
|
|
7
7
|
let sdlTypes = [
|
|
8
8
|
`type Platform_FieldReference {\n fieldName: String!\n entity: String!\n plugin: String\n}`,
|
|
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 requiredAccess: [String!]\n ownerField: String\n}`,
|
|
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 allowedStatesSource: String\n apiExposed: Boolean\n requiredAccess: [String!]\n ownerField: String\n}`,
|
|
10
10
|
`type Platform_EventDef {\n name: String!\n schema: String!\n references: [Platform_FieldReference!]!\n}`,
|
|
11
11
|
`type Platform_ErrorDef {\n name: String!\n schema: String!\n references: [Platform_FieldReference!]!\n}`,
|
|
12
12
|
`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 errors: [Platform_ErrorDef!]!\n chapter: String\n}`,
|
|
@@ -78,6 +78,10 @@ function encodeCommandDef(c) {
|
|
|
78
78
|
"targetState",
|
|
79
79
|
Stdlib_Option.mapOr(c.targetState, null, prim => prim)
|
|
80
80
|
],
|
|
81
|
+
[
|
|
82
|
+
"allowedStatesSource",
|
|
83
|
+
Stdlib_Option.mapOr(c.allowedStatesSource, null, prim => prim)
|
|
84
|
+
],
|
|
81
85
|
[
|
|
82
86
|
"apiExposed",
|
|
83
87
|
Stdlib_Option.mapOr(c.apiExposed, null, prim => prim)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Where a deployment's slot module goes, and what the shell is told to fetch.
|
|
2
|
+
//
|
|
3
|
+
// Stated once, in core, for the reason `Platform_BakedManifest.defaultKey`
|
|
4
|
+
// gives: four places have to agree on this string — the AWS `BucketObject` key,
|
|
5
|
+
// the `uiSlotsUrl` the AWS deploy writes into `config.json`, the file the local
|
|
6
|
+
// platform serves out of the host-shell `dist/`, and the `uiSlotsUrl` that
|
|
7
|
+
// platform overlays. A platform that spelled it differently from the one that
|
|
8
|
+
// wrote the config key would put the module where nothing imports it, and the
|
|
9
|
+
// symptom is a surface that draws its own regions — indistinguishable from a
|
|
10
|
+
// deployment that declared no slots at all.
|
|
11
|
+
//
|
|
12
|
+
// Both platforms write the module beside `config.json` at what is also the
|
|
13
|
+
// shell's URL root, so the file name and the path the browser asks for are one
|
|
14
|
+
// string.
|
|
15
|
+
|
|
16
|
+
let fileName = "ui-slots.js"
|
|
17
|
+
|
|
18
|
+
/** The `config.json` key naming the module, as `SlotModules.load` reads it. */
|
|
19
|
+
let configKey = "uiSlotsUrl"
|
|
20
|
+
|
|
21
|
+
let url = "/" ++ fileName
|
|
@@ -66,15 +66,25 @@ module Make = (
|
|
|
66
66
|
// A domain rejection from Behavior.decide is recorded as `CmdRejected` (no state change,
|
|
67
67
|
// surviving commands in the batch continue); a successful decide records `CmdOk(events)`
|
|
68
68
|
// and advances state via `Behavior.evolve`.
|
|
69
|
-
|
|
69
|
+
//
|
|
70
|
+
// `~seq` is the replayed sequence number the batch decides from, carried in so
|
|
71
|
+
// the line below can name the state rather than serialise it.
|
|
72
|
+
let processCommand = (~seq: int) => (
|
|
70
73
|
(state, outcomes): (Behavior.state, array<(string, cmdOutcome, Message.meta)>),
|
|
71
74
|
topicItem: CommandTopic.topicItem<Message.command'<Spec.Id.t, Spec.command>>,
|
|
72
75
|
) => {
|
|
73
76
|
let {reference, command: command'} = topicItem
|
|
74
77
|
let meta = command'->updateMeta
|
|
78
|
+
let id = command'.id->Spec.Id.toString
|
|
79
|
+
// Identity, not content: the folded state grows with the entity's whole
|
|
80
|
+
// history, so serialising it here makes the line's size a function of how
|
|
81
|
+
// long the system has been in use. The state at decision time is a
|
|
82
|
+
// debugger's subject, not a log's.
|
|
83
|
+
let cmdName =
|
|
84
|
+
command'.command->Message.encode(Spec.commandSchema)->Message.variantNameOfJson->LogFormat.bold
|
|
75
85
|
EffectLogger.logDebug(
|
|
76
86
|
~comp,
|
|
77
|
-
`deciding
|
|
87
|
+
`deciding: id=${id} seq=${seq->Int.toString} cmd=${cmdName}`,
|
|
78
88
|
)->Effect.runSync
|
|
79
89
|
switch Behavior.decide(state, command'.command) {
|
|
80
90
|
| Ok(generatedEvents) =>
|
|
@@ -89,7 +99,6 @@ module Make = (
|
|
|
89
99
|
payloadDict->Dict.toArray->Array.length == 0
|
|
90
100
|
? ""
|
|
91
101
|
: payloadDict->JSON.Encode.object->JSON.stringify
|
|
92
|
-
let id = command'.id->Spec.Id.toString
|
|
93
102
|
EffectLogger.logError(
|
|
94
103
|
~comp,
|
|
95
104
|
`decide rejected: ${errorCode} ${errorDetail} id=${id}`,
|
|
@@ -336,7 +345,7 @@ module Make = (
|
|
|
336
345
|
readState
|
|
337
346
|
->Effect.flatMap(((initialState, sequenceNr)) => {
|
|
338
347
|
let (finalState, outcomes) =
|
|
339
|
-
topicItemsForId->Array.reduce((initialState, []), processCommand)
|
|
348
|
+
topicItemsForId->Array.reduce((initialState, []), processCommand(~seq=sequenceNr))
|
|
340
349
|
|
|
341
350
|
let eventsToAppend =
|
|
342
351
|
outcomes
|
|
@@ -29,13 +29,15 @@ function Make(Spec) {
|
|
|
29
29
|
Spec.Id.makeFromString(param[0]),
|
|
30
30
|
param[1]
|
|
31
31
|
]));
|
|
32
|
-
let processCommand = (param, topicItem) => {
|
|
32
|
+
let processCommand = seq => ((param, topicItem) => {
|
|
33
33
|
let reference = topicItem.reference;
|
|
34
34
|
let command$p = topicItem.command;
|
|
35
35
|
let outcomes = param[1];
|
|
36
36
|
let state = param[0];
|
|
37
37
|
let meta = Message$ReventlessCore.deriveMeta(command$p.meta, undefined);
|
|
38
|
-
|
|
38
|
+
let id = Spec.Id.toString(command$p.id);
|
|
39
|
+
let cmdName = LogFormat$ReventlessCore.bold(Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(command$p.command, Spec.commandSchema)));
|
|
40
|
+
Effect.runSync(EffectLogger$ReventlessCore.logDebug(comp, undefined, `deciding: id=` + id + ` seq=` + seq.toString() + ` cmd=` + cmdName));
|
|
39
41
|
let generatedEvents = Behavior.decide(state, command$p.command);
|
|
40
42
|
if (generatedEvents.TAG === "Ok") {
|
|
41
43
|
let generatedEvents$1 = generatedEvents._0;
|
|
@@ -57,7 +59,6 @@ function Make(Spec) {
|
|
|
57
59
|
let match = Message$ReventlessCore.splitMessage(errorJson);
|
|
58
60
|
let payloadDict = match[1];
|
|
59
61
|
let errorDetail = Object.entries(payloadDict).length === 0 ? "" : JSON.stringify(payloadDict);
|
|
60
|
-
let id = Spec.Id.toString(command$p.id);
|
|
61
62
|
Effect.runSync(EffectLogger$ReventlessCore.logError(comp, undefined, `decide rejected: ` + errorCode + ` ` + errorDetail + ` id=` + id));
|
|
62
63
|
return [
|
|
63
64
|
state,
|
|
@@ -71,7 +72,7 @@ function Make(Spec) {
|
|
|
71
72
|
meta
|
|
72
73
|
]])
|
|
73
74
|
];
|
|
74
|
-
};
|
|
75
|
+
});
|
|
75
76
|
let replayCache = Lru$ReventlessCore.make(100);
|
|
76
77
|
let resetCache = () => Lru$ReventlessCore.clear(replayCache);
|
|
77
78
|
let snapshotConfig = Behavior.snapshot;
|
|
@@ -249,7 +250,7 @@ function Make(Spec) {
|
|
|
249
250
|
let match = Stdlib_Array.reduce(topicItemsForId, [
|
|
250
251
|
initialState,
|
|
251
252
|
[]
|
|
252
|
-
], processCommand);
|
|
253
|
+
], processCommand(sequenceNr));
|
|
253
254
|
let outcomes = match[1];
|
|
254
255
|
let finalState = match[0];
|
|
255
256
|
let eventsToAppend = outcomes.map(param => {
|
|
@@ -42,6 +42,7 @@ let rec fromSchemaType = (
|
|
|
42
42
|
switch st {
|
|
43
43
|
| ScalarString => `String${bang}`
|
|
44
44
|
| ScalarNumber => `Float${bang}`
|
|
45
|
+
| ScalarInt => `Int${bang}`
|
|
45
46
|
| ScalarBoolean => `Boolean${bang}`
|
|
46
47
|
| ScalarBigInt => `String${bang}`
|
|
47
48
|
| EntityId => `ID${bang}`
|
|
@@ -286,6 +287,7 @@ let rec scalarOfSchemaType = (st: SchemaType.schemaType): string =>
|
|
|
286
287
|
switch st {
|
|
287
288
|
| ScalarString => "String"
|
|
288
289
|
| ScalarNumber => "Float"
|
|
290
|
+
| ScalarInt => "Int"
|
|
289
291
|
| ScalarBoolean => "Boolean"
|
|
290
292
|
| ScalarBigInt => "String"
|
|
291
293
|
| EntityId => "ID"
|
|
@@ -43,6 +43,8 @@ function fromSchemaType(_required, _asInputOpt, _st, collectedTypes, seenTypes)
|
|
|
43
43
|
switch (st) {
|
|
44
44
|
case "ScalarNumber" :
|
|
45
45
|
return `Float` + bang;
|
|
46
|
+
case "ScalarInt" :
|
|
47
|
+
return `Int` + bang;
|
|
46
48
|
case "ScalarBoolean" :
|
|
47
49
|
return `Boolean` + bang;
|
|
48
50
|
case "EntityId" :
|
|
@@ -219,6 +221,8 @@ function scalarOfSchemaType(_st) {
|
|
|
219
221
|
switch (st) {
|
|
220
222
|
case "ScalarNumber" :
|
|
221
223
|
return "Float";
|
|
224
|
+
case "ScalarInt" :
|
|
225
|
+
return "Int";
|
|
222
226
|
case "ScalarBoolean" :
|
|
223
227
|
return "Boolean";
|
|
224
228
|
case "EntityId" :
|
|
@@ -24,7 +24,7 @@ function generateTools(pluginName, mutationEntries) {
|
|
|
24
24
|
if (fieldName.length <= 0) {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
let inputSchema = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema);
|
|
27
|
+
let inputSchema = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(undefined, undefined, schema);
|
|
28
28
|
let base = entryDescription.length > 0 ? entryDescription : `Execute ` + fieldName + ` on ` + pluginName;
|
|
29
29
|
let desc = buildDescriptionSuffix(base, entry);
|
|
30
30
|
tools.push({
|
|
@@ -39,7 +39,7 @@ function generateTools(pluginName, mutationEntries) {
|
|
|
39
39
|
if (fieldName.length <= 0) {
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
-
let inputSchema = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(variantSchema);
|
|
42
|
+
let inputSchema = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(undefined, undefined, variantSchema);
|
|
43
43
|
let obj = Stdlib_JSON.Decode.object(inputSchema);
|
|
44
44
|
let withId;
|
|
45
45
|
if (obj !== undefined) {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
type rec schemaType =
|
|
2
2
|
| ScalarString
|
|
3
3
|
| ScalarNumber
|
|
4
|
+
| /** A number the schema says is whole. sury carries the fact on the number's
|
|
5
|
+
`format`, and it was being dropped here — so every `int` in the domain
|
|
6
|
+
reached the SDL as `Float` and JSON Schema as `"number"`, and a quantity
|
|
7
|
+
was a field a client could legally send `2.5` in. */
|
|
8
|
+
ScalarInt
|
|
4
9
|
| ScalarBoolean
|
|
5
10
|
| ScalarBigInt
|
|
6
11
|
| EntityId
|
|
@@ -103,6 +108,9 @@ and shapeOf = (~parentName: string, ~fieldName: string, schema: S.t<unknown>): s
|
|
|
103
108
|
| String({const: ?Some(_)}) => ScalarString
|
|
104
109
|
| String(_) if isDateTime(schema) => DateTime
|
|
105
110
|
| String(_) => isIdFieldName(fieldName) ? EntityId : ScalarString
|
|
111
|
+
// `Int32` and `Port` are both whole-number formats; anything else (or no
|
|
112
|
+
// format at all) is a `float` as far as the schema is concerned.
|
|
113
|
+
| Number({format: ?Some(Int32 | Port)}) => ScalarInt
|
|
106
114
|
| Number(_) => ScalarNumber
|
|
107
115
|
| Boolean(_) => ScalarBoolean
|
|
108
116
|
| BigInt(_) => ScalarBigInt
|
|
@@ -85,7 +85,17 @@ function shapeOf(parentName, fieldName, schema) {
|
|
|
85
85
|
return "ScalarString";
|
|
86
86
|
}
|
|
87
87
|
case "number" :
|
|
88
|
-
|
|
88
|
+
let match = schema.format;
|
|
89
|
+
if (match === undefined) {
|
|
90
|
+
return "ScalarNumber";
|
|
91
|
+
}
|
|
92
|
+
switch (match) {
|
|
93
|
+
case "int32" :
|
|
94
|
+
case "port" :
|
|
95
|
+
return "ScalarInt";
|
|
96
|
+
case "integer" :
|
|
97
|
+
return "ScalarNumber";
|
|
98
|
+
}
|
|
89
99
|
case "bigint" :
|
|
90
100
|
return "ScalarBigInt";
|
|
91
101
|
case "boolean" :
|
|
@@ -168,12 +178,12 @@ function shapeOf(parentName, fieldName, schema) {
|
|
|
168
178
|
return $$enum;
|
|
169
179
|
}
|
|
170
180
|
}
|
|
171
|
-
let match = TaggedUnion$Reventless.classify(schema);
|
|
172
|
-
if (match === undefined) {
|
|
181
|
+
let match$1 = TaggedUnion$Reventless.classify(schema);
|
|
182
|
+
if (match$1 === undefined) {
|
|
173
183
|
return "Unknown";
|
|
174
184
|
}
|
|
175
|
-
let unionName = match[0];
|
|
176
|
-
let armTypes = match[1].map(param => {
|
|
185
|
+
let unionName = match$1[0];
|
|
186
|
+
let armTypes = match$1[1].map(param => {
|
|
177
187
|
let armSchema = param.schema;
|
|
178
188
|
let tag = param.tag;
|
|
179
189
|
let memberName = TaggedUnion$Reventless.memberTypeName(unionName, tag);
|