@reventlessdev/reventless-local 3.0.0-alpha.132 → 3.0.0-alpha.134

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 CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.134 (2026-07-10)
7
+
8
+ ### Features
9
+
10
+ * **reventless-ppx:** add [@group](https://github.com/group)By state-field annotation → x-reventless-group-by ([9e7a7b2](https://github.com/ReventlessDev/reventless-core/commit/9e7a7b29e54fb4eda1c6a145c0e7b6dcf26940ee))
11
+
12
+
13
+ # 3.0.0-alpha.133 (2026-07-09)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **reventless-core:** DCB StateChangeSlice mutations use ${plugin}_${command}, not ${plugin}_${slice}_${command} ([c5874d2](https://github.com/ReventlessDev/reventless-core/commit/c5874d27ad63e6af5c639c8b6c6f8596fd7f682b))
18
+
19
+
6
20
  # 3.0.0-alpha.132 (2026-07-09)
7
21
 
8
22
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-local",
3
- "version": "3.0.0-alpha.132",
3
+ "version": "3.0.0-alpha.134",
4
4
  "description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -32,20 +32,20 @@
32
32
  "sury": "11.0.0-alpha.4",
33
33
  "ws": "^8.18.0",
34
34
  "@reventlessdev/rescript-effect": "0.1.0-alpha.25",
35
- "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.18",
36
35
  "@reventlessdev/rescript-jest": "1.0.0-alpha.6",
37
36
  "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.17",
38
37
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
39
- "@reventlessdev/reventless-core": "3.0.0-alpha.148",
40
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.93",
41
- "@reventlessdev/reventless-infra": "3.0.0-alpha.91",
42
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.12",
43
- "@reventlessdev/reventless-spec": "3.0.0-alpha.69"
38
+ "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.18",
39
+ "@reventlessdev/reventless-core": "3.0.0-alpha.150",
40
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.92",
41
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.95",
42
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.14",
43
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.70"
44
44
  },
45
45
  "devDependencies": {
46
46
  "rescript": "^12.3.0",
47
47
  "sury-ppx": "11.0.0-alpha.2",
48
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.51"
48
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.52"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "rescript": "^12.3.0"
@@ -239,7 +239,7 @@ let registerDcb = (
239
239
  ensureCommandResultTypes(server)
240
240
  // Derive the constructor from the field name's trailing `_` segment (mirrors the
241
241
  // aggregate resolver). A single-command slice's `Plugin_Slice` resolves to its sole
242
- // constructor (slice name == constructor); a multi-command slice's `Plugin_Slice_Ctor`
242
+ // constructor (slice name == constructor); a multi-command slice's `Plugin_Ctor`
243
243
  // resolves to `Ctor`. Position 0 was previously hardcoded, which dropped every
244
244
  // non-first constructor of a multi-command slice.
245
245
  let constructorNames = Reventless.DcbTag.extractAllVariantNames(commandSchema->Obj.magic)
@@ -41,7 +41,7 @@ type unionCommand =
41
41
  // DCB StateChangeSlice command shapes for the per-constructor mutation fan-out.
42
42
  // A single-variant union models a single-command slice (must keep the byte-identical
43
43
  // `Plugin_Slice` field); a multi-variant union models a multi-command slice (must
44
- // expose one `Plugin_Slice_Ctor` field per constructor).
44
+ // expose one `Plugin_Command` field per constructor).
45
45
  @schema
46
46
  type dcbSingleCommand =
47
47
  | AddCategory({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
@@ -85,6 +85,7 @@ let indexedStateSchemaWithAnnotations = indexedStateSchema->S.Metadata.set(
85
85
  scan: [],
86
86
  scanSort: [],
87
87
  status: None,
88
+ groupBy: None,
88
89
  visibility: None,
89
90
  },
90
91
  )
@@ -111,6 +112,7 @@ let orderedStateSchemaWithAnnotations = orderedStateSchema->S.Metadata.set(
111
112
  scan: [],
112
113
  scanSort: [],
113
114
  status: None,
115
+ groupBy: None,
114
116
  visibility: None,
115
117
  },
116
118
  )
@@ -138,6 +140,7 @@ let scanStateSchemaWithAnnotations = scanStateSchema->S.Metadata.set(
138
140
  scan: ["status"],
139
141
  scanSort: ["name"],
140
142
  status: None,
143
+ groupBy: None,
141
144
  visibility: None,
142
145
  },
143
146
  )
@@ -802,21 +805,22 @@ describe("DCB StateChangeSlice — one mutation per command constructor", () =>
802
805
  expect(fieldNames)->toEqual(["Catalog_AddCategory"])
803
806
  })
