@reventlessdev/reventless-core 3.0.0-alpha.223 → 3.0.0-alpha.224

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 (28) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/package.json +7 -7
  3. package/src/admin/Platform_Admin_Structure.res +10 -0
  4. package/src/admin/Platform_Admin_Structure.res.mjs +6 -1
  5. package/src/admin/Platform_ComponentDefinitionsApi.res +11 -1
  6. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +13 -1
  7. package/src/components/Api/Api_Ids.res +63 -0
  8. package/src/components/Api/Api_Ids.res.mjs +43 -0
  9. package/src/components/Api/GraphQL_FragmentGenerator.res +121 -28
  10. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +97 -27
  11. package/src/components/Api/QueryDbListQuery.res +13 -5
  12. package/src/components/Api/QueryDbListQuery.res.mjs +5 -3
  13. package/src/components/Api/SchemaType.res +36 -2
  14. package/src/components/Api/SchemaType.res.mjs +31 -10
  15. package/src/plugin/component/Plugin_Structure.res +19 -0
  16. package/src/plugin/component/Plugin_Structure.res.mjs +11 -2
  17. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +63 -0
  18. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +117 -4
  19. package/tests/admin/Platform_PluginStructuresApiTest.res +10 -0
  20. package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +23 -2
  21. package/tests/api/Api_IdsTest.res +143 -0
  22. package/tests/api/Api_IdsTest.res.mjs +97 -0
  23. package/tests/api/GraphQL_FragmentGeneratorTest.res +169 -0
  24. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +179 -0
  25. package/tests/plugin/PluginStructureTest.res +90 -0
  26. package/tests/plugin/PluginStructureTest.res.mjs +86 -8
  27. package/tests/plugin/StateViewSlice/PsCategoriesView.res +17 -0
  28. package/tests/plugin/StateViewSlice/PsCategoriesView.res.mjs +57 -0
@@ -78,6 +78,15 @@ module PsAnnotatedViewSlice: ReventlessInfra.StateViewSlice.T = {
78
78
  type component = svsComponent
79
79
  let make = (~dcbEventLog as _, ~runtime as _=?, ~opts as _=?): component => Obj.magic(0)
80
80
  }
81
+ module PsCategoriesViewSlice: ReventlessInfra.StateViewSlice.T = {
82
+ module Spec = PsCategoriesView
83
+ module Projection = {
84
+ let project = PsCategoriesView.project
85
+ let moduleUrl = PsCategoriesView.moduleUrl
86
+ }
87
+ type component = svsComponent
88
+ let make = (~dcbEventLog as _, ~runtime as _=?, ~opts as _=?): component => Obj.magic(0)
89
+ }
81
90
 
82
91
  module PsAttachInvoiceSlice: ReventlessInfra.StateChangeSlice.T = {
83
92
  module Spec = PsAttachInvoice
@@ -560,6 +569,87 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
560
569
  })
561
570
  })
562
571
 
