@reventlessdev/reventless-core 3.0.0-alpha.149 → 3.0.0-alpha.151
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 +15 -0
- package/package.json +7 -7
- package/src/components/Api/SuryToJsonSchema.res +5 -0
- package/src/components/Api/SuryToJsonSchema.res.mjs +4 -0
- package/src/plugin/connect/PluginExtensionPoint_Plugin.res +8 -0
- package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +20 -0
- package/src/plugin/lifecycle/PluginBehavior.res +15 -1
- package/src/plugin/lifecycle/PluginBehavior.res.mjs +74 -35
- package/src/plugin/lifecycle/PluginSpec.res +4 -0
- package/src/plugin/lifecycle/PluginSpec.res.mjs +5 -0
- package/src/plugin/lifecycle/PluginsProjection.res +4 -1
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +13 -2
- package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +2 -1
- package/tests/api/SuryToJsonSchemaTest.res +20 -0
- package/tests/api/SuryToJsonSchemaTest.res.mjs +51 -0
- package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.151 (2026-07-10)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **plugin-lifecycle:** backfill plugin kind onto existing rows via deploy-time RedetectPlugin ([502dbd6](https://github.com/ReventlessDev/reventless-core/commit/502dbd65d1fdd5b8beae8a7b96df06dcfa32fe3e))
|
|
11
|
+
* **plugin-lifecycle:** make read-model kind nullable so kind-less rows don't null the admin Plugins query ([671c4f6](https://github.com/ReventlessDev/reventless-core/commit/671c4f6cbbdde32a8dced7290fc8aa5b0063536f))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 3.0.0-alpha.150 (2026-07-10)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **reventless-ppx:** add [@group](https://github.com/group)By state-field annotation → x-reventless-group-by ([9e7a7b2](https://github.com/ReventlessDev/reventless-core/commit/9e7a7b29e54fb4eda1c6a145c0e7b6dcf26940ee))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.149 (2026-07-09)
|
|
7
22
|
|
|
8
23
|
### Bug Fixes
|
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.151",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -30,20 +30,20 @@
|
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
31
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.25",
|
|
32
32
|
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.10",
|
|
33
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.6",
|
|
34
|
-
"@reventlessdev/rescript-fast-csv": "1.2.0-alpha.11",
|
|
35
33
|
"@reventlessdev/rescript-node-streams": "1.1.0-alpha.10",
|
|
34
|
+
"@reventlessdev/rescript-fast-csv": "1.2.0-alpha.11",
|
|
35
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.6",
|
|
36
36
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
|
|
37
37
|
"@reventlessdev/rescript-ssh2": "1.1.0-alpha.11",
|
|
38
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.91",
|
|
39
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.69",
|
|
40
38
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.14",
|
|
41
|
-
"@reventlessdev/reventless-
|
|
39
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.93",
|
|
40
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.24",
|
|
41
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.71"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "^12.3.0",
|
|
45
45
|
"sury-ppx": "11.0.0-alpha.2",
|
|
46
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
46
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.52"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"rescript": "^12.3.0"
|
|
@@ -44,6 +44,11 @@ let mergeAnnotations = (
|
|
|
44
44
|
if spec.summary->Array.includes(fieldName) {
|
|
45
45
|
obj->Dict.set("x-reventless-summary", JSON.Encode.bool(true))
|
|
46
46
|
}
|
|
47
|
+
switch spec.groupBy {
|
|
48
|
+
| Some(name) if name === fieldName =>
|
|
49
|
+
obj->Dict.set("x-reventless-group-by", JSON.Encode.bool(true))
|
|
50
|
+
| _ => ()
|
|
51
|
+
}
|
|
47
52
|
switch spec.drillTargets->Array.find(((field, _)) => field === fieldName) {
|
|
48
53
|
| Some((_, sliceName)) =>
|
|
49
54
|
obj->Dict.set("x-reventless-drillTarget", JSON.Encode.string(sliceName))
|
|
@@ -41,6 +41,10 @@ function mergeAnnotations(fieldSchema, fieldName, spec) {
|
|
|
41
41
|
if (spec.summary.includes(fieldName)) {
|
|
42
42
|
obj["x-reventless-summary"] = true;
|
|
43
43
|
}
|
|
44
|
+
let name = spec.groupBy;
|
|
45
|
+
if (name !== undefined && name === fieldName) {
|
|
46
|
+
obj["x-reventless-group-by"] = true;
|
|
47
|
+
}
|
|
44
48
|
let match$1 = spec.drillTargets.find(param => param[0] === fieldName);
|
|
45
49
|
if (match$1 !== undefined) {
|
|
46
50
|
obj["x-reventless-drillTarget"] = match$1[1];
|
|
@@ -150,6 +150,14 @@ module Make = (Spec: Spec) => {
|
|
|
150
150
|
// 1 additional minute to allow additional latency
|
|
151
151
|
HandleDirective(directiveHandler, CreateDisconnectSchedule(id, interval + 2)),
|
|
152
152
|
]
|
|
153
|
+
| RedetectPlugin(interval) => [
|
|
154
|
+
// Deploy-time re-detect: drive Redetect (not Heartbeat) so an already-connected
|
|
155
|
+
// version re-runs the handshake and refreshes its stored definition. Still
|
|
156
|
+
// re-arm the disconnect schedule exactly like a heartbeat so liveness tracking
|
|
157
|
+
// continues from the deploy moment.
|
|
158
|
+
PublishCommand(Plugin.name(id), Delegate.Redetect(Plugin.version(id))),
|
|
159
|
+
HandleDirective(directiveHandler, CreateDisconnectSchedule(id, interval + 2)),
|
|
160
|
+
]
|
|
153
161
|
| ConnectPlugin(pluginDefinition) =>
|
|
154
162
|
// Validate protocol versions declared by the connecting plugin.
|
|
155
163
|
// On mismatch, emit a ReportIncompatibility command so that an
|
|
@@ -183,6 +183,26 @@ function Make(Spec) {
|
|
|
183
183
|
_0: cmd._0
|
|
184
184
|
}
|
|
185
185
|
}];
|
|
186
|
+
case "RedetectPlugin" :
|
|
187
|
+
return [
|
|
188
|
+
{
|
|
189
|
+
TAG: "PublishCommand",
|
|
190
|
+
_0: Plugin$ReventlessCore.name(id),
|
|
191
|
+
_1: {
|
|
192
|
+
TAG: "Redetect",
|
|
193
|
+
_0: Plugin$ReventlessCore.version(id)
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
TAG: "HandleDirective",
|
|
198
|
+
_0: directiveHandler,
|
|
199
|
+
_1: {
|
|
200
|
+
TAG: "CreateDisconnectSchedule",
|
|
201
|
+
_0: id,
|
|
202
|
+
_1: cmd._0 + 2 | 0
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
];
|
|
186
206
|
}
|
|
187
207
|
};
|
|
188
208
|
let mapOutgoingEvent = (id, event, _meta, _queryEngine) => {
|
|
@@ -145,10 +145,24 @@ let decide = (state, command) =>
|
|
|
145
145
|
| Some({status: Disconnected, definition}) => Ok(connectEvents(state, v, definition)) // reconnect
|
|
146
146
|
| Some({status: Inactive | Retired}) => Ok([]) // not heartbeat-revivable (admin only)
|
|
147
147
|
}
|
|
148
|
+
| Redetect(v) =>
|
|
149
|
+
// Deploy-time re-detect: unlike Heartbeat, a *connected* version re-runs the
|
|
150
|
+
// handshake (VersionDetected → ConnectPlugin → Connect) so the plugin re-answers
|
|
151
|
+
// with its current definition and Connect below refreshes the stored def. Unknown
|
|
152
|
+
// and disconnected versions detect exactly as a heartbeat would; archived versions
|
|
153
|
+
// stay admin-only.
|
|
154
|
+
switch state.known->Dict.get(v) {
|
|
155
|
+
| Some({status: Inactive | Retired}) => Ok([]) // archived — not re-detect-revivable
|
|
156
|
+
| None | Some({status: Connected | Disconnected}) => Ok([VersionDetected(v)])
|
|
157
|
+
}
|
|
148
158
|
| Connect(def) =>
|
|
149
159
|
let v = def.version
|
|
150
160
|
switch state.known->Dict.get(v) {
|
|
151
|
-
|
|
161
|
+
// Idempotent when the definition is unchanged; when a redeploy carries a changed
|
|
162
|
+
// definition (e.g. a newly added `kind`, updated uiFragments/protocols) re-emit
|
|
163
|
+
// VersionConnected to overwrite the stored def and re-project the row. The version
|
|
164
|
+
// is already current, so connectEvents' supersede stays empty — a bare refresh.
|
|
165
|
+
| Some({status: Connected, definition}) => definition == def ? Ok([]) : Ok([VersionConnected(def)])
|
|
152
166
|
| _ => Ok(connectEvents(state, v, def))
|
|
153
167
|
}
|
|
154
168
|
| Disconnect(v) =>
|
|
@@ -172,55 +172,94 @@ function decide(state, command) {
|
|
|
172
172
|
_0: []
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
-
case "
|
|
176
|
-
let
|
|
177
|
-
let v$1 = def.version;
|
|
175
|
+
case "Redetect" :
|
|
176
|
+
let v$1 = command._0;
|
|
178
177
|
let match$1 = state.known[v$1];
|
|
179
|
-
if (match$1
|
|
178
|
+
if (match$1 === undefined) {
|
|
180
179
|
return {
|
|
181
180
|
TAG: "Ok",
|
|
182
|
-
_0: [
|
|
181
|
+
_0: [{
|
|
182
|
+
TAG: "VersionDetected",
|
|
183
|
+
_0: v$1
|
|
184
|
+
}]
|
|
183
185
|
};
|
|
186
|
+
}
|
|
187
|
+
switch (match$1.status) {
|
|
188
|
+
case "Connected" :
|
|
189
|
+
case "Disconnected" :
|
|
190
|
+
return {
|
|
191
|
+
TAG: "Ok",
|
|
192
|
+
_0: [{
|
|
193
|
+
TAG: "VersionDetected",
|
|
194
|
+
_0: v$1
|
|
195
|
+
}]
|
|
196
|
+
};
|
|
197
|
+
case "Inactive" :
|
|
198
|
+
case "Retired" :
|
|
199
|
+
return {
|
|
200
|
+
TAG: "Ok",
|
|
201
|
+
_0: []
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
case "Connect" :
|
|
205
|
+
let def = command._0;
|
|
206
|
+
let v$2 = def.version;
|
|
207
|
+
let match$2 = state.known[v$2];
|
|
208
|
+
if (match$2 !== undefined && match$2.status === "Connected") {
|
|
209
|
+
if (Primitive_object.equal(match$2.definition, def)) {
|
|
210
|
+
return {
|
|
211
|
+
TAG: "Ok",
|
|
212
|
+
_0: []
|
|
213
|
+
};
|
|
214
|
+
} else {
|
|
215
|
+
return {
|
|
216
|
+
TAG: "Ok",
|
|
217
|
+
_0: [{
|
|
218
|
+
TAG: "VersionConnected",
|
|
219
|
+
_0: def
|
|
220
|
+
}]
|
|
221
|
+
};
|
|
222
|
+
}
|
|
184
223
|
} else {
|
|
185
224
|
return {
|
|
186
225
|
TAG: "Ok",
|
|
187
|
-
_0: connectEvents(state, v$
|
|
226
|
+
_0: connectEvents(state, v$2, def)
|
|
188
227
|
};
|
|
189
228
|
}
|
|
190
229
|
case "Disconnect" :
|
|
191
|
-
let v$
|
|
192
|
-
let match$
|
|
193
|
-
if (match$
|
|
230
|
+
let v$3 = command._0;
|
|
231
|
+
let match$3 = state.known[v$3];
|
|
232
|
+
if (match$3 === undefined) {
|
|
194
233
|
return {
|
|
195
234
|
TAG: "Ok",
|
|
196
235
|
_0: []
|
|
197
236
|
};
|
|
198
237
|
}
|
|
199
|
-
if (match$
|
|
238
|
+
if (match$3.status !== "Connected") {
|
|
200
239
|
return {
|
|
201
240
|
TAG: "Ok",
|
|
202
241
|
_0: []
|
|
203
242
|
};
|
|
204
243
|
}
|
|
205
|
-
let definition = match$
|
|
244
|
+
let definition = match$3.definition;
|
|
206
245
|
return {
|
|
207
246
|
TAG: "Ok",
|
|
208
247
|
_0: [{
|
|
209
248
|
TAG: "VersionDisconnected",
|
|
210
249
|
_0: definition
|
|
211
|
-
}].concat(uiDeregisterEvents(definition.id, definition.uiFragments)).concat(promoteEvents(state, v$
|
|
250
|
+
}].concat(uiDeregisterEvents(definition.id, definition.uiFragments)).concat(promoteEvents(state, v$3))
|
|
212
251
|
};
|
|
213
252
|
case "Activate" :
|
|
214
|
-
let v$
|
|
215
|
-
let match$
|
|
216
|
-
if (match$
|
|
253
|
+
let v$4 = command._0;
|
|
254
|
+
let match$4 = state.known[v$4];
|
|
255
|
+
if (match$4 === undefined) {
|
|
217
256
|
return {
|
|
218
257
|
TAG: "Error",
|
|
219
258
|
_0: "UnknownVersion"
|
|
220
259
|
};
|
|
221
260
|
}
|
|
222
|
-
let definition$1 = match$
|
|
223
|
-
switch (match$
|
|
261
|
+
let definition$1 = match$4.definition;
|
|
262
|
+
switch (match$4.status) {
|
|
224
263
|
case "Connected" :
|
|
225
264
|
return {
|
|
226
265
|
TAG: "Ok",
|
|
@@ -240,17 +279,17 @@ function decide(state, command) {
|
|
|
240
279
|
_0: definition$1
|
|
241
280
|
}].concat(uiRegisterEvents(definition$1.id, definition$1.uiFragments));
|
|
242
281
|
let supersede;
|
|
243
|
-
if (currentAfterConnect(state.known, v$
|
|
282
|
+
if (currentAfterConnect(state.known, v$4) === v$4) {
|
|
244
283
|
let c = state.current;
|
|
245
284
|
if (c !== undefined) {
|
|
246
|
-
if (c !== v$
|
|
247
|
-
let match$
|
|
248
|
-
supersede = match$
|
|
285
|
+
if (c !== v$4) {
|
|
286
|
+
let match$5 = state.known[c];
|
|
287
|
+
supersede = match$5 !== undefined ? [{
|
|
249
288
|
TAG: "VersionSuperseded",
|
|
250
289
|
_0: {
|
|
251
290
|
supersededVersion: c,
|
|
252
|
-
supersededDefinition: match$
|
|
253
|
-
newVersion: v$
|
|
291
|
+
supersededDefinition: match$5.definition,
|
|
292
|
+
newVersion: v$4,
|
|
254
293
|
newDefinition: definition$1
|
|
255
294
|
}
|
|
256
295
|
}] : [];
|
|
@@ -269,16 +308,16 @@ function decide(state, command) {
|
|
|
269
308
|
};
|
|
270
309
|
break;
|
|
271
310
|
case "Deactivate" :
|
|
272
|
-
let v$
|
|
273
|
-
let match$
|
|
274
|
-
if (match$
|
|
311
|
+
let v$5 = command._0;
|
|
312
|
+
let match$6 = state.known[v$5];
|
|
313
|
+
if (match$6 === undefined) {
|
|
275
314
|
return {
|
|
276
315
|
TAG: "Error",
|
|
277
316
|
_0: "UnknownVersion"
|
|
278
317
|
};
|
|
279
318
|
}
|
|
280
|
-
let definition$2 = match$
|
|
281
|
-
switch (match$
|
|
319
|
+
let definition$2 = match$6.definition;
|
|
320
|
+
switch (match$6.status) {
|
|
282
321
|
case "Connected" :
|
|
283
322
|
case "Disconnected" :
|
|
284
323
|
break;
|
|
@@ -294,7 +333,7 @@ function decide(state, command) {
|
|
|
294
333
|
_0: [{
|
|
295
334
|
TAG: "VersionDeactivated",
|
|
296
335
|
_0: definition$2
|
|
297
|
-
}].concat(uiDeregisterEvents(definition$2.id, definition$2.uiFragments)).concat(promoteEvents(state, v$
|
|
336
|
+
}].concat(uiDeregisterEvents(definition$2.id, definition$2.uiFragments)).concat(promoteEvents(state, v$5))
|
|
298
337
|
};
|
|
299
338
|
break;
|
|
300
339
|
case "ReportIncompatibility" :
|
|
@@ -306,16 +345,16 @@ function decide(state, command) {
|
|
|
306
345
|
}]
|
|
307
346
|
};
|
|
308
347
|
case "Retire" :
|
|
309
|
-
let v$
|
|
310
|
-
let match$
|
|
311
|
-
if (match$
|
|
348
|
+
let v$6 = command._0;
|
|
349
|
+
let match$7 = state.known[v$6];
|
|
350
|
+
if (match$7 === undefined) {
|
|
312
351
|
return {
|
|
313
352
|
TAG: "Error",
|
|
314
353
|
_0: "UnknownVersion"
|
|
315
354
|
};
|
|
316
355
|
}
|
|
317
|
-
let definition$3 = match$
|
|
318
|
-
if (match$
|
|
356
|
+
let definition$3 = match$7.definition;
|
|
357
|
+
if (match$7.status === "Retired") {
|
|
319
358
|
return {
|
|
320
359
|
TAG: "Ok",
|
|
321
360
|
_0: []
|
|
@@ -326,7 +365,7 @@ function decide(state, command) {
|
|
|
326
365
|
_0: [{
|
|
327
366
|
TAG: "VersionRetired",
|
|
328
367
|
_0: definition$3
|
|
329
|
-
}].concat(uiDeregisterEvents(definition$3.id, definition$3.uiFragments)).concat(promoteEvents(state, v$
|
|
368
|
+
}].concat(uiDeregisterEvents(definition$3.id, definition$3.uiFragments)).concat(promoteEvents(state, v$6))
|
|
330
369
|
};
|
|
331
370
|
}
|
|
332
371
|
}
|
|
@@ -10,6 +10,10 @@ open Reventless.Plugin
|
|
|
10
10
|
@schema
|
|
11
11
|
type command =
|
|
12
12
|
| @noApi Heartbeat(version)
|
|
13
|
+
// Deploy-time re-detect (from RedetectPlugin). Forces the connect handshake to
|
|
14
|
+
// re-run for an already-connected version so its definition is refreshed on the
|
|
15
|
+
// row — unlike Heartbeat, which no-ops a connected keep-alive.
|
|
16
|
+
| @noApi Redetect(version)
|
|
13
17
|
| @noApi Connect(pluginDefinition)
|
|
14
18
|
| @noApi Disconnect(version)
|
|
15
19
|
// Admin lifecycle commands — API-exposed (auto-derived admin mutations,
|
|
@@ -9,6 +9,10 @@ let commandSchema = S.union([
|
|
|
9
9
|
TAG: "Heartbeat",
|
|
10
10
|
_0: s.m(Plugin$Reventless.versionSchema)
|
|
11
11
|
})),
|
|
12
|
+
S.schema(s => ({
|
|
13
|
+
TAG: "Redetect",
|
|
14
|
+
_0: s.m(Plugin$Reventless.versionSchema)
|
|
15
|
+
})),
|
|
12
16
|
S.schema(s => ({
|
|
13
17
|
TAG: "Connect",
|
|
14
18
|
_0: s.m(Plugin$Reventless.pluginDefinitionSchema)
|
|
@@ -117,6 +121,7 @@ let errorSchema = S.union([
|
|
|
117
121
|
|
|
118
122
|
let commandSchema$1 = Api$ReventlessInfra.markNoApiVariants(commandSchema, [
|
|
119
123
|
"Heartbeat",
|
|
124
|
+
"Redetect",
|
|
120
125
|
"Connect",
|
|
121
126
|
"Disconnect",
|
|
122
127
|
"ReportIncompatibility"
|
|
@@ -58,7 +58,10 @@ let displayState = (
|
|
|
58
58
|
uiFragments: def.uiFragments,
|
|
59
59
|
structure: def.structure,
|
|
60
60
|
dcbEventLog: def.dcbEventLog,
|
|
61
|
-
kind
|
|
61
|
+
// A freshly projected row always knows its kind (the definition carries it as a
|
|
62
|
+
// mandatory field). `option` here is purely for rows persisted before `kind`
|
|
63
|
+
// existed — see PluginsReadModelSpec.state.kind.
|
|
64
|
+
kind: Some(def.kind),
|
|
62
65
|
// The current version is never listed among the "other" connected versions.
|
|
63
66
|
otherConnectedVersions: otherConnectedVersions->Util.without(def.version),
|
|
64
67
|
}
|
|
@@ -42,7 +42,18 @@ type state = {
|
|
|
42
42
|
// @scan opts the field into server-side equality filtering so the connection gains
|
|
43
43
|
// a `kindEq` filter — the panel split (main list vs System/Infrastructure) pages
|
|
44
44
|
// correctly server-side rather than filtering a page client-side.
|
|
45
|
-
@
|
|
45
|
+
// @groupBy tags this field as the list view's section key (emitted as
|
|
46
|
+
// `x-reventless-group-by`); AutoUI sections the admin Plugins view by kind.
|
|
47
|
+
//
|
|
48
|
+
// **Nullable on purpose.** Rows projected before `kind` existed carry no `kind`
|
|
49
|
+
// attribute, and the deploy-time RedetectPlugin backfill only stamps a row on the
|
|
50
|
+
// owning plugin's next deploy. A non-null `PluginKind!` would make GraphQL null the
|
|
51
|
+
// entire `Platform_Plugins` query the moment a single kind-less row is returned
|
|
52
|
+
// (existing rows, a newly-connecting plugin before it's stamped, or an internal
|
|
53
|
+
// bookkeeping row). Optional here → nullable `Platform_PluginKind` in the SDL, so a
|
|
54
|
+
// blank kind degrades to a missing group (AutoListView's trailing group) instead of
|
|
55
|
+
// taking down the whole list. Backfill then upgrades blanks to real kinds.
|
|
56
|
+
@scan @groupBy kind: option<Reventless.Plugin.pluginKind>,
|
|
46
57
|
// Other versions of this name that are currently **Connected** — excludes this
|
|
47
58
|
// row's own `version` (whose status is the `status` field above). Lets the
|
|
48
59
|
// projection recompute which version is current (highest Connected) during a
|
|
@@ -67,7 +78,7 @@ type queryResult = {
|
|
|
67
78
|
uiFragments: option<Reventless.Plugin.uiFragmentManifest>,
|
|
68
79
|
structure: option<Reventless.Plugin.pluginStructure>,
|
|
69
80
|
dcbEventLog: option<Reventless.Plugin.dcbEventLogDefinition>,
|
|
70
|
-
kind: Reventless.Plugin.pluginKind
|
|
81
|
+
kind: option<Reventless.Plugin.pluginKind>,
|
|
71
82
|
}
|
|
72
83
|
|
|
73
84
|
|
|
@@ -28,7 +28,7 @@ let stateSchema = S.schema(s => ({
|
|
|
28
28
|
uiFragments: s.m(Plugin$Reventless.uiFragmentManifestOptionSchema),
|
|
29
29
|
structure: s.m(Plugin$Reventless.pluginStructureOptionSchema),
|
|
30
30
|
dcbEventLog: s.m(Plugin$Reventless.dcbEventLogOptionSchema),
|
|
31
|
-
kind: s.m(Plugin$Reventless.pluginKindSchema),
|
|
31
|
+
kind: s.m(S.option(Plugin$Reventless.pluginKindSchema)),
|
|
32
32
|
otherConnectedVersions: s.m(S.array(Plugin$Reventless.versionSchema))
|
|
33
33
|
}));
|
|
34
34
|
|
|
@@ -48,6 +48,7 @@ let stateSchema$1 = S.Metadata.set(stateSchema, StateAnnotations$Reventless.stat
|
|
|
48
48
|
scan: ["kind"],
|
|
49
49
|
scanSort: [],
|
|
50
50
|
status: undefined,
|
|
51
|
+
groupBy: "kind",
|
|
51
52
|
visibility: undefined
|
|
52
53
|
});
|
|
53
54
|
|
|
@@ -30,6 +30,7 @@ let emptySpec: Reventless.StateAnnotations.stateAnnotationSpec = {
|
|
|
30
30
|
scan: [],
|
|
31
31
|
scanSort: [],
|
|
32
32
|
status: None,
|
|
33
|
+
groupBy: None,
|
|
33
34
|
visibility: None,
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -305,6 +306,25 @@ describe("SuryToJsonSchema:", () => {
|
|
|
305
306
|
))->toEqual((Some(true), None))
|
|
306
307
|
})
|
|
307
308
|
|
|
309
|
+
testSync("emits x-reventless-group-by on the field named by groupBy", () => {
|
|
310
|
+
let schema = S.schema(s =>
|
|
311
|
+
{
|
|
312
|
+
"id": s.matches(S.string),
|
|
313
|
+
"kind": s.matches(S.string),
|
|
314
|
+
}
|
|
315
|
+
)->S.castToUnknown
|
|
316
|
+
let schema' = schema->withSpec({...emptySpec, groupBy: Some("kind")})
|
|
317
|
+
let json = SuryToJsonSchema.deriveObjectSchema(schema')
|
|
318
|
+
let kindSchema = getPropertyOf(json, "kind")
|
|
319
|
+
let idSchema = getPropertyOf(json, "id")
|
|
320
|
+
expect((
|
|
321
|
+
kindSchema
|
|
322
|
+
->Option.flatMap(s => getProperty(s, "x-reventless-group-by"))
|
|
323
|
+
->Option.flatMap(JSON.Decode.bool),
|
|
324
|
+
idSchema->Option.flatMap(s => getProperty(s, "x-reventless-group-by")),
|
|
325
|
+
))->toEqual((Some(true), None))
|
|
326
|
+
})
|
|
327
|
+
|
|
308
328
|
testSync("emits x-reventless-collapsed on field listed in collapsed", () => {
|
|
309
329
|
let schema = S.schema(s =>
|
|
310
330
|
{
|
|
@@ -64,6 +64,7 @@ let emptySpec = {
|
|
|
64
64
|
scan: emptySpec_scan,
|
|
65
65
|
scanSort: emptySpec_scanSort,
|
|
66
66
|
status: undefined,
|
|
67
|
+
groupBy: undefined,
|
|
67
68
|
visibility: undefined
|
|
68
69
|
};
|
|
69
70
|
|
|
@@ -100,6 +101,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
100
101
|
scan: emptySpec_scan,
|
|
101
102
|
scanSort: emptySpec_scanSort,
|
|
102
103
|
status: undefined,
|
|
104
|
+
groupBy: undefined,
|
|
103
105
|
visibility: undefined
|
|
104
106
|
});
|
|
105
107
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -129,6 +131,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
129
131
|
scan: emptySpec_scan,
|
|
130
132
|
scanSort: emptySpec_scanSort,
|
|
131
133
|
status: undefined,
|
|
134
|
+
groupBy: undefined,
|
|
132
135
|
visibility: undefined
|
|
133
136
|
});
|
|
134
137
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -161,6 +164,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
161
164
|
scan: emptySpec_scan,
|
|
162
165
|
scanSort: emptySpec_scanSort,
|
|
163
166
|
status: undefined,
|
|
167
|
+
groupBy: undefined,
|
|
164
168
|
visibility: undefined
|
|
165
169
|
});
|
|
166
170
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -189,6 +193,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
189
193
|
scan: emptySpec_scan,
|
|
190
194
|
scanSort: emptySpec_scanSort,
|
|
191
195
|
status: undefined,
|
|
196
|
+
groupBy: undefined,
|
|
192
197
|
visibility: undefined
|
|
193
198
|
});
|
|
194
199
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -217,6 +222,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
217
222
|
scan: emptySpec_scan,
|
|
218
223
|
scanSort: emptySpec_scanSort,
|
|
219
224
|
status: undefined,
|
|
225
|
+
groupBy: undefined,
|
|
220
226
|
visibility: undefined
|
|
221
227
|
});
|
|
222
228
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -242,6 +248,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
242
248
|
scan: emptySpec_scan,
|
|
243
249
|
scanSort: emptySpec_scanSort,
|
|
244
250
|
status: undefined,
|
|
251
|
+
groupBy: undefined,
|
|
245
252
|
visibility: undefined
|
|
246
253
|
});
|
|
247
254
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -267,6 +274,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
267
274
|
scan: emptySpec_scan,
|
|
268
275
|
scanSort: emptySpec_scanSort,
|
|
269
276
|
status: undefined,
|
|
277
|
+
groupBy: undefined,
|
|
270
278
|
visibility: undefined
|
|
271
279
|
});
|
|
272
280
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -299,6 +307,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
299
307
|
scan: emptySpec_scan,
|
|
300
308
|
scanSort: emptySpec_scanSort,
|
|
301
309
|
status: undefined,
|
|
310
|
+
groupBy: undefined,
|
|
302
311
|
visibility: undefined
|
|
303
312
|
});
|
|
304
313
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -334,6 +343,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
334
343
|
scan: emptySpec_scan,
|
|
335
344
|
scanSort: emptySpec_scanSort,
|
|
336
345
|
status: undefined,
|
|
346
|
+
groupBy: undefined,
|
|
337
347
|
visibility: undefined
|
|
338
348
|
});
|
|
339
349
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -372,6 +382,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
372
382
|
scan: emptySpec_scan,
|
|
373
383
|
scanSort: emptySpec_scanSort,
|
|
374
384
|
status: undefined,
|
|
385
|
+
groupBy: undefined,
|
|
375
386
|
visibility: undefined
|
|
376
387
|
});
|
|
377
388
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -400,6 +411,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
400
411
|
scan: emptySpec_scan,
|
|
401
412
|
scanSort: emptySpec_scanSort,
|
|
402
413
|
status: undefined,
|
|
414
|
+
groupBy: undefined,
|
|
403
415
|
visibility: undefined
|
|
404
416
|
});
|
|
405
417
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -425,6 +437,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
425
437
|
scan: ["status"],
|
|
426
438
|
scanSort: emptySpec_scanSort,
|
|
427
439
|
status: undefined,
|
|
440
|
+
groupBy: undefined,
|
|
428
441
|
visibility: undefined
|
|
429
442
|
});
|
|
430
443
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -457,6 +470,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
457
470
|
scan: emptySpec_scan,
|
|
458
471
|
scanSort: ["name"],
|
|
459
472
|
status: undefined,
|
|
473
|
+
groupBy: undefined,
|
|
460
474
|
visibility: undefined
|
|
461
475
|
});
|
|
462
476
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -470,6 +484,39 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
470
484
|
undefined
|
|
471
485
|
]);
|
|
472
486
|
});
|
|
487
|
+
globalThis.test("emits x-reventless-group-by on the field named by groupBy", () => {
|
|
488
|
+
let schema = S.schema(s => ({
|
|
489
|
+
id: s.m(S.string),
|
|
490
|
+
kind: s.m(S.string)
|
|
491
|
+
}));
|
|
492
|
+
let schema$p = withSpec(schema, {
|
|
493
|
+
ids: emptySpec_ids,
|
|
494
|
+
compositeIds: emptySpec_compositeIds,
|
|
495
|
+
subIds: emptySpec_subIds,
|
|
496
|
+
compositeSubIds: emptySpec_compositeSubIds,
|
|
497
|
+
indexes: emptySpec_indexes,
|
|
498
|
+
hidden: emptySpec_hidden,
|
|
499
|
+
summary: emptySpec_summary,
|
|
500
|
+
drillTargets: emptySpec_drillTargets,
|
|
501
|
+
drillTargetKeys: emptySpec_drillTargetKeys,
|
|
502
|
+
collapsed: emptySpec_collapsed,
|
|
503
|
+
scan: emptySpec_scan,
|
|
504
|
+
scanSort: emptySpec_scanSort,
|
|
505
|
+
status: undefined,
|
|
506
|
+
groupBy: "kind",
|
|
507
|
+
visibility: undefined
|
|
508
|
+
});
|
|
509
|
+
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
510
|
+
let kindSchema = getPropertyOf(json, "kind");
|
|
511
|
+
let idSchema = getPropertyOf(json, "id");
|
|
512
|
+
globalThis.expect([
|
|
513
|
+
Stdlib_Option.flatMap(Stdlib_Option.flatMap(kindSchema, s => getProperty(s, "x-reventless-group-by")), Stdlib_JSON.Decode.bool),
|
|
514
|
+
Stdlib_Option.flatMap(idSchema, s => getProperty(s, "x-reventless-group-by"))
|
|
515
|
+
]).toEqual([
|
|
516
|
+
true,
|
|
517
|
+
undefined
|
|
518
|
+
]);
|
|
519
|
+
});
|
|
473
520
|
globalThis.test("emits x-reventless-collapsed on field listed in collapsed", () => {
|
|
474
521
|
let schema = S.schema(s => ({
|
|
475
522
|
id: s.m(S.string),
|
|
@@ -489,6 +536,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
489
536
|
scan: emptySpec_scan,
|
|
490
537
|
scanSort: emptySpec_scanSort,
|
|
491
538
|
status: undefined,
|
|
539
|
+
groupBy: undefined,
|
|
492
540
|
visibility: undefined
|
|
493
541
|
});
|
|
494
542
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -520,6 +568,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
520
568
|
scan: emptySpec_scan,
|
|
521
569
|
scanSort: emptySpec_scanSort,
|
|
522
570
|
status: undefined,
|
|
571
|
+
groupBy: undefined,
|
|
523
572
|
visibility: "Internal"
|
|
524
573
|
});
|
|
525
574
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -543,6 +592,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
543
592
|
scan: emptySpec_scan,
|
|
544
593
|
scanSort: emptySpec_scanSort,
|
|
545
594
|
status: undefined,
|
|
595
|
+
groupBy: undefined,
|
|
546
596
|
visibility: undefined
|
|
547
597
|
});
|
|
548
598
|
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(schema$p);
|
|
@@ -606,6 +656,7 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
606
656
|
scan: emptySpec_scan,
|
|
607
657
|
scanSort: emptySpec_scanSort,
|
|
608
658
|
status: undefined,
|
|
659
|
+
groupBy: undefined,
|
|
609
660
|
visibility: undefined
|
|
610
661
|
});
|
|
611
662
|
let native = S.toJSONSchema(schema$p);
|