804
807
 
805
- testPromise("multi-command slice emits one field per constructor", async () => {
808
+ testPromise("multi-command slice emits one `${plugin}_${command}` field per constructor", async () => {
809
+ // The command constructor already names the operation; callers/plans expect
810
+ // `${plugin}_${command}` (e.g. Platform_SyncComponent / Platform_RemoveComponent),
811
+ // NOT the aggregate-style `${plugin}_${slice}_${command}` doubling — which both
812
+ // renamed the primary command and overflowed AppSync's subscription cap.
806
813
  let fieldNames =
807
814
  ReventlessCore.Api_Naming.sliceMutationFields(
808
815
  ~plugin="Ordering",
809
816
  ~slice="SyncCatalogProduct",
810
817
  ~commandSchema=dcbMultiCommandSchema->S.castToUnknown,
811
818
  )->Array.map(((f, _)) => f)
812
- expect(fieldNames)->toEqual([
813
- "Ordering_SyncCatalogProduct_SyncNewProduct",
814
- "Ordering_SyncCatalogProduct_ChangeSyncedPrice",
815
- ])
819
+ expect(fieldNames)->toEqual(["Ordering_SyncNewProduct", "Ordering_ChangeSyncedPrice"])
816
820
  })
817
821
 
818
822
  testPromise(
819
- "multi-command slice: both mutations carry their own args, plus both subscriptions",
823
+ "multi-command slice: both mutations carry their own args, plus both subscriptions (each ≤ 50 chars)",
820
824
  async () => {
821
825
  let fieldNames =
822
826
  ReventlessCore.Api_Naming.sliceMutationFields(
@@ -834,12 +838,12 @@ describe("DCB StateChangeSlice — one mutation per command constructor", () =>
834
838
  // Each constructor's mutation carries that constructor's own argument shape.
835
839
  expect(
836
840
  sdl->String.includes(
837
- "Ordering_SyncCatalogProduct_SyncNewProduct(id: ID!, productId: ID!, name: String!, price: Float!)",
841
+ "Ordering_SyncNewProduct(id: ID!, productId: ID!, name: String!, price: Float!)",
838
842
  ),
839
843
  )->toBe(true)
840
844
  expect(
841
845
  sdl->String.includes(
842
- "Ordering_SyncCatalogProduct_ChangeSyncedPrice(id: ID!, productId: ID!, price: Float!)",
846
+ "Ordering_ChangeSyncedPrice(id: ID!, productId: ID!, price: Float!)",
843
847
  ),
844
848
  )->toBe(true)
845
849
 
@@ -849,8 +853,38 @@ describe("DCB StateChangeSlice — one mutation per command constructor", () =>
849
853
  ~eventLogEntries=[],
850
854
  )
851
855
  let subSdl = subs.subscriptionFields->Array.join("\n")
852
- expect(subSdl->String.includes("onOrdering_SyncCatalogProduct_SyncNewProduct"))->toBe(true)
853
- expect(subSdl->String.includes("onOrdering_SyncCatalogProduct_ChangeSyncedPrice"))->toBe(true)
856
+ expect(subSdl->String.includes("onOrdering_SyncNewProduct"))->toBe(true)
857
+ expect(subSdl->String.includes("onOrdering_ChangeSyncedPrice"))->toBe(true)
858
+
859
+ // Every emitted subscription field name stays within AppSync's 50-char cap.
860
+ subs.subscriptionFields->Array.forEach(sub => {
861
+ // `sub` is the full SDL line ` on<Field>(id: ID): CommandResult\n ...`;
862
+ // extract the leading `on<Field>` token to length-check the field name itself.
863
+ let name =
864
+ sub->String.trim->String.split("(")->Array.getUnsafe(0)
865
+ expect(name->String.length <= 50)->toBe(true)
866
+ })
867
+ },
868
+ )
869
+
870
+ testPromise(
871
+ "regression: a slice whose plugin+command overflow the 50-char cap fails at build, not at AppSync",
872
+ async () => {
873
+ // `on` + `Platform_SyncExtensionWiring_RemoveExtensionWiring` (the old doubled
874
+ // shape) was 52 chars → AppSync rejected the schema push with an opaque 500.
875
+ // A field long enough to overflow must now throw an actionable build-time error.
876
+ let longPlugin = "PlatformIntegrationOrchestration"
877
+ let field = ReventlessCore.Api_Naming.dcbCommandMutationField(
878
+ ~plugin=longPlugin,
879
+ ~command="RemoveExtensionWiring",
880
+ )
881
+ let raised = try {
882
+ let _ = ReventlessCore.Api_Naming.assertSubscriptionNameFits(~fieldName=field)
883
+ false
884
+ } catch {
885
+ | JsExn(_) => true
886
+ }
887
+ expect(raised)->toBe(true)
854
888
  },
855
889
  )
856
890
  })
@@ -3,6 +3,7 @@
3
3
  import * as S from "sury/src/S.res.mjs";
4
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
5
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
6
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
6
7
  import * as Api_Naming$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/Api_Naming.res.mjs";
7
8
  import * as TestRunner$ReventlessLocal from "../../src/test/TestRunner.res.mjs";
8
9
  import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
@@ -95,6 +96,7 @@ let indexedStateSchemaWithAnnotations = S.Metadata.set(indexedStateSchema, State
95
96
  scan: [],
96
97
  scanSort: [],
97
98
  status: undefined,
99
+ groupBy: undefined,
98
100
  visibility: undefined
99
101
  });
100
102
 
@@ -118,6 +120,7 @@ let orderedStateSchemaWithAnnotations = S.Metadata.set(orderedStateSchema, State
118
120
  scan: [],
119
121
  scanSort: [],
120
122
  status: undefined,
123
+ groupBy: undefined,
121
124
  visibility: undefined
122
125
  });
