@reventlessdev/reventless-spec 3.0.0-alpha.100
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 +931 -0
- package/LICENSE +202 -0
- package/README.md +109 -0
- package/package.json +49 -0
- package/rescript.json +32 -0
- package/run-generator.mjs +2 -0
- package/run-platform-generator.mjs +2 -0
- package/scripts/generate-currency.mjs +215 -0
- package/scripts/iso-4217-list-one.xml +1956 -0
- package/src/AnsiStyle.res +40 -0
- package/src/AnsiStyle.res.mjs +54 -0
- package/src/LogPrefix.res +192 -0
- package/src/LogPrefix.res.mjs +159 -0
- package/src/PackageVersion.res +67 -0
- package/src/PackageVersion.res.mjs +81 -0
- package/src/components/Aggregate.res +64 -0
- package/src/components/Aggregate.res.mjs +2 -0
- package/src/components/AutomationSlice.res +279 -0
- package/src/components/AutomationSlice.res.mjs +30 -0
- package/src/components/CapabilityManifest.res +74 -0
- package/src/components/CapabilityManifest.res.mjs +61 -0
- package/src/components/ComponentKind.res +99 -0
- package/src/components/ComponentKind.res.mjs +125 -0
- package/src/components/Counter.res +24 -0
- package/src/components/Counter.res.mjs +2 -0
- package/src/components/DcbDecode.res +118 -0
- package/src/components/DcbDecode.res.mjs +100 -0
- package/src/components/DcbScopeInference.res +244 -0
- package/src/components/DcbScopeInference.res.mjs +177 -0
- package/src/components/DcbTag.res +1335 -0
- package/src/components/DcbTag.res.mjs +898 -0
- package/src/components/DcbValidation.res +427 -0
- package/src/components/DcbValidation.res.mjs +423 -0
- package/src/components/DisplayName.res +40 -0
- package/src/components/DisplayName.res.mjs +26 -0
- package/src/components/ExtensionPoint.res +27 -0
- package/src/components/ExtensionPoint.res.mjs +2 -0
- package/src/components/InboundTranslationSlice.res +85 -0
- package/src/components/InboundTranslationSlice.res.mjs +2 -0
- package/src/components/OutboundTranslationSlice.res +153 -0
- package/src/components/OutboundTranslationSlice.res.mjs +2 -0
- package/src/components/Plugin.res +538 -0
- package/src/components/Plugin.res.mjs +264 -0
- package/src/components/PluginName.res +39 -0
- package/src/components/PluginName.res.mjs +45 -0
- package/src/components/ReadModel.res +199 -0
- package/src/components/ReadModel.res.mjs +18 -0
- package/src/components/Reference.res +55 -0
- package/src/components/Reference.res.mjs +50 -0
- package/src/components/Snapshot.res +26 -0
- package/src/components/Snapshot.res.mjs +2 -0
- package/src/components/StateAnnotations.res +97 -0
- package/src/components/StateAnnotations.res.mjs +15 -0
- package/src/components/StateChangeSlice.res +131 -0
- package/src/components/StateChangeSlice.res.mjs +2 -0
- package/src/components/StateViewSlice.res +123 -0
- package/src/components/StateViewSlice.res.mjs +2 -0
- package/src/components/Task.res +62 -0
- package/src/components/Task.res.mjs +2 -0
- package/src/generator/Codegen.res +842 -0
- package/src/generator/Codegen.res.mjs +565 -0
- package/src/generator/Config.res +106 -0
- package/src/generator/Config.res.mjs +69 -0
- package/src/generator/Discovery.res +230 -0
- package/src/generator/Discovery.res.mjs +198 -0
- package/src/generator/Generator_Node.res +14 -0
- package/src/generator/Generator_Node.res.mjs +18 -0
- package/src/generator/Pairing.res +460 -0
- package/src/generator/Pairing.res.mjs +415 -0
- package/src/generator/PlatformCodegen.res +207 -0
- package/src/generator/PlatformCodegen.res.mjs +154 -0
- package/src/generator/PlatformGenerator.res +126 -0
- package/src/generator/PlatformGenerator.res.mjs +114 -0
- package/src/generator/PlatformManifests.res +203 -0
- package/src/generator/PlatformManifests.res.mjs +212 -0
- package/src/generator/PluginGenerator.res +57 -0
- package/src/generator/PluginGenerator.res.mjs +73 -0
- package/src/semantic/Bytes.res +54 -0
- package/src/semantic/Bytes.res.mjs +38 -0
- package/src/semantic/Capabilities.res +43 -0
- package/src/semantic/Capabilities.res.mjs +17 -0
- package/src/semantic/Color.res +51 -0
- package/src/semantic/Color.res.mjs +29 -0
- package/src/semantic/Currency.res +598 -0
- package/src/semantic/Currency.res.mjs +743 -0
- package/src/semantic/DateRange.res +148 -0
- package/src/semantic/DateRange.res.mjs +74 -0
- package/src/semantic/Duration.res +53 -0
- package/src/semantic/Duration.res.mjs +26 -0
- package/src/semantic/Email.res +51 -0
- package/src/semantic/Email.res.mjs +31 -0
- package/src/semantic/GeoPoint.res +226 -0
- package/src/semantic/GeoPoint.res.mjs +190 -0
- package/src/semantic/Geocoding.res +127 -0
- package/src/semantic/Geocoding.res.mjs +36 -0
- package/src/semantic/Money.res +196 -0
- package/src/semantic/Money.res.mjs +138 -0
- package/src/semantic/Offload.res +294 -0
- package/src/semantic/Offload.res.mjs +191 -0
- package/src/semantic/Percent.res +53 -0
- package/src/semantic/Percent.res.mjs +33 -0
- package/src/semantic/Phone.res +55 -0
- package/src/semantic/Phone.res.mjs +29 -0
- package/src/semantic/Semantic.res +162 -0
- package/src/semantic/Semantic.res.mjs +95 -0
- package/src/semantic/StorageRef.res +164 -0
- package/src/semantic/StorageRef.res.mjs +111 -0
- package/src/semantic/Url.res +66 -0
- package/src/semantic/Url.res.mjs +48 -0
- package/src/types/Authorization.res +23 -0
- package/src/types/Authorization.res.mjs +33 -0
- package/src/types/Behavior.res +86 -0
- package/src/types/Behavior.res.mjs +2 -0
- package/src/types/DateTime.res +29 -0
- package/src/types/DateTime.res.mjs +16 -0
- package/src/types/EventMapping.res +100 -0
- package/src/types/EventMapping.res.mjs +15 -0
- package/src/types/Handler.res +30 -0
- package/src/types/Handler.res.mjs +2 -0
- package/src/types/Id.res +75 -0
- package/src/types/Id.res.mjs +37 -0
- package/src/types/Identity.res +46 -0
- package/src/types/Identity.res.mjs +51 -0
- package/src/types/Message.res +326 -0
- package/src/types/Message.res.mjs +186 -0
- package/src/types/Projection.res +220 -0
- package/src/types/Projection.res.mjs +44 -0
- package/src/types/QueryEngine.res +123 -0
- package/src/types/QueryEngine.res.mjs +12 -0
- package/src/types/ReadConsistency.res +38 -0
- package/src/types/ReadConsistency.res.mjs +29 -0
- package/src/types/Schedule.res +65 -0
- package/src/types/Schedule.res.mjs +68 -0
- package/src/types/SideEffect.res +45 -0
- package/src/types/SideEffect.res.mjs +2 -0
- package/src/types/StoredEvent.res +46 -0
- package/src/types/StoredEvent.res.mjs +32 -0
- package/src/types/Visibility.res +24 -0
- package/src/types/Visibility.res.mjs +25 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
S.enableJson()
|
|
2
|
+
|
|
3
|
+
/** Identifies the service that produced or is addressed by a message. */
|
|
4
|
+
@schema
|
|
5
|
+
type service = string
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
Envelope metadata attached to every event and command.
|
|
9
|
+
|
|
10
|
+
Every message that flows through Reventless carries this metadata so that
|
|
11
|
+
audit trails, correlation, distributed tracing, and routing are available
|
|
12
|
+
without inspecting the domain payload.
|
|
13
|
+
|
|
14
|
+
Shared by both `event'<>` and `command'<>` envelopes — fields are kept
|
|
15
|
+
generic so they apply equally to commands and events.
|
|
16
|
+
|
|
17
|
+
@example
|
|
18
|
+
```rescript
|
|
19
|
+
let meta: Message.meta = {
|
|
20
|
+
service: "CatalogService",
|
|
21
|
+
time: "2024-01-01T00:00:00Z",
|
|
22
|
+
msgId: "msg-abc",
|
|
23
|
+
correlationId: "cmd-xyz",
|
|
24
|
+
user: "alice",
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
*/
|
|
28
|
+
@schema
|
|
29
|
+
type meta = {
|
|
30
|
+
/** Name of the service that created or is addressed by this message. */
|
|
31
|
+
service: service,
|
|
32
|
+
/** ISO-8601 timestamp of when the message was created (producer time). */
|
|
33
|
+
time: string,
|
|
34
|
+
/** IP address of the service instance that created the message. Absent when unknown
|
|
35
|
+
(e.g. serverless contexts without a caller IP). */
|
|
36
|
+
ip?: string,
|
|
37
|
+
/** Identity of the actor who initiated the action. Absent for system-initiated messages. */
|
|
38
|
+
user?: string,
|
|
39
|
+
/** Unique identifier for this message. */
|
|
40
|
+
msgId: string,
|
|
41
|
+
/** Identifier of the root message of the correlation chain. Always present;
|
|
42
|
+
defaults to `msgId` when this is the root. */
|
|
43
|
+
correlationId: string,
|
|
44
|
+
/** Identifier of the *direct* parent message that caused this one. Absent at the chain root. */
|
|
45
|
+
causationId?: string,
|
|
46
|
+
/** W3C Trace Context `traceparent` header value (opaque pass-through).
|
|
47
|
+
Intentionally all-lowercase: matches the HTTP header name, OpenTelemetry SDKs,
|
|
48
|
+
and the CloudEvents distributed-tracing extension. */
|
|
49
|
+
traceparent?: string,
|
|
50
|
+
/** Schema version stamp for this message's payload variant. Absent = unversioned. */
|
|
51
|
+
schemaVersion?: string,
|
|
52
|
+
/** Extensible header bag for cross-cutting context (tenantId, feature flags, etc.).
|
|
53
|
+
Absent when empty — consumers normalise with `->Option.getOr(Dict.make())`. */
|
|
54
|
+
headers?: dict<string>,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
Context passed into command handlers, carrying the aggregate ID and envelope metadata.
|
|
59
|
+
|
|
60
|
+
Passed as the second argument to `Behavior.T.create` and `Behavior.T.execute`
|
|
61
|
+
so business logic can read the caller identity or propagate correlation IDs.
|
|
62
|
+
*/
|
|
63
|
+
@schema
|
|
64
|
+
type context = {
|
|
65
|
+
id: string,
|
|
66
|
+
meta: meta,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
A domain event envelope pairing an aggregate ID with the event payload and metadata.
|
|
71
|
+
|
|
72
|
+
The generic parameters `'id` and `'event` are fixed by each aggregate's `Spec`:
|
|
73
|
+
- `'id` comes from `Spec.Id.t`
|
|
74
|
+
- `'event` comes from `Spec.event`
|
|
75
|
+
|
|
76
|
+
@example
|
|
77
|
+
```rescript
|
|
78
|
+
let ev: Message.event'<string, Category.event> = {
|
|
79
|
+
id: "cat-1",
|
|
80
|
+
meta,
|
|
81
|
+
event: CategoryAdded({categoryId: "cat-1", name: "Electronics"}),
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
*/
|
|
85
|
+
type event'<'id, 'event> = {
|
|
86
|
+
id: 'id,
|
|
87
|
+
meta: meta,
|
|
88
|
+
event: 'event,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
Records who changed a resource's status and when.
|
|
93
|
+
Useful in audit-trail projections (e.g. "approved by bob at 2024-01-15").
|
|
94
|
+
*/
|
|
95
|
+
@schema
|
|
96
|
+
type statusChange = {
|
|
97
|
+
at: string,
|
|
98
|
+
by: string,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
A command envelope pairing an aggregate ID with the command payload and metadata.
|
|
103
|
+
|
|
104
|
+
@example
|
|
105
|
+
```rescript
|
|
106
|
+
let cmd: Message.command'<string, Category.command> = {
|
|
107
|
+
id: "cat-1",
|
|
108
|
+
meta,
|
|
109
|
+
command: AddCategory({categoryId: "cat-1", name: "Electronics"}),
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
*/
|
|
113
|
+
type command'<'id, 'command> = {
|
|
114
|
+
id: 'id,
|
|
115
|
+
meta: meta,
|
|
116
|
+
command: 'command,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
A serialized command envelope used when routing commands across service boundaries.
|
|
121
|
+
|
|
122
|
+
`commandJson` holds the raw JSON payload encoded by sury. The optional `delay`
|
|
123
|
+
field specifies a delivery delay in seconds (used by the scheduler).
|
|
124
|
+
|
|
125
|
+
@example
|
|
126
|
+
```rescript
|
|
127
|
+
let queued: Message.commandJson = {
|
|
128
|
+
id: "cat-1",
|
|
129
|
+
meta,
|
|
130
|
+
commandJson: json,
|
|
131
|
+
delay: Some(5),
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
*/
|
|
135
|
+
@schema
|
|
136
|
+
type commandJson = {
|
|
137
|
+
id: string,
|
|
138
|
+
meta: meta,
|
|
139
|
+
commandJson: JSON.t,
|
|
140
|
+
delay?: int,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Schema-migration-on-read ──────────────────────────────────────────────────
|
|
144
|
+
// Nested `@schema` types (notably `pluginDefinition`/`pluginStructure`) gain fields
|
|
145
|
+
// over time — `kind`, `chapter`, `events`, `extensionPoints`, `apiExposed`, … Because
|
|
146
|
+
// those types are JSON-encoded inside union-variant payloads, every optional field must
|
|
147
|
+
// use the `js_nullable` (`T | null`) encoding: it is the only JSON-safe optional form,
|
|
148
|
+
// since `S.option`/`nullableAsOption` carry `undefined`, which fails sury's
|
|
149
|
+
// `jsonableValidation` inside a union variant. That encoding is *present-required on
|
|
150
|
+
// decode*, so ONE message persisted before a field was added SuryError-bricks decode. For
|
|
151
|
+
// an aggregate that rehydrates from its own event log (the Plugin lifecycle aggregate),
|
|
152
|
+
// that single event then freezes EVERY later heartbeat/redetect/connect on that instance
|
|
153
|
+
// — a silent lifecycle freeze with no error surfaced near the operator.
|
|
154
|
+
//
|
|
155
|
+
// We heal on read. Strict decode stays the fast path (unchanged for every current
|
|
156
|
+
// message); only when it throws do we schema-guide the raw JSON and retry once. The fill
|
|
157
|
+
// walks the target sury schema and inserts, for any absent field, the value that field's
|
|
158
|
+
// schema expects: `null` for a `T | null` union (→ `None`), `[]` for a missing array, the
|
|
159
|
+
// first variant of a mandatory enum (`kind` → `Domain`), a filled `{}` for a missing
|
|
160
|
+
// nested object, and a zero value for a missing scalar. It descends only into values
|
|
161
|
+
// actually present, matches tagged-union members by their `TAG` const, is purely additive
|
|
162
|
+
// (clones via a JSON round-trip; never re-encodes through the schema), is idempotent on
|
|
163
|
+
// valid data, and falls back to the ORIGINAL error when the fill doesn't resolve the
|
|
164
|
+
// failure — so genuine corruption still surfaces.
|
|
165
|
+
// See docs/plans/platform-infrastructure-in-plugin-list.md (durable fix option 2).
|
|
166
|
+
//
|
|
167
|
+
// The scalar arm is the odd one out and is deliberately noisy. Every other fill is
|
|
168
|
+
// *derived* — the schema states what an absent value means, and the fill supplies exactly
|
|
169
|
+
// that. A scalar has no such statement, so `""` / `0` / `false` is a value this code
|
|
170
|
+
// invented: right for a descriptive field added later, wrong for a field that carries
|
|
171
|
+
// meaning. It also widens what can be masked, since a genuinely truncated payload now
|
|
172
|
+
// decodes where it used to throw. Every scalar fill is therefore reported to the caller
|
|
173
|
+
// and logged, so healing stays findable instead of becoming the silent default. Without
|
|
174
|
+
// it, a required-scalar addition freezes an aggregate outright — see
|
|
175
|
+
// docs/analysis/plugin-definition-schema-evolution-wedge.md.
|
|
176
|
+
//
|
|
177
|
+
// `bigint` is excluded on purpose: it has no JSON representation, so any value invented
|
|
178
|
+
// here would fail the retry anyway and mask the real error path.
|
|
179
|
+
//
|
|
180
|
+
// `scalarFills` is an out-parameter: the walker pushes `path := value` for each scalar it
|
|
181
|
+
// invented.
|
|
182
|
+
let fillMissingDefaults: (S.t<'a>, JSON.t, array<string>) => JSON.t = %raw(`function(schema, json, scalarFills){
|
|
183
|
+
function isSchema(x){ return x && typeof x === "object" && typeof x.type === "string"; }
|
|
184
|
+
function firstConst(anyOf){ var m=(anyOf||[]).find(function(s){return s.const!==undefined;}); return m ? m.const : undefined; }
|
|
185
|
+
function scalarDefault(schema){
|
|
186
|
+
if(schema.const!==undefined) return schema.const;
|
|
187
|
+
switch(schema.type){
|
|
188
|
+
case "string": return "";
|
|
189
|
+
case "number": return 0;
|
|
190
|
+
case "boolean": return false;
|
|
191
|
+
default: return undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function fill(schema, value, path){
|
|
195
|
+
if(!isSchema(schema)) return value;
|
|
196
|
+
switch(schema.type){
|
|
197
|
+
case "object": {
|
|
198
|
+
if(value===undefined){ value={}; }
|
|
199
|
+
else if(value===null || typeof value!=="object" || Array.isArray(value)) return value;
|
|
200
|
+
var items=schema.items||[];
|
|
201
|
+
for(var i=0;i<items.length;i++){ var it=items[i]; value[it.location]=fill(it.schema, value[it.location], path+"."+it.location); }
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
case "array": {
|
|
205
|
+
if(Array.isArray(value)){ var el=schema.additionalItems; return isSchema(el) ? value.map(function(v,ix){return fill(el,v,path+"["+ix+"]");}) : value; }
|
|
206
|
+
if(value===undefined) return [];
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
case "union": {
|
|
210
|
+
var has=schema.has||{};
|
|
211
|
+
if(value===undefined){
|
|
212
|
+
if(has.null) return null;
|
|
213
|
+
var c=firstConst(schema.anyOf); if(c!==undefined) return c;
|
|
214
|
+
var obj=(schema.anyOf||[]).find(function(s){return s.type==="object";}); if(obj) return fill(obj,{},path);
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
if(value===null) return null;
|
|
218
|
+
var members=schema.anyOf||[];
|
|
219
|
+
if(Array.isArray(value)){ var a=members.find(function(s){return s.type==="array";}); return a ? fill(a,value,path) : value; }
|
|
220
|
+
if(typeof value==="object"){
|
|
221
|
+
var m=members.find(function(s){return s.type==="object" && (s.items||[]).some(function(it){return it.location==="TAG" && it.schema.const===value.TAG;});});
|
|
222
|
+
if(!m) m=members.find(function(s){return s.type==="object";});
|
|
223
|
+
return m ? fill(m,value,path) : value;
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
default: {
|
|
228
|
+
if(value!==undefined) return value;
|
|
229
|
+
var d=scalarDefault(schema);
|
|
230
|
+
if(d===undefined) return value;
|
|
231
|
+
scalarFills.push(path + " := " + JSON.stringify(d));
|
|
232
|
+
return d;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Clone via JSON round-trip (json is already pure JSON) so the caller's value is never mutated.
|
|
237
|
+
return fill(schema, JSON.parse(JSON.stringify(json)), "");
|
|
238
|
+
}`)
|
|
239
|
+
|
|
240
|
+
// Strict parse with a single schema-migration-on-read retry (see fillMissingDefaults).
|
|
241
|
+
let parseJsonTolerant = (json, schema) =>
|
|
242
|
+
switch json->S.parseJsonOrThrow(schema) {
|
|
243
|
+
| value => value
|
|
244
|
+
| exception firstErr =>
|
|
245
|
+
let scalarFills = []
|
|
246
|
+
switch fillMissingDefaults(schema, json, scalarFills)->S.parseJsonOrThrow(schema) {
|
|
247
|
+
| value =>
|
|
248
|
+
// Only the invented values are worth a line. A heal that used nothing but
|
|
249
|
+
// schema-derived defaults is the mechanism working as designed.
|
|
250
|
+
if scalarFills->Array.length > 0 {
|
|
251
|
+
Console.warn(
|
|
252
|
+
`[reventless] decoded a stored message by inventing ${scalarFills
|
|
253
|
+
->Array.length
|
|
254
|
+
->Int.toString} missing scalar field(s): ${scalarFills->Array.join(
|
|
255
|
+
", ",
|
|
256
|
+
)}. A required scalar was added to a persisted type after this message was ` ++
|
|
257
|
+
`written; the value above is fabricated, not recovered. Prefer a js_nullable (T | null) field.`,
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
value
|
|
261
|
+
| exception _ => throw(firstErr)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
Decode a JSON value into `'a` using a sury schema.
|
|
267
|
+
|
|
268
|
+
Strict decode is the fast path; on failure it applies a single schema-migration-on-read
|
|
269
|
+
retry (see `fillMissingDefaults`) so a message persisted before a nested `@schema` field
|
|
270
|
+
existed still decodes instead of bricking. Re-throws the original error if the fill does
|
|
271
|
+
not resolve the failure.
|
|
272
|
+
|
|
273
|
+
@example
|
|
274
|
+
```rescript
|
|
275
|
+
let event = json->Message.decode(Category.eventSchema)
|
|
276
|
+
```
|
|
277
|
+
*/
|
|
278
|
+
let decode = (json, schema: S.t<'a>) => json->parseJsonTolerant(schema)
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
Encode a value to JSON using a sury schema.
|
|
282
|
+
|
|
283
|
+
@example
|
|
284
|
+
```rescript
|
|
285
|
+
let json = event->Message.encode(Category.eventSchema)
|
|
286
|
+
```
|
|
287
|
+
*/
|
|
288
|
+
let encode = (value, schema: S.t<'a>) => value->S.reverseConvertToJsonOrThrow(schema)
|
|
289
|
+
|
|
290
|
+
/** Raised by adapters when an incoming event JSON cannot be matched to a known event variant. */
|
|
291
|
+
exception InvalidEvent(JSON.t)
|
|
292
|
+
|
|
293
|
+
let toEventSchema' = (idSchema, eventSchema) =>
|
|
294
|
+
S.object(s => {
|
|
295
|
+
id: s.field("id", idSchema),
|
|
296
|
+
meta: s.field("meta", metaSchema),
|
|
297
|
+
event: s.field("event", eventSchema),
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
/** Decode a raw event JSON envelope into a typed `event'<'id, 'event>`. Tolerant on
|
|
301
|
+
read (see `fillMissingDefaults`) so envelopes persisted before a nested field existed
|
|
302
|
+
still decode. */
|
|
303
|
+
let decodeEvent' = (json, idSchema, eventSchema) =>
|
|
304
|
+
json->parseJsonTolerant(toEventSchema'(idSchema, eventSchema))
|
|
305
|
+
|
|
306
|
+
/** Extract the variant constructor name from a sury-encoded variant JSON. */
|
|
307
|
+
let variantNameOfJson = json =>
|
|
308
|
+
switch json {
|
|
309
|
+
| JSON.String(str) => str
|
|
310
|
+
| Object(dict) =>
|
|
311
|
+
switch dict->Dict.get("TAG") {
|
|
312
|
+
| Some(String(tag)) => tag
|
|
313
|
+
| _ => "unknown"
|
|
314
|
+
}
|
|
315
|
+
| _ => "unknown"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Compose a raw event JSON envelope from id, meta, and event JSON. */
|
|
319
|
+
let composeEventJson' = (id, meta, eventJson) =>
|
|
320
|
+
[
|
|
321
|
+
("id", id->JSON.Encode.string),
|
|
322
|
+
("meta", meta->S.reverseConvertToJsonOrThrow(metaSchema)),
|
|
323
|
+
("event", eventJson),
|
|
324
|
+
]
|
|
325
|
+
->Dict.fromArray
|
|
326
|
+
->JSON.Encode.object
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
5
|
+
|
|
6
|
+
S.enableJson();
|
|
7
|
+
|
|
8
|
+
let metaSchema = S.schema(s => ({
|
|
9
|
+
service: s.m(S.string),
|
|
10
|
+
time: s.m(S.string),
|
|
11
|
+
ip: s.m(S.option(S.string)),
|
|
12
|
+
user: s.m(S.option(S.string)),
|
|
13
|
+
msgId: s.m(S.string),
|
|
14
|
+
correlationId: s.m(S.string),
|
|
15
|
+
causationId: s.m(S.option(S.string)),
|
|
16
|
+
traceparent: s.m(S.option(S.string)),
|
|
17
|
+
schemaVersion: s.m(S.option(S.string)),
|
|
18
|
+
headers: s.m(S.option(S.dict(S.string)))
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
let contextSchema = S.schema(s => ({
|
|
22
|
+
id: s.m(S.string),
|
|
23
|
+
meta: s.m(metaSchema)
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
let statusChangeSchema = S.schema(s => ({
|
|
27
|
+
at: s.m(S.string),
|
|
28
|
+
by: s.m(S.string)
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
let commandJsonSchema = S.schema(s => ({
|
|
32
|
+
id: s.m(S.string),
|
|
33
|
+
meta: s.m(metaSchema),
|
|
34
|
+
commandJson: s.m(S.json),
|
|
35
|
+
delay: s.m(S.option(S.int))
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
let fillMissingDefaults = (function(schema, json, scalarFills){
|
|
39
|
+
function isSchema(x){ return x && typeof x === "object" && typeof x.type === "string"; }
|
|
40
|
+
function firstConst(anyOf){ var m=(anyOf||[]).find(function(s){return s.const!==undefined;}); return m ? m.const : undefined; }
|
|
41
|
+
function scalarDefault(schema){
|
|
42
|
+
if(schema.const!==undefined) return schema.const;
|
|
43
|
+
switch(schema.type){
|
|
44
|
+
case "string": return "";
|
|
45
|
+
case "number": return 0;
|
|
46
|
+
case "boolean": return false;
|
|
47
|
+
default: return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function fill(schema, value, path){
|
|
51
|
+
if(!isSchema(schema)) return value;
|
|
52
|
+
switch(schema.type){
|
|
53
|
+
case "object": {
|
|
54
|
+
if(value===undefined){ value={}; }
|
|
55
|
+
else if(value===null || typeof value!=="object" || Array.isArray(value)) return value;
|
|
56
|
+
var items=schema.items||[];
|
|
57
|
+
for(var i=0;i<items.length;i++){ var it=items[i]; value[it.location]=fill(it.schema, value[it.location], path+"."+it.location); }
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
case "array": {
|
|
61
|
+
if(Array.isArray(value)){ var el=schema.additionalItems; return isSchema(el) ? value.map(function(v,ix){return fill(el,v,path+"["+ix+"]");}) : value; }
|
|
62
|
+
if(value===undefined) return [];
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
case "union": {
|
|
66
|
+
var has=schema.has||{};
|
|
67
|
+
if(value===undefined){
|
|
68
|
+
if(has.null) return null;
|
|
69
|
+
var c=firstConst(schema.anyOf); if(c!==undefined) return c;
|
|
70
|
+
var obj=(schema.anyOf||[]).find(function(s){return s.type==="object";}); if(obj) return fill(obj,{},path);
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
if(value===null) return null;
|
|
74
|
+
var members=schema.anyOf||[];
|
|
75
|
+
if(Array.isArray(value)){ var a=members.find(function(s){return s.type==="array";}); return a ? fill(a,value,path) : value; }
|
|
76
|
+
if(typeof value==="object"){
|
|
77
|
+
var m=members.find(function(s){return s.type==="object" && (s.items||[]).some(function(it){return it.location==="TAG" && it.schema.const===value.TAG;});});
|
|
78
|
+
if(!m) m=members.find(function(s){return s.type==="object";});
|
|
79
|
+
return m ? fill(m,value,path) : value;
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
default: {
|
|
84
|
+
if(value!==undefined) return value;
|
|
85
|
+
var d=scalarDefault(schema);
|
|
86
|
+
if(d===undefined) return value;
|
|
87
|
+
scalarFills.push(path + " := " + JSON.stringify(d));
|
|
88
|
+
return d;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Clone via JSON round-trip (json is already pure JSON) so the caller's value is never mutated.
|
|
93
|
+
return fill(schema, JSON.parse(JSON.stringify(json)), "");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
function parseJsonTolerant(json, schema) {
|
|
97
|
+
try {
|
|
98
|
+
return S.parseJsonOrThrow(json, schema);
|
|
99
|
+
} catch (firstErr) {
|
|
100
|
+
let scalarFills = [];
|
|
101
|
+
let value;
|
|
102
|
+
try {
|
|
103
|
+
value = S.parseJsonOrThrow(fillMissingDefaults(schema, json, scalarFills), schema);
|
|
104
|
+
} catch (exn) {
|
|
105
|
+
throw firstErr;
|
|
106
|
+
}
|
|
107
|
+
if (scalarFills.length !== 0) {
|
|
108
|
+
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 a js_nullable (T | null) field.`);
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let decode = parseJsonTolerant;
|
|
115
|
+
|
|
116
|
+
let encode = S.reverseConvertToJsonOrThrow;
|
|
117
|
+
|
|
118
|
+
let InvalidEvent = /* @__PURE__ */Primitive_exceptions.create("Message-Reventless.InvalidEvent");
|
|
119
|
+
|
|
120
|
+
function toEventSchema$p(idSchema, eventSchema) {
|
|
121
|
+
return S.object(s => ({
|
|
122
|
+
id: s.f("id", idSchema),
|
|
123
|
+
meta: s.f("meta", metaSchema),
|
|
124
|
+
event: s.f("event", eventSchema)
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function decodeEvent$p(json, idSchema, eventSchema) {
|
|
129
|
+
return parseJsonTolerant(json, toEventSchema$p(idSchema, eventSchema));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function variantNameOfJson(json) {
|
|
133
|
+
if (json === null || Array.isArray(json)) {
|
|
134
|
+
return "unknown";
|
|
135
|
+
}
|
|
136
|
+
switch (typeof json) {
|
|
137
|
+
case "string" :
|
|
138
|
+
return json;
|
|
139
|
+
case "object" :
|
|
140
|
+
let match = json["TAG"];
|
|
141
|
+
if (typeof match === "string") {
|
|
142
|
+
return match;
|
|
143
|
+
} else {
|
|
144
|
+
return "unknown";
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
return "unknown";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function composeEventJson$p(id, meta, eventJson) {
|
|
152
|
+
return Object.fromEntries([
|
|
153
|
+
[
|
|
154
|
+
"id",
|
|
155
|
+
id
|
|
156
|
+
],
|
|
157
|
+
[
|
|
158
|
+
"meta",
|
|
159
|
+
S.reverseConvertToJsonOrThrow(meta, metaSchema)
|
|
160
|
+
],
|
|
161
|
+
[
|
|
162
|
+
"event",
|
|
163
|
+
eventJson
|
|
164
|
+
]
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let serviceSchema = S.string;
|
|
169
|
+
|
|
170
|
+
export {
|
|
171
|
+
serviceSchema,
|
|
172
|
+
metaSchema,
|
|
173
|
+
contextSchema,
|
|
174
|
+
statusChangeSchema,
|
|
175
|
+
commandJsonSchema,
|
|
176
|
+
fillMissingDefaults,
|
|
177
|
+
parseJsonTolerant,
|
|
178
|
+
decode,
|
|
179
|
+
encode,
|
|
180
|
+
InvalidEvent,
|
|
181
|
+
toEventSchema$p,
|
|
182
|
+
decodeEvent$p,
|
|
183
|
+
variantNameOfJson,
|
|
184
|
+
composeEventJson$p,
|
|
185
|
+
}
|
|
186
|
+
/* Not a pure module */
|