@reventlessdev/reventless-local 3.0.0-alpha.246 → 3.0.0-alpha.248
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/package.json +9 -9
- package/src/Platform.res.mjs +4 -4
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +17 -0
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +9 -0
- package/src/reset/LocalSeedReset.res.mjs +1 -1
- package/tests/PluginEventDecodeTest.res.mjs +1 -1
- package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +98 -0
- package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res.mjs +187 -0
- package/tests/components/stateviewslice/StateViewSliceDisplayNameTest.res +32 -0
- package/tests/components/stateviewslice/StateViewSliceDisplayNameTest.res.mjs +35 -0
- package/tests/plugin/UiFragmentRegistry_GWT.res.mjs +2 -2
- package/tests/plugin/UiFragments_GWT.res.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.248 (2026-09-08)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* a slice folder is named for its kind, without the Slice ([a400555](https://github.com/ReventlessDev/reventless-core/commit/a400555121b023823983ebe846e99cda27063c1f))
|
|
11
|
+
* **core:** a reference names a row and shows its picture ([2b40d04](https://github.com/ReventlessDev/reventless-core/commit/2b40d0418bba2bf83463f446e94e372ea0c9898c))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 3.0.0-alpha.247 (2026-09-07)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **core:** a state view slice composes its [@display](https://github.com/display)Name like a read model ([851aa1d](https://github.com/ReventlessDev/reventless-core/commit/851aa1d12ddae9dd39f91084f5719cd98fe1cbd4))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.246 (2026-09-07)
|
|
7
22
|
|
|
8
23
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-local",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.248",
|
|
4
4
|
"description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -36,17 +36,17 @@
|
|
|
36
36
|
"ws": "^8.18.0",
|
|
37
37
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
38
38
|
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.27",
|
|
39
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
40
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.9",
|
|
41
39
|
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
|
|
40
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.9",
|
|
41
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
42
42
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
43
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.
|
|
44
|
-
"@reventlessdev/reventless-
|
|
45
|
-
"@reventlessdev/reventless-
|
|
46
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.121",
|
|
47
|
-
"@reventlessdev/reventless-gwt": "1.0.0-alpha.204",
|
|
43
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.259",
|
|
44
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.206",
|
|
45
|
+
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.107",
|
|
48
46
|
"@reventlessdev/reventless-seed": "1.0.0-alpha.19",
|
|
49
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
47
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.132",
|
|
48
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.160",
|
|
49
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.123"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"rescript": "12.3.0",
|
package/src/Platform.res.mjs
CHANGED
|
@@ -28,7 +28,7 @@ import * as Api_Naming$ReventlessCore from "@reventlessdev/reventless-core/src/c
|
|
|
28
28
|
import * as PluginSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginSpec.res.mjs";
|
|
29
29
|
import * as UserStore$ReventlessLocal from "./adapter/Auth/UserStore.res.mjs";
|
|
30
30
|
import * as TestRunner$ReventlessLocal from "./test/TestRunner.res.mjs";
|
|
31
|
-
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
31
|
+
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateView/UiFragments.res.mjs";
|
|
32
32
|
import * as CommandTopic$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic.res.mjs";
|
|
33
33
|
import * as EffectLogger$ReventlessCore from "@reventlessdev/reventless-core/src/util/EffectLogger.res.mjs";
|
|
34
34
|
import * as ShellConfig$ReventlessLocal from "./ShellConfig.res.mjs";
|
|
@@ -60,7 +60,7 @@ import * as LocalClonerRunner$ReventlessLocal from "./adapter/Cloner/LocalCloner
|
|
|
60
60
|
import * as PluginBaseFragment$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/api/PluginBaseFragment.res.mjs";
|
|
61
61
|
import * as ProjectionPending$ReventlessLocal from "./adapter/ProjectionPending.res.mjs";
|
|
62
62
|
import * as ReadModel_Builder$ReventlessLocal from "./components/ReadModel_Builder.res.mjs";
|
|
63
|
-
import * as UiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
63
|
+
import * as UiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res.mjs";
|
|
64
64
|
import * as LocalEvents_Server$ReventlessLocal from "./adapter/Api/LocalEvents_Server.res.mjs";
|
|
65
65
|
import * as LocalPlatformStart$ReventlessLocal from "./LocalPlatformStart.res.mjs";
|
|
66
66
|
import * as PlatformMCP_Server$ReventlessLocal from "./adapter/PlatformMCP_Server.res.mjs";
|
|
@@ -76,7 +76,7 @@ import * as ProjectionCheckpoint$ReventlessLocal from "./adapter/ProjectionCheck
|
|
|
76
76
|
import * as ExtensionPointMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionPointMapping.res.mjs";
|
|
77
77
|
import * as LocalGeocodeResolvers$ReventlessLocal from "./adapter/LocalGeocodeResolvers.res.mjs";
|
|
78
78
|
import * as LocalPlatformRegistry$ReventlessLocal from "./LocalPlatformRegistry.res.mjs";
|
|
79
|
-
import * as UiFragments_Projection$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
79
|
+
import * as UiFragments_Projection$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateView/UiFragments_Projection.res.mjs";
|
|
80
80
|
import * as EventLogStorage_Sqlite$ReventlessLocal from "./adapter/EventLog/EventLogStorage_Sqlite.res.mjs";
|
|
81
81
|
import * as ExtensionPoint_Builder$ReventlessLocal from "./components/ExtensionPoint_Builder.res.mjs";
|
|
82
82
|
import * as PlatformGraphQL_Server$ReventlessLocal from "./adapter/PlatformGraphQL_Server.res.mjs";
|
|
@@ -94,7 +94,7 @@ import * as StateChangeSlice_Builder$ReventlessLocal from "./components/StateCha
|
|
|
94
94
|
import * as DcbEventLogStorage_Sqlite$ReventlessLocal from "./adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs";
|
|
95
95
|
import * as LocalEventCollectorChannel$ReventlessLocal from "./adapter/EventCollector/LocalEventCollectorChannel.res.mjs";
|
|
96
96
|
import * as PluginRuntime_Builder_Micro$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Runtime/PluginRuntime_Builder_Micro.res.mjs";
|
|
97
|
-
import * as UiFragmentRegistry_Behavior$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
97
|
+
import * as UiFragmentRegistry_Behavior$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry_Behavior.res.mjs";
|
|
98
98
|
import * as Platform_PluginStructuresApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_PluginStructuresApi.res.mjs";
|
|
99
99
|
import * as EventHistoryResolvers_GraphQL$ReventlessLocal from "./adapter/EventHistory/EventHistoryResolvers_GraphQL.res.mjs";
|
|
100
100
|
import * as InboundTranslationSlice_Builder$ReventlessLocal from "./components/InboundTranslationSlice_Builder.res.mjs";
|
|
@@ -190,6 +190,14 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
190
190
|
->Option.flatMap(Reventless.StateAnnotations.getSpec)
|
|
191
191
|
->Option.flatMap(spec => spec.retired)
|
|
192
192
|
|
|
193
|
+
// The picture a reference to one of these rows shows it by, derived once for
|
|
194
|
+
// every backend in `Reventless.RowImage`. Read per request for the same
|
|
195
|
+
// reason the two above are.
|
|
196
|
+
let imageSourceOf = () =>
|
|
197
|
+
Plugin_Helpers.stateSchemaRegistry
|
|
198
|
+
->Dict.get(name)
|
|
199
|
+
->Option.flatMap(Reventless.RowImage.sourceFrom)
|
|
200
|
+
|
|
193
201
|
// `includeRetired` is the caller asking; `decideRetired` decides whether the
|
|
194
202
|
// asking counts. Read here rather than inside the decision so the argument
|
|
195
203
|
// stays an argument — the door reports what was asked, the classifier says
|
|
@@ -500,10 +508,19 @@ module Make = (Bus: LocalBus.T) => {
|
|
|
500
508
|
// A view with no label field resolves to its id, which is
|
|
501
509
|
// what `labelField`'s own fallback already decided.
|
|
502
510
|
->Option.getOr(id)
|
|
511
|
+
// The picture travels with the name, so a retired row that
|
|
512
|
+
// keeps one keeps the other: an archived product reads as
|
|
513
|
+
// itself on the order that bought it.
|
|
514
|
+
let image =
|
|
515
|
+
switch (imageSourceOf(), item->JSON.Decode.object) {
|
|
516
|
+
| (Some(source), Some(d)) => d->Reventless.RowImage.refFrom(source)
|
|
517
|
+
| _ => None
|
|
518
|
+
}
|
|
503
519
|
Some(
|
|
504
520
|
Dict.fromArray([
|
|
505
521
|
("id", JSON.Encode.string(id)),
|
|
506
522
|
("label", JSON.Encode.string(label)),
|
|
523
|
+
("image", image->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
507
524
|
("retired", JSON.Encode.bool(retired)),
|
|
508
525
|
(
|
|
509
526
|
"retiredState",
|
|
@@ -10,6 +10,7 @@ import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js";
|
|
|
10
10
|
import * as Owner$Reventless from "@reventlessdev/reventless-spec/src/components/Owner.res.mjs";
|
|
11
11
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
12
12
|
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
13
|
+
import * as RowImage$Reventless from "@reventlessdev/reventless-spec/src/semantic/RowImage.res.mjs";
|
|
13
14
|
import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
|
|
14
15
|
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
15
16
|
import * as Api_Ids$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/Api_Ids.res.mjs";
|
|
@@ -106,6 +107,7 @@ function Make(Bus) {
|
|
|
106
107
|
return Stdlib_Option.mapOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), d => d[field]), Stdlib_JSON.Decode.string), false, v => v === required);
|
|
107
108
|
};
|
|
108
109
|
let retiredSpecOf = () => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Plugin_Helpers$ReventlessCore.stateSchemaRegistry[name], StateAnnotations$Reventless.getSpec), spec => spec.retired);
|
|
110
|
+
let imageSourceOf = () => Stdlib_Option.flatMap(Plugin_Helpers$ReventlessCore.stateSchemaRegistry[name], RowImage$Reventless.sourceFrom);
|
|
109
111
|
let askedForRetired = args => Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(args), d => d["includeRetired"]), Stdlib_JSON.Decode.bool), false);
|
|
110
112
|
let retiredDecision = (ctx, args) => OwnerScope$Reventless.decideRetired(extractIdentity(ctx), Stdlib_Option.map(retiredSpecOf(), r => r.field), Stdlib_Option.flatMap(retiredSpecOf(), r => r.values), askedForRetired(args), undefined);
|
|
111
113
|
let retiredAllows = (ctx, args, item) => {
|
|
@@ -289,6 +291,9 @@ function Make(Bus) {
|
|
|
289
291
|
return;
|
|
290
292
|
}
|
|
291
293
|
let label = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), d => d[labelField]), Stdlib_JSON.Decode.string), id);
|
|
294
|
+
let match$1 = imageSourceOf();
|
|
295
|
+
let match$2 = Stdlib_JSON.Decode.object(item);
|
|
296
|
+
let image = match$1 !== undefined && match$2 !== undefined ? RowImage$Reventless.refFrom(match$2, match$1) : undefined;
|
|
292
297
|
return Object.fromEntries([
|
|
293
298
|
[
|
|
294
299
|
"id",
|
|
@@ -298,6 +303,10 @@ function Make(Bus) {
|
|
|
298
303
|
"label",
|
|
299
304
|
label
|
|
300
305
|
],
|
|
306
|
+
[
|
|
307
|
+
"image",
|
|
308
|
+
Stdlib_Option.mapOr(image, null, prim => prim)
|
|
309
|
+
],
|
|
301
310
|
[
|
|
302
311
|
"retired",
|
|
303
312
|
retired
|
|
@@ -8,7 +8,7 @@ import * as Offload$Reventless from "@reventlessdev/reventless-spec/src/semantic
|
|
|
8
8
|
import * as Backend$ReventlessLocal from "../adapter/Backend.res.mjs";
|
|
9
9
|
import * as PluginSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginSpec.res.mjs";
|
|
10
10
|
import * as Seed_Prompt$ReventlessSeed from "@reventlessdev/reventless-seed/src/Seed_Prompt.res.mjs";
|
|
11
|
-
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
11
|
+
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateView/UiFragments.res.mjs";
|
|
12
12
|
import * as ComponentType$ReventlessCore from "@reventlessdev/reventless-core/src/ComponentType.res.mjs";
|
|
13
13
|
import * as SqliteDriver$ReventlessLocal from "../adapter/SqliteDriver.res.mjs";
|
|
14
14
|
import * as LocalSeedTarget$ReventlessLocal from "../LocalSeedTarget.res.mjs";
|
|
@@ -7,7 +7,7 @@ import * as PluginSpec$ReventlessCore from "@reventlessdev/reventless-core/src/p
|
|
|
7
7
|
import * as TestRunner$ReventlessLocal from "../src/test/TestRunner.res.mjs";
|
|
8
8
|
import * as ComponentType$ReventlessCore from "@reventlessdev/reventless-core/src/ComponentType.res.mjs";
|
|
9
9
|
import * as Plugin_Fixtures$ReventlessLocal from "./plugin/Plugin_Fixtures.res.mjs";
|
|
10
|
-
import * as UiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
10
|
+
import * as UiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res.mjs";
|
|
11
11
|
import * as Platform_Admin_Structure$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_Admin_Structure.res.mjs";
|
|
12
12
|
|
|
13
13
|
TestRunner$ReventlessLocal.setup();
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// E2E fixtures for the @displayName overlay on a StateViewSlice.
|
|
2
|
+
// A slice's state carries the same synthetic `displayName` a read model's does;
|
|
3
|
+
// this builds one by hand (the ppx writes the field and the metadata) so the
|
|
4
|
+
// projection path can be asserted to compose it.
|
|
5
|
+
|
|
6
|
+
open Reventless.Projection
|
|
7
|
+
|
|
8
|
+
module OrderEventLog = {
|
|
9
|
+
@schema
|
|
10
|
+
type event =
|
|
11
|
+
| OrderPlaced({id: @s.matches(Reventless.DcbTag.string) string, placedAt: string})
|
|
12
|
+
| OrderShipped({id: @s.matches(Reventless.DcbTag.string) string, shippedAt: string})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module OrdersViewSpec = {
|
|
16
|
+
let name = "DnOrdersView"
|
|
17
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
18
|
+
|
|
19
|
+
@schema
|
|
20
|
+
type consumedEvent =
|
|
21
|
+
| OrderPlaced({id: string, placedAt: string})
|
|
22
|
+
| OrderShipped({id: string, shippedAt: string})
|
|
23
|
+
|
|
24
|
+
@schema
|
|
25
|
+
type state = {
|
|
26
|
+
id: string,
|
|
27
|
+
placedAt: string,
|
|
28
|
+
shippedAt: string,
|
|
29
|
+
displayName: option<string>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// What the ppx emits for `@displayName placedAt`.
|
|
33
|
+
let stateSchema =
|
|
34
|
+
stateSchema->S.Metadata.set(
|
|
35
|
+
~id=Reventless.DisplayName.displayNameId,
|
|
36
|
+
{Reventless.DisplayName.fields: ["placedAt"], separator: " "},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
let config = Reventless.ReadModel.config()
|
|
40
|
+
let subIdConfig = None
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module OrdersViewProjection = {
|
|
44
|
+
module Spec = OrdersViewSpec
|
|
45
|
+
open OrdersViewSpec
|
|
46
|
+
|
|
47
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
48
|
+
|
|
49
|
+
let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
|
|
50
|
+
switch event {
|
|
51
|
+
| OrderPlaced({id, placedAt}) =>
|
|
52
|
+
[Set(id, {id, placedAt, shippedAt: "", displayName: None})]
|
|
53
|
+
| OrderShipped({id, shippedAt}) => [Update(id, s => {...s, shippedAt})]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module Bus = LocalBus.Make()
|
|
58
|
+
|
|
59
|
+
let _ = TestRunner.setup()
|
|
60
|
+
|
|
61
|
+
module OrderEventLogMaker = DcbEventLog_Builder.Make(Bus)
|
|
62
|
+
let eventLog = OrderEventLogMaker.make(
|
|
63
|
+
~name="DnOrderEventLog",
|
|
64
|
+
~partitionTag=Reventless.DcbTag.Simple({key: "id"}),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
module SVMaker = StateViewSlice_Builder.Make(Bus)
|
|
68
|
+
module OrdersViewMaker = SVMaker.Make(OrdersViewSpec, OrdersViewProjection)
|
|
69
|
+
let sv = OrdersViewMaker.make(~dcbEventLog=eventLog)
|
|
70
|
+
|
|
71
|
+
let dcbEventTopicResource =
|
|
72
|
+
(eventLog->ReventlessCore.Component.outputs).eventTopic.resources->Array.getUnsafe(0)
|
|
73
|
+
|
|
74
|
+
let encodeEvent = (event: OrderEventLog.event): ReventlessInfra.DcbEventLog.rawEvent => {
|
|
75
|
+
let json = event->Reventless.Util_Sury.toJson(OrderEventLog.eventSchema)
|
|
76
|
+
let (eventType, data) = json->ReventlessCore.Message.splitMessage
|
|
77
|
+
let tags = Reventless.DcbTag.extractTags(OrderEventLog.eventSchema, event)
|
|
78
|
+
let meta = ReventlessCore.Message.generateMeta(~service="test")
|
|
79
|
+
{eventType, data: JSON.Object(data), tags, meta}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let appendEvent = async event => {
|
|
83
|
+
let ops = await eventLog->OrderEventLogMaker.operations->TestRunner.resolve
|
|
84
|
+
let _ = await ops.append([encodeEvent(event)])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let loadState = async id => {
|
|
88
|
+
switch Bus.getQueryDb("DnOrdersView") {
|
|
89
|
+
| None => []
|
|
90
|
+
| Some(ops) =>
|
|
91
|
+
let states =
|
|
92
|
+
await ops.loadStream(id)
|
|
93
|
+
->Stream.runCollect
|
|
94
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
95
|
+
->Effect.runPromise
|
|
96
|
+
states->Array.map(json => json->Reventless.Util_Sury.fromJson(OrdersViewSpec.stateSchema))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
7
|
+
import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
|
|
8
|
+
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
9
|
+
import * as DisplayName$Reventless from "@reventlessdev/reventless-spec/src/components/DisplayName.res.mjs";
|
|
10
|
+
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
11
|
+
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
12
|
+
import * as LocalBus$ReventlessLocal from "../../../src/adapter/LocalBus.res.mjs";
|
|
13
|
+
import * as TestRunner$ReventlessLocal from "../../../src/test/TestRunner.res.mjs";
|
|
14
|
+
import * as DcbEventLog_Builder$ReventlessLocal from "../../../src/components/DcbEventLog_Builder.res.mjs";
|
|
15
|
+
import * as StateViewSlice_Builder$ReventlessLocal from "../../../src/components/StateViewSlice_Builder.res.mjs";
|
|
16
|
+
|
|
17
|
+
let eventSchema = Sury.union([
|
|
18
|
+
Sury.$schema(s => ({
|
|
19
|
+
TAG: "OrderPlaced",
|
|
20
|
+
id: s.m(DcbTag$Reventless.string),
|
|
21
|
+
placedAt: s.m(Sury.string)
|
|
22
|
+
})),
|
|
23
|
+
Sury.$schema(s => ({
|
|
24
|
+
TAG: "OrderShipped",
|
|
25
|
+
id: s.m(DcbTag$Reventless.string),
|
|
26
|
+
shippedAt: s.m(Sury.string)
|
|
27
|
+
}))
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
let OrderEventLog = {
|
|
31
|
+
eventSchema: eventSchema
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let name = "DnOrdersView";
|
|
35
|
+
|
|
36
|
+
let moduleUrl = import.meta.url;
|
|
37
|
+
|
|
38
|
+
let consumedEventSchema = Sury.union([
|
|
39
|
+
Sury.$schema(s => ({
|
|
40
|
+
TAG: "OrderPlaced",
|
|
41
|
+
id: s.m(Sury.string),
|
|
42
|
+
placedAt: s.m(Sury.string)
|
|
43
|
+
})),
|
|
44
|
+
Sury.$schema(s => ({
|
|
45
|
+
TAG: "OrderShipped",
|
|
46
|
+
id: s.m(Sury.string),
|
|
47
|
+
shippedAt: s.m(Sury.string)
|
|
48
|
+
}))
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
let stateSchema = Sury.$schema(s => ({
|
|
52
|
+
id: s.m(Sury.string),
|
|
53
|
+
placedAt: s.m(Sury.string),
|
|
54
|
+
shippedAt: s.m(Sury.string),
|
|
55
|
+
displayName: s.m(Sury.$option(Sury.string))
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
let stateSchema$1 = Sury.$Metadata_set(stateSchema, DisplayName$Reventless.displayNameId, {
|
|
59
|
+
fields: ["placedAt"],
|
|
60
|
+
separator: " "
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let config = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
64
|
+
|
|
65
|
+
let OrdersViewSpec = {
|
|
66
|
+
name: name,
|
|
67
|
+
moduleUrl: moduleUrl,
|
|
68
|
+
consumedEventSchema: consumedEventSchema,
|
|
69
|
+
stateSchema: stateSchema$1,
|
|
70
|
+
config: config,
|
|
71
|
+
subIdConfig: undefined,
|
|
72
|
+
authorization: "AllowAuthenticated",
|
|
73
|
+
visibility: "Public"
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
let moduleUrl$1 = import.meta.url;
|
|
77
|
+
|
|
78
|
+
function project(param) {
|
|
79
|
+
let event = param.event;
|
|
80
|
+
if (event.TAG === "OrderPlaced") {
|
|
81
|
+
let id = event.id;
|
|
82
|
+
return [{
|
|
83
|
+
TAG: "Set",
|
|
84
|
+
_0: id,
|
|
85
|
+
_1: {
|
|
86
|
+
id: id,
|
|
87
|
+
placedAt: event.placedAt,
|
|
88
|
+
shippedAt: "",
|
|
89
|
+
displayName: undefined
|
|
90
|
+
}
|
|
91
|
+
}];
|
|
92
|
+
}
|
|
93
|
+
let shippedAt = event.shippedAt;
|
|
94
|
+
return [{
|
|
95
|
+
TAG: "Update",
|
|
96
|
+
_0: event.id,
|
|
97
|
+
_1: s => ({
|
|
98
|
+
id: s.id,
|
|
99
|
+
placedAt: s.placedAt,
|
|
100
|
+
shippedAt: shippedAt,
|
|
101
|
+
displayName: s.displayName
|
|
102
|
+
})
|
|
103
|
+
}];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let OrdersViewProjection = {
|
|
107
|
+
Spec: undefined,
|
|
108
|
+
moduleUrl: moduleUrl$1,
|
|
109
|
+
project: project
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
let Bus = LocalBus$ReventlessLocal.Make({});
|
|
113
|
+
|
|
114
|
+
TestRunner$ReventlessLocal.setup();
|
|
115
|
+
|
|
116
|
+
let OrderEventLogMaker = DcbEventLog_Builder$ReventlessLocal.Make(Bus);
|
|
117
|
+
|
|
118
|
+
let eventLog = OrderEventLogMaker.make("DnOrderEventLog", undefined, {
|
|
119
|
+
TAG: "Simple",
|
|
120
|
+
_0: {
|
|
121
|
+
key: "id"
|
|
122
|
+
}
|
|
123
|
+
}, undefined);
|
|
124
|
+
|
|
125
|
+
let SVMaker = StateViewSlice_Builder$ReventlessLocal.Make(Bus);
|
|
126
|
+
|
|
127
|
+
let OrdersViewMaker = SVMaker.Make({
|
|
128
|
+
name: name,
|
|
129
|
+
moduleUrl: moduleUrl,
|
|
130
|
+
stateSchema: stateSchema$1,
|
|
131
|
+
consumedEventSchema: consumedEventSchema,
|
|
132
|
+
config: config,
|
|
133
|
+
subIdConfig: undefined,
|
|
134
|
+
authorization: "AllowAuthenticated",
|
|
135
|
+
visibility: "Public"
|
|
136
|
+
})({
|
|
137
|
+
project: project,
|
|
138
|
+
moduleUrl: moduleUrl$1
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
let sv = OrdersViewMaker.make(eventLog, undefined, undefined);
|
|
142
|
+
|
|
143
|
+
let dcbEventTopicResource = Component$ReventlessCore.outputs(eventLog).eventTopic.resources[0];
|
|
144
|
+
|
|
145
|
+
function encodeEvent(event) {
|
|
146
|
+
let json = Util_Sury$Reventless.toJson(event, eventSchema);
|
|
147
|
+
let match = Message$ReventlessCore.splitMessage(json);
|
|
148
|
+
let tags = DcbTag$Reventless.extractTags(eventSchema, event);
|
|
149
|
+
let meta = Message$ReventlessCore.generateMeta("test", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
150
|
+
return {
|
|
151
|
+
eventType: match[0],
|
|
152
|
+
data: match[1],
|
|
153
|
+
tags: tags,
|
|
154
|
+
meta: meta
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function appendEvent(event) {
|
|
159
|
+
let ops = await TestRunner$ReventlessLocal.resolve(OrderEventLogMaker.operations(eventLog));
|
|
160
|
+
await ops.append([encodeEvent(event)], undefined);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function loadState(id) {
|
|
164
|
+
let ops = Bus.getQueryDb("DnOrdersView");
|
|
165
|
+
if (ops === undefined) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
let states = await Effect.runPromise(Effect.catchAll(Stream.runCollect(ops.loadStream(id)), param => Effect.succeed([])));
|
|
169
|
+
return states.map(json => Util_Sury$Reventless.fromJson(json, stateSchema$1));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
OrderEventLog,
|
|
174
|
+
OrdersViewSpec,
|
|
175
|
+
OrdersViewProjection,
|
|
176
|
+
Bus,
|
|
177
|
+
OrderEventLogMaker,
|
|
178
|
+
eventLog,
|
|
179
|
+
SVMaker,
|
|
180
|
+
OrdersViewMaker,
|
|
181
|
+
sv,
|
|
182
|
+
dcbEventTopicResource,
|
|
183
|
+
encodeEvent,
|
|
184
|
+
appendEvent,
|
|
185
|
+
loadState,
|
|
186
|
+
}
|
|
187
|
+
/* eventSchema Not a pure module */
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The @displayName overlay on the StateViewSlice projection path.
|
|
2
|
+
// Without it the column stays null and every surface names the row by its id.
|
|
3
|
+
|
|
4
|
+
open JestGlobals
|
|
5
|
+
open StateViewSliceDisplayNameFixtures
|
|
6
|
+
|
|
7
|
+
describe("StateViewSlice @displayName", () => {
|
|
8
|
+
let _ = beforeAllAsync(async () => {
|
|
9
|
+
let _ = await sv->OrdersViewMaker.operations->TestRunner.resolve
|
|
10
|
+
let _ = await dcbEventTopicResource.name->TestRunner.resolve
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
testPromise("a projected state carries the composed label", async () => {
|
|
14
|
+
let _ = await appendEvent(
|
|
15
|
+
OrderEventLog.OrderPlaced({id: "order-1", placedAt: "2026-09-01T10:00:00.000Z"}),
|
|
16
|
+
)
|
|
17
|
+
let states = await loadState("order-1")
|
|
18
|
+
expect(states->Array.length)->toBe(1)
|
|
19
|
+
let s = states->Array.getUnsafe(0)
|
|
20
|
+
expect(s.displayName)->toEqual(Some("2026-09-01T10:00:00.000Z"))
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
testPromise("an update recomposes it from the state it wrote", async () => {
|
|
24
|
+
let _ = await appendEvent(
|
|
25
|
+
OrderEventLog.OrderShipped({id: "order-1", shippedAt: "2026-09-02T10:00:00.000Z"}),
|
|
26
|
+
)
|
|
27
|
+
let states = await loadState("order-1")
|
|
28
|
+
let s = states->Array.getUnsafe(0)
|
|
29
|
+
expect(s.shippedAt)->toBe("2026-09-02T10:00:00.000Z")
|
|
30
|
+
expect(s.displayName)->toEqual(Some("2026-09-01T10:00:00.000Z"))
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as TestRunner$ReventlessLocal from "../../../src/test/TestRunner.res.mjs";
|
|
4
|
+
import * as StateViewSliceDisplayNameFixtures$ReventlessLocal from "./StateViewSliceDisplayNameFixtures.res.mjs";
|
|
5
|
+
|
|
6
|
+
globalThis.describe("StateViewSlice @displayName", () => {
|
|
7
|
+
globalThis.beforeAll(async () => {
|
|
8
|
+
await TestRunner$ReventlessLocal.resolve(StateViewSliceDisplayNameFixtures$ReventlessLocal.OrdersViewMaker.operations(StateViewSliceDisplayNameFixtures$ReventlessLocal.sv));
|
|
9
|
+
await TestRunner$ReventlessLocal.resolve(StateViewSliceDisplayNameFixtures$ReventlessLocal.dcbEventTopicResource.name);
|
|
10
|
+
});
|
|
11
|
+
globalThis.test("a projected state carries the composed label", async () => {
|
|
12
|
+
await StateViewSliceDisplayNameFixtures$ReventlessLocal.appendEvent({
|
|
13
|
+
TAG: "OrderPlaced",
|
|
14
|
+
id: "order-1",
|
|
15
|
+
placedAt: "2026-09-01T10:00:00.000Z"
|
|
16
|
+
});
|
|
17
|
+
let states = await StateViewSliceDisplayNameFixtures$ReventlessLocal.loadState("order-1");
|
|
18
|
+
globalThis.expect(states.length).toBe(1);
|
|
19
|
+
let s = states[0];
|
|
20
|
+
globalThis.expect(s.displayName).toEqual("2026-09-01T10:00:00.000Z");
|
|
21
|
+
});
|
|
22
|
+
globalThis.test("an update recomposes it from the state it wrote", async () => {
|
|
23
|
+
await StateViewSliceDisplayNameFixtures$ReventlessLocal.appendEvent({
|
|
24
|
+
TAG: "OrderShipped",
|
|
25
|
+
id: "order-1",
|
|
26
|
+
shippedAt: "2026-09-02T10:00:00.000Z"
|
|
27
|
+
});
|
|
28
|
+
let states = await StateViewSliceDisplayNameFixtures$ReventlessLocal.loadState("order-1");
|
|
29
|
+
let s = states[0];
|
|
30
|
+
globalThis.expect(s.shippedAt).toBe("2026-09-02T10:00:00.000Z");
|
|
31
|
+
globalThis.expect(s.displayName).toEqual("2026-09-01T10:00:00.000Z");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/* Not a pure module */
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Behavior_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/Behavior_GWT.res.mjs";
|
|
4
4
|
import * as Plugin_Fixtures$ReventlessLocal from "./Plugin_Fixtures.res.mjs";
|
|
5
|
-
import * as UiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
6
|
-
import * as UiFragmentRegistry_Behavior$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
5
|
+
import * as UiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res.mjs";
|
|
6
|
+
import * as UiFragmentRegistry_Behavior$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry_Behavior.res.mjs";
|
|
7
7
|
|
|
8
8
|
let Test = Behavior_GWT$ReventlessGwt.Make({
|
|
9
9
|
name: UiFragmentRegistry$ReventlessCore.name,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
3
|
+
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateView/UiFragments.res.mjs";
|
|
4
4
|
import * as Projection_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/Projection_GWT.res.mjs";
|
|
5
5
|
import * as Plugin_Fixtures$ReventlessLocal from "./Plugin_Fixtures.res.mjs";
|
|
6
|
-
import * as UiFragments_Projection$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/
|
|
6
|
+
import * as UiFragments_Projection$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateView/UiFragments_Projection.res.mjs";
|
|
7
7
|
|
|
8
8
|
let Test = Projection_GWT$ReventlessGwt.Make({
|
|
9
9
|
name: UiFragments$ReventlessCore.name,
|