@reventlessdev/reventless-core 3.0.0-alpha.236 → 3.0.0-alpha.238

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 (53) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/package.json +6 -6
  3. package/src/Message.res +1 -1
  4. package/src/adapter/Monitoring/Monitoring.res +1 -1
  5. package/src/admin/Platform_Admin_Structure.res +5 -1
  6. package/src/admin/Platform_Admin_Structure.res.mjs +4 -2
  7. package/src/admin/Platform_ComponentDefinitionsApi.res +4 -2
  8. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +11 -3
  9. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res +1 -1
  10. package/src/components/Aggregate/Aggregate_Callback.res +2 -2
  11. package/src/components/Api/ApiAllowedStatesHelpers.res +2 -3
  12. package/src/components/Api/ApiTargetStateHelpers.res +4 -3
  13. package/src/components/Api/GraphQL_FragmentGenerator.res +14 -1
  14. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +1 -1
  15. package/src/components/Api/QueryDbListQuery.res +25 -1
  16. package/src/components/Api/QueryDbListQuery.res.mjs +11 -4
  17. package/src/components/Api/SuryToJsonSchema.res +39 -0
  18. package/src/components/Api/SuryToJsonSchema.res.mjs +34 -3
  19. package/src/components/Dcb/Dcb_Builder.res +79 -16
  20. package/src/components/Dcb/Dcb_Builder.res.mjs +41 -5
  21. package/src/components/EventLog/EventLog.res +1 -1
  22. package/src/plugin/component/Plugin_Structure.res +377 -18
  23. package/src/plugin/component/Plugin_Structure.res.mjs +204 -9
  24. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res +1 -1
  25. package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +3 -2
  26. package/tests/admin/Platform_Admin_StructureTest.res +49 -0
  27. package/tests/admin/Platform_Admin_StructureTest.res.mjs +24 -0
  28. package/tests/admin/Platform_BakedManifestTest.res +3 -1
  29. package/tests/admin/Platform_BakedManifestTest.res.mjs +3 -1
  30. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +14 -8
  31. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +24 -12
  32. package/tests/admin/Platform_PluginStructuresApiTest.res +3 -1
  33. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +6 -2
  34. package/tests/aggregate/AggregateCacheTest.res +1 -1
  35. package/tests/aggregate/AggregateSnapshotTest.res +1 -1
  36. package/tests/api/Api_IdsTest.res.mjs +1 -1
  37. package/tests/api/SuryToJsonSchemaTest.res +141 -1
  38. package/tests/api/SuryToJsonSchemaTest.res.mjs +269 -54
  39. package/tests/commandgenerator/OwnerStampingTest.res +57 -0
  40. package/tests/commandgenerator/OwnerStampingTest.res.mjs +19 -0
  41. package/tests/message/MessageTest.res +1 -1
  42. package/tests/plugin/HeartbeatDisconnectGraceTest.res +1 -1
  43. package/tests/plugin/PluginStructureTest.res +332 -15
  44. package/tests/plugin/PluginStructureTest.res.mjs +316 -10
  45. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res +31 -0
  46. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +73 -0
  47. package/tests/plugin/StateChangeSlice/PsShipOrder.res +16 -1
  48. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +7 -2
  49. package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +3 -2
  50. package/tests/plugin/StateViewSlice/PsShipmentsView.res +26 -0
  51. package/tests/plugin/StateViewSlice/PsShipmentsView.res.mjs +101 -0
  52. package/tests/plugin/pluginDefinitionRequiredScalars.txt +2 -0
  53. package/tests/util/CsvStreamTest.res +1 -1
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as S from "sury/src/S.res.mjs";
4
+ import * as Stdlib_Exn from "@rescript/runtime/lib/es6/Stdlib_Exn.js";
4
5
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
6
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
7
  import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
@@ -8,12 +9,14 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
9
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
9
10
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
10
11
  import * as DateTime$Reventless from "@reventlessdev/reventless-spec/src/types/DateTime.res.mjs";
12
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
11
13
  import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
12
14
  import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
