@reventlessdev/reventless-spec 3.0.0-alpha.80 → 3.0.0-alpha.81
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 +8 -0
- package/package.json +1 -1
- package/src/components/Plugin.res +10 -0
- package/src/components/Plugin.res.mjs +1 -0
- package/src/components/StateAnnotations.res +23 -0
- package/src/components/StateViewSlice.res +27 -3
- package/src/types/DateTime.res +33 -0
- package/src/types/DateTime.res.mjs +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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.81 (2026-07-26)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **auto-ui:** declare command target state via [@target](https://github.com/target)State ([5fc0374](https://github.com/ReventlessDev/reventless-core/commit/5fc03741a8816c57085b86a4ad7d595e3b690193)), closes [#5](https://github.com/ReventlessDev/reventless-core/issues/5)
|
|
11
|
+
* **auto-ui:** declare field semantics + dashboard metrics ([@semantic](https://github.com/semantic), [@metric](https://github.com/metric)) ([d74ff77](https://github.com/ReventlessDev/reventless-core/commit/d74ff7721e18e8638a82931a370a549b304dac94)), closes [#4](https://github.com/ReventlessDev/reventless-core/issues/4)
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
# 3.0.0-alpha.80 (2026-07-22)
|
|
7
15
|
|
|
8
16
|
### Features
|
package/package.json
CHANGED
|
@@ -177,6 +177,16 @@ type commandDef = {
|
|
|
177
177
|
*/
|
|
178
178
|
allowedStates: @s.matches(stringArrayOptionSchema) option<array<string>>,
|
|
179
179
|
/**
|
|
180
|
+
The single status value this command's handler writes — the command's *to*
|
|
181
|
+
state, sibling of `allowedStates`' *from* set. Source: the
|
|
182
|
+
`@targetState("Shipped")` command-variant annotation. `None` (absent
|
|
183
|
+
annotation) is the back-compat default: AutoUI's board resolver then falls
|
|
184
|
+
back to its name-stem heuristic. `Some("Shipped")` lets the resolver move a
|
|
185
|
+
row by a declared transition instead of a guess. js_nullable for JSON safety,
|
|
186
|
+
same as `allowedStates`.
|
|
187
|
+
*/
|
|
188
|
+
targetState: @s.matches(stringOptionSchema) option<string>,
|
|
189
|
+
/**
|
|
180
190
|
Whether this command variant is exposed in the generated API (a non-`@noApi`
|
|
181
191
|
variant of a non-`@noApi` command). Dev tooling badges API-exposed commands in
|
|
182
192
|
the event graph. js_nullable (T | null) so it stays JSON-safe inside the
|
|
@@ -102,6 +102,7 @@ let commandDefSchema = S.schema(s => ({
|
|
|
102
102
|
mutationField: s.m(S.string),
|
|
103
103
|
references: s.m(S.array(fieldReferenceSchema)),
|
|
104
104
|
allowedStates: s.m(stringArrayOptionSchema),
|
|
105
|
+
targetState: s.m(stringOptionSchema),
|
|
105
106
|
apiExposed: s.m(boolOptionSchema)
|
|
106
107
|
}));
|
|
107
108
|
|
|
@@ -26,6 +26,13 @@ free on the in-memory adapter but is `O(n)` Scan + FilterExpression on
|
|
|
26
26
|
DynamoDB-backed adapters — the annotation is the explicit signal that the
|
|
27
27
|
read model is small enough or the cost is acceptable.
|
|
28
28
|
*/
|
|
29
|
+
/**
|
|
30
|
+
Declared aggregation for a `@metric`-annotated numeric field. `aggregate` is one
|
|
31
|
+
of `"count"`, `"sum"`, `"avg"` (the UI's dashboard vocabulary); `label` is the
|
|
32
|
+
KPI label, or `""` to let the UI derive one from the field name.
|
|
33
|
+
*/
|
|
34
|
+
type metricSpec = {aggregate: string, label: string}
|
|
35
|
+
|
|
29
36
|
type stateAnnotationSpec = {
|
|
30
37
|
ids: array<string>,
|
|
31
38
|
compositeIds: array<string>,
|
|
@@ -40,6 +47,22 @@ type stateAnnotationSpec = {
|
|
|
40
47
|
scan: array<string>,
|
|
41
48
|
scanSort: array<string>,
|
|
42
49
|
/**
|
|
50
|
+
Fields annotated `@semantic("<id>")` — `(fieldName, semanticId)` pairs. The
|
|
51
|
+
semantic id is the UI's `AutoSemantics` vocabulary (e.g. `"currency"`,
|
|
52
|
+
`"geo-lat"`); the PPX can't validate it (the UI owns that vocabulary).
|
|
53
|
+
`SuryToJsonSchema` emits `x-reventless-semantic` on the named field, which
|
|
54
|
+
AutoUI reads at `#Annotation` provenance — above its heuristic, below a
|
|
55
|
+
`ui-hints.json` `fields:` override.
|
|
56
|
+
*/
|
|
57
|
+
semantic: array<(string, string)>,
|
|
58
|
+
/**
|
|
59
|
+
Fields annotated `@metric(...)` — `(fieldName, metricSpec)` pairs.
|
|
60
|
+
`SuryToJsonSchema` emits `x-reventless-metric: {aggregate, label}` on the named
|
|
61
|
+
field, which AutoUI folds into the dashboard metric list it already builds from
|
|
62
|
+
hints — so a plugin gets a Dashboard page from its schema alone.
|
|
63
|
+
*/
|
|
64
|
+
metric: array<(string, metricSpec)>,
|
|
65
|
+
/**
|
|
43
66
|
Field annotated `@status` on the state record (PPX-emitted). `Some(name)`
|
|
44
67
|
when one such annotation exists; the PPX errors on duplicate `@status`
|
|
45
68
|
annotations within the same record. Codegen consumes this to populate
|
|
@@ -24,7 +24,7 @@ let name = "CategoriesView"
|
|
|
24
24
|
| CategoryRenamed({categoryId: string, name: string})
|
|
25
25
|
| CategoryArchived({categoryId: string})
|
|
26
26
|
|
|
27
|
-
let project = event => switch event {
|
|
27
|
+
let project = ({event}) => switch event {
|
|
28
28
|
| CategoryAdded({categoryId, name}) =>
|
|
29
29
|
[Set(categoryId, {categoryId, name, archived: false})]
|
|
30
30
|
| CategoryRenamed({categoryId, name}) =>
|
|
@@ -81,6 +81,29 @@ module type Spec = {
|
|
|
81
81
|
let visibility: Visibility.t
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
The envelope a projection receives for each consumed event.
|
|
86
|
+
|
|
87
|
+
Carries the decoded event alongside the metadata the storage layer already
|
|
88
|
+
holds, so a projection can persist producer time or the acting user without the
|
|
89
|
+
command author having to duplicate that framework state into the event payload.
|
|
90
|
+
|
|
91
|
+
`meta.time` and `recordedAt` are **different clocks** — pick deliberately:
|
|
92
|
+
|
|
93
|
+
- `meta.time` — *producer* time, stamped when the command handler created the
|
|
94
|
+
event. This is the domain-meaningful timestamp (when the order was placed,
|
|
95
|
+
when it shipped); it is identical across every path.
|
|
96
|
+
- `recordedAt` — *storage* time, when the event was appended to the DCB log. On
|
|
97
|
+
the AWS (DynamoDB-stream) path this is the authoritative stored column; on the
|
|
98
|
+
local (topic) path it is stamped at publish, so it may sit a few milliseconds
|
|
99
|
+
after the stored value. Use it for storage-lag diagnostics, not domain dates.
|
|
100
|
+
*/
|
|
101
|
+
type consumed<'e> = {
|
|
102
|
+
event: 'e,
|
|
103
|
+
meta: Message.meta,
|
|
104
|
+
recordedAt: string,
|
|
105
|
+
}
|
|
106
|
+
|
|
84
107
|
/**
|
|
85
108
|
The Projection — the pure projection function from consumed events to actions.
|
|
86
109
|
*/
|
|
@@ -89,9 +112,10 @@ module type Projection = {
|
|
|
89
112
|
|
|
90
113
|
/**
|
|
91
114
|
Projects one consumed event into read model actions.
|
|
92
|
-
Receives
|
|
115
|
+
Receives the event wrapped in a `consumed` envelope (event + `meta` +
|
|
116
|
+
`recordedAt`); only events declared in `Spec.consumedEvent` — no wildcard needed.
|
|
93
117
|
*/
|
|
94
|
-
let project: Spec.consumedEvent => array<Projection.action<string, Spec.state>>
|
|
118
|
+
let project: consumed<Spec.consumedEvent> => array<Projection.action<string, Spec.state>>
|
|
95
119
|
|
|
96
120
|
/** File URL of this Projection module (`import.meta.url`). */
|
|
97
121
|
let moduleUrl: string
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
/** Internal sury metadata ID used to mark a date-time string field. */
|
|
25
|
+
let dateTimeId: S.Metadata.Id.t<bool> = S.Metadata.Id.make(~namespace="reventless", ~name="dateTime")
|
|
26
|
+
|
|
27
|
+
/** A sury string schema annotated as an ISO-8601 date-time field.
|
|
28
|
+
Use with `@s.matches(Reventless.DateTime.string)`. */
|
|
29
|
+
let string: S.t<string> = S.string->S.Metadata.set(~id=dateTimeId, true)
|
|
30
|
+
|
|
31
|
+
/** Whether a field schema carries the date-time marker. */
|
|
32
|
+
let isDateTime = (fieldSchema: S.t<unknown>) =>
|
|
33
|
+
S.Metadata.get(fieldSchema, ~id=dateTimeId)->Option.isSome
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
|
|
6
|
+
let dateTimeId = S.Metadata.Id.make("reventless", "dateTime");
|
|
7
|
+
|
|
8
|
+
let string = S.Metadata.set(S.string, dateTimeId, true);
|
|
9
|
+
|
|
10
|
+
function isDateTime(fieldSchema) {
|
|
11
|
+
return Stdlib_Option.isSome(S.Metadata.get(fieldSchema, dateTimeId));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
dateTimeId,
|
|
16
|
+
string,
|
|
17
|
+
isDateTime,
|
|
18
|
+
}
|
|
19
|
+
/* dateTimeId Not a pure module */
|