@reventlessdev/reventless-core 3.0.0-alpha.253 → 3.0.0-alpha.255

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +8 -8
  3. package/src/adapter/Messaging/Messaging_Log_Backend.res +98 -0
  4. package/src/adapter/Messaging/Messaging_Log_Backend.res.mjs +86 -0
  5. package/src/admin/Platform_ComponentDefinitionsApi.res +5 -1
  6. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
  7. package/src/components/Api/GraphQL_FragmentGenerator.res +55 -14
  8. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +43 -12
  9. package/src/components/Api/SchemaType.res +1 -0
  10. package/src/components/Api/SchemaType.res.mjs +4 -0
  11. package/src/components/Api/SuryToJsonSchema.res +41 -0
  12. package/src/components/Api/SuryToJsonSchema.res.mjs +50 -17
  13. package/src/components/Dcb/Dcb_Builder.res +21 -4
  14. package/src/components/Dcb/Dcb_Builder.res.mjs +6 -3
  15. package/src/plugin/component/Plugin_Structure.res +13 -0
  16. package/src/plugin/component/Plugin_Structure.res.mjs +5 -1
  17. package/tests/admin/AdminApiSchemaDriftTest.res +1 -0
  18. package/tests/admin/AdminApiSchemaDriftTest.res.mjs +4 -1
  19. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +2 -0
  20. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +4 -2
  21. package/tests/admin/Platform_PluginStructuresApiTest.res +32 -0
  22. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +9 -0
  23. package/tests/api/GraphQL_FragmentGeneratorTest.res +47 -0
  24. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +32 -0
  25. package/tests/api/SuryToJsonSchemaTest.res +157 -0
  26. package/tests/api/SuryToJsonSchemaTest.res.mjs +81 -0
  27. package/tests/plugin/PluginStructureTest.res +22 -0
  28. package/tests/plugin/PluginStructureTest.res.mjs +7 -0
  29. package/tests/plugin/StateViewSlice/PsAnnotatedView.res +13 -3
  30. package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +7 -3
  31. package/tests/plugin/pluginDefinitionRequiredScalars.txt +6 -0
@@ -13,8 +13,11 @@ import * as Percent$Reventless from "@reventlessdev/reventless-spec/src/semantic
13
13
  import * as Currency$Reventless from "@reventlessdev/reventless-spec/src/semantic/Currency.res.mjs";
14
14
  import * as DateTime$Reventless from "@reventlessdev/reventless-spec/src/types/DateTime.res.mjs";
15
15
  import * as GeoPoint$Reventless from "@reventlessdev/reventless-spec/src/semantic/GeoPoint.res.mjs";
16
+ import * as Semantic$Reventless from "@reventlessdev/reventless-spec/src/semantic/Semantic.res.mjs";
16
17
  import * as DateRange$Reventless from "@reventlessdev/reventless-spec/src/semantic/DateRange.res.mjs";
18
+ import * as MemberRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/MemberRef.res.mjs";
17
19
  import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
20
+ import * as Sensitive$Reventless from "@reventlessdev/reventless-spec/src/components/Sensitive.res.mjs";
18
21
  import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
19
22
  import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
20
23
  import * as SuryToJsonSchema$ReventlessCore from "../../src/components/Api/SuryToJsonSchema.res.mjs";
@@ -130,6 +133,45 @@ globalThis.describe("SuryToJsonSchema:", () => {
130
133
  globalThis.expect(ownerOf(optJson, "customerId")).toEqual(true);
131
134
  });
132
135
  });