572
+ // The singular is published because it is not derivable from the plural without
573
+ // re-implementing `Api_Naming.singularize` — and a consumer that guesses gets an
574
+ // irregular plural wrong for both the detail query and the filter input type.
575
+ describe("singleQueryField — the singular the schema actually serves", () => {
576
+ let withIrregularPlural = Plugin_Structure.make(
577
+ ~name="TestPlugin",
578
+ ~stateChangeSlices=[],
579
+ ~stateViewSlices=[module(PsCategoriesViewSlice)],
580
+ )
581
+ let categories = withIrregularPlural.stateViewSlices->Array.getUnsafe(0)
582
+
583
+ // "strip a trailing `s`" would publish `TestPlugin_Categorie`, a field the
584
+ // generated SDL does not contain.
585
+ testSync("an `-ies` plural publishes the `y` singular, not a truncation", () => {
586
+ expect((categories.queryField, categories.singleQueryField))->toEqual((
587
+ "TestPlugin_Categories",
588
+ Some("TestPlugin_Category"),
589
+ ))
590
+ })
591
+
592
+ testSync("every built def states the name Api_Naming returned", () => {
593
+ let stated = structure.stateViewSlices->Array.every(q =>
594
+ q.singleQueryField ==
595
+ Some(
596
+ Api_Naming.queryFieldNamesForStateView(
597
+ ~plugin="TestPlugin",
598
+ ~viewName=q.name,
599
+ ).singleFieldName,
600
+ )
601
+ )
602
+ expect(stated)->toEqual(true)
603
+ })
604
+ })
605
+
606
+ // Which field identifies a row, and whether the author said so or this side
607
+ // worked it out — the same declaration-vs-guess distinction `labelFieldSource`
608
+ // publishes, for the field the generated filter and order-by are built from.
609
+ describe("idField / idFieldSource — the key and its provenance", () => {
610
+ let keyOf = (q: Reventless.Plugin.queryableDef) => (q.idField, q.idFieldSource)
611
+ let withCategories = Plugin_Structure.make(
612
+ ~name="TestPlugin",
613
+ ~stateChangeSlices=[],
614
+ ~stateViewSlices=[module(PsCategoriesViewSlice)],
615
+ )
616
+
617
+ // Orders holds `orderId` + `customerName`; the component name matches the
618
+ // former, so the rung is the one a client could also reach on its own.
619
+ testSync("a name-matching `*Id` field is a guess the consumer can also make", () =>
620
+ expect(keyOf(structure.stateViewSlices->Array.getUnsafe(0)))->toEqual((
621
+ Some("orderId"),
622
+ Some("convention"),
623
+ ))
624
+ )
625
+
626
+ // AvailableProducts: the name would ask for `availableProductId`, which is not
627
+ // a field — `productId` wins because it is the only candidate there is.
628
+ testSync("a sole `*Id` field is a fact only this side can see", () =>
629
+ expect(keyOf(structure.stateViewSlices->Array.getUnsafe(1)))->toEqual((
630
+ Some("productId"),
631
+ Some("sole"),
632
+ ))
633
+ )
634
+
635
+ testSync("an `-ies` component name singularises to its key", () =>
636
+ expect(keyOf(withCategories.stateViewSlices->Array.getUnsafe(0)))->toEqual((
637
+ Some("categoryId"),
638
+ Some("convention"),
639
+ ))
640
+ )
641
+
642
+ // AnnotatedView declares `@id itemId` beside an `@index` field `ownerId`, and
643
+ // its name matches neither — inference alone would decline here, so reporting
644
+ // `itemId` proves the declaration produced it.
645
+ testSync("a declared @id is used where inference would decline", () =>
646
+ expect(keyOf(structure.stateViewSlices->Array.getUnsafe(3)))->toEqual((
647
+ Some("itemId"),
648
+ Some("annotation"),
649
+ ))
650
+ )
651
+ })
652
+
563
653
  describe("statusField — the shape rule", () => {
564
654
  let statusOf = schema =>
565
655
  Plugin_Structure.statusFieldFromStateSchema(~entityName="Test", schema->S.castToUnknown)
@@ -5,10 +5,12 @@ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
5
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
6
  import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
7
7
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
8
9
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
9
10
  import * as DateTime$Reventless from "@reventlessdev/reventless-spec/src/types/DateTime.res.mjs";
10
11
  import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
11
12
  import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
13
+ import * as Api_Naming$ReventlessCore from "../../src/components/Api/Api_Naming.res.mjs";
12
14
  import * as PsShipOrder$ReventlessCore from "./StateChangeSlice/PsShipOrder.res.mjs";
13
15
  import * as PsOrdersView$ReventlessCore from "./StateViewSlice/PsOrdersView.res.mjs";
14
16
  import * as PsPlaceOrder$ReventlessCore from "./StateChangeSlice/PsPlaceOrder.res.mjs";
@@ -20,6 +22,7 @@ import * as PsAnnotatedView$ReventlessCore from "./StateViewSlice/PsAnnotatedVie
20
22
  import * as PsAttachInvoice$ReventlessCore from "./StateChangeSlice/PsAttachInvoice.res.mjs";
21
23
  import * as PsCustomersView$ReventlessCore from "./StateViewSlice/PsCustomersView.res.mjs";
22
24
  import * as Plugin_Structure$ReventlessCore from "../../src/plugin/component/Plugin_Structure.res.mjs";
25
+ import * as PsCategoriesView$ReventlessCore from "./StateViewSlice/PsCategoriesView.res.mjs";
23
26
  import * as Capability_Inference$ReventlessCore from "../../src/plugin/component/Capability_Inference.res.mjs";
24
27
  import * as PsAvailableProductsView$ReventlessCore from "./StateViewSlice/PsAvailableProductsView.res.mjs";
25
28
 
@@ -187,6 +190,32 @@ let PsAnnotatedViewSlice = {
187
190
  make: make$5
188
191
  };
189
192
 
193
+ let Projection$4 = {
194
+ project: PsCategoriesView$ReventlessCore.project,
195
+ moduleUrl: PsCategoriesView$ReventlessCore.moduleUrl
196
+ };
197
+
198
+ function make$6(param, param$1, param$2) {
199
+ return 0;
200
+ }
201
+
202
+ let PsCategoriesViewSlice_Spec = {
203
+ name: PsCategoriesView$ReventlessCore.name,
204
+ moduleUrl: PsCategoriesView$ReventlessCore.moduleUrl,
205
+ stateSchema: PsCategoriesView$ReventlessCore.stateSchema,
206
+ consumedEventSchema: PsCategoriesView$ReventlessCore.consumedEventSchema,
207
+ config: PsCategoriesView$ReventlessCore.config,
208
+ subIdConfig: undefined,
209
+ authorization: PsCategoriesView$ReventlessCore.authorization,
210
+ visibility: PsCategoriesView$ReventlessCore.visibility
211
+ };
212
+
213
+ let PsCategoriesViewSlice = {
214
+ Spec: PsCategoriesViewSlice_Spec,
215
+ Projection: Projection$4,
216
+ make: make$6
217
+ };
218
+
190
219
  let Behavior$2 = {
191
220
  initialState: false,
192
221
  evolve: PsAttachInvoice$ReventlessCore.evolve,
@@ -194,7 +223,7 @@ let Behavior$2 = {
194
223
  moduleUrl: PsAttachInvoice$ReventlessCore.moduleUrl
195
224
  };
196
225
 
197
- function make$6(param, param$1, param$2, param$3, param$4, param$5) {
226
+ function make$7(param, param$1, param$2, param$3, param$4, param$5) {
198
227
  return 0;
199
228
  }
200
229
 
@@ -214,7 +243,7 @@ let PsAttachInvoiceSlice = {
214
243
  Spec: PsAttachInvoiceSlice_Spec,
215
244
  Behavior: Behavior$2,
216
245
  isAsync: false,
217
- make: make$6
246
+ make: make$7
218
247
  };
219
248
 
220
249
  let Behavior$3 = {
@@ -224,7 +253,7 @@ let Behavior$3 = {
224
253
  moduleUrl: PsUploadAvatar$ReventlessCore.moduleUrl
225
254
  };
226
255
 
227
- function make$7(param, param$1, param$2, param$3, param$4, param$5) {
256
+ function make$8(param, param$1, param$2, param$3, param$4, param$5) {
228
257
  return 0;
229
258
  }
230
259
 
@@ -244,7 +273,7 @@ let PsUploadAvatarSlice = {
244
273
  Spec: PsUploadAvatarSlice_Spec,
245
274
  Behavior: Behavior$3,
246
275
  isAsync: false,
247
- make: make$7
276
+ make: make$8
248
277
  };
249
278
 
250
279
  let Behavior$4 = {
@@ -254,7 +283,7 @@ let Behavior$4 = {
254
283
  moduleUrl: PsChangePhoto$ReventlessCore.moduleUrl
255
284
  };
256
285
 
257
- function make$8(param, param$1, param$2, param$3, param$4, param$5) {
286
+ function make$9(param, param$1, param$2, param$3, param$4, param$5) {
258
287
  return 0;
259
288
  }
260
289
 
@@ -274,7 +303,7 @@ let PsChangePhotoSlice = {
274
303
  Spec: PsChangePhotoSlice_Spec,
275
304
  Behavior: Behavior$4,
276
305
  isAsync: false,
277
- make: make$8
306
+ make: make$9
278
307
  };
279
308
 
280
309
  let Behavior$5 = {
@@ -284,7 +313,7 @@ let Behavior$5 = {
284
313
  moduleUrl: PsReserveStock$ReventlessCore.moduleUrl
285
314
  };
286
315
 
287
- function make$9(param, param$1, param$2, param$3, param$4, param$5) {
316
+ function make$10(param, param$1, param$2, param$3, param$4, param$5) {
288
317
  return 0;
289
318
  }
290
319
 
@@ -304,7 +333,7 @@ let PsReserveStockSlice = {
304
333
  Spec: PsReserveStockSlice_Spec,
305
334
  Behavior: Behavior$5,
306
335
  isAsync: false,
307
- make: make$9
336
+ make: make$10
308
337
  };
309
338
 
310
339
  let structure = Plugin_Structure$ReventlessCore.make("TestPlugin", undefined, undefined, [
@@ -709,6 +738,54 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
709
738
  globalThis.expect(stated).toEqual(true);
710
739
  });
711
740
  });
741
+ globalThis.describe("singleQueryField — the singular the schema actually serves", () => {
742
+ let withIrregularPlural = Plugin_Structure$ReventlessCore.make("TestPlugin", undefined, undefined, [PsCategoriesViewSlice], [], undefined, undefined, undefined, undefined, undefined, undefined);
743
+ let categories = withIrregularPlural.stateViewSlices[0];
744
+ globalThis.test("an `-ies` plural publishes the `y` singular, not a truncation", () => {
745
+ globalThis.expect([
746
+ categories.queryField,
747
+ categories.singleQueryField
748
+ ]).toEqual([
749
+ "TestPlugin_Categories",
750
+ "TestPlugin_Category"
751
+ ]);
752
+ });
753
+ globalThis.test("every built def states the name Api_Naming returned", () => {
754
+ let stated = structure.stateViewSlices.every(q => Primitive_object.equal(q.singleQueryField, Api_Naming$ReventlessCore.queryFieldNamesForStateView("TestPlugin", q.name, undefined).singleFieldName));
755
+ globalThis.expect(stated).toEqual(true);
756
+ });
757
+ });
758
+ globalThis.describe("idField / idFieldSource — the key and its provenance", () => {
759
+ let keyOf = q => [
760
+ q.idField,
761
+ q.idFieldSource
762
+ ];
763
+ let withCategories = Plugin_Structure$ReventlessCore.make("TestPlugin", undefined, undefined, [PsCategoriesViewSlice], [], undefined, undefined, undefined, undefined, undefined, undefined);
764
+ globalThis.test("a name-matching `*Id` field is a guess the consumer can also make", () => {
765
+ globalThis.expect(keyOf(structure.stateViewSlices[0])).toEqual([
766
+ "orderId",
767
+ "convention"
768
+ ]);
769
+ });
770
+ globalThis.test("a sole `*Id` field is a fact only this side can see", () => {
771
+ globalThis.expect(keyOf(structure.stateViewSlices[1])).toEqual([
772
+ "productId",
773
+ "sole"
774
+ ]);
775
+ });
776
+ globalThis.test("an `-ies` component name singularises to its key", () => {
777
+ globalThis.expect(keyOf(withCategories.stateViewSlices[0])).toEqual([
778
+ "categoryId",
779
+ "convention"
780
+ ]);
781
+ });
782
+ globalThis.test("a declared @id is used where inference would decline", () => {
783
+ globalThis.expect(keyOf(structure.stateViewSlices[3])).toEqual([
784
+ "itemId",
785
+ "annotation"
786
+ ]);
787
+ });
788
+ });
712
789
  globalThis.describe("statusField — the shape rule", () => {
713
790
  globalThis.test("a `status` field holding a closed set of values is the lifecycle field", () => {
714
791
  let schema = S.schema(s => ({
@@ -1046,6 +1123,7 @@ export {
1046
1123
  PsAvailableProductsViewSlice,
1047
1124
  PsCustomersViewSlice,
1048
1125
  PsAnnotatedViewSlice,
1126
+ PsCategoriesViewSlice,
1049
1127
  PsAttachInvoiceSlice,
1050
1128
  PsUploadAvatarSlice,
1051
1129
  PsChangePhotoSlice,
@@ -0,0 +1,17 @@
1
+ // Test fixture for the published singular query field.
2
+ // "Categories" is the name a naive "strip a trailing s" derivation gets wrong
3
+ // (`Categorie`), so it is the one that proves `singleQueryField` comes from
4
+ // `Api_Naming` rather than from a rule re-invented at the consumer.
5
+
6
+ @@reventless.spec("Categories")
7
+
8
+ @schema
9
+ type consumedEvent = CategoryAdded({categoryId: string, name: string})
10
+
11
+ @schema
12
+ type state = {categoryId: string, name: string}
13
+
14
+ let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
15
+ switch event {
16
+ | CategoryAdded({categoryId, name}) => [Set(categoryId, {categoryId, name})]
17
+ }
@@ -0,0 +1,57 @@
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 ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
6
+
7
+ let consumedEventSchema = S.schema(s => ({
8
+ TAG: "CategoryAdded",
9
+ categoryId: s.m(DcbTag$Reventless.string),
10
+ name: s.m(S.string)
11
+ }));
12
+
13
+ let stateSchema = S.schema(s => ({
14
+ categoryId: s.m(S.string),
15
+ name: s.m(S.string)
16
+ }));
17
+
18
+ function project(param) {
19
+ let event = param.event;
20
+ let categoryId = event.categoryId;
21
+ return [{
22
+ TAG: "Set",
23
+ _0: categoryId,
24
+ _1: {
25
+ categoryId: categoryId,
26
+ name: event.name
27
+ }
28
+ }];
29
+ }
30
+
31
+ let config = ReadModel$Reventless.config(undefined, undefined, undefined);
32
+
33
+ let name = "Categories";
34
+
35
+ let Id;
36
+
37
+ let subIdConfig;
38
+
39
+ let moduleUrl = "@reventlessdev/reventless-core/tests/plugin/StateViewSlice/PsCategoriesView.res.mjs";
40
+
41
+ let authorization = "AllowAuthenticated";
42
+
43
+ let visibility = "Public";
44
+
45
+ export {
46
+ name,
47
+ Id,
48
+ consumedEventSchema,
49
+ stateSchema,
50
+ project,
51
+ config,
52
+ subIdConfig,
53
+ moduleUrl,
54
+ authorization,
55
+ visibility,
56
+ }
57
+ /* consumedEventSchema Not a pure module */