13
15
  import * as Api_Naming$ReventlessCore from "../../src/components/Api/Api_Naming.res.mjs";
14
16
  import * as PsShipOrder$ReventlessCore from "./StateChangeSlice/PsShipOrder.res.mjs";
15
17
  import * as PsOrdersView$ReventlessCore from "./StateViewSlice/PsOrdersView.res.mjs";
16
18
  import * as PsPlaceOrder$ReventlessCore from "./StateChangeSlice/PsPlaceOrder.res.mjs";
19
+ import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
17
20
  import * as PsChangePhoto$ReventlessCore from "./StateChangeSlice/PsChangePhoto.res.mjs";
18
21
  import * as CapabilityManifest$Reventless from "@reventlessdev/reventless-spec/src/components/CapabilityManifest.res.mjs";
19
22
  import * as PsReserveStock$ReventlessCore from "./StateChangeSlice/PsReserveStock.res.mjs";
@@ -21,8 +24,10 @@ import * as PsUploadAvatar$ReventlessCore from "./StateChangeSlice/PsUploadAvata
21
24
  import * as PsAnnotatedView$ReventlessCore from "./StateViewSlice/PsAnnotatedView.res.mjs";
22
25
  import * as PsAttachInvoice$ReventlessCore from "./StateChangeSlice/PsAttachInvoice.res.mjs";
23
26
  import * as PsCustomersView$ReventlessCore from "./StateViewSlice/PsCustomersView.res.mjs";
27
+ import * as PsShipmentsView$ReventlessCore from "./StateViewSlice/PsShipmentsView.res.mjs";
24
28
  import * as Plugin_Structure$ReventlessCore from "../../src/plugin/component/Plugin_Structure.res.mjs";
25
29
  import * as PsCategoriesView$ReventlessCore from "./StateViewSlice/PsCategoriesView.res.mjs";
30
+ import * as PsDispatchShipment$ReventlessCore from "./StateChangeSlice/PsDispatchShipment.res.mjs";
26
31
  import * as Capability_Inference$ReventlessCore from "../../src/plugin/component/Capability_Inference.res.mjs";
27
32
  import * as PsAvailableProductsView$ReventlessCore from "./StateViewSlice/PsAvailableProductsView.res.mjs";
28
33
 
@@ -336,6 +341,62 @@ let PsReserveStockSlice = {
336
341
  make: make$10
337
342
  };
338
343
 