123
126
 
@@ -142,6 +145,7 @@ let scanStateSchemaWithAnnotations = S.Metadata.set(scanStateSchema, StateAnnota
142
145
  scan: ["status"],
143
146
  scanSort: ["name"],
144
147
  status: undefined,
148
+ groupBy: undefined,
145
149
  visibility: undefined
146
150
  });
147
151
 
@@ -578,29 +582,49 @@ globalThis.describe("DCB StateChangeSlice — one mutation per command construct
578
582
  let fieldNames = Api_Naming$ReventlessCore.sliceMutationFields("Catalog", "AddCategory", dcbSingleCommandSchema).map(param => param[0]);
579
583
  globalThis.expect(fieldNames).toEqual(["Catalog_AddCategory"]);
580
584
  });
581
- globalThis.test("multi-command slice emits one field per constructor", async () => {
585
+ globalThis.test("multi-command slice emits one `${plugin}_${command}` field per constructor", async () => {
582
586
  let fieldNames = Api_Naming$ReventlessCore.sliceMutationFields("Ordering", "SyncCatalogProduct", dcbMultiCommandSchema).map(param => param[0]);
583
587
  globalThis.expect(fieldNames).toEqual([
584
- "Ordering_SyncCatalogProduct_SyncNewProduct",
585
- "Ordering_SyncCatalogProduct_ChangeSyncedPrice"
588
+ "Ordering_SyncNewProduct",
589
+ "Ordering_ChangeSyncedPrice"
586
590
  ]);
587
591
  });
588
- globalThis.test("multi-command slice: both mutations carry their own args, plus both subscriptions", async () => {
592
+ globalThis.test("multi-command slice: both mutations carry their own args, plus both subscriptions (each ≤ 50 chars)", async () => {
589
593
  let fieldNames = Api_Naming$ReventlessCore.sliceMutationFields("Ordering", "SyncCatalogProduct", dcbMultiCommandSchema).map(param => param[0]);
590
594
  let fragment = GraphQL_FragmentGenerator$ReventlessCore.generate([{
591
595
  fieldNames: fieldNames,
592
596
  commandSchema: dcbMultiCommandSchema
593
597
  }], []);
594
598
  let sdl = GraphQL_SchemaInspector$ReventlessCore.inspectFragment(fragment).sdlPreview;
595
- globalThis.expect(sdl.includes("Ordering_SyncCatalogProduct_SyncNewProduct(id: ID!, productId: ID!, name: String!, price: Float!)")).toBe(true);
596
- globalThis.expect(sdl.includes("Ordering_SyncCatalogProduct_ChangeSyncedPrice(id: ID!, productId: ID!, price: Float!)")).toBe(true);
599
+ globalThis.expect(sdl.includes("Ordering_SyncNewProduct(id: ID!, productId: ID!, name: String!, price: Float!)")).toBe(true);
600
+ globalThis.expect(sdl.includes("Ordering_ChangeSyncedPrice(id: ID!, productId: ID!, price: Float!)")).toBe(true);
597
601
  let subs = Plugin_SubscriptionSchema$ReventlessCore.generate([{
598
602
  fieldNames: fieldNames,
599
603
  commandSchema: dcbMultiCommandSchema
600
604
  }], []);
601
605
  let subSdl = subs.subscriptionFields.join("\n");
602
- globalThis.expect(subSdl.includes("onOrdering_SyncCatalogProduct_SyncNewProduct")).toBe(true);
603
- globalThis.expect(subSdl.includes("onOrdering_SyncCatalogProduct_ChangeSyncedPrice")).toBe(true);
606
+ globalThis.expect(subSdl.includes("onOrdering_SyncNewProduct")).toBe(true);
607
+ globalThis.expect(subSdl.includes("onOrdering_ChangeSyncedPrice")).toBe(true);
608
+ subs.subscriptionFields.forEach(sub => {
609
+ let name = sub.trim().split("(")[0];
610
+ globalThis.expect(name.length <= 50).toBe(true);
611
+ });
612
+ });
613
+ globalThis.test("regression: a slice whose plugin+command overflow the 50-char cap fails at build, not at AppSync", async () => {
614
+ let field = Api_Naming$ReventlessCore.dcbCommandMutationField("PlatformIntegrationOrchestration", "RemoveExtensionWiring");
615
+ let raised;
616
+ try {
617
+ Api_Naming$ReventlessCore.assertSubscriptionNameFits(field);
618
+ raised = false;
619
+ } catch (raw_exn) {
620
+ let exn = Primitive_exceptions.internalToException(raw_exn);
621
+ if (exn.RE_EXN_ID === "JsExn") {
622
+ raised = true;
623
+ } else {
624
+ throw exn;
625
+ }
626
+ }
627
+ globalThis.expect(raised).toBe(true);
604
628
  });
605
629
  });
606
630
 
@@ -36,6 +36,7 @@ let rowStateSchemaWithAnnotations =
36
36
  scan: ["status"],
37
37
  scanSort: ["name"],
38
38
  status: None,
39
+ groupBy: None,
39
40
  visibility: None,
40
41
  },
41
42
  )
@@ -44,6 +44,7 @@ let rowStateSchemaWithAnnotations = S.Metadata.set(rowStateSchema, StateAnnotati
44
44
  scan: ["status"],
45
45
  scanSort: ["name"],
46
46
  status: undefined,
47
+ groupBy: undefined,
47
48
  visibility: undefined
48
49
  });
49
50