@reventlessdev/reventless-spec 3.0.0-alpha.130 → 3.0.0-alpha.132
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 +17 -0
- package/package.json +3 -2
- package/run-check-lifecycle.mjs +2 -0
- package/schema/platform-api.graphql +1 -0
- package/src/components/ComponentKind.res +14 -8
- package/src/components/ComponentKind.res.mjs +8 -6
- package/src/lifecycle/CheckLifecycleModel.res +1353 -0
- package/src/lifecycle/CheckLifecycleModel.res.mjs +1134 -0
- package/src/lifecycle/loadPluginStructure.mjs +66 -0
- package/src/semantic/CalendarDate.res +58 -0
- package/src/semantic/CalendarDate.res.mjs +37 -0
- package/src/semantic/DateRange.res +10 -10
- package/src/semantic/DateRange.res.mjs +12 -14
- package/src/semantic/DateTime.res +92 -0
- package/src/semantic/DateTime.res.mjs +65 -0
- package/src/semantic/RowImage.res +122 -0
- package/src/semantic/RowImage.res.mjs +101 -0
- package/src/semantic/Semantic.res +8 -0
- package/src/semantic/Semantic.res.mjs +2 -0
- package/src/types/Lifecycle.res +106 -0
- package/src/types/Lifecycle.res.mjs +115 -0
- package/src/types/DateTime.res +0 -29
- package/src/types/DateTime.res.mjs +0 -16
|
@@ -6,6 +6,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
6
6
|
|
|
7
7
|
let Id = {
|
|
8
8
|
dateTime: "dateTime",
|
|
9
|
+
date: "date",
|
|
9
10
|
reference: "reference",
|
|
10
11
|
storageRef: "storageRef",
|
|
11
12
|
offload: "offload",
|
|
@@ -25,6 +26,7 @@ let Id = {
|
|
|
25
26
|
money: "money",
|
|
26
27
|
dateRange: "dateRange",
|
|
27
28
|
geoPoint: "geoPoint",
|
|
29
|
+
lifecycleTrail: "lifecycleTrail",
|
|
28
30
|
geolocation: "geolocation"
|
|
29
31
|
};
|
|
30
32
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Where a record's lifecycle lives, and the trail of states it has been through.
|
|
3
|
+
|
|
4
|
+
`fieldName` is the single rule for which field holds a lifecycle — every
|
|
5
|
+
consumer resolves it here rather than restating it. `Trail` is the one field a
|
|
6
|
+
view declares to have each state it reaches recorded with the instant it was
|
|
7
|
+
reached; the projection machinery fills it, not the domain.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// A wire enum: a union whose non-null members are all string constants. A
|
|
11
|
+
// tagged union's members are objects, so the same test excludes it.
|
|
12
|
+
let isEnumSchema = (schema: S.t<unknown>): bool =>
|
|
13
|
+
switch schema {
|
|
14
|
+
| AnyOf({anyOf}) =>
|
|
15
|
+
let members = anyOf->Array.filter(v =>
|
|
16
|
+
switch v {
|
|
17
|
+
| Null(_) | Undefined(_) => false
|
|
18
|
+
| _ => true
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
members->Array.length > 0 &&
|
|
22
|
+
members->Array.every(v =>
|
|
23
|
+
switch v {
|
|
24
|
+
| String({const: ?Some(_)}) => true
|
|
25
|
+
| _ => false
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
| _ => false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// A semantic or a DCB tag makes the field an instant, an id or a reference
|
|
32
|
+
// before it is ever an enum — the order `SchemaType.fromSury` resolves in.
|
|
33
|
+
let isLifecycleShape = (schema: S.t<unknown>): bool =>
|
|
34
|
+
Semantic.get(schema)->Option.isNone && !DcbTag.isTagged(schema) && isEnumSchema(schema)
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
The field holding a record's lifecycle: `@lifecycle`, else an enum field
|
|
38
|
+
literally named `lifecycle`. Not keyed on `status`, a name that would guess.
|
|
39
|
+
*/
|
|
40
|
+
let fieldName = (stateSchema: S.t<unknown>): option<string> =>
|
|
41
|
+
switch StateAnnotations.getSpec(stateSchema) {
|
|
42
|
+
| Some({lifecycle: Some(_) as annotated}) => annotated
|
|
43
|
+
| _ =>
|
|
44
|
+
switch stateSchema {
|
|
45
|
+
| Object({properties}) =>
|
|
46
|
+
properties
|
|
47
|
+
->Dict.get("lifecycle")
|
|
48
|
+
->Option.flatMap(schema => isLifecycleShape(schema) ? Some("lifecycle") : None)
|
|
49
|
+
| _ => None
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
The ordered record of the states a row has been through.
|
|
55
|
+
|
|
56
|
+
Ordered rather than keyed by state because a lifecycle revisits states — a
|
|
57
|
+
reopened order is `Placed` twice, and a map has to choose which visit to keep.
|
|
58
|
+
A state the row never reached has no entry; there is no value to invent for it.
|
|
59
|
+
*/
|
|
60
|
+
module Trail = {
|
|
61
|
+
/** One state the row entered, and the instant it did. */
|
|
62
|
+
@schema
|
|
63
|
+
type entry<'state> = {
|
|
64
|
+
state: 'state,
|
|
65
|
+
at: DateTime.t,
|
|
66
|
+
/** First entry after a span the cap dropped. Absent while trails are kept
|
|
67
|
+
whole; declared now so capping later is not a contract change. */
|
|
68
|
+
afterGap?: bool,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type t<'state> = array<entry<'state>>
|
|
72
|
+
|
|
73
|
+
/** The schema for a trail field. Resolved by sury-ppx from the declared type
|
|
74
|
+
`Reventless.Lifecycle.Trail.t<lifecycle>`, so a view writes no annotation.
|
|
75
|
+
|
|
76
|
+
Marked with the shared semantic rather than a metadata id of its own: that
|
|
77
|
+
marker is the one the JSON Schema walk emits, so a private id would leave
|
|
78
|
+
the trail indistinguishable on the wire from any other array of objects —
|
|
79
|
+
and a consumer reduced to matching a field called `trail` by name. */
|
|
80
|
+
let schema = (stateSchema: S.t<'state>): S.t<t<'state>> =>
|
|
81
|
+
S.array(entrySchema(stateSchema))->Semantic.mark(~id=Semantic.Id.lifecycleTrail)
|
|
82
|
+
|
|
83
|
+
let isTrail = (schema: S.t<unknown>): bool =>
|
|
84
|
+
Semantic.has(schema, ~id=Semantic.Id.lifecycleTrail)
|
|
85
|
+
|
|
86
|
+
/** The trail field declared on a state record, if it declares one. */
|
|
87
|
+
let fieldName = (stateSchema: S.t<unknown>): option<string> =>
|
|
88
|
+
switch stateSchema {
|
|
89
|
+
| Object({properties}) =>
|
|
90
|
+
properties
|
|
91
|
+
->Dict.toArray
|
|
92
|
+
->Array.find(((_, schema)) => isTrail(schema))
|
|
93
|
+
->Option.map(((name, _)) => name)
|
|
94
|
+
| _ => None
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Append `{state, at}` to the trail a state's JSON holds at `field`. */
|
|
98
|
+
let record = (stateDict: dict<JSON.t>, ~field: string, ~state: JSON.t, ~at: string): unit => {
|
|
99
|
+
let entries = switch stateDict->Dict.get(field) {
|
|
100
|
+
| Some(Array(existing)) => existing
|
|
101
|
+
| _ => []
|
|
102
|
+
}
|
|
103
|
+
let entry = Dict.fromArray([("state", state), ("at", JSON.Encode.string(at))])
|
|
104
|
+
stateDict->Dict.set(field, entries->Array.concat([entry->JSON.Encode.object])->JSON.Encode.array)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as DcbTag$Reventless from "../components/DcbTag.res.mjs";
|
|
6
|
+
import * as DateTime$Reventless from "../semantic/DateTime.res.mjs";
|
|
7
|
+
import * as Semantic$Reventless from "../semantic/Semantic.res.mjs";
|
|
8
|
+
import * as StateAnnotations$Reventless from "../components/StateAnnotations.res.mjs";
|
|
9
|
+
|
|
10
|
+
function isEnumSchema(schema) {
|
|
11
|
+
if (schema.type !== "anyOf") {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
let members = schema.anyOf.filter(v => {
|
|
15
|
+
switch (v.type) {
|
|
16
|
+
case "null" :
|
|
17
|
+
case "undefined" :
|
|
18
|
+
return false;
|
|
19
|
+
default:
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
if (members.length !== 0) {
|
|
24
|
+
return members.every(v => {
|
|
25
|
+
if (v.type === "string") {
|
|
26
|
+
return v.const !== undefined;
|
|
27
|
+
} else {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isLifecycleShape(schema) {
|
|
37
|
+
if (Stdlib_Option.isNone(Semantic$Reventless.get(schema)) && !DcbTag$Reventless.isTagged(schema)) {
|
|
38
|
+
return isEnumSchema(schema);
|
|
39
|
+
} else {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fieldName(stateSchema) {
|
|
45
|
+
let match = StateAnnotations$Reventless.getSpec(stateSchema);
|
|
46
|
+
if (match !== undefined) {
|
|
47
|
+
let annotated = match.lifecycle;
|
|
48
|
+
if (annotated !== undefined) {
|
|
49
|
+
return annotated;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (stateSchema.type === "object") {
|
|
53
|
+
return Stdlib_Option.flatMap(stateSchema.properties["lifecycle"], schema => {
|
|
54
|
+
if (isLifecycleShape(schema)) {
|
|
55
|
+
return "lifecycle";
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function entrySchema(_stateSchema) {
|
|
62
|
+
return Sury.$schema(s => ({
|
|
63
|
+
state: s.m(_stateSchema),
|
|
64
|
+
at: s.m(DateTime$Reventless.schema),
|
|
65
|
+
afterGap: s.m(Sury.$option(Sury.bool))
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function schema(stateSchema) {
|
|
70
|
+
return Semantic$Reventless.mark(Sury.array(entrySchema(stateSchema)), Semantic$Reventless.Id.lifecycleTrail, undefined);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isTrail(schema) {
|
|
74
|
+
return Semantic$Reventless.has(schema, Semantic$Reventless.Id.lifecycleTrail);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function fieldName$1(stateSchema) {
|
|
78
|
+
if (stateSchema.type === "object") {
|
|
79
|
+
return Stdlib_Option.map(Object.entries(stateSchema.properties).find(param => Semantic$Reventless.has(param[1], Semantic$Reventless.Id.lifecycleTrail)), param => param[0]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function record(stateDict, field, state, at) {
|
|
84
|
+
let match = stateDict[field];
|
|
85
|
+
let entries = match !== undefined ? (
|
|
86
|
+
Array.isArray(match) ? match : []
|
|
87
|
+
) : [];
|
|
88
|
+
let entry = Object.fromEntries([
|
|
89
|
+
[
|
|
90
|
+
"state",
|
|
91
|
+
state
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
"at",
|
|
95
|
+
at
|
|
96
|
+
]
|
|
97
|
+
]);
|
|
98
|
+
stateDict[field] = entries.concat([entry]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let Trail = {
|
|
102
|
+
entrySchema: entrySchema,
|
|
103
|
+
schema: schema,
|
|
104
|
+
isTrail: isTrail,
|
|
105
|
+
fieldName: fieldName$1,
|
|
106
|
+
record: record
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
isEnumSchema,
|
|
111
|
+
isLifecycleShape,
|
|
112
|
+
fieldName,
|
|
113
|
+
Trail,
|
|
114
|
+
}
|
|
115
|
+
/* sury Not a pure module */
|
package/src/types/DateTime.res
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
Marks a `string` state field as an ISO-8601 date-time.
|
|
3
|
-
|
|
4
|
-
Sury has no string-typed datetime format (`S.datetime` transforms to `Js.Date.t`,
|
|
5
|
-
changing the field's runtime type), so this mirrors the `DcbTag.string`
|
|
6
|
-
precedent: a `S.t<string>` carrying sury metadata that downstream schema walkers
|
|
7
|
-
detect. `SchemaType`/`SuryToJsonSchema` surface it as `format: "date-time"` on
|
|
8
|
-
the field's JSON Schema, which the AutoUI date heuristics key off (CalendarView,
|
|
9
|
-
TimelineView, date-axis charts).
|
|
10
|
-
|
|
11
|
-
Use on a producer/storage timestamp a projection writes into its state — most
|
|
12
|
-
commonly a `meta.time`-derived field such as `placedAt` / `shippedAt`:
|
|
13
|
-
|
|
14
|
-
@example
|
|
15
|
-
```rescript
|
|
16
|
-
@schema
|
|
17
|
-
type state = {
|
|
18
|
-
orderId: string,
|
|
19
|
-
placedAt: @s.matches(Reventless.DateTime.string) string,
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/** A sury string schema annotated as an ISO-8601 date-time field.
|
|
25
|
-
Use with `@s.matches(Reventless.DateTime.string)`. */
|
|
26
|
-
let string: S.t<string> = S.string->Semantic.mark(~id=Semantic.Id.dateTime)
|
|
27
|
-
|
|
28
|
-
/** Whether a field schema carries the date-time marker. */
|
|
29
|
-
let isDateTime = (fieldSchema: S.t<unknown>) => fieldSchema->Semantic.has(~id=Semantic.Id.dateTime)
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Sury from "sury";
|
|
4
|
-
import * as Semantic$Reventless from "../semantic/Semantic.res.mjs";
|
|
5
|
-
|
|
6
|
-
let string = Semantic$Reventless.mark(Sury.string, Semantic$Reventless.Id.dateTime, undefined);
|
|
7
|
-
|
|
8
|
-
function isDateTime(fieldSchema) {
|
|
9
|
-
return Semantic$Reventless.has(fieldSchema, Semantic$Reventless.Id.dateTime);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
string,
|
|
14
|
-
isDateTime,
|
|
15
|
-
}
|
|
16
|
-
/* string Not a pure module */
|