136
+ globalThis.describe("deriveObjectSchema with a sensitive field:", () => {
137
+ let sensitiveOf = (json, name) => Stdlib_Option.flatMap(getPropertyOf(json, name), s => getProperty(s, "x-reventless-sensitive"));
138
+ let ownerOf = (json, name) => Stdlib_Option.flatMap(getPropertyOf(json, name), s => getProperty(s, "x-reventless-owner"));
139
+ let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(Sury.$schema(s => ({
140
+ resetToken: s.m(Sensitive$Reventless.string),
141
+ note: s.m(Sury.string),
142
+ contact: s.m(Email$Reventless.schema)
143
+ })));
144
+ globalThis.test("the marked field carries x-reventless-sensitive", () => {
145
+ globalThis.expect(sensitiveOf(json, "resetToken")).toEqual(true);
146
+ });
147
+ globalThis.test("an unmarked string field carries nothing", () => {
148
+ globalThis.expect(sensitiveOf(json, "note")).toBe(undefined);
149
+ });
150
+ globalThis.test("an email field is sensitive with no annotation", () => {
151
+ globalThis.expect(sensitiveOf(json, "contact")).toEqual(true);
152
+ });
153
+ globalThis.test("the field is still a plain string on the wire", () => {
154
+ globalThis.expect(Stdlib_Option.flatMap(getPropertyOf(json, "resetToken"), s => getProperty(s, "type"))).toEqual("string");
155
+ });
156
+ globalThis.test("an optional sensitive field is still recognised", () => {
157
+ let optJson = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(Sury.$schema(s => ({
158
+ resetToken: s.m(Sury.$option(Sensitive$Reventless.string))
159
+ })));
160
+ globalThis.expect(sensitiveOf(optJson, "resetToken")).toEqual(true);
161
+ });
162
+ globalThis.test("sensitivity composes with ownership on one field", () => {
163
+ let bothJson = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(Sury.$schema(s => ({
164
+ customerId: s.m(Sensitive$Reventless.mark(Owner$Reventless.string))
165
+ })));
166
+ globalThis.expect([
167
+ sensitiveOf(bothJson, "customerId"),
168
+ ownerOf(bothJson, "customerId")
169
+ ]).toEqual([
170
+ true,
171
+ true
172
+ ]);
173
+ });
174
+ });
133
175
  globalThis.describe("deriveObjectSchema with stateAnnotations metadata:", () => {
134
176
  globalThis.test("emits x-reventless-id on field listed in ids", () => {
135
177
  let schema = Sury.$schema(s => ({
@@ -872,6 +914,45 @@ globalThis.describe("SuryToJsonSchema:", () => {
872
914
  globalThis.expect(semanticOf(json, "imageUrl")).toEqual("storageRef");
873
915
  });
874
916
  });
917
+ globalThis.describe("a member-reference field:", () => {
918
+ let targetOf = (json, name) => Stdlib_Option.flatMap(getPropertyOf(json, name), s => getProperty(s, "x-reventless-semantic-target"));
919
+ globalThis.test("carries the collection, the view holding it, and what a member is", () => {
920
+ let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(Sury.$schema(s => ({
921
+ productImage: s.m(MemberRef$Reventless.of_(undefined, "Products", Semantic$Reventless.Id.imageRef, "productImages"))
922
+ })));
923
+ globalThis.expect(Stdlib_Option.flatMap(getPropertyOf(json, "productImage"), s => getProperty(s, "x-reventless-semantic"))).toEqual("memberRef");
924
+ globalThis.expect(targetOf(json, "productImage")).toEqual(Object.fromEntries([
925
+ [
926
+ "field",
927
+ "productImages"
928
+ ],
929
+ [
930
+ "view",
931
+ "Products"
932
+ ],
933
+ [
934
+ "content",
935
+ "imageRef"
936
+ ]
937
+ ]));
938
+ });
939
+ globalThis.test("omits the view where the collection is on this very record", () => {
940
+ globalThis.expect(targetOf(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(Sury.$schema(s => ({
941
+ productImage: s.m(MemberRef$Reventless.of_(undefined, undefined, undefined, "productImages"))
942
+ }))), "productImage")).toEqual(Object.fromEntries([[
943
+ "field",
944
+ "productImages"
945
+ ]]));
946
+ });
947
+ globalThis.test("declares no store, so nothing is provisioned for it", () => {
948
+ globalThis.expect(StorageRef$Reventless.getFieldStore(MemberRef$Reventless.of_(undefined, "Products", undefined, "productImages"))).toBe(undefined);
949
+ });
950
+ globalThis.test("stays a plain string on the wire", () => {
951
+ globalThis.expect(Stdlib_Option.flatMap(getPropertyOf(SuryToJsonSchema$ReventlessCore.deriveObjectSchema(Sury.$schema(s => ({
952
+ productImage: s.m(MemberRef$Reventless.of_(undefined, undefined, undefined, "productImages"))
953
+ }))), "productImage"), s => getProperty(s, "type"))).toEqual("string");
954
+ });
955
+ });
875
956
  globalThis.describe("branded scalars keep their underlying shape:", () => {
876
957
  let keyOf = (json, name, key) => Stdlib_Option.flatMap(getPropertyOf(json, name), s => getProperty(s, key));
877
958
  let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(Sury.$schema(s => ({
@@ -1300,6 +1300,28 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
1300
1300
  )->toBe(Some("currency"))
1301
1301
  })
1302
1302
 
1303
+ testSync("@sensitive flows through the PPX to x-reventless-sensitive", () => {
1304
+ expect(
1305
+ annotatedSchema
1306
+ ->getPropertyOf("contact")
1307
+ ->Option.flatMap(s => getProperty(s, "x-reventless-sensitive"))
1308
+ ->Option.flatMap(JSON.Decode.bool),
1309
+ )->toBe(Some(true))
1310
+ })
1311
+
1312
+ // The case the marker exists for. A renderer composes from an event payload,
1313
+ // and the annotation path `deriveObjectSchema` reads for state is
1314
+ // state-only — so a marker that lived there would be absent exactly where
1315
+ // the value is about to be interpolated into a message.
1316
+ testSync("@sensitive reaches an event variant's payload, not only state", () =>
1317
+ expect(
1318
+ Reventless.Sensitive.variantFieldNames(
1319
+ PsAnnotatedView.consumedEventSchema->S.castToUnknown,
1320
+ ~variant="ItemRecorded",
1321
+ ),
1322
+ )->toEqual(["contact"])
1323
+ )
1324
+
1303
1325
  testSync("@metric flows through the PPX to x-reventless-metric {aggregate,label}", () => {
1304
1326
  let metricObj =
1305
1327
  annotatedSchema
@@ -11,6 +11,7 @@ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/component
11
11
  import * as DateTime$Reventless from "@reventlessdev/reventless-spec/src/types/DateTime.res.mjs";
12
12
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
13
13
  import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
14
+ import * as Sensitive$Reventless from "@reventlessdev/reventless-spec/src/components/Sensitive.res.mjs";
14
15
  import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
15
16
  import * as Api_Naming$ReventlessCore from "../../src/components/Api/Api_Naming.res.mjs";
16
17
  import * as PsShipOrder$ReventlessCore from "./StateChangeSlice/PsShipOrder.res.mjs";
@@ -1444,6 +1445,12 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
1444
1445
  globalThis.test("@semantic(\"currency\") flows through the PPX to x-reventless-semantic", () => {
1445
1446
  globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(getPropertyOf(annotatedSchema, "total"), s => getProperty(s, "x-reventless-semantic")), Stdlib_JSON.Decode.string)).toBe("currency");
1446
1447
  });
1448
+ globalThis.test("@sensitive flows through the PPX to x-reventless-sensitive", () => {
1449
+ globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(getPropertyOf(annotatedSchema, "contact"), s => getProperty(s, "x-reventless-sensitive")), Stdlib_JSON.Decode.bool)).toBe(true);
1450
+ });
1451
+ globalThis.test("@sensitive reaches an event variant's payload, not only state", () => {
1452
+ globalThis.expect(Sensitive$Reventless.variantFieldNames(PsAnnotatedView$ReventlessCore.consumedEventSchema, "ItemRecorded")).toEqual(["contact"]);
1453
+ });
1447
1454
  globalThis.test("@metric flows through the PPX to x-reventless-metric {aggregate,label}", () => {
1448
1455
  let metricObj = Stdlib_Option.flatMap(getPropertyOf(annotatedSchema, "total"), s => getProperty(s, "x-reventless-metric"));
1449
1456
  globalThis.expect([
@@ -7,7 +7,16 @@
7
7
 
8
8
  @schema
9
9
  type consumedEvent =
10
- | ItemRecorded({itemId: string, ownerId: string, version: string, name: string, total: float})
10
+ | ItemRecorded({
11
+ itemId: string,
12
+ ownerId: string,
13
+ version: string,
14
+ name: string,
15
+ total: float,
16
+ // On the event, not just the state: a renderer composes from an event
17
+ // payload, so this is the case the marker has to reach.
18
+ @sensitive contact: string,
19
+ })
11
20
 
12
21
  @live(false)
13
22
  @schema
@@ -17,10 +26,11 @@ type state = {
17
26
  @index("byOwner") ownerId: string,
18
27
  name: string,
19
28
  @semantic("currency") @metric({aggregate: "sum", label: "Revenue"}) total: float,
29
+ @sensitive contact: string,
20
30
  }
21
31
 
22
32
  let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
23
33
  switch event {
24
- | ItemRecorded({itemId, ownerId, version, name, total}) =>
25
- [Set(itemId, {itemId, ownerId, version, name, total})]
34
+ | ItemRecorded({itemId, ownerId, version, name, total, contact}) =>
35
+ [Set(itemId, {itemId, ownerId, version, name, total, contact})]
26
36
  }
@@ -3,6 +3,7 @@
3
3
  import * as Sury from "sury";
4
4
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
5
5
  import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
6
+ import * as Sensitive$Reventless from "@reventlessdev/reventless-spec/src/components/Sensitive.res.mjs";
6
7
  import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
7
8
 
8
9
  let consumedEventSchema = Sury.$schema(s => ({
@@ -11,7 +12,8 @@ let consumedEventSchema = Sury.$schema(s => ({
11
12
  ownerId: s.m(DcbTag$Reventless.string),
12
13
  version: s.m(Sury.string),
13
14
  name: s.m(Sury.string),
14
- total: s.m(Sury.float)
15
+ total: s.m(Sury.float),
16
+ contact: s.m(Sensitive$Reventless.string)
15
17
  }));
16
18
 
17
19
  let stateSchema = Sury.$schema(s => ({
@@ -19,7 +21,8 @@ let stateSchema = Sury.$schema(s => ({
19
21
  version: s.m(Sury.string),
20
22
  ownerId: s.m(Sury.string),
21
23
  name: s.m(Sury.string),
22
- total: s.m(Sury.float)
24
+ total: s.m(Sury.float),
25
+ contact: s.m(Sensitive$Reventless.string)
23
26
  }));
24
27
 
25
28
  function project(param) {
@@ -33,7 +36,8 @@ function project(param) {
33
36
  version: event.version,
34
37
  ownerId: event.ownerId,
35
38
  name: event.name,
36
- total: event.total
39
+ total: event.total,
40
+ contact: event.contact
37
41
  }
38
42
  }];
39
43
  }
@@ -94,6 +94,12 @@
94
94
  .structure.inboundTranslationSlices[].name: string
95
95
  .structure.inboundTranslationSlices[].targetName: string
96
96
  .structure.outboundTranslationSlices[].consumedEventTypes[]: string
97
+ # `consumedSources` is itself `js_nullable` and is NEW, so no persisted payload
98
+ # carries the array: a stored structure heals to null and this line is never
99
+ # reached — the same call `traitDeclarations` made below. Required inside it
100
+ # because a source name is a topic key the slice subscribes to, and a fabricated
101
+ # `""` would name a topic that does not exist rather than saying nothing.
102
+ .structure.outboundTranslationSlices[].consumedSources[]: string
97
103
  .structure.outboundTranslationSlices[].inboundCommandTypes[]: string
98
104
  .structure.outboundTranslationSlices[].name: string
99
105
  .structure.readModels[].consumedEventTypes[]: string