@reventlessdev/reventless-core 3.0.0-alpha.179 → 3.0.0-alpha.181
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 +21 -0
- package/package.json +8 -8
- package/src/Message.res +2 -1
- package/src/Message.res.mjs +5 -1
- package/src/adapter/Monitoring/Monitoring.res +14 -2
- package/src/adapter/Monitoring/Monitoring.res.mjs +4 -2
- package/src/admin/Platform_Admin_Structure.res +2 -0
- package/src/admin/Platform_Admin_Structure.res.mjs +2 -0
- package/src/admin/Platform_ComponentDefinitionsApi.res +2 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
- package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res +1 -1
- package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res.mjs +2 -1
- package/src/components/Api/ApiTargetStateHelpers.res +20 -0
- package/src/components/Api/ApiTargetStateHelpers.res.mjs +16 -0
- package/src/components/Api/GraphQL_FragmentGenerator.res +1 -0
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +1 -0
- package/src/components/Api/SchemaType.res +3 -0
- package/src/components/Api/SchemaType.res.mjs +10 -2
- package/src/components/Api/SuryToJsonSchema.res +16 -0
- package/src/components/Api/SuryToJsonSchema.res.mjs +28 -0
- package/src/components/Dcb/Dcb_Builder.res +4 -5
- package/src/components/Dcb/Dcb_Builder.res.mjs +3 -5
- package/src/components/DcbEventLog/DcbEventLog_Operations.res +6 -1
- package/src/components/DcbEventLog/DcbEventLog_Operations.res.mjs +2 -1
- package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +25 -0
- package/src/components/InboundTranslationSlice/InboundTranslationSlice.res.mjs +31 -0
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +8 -8
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +26 -6
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +44 -37
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res.mjs +17 -25
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +27 -2
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -0
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +20 -6
- package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +23 -5
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +1 -1
- package/src/components/StateViewSlice/StateViewSlice_Callback.res.mjs +5 -1
- package/src/plugin/component/Plugin_Helpers.res +4 -1
- package/src/plugin/component/Plugin_Structure.res +15 -1
- package/src/plugin/component/Plugin_Structure.res.mjs +4 -1
- package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +2 -0
- package/tests/IdentityTest.res.mjs +174 -0
- package/tests/ResourceAttributionTest.res.mjs +75 -0
- package/tests/adapter/MonitoringTest.res +32 -2
- package/tests/adapter/MonitoringTest.res.mjs +116 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +10 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +365 -0
- package/tests/admin/Platform_UIFragmentsApiTest.res.mjs +101 -0
- package/tests/aggregate/AggregateCacheTest.res.mjs +433 -0
- package/tests/aggregate/AggregateCallbackTest.res.mjs +143 -0
- package/tests/aggregate/AggregateCausationTest.res.mjs +47 -0
- package/tests/aggregate/AggregateConflictTest.res.mjs +386 -0
- package/tests/aggregate/AggregateFixtures.res.mjs +308 -0
- package/tests/aggregate/AggregateRejectionTest.res.mjs +166 -0
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +645 -0
- package/tests/api/ApiNoApiTest.res.mjs +90 -0
- package/tests/api/GraphQL_StitcherTest.res.mjs +112 -0
- package/tests/api/SimpleTest.res.mjs +10 -0
- package/tests/api/SuryToJsonSchemaTest.res +68 -0
- package/tests/api/SuryToJsonSchemaTest.res.mjs +843 -0
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res.mjs +160 -0
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +197 -0
- package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +152 -0
- package/tests/commandtopic/CommandTopicCallbackTest.res.mjs +106 -0
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res.mjs +109 -0
- package/tests/counter/CounterCallbackTest.res.mjs +140 -0
- package/tests/counter/CounterFixtures.res.mjs +67 -0
- package/tests/dcb/DcbDecodeTest.res.mjs +194 -0
- package/tests/dcb/DcbEventLogOperationsTest.res.mjs +268 -0
- package/tests/dcb/DcbFixtures.res.mjs +534 -0
- package/tests/dcb/DcbScopeInferenceTest.res.mjs +480 -0
- package/tests/dcb/DcbStateChangeSliceTest.res.mjs +540 -0
- package/tests/dcb/DcbTagTest.res.mjs +867 -0
- package/tests/dcb/DcbValidationTest.res.mjs +476 -0
- package/tests/eventlog/EventLogFixtures.res.mjs +196 -0
- package/tests/eventlog/EventLogOperationsTest.res.mjs +192 -0
- package/tests/eventlog/EventLogRetryTest.res.mjs +195 -0
- package/tests/eventmapper/EventMapperCallbackTest.res.mjs +141 -0
- package/tests/eventmapper/EventMapperFixtures.res.mjs +440 -0
- package/tests/eventmapper/EventMapperTest.res.mjs +83 -0
- package/tests/eventtopic/EventTopicFixtures.res.mjs +65 -0
- package/tests/eventtopic/EventTopicOperationsTest.res.mjs +71 -0
- package/tests/extensionpoint/ExtensionPointCallbackTest.res.mjs +94 -0
- package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +205 -0
- package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +304 -0
- package/tests/extensionpoint/PluginDisconnectScheduleTest.res.mjs +92 -0
- package/tests/ftp/FTPTest.res.mjs +88 -0
- package/tests/logger/LogFormatTest.res.mjs +329 -0
- package/tests/logger/LoggerLazyTest.res.mjs +40 -0
- package/tests/message/MessageTest.res.mjs +218 -0
- package/tests/message/MetaEnvelopeTest.res.mjs +188 -0
- package/tests/plugin/HeartbeatDisconnectGraceTest.res.mjs +91 -0
- package/tests/plugin/ManifestVisibilityTest.res.mjs +215 -0
- package/tests/plugin/PluginSpecExperiment.res.mjs +31 -0
- package/tests/plugin/PluginStructureTest.res +55 -0
- package/tests/plugin/PluginStructureTest.res.mjs +467 -0
- package/tests/plugin/PluginVersionTest.res.mjs +30 -0
- package/tests/plugin/SchemaWalkerTest.res.mjs +160 -0
- package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +62 -0
- package/tests/plugin/StateChangeSlice/PsShipOrder.res +4 -1
- package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +77 -0
- package/tests/plugin/StateViewSlice/PsAnnotatedView.res +5 -4
- package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +110 -0
- package/tests/plugin/StateViewSlice/PsAvailableProductsView.res +1 -1
- package/tests/plugin/StateViewSlice/PsAvailableProductsView.res.mjs +70 -0
- package/tests/plugin/StateViewSlice/PsCustomersView.res +1 -1
- package/tests/plugin/StateViewSlice/PsCustomersView.res.mjs +70 -0
- package/tests/plugin/StateViewSlice/PsOrdersView.res +1 -1
- package/tests/plugin/StateViewSlice/PsOrdersView.res.mjs +82 -0
- package/tests/poc/StructuralSubtypingPoc.res.mjs +61 -0
- package/tests/projection/ProjectionOptimizeTest.res.mjs +75 -0
- package/tests/querydb/QueryDbFixtures.res.mjs +135 -0
- package/tests/querydb/QueryDbOperationsTest.res.mjs +168 -0
- package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res.mjs +77 -0
- package/tests/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +139 -0
- package/tests/util/CommandPublisherTest.res.mjs +78 -0
- package/tests/util/CsvStreamTest.res.mjs +51 -0
- package/tests/util/LruTest.res.mjs +105 -0
- package/tests/util/MetricsTest.res.mjs +53 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Message$ReventlessCore from "../../src/Message.res.mjs";
|
|
7
|
+
|
|
8
|
+
globalThis.describe("Message.meta optional fields:", () => {
|
|
9
|
+
globalThis.test("generateMeta() with no ~ip/~user omits those keys from JSON", () => {
|
|
10
|
+
let meta = Message$ReventlessCore.generateMeta("svc", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
11
|
+
let json = S.reverseConvertToJsonOrThrow(meta, Message$ReventlessCore.metaSchema);
|
|
12
|
+
let obj = Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.object(json), undefined);
|
|
13
|
+
globalThis.expect(obj["ip"]).toEqual(undefined);
|
|
14
|
+
globalThis.expect(obj["user"]).toEqual(undefined);
|
|
15
|
+
globalThis.expect(obj["causationId"]).toEqual(undefined);
|
|
16
|
+
globalThis.expect(obj["traceparent"]).toEqual(undefined);
|
|
17
|
+
globalThis.expect(obj["schemaVersion"]).toEqual(undefined);
|
|
18
|
+
globalThis.expect(obj["headers"]).toEqual(undefined);
|
|
19
|
+
globalThis.expect(Stdlib_Option.isSome(obj["service"])).toBe(true);
|
|
20
|
+
globalThis.expect(Stdlib_Option.isSome(obj["time"])).toBe(true);
|
|
21
|
+
globalThis.expect(Stdlib_Option.isSome(obj["msgId"])).toBe(true);
|
|
22
|
+
globalThis.expect(Stdlib_Option.isSome(obj["correlationId"])).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
globalThis.test("generateMeta() correlationId defaults to msgId", () => {
|
|
25
|
+
let meta = Message$ReventlessCore.generateMeta("svc", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
26
|
+
globalThis.expect(meta.correlationId).toBe(meta.msgId);
|
|
27
|
+
});
|
|
28
|
+
globalThis.test("generateMeta(~user=alice) round-trips Some(alice)", () => {
|
|
29
|
+
let meta = Message$ReventlessCore.generateMeta("svc", undefined, "alice", undefined, undefined, undefined, undefined, undefined);
|
|
30
|
+
let json = S.reverseConvertToJsonOrThrow(meta, Message$ReventlessCore.metaSchema);
|
|
31
|
+
let decoded = S.parseJsonOrThrow(json, Message$ReventlessCore.metaSchema);
|
|
32
|
+
globalThis.expect(decoded.user).toEqual("alice");
|
|
33
|
+
});
|
|
34
|
+
globalThis.test("all new optional fields round-trip when set", () => {
|
|
35
|
+
let headers = Object.fromEntries([
|
|
36
|
+
[
|
|
37
|
+
"tenantId",
|
|
38
|
+
"t1"
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"feature.x",
|
|
42
|
+
"on"
|
|
43
|
+
]
|
|
44
|
+
]);
|
|
45
|
+
let meta = Message$ReventlessCore.generateMeta("svc", "10.0.0.1", "bob", "parent-msg-id", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", "root-msg-id", "1.2.0", headers);
|
|
46
|
+
let json = S.reverseConvertToJsonOrThrow(meta, Message$ReventlessCore.metaSchema);
|
|
47
|
+
let decoded = S.parseJsonOrThrow(json, Message$ReventlessCore.metaSchema);
|
|
48
|
+
globalThis.expect(decoded.ip).toEqual("10.0.0.1");
|
|
49
|
+
globalThis.expect(decoded.user).toEqual("bob");
|
|
50
|
+
globalThis.expect(decoded.causationId).toEqual("parent-msg-id");
|
|
51
|
+
globalThis.expect(decoded.traceparent).toEqual("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01");
|
|
52
|
+
globalThis.expect(decoded.correlationId).toBe("root-msg-id");
|
|
53
|
+
globalThis.expect(decoded.schemaVersion).toEqual("1.2.0");
|
|
54
|
+
globalThis.expect(Stdlib_Option.getOr(decoded.headers, {})["tenantId"]).toEqual("t1");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
globalThis.describe("Message.deriveMeta:", () => {
|
|
59
|
+
globalThis.test("emits a new msgId distinct from the parent", () => {
|
|
60
|
+
let parent = Message$ReventlessCore.generateMeta("svc", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
61
|
+
let child = Message$ReventlessCore.deriveMeta(parent, undefined);
|
|
62
|
+
globalThis.expect(child.msgId === parent.msgId).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
globalThis.test("sets causationId = parent.msgId", () => {
|
|
65
|
+
let parent = Message$ReventlessCore.generateMeta("svc", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
66
|
+
let child = Message$ReventlessCore.deriveMeta(parent, undefined);
|
|
67
|
+
globalThis.expect(child.causationId).toEqual(parent.msgId);
|
|
68
|
+
});
|
|
69
|
+
globalThis.test("inherits correlationId (chain-root id stays stable)", () => {
|
|
70
|
+
let root = Message$ReventlessCore.generateMeta("svc", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
71
|
+
let mid = Message$ReventlessCore.deriveMeta(root, undefined);
|
|
72
|
+
let leaf = Message$ReventlessCore.deriveMeta(mid, undefined);
|
|
73
|
+
globalThis.expect(mid.correlationId).toBe(root.correlationId);
|
|
74
|
+
globalThis.expect(leaf.correlationId).toBe(root.correlationId);
|
|
75
|
+
globalThis.expect(leaf.causationId).toEqual(mid.msgId);
|
|
76
|
+
});
|
|
77
|
+
globalThis.test("inherits ip/user/traceparent/schemaVersion/headers from parent", () => {
|
|
78
|
+
let parent = Message$ReventlessCore.generateMeta("svc", "10.0.0.1", "alice", undefined, "tp", undefined, "1", Object.fromEntries([[
|
|
79
|
+
"k",
|
|
80
|
+
"v"
|
|
81
|
+
]]));
|
|
82
|
+
let child = Message$ReventlessCore.deriveMeta(parent, undefined);
|
|
83
|
+
globalThis.expect(child.ip).toEqual("10.0.0.1");
|
|
84
|
+
globalThis.expect(child.user).toEqual("alice");
|
|
85
|
+
globalThis.expect(child.traceparent).toEqual("tp");
|
|
86
|
+
globalThis.expect(child.schemaVersion).toEqual("1");
|
|
87
|
+
globalThis.expect(Stdlib_Option.getOr(child.headers, {})["k"]).toEqual("v");
|
|
88
|
+
});
|
|
89
|
+
globalThis.test("~service overrides parent's service; defaults to parent's service", () => {
|
|
90
|
+
let parent = Message$ReventlessCore.generateMeta("parent-svc", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
91
|
+
let inherited = Message$ReventlessCore.deriveMeta(parent, undefined);
|
|
92
|
+
let overridden = Message$ReventlessCore.deriveMeta(parent, "child-svc");
|
|
93
|
+
globalThis.expect(inherited.service).toBe("parent-svc");
|
|
94
|
+
globalThis.expect(overridden.service).toBe("child-svc");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
globalThis.describe("StoredEvent encode/decode round-trip:", () => {
|
|
99
|
+
globalThis.test("aggregate-style record (no tags) round-trips through the flat-dict bridge", () => {
|
|
100
|
+
let meta = Message$ReventlessCore.generateMeta("catalog", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
101
|
+
let stored_data = Object.fromEntries([
|
|
102
|
+
[
|
|
103
|
+
"itemId",
|
|
104
|
+
"agg-1"
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
"name",
|
|
108
|
+
"Widget"
|
|
109
|
+
]
|
|
110
|
+
]);
|
|
111
|
+
let stored = {
|
|
112
|
+
id: "agg-1",
|
|
113
|
+
position: "000000001",
|
|
114
|
+
event: "ItemCreated",
|
|
115
|
+
data: stored_data,
|
|
116
|
+
meta: meta,
|
|
117
|
+
recordedAt: "2026-05-13T10:00:00.000Z"
|
|
118
|
+
};
|
|
119
|
+
let flat = Message$ReventlessCore.storedEventToFlatJson(stored, S.string);
|
|
120
|
+
let roundTrip = Message$ReventlessCore.flatJsonToStoredEvent(flat, S.string);
|
|
121
|
+
globalThis.expect(roundTrip.id).toBe("agg-1");
|
|
122
|
+
globalThis.expect(roundTrip.position).toBe("000000001");
|
|
123
|
+
globalThis.expect(roundTrip.event).toBe("ItemCreated");
|
|
124
|
+
globalThis.expect(roundTrip.recordedAt).toBe("2026-05-13T10:00:00.000Z");
|
|
125
|
+
globalThis.expect(roundTrip.meta.msgId).toBe(meta.msgId);
|
|
126
|
+
globalThis.expect(roundTrip.meta.correlationId).toBe(meta.correlationId);
|
|
127
|
+
globalThis.expect(roundTrip.tags).toEqual(undefined);
|
|
128
|
+
});
|
|
129
|
+
globalThis.test("DCB-style record (with tags) round-trips and preserves the tag list", () => {
|
|
130
|
+
let meta = Message$ReventlessCore.generateMeta("catalog", undefined, undefined, "cmd-1", undefined, undefined, undefined, undefined);
|
|
131
|
+
let stored_data = Object.fromEntries([[
|
|
132
|
+
"productId",
|
|
133
|
+
"p-1"
|
|
134
|
+
]]);
|
|
135
|
+
let stored_tags = [
|
|
136
|
+
{
|
|
137
|
+
key: "productId",
|
|
138
|
+
value: "p-1"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
key: "categoryId",
|
|
142
|
+
value: "c-1"
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
let stored = {
|
|
146
|
+
id: "productId:p-1",
|
|
147
|
+
position: "1736782800000-abc-uuid",
|
|
148
|
+
event: "ProductAdded",
|
|
149
|
+
data: stored_data,
|
|
150
|
+
meta: meta,
|
|
151
|
+
recordedAt: "2026-05-13T10:00:00.000Z",
|
|
152
|
+
tags: stored_tags
|
|
153
|
+
};
|
|
154
|
+
let flat = Message$ReventlessCore.storedEventToFlatJson(stored, S.string);
|
|
155
|
+
let roundTrip = Message$ReventlessCore.flatJsonToStoredEvent(flat, S.string);
|
|
156
|
+
globalThis.expect(roundTrip.id).toBe("productId:p-1");
|
|
157
|
+
globalThis.expect(roundTrip.position).toBe("1736782800000-abc-uuid");
|
|
158
|
+
globalThis.expect(roundTrip.event).toBe("ProductAdded");
|
|
159
|
+
globalThis.expect(roundTrip.meta.causationId).toEqual("cmd-1");
|
|
160
|
+
let tags = Stdlib_Option.getOrThrow(roundTrip.tags, undefined);
|
|
161
|
+
globalThis.expect(tags.length).toBe(2);
|
|
162
|
+
let first = tags[0];
|
|
163
|
+
globalThis.expect(first.key).toBe("productId");
|
|
164
|
+
globalThis.expect(first.value).toBe("p-1");
|
|
165
|
+
});
|
|
166
|
+
globalThis.test("meta is flattened to top-level keys in the on-disk JSON (DynamoDB GSI projectability)", () => {
|
|
167
|
+
let meta = Message$ReventlessCore.generateMeta("catalog", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
168
|
+
let stored_data = {};
|
|
169
|
+
let stored = {
|
|
170
|
+
id: "agg-1",
|
|
171
|
+
position: "000000001",
|
|
172
|
+
event: "ItemCreated",
|
|
173
|
+
data: stored_data,
|
|
174
|
+
meta: meta,
|
|
175
|
+
recordedAt: "2026-05-13T10:00:00.000Z"
|
|
176
|
+
};
|
|
177
|
+
let flat = Message$ReventlessCore.storedEventToFlatJson(stored, S.string);
|
|
178
|
+
let obj = Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.object(flat), undefined);
|
|
179
|
+
globalThis.expect(obj["meta"]).toEqual(undefined);
|
|
180
|
+
globalThis.expect(obj["service"]).toEqual("catalog");
|
|
181
|
+
globalThis.expect(Stdlib_Option.isSome(obj["msgId"])).toBe(true);
|
|
182
|
+
globalThis.expect(obj["position"]).toEqual("000000001");
|
|
183
|
+
globalThis.expect(obj["event"]).toEqual("ItemCreated");
|
|
184
|
+
globalThis.expect(obj["recordedAt"]).toEqual("2026-05-13T10:00:00.000Z");
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
|
+
import * as Message$ReventlessCore from "../../src/Message.res.mjs";
|
|
5
|
+
import * as PluginExtensionPoint_Plugin$ReventlessCore from "../../src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs";
|
|
6
|
+
|
|
7
|
+
let TestMapping = PluginExtensionPoint_Plugin$ReventlessCore.Make({
|
|
8
|
+
runtimeOps: undefined,
|
|
9
|
+
environment: "test",
|
|
10
|
+
updateApiSchema: undefined,
|
|
11
|
+
manageSubscriptions: undefined
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function graceFromActions(actions) {
|
|
15
|
+
return Stdlib_Array.findMap(actions, action => {
|
|
16
|
+
if (action.TAG === "PublishCommand") {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let match = action._1;
|
|
20
|
+
if (match.TAG === "CreateDisconnectSchedule") {
|
|
21
|
+
return match._1;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let meta = Message$ReventlessCore.generateMeta("test", undefined, "test", undefined, undefined, undefined, undefined, undefined);
|
|
27
|
+
|
|
28
|
+
globalThis.describe("disconnectGrace", () => {
|
|
29
|
+
globalThis.test("scales with the interval (5→7, 10→15, 60→90)", () => {
|
|
30
|
+
globalThis.expect(PluginExtensionPoint_Plugin$ReventlessCore.disconnectGrace(5)).toBe(7);
|
|
31
|
+
globalThis.expect(PluginExtensionPoint_Plugin$ReventlessCore.disconnectGrace(10)).toBe(15);
|
|
32
|
+
globalThis.expect(PluginExtensionPoint_Plugin$ReventlessCore.disconnectGrace(60)).toBe(90);
|
|
33
|
+
});
|
|
34
|
+
globalThis.test("always leaves at least a full interval of headroom past one beat", () => {
|
|
35
|
+
[
|
|
36
|
+
1,
|
|
37
|
+
2,
|
|
38
|
+
5,
|
|
39
|
+
10,
|
|
40
|
+
30,
|
|
41
|
+
60,
|
|
42
|
+
120
|
|
43
|
+
].forEach(interval => {
|
|
44
|
+
let grace = PluginExtensionPoint_Plugin$ReventlessCore.disconnectGrace(interval);
|
|
45
|
+
globalThis.expect(grace > interval).toBe(true);
|
|
46
|
+
globalThis.expect(grace >= (interval + 2 | 0)).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
globalThis.test("a 60-min cadence gets far more than the old fixed +2 grace", () => {
|
|
50
|
+
globalThis.expect(PluginExtensionPoint_Plugin$ReventlessCore.disconnectGrace(60) > 62).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
globalThis.describe("PluginExtensionPoint mapping arms the disconnect schedule from the beat interval", () => {
|
|
55
|
+
globalThis.test("Heartbeat(interval) → CreateDisconnectSchedule(_, disconnectGrace(interval))", () => {
|
|
56
|
+
[
|
|
57
|
+
5,
|
|
58
|
+
10,
|
|
59
|
+
60
|
|
60
|
+
].forEach(interval => {
|
|
61
|
+
let actions = TestMapping.PluginMapping.mapIncomingCommand("Test@1", {
|
|
62
|
+
TAG: "Heartbeat",
|
|
63
|
+
_0: interval
|
|
64
|
+
}, meta);
|
|
65
|
+
globalThis.expect(graceFromActions(actions)).toEqual(PluginExtensionPoint_Plugin$ReventlessCore.disconnectGrace(interval));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
globalThis.test("RedetectPlugin(interval) arms the same interval-derived grace as a heartbeat", () => {
|
|
69
|
+
[
|
|
70
|
+
5,
|
|
71
|
+
10,
|
|
72
|
+
60
|
|
73
|
+
].forEach(interval => {
|
|
74
|
+
let actions = TestMapping.PluginMapping.mapIncomingCommand("Test@1", {
|
|
75
|
+
TAG: "RedetectPlugin",
|
|
76
|
+
_0: interval
|
|
77
|
+
}, meta);
|
|
78
|
+
globalThis.expect(graceFromActions(actions)).toEqual(PluginExtensionPoint_Plugin$ReventlessCore.disconnectGrace(interval));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
let PluginExtensionPointSpec;
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
PluginExtensionPointSpec,
|
|
87
|
+
TestMapping,
|
|
88
|
+
graceFromActions,
|
|
89
|
+
meta,
|
|
90
|
+
}
|
|
91
|
+
/* TestMapping Not a pure module */
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
7
|
+
import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
|
|
8
|
+
import * as Plugin_Structure$ReventlessCore from "../../src/plugin/component/Plugin_Structure.res.mjs";
|
|
9
|
+
|
|
10
|
+
let name = "PublicView";
|
|
11
|
+
|
|
12
|
+
let moduleUrl = "";
|
|
13
|
+
|
|
14
|
+
let stateSchema = S.schema(s => ({
|
|
15
|
+
id: s.m(S.string),
|
|
16
|
+
name: s.m(S.string)
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
let config = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
20
|
+
|
|
21
|
+
let PublicSpec = {
|
|
22
|
+
Id: undefined,
|
|
23
|
+
name: name,
|
|
24
|
+
moduleUrl: moduleUrl,
|
|
25
|
+
stateSchema: stateSchema,
|
|
26
|
+
config: config,
|
|
27
|
+
subIdConfig: undefined,
|
|
28
|
+
authorization: "AllowAuthenticated",
|
|
29
|
+
visibility: "Public"
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let name$1 = "InternalView";
|
|
33
|
+
|
|
34
|
+
let moduleUrl$1 = "";
|
|
35
|
+
|
|
36
|
+
let stateSchema$1 = S.schema(s => ({
|
|
37
|
+
id: s.m(S.string),
|
|
38
|
+
name: s.m(S.string)
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
let config$1 = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
42
|
+
|
|
43
|
+
let InternalSpec = {
|
|
44
|
+
Id: undefined,
|
|
45
|
+
name: name$1,
|
|
46
|
+
moduleUrl: moduleUrl$1,
|
|
47
|
+
stateSchema: stateSchema$1,
|
|
48
|
+
config: config$1,
|
|
49
|
+
subIdConfig: undefined,
|
|
50
|
+
visibility: "Internal",
|
|
51
|
+
authorization: "AllowAuthenticated"
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
let sourceNames = [];
|
|
55
|
+
|
|
56
|
+
let consumedEventNames = ["OrderPlaced"];
|
|
57
|
+
|
|
58
|
+
function make(param, param$1, param$2, param$3, param$4) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function outputs(param) {
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function operations(param) {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function finish() {
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let PublicReadModel_Spec = {
|
|
75
|
+
Id: {
|
|
76
|
+
schema: Id$Reventless.StringPure.schema,
|
|
77
|
+
make: prim => prim,
|
|
78
|
+
makeFromString: prim => prim,
|
|
79
|
+
toString: prim => prim,
|
|
80
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
81
|
+
},
|
|
82
|
+
name: name,
|
|
83
|
+
moduleUrl: moduleUrl,
|
|
84
|
+
stateSchema: stateSchema,
|
|
85
|
+
config: config,
|
|
86
|
+
subIdConfig: undefined,
|
|
87
|
+
authorization: "AllowAuthenticated",
|
|
88
|
+
visibility: "Public"
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
let PublicReadModel = {
|
|
92
|
+
Spec: PublicReadModel_Spec,
|
|
93
|
+
sourceNames: sourceNames,
|
|
94
|
+
consumedEventNames: consumedEventNames,
|
|
95
|
+
make: make,
|
|
96
|
+
outputs: outputs,
|
|
97
|
+
operations: operations,
|
|
98
|
+
finish: finish
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
let sourceNames$1 = [];
|
|
102
|
+
|
|
103
|
+
let consumedEventNames$1 = [];
|
|
104
|
+
|
|
105
|
+
function make$1(param, param$1, param$2, param$3, param$4) {
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function outputs$1(param) {
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function operations$1(param) {
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function finish$1() {
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let InternalReadModel_Spec = {
|
|
122
|
+
Id: {
|
|
123
|
+
schema: Id$Reventless.StringPure.schema,
|
|
124
|
+
make: prim => prim,
|
|
125
|
+
makeFromString: prim => prim,
|
|
126
|
+
toString: prim => prim,
|
|
127
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
128
|
+
},
|
|
129
|
+
name: name$1,
|
|
130
|
+
moduleUrl: moduleUrl$1,
|
|
131
|
+
stateSchema: stateSchema$1,
|
|
132
|
+
config: config$1,
|
|
133
|
+
subIdConfig: undefined,
|
|
134
|
+
authorization: "AllowAuthenticated",
|
|
135
|
+
visibility: "Internal"
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
let InternalReadModel = {
|
|
139
|
+
Spec: InternalReadModel_Spec,
|
|
140
|
+
sourceNames: sourceNames$1,
|
|
141
|
+
consumedEventNames: consumedEventNames$1,
|
|
142
|
+
make: make$1,
|
|
143
|
+
outputs: outputs$1,
|
|
144
|
+
operations: operations$1,
|
|
145
|
+
finish: finish$1
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
function isVisible(R) {
|
|
149
|
+
let match = R.Spec.visibility;
|
|
150
|
+
return match === "Public";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let readModels = [
|
|
154
|
+
PublicReadModel,
|
|
155
|
+
InternalReadModel
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
globalThis.describe("ReadModel visibility filter", () => {
|
|
159
|
+
globalThis.test("PPX defaults visibility to Public when no attribute is present", () => {
|
|
160
|
+
globalThis.expect("Public").toEqual("Public");
|
|
161
|
+
});
|
|
162
|
+
globalThis.test("Internal spec retains Internal value", () => {
|
|
163
|
+
globalThis.expect("Internal").toEqual("Internal");
|
|
164
|
+
});
|
|
165
|
+
globalThis.test("filter predicate excludes Internal", () => {
|
|
166
|
+
let visible = readModels.filter(isVisible);
|
|
167
|
+
globalThis.expect(visible.length).toBe(1);
|
|
168
|
+
});
|
|
169
|
+
globalThis.test("filter predicate retains Public name", () => {
|
|
170
|
+
let visible = readModels.filter(isVisible);
|
|
171
|
+
let first = visible[0];
|
|
172
|
+
globalThis.expect(first.Spec.name).toEqual("PublicView");
|
|
173
|
+
});
|
|
174
|
+
globalThis.test("unfiltered list still contains both entries (no over-filtering)", () => {
|
|
175
|
+
globalThis.expect(readModels.length).toBe(2);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
globalThis.describe("Plugin_Structure.make — visibility tagging", () => {
|
|
180
|
+
let structure = Plugin_Structure$ReventlessCore.make("VisibilityPlugin", undefined, readModels, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
181
|
+
globalThis.test("readModels carries both Public and Internal entries", () => {
|
|
182
|
+
globalThis.expect(structure.readModels.length).toBe(2);
|
|
183
|
+
});
|
|
184
|
+
globalThis.test("the Public entry is tagged visibility = None", () => {
|
|
185
|
+
let $$public = structure.readModels.find(d => d.name === "PublicView");
|
|
186
|
+
globalThis.expect(Stdlib_Option.map($$public, d => d.visibility)).toEqual(Primitive_option.some(undefined));
|
|
187
|
+
});
|
|
188
|
+
globalThis.test("the Internal entry is carried and tagged visibility = Some(\"Internal\")", () => {
|
|
189
|
+
let internal = structure.readModels.find(d => d.name === "InternalView");
|
|
190
|
+
globalThis.expect(Stdlib_Option.map(internal, d => d.visibility)).toEqual("Internal");
|
|
191
|
+
});
|
|
192
|
+
globalThis.test("a read model's mapping-consumed events surface as qualified consumedEventTypes", () => {
|
|
193
|
+
let $$public = structure.readModels.find(d => d.name === "PublicView");
|
|
194
|
+
globalThis.expect(Stdlib_Option.map($$public, d => d.consumedEventTypes)).toEqual(["VisibilityPlugin.OrderPlaced"]);
|
|
195
|
+
});
|
|
196
|
+
globalThis.test("aggregates / stateViewSlices stay empty when none are passed (sanity)", () => {
|
|
197
|
+
globalThis.expect([
|
|
198
|
+
structure.aggregates.length,
|
|
199
|
+
structure.stateViewSlices.length
|
|
200
|
+
]).toEqual([
|
|
201
|
+
0,
|
|
202
|
+
0
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export {
|
|
208
|
+
PublicSpec,
|
|
209
|
+
InternalSpec,
|
|
210
|
+
PublicReadModel,
|
|
211
|
+
InternalReadModel,
|
|
212
|
+
isVisible,
|
|
213
|
+
readModels,
|
|
214
|
+
}
|
|
215
|
+
/* stateSchema Not a pure module */
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
|
|
5
|
+
S.enableJson();
|
|
6
|
+
|
|
7
|
+
let recordSchema = S.schema(s => ({
|
|
8
|
+
a: s.m(S.meta(S.string, {
|
|
9
|
+
description: "aaa"
|
|
10
|
+
})),
|
|
11
|
+
b: s.m(S.string)
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
let variantSchema = S.union([
|
|
15
|
+
S.schema(s => ({
|
|
16
|
+
TAG: "Q",
|
|
17
|
+
_0: s.m(S.int)
|
|
18
|
+
})),
|
|
19
|
+
S.literal("X"),
|
|
20
|
+
S.literal("Y")
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
console.log("Record:", JSON.stringify(S.toJSONSchema(recordSchema), undefined, 2));
|
|
24
|
+
|
|
25
|
+
console.log("Variant:", JSON.stringify(S.toJSONSchema(variantSchema), undefined, 2));
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
recordSchema,
|
|
29
|
+
variantSchema,
|
|
30
|
+
}
|
|
31
|
+
/* Not a pure module */
|
|
@@ -159,6 +159,41 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
159
159
|
expect(shipOrder.commands->Array.map(c => c.name))->toEqual(["ShipOrder", "CancelShipment"])
|
|
160
160
|
})
|
|
161
161
|
|
|
162
|
+
testSync("ShipOrder: the @noApi CancelShipment is tagged apiExposed=false; ShipOrder stays exposed", () => {
|
|
163
|
+
let shipOrder = structure.stateChangeSlices->Array.getUnsafe(1)
|
|
164
|
+
let byName = name => shipOrder.commands->Array.find(c => c.name == name)
|
|
165
|
+
expect((
|
|
166
|
+
byName("ShipOrder")->Option.flatMap(c => c.apiExposed),
|
|
167
|
+
byName("CancelShipment")->Option.flatMap(c => c.apiExposed),
|
|
168
|
+
))->toEqual((Some(true), Some(false)))
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
testSync("ShipOrder: @targetState(\"Shipped\") flows through the PPX to commandDef.targetState", () => {
|
|
172
|
+
// End-to-end: the reventless-ppx @targetState annotation → markTargetState
|
|
173
|
+
// metadata → ApiTargetStateHelpers.getTargetState → commandDef. The
|
|
174
|
+
// un-annotated CancelShipment carries None (resolver falls back to
|
|
175
|
+
// name-stem).
|
|
176
|
+
let shipOrder = structure.stateChangeSlices->Array.getUnsafe(1)
|
|
177
|
+
let byName = name => shipOrder.commands->Array.find(c => c.name == name)
|
|
178
|
+
expect((
|
|
179
|
+
byName("ShipOrder")->Option.flatMap(c => c.targetState),
|
|
180
|
+
byName("CancelShipment")->Option.flatMap(c => c.targetState),
|
|
181
|
+
))->toEqual((Some("Shipped"), None))
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
testSync("ShipOrder: the @noApi variant carries no callable mutation field (no sibling leak)", () => {
|
|
185
|
+
// Regression: for a single-exposed-command slice, `mutationFieldFor`
|
|
186
|
+
// resolves every variant — including the @noApi one — to the slice's one
|
|
187
|
+
// mutation field. The non-exposed variant must not carry that sibling
|
|
188
|
+
// field; it emits an empty sentinel instead, while the exposed command
|
|
189
|
+
// keeps a real, non-empty field.
|
|
190
|
+
let shipOrder = structure.stateChangeSlices->Array.getUnsafe(1)
|
|
191
|
+
let byName = name => shipOrder.commands->Array.find(c => c.name == name)
|
|
192
|
+
let shipField = byName("ShipOrder")->Option.map(c => c.mutationField)->Option.getOr("")
|
|
193
|
+
let cancelField = byName("CancelShipment")->Option.map(c => c.mutationField)->Option.getOr("x")
|
|
194
|
+
expect((shipField->String.length > 0, cancelField))->toEqual((true, ""))
|
|
195
|
+
})
|
|
196
|
+
|
|
162
197
|
testSync("ShipOrder: payload-less event ShipmentVoided is surfaced in events", () => {
|
|
163
198
|
let shipOrder = structure.stateChangeSlices->Array.getUnsafe(1)
|
|
164
199
|
expect(shipOrder.events->Array.map(e => e.name))->toEqual(["OrderShipped", "ShipmentVoided"])
|
|
@@ -278,6 +313,26 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
|
|
|
278
313
|
)->toBe(Some("byOwner"))
|
|
279
314
|
})
|
|
280
315
|
|
|
316
|
+
testSync("@semantic(\"currency\") flows through the PPX to x-reventless-semantic", () => {
|
|
317
|
+
expect(
|
|
318
|
+
annotatedSchema
|
|
319
|
+
->getPropertyOf("total")
|
|
320
|
+
->Option.flatMap(s => getProperty(s, "x-reventless-semantic"))
|
|
321
|
+
->Option.flatMap(JSON.Decode.string),
|
|
322
|
+
)->toBe(Some("currency"))
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
testSync("@metric flows through the PPX to x-reventless-metric {aggregate,label}", () => {
|
|
326
|
+
let metricObj =
|
|
327
|
+
annotatedSchema
|
|
328
|
+
->getPropertyOf("total")
|
|
329
|
+
->Option.flatMap(s => getProperty(s, "x-reventless-metric"))
|
|
330
|
+
expect((
|
|
331
|
+
metricObj->Option.flatMap(m => getProperty(m, "aggregate"))->Option.flatMap(JSON.Decode.string),
|
|
332
|
+
metricObj->Option.flatMap(m => getProperty(m, "label"))->Option.flatMap(JSON.Decode.string),
|
|
333
|
+
))->toEqual((Some("sum"), Some("Revenue")))
|
|
334
|
+
})
|
|
335
|
+
|
|
281
336
|
testSync("unannotated field 'name' has no x-reventless-* keys", () => {
|
|
282
337
|
let nameField = annotatedSchema->getPropertyOf("name")
|
|
283
338
|
expect((
|