344
+ let Behavior$6 = {
345
+ initialState: false,
346
+ evolve: PsDispatchShipment$ReventlessCore.evolve,
347
+ decide: PsDispatchShipment$ReventlessCore.decide,
348
+ moduleUrl: PsDispatchShipment$ReventlessCore.moduleUrl
349
+ };
350
+
351
+ function make$11(param, param$1, param$2, param$3, param$4, param$5) {
352
+ return 0;
353
+ }
354
+
355
+ let PsDispatchShipmentSlice_Spec = {
356
+ name: PsDispatchShipment$ReventlessCore.name,
357
+ moduleUrl: PsDispatchShipment$ReventlessCore.moduleUrl,
358
+ Id: Id$Reventless.$$String,
359
+ consumedEventSchema: PsDispatchShipment$ReventlessCore.consumedEventSchema,
360
+ errorSchema: PsDispatchShipment$ReventlessCore.errorSchema,
361
+ eventSchema: PsDispatchShipment$ReventlessCore.eventSchema,
362
+ commandSchema: PsDispatchShipment$ReventlessCore.commandSchema,
363
+ commandAuthorization: PsDispatchShipment$ReventlessCore.commandAuthorization,
364
+ readConsistency: PsDispatchShipment$ReventlessCore.readConsistency
365
+ };
366
+
367
+ let PsDispatchShipmentSlice = {
368
+ Spec: PsDispatchShipmentSlice_Spec,
369
+ Behavior: Behavior$6,
370
+ isAsync: false,
371
+ make: make$11
372
+ };
373
+
374
+ let Projection$5 = {
375
+ project: PsShipmentsView$ReventlessCore.project,
376
+ moduleUrl: PsShipmentsView$ReventlessCore.moduleUrl
377
+ };
378
+
379
+ function make$12(param, param$1, param$2) {
380
+ return 0;
381
+ }
382
+
383
+ let PsShipmentsViewSlice_Spec = {
384
+ name: PsShipmentsView$ReventlessCore.name,
385
+ moduleUrl: PsShipmentsView$ReventlessCore.moduleUrl,
386
+ stateSchema: PsShipmentsView$ReventlessCore.stateSchema,
387
+ consumedEventSchema: PsShipmentsView$ReventlessCore.consumedEventSchema,
388
+ config: PsShipmentsView$ReventlessCore.config,
389
+ subIdConfig: undefined,
390
+ authorization: PsShipmentsView$ReventlessCore.authorization,
391
+ visibility: PsShipmentsView$ReventlessCore.visibility
392
+ };
393
+
394
+ let PsShipmentsViewSlice = {
395
+ Spec: PsShipmentsViewSlice_Spec,
396
+ Projection: Projection$5,
397
+ make: make$12
398
+ };
399
+
339
400
  let structure = Plugin_Structure$ReventlessCore.make("TestPlugin", undefined, undefined, [
340
401
  PsOrdersViewSlice,
341
402
  PsAvailableProductsViewSlice,
@@ -346,6 +407,102 @@ let structure = Plugin_Structure$ReventlessCore.make("TestPlugin", undefined, un
346
407
  PsShipOrderSlice
347
408
  ], undefined, undefined, undefined, undefined, undefined, undefined);
348
409
 
410
+ globalThis.describe("lifecycle topology", () => {
411
+ let ordersStates = Object.fromEntries([[
412
+ "Orders",
413
+ [
414
+ "Placed",
415
+ "Shipped",
416
+ "Cancelled"
417
+ ]
418
+ ]]);
419
+ let writableWith = commands => ({
420
+ name: "Ordering",
421
+ commands: commands,
422
+ producedEventTypes: [],
423
+ consumedEventTypes: [],
424
+ linkedViews: ["Orders"],
425
+ consistencyRead: undefined,
426
+ events: [],
427
+ errors: [],
428
+ chapter: undefined
429
+ });
430
+ let command = (name, allowedStates, targetState, param) => ({
431
+ name: name,
432
+ schema: "",
433
+ level: "Instance",
434
+ aggregateIdField: undefined,
435
+ mutationField: "",
436
+ references: [],
437
+ allowedStates: allowedStates,
438
+ targetState: targetState,
439
+ apiExposed: undefined,
440
+ requiredAccess: undefined,
441
+ ownerField: undefined
442
+ });
443
+ globalThis.test("names a state no command can reach", () => {
444
+ let findings = Plugin_Structure$ReventlessCore.lifecycleTopologyFindings([writableWith([command("Ship", ["Placed"], "Shipped", undefined)])], ordersStates);
445
+ globalThis.expect(findings.map(param => param[1].includes("Cancelled"))).toEqual([true]);
446
+ });
447
+ globalThis.test("says nothing when every state is reachable", () => {
448
+ let findings = Plugin_Structure$ReventlessCore.lifecycleTopologyFindings([writableWith([
449
+ command("Ship", ["Placed"], "Shipped", undefined),
450
+ command("Cancel", ["Placed"], "Cancelled", undefined)
451
+ ])], ordersStates);
452
+ globalThis.expect(findings.length).toBe(0);
453
+ });
454
+ globalThis.test("does not call the initial state unreachable", () => {
455
+ let findings = Plugin_Structure$ReventlessCore.lifecycleTopologyFindings([writableWith([
456
+ command("Ship", ["Placed"], "Shipped", undefined),
457
+ command("Cancel", ["Placed"], "Cancelled", undefined)
458
+ ])], ordersStates);
459
+ globalThis.expect(findings.map(param => param[1].includes("Placed"))).toEqual([]);
460
+ });
461
+ globalThis.test("does not report a state with no way out", () => {
462
+ let findings = Plugin_Structure$ReventlessCore.lifecycleTopologyFindings([writableWith([
463
+ command("Ship", ["Placed"], "Shipped", undefined),
464
+ command("Cancel", ["Placed"], "Cancelled", undefined)
465
+ ])], ordersStates);
466
+ globalThis.expect(findings.length).toBe(0);
467
+ });
468
+ });
469
+
470
+ globalThis.describe("@transition cross-check", () => {
471
+ let buildBad = () => {
472
+ try {
473
+ Plugin_Structure$ReventlessCore.make("TransitionPlugin", undefined, undefined, [PsShipmentsViewSlice], [PsDispatchShipmentSlice], undefined, undefined, undefined, undefined, undefined, undefined);
474
+ return;
475
+ } catch (raw_e) {
476
+ let e = Primitive_exceptions.internalToException(raw_e);
477
+ if (e.RE_EXN_ID === Stdlib_Exn.$$Error) {
478
+ return Stdlib_Option.getOr(e._1.message, "");
479
+ } else {
480
+ return "";
481
+ }
482
+ }
483
+ };
484
+ globalThis.test("a state no linked view declares fails the build", () => {
485
+ globalThis.expect(Stdlib_Option.isSome(buildBad())).toBe(true);
486
+ });
487
+ globalThis.test("the failure names the command, the state and the view", () => {
488
+ let message = Stdlib_Option.getOr(buildBad(), "");
489
+ globalThis.expect([
490
+ message.includes("DispatchShipment"),
491
+ message.includes("Dispatchd"),
492
+ message.includes("Shipments"),
493
+ message.includes("Booked")
494
+ ]).toEqual([
495
+ true,
496
+ true,
497
+ true,
498
+ true
499
+ ]);
500
+ });
501
+ globalThis.test("a plugin whose views declare no lifecycle still builds", () => {
502
+ globalThis.expect(structure.stateChangeSlices.length).toBe(2);
503
+ });
504
+ });
505
+
349
506
  globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
350
507
  globalThis.describe("stateChangeSlices", () => {
351
508
  globalThis.test("produces two SCS entries in declaration order", () => {
@@ -423,7 +580,7 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
423
580
  false
424
581
  ]);
425
582
  });
426
- globalThis.test("ShipOrder: @targetState(\"Shipped\") flows through the PPX to commandDef.targetState", () => {
583
+ globalThis.test("ShipOrder: @transition's target flows through the PPX to commandDef.targetState", () => {
427
584
  let shipOrder = structure.stateChangeSlices[1];
428
585
  let byName = name => shipOrder.commands.find(c => c.name === name);
429
586
  globalThis.expect([
@@ -434,6 +591,17 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
434
591
  undefined
435
592
  ]);
436
593
  });
594
+ globalThis.test("ShipOrder: @transition's from-set flows through to commandDef.allowedStates", () => {
595
+ let shipOrder = structure.stateChangeSlices[1];
596
+ let byName = name => shipOrder.commands.find(c => c.name === name);
597
+ globalThis.expect([
598
+ Stdlib_Option.flatMap(byName("ShipOrder"), c => c.allowedStates),
599
+ Stdlib_Option.flatMap(byName("CancelShipment"), c => c.allowedStates)
600
+ ]).toEqual([
601
+ ["Placed"],
602
+ undefined
603
+ ]);
604
+ });
437
605
  globalThis.test("ShipOrder: the @noApi variant carries no callable mutation field (no sibling leak)", () => {
438
606
  let shipOrder = structure.stateChangeSlices[1];
439
607
  let byName = name => shipOrder.commands.find(c => c.name === name);
@@ -796,30 +964,166 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
796
964
  ]);
797
965
  });
798
966
  });
