@reventlessdev/reventless-spec 3.0.0-alpha.128 → 3.0.0-alpha.129
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 +40 -0
- package/package.json +2 -2
- package/src/AnsiStyle.res +31 -7
- package/src/AnsiStyle.res.mjs +16 -2
- package/src/components/Plugin.res +59 -85
- package/src/components/Plugin.res.mjs +46 -82
- package/src/generator/GraftTrait.res +5 -4
- package/src/generator/GraftTrait.res.mjs +4 -4
- package/src/types/Message.res +14 -12
- package/src/types/Message.res.mjs +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,46 @@
|
|
|
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.129 (2026-09-04)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **logging:** let a runtime declare whether a person reads its logs ([df7a6cb](https://github.com/ReventlessDev/reventless-core/commit/df7a6cbdff62b856ea6b9d1f169b913563ba13a1))
|
|
11
|
+
* feat(spec)!: one optional encoding on the wire, with no annotation ([320f91d](https://github.com/ReventlessDev/reventless-core/commit/320f91daa8bd90812a6e82069e7a1cb473041930))
|
|
12
|
+
|
|
13
|
+
### BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
* the two encodings cannot read each other. Stored
|
|
16
|
+
pluginDefinition / pluginStructure payloads and already-deployed plugins must
|
|
17
|
+
go — wipe the platform scope (SEED_RESET_SCOPE=platform), quiesce, and
|
|
18
|
+
redeploy the fleet from one commit. Domain plugin data is untouched.
|
|
19
|
+
|
|
20
|
+
Two guards had to learn the new shape, both of which defined "optional" as
|
|
21
|
+
has.null and so mistook an omitted key for something to invent:
|
|
22
|
+
|
|
23
|
+
- Message.fillMissingDefaults reached `return undefined` only below two arms
|
|
24
|
+
that fire first — an enum's first const, and an object member filled with
|
|
25
|
+
zeros. Absent option<record> therefore healed to a zero-filled record, not
|
|
26
|
+
None: on the real schema, dcbEventLog became Some({name: "", eventTopicArn:
|
|
27
|
+
""}), which manageSubscriptions would have read as a peer to subscribe to.
|
|
28
|
+
One line, mirroring the has.null guard, above both arms.
|
|
29
|
+
- PluginDefinitionScalars' walker reported 16 optional fields as newly-added
|
|
30
|
+
bare required scalars. With both encodings understood, the golden list is
|
|
31
|
+
unchanged.
|
|
32
|
+
|
|
33
|
+
The frozen lifecycle corpus holds five real payloads in the old encoding.
|
|
34
|
+
Null-valued keys were stripped mechanically — the rewrite round-trips each
|
|
35
|
+
file unchanged before editing, and a key-by-key diff shows null removals and
|
|
36
|
+
nothing else. The README records it beside the account-id redaction and says
|
|
37
|
+
why it is not a regeneration: no fixture was rebuilt from ReScript types, so
|
|
38
|
+
every generation in its table is still pinned.
|
|
39
|
+
|
|
40
|
+
check:graphql is unchanged, as expected: SchemaType.fromSury collapses Null
|
|
41
|
+
and Undefined to the same Nullable, so the emitted schema never distinguished
|
|
42
|
+
them.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
6
46
|
# 3.0.0-alpha.128 (2026-09-04)
|
|
7
47
|
|
|
8
48
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-spec",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.129",
|
|
4
4
|
"description": "Specifications for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"sury": "11.0.0-rc.2",
|
|
25
25
|
"sury-ppx": "11.0.0-rc.2",
|
|
26
26
|
"yaml": "^2.8.3",
|
|
27
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.
|
|
27
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.9"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"rescript": "12.3.0",
|
package/src/AnsiStyle.res
CHANGED
|
@@ -11,14 +11,23 @@
|
|
|
11
11
|
@val external _isTty: option<bool> = "process.stdout.isTTY"
|
|
12
12
|
@val external _logFormat: option<string> = "process.env.REVENTLESS_LOG_FORMAT"
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
// a
|
|
16
|
-
//
|
|
14
|
+
// What this runtime is, for when stdout cannot say. A local dev platform's logs
|
|
15
|
+
// are read by a person even when its stdout is a pipe — `concurrently`, `tsx
|
|
16
|
+
// watch` and an IDE terminal all pipe — while a Lambda's are read by a collector
|
|
17
|
+
// through a pipe that looks identical. TTY-ness is only a proxy for "a person is
|
|
18
|
+
// reading this", and a pipe is exactly where the proxy fails, so a runtime that
|
|
19
|
+
// knows which it is says so instead of being guessed at.
|
|
20
|
+
let _default: ref<option<string>> = ref(None)
|
|
21
|
+
|
|
22
|
+
// "json" | "text", in precedence order: an explicit REVENTLESS_LOG_FORMAT, then
|
|
23
|
+
// what the runtime declared itself to be, then the TTY probe — a TTY stdout ⇒
|
|
24
|
+
// text, everything else (Lambda, Fargate, ECS, Docker, CI) ⇒ structured JSON.
|
|
17
25
|
let _resolveFormat = (): string =>
|
|
18
|
-
switch (_logFormat, _isTty) {
|
|
19
|
-
| (Some("json"), _) => "json"
|
|
20
|
-
| (Some("text"), _) => "text"
|
|
21
|
-
| (_, Some(
|
|
26
|
+
switch (_logFormat, _default.contents, _isTty) {
|
|
27
|
+
| (Some("json"), _, _) => "json"
|
|
28
|
+
| (Some("text"), _, _) => "text"
|
|
29
|
+
| (_, Some(declared), _) => declared
|
|
30
|
+
| (_, None, Some(true)) => "text"
|
|
22
31
|
| _ => "json"
|
|
23
32
|
}
|
|
24
33
|
|
|
@@ -27,6 +36,21 @@ let _resolveFormat = (): string =>
|
|
|
27
36
|
// keeps every log call off `process.env`.
|
|
28
37
|
let _format = ref(_resolveFormat())
|
|
29
38
|
|
|
39
|
+
/** Declares what this runtime is, for the pipe case the TTY probe gets wrong.
|
|
40
|
+
Call it at module init, before anything logs. An explicit
|
|
41
|
+
REVENTLESS_LOG_FORMAT still wins, so a developer can always ask for the other
|
|
42
|
+
one. A runtime that says nothing keeps the TTY behaviour. */
|
|
43
|
+
let setDefaultFormat = (format: [#text | #json]) => {
|
|
44
|
+
_default :=
|
|
45
|
+
Some(
|
|
46
|
+
switch format {
|
|
47
|
+
| #text => "text"
|
|
48
|
+
| #json => "json"
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
_format := _resolveFormat()
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
/** Test-only: re-evaluate REVENTLESS_LOG_FORMAT / process.stdout.isTTY. */
|
|
31
55
|
let reload = () => _format := _resolveFormat()
|
|
32
56
|
|
package/src/AnsiStyle.res.mjs
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
let _default = {
|
|
5
|
+
contents: undefined
|
|
6
|
+
};
|
|
7
|
+
|
|
4
8
|
function _resolveFormat() {
|
|
5
9
|
let match = process.env.REVENTLESS_LOG_FORMAT;
|
|
6
|
-
let match$1 =
|
|
10
|
+
let match$1 = _default.contents;
|
|
11
|
+
let match$2 = process.stdout.isTTY;
|
|
7
12
|
if (match !== undefined) {
|
|
8
13
|
switch (match) {
|
|
9
14
|
case "json" :
|
|
@@ -12,7 +17,9 @@ function _resolveFormat() {
|
|
|
12
17
|
return "text";
|
|
13
18
|
}
|
|
14
19
|
}
|
|
15
|
-
if (match$1 !== undefined
|
|
20
|
+
if (match$1 !== undefined) {
|
|
21
|
+
return match$1;
|
|
22
|
+
} else if (match$2 !== undefined && match$2) {
|
|
16
23
|
return "text";
|
|
17
24
|
} else {
|
|
18
25
|
return "json";
|
|
@@ -23,6 +30,11 @@ let _format = {
|
|
|
23
30
|
contents: _resolveFormat()
|
|
24
31
|
};
|
|
25
32
|
|
|
33
|
+
function setDefaultFormat(format) {
|
|
34
|
+
_default.contents = format === "text" ? "text" : "json";
|
|
35
|
+
_format.contents = _resolveFormat();
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
function reload() {
|
|
27
39
|
_format.contents = _resolveFormat();
|
|
28
40
|
}
|
|
@@ -44,8 +56,10 @@ function bold(s) {
|
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
export {
|
|
59
|
+
_default,
|
|
47
60
|
_resolveFormat,
|
|
48
61
|
_format,
|
|
62
|
+
setDefaultFormat,
|
|
49
63
|
reload,
|
|
50
64
|
isJsonSink,
|
|
51
65
|
useAnsi,
|
|
@@ -73,13 +73,13 @@ type apiSchemaFragment = {encoded: string, protocol: string}
|
|
|
73
73
|
@schema
|
|
74
74
|
type apiTarget = Domain | Platform
|
|
75
75
|
|
|
76
|
-
//
|
|
77
|
-
//
|
|
76
|
+
// Every optional below carries sury's default `option` encoding: the key is omitted,
|
|
77
|
+
// never written as `null`. That is the shape the rest of the repo already uses
|
|
78
|
+
// (`Message.meta`, `storedEvent.tags`), so there is one optional form on the wire and
|
|
79
|
+
// nothing to annotate. The `T | null` these fields used to carry worked around a sury
|
|
80
|
+
// bug — undefined failing jsonableValidation inside a union variant payload — fixed in
|
|
81
|
+
// 11.0.0-alpha.11. `Offload` below is not an optional wrapper but an either-or codec.
|
|
78
82
|
let apiSchemaFragmentOffloadSchema = Offload.optionSchema(~store="pluginApiFragments", apiSchemaFragmentSchema)
|
|
79
|
-
let dcbEventLogOptionSchema = dcbEventLogDefinitionSchema->S.nullAsOption
|
|
80
|
-
let stringOptionSchema = S.string->S.nullAsOption
|
|
81
|
-
let stringArrayOptionSchema = S.array(S.string)->S.nullAsOption
|
|
82
|
-
let boolOptionSchema = S.bool->S.nullAsOption
|
|
83
83
|
|
|
84
84
|
// ── UI fragment manifest types ────────────────────────────────────────────────
|
|
85
85
|
|
|
@@ -89,14 +89,14 @@ type panelManifestEntry = {
|
|
|
89
89
|
title: string,
|
|
90
90
|
description: string,
|
|
91
91
|
positions: array<string>,
|
|
92
|
-
requiredAccess:
|
|
92
|
+
requiredAccess: option<string>,
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
@schema
|
|
96
96
|
type menuEntry = {
|
|
97
97
|
label: string,
|
|
98
|
-
icon:
|
|
99
|
-
group:
|
|
98
|
+
icon: option<string>,
|
|
99
|
+
group: option<string>,
|
|
100
100
|
sortOrder: int,
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -105,7 +105,7 @@ type pageManifestEntry = {
|
|
|
105
105
|
fragmentId: string,
|
|
106
106
|
title: string,
|
|
107
107
|
menuEntry: menuEntry,
|
|
108
|
-
requiredAccess:
|
|
108
|
+
requiredAccess: option<string>,
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
@schema
|
|
@@ -115,7 +115,7 @@ type uiFragmentManifest = {
|
|
|
115
115
|
pages: array<pageManifestEntry>,
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
let uiFragmentManifestOptionSchema =
|
|
118
|
+
let uiFragmentManifestOptionSchema = S.option(uiFragmentManifestSchema)
|
|
119
119
|
|
|
120
120
|
// ── Plugin structure types (component metadata for Auto UI and event graph) ──
|
|
121
121
|
|
|
@@ -126,7 +126,7 @@ type commandLevel = Collection | Instance
|
|
|
126
126
|
type fieldReference = {
|
|
127
127
|
fieldName: string,
|
|
128
128
|
entity: string,
|
|
129
|
-
plugin:
|
|
129
|
+
plugin: option<string>,
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
@schema
|
|
@@ -134,23 +134,23 @@ type commandDef = {
|
|
|
134
134
|
name: string,
|
|
135
135
|
schema: string,
|
|
136
136
|
level: commandLevel,
|
|
137
|
-
aggregateIdField:
|
|
137
|
+
aggregateIdField: option<string>,
|
|
138
138
|
mutationField: string,
|
|
139
139
|
references: array<fieldReference>,
|
|
140
140
|
/** The declared *from* set — lifecycle states this command is meaningful in.
|
|
141
141
|
`None` means always available; `Some([])` means never show. */
|
|
142
|
-
allowedStates:
|
|
142
|
+
allowedStates: option<array<string>>,
|
|
143
143
|
/** The declared *to* state this command's handler writes. `None` with a
|
|
144
144
|
from-set present means the command does not move the row. */
|
|
145
|
-
targetState:
|
|
145
|
+
targetState: option<string>,
|
|
146
146
|
/** Whether the variant is exposed in the generated API (non-`@noApi`). */
|
|
147
|
-
apiExposed:
|
|
147
|
+
apiExposed: option<bool>,
|
|
148
148
|
/** Access keys — any one of them — a caller needs to be *offered* this command.
|
|
149
149
|
A hint derived from the server's rule, never the refusal itself. */
|
|
150
|
-
requiredAccess:
|
|
150
|
+
requiredAccess: option<array<string>>,
|
|
151
151
|
/** The `@owner` command field the server stamps with the caller's identity; a
|
|
152
152
|
client omits it from a form, since whatever it collects is discarded. */
|
|
153
|
-
ownerField:
|
|
153
|
+
ownerField: option<string>,
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
@schema
|
|
@@ -168,41 +168,41 @@ type queryableDef = {
|
|
|
168
168
|
searchableFields: array<string>,
|
|
169
169
|
/** Which rung produced `labelField`, so a consumer can rank it against its own
|
|
170
170
|
rule: `"annotation"` | `"convention"` | `"position"` | `"fallback"`. */
|
|
171
|
-
labelFieldSource:
|
|
171
|
+
labelFieldSource: option<string>,
|
|
172
172
|
/** The state field holding the row's lifecycle, paired with
|
|
173
173
|
`commandDef.allowedStates`. From `@lifecycle`, else an enum named `lifecycle`. */
|
|
174
|
-
lifecycleField:
|
|
174
|
+
lifecycleField: option<string>,
|
|
175
175
|
/** The `@owner` state field. Reads of this view are narrowed server-side to a
|
|
176
176
|
non-elevated caller's own rows. */
|
|
177
|
-
ownerField:
|
|
177
|
+
ownerField: option<string>,
|
|
178
178
|
/** The `@retired` state field withdrawing a row from ordinary reads. From the
|
|
179
179
|
annotation only — no fallback by name, since guessing hides data. */
|
|
180
|
-
retiredField:
|
|
180
|
+
retiredField: option<string>,
|
|
181
181
|
/** The states a row is retired *in* (state form of `@retired`); `None` is the
|
|
182
182
|
boolean form, where the excluded value is always `true`. */
|
|
183
|
-
retiredValues:
|
|
183
|
+
retiredValues: option<array<string>>,
|
|
184
184
|
/** Whether the view publishes the by-ids reference door that names a retired row
|
|
185
185
|
to any caller holding a pointer (`@namedWhenRetired`). Never true without
|
|
186
186
|
`retiredField`. */
|
|
187
|
-
namedWhenRetired:
|
|
187
|
+
namedWhenRetired: option<bool>,
|
|
188
188
|
/** `@@reventless.visibility`. `Some("Internal")` hides the component from AutoUI;
|
|
189
189
|
it is still carried here for developer tooling. `None` means Public. */
|
|
190
|
-
visibility:
|
|
190
|
+
visibility: option<string>,
|
|
191
191
|
/** Intra-plugin grouping band, the first non-kind path segment under `src/`.
|
|
192
192
|
`None` renders flat. */
|
|
193
|
-
chapter:
|
|
193
|
+
chapter: option<string>,
|
|
194
194
|
/** The singular counterpart of `queryField` (`Plugin_Order`), also the prefix of
|
|
195
195
|
the generated input types. Not derivable without `Api_Naming.singularize`. */
|
|
196
|
-
singleQueryField:
|
|
196
|
+
singleQueryField: option<string>,
|
|
197
197
|
/** The state field identifying a row, as opposed to a reference to another entity.
|
|
198
198
|
`None` means unresolved — no key-derived filter or sort until `@id` is declared. */
|
|
199
|
-
idField:
|
|
199
|
+
idField: option<string>,
|
|
200
200
|
/** Which rung produced `idField`, as `labelFieldSource` does: `"annotation"` |
|
|
201
201
|
`"convention"` | `"sole"`. */
|
|
202
|
-
idFieldSource:
|
|
202
|
+
idFieldSource: option<string>,
|
|
203
203
|
/** Access keys — any one of them — a caller needs to be *offered* this view. A
|
|
204
204
|
denied read comes back empty rather than erroring, hence the hint. */
|
|
205
|
-
requiredAccess:
|
|
205
|
+
requiredAccess: option<array<string>>,
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
/** One emitted event of a write side. `name` is the variant name, `schema` its
|
|
@@ -230,7 +230,7 @@ type writableDef = {
|
|
|
230
230
|
producedEventTypes: array<string>,
|
|
231
231
|
consumedEventTypes: array<string>,
|
|
232
232
|
linkedViews: array<string>,
|
|
233
|
-
consistencyRead:
|
|
233
|
+
consistencyRead: option<string>,
|
|
234
234
|
/** Emitted-event field schemas; `[]` when there are none. */
|
|
235
235
|
events: array<eventDef>,
|
|
236
236
|
/** Declared-error field schemas. Required, but a persisted structure predating a
|
|
@@ -238,7 +238,7 @@ type writableDef = {
|
|
|
238
238
|
`[]` on read, and a new one must be added there too. */
|
|
239
239
|
errors: array<errorDef>,
|
|
240
240
|
/** Chapter grouping band — see `queryableDef.chapter`. */
|
|
241
|
-
chapter:
|
|
241
|
+
chapter: option<string>,
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
@schema
|
|
@@ -248,7 +248,7 @@ type automationSliceDef = {
|
|
|
248
248
|
producedCommandTypes: array<string>,
|
|
249
249
|
targetName: string,
|
|
250
250
|
/** Chapter grouping band — see `queryableDef.chapter`. */
|
|
251
|
-
chapter:
|
|
251
|
+
chapter: option<string>,
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
@schema
|
|
@@ -256,11 +256,11 @@ type outboundTranslationSliceDef = {
|
|
|
256
256
|
name: string,
|
|
257
257
|
consumedEventTypes: array<string>,
|
|
258
258
|
inboundCommandTypes: array<string>,
|
|
259
|
-
targetName:
|
|
259
|
+
targetName: option<string>,
|
|
260
260
|
// Foreign system this slice publishes to — drives the external box (Event Graph).
|
|
261
|
-
externalSystem:
|
|
261
|
+
externalSystem: option<string>,
|
|
262
262
|
/** Chapter grouping band — see `queryableDef.chapter`. */
|
|
263
|
-
chapter:
|
|
263
|
+
chapter: option<string>,
|
|
264
264
|
/** The topics this slice subscribes to, as `Spec.sourceNames` declares them —
|
|
265
265
|
an Aggregate's `Spec.name` or a DCB source name. `Some([])` is the declared
|
|
266
266
|
default and means this plugin's own DCB log; `None` is an older structure
|
|
@@ -268,7 +268,7 @@ type outboundTranslationSliceDef = {
|
|
|
268
268
|
which names event types and not where they came from — two topics carrying
|
|
269
269
|
an event of the same name are indistinguishable there. Optional so an older
|
|
270
270
|
reader ignores it. */
|
|
271
|
-
consumedSources:
|
|
271
|
+
consumedSources: option<array<string>>,
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
@schema
|
|
@@ -277,9 +277,9 @@ type inboundTranslationSliceDef = {
|
|
|
277
277
|
commandTypes: array<string>,
|
|
278
278
|
targetName: string,
|
|
279
279
|
// Foreign system this slice receives from — drives the external box (Event Graph).
|
|
280
|
-
externalSystem:
|
|
280
|
+
externalSystem: option<string>,
|
|
281
281
|
/** Chapter grouping band — see `queryableDef.chapter`. */
|
|
282
|
-
chapter:
|
|
282
|
+
chapter: option<string>,
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
/** A published event of an extension point and the internal events producing it.
|
|
@@ -291,8 +291,6 @@ type publishedEventDef = {
|
|
|
291
291
|
fromEventTypes: array<string>,
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
let publishedEventDefArrayOptionSchema = S.array(publishedEventDefSchema)->S.nullAsOption
|
|
295
|
-
|
|
296
294
|
/** The command direction's producer half: a command an extension point takes and
|
|
297
295
|
the delegate commands it routes to. `name` is EP-qualified, `toCommandTypes`
|
|
298
296
|
plugin-qualified. */
|
|
@@ -302,8 +300,6 @@ type acceptedCommandDef = {
|
|
|
302
300
|
toCommandTypes: array<string>,
|
|
303
301
|
}
|
|
304
302
|
|
|
305
|
-
let acceptedCommandDefArrayOptionSchema = S.array(acceptedCommandDefSchema)->S.nullAsOption
|
|
306
|
-
|
|
307
303
|
/** The subscriber's half: a published event and the commands it routes to. A
|
|
308
304
|
delegate command is plugin-qualified, one sent back to the EP is EP-qualified. */
|
|
309
305
|
@schema
|
|
@@ -312,8 +308,6 @@ type handledEventDef = {
|
|
|
312
308
|
toCommandTypes: array<string>,
|
|
313
309
|
}
|
|
314
310
|
|
|
315
|
-
let handledEventDefArrayOptionSchema = S.array(handledEventDefSchema)->S.nullAsOption
|
|
316
|
-
|
|
317
311
|
/** The command direction's subscriber half: a command sent back to the port and
|
|
318
312
|
the internal events producing it. `name` is EP-qualified, `fromEventTypes`
|
|
319
313
|
plugin-qualified. */
|
|
@@ -323,21 +317,19 @@ type issuedCommandDef = {
|
|
|
323
317
|
fromEventTypes: array<string>,
|
|
324
318
|
}
|
|
325
319
|
|
|
326
|
-
let issuedCommandDefArrayOptionSchema = S.array(issuedCommandDefSchema)->S.nullAsOption
|
|
327
|
-
|
|
328
320
|
@schema
|
|
329
321
|
type extensionDef = {
|
|
330
322
|
name: string,
|
|
331
323
|
delegateNames: array<string>,
|
|
332
324
|
eventTypes: array<string>,
|
|
333
325
|
commandTypes: array<string>,
|
|
334
|
-
/** Which published event routes to which commands.
|
|
326
|
+
/** Which published event routes to which commands. Optional like
|
|
335
327
|
`extensionPointDef.commandTypes`; re-emit definitions persisted before it. */
|
|
336
|
-
handledEvents:
|
|
328
|
+
handledEvents: option<array<handledEventDef>>,
|
|
337
329
|
/** Which internal event sends which command back to the port. `None` means a
|
|
338
330
|
definition persisted before the field, NOT an extension that issues nothing —
|
|
339
331
|
a reader joining the two halves must keep them apart. */
|
|
340
|
-
issuedCommands:
|
|
332
|
+
issuedCommands: option<array<issuedCommandDef>>,
|
|
341
333
|
}
|
|
342
334
|
|
|
343
335
|
/**
|
|
@@ -346,28 +338,21 @@ An extension point owned by a plugin, from the producer side.
|
|
|
346
338
|
`sourceEventTypes` are the `Delegate`'s events feeding the published protocol,
|
|
347
339
|
plugin-qualified to match `writableDef.producedEventTypes`. `commandTypes` is the
|
|
348
340
|
EP's inbound protocol — None (read as []) for a `command = unit` EP, which routes
|
|
349
|
-
nothing.
|
|
350
|
-
lifecycle Message union); definitions persisted before a field must be re-emitted.
|
|
341
|
+
nothing. Definitions persisted before a field was added must be re-emitted.
|
|
351
342
|
*/
|
|
352
343
|
@schema
|
|
353
344
|
type extensionPointDef = {
|
|
354
345
|
name: string,
|
|
355
346
|
delegateNames: array<string>,
|
|
356
347
|
sourceEventTypes: array<string>,
|
|
357
|
-
commandTypes:
|
|
348
|
+
commandTypes: option<array<string>>,
|
|
358
349
|
/** Which internal event becomes which published event. */
|
|
359
|
-
publishedEvents:
|
|
360
|
-
option<array<publishedEventDef>>,
|
|
350
|
+
publishedEvents: option<array<publishedEventDef>>,
|
|
361
351
|
/** Which arriving command becomes which delegate command. `None` means a
|
|
362
352
|
definition persisted before the field, NOT a port that accepts nothing. */
|
|
363
|
-
acceptedCommands:
|
|
364
|
-
option<array<acceptedCommandDef>>,
|
|
353
|
+
acceptedCommands: option<array<acceptedCommandDef>>,
|
|
365
354
|
}
|
|
366
355
|
|
|
367
|
-
// js_nullable creates `array | null` (not `| undefined`), which passes sury's
|
|
368
|
-
// jsonableValidation inside the pluginStructure union variant payload.
|
|
369
|
-
let extensionPointDefArrayOptionSchema = S.array(extensionPointDefSchema)->S.nullAsOption
|
|
370
|
-
|
|
371
356
|
/**
|
|
372
357
|
One field's store requirement, with its provenance.
|
|
373
358
|
|
|
@@ -382,12 +367,9 @@ type requiredStoreDeclaration = {
|
|
|
382
367
|
store: string,
|
|
383
368
|
component: string,
|
|
384
369
|
field: string,
|
|
385
|
-
annotation:
|
|
370
|
+
annotation: option<string>,
|
|
386
371
|
}
|
|
387
372
|
|
|
388
|
-
let requiredStoreDeclarationArrayOptionSchema =
|
|
389
|
-
S.array(requiredStoreDeclarationSchema)->S.nullAsOption
|
|
390
|
-
|
|
391
373
|
/**
|
|
392
374
|
One component's capability requirement, with its provenance.
|
|
393
375
|
|
|
@@ -400,9 +382,6 @@ expressible as an annotation on one, which is why the need is declared.
|
|
|
400
382
|
@schema
|
|
401
383
|
type requiredCapabilityDeclaration = {capability: string, component: string}
|
|
402
384
|
|
|
403
|
-
let requiredCapabilityDeclarationArrayOptionSchema =
|
|
404
|
-
S.array(requiredCapabilityDeclarationSchema)->S.nullAsOption
|
|
405
|
-
|
|
406
385
|
/**
|
|
407
386
|
One graft's provenance: which trait, at which version, on which component.
|
|
408
387
|
|
|
@@ -427,14 +406,13 @@ type traitDeclaration = {
|
|
|
427
406
|
component: string,
|
|
428
407
|
}
|
|
429
408
|
|
|
430
|
-
let traitDeclarationArrayOptionSchema = S.array(traitDeclarationSchema)->S.nullAsOption
|
|
431
|
-
|
|
432
409
|
/**
|
|
433
410
|
Adding a field here? It must be a shape a stale event can be healed into — the
|
|
434
411
|
lifecycle aggregate replays its own log before every decision, so one event that
|
|
435
|
-
fails to decode freezes that plugin's registration. `
|
|
436
|
-
|
|
437
|
-
and
|
|
412
|
+
fails to decode freezes that plugin's registration. Make it `option`, which
|
|
413
|
+
`Message.parseJsonTolerant` heals to `None` whatever the inner type is. A required
|
|
414
|
+
array heals to `[]` and a required enum to its first variant; a required *scalar*
|
|
415
|
+
is fabricated and warned about, so it is the one shape to avoid. Regression suite:
|
|
438
416
|
`PluginLifecycleCorpusTest` — if it goes red, re-shape the field, not the fixtures.
|
|
439
417
|
*/
|
|
440
418
|
@schema
|
|
@@ -449,27 +427,23 @@ type pluginStructure = {
|
|
|
449
427
|
extensions: array<extensionDef>,
|
|
450
428
|
// Extension points owned by this plugin (producer side). Optional so older
|
|
451
429
|
// definitions still decode (absent → None, read as []).
|
|
452
|
-
extensionPoints:
|
|
453
|
-
option<array<extensionPointDef>>,
|
|
430
|
+
extensionPoints: option<array<extensionPointDef>>,
|
|
454
431
|
/** The object stores this plugin's fields declare they need, deduplicated and
|
|
455
432
|
qualified as `{plugin}.{store}` even for the same-plugin case. */
|
|
456
|
-
requiredStores:
|
|
433
|
+
requiredStores: option<array<string>>,
|
|
457
434
|
/** Provenance for `requiredStores`: one entry per declaring `(component, field)`.
|
|
458
435
|
`requiredStores` is derived from it, so the two cannot disagree. */
|
|
459
|
-
requiredStoreDeclarations:
|
|
460
|
-
option<array<requiredStoreDeclaration>>,
|
|
436
|
+
requiredStoreDeclarations: option<array<requiredStoreDeclaration>>,
|
|
461
437
|
/** The platform capabilities this plugin's components declare they need, one
|
|
462
438
|
entry per declaring component. Object stores are not here — a store need is
|
|
463
439
|
a field's, and travels as `requiredStores`. Absent → None, read as []. */
|
|
464
|
-
requiredCapabilities:
|
|
465
|
-
option<array<requiredCapabilityDeclaration>>,
|
|
440
|
+
requiredCapabilities: option<array<requiredCapabilityDeclaration>>,
|
|
466
441
|
/** The domain traits grafted into this plugin, one entry per declaring
|
|
467
442
|
component. Absent → None, read as []. The only signal a graft leaves that
|
|
468
443
|
survives into a deployed plugin — every other one (the dependency, the
|
|
469
444
|
variant spread, the rules alias, the conformance binding) is source-side.
|
|
470
445
|
A claim about origin, never about behaviour: see `Trait`. */
|
|
471
|
-
traitDeclarations:
|
|
472
|
-
option<array<traitDeclaration>>,
|
|
446
|
+
traitDeclarations: option<array<traitDeclaration>>,
|
|
473
447
|
}
|
|
474
448
|
|
|
475
449
|
let pluginStructureOffloadSchema = Offload.optionSchema(~store="pluginStructures", pluginStructureSchema)
|
|
@@ -496,15 +470,15 @@ type pluginDefinition = {
|
|
|
496
470
|
apiSchemaFragment: @s.matches(apiSchemaFragmentOffloadSchema) option<Offload.payload<apiSchemaFragment>>,
|
|
497
471
|
// Schema routing in split-API mode: None/"Domain" → DomainApi, Some("Platform") →
|
|
498
472
|
// PlatformApi (and excluded from the DomainApi runtime schema).
|
|
499
|
-
apiTarget:
|
|
473
|
+
apiTarget: option<string>,
|
|
500
474
|
// Component graph metadata, offloadable like apiSchemaFragment. Absent for older
|
|
501
475
|
// protocol versions.
|
|
502
476
|
structure: @s.matches(pluginStructureOffloadSchema) option<Offload.payload<pluginStructure>>,
|
|
503
477
|
// EventTopic ARN of a bundled DcbEventLog, so the admin can subscribe peer
|
|
504
478
|
// EventCollectors to it. None for plugins without one.
|
|
505
|
-
dcbEventLog:
|
|
479
|
+
dcbEventLog: option<dcbEventLogDefinition>,
|
|
506
480
|
// Mandatory; `Domain` is resolved as the default in Plugin_Builder. Payload-less
|
|
507
|
-
// variant → a bare JSON string
|
|
481
|
+
// variant → a bare JSON string.
|
|
508
482
|
kind: pluginKind,
|
|
509
483
|
}
|
|
510
484
|
|
|
@@ -49,26 +49,18 @@ let apiTargetSchema = Sury.union([
|
|
|
49
49
|
|
|
50
50
|
let apiSchemaFragmentOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginApiFragments", undefined, apiSchemaFragmentSchema);
|
|
51
51
|
|
|
52
|
-
let dcbEventLogOptionSchema = Sury.$nullAsOption(dcbEventLogDefinitionSchema);
|
|
53
|
-
|
|
54
|
-
let stringOptionSchema = Sury.$nullAsOption(Sury.string);
|
|
55
|
-
|
|
56
|
-
let stringArrayOptionSchema = Sury.$nullAsOption(Sury.array(Sury.string));
|
|
57
|
-
|
|
58
|
-
let boolOptionSchema = Sury.$nullAsOption(Sury.bool);
|
|
59
|
-
|
|
60
52
|
let panelManifestEntrySchema = Sury.$schema(s => ({
|
|
61
53
|
fragmentId: s.m(Sury.string),
|
|
62
54
|
title: s.m(Sury.string),
|
|
63
55
|
description: s.m(Sury.string),
|
|
64
56
|
positions: s.m(Sury.array(Sury.string)),
|
|
65
|
-
requiredAccess: s.m(
|
|
57
|
+
requiredAccess: s.m(Sury.$option(Sury.string))
|
|
66
58
|
}));
|
|
67
59
|
|
|
68
60
|
let menuEntrySchema = Sury.$schema(s => ({
|
|
69
61
|
label: s.m(Sury.string),
|
|
70
|
-
icon: s.m(
|
|
71
|
-
group: s.m(
|
|
62
|
+
icon: s.m(Sury.$option(Sury.string)),
|
|
63
|
+
group: s.m(Sury.$option(Sury.string)),
|
|
72
64
|
sortOrder: s.m(Sury.int)
|
|
73
65
|
}));
|
|
74
66
|
|
|
@@ -76,7 +68,7 @@ let pageManifestEntrySchema = Sury.$schema(s => ({
|
|
|
76
68
|
fragmentId: s.m(Sury.string),
|
|
77
69
|
title: s.m(Sury.string),
|
|
78
70
|
menuEntry: s.m(menuEntrySchema),
|
|
79
|
-
requiredAccess: s.m(
|
|
71
|
+
requiredAccess: s.m(Sury.$option(Sury.string))
|
|
80
72
|
}));
|
|
81
73
|
|
|
82
74
|
let uiFragmentManifestSchema = Sury.$schema(s => ({
|
|
@@ -85,7 +77,7 @@ let uiFragmentManifestSchema = Sury.$schema(s => ({
|
|
|
85
77
|
pages: s.m(Sury.array(pageManifestEntrySchema))
|
|
86
78
|
}));
|
|
87
79
|
|
|
88
|
-
let uiFragmentManifestOptionSchema = Sury.$
|
|
80
|
+
let uiFragmentManifestOptionSchema = Sury.$option(uiFragmentManifestSchema);
|
|
89
81
|
|
|
90
82
|
let commandLevelSchema = Sury.union([
|
|
91
83
|
Sury.literal("Collection"),
|
|
@@ -95,21 +87,21 @@ let commandLevelSchema = Sury.union([
|
|
|
95
87
|
let fieldReferenceSchema = Sury.$schema(s => ({
|
|
96
88
|
fieldName: s.m(Sury.string),
|
|
97
89
|
entity: s.m(Sury.string),
|
|
98
|
-
plugin: s.m(
|
|
90
|
+
plugin: s.m(Sury.$option(Sury.string))
|
|
99
91
|
}));
|
|
100
92
|
|
|
101
93
|
let commandDefSchema = Sury.$schema(s => ({
|
|
102
94
|
name: s.m(Sury.string),
|
|
103
95
|
schema: s.m(Sury.string),
|
|
104
96
|
level: s.m(commandLevelSchema),
|
|
105
|
-
aggregateIdField: s.m(
|
|
97
|
+
aggregateIdField: s.m(Sury.$option(Sury.string)),
|
|
106
98
|
mutationField: s.m(Sury.string),
|
|
107
99
|
references: s.m(Sury.array(fieldReferenceSchema)),
|
|
108
|
-
allowedStates: s.m(
|
|
109
|
-
targetState: s.m(
|
|
110
|
-
apiExposed: s.m(
|
|
111
|
-
requiredAccess: s.m(
|
|
112
|
-
ownerField: s.m(
|
|
100
|
+
allowedStates: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
101
|
+
targetState: s.m(Sury.$option(Sury.string)),
|
|
102
|
+
apiExposed: s.m(Sury.$option(Sury.bool)),
|
|
103
|
+
requiredAccess: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
104
|
+
ownerField: s.m(Sury.$option(Sury.string))
|
|
113
105
|
}));
|
|
114
106
|
|
|
115
107
|
let queryableDefSchema = Sury.$schema(s => ({
|
|
@@ -120,18 +112,18 @@ let queryableDefSchema = Sury.$schema(s => ({
|
|
|
120
112
|
linkedWriteSide: s.m(Sury.array(Sury.string)),
|
|
121
113
|
labelField: s.m(Sury.string),
|
|
122
114
|
searchableFields: s.m(Sury.array(Sury.string)),
|
|
123
|
-
labelFieldSource: s.m(
|
|
124
|
-
lifecycleField: s.m(
|
|
125
|
-
ownerField: s.m(
|
|
126
|
-
retiredField: s.m(
|
|
127
|
-
retiredValues: s.m(
|
|
128
|
-
namedWhenRetired: s.m(
|
|
129
|
-
visibility: s.m(
|
|
130
|
-
chapter: s.m(
|
|
131
|
-
singleQueryField: s.m(
|
|
132
|
-
idField: s.m(
|
|
133
|
-
idFieldSource: s.m(
|
|
134
|
-
requiredAccess: s.m(
|
|
115
|
+
labelFieldSource: s.m(Sury.$option(Sury.string)),
|
|
116
|
+
lifecycleField: s.m(Sury.$option(Sury.string)),
|
|
117
|
+
ownerField: s.m(Sury.$option(Sury.string)),
|
|
118
|
+
retiredField: s.m(Sury.$option(Sury.string)),
|
|
119
|
+
retiredValues: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
120
|
+
namedWhenRetired: s.m(Sury.$option(Sury.bool)),
|
|
121
|
+
visibility: s.m(Sury.$option(Sury.string)),
|
|
122
|
+
chapter: s.m(Sury.$option(Sury.string)),
|
|
123
|
+
singleQueryField: s.m(Sury.$option(Sury.string)),
|
|
124
|
+
idField: s.m(Sury.$option(Sury.string)),
|
|
125
|
+
idFieldSource: s.m(Sury.$option(Sury.string)),
|
|
126
|
+
requiredAccess: s.m(Sury.$option(Sury.array(Sury.string)))
|
|
135
127
|
}));
|
|
136
128
|
|
|
137
129
|
let eventDefSchema = Sury.$schema(s => ({
|
|
@@ -152,10 +144,10 @@ let writableDefSchema = Sury.$schema(s => ({
|
|
|
152
144
|
producedEventTypes: s.m(Sury.array(Sury.string)),
|
|
153
145
|
consumedEventTypes: s.m(Sury.array(Sury.string)),
|
|
154
146
|
linkedViews: s.m(Sury.array(Sury.string)),
|
|
155
|
-
consistencyRead: s.m(
|
|
147
|
+
consistencyRead: s.m(Sury.$option(Sury.string)),
|
|
156
148
|
events: s.m(Sury.array(eventDefSchema)),
|
|
157
149
|
errors: s.m(Sury.array(errorDefSchema)),
|
|
158
|
-
chapter: s.m(
|
|
150
|
+
chapter: s.m(Sury.$option(Sury.string))
|
|
159
151
|
}));
|
|
160
152
|
|
|
161
153
|
let automationSliceDefSchema = Sury.$schema(s => ({
|
|
@@ -163,25 +155,25 @@ let automationSliceDefSchema = Sury.$schema(s => ({
|
|
|
163
155
|
consumedEventTypes: s.m(Sury.array(Sury.string)),
|
|
164
156
|
producedCommandTypes: s.m(Sury.array(Sury.string)),
|
|
165
157
|
targetName: s.m(Sury.string),
|
|
166
|
-
chapter: s.m(
|
|
158
|
+
chapter: s.m(Sury.$option(Sury.string))
|
|
167
159
|
}));
|
|
168
160
|
|
|
169
161
|
let outboundTranslationSliceDefSchema = Sury.$schema(s => ({
|
|
170
162
|
name: s.m(Sury.string),
|
|
171
163
|
consumedEventTypes: s.m(Sury.array(Sury.string)),
|
|
172
164
|
inboundCommandTypes: s.m(Sury.array(Sury.string)),
|
|
173
|
-
targetName: s.m(
|
|
174
|
-
externalSystem: s.m(
|
|
175
|
-
chapter: s.m(
|
|
176
|
-
consumedSources: s.m(
|
|
165
|
+
targetName: s.m(Sury.$option(Sury.string)),
|
|
166
|
+
externalSystem: s.m(Sury.$option(Sury.string)),
|
|
167
|
+
chapter: s.m(Sury.$option(Sury.string)),
|
|
168
|
+
consumedSources: s.m(Sury.$option(Sury.array(Sury.string)))
|
|
177
169
|
}));
|
|
178
170
|
|
|
179
171
|
let inboundTranslationSliceDefSchema = Sury.$schema(s => ({
|
|
180
172
|
name: s.m(Sury.string),
|
|
181
173
|
commandTypes: s.m(Sury.array(Sury.string)),
|
|
182
174
|
targetName: s.m(Sury.string),
|
|
183
|
-
externalSystem: s.m(
|
|
184
|
-
chapter: s.m(
|
|
175
|
+
externalSystem: s.m(Sury.$option(Sury.string)),
|
|
176
|
+
chapter: s.m(Sury.$option(Sury.string))
|
|
185
177
|
}));
|
|
186
178
|
|
|
187
179
|
let publishedEventDefSchema = Sury.$schema(s => ({
|
|
@@ -189,65 +181,51 @@ let publishedEventDefSchema = Sury.$schema(s => ({
|
|
|
189
181
|
fromEventTypes: s.m(Sury.array(Sury.string))
|
|
190
182
|
}));
|
|
191
183
|
|
|
192
|
-
let publishedEventDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(publishedEventDefSchema));
|
|
193
|
-
|
|
194
184
|
let acceptedCommandDefSchema = Sury.$schema(s => ({
|
|
195
185
|
name: s.m(Sury.string),
|
|
196
186
|
toCommandTypes: s.m(Sury.array(Sury.string))
|
|
197
187
|
}));
|
|
198
188
|
|
|
199
|
-
let acceptedCommandDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(acceptedCommandDefSchema));
|
|
200
|
-
|
|
201
189
|
let handledEventDefSchema = Sury.$schema(s => ({
|
|
202
190
|
name: s.m(Sury.string),
|
|
203
191
|
toCommandTypes: s.m(Sury.array(Sury.string))
|
|
204
192
|
}));
|
|
205
193
|
|
|
206
|
-
let handledEventDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(handledEventDefSchema));
|
|
207
|
-
|
|
208
194
|
let issuedCommandDefSchema = Sury.$schema(s => ({
|
|
209
195
|
name: s.m(Sury.string),
|
|
210
196
|
fromEventTypes: s.m(Sury.array(Sury.string))
|
|
211
197
|
}));
|
|
212
198
|
|
|
213
|
-
let issuedCommandDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(issuedCommandDefSchema));
|
|
214
|
-
|
|
215
199
|
let extensionDefSchema = Sury.$schema(s => ({
|
|
216
200
|
name: s.m(Sury.string),
|
|
217
201
|
delegateNames: s.m(Sury.array(Sury.string)),
|
|
218
202
|
eventTypes: s.m(Sury.array(Sury.string)),
|
|
219
203
|
commandTypes: s.m(Sury.array(Sury.string)),
|
|
220
|
-
handledEvents: s.m(
|
|
221
|
-
issuedCommands: s.m(
|
|
204
|
+
handledEvents: s.m(Sury.$option(Sury.array(handledEventDefSchema))),
|
|
205
|
+
issuedCommands: s.m(Sury.$option(Sury.array(issuedCommandDefSchema)))
|
|
222
206
|
}));
|
|
223
207
|
|
|
224
208
|
let extensionPointDefSchema = Sury.$schema(s => ({
|
|
225
209
|
name: s.m(Sury.string),
|
|
226
210
|
delegateNames: s.m(Sury.array(Sury.string)),
|
|
227
211
|
sourceEventTypes: s.m(Sury.array(Sury.string)),
|
|
228
|
-
commandTypes: s.m(
|
|
229
|
-
publishedEvents: s.m(
|
|
230
|
-
acceptedCommands: s.m(
|
|
212
|
+
commandTypes: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
213
|
+
publishedEvents: s.m(Sury.$option(Sury.array(publishedEventDefSchema))),
|
|
214
|
+
acceptedCommands: s.m(Sury.$option(Sury.array(acceptedCommandDefSchema)))
|
|
231
215
|
}));
|
|
232
216
|
|
|
233
|
-
let extensionPointDefArrayOptionSchema = Sury.$nullAsOption(Sury.array(extensionPointDefSchema));
|
|
234
|
-
|
|
235
217
|
let requiredStoreDeclarationSchema = Sury.$schema(s => ({
|
|
236
218
|
store: s.m(Sury.string),
|
|
237
219
|
component: s.m(Sury.string),
|
|
238
220
|
field: s.m(Sury.string),
|
|
239
|
-
annotation: s.m(
|
|
221
|
+
annotation: s.m(Sury.$option(Sury.string))
|
|
240
222
|
}));
|
|
241
223
|
|
|
242
|
-
let requiredStoreDeclarationArrayOptionSchema = Sury.$nullAsOption(Sury.array(requiredStoreDeclarationSchema));
|
|
243
|
-
|
|
244
224
|
let requiredCapabilityDeclarationSchema = Sury.$schema(s => ({
|
|
245
225
|
capability: s.m(Sury.string),
|
|
246
226
|
component: s.m(Sury.string)
|
|
247
227
|
}));
|
|
248
228
|
|
|
249
|
-
let requiredCapabilityDeclarationArrayOptionSchema = Sury.$nullAsOption(Sury.array(requiredCapabilityDeclarationSchema));
|
|
250
|
-
|
|
251
229
|
let traitDeclarationSchema = Sury.$schema(s => ({
|
|
252
230
|
trait: s.m(Sury.string),
|
|
253
231
|
version: s.m(Sury.string),
|
|
@@ -255,8 +233,6 @@ let traitDeclarationSchema = Sury.$schema(s => ({
|
|
|
255
233
|
component: s.m(Sury.string)
|
|
256
234
|
}));
|
|
257
235
|
|
|
258
|
-
let traitDeclarationArrayOptionSchema = Sury.$nullAsOption(Sury.array(traitDeclarationSchema));
|
|
259
|
-
|
|
260
236
|
let pluginStructureSchema = Sury.$schema(s => ({
|
|
261
237
|
readModels: s.m(Sury.array(queryableDefSchema)),
|
|
262
238
|
stateViewSlices: s.m(Sury.array(queryableDefSchema)),
|
|
@@ -266,11 +242,11 @@ let pluginStructureSchema = Sury.$schema(s => ({
|
|
|
266
242
|
outboundTranslationSlices: s.m(Sury.array(outboundTranslationSliceDefSchema)),
|
|
267
243
|
inboundTranslationSlices: s.m(Sury.array(inboundTranslationSliceDefSchema)),
|
|
268
244
|
extensions: s.m(Sury.array(extensionDefSchema)),
|
|
269
|
-
extensionPoints: s.m(
|
|
270
|
-
requiredStores: s.m(
|
|
271
|
-
requiredStoreDeclarations: s.m(
|
|
272
|
-
requiredCapabilities: s.m(
|
|
273
|
-
traitDeclarations: s.m(
|
|
245
|
+
extensionPoints: s.m(Sury.$option(Sury.array(extensionPointDefSchema))),
|
|
246
|
+
requiredStores: s.m(Sury.$option(Sury.array(Sury.string))),
|
|
247
|
+
requiredStoreDeclarations: s.m(Sury.$option(Sury.array(requiredStoreDeclarationSchema))),
|
|
248
|
+
requiredCapabilities: s.m(Sury.$option(Sury.array(requiredCapabilityDeclarationSchema))),
|
|
249
|
+
traitDeclarations: s.m(Sury.$option(Sury.array(traitDeclarationSchema)))
|
|
274
250
|
}));
|
|
275
251
|
|
|
276
252
|
let pluginStructureOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginStructures", undefined, pluginStructureSchema);
|
|
@@ -284,9 +260,9 @@ let pluginDefinitionSchema = Sury.$schema(s => ({
|
|
|
284
260
|
eventCollector: s.m(Sury.string),
|
|
285
261
|
extensionProtocols: s.m(Sury.array(extensionProtocolSchema)),
|
|
286
262
|
apiSchemaFragment: s.m(apiSchemaFragmentOffloadSchema),
|
|
287
|
-
apiTarget: s.m(
|
|
263
|
+
apiTarget: s.m(Sury.$option(Sury.string)),
|
|
288
264
|
structure: s.m(pluginStructureOffloadSchema),
|
|
289
|
-
dcbEventLog: s.m(
|
|
265
|
+
dcbEventLog: s.m(Sury.$option(dcbEventLogDefinitionSchema)),
|
|
290
266
|
kind: s.m(pluginKindSchema)
|
|
291
267
|
}));
|
|
292
268
|
|
|
@@ -301,10 +277,6 @@ export {
|
|
|
301
277
|
apiSchemaFragmentSchema,
|
|
302
278
|
apiTargetSchema,
|
|
303
279
|
apiSchemaFragmentOffloadSchema,
|
|
304
|
-
dcbEventLogOptionSchema,
|
|
305
|
-
stringOptionSchema,
|
|
306
|
-
stringArrayOptionSchema,
|
|
307
|
-
boolOptionSchema,
|
|
308
280
|
panelManifestEntrySchema,
|
|
309
281
|
menuEntrySchema,
|
|
310
282
|
pageManifestEntrySchema,
|
|
@@ -321,22 +293,14 @@ export {
|
|
|
321
293
|
outboundTranslationSliceDefSchema,
|
|
322
294
|
inboundTranslationSliceDefSchema,
|
|
323
295
|
publishedEventDefSchema,
|
|
324
|
-
publishedEventDefArrayOptionSchema,
|
|
325
296
|
acceptedCommandDefSchema,
|
|
326
|
-
acceptedCommandDefArrayOptionSchema,
|
|
327
297
|
handledEventDefSchema,
|
|
328
|
-
handledEventDefArrayOptionSchema,
|
|
329
298
|
issuedCommandDefSchema,
|
|
330
|
-
issuedCommandDefArrayOptionSchema,
|
|
331
299
|
extensionDefSchema,
|
|
332
300
|
extensionPointDefSchema,
|
|
333
|
-
extensionPointDefArrayOptionSchema,
|
|
334
301
|
requiredStoreDeclarationSchema,
|
|
335
|
-
requiredStoreDeclarationArrayOptionSchema,
|
|
336
302
|
requiredCapabilityDeclarationSchema,
|
|
337
|
-
requiredCapabilityDeclarationArrayOptionSchema,
|
|
338
303
|
traitDeclarationSchema,
|
|
339
|
-
traitDeclarationArrayOptionSchema,
|
|
340
304
|
pluginStructureSchema,
|
|
341
305
|
pluginStructureOffloadSchema,
|
|
342
306
|
pluginDefinitionSchema,
|
|
@@ -55,7 +55,7 @@ let parseFlags = (args: array<string>): dict<JSON.t> => {
|
|
|
55
55
|
switch args->Array.get(i) {
|
|
56
56
|
| None => ()
|
|
57
57
|
| Some(arg) if arg->String.startsWith("--") =>
|
|
58
|
-
let key = arg->String.
|
|
58
|
+
let key = arg->String.slice(~start=2, ~end=arg->String.length)
|
|
59
59
|
switch args->Array.get(i + 1) {
|
|
60
60
|
| Some(value) if !(value->String.startsWith("--")) =>
|
|
61
61
|
out->Dict.set(key, JSON.Encode.string(value))
|
|
@@ -107,14 +107,14 @@ let arrayFieldNames = (schema: S.t<unknown>): array<string> =>
|
|
|
107
107
|
// ── Entry point ──────────────────────────────────────────────────────────────
|
|
108
108
|
|
|
109
109
|
let main = async () => {
|
|
110
|
-
let argv = NodeProcess.argv->Array.
|
|
110
|
+
let argv = NodeProcess.argv->Array.slice(~start=2, ~end=NodeProcess.argv->Array.length)
|
|
111
111
|
switch argv->Array.get(0) {
|
|
112
112
|
| None | Some("") | Some("--help") | Some("-h") => {
|
|
113
113
|
Console.log(usage)
|
|
114
114
|
NodeProcess.exit(argv->Array.length == 0 ? 1 : 0)
|
|
115
115
|
}
|
|
116
116
|
| Some(traitPackage) => {
|
|
117
|
-
let flags = parseFlags(argv->Array.
|
|
117
|
+
let flags = parseFlags(argv->Array.slice(~start=1, ~end=argv->Array.length))
|
|
118
118
|
let stringFlag = key => flags->Dict.get(key)->Option.flatMap(JSON.Decode.string)
|
|
119
119
|
let into = stringFlag("into")
|
|
120
120
|
let tests = stringFlag("tests")
|
|
@@ -136,7 +136,8 @@ let main = async () => {
|
|
|
136
136
|
->String.replace("trait-", "")
|
|
137
137
|
->String.split("-")
|
|
138
138
|
->Array.map(part =>
|
|
139
|
-
part->String.charAt(0)->String.toUpperCase ++
|
|
139
|
+
part->String.charAt(0)->String.toUpperCase ++
|
|
140
|
+
part->String.slice(~start=1, ~end=part->String.length)
|
|
140
141
|
)
|
|
141
142
|
->Array.join("") ++ "_Scaffold"
|
|
142
143
|
let specifier = `${traitPackage}/src/${scaffoldModule}.res.mjs`
|
|
@@ -36,7 +36,7 @@ function parseFlags(args) {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
if (arg.startsWith("--")) {
|
|
39
|
-
let key = arg.slice(2);
|
|
39
|
+
let key = arg.slice(2, arg.length);
|
|
40
40
|
let value = args[i + 1 | 0];
|
|
41
41
|
if (value !== undefined && !value.startsWith("--")) {
|
|
42
42
|
out[key] = value;
|
|
@@ -83,7 +83,7 @@ function arrayFieldNames(schema) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
async function main() {
|
|
86
|
-
let argv = process.argv.slice(2);
|
|
86
|
+
let argv = process.argv.slice(2, process.argv.length);
|
|
87
87
|
let traitPackage = argv[0];
|
|
88
88
|
if (traitPackage !== undefined) {
|
|
89
89
|
switch (traitPackage) {
|
|
@@ -92,7 +92,7 @@ async function main() {
|
|
|
92
92
|
case "-h" :
|
|
93
93
|
break;
|
|
94
94
|
default:
|
|
95
|
-
let flags = parseFlags(argv.slice(1));
|
|
95
|
+
let flags = parseFlags(argv.slice(1, argv.length));
|
|
96
96
|
let stringFlag = key => Stdlib_Option.flatMap(flags[key], Stdlib_JSON.Decode.string);
|
|
97
97
|
let into = stringFlag("into");
|
|
98
98
|
let tests = stringFlag("tests");
|
|
@@ -103,7 +103,7 @@ async function main() {
|
|
|
103
103
|
if (tests === undefined) {
|
|
104
104
|
return fail("--into and --tests are both required.\n\n" + usage);
|
|
105
105
|
}
|
|
106
|
-
let scaffoldModule = Stdlib_Option.getOr(Stdlib_Array.last(traitPackage.split("/")), "").replace("trait-", "").split("-").map(part => part.charAt(0).toUpperCase() + part.slice(1)).join("") + "_Scaffold";
|
|
106
|
+
let scaffoldModule = Stdlib_Option.getOr(Stdlib_Array.last(traitPackage.split("/")), "").replace("trait-", "").split("-").map(part => part.charAt(0).toUpperCase() + part.slice(1, part.length)).join("") + "_Scaffold";
|
|
107
107
|
let specifier = traitPackage + `/src/` + scaffoldModule + `.res.mjs`;
|
|
108
108
|
let modulePath;
|
|
109
109
|
try {
|
package/src/types/Message.res
CHANGED
|
@@ -141,22 +141,23 @@ type commandJson = {
|
|
|
141
141
|
|
|
142
142
|
// ── Schema-migration-on-read ──────────────────────────────────────────────────
|
|
143
143
|
// Nested `@schema` types (notably `pluginDefinition`/`pluginStructure`) gain fields
|
|
144
|
-
// over time — `kind`, `chapter`, `events`, `extensionPoints`, `apiExposed`, …
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// an aggregate that rehydrates from its own event log (the Plugin lifecycle aggregate),
|
|
151
|
-
// that single event then freezes EVERY later heartbeat/redetect/connect on that instance
|
|
152
|
-
// — a silent lifecycle freeze with no error surfaced near the operator.
|
|
144
|
+
// over time — `kind`, `chapter`, `events`, `extensionPoints`, `apiExposed`, … A field
|
|
145
|
+
// that is not optional is *present-required on decode*, so ONE message persisted before
|
|
146
|
+
// it was added SuryError-bricks decode. For an aggregate that rehydrates from its own
|
|
147
|
+
// event log (the Plugin lifecycle aggregate), that single event then freezes EVERY later
|
|
148
|
+
// heartbeat/redetect/connect on that instance — a silent lifecycle freeze with no error
|
|
149
|
+
// surfaced near the operator.
|
|
153
150
|
//
|
|
154
151
|
// We heal on read. Strict decode stays the fast path (unchanged for every current
|
|
155
152
|
// message); only when it throws do we schema-guide the raw JSON and retry once. The fill
|
|
156
153
|
// walks the target sury schema and inserts, for any absent field, the value that field's
|
|
157
|
-
// schema expects: `null` for a `T | null` union (→ `None`),
|
|
154
|
+
// schema expects: `null` for a `T | null` union (→ `None`), nothing at all for an
|
|
155
|
+
// `option` (a union admitting `undefined` — also `None`), `[]` for a missing array, the
|
|
158
156
|
// first variant of a mandatory enum (`kind` → `Domain`), a filled `{}` for a missing
|
|
159
|
-
// nested object, and a zero value for a missing scalar.
|
|
157
|
+
// nested object, and a zero value for a missing scalar. The `undefined` arm mirrors the
|
|
158
|
+
// `null` one and must precede the enum/object guesses below it: an absent `option<enum>`
|
|
159
|
+
// would otherwise heal to that enum's first variant and an absent `option<record>` to a
|
|
160
|
+
// filled `{}`, turning `None` into a `Some` of an invented value. It descends only into values
|
|
160
161
|
// actually present, matches tagged-union members by their `TAG` const, is purely additive
|
|
161
162
|
// (clones via a JSON round-trip; never re-encodes through the schema), is idempotent on
|
|
162
163
|
// valid data, and falls back to the ORIGINAL error when the fill doesn't resolve the
|
|
@@ -229,6 +230,7 @@ let fillMissingDefaults: (S.t<'a>, JSON.t, array<string>) => JSON.t = %raw(`func
|
|
|
229
230
|
var has=schema.has||{};
|
|
230
231
|
if(value===undefined){
|
|
231
232
|
if(has.null) return null;
|
|
233
|
+
if(has.undefined) return undefined;
|
|
232
234
|
var c=firstConst(schema.anyOf); if(c!==undefined) return c;
|
|
233
235
|
var obj=(schema.anyOf||[]).find(function(s){return s.type==="object";}); if(obj) return fill(obj,{},path);
|
|
234
236
|
return undefined;
|
|
@@ -273,7 +275,7 @@ let parseJsonTolerant = (json, schema) =>
|
|
|
273
275
|
->Int.toString} missing scalar field(s): ${scalarFills->Array.join(
|
|
274
276
|
", ",
|
|
275
277
|
)}. A required scalar was added to a persisted type after this message was ` ++
|
|
276
|
-
`written; the value above is fabricated, not recovered. Prefer
|
|
278
|
+
`written; the value above is fabricated, not recovered. Prefer an optional field.`,
|
|
277
279
|
)
|
|
278
280
|
}
|
|
279
281
|
value
|
|
@@ -87,6 +87,7 @@ let fillMissingDefaults = (function(schema, json, scalarFills){
|
|
|
87
87
|
var has=schema.has||{};
|
|
88
88
|
if(value===undefined){
|
|
89
89
|
if(has.null) return null;
|
|
90
|
+
if(has.undefined) return undefined;
|
|
90
91
|
var c=firstConst(schema.anyOf); if(c!==undefined) return c;
|
|
91
92
|
var obj=(schema.anyOf||[]).find(function(s){return s.type==="object";}); if(obj) return fill(obj,{},path);
|
|
92
93
|
return undefined;
|
|
@@ -126,7 +127,7 @@ function parseJsonTolerant(json, schema) {
|
|
|
126
127
|
throw firstErr;
|
|
127
128
|
}
|
|
128
129
|
if (scalarFills.length !== 0) {
|
|
129
|
-
console.warn(`[reventless] decoded a stored message by inventing ` + scalarFills.length.toString() + ` missing scalar field(s): ` + scalarFills.join(", ") + `. A required scalar was added to a persisted type after this message was written; the value above is fabricated, not recovered. Prefer
|
|
130
|
+
console.warn(`[reventless] decoded a stored message by inventing ` + scalarFills.length.toString() + ` missing scalar field(s): ` + scalarFills.join(", ") + `. A required scalar was added to a persisted type after this message was written; the value above is fabricated, not recovered. Prefer an optional field.`);
|
|
130
131
|
}
|
|
131
132
|
return value;
|
|
132
133
|
}
|