799
- globalThis.describe("statusField — the shape rule", () => {
800
- globalThis.test("a `status` field holding a closed set of values is the lifecycle field", () => {
967
+ globalThis.describe("lifecycleField — the name rule and the shape rule", () => {
968
+ let withLifecycle = (schema, field) => S.Metadata.set(schema, StateAnnotations$Reventless.stateAnnotationsId, {
969
+ ids: [],
970
+ compositeIds: [],
971
+ subIds: [],
972
+ compositeSubIds: [],
973
+ indexes: [],
974
+ hidden: [],
975
+ summary: [],
976
+ drillTargets: [],
977
+ drillTargetKeys: [],
978
+ collapsed: [],
979
+ scan: [],
980
+ scanSort: [],
981
+ semantic: [],
982
+ metric: [],
983
+ lifecycle: field,
984
+ groupBy: undefined,
985
+ visibility: undefined,
986
+ live: undefined,
987
+ retired: undefined
988
+ });
989
+ globalThis.test("a `lifecycle` field holding a closed set of values is the lifecycle field", () => {
801
990
  let schema = S.schema(s => ({
802
- status: s.m(S.union([
991
+ lifecycle: s.m(S.union([
803
992
  S.literal("Placed"),
804
993
  S.literal("Shipped")
805
994
  ]))
806
995
  }));
807
- globalThis.expect(Plugin_Structure$ReventlessCore.statusFieldFromStateSchema("Test", schema)).toEqual("status");
996
+ globalThis.expect(Plugin_Structure$ReventlessCore.lifecycleFieldFromStateSchema("Test", schema)).toEqual("lifecycle");
808
997
  });
809
998
  globalThis.test("an optional one counts too", () => {
810
999
  let schema = S.schema(s => ({
811
- status: s.m(S.option(S.union([
1000
+ lifecycle: s.m(S.option(S.union([
812
1001
  S.literal("Placed"),
813
1002
  S.literal("Shipped")
814
1003
  ])))
815
1004
  }));
816
- globalThis.expect(Plugin_Structure$ReventlessCore.statusFieldFromStateSchema("Test", schema)).toEqual("status");
1005
+ globalThis.expect(Plugin_Structure$ReventlessCore.lifecycleFieldFromStateSchema("Test", schema)).toEqual("lifecycle");
1006
+ });
1007
+ globalThis.test("free text named `lifecycle` is not a lifecycle", () => {
1008
+ let schema = S.schema(s => ({
1009
+ lifecycle: s.m(S.string)
1010
+ }));
1011
+ globalThis.expect(Plugin_Structure$ReventlessCore.lifecycleFieldFromStateSchema("Test", schema)).toEqual(undefined);
1012
+ });
1013
+ globalThis.test("an unannotated field named `status` resolves to None", () => {
1014
+ let schema = S.schema(s => ({
1015
+ status: s.m(S.union([
1016
+ S.literal("Placed"),
1017
+ S.literal("Shipped")
1018
+ ]))
1019
+ }));
1020
+ globalThis.expect(Plugin_Structure$ReventlessCore.lifecycleFieldFromStateSchema("Test", schema)).toEqual(undefined);
1021
+ });
1022
+ globalThis.test("the annotation names a field the convention would never reach", () => {
1023
+ let schema = S.schema(s => ({
1024
+ locationStatus: s.m(S.union([
1025
+ S.literal("Pending"),
1026
+ S.literal("Located")
1027
+ ]))
1028
+ }));
1029
+ globalThis.expect(Plugin_Structure$ReventlessCore.lifecycleFieldFromStateSchema("Test", withLifecycle(schema, "locationStatus"))).toEqual("locationStatus");
1030
+ });
1031
+ });
1032
+ globalThis.describe("retiredField — annotation and nothing else", () => {
1033
+ let withRetired = (schema, field) => S.Metadata.set(schema, StateAnnotations$Reventless.stateAnnotationsId, {
1034
+ ids: [],
1035
+ compositeIds: [],
1036
+ subIds: [],
1037
+ compositeSubIds: [],
1038
+ indexes: [],
1039
+ hidden: [],
1040
+ summary: [],
1041
+ drillTargets: [],
1042
+ drillTargetKeys: [],
1043
+ collapsed: [],
1044
+ scan: [],
1045
+ scanSort: [],
1046
+ semantic: [],
1047
+ metric: [],
1048
+ lifecycle: undefined,
1049
+ groupBy: undefined,
1050
+ visibility: undefined,
1051
+ live: undefined,
1052
+ retired: {
1053
+ field: field,
1054
+ label: "",
1055
+ showWhenFalse: false,
1056
+ values: undefined
1057
+ }
1058
+ });
1059
+ globalThis.test("publishes the retirement state beside the field", () => {
1060
+ let withState = schema => S.Metadata.set(schema, StateAnnotations$Reventless.stateAnnotationsId, {
1061
+ ids: [],
1062
+ compositeIds: [],
1063
+ subIds: [],
1064
+ compositeSubIds: [],
1065
+ indexes: [],
1066
+ hidden: [],
1067
+ summary: [],
1068
+ drillTargets: [],
1069
+ drillTargetKeys: [],
1070
+ collapsed: [],
1071
+ scan: [],
1072
+ scanSort: [],
1073
+ semantic: [],
1074
+ metric: [],
1075
+ lifecycle: "accountStatus",
1076
+ groupBy: undefined,
1077
+ visibility: undefined,
1078
+ live: undefined,
1079
+ retired: {
1080
+ field: "accountStatus",
1081
+ label: "",
1082
+ showWhenFalse: false,
1083
+ values: [
1084
+ "Deactivated",
1085
+ "Closed"
1086
+ ]
1087
+ }
1088
+ });
1089
+ let schema = withState(S.schema(s => ({
1090
+ accountStatus: s.m(S.string)
1091
+ })));
1092
+ globalThis.expect([
1093
+ Plugin_Structure$ReventlessCore.retiredFieldFromStateSchema(schema),
1094
+ Plugin_Structure$ReventlessCore.retiredValuesFromStateSchema(schema)
1095
+ ]).toEqual([
1096
+ "accountStatus",
1097
+ [
1098
+ "Deactivated",
1099
+ "Closed"
1100
+ ]
1101
+ ]);
1102
+ });
1103
+ globalThis.test("publishes no states on the boolean form", () => {
1104
+ let schema = S.schema(s => ({
1105
+ archived: s.m(S.bool)
1106
+ }));
1107
+ globalThis.expect(Plugin_Structure$ReventlessCore.retiredValuesFromStateSchema(withRetired(schema, "archived"))).toEqual(undefined);
1108
+ });
1109
+ globalThis.test("names the annotated field", () => {
1110
+ let schema = S.schema(s => ({
1111
+ archived: s.m(S.bool)
1112
+ }));
1113
+ globalThis.expect(Plugin_Structure$ReventlessCore.retiredFieldFromStateSchema(withRetired(schema, "archived"))).toEqual("archived");
1114
+ });
1115
+ globalThis.test("declines a conventionally-named boolean nobody annotated", () => {
1116
+ let schema = S.schema(s => ({
1117
+ archived: s.m(S.bool),
1118
+ deactivated: s.m(S.bool)
1119
+ }));
1120
+ globalThis.expect(Plugin_Structure$ReventlessCore.retiredFieldFromStateSchema(schema)).toEqual(undefined);
817
1121
  });
818
- globalThis.test("free text named `status` is not a lifecycle", () => {
1122
+ globalThis.test("declines a schema carrying no annotation spec at all", () => {
819
1123
  let schema = S.schema(s => ({
820
- status: s.m(S.string)
1124
+ id: s.m(S.string)
821
1125
  }));
822
- globalThis.expect(Plugin_Structure$ReventlessCore.statusFieldFromStateSchema("Test", schema)).toEqual(undefined);
1126
+ globalThis.expect(Plugin_Structure$ReventlessCore.retiredFieldFromStateSchema(schema)).toEqual(undefined);
823
1127
  });
824
1128
  });
825
1129
  let getProperty = (json, key) => {
@@ -1138,6 +1442,8 @@ export {
1138
1442
  PsUploadAvatarSlice,
1139
1443
  PsChangePhotoSlice,
1140
1444
  PsReserveStockSlice,
1445
+ PsDispatchShipmentSlice,
1446
+ PsShipmentsViewSlice,
1141
1447
  structure,
1142
1448
  }
1143
1449
  /* structure Not a pure module */
@@ -0,0 +1,31 @@
1
+ // Test fixture spec for the @transition cross-check — the failing case.
2
+ //
3
+ // `Dispatchd` is a deliberate typo. It compiles: the PPX strips the attribute
4
+ // before the typechecker sees it, so a misspelled state is a well-formed string
5
+ // all the way down. That is exactly the hole the structure-side check closes,
6
+ // and a fixture that could not compile would not demonstrate it.
7
+
8
+ @@reventless.spec("DispatchShipment")
9
+
10
+ type shipmentStatus =
11
+ | Booked
12
+ | Dispatched
13
+
14
+ @schema
15
+ type consumedEvent = ShipmentBooked({shipmentId: string})
16
+
17
+ let evolve = (_state, _event) => true
18
+
19
+ type state = bool
20
+ let initialState = false
21
+
22
+ @schema
23
+ type command = @transition(([Booked]) => Dispatchd) DispatchShipment({shipmentId: string})
24
+
25
+ @schema
26
+ type error = ShipmentNotFound
27
+
28
+ @schema
29
+ type event = ShipmentDispatched({shipmentId: string})
30
+
31
+ let decide = (_state, _command): result<array<event>, error> => Ok([])
@@ -0,0 +1,73 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
5
+ import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
6
+
7
+ let consumedEventSchema = S.schema(s => ({
8
+ TAG: "ShipmentBooked",
9
+ shipmentId: s.m(DcbTag$Reventless.string)
10
+ }));
11
+
12
+ function evolve(_state, _event) {
13
+ return true;
14
+ }
15
+
16
+ let commandSchema = S.schema(s => ({
17
+ TAG: "DispatchShipment",
18
+ shipmentId: s.m(DcbTag$Reventless.string)
19
+ }));
20
+
21
+ let errorSchema = S.literal("ShipmentNotFound");
22
+
23
+ let eventSchema = S.schema(s => ({
24
+ TAG: "ShipmentDispatched",
25
+ shipmentId: s.m(DcbTag$Reventless.string)
26
+ }));
27
+
28
+ function decide(_state, _command) {
29
+ return {
30
+ TAG: "Ok",
31
+ _0: []
32
+ };
33
+ }
34
+
35
+ let commandSchema$1 = Api$ReventlessInfra.markAllowedStates(commandSchema, [[
36
+ "DispatchShipment",
37
+ ["Booked"]
38
+ ]]);
39
+
40
+ let commandSchema$2 = Api$ReventlessInfra.markTargetState(commandSchema$1, [[
41
+ "DispatchShipment",
42
+ "Dispatchd"
43
+ ]]);
44
+
45
+ function commandAuthorization(param) {
46
+ return "AllowAuthenticated";
47
+ }
48
+
49
+ let name = "DispatchShipment";
50
+
51
+ let Id;
52
+
53
+ let initialState = false;
54
+
55
+ let moduleUrl = "@reventlessdev/reventless-core/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs";
56
+
57
+ let readConsistency = "EscalateOnRetry";
58
+
59
+ export {
60
+ name,
61
+ Id,
62
+ consumedEventSchema,
63
+ evolve,
64
+ initialState,
65
+ errorSchema,
66
+ eventSchema,
67
+ decide,
68
+ commandSchema$2 as commandSchema,
69
+ moduleUrl,
70
+ commandAuthorization,
71
+ readConsistency,
72
+ }
73
+ /* consumedEventSchema Not a pure module */
@@ -18,8 +18,23 @@ let evolve = (_state, _event) => true
18
18
  // It is also `@noApi` (internal/admin only): a two-variant slice with exactly
19
19
  // one API-exposed command, which is the case that used to leak the exposed
20
20
  // command's slice mutation field onto the non-exposed variant.
21
+ // The lifecycle this slice's commands move a shipment through. The PPX strips
22
+ // @transition before the typechecker sees it, so this enum is not what makes the
23
+ // reference below valid — the structure-side check against the linked view is.
24
+ // It is here because a state is a constructor, and naming one in a string is a
25
+ // reference nothing can check at any layer.
26
+ type shipmentStatus = Placed | Shipped | Cancelled
27
+
28
+ // CancelShipment is payload-less — it compiles to a bare string literal, so it
29
+ // exercises toCommandDef's payload-less branch (still surfaced in `commands`).
30
+ // It is also `@noApi` (internal/admin only): a two-variant slice with exactly
31
+ // one API-exposed command, which is the case that used to leak the exposed
32
+ // command's slice mutation field onto the non-exposed variant. It carries no
33
+ // @transition, which is the un-annotated case the resolver still guesses for.
21
34
  @schema
22
- type command = @targetState("Shipped") ShipOrder({orderId: string}) | @noApi CancelShipment
35
+ type command =
36
+ | @transition(([Placed]) => Shipped) ShipOrder({orderId: string})
37
+ | @noApi CancelShipment
23
38
 
24
39
  // Two variants, one payload-less and one carrying a field — the same two branches
25
40
  // `events` exercises, so the structure's `errors` list is pinned on both.
@@ -47,7 +47,12 @@ function decide(_state, _command) {
47
47
 
48
48
  let commandSchema$1 = Api$ReventlessInfra.markNoApiVariants(commandSchema, ["CancelShipment"]);
49
49
 
50
- let commandSchema$2 = Api$ReventlessInfra.markTargetState(commandSchema$1, [[
50
+ let commandSchema$2 = Api$ReventlessInfra.markAllowedStates(commandSchema$1, [[
51
+ "ShipOrder",
52
+ ["Placed"]
53
+ ]]);
54
+
55
+ let commandSchema$3 = Api$ReventlessInfra.markTargetState(commandSchema$2, [[
51
56
  "ShipOrder",
52
57
  "Shipped"
53
58
  ]]);
@@ -75,7 +80,7 @@ export {
75
80
  errorSchema,
76
81
  eventSchema,
77
82
  decide,
78
- commandSchema$2 as commandSchema,
83
+ commandSchema$3 as commandSchema,
79
84
  moduleUrl,
80
85
  commandAuthorization,
81
86
  readConsistency,
@@ -79,10 +79,11 @@ let stateSchema$1 = S.Metadata.set(stateSchema, StateAnnotations$Reventless.stat
79
79
  label: "Revenue"
80
80
  }
81
81
  ]],
82
- status: undefined,
82
+ lifecycle: undefined,
83
83
  groupBy: undefined,
84
84
  visibility: undefined,
85
- live: false
85
+ live: false,
86
+ retired: undefined
86
87
  });
87
88
 
88
89
  let name = "AnnotatedView";
@@ -0,0 +1,26 @@
1
+ // Test fixture spec for the @transition cross-check.
2
+ // A view that declares a lifecycle, so a command's declared states have
3
+ // something to be checked against. `PsOrdersView` deliberately declares none —
4
+ // the two together cover both the checked and the unvalidated path.
5
+
6
+ @@reventless.spec("Shipments")
7
+
8
+ @schema
9
+ type consumedEvent =
10
+ | ShipmentBooked({shipmentId: string})
11
+ | ShipmentDispatched({shipmentId: string})
12
+
13
+ @schema
14
+ type shipmentStatus =
15
+ | Booked
16
+ | Dispatched
17
+
18
+ @schema
19
+ type state = {shipmentId: string, @lifecycle shipmentStatus: shipmentStatus}
20
+
21
+ let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
22
+ switch event {
23
+ | ShipmentBooked({shipmentId}) => [Set(shipmentId, {shipmentId, shipmentStatus: Booked})]
24
+ | ShipmentDispatched({shipmentId}) =>
25
+ [Update(shipmentId, s => {...s, shipmentStatus: Dispatched})]
26
+ }