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

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,13 @@
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.133 (2026-07-09)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **reventless-core:** DCB StateChangeSlice mutations use ${plugin}_${command}, not ${plugin}_${slice}_${command} ([c5874d2](https://github.com/ReventlessDev/reventless-core/commit/c5874d27ad63e6af5c639c8b6c6f8596fd7f682b))
11
+
12
+
6
13
  # 3.0.0-alpha.132 (2026-07-09)
7
14
 
8
15
  ### 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.133",
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": {
@@ -33,14 +33,14 @@
33
33
  "ws": "^8.18.0",
34
34
  "@reventlessdev/rescript-effect": "0.1.0-alpha.25",
35
35
  "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.18",
36
- "@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",
38
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.6",
39
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.94",
41
40
  "@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"
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.69",
42
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.13",
43
+ "@reventlessdev/reventless-core": "3.0.0-alpha.149"
44
44
  },
45
45
  "devDependencies": {
46
46
  "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})
@@ -802,21 +802,22 @@ describe("DCB StateChangeSlice — one mutation per command constructor", () =>
802
802
  expect(fieldNames)->toEqual(["Catalog_AddCategory"])
803
803
  })
804
804
 
805
- testPromise("multi-command slice emits one field per constructor", async () => {
805
+ testPromise("multi-command slice emits one `${plugin}_${command}` field per constructor", async () => {
806
+ // The command constructor already names the operation; callers/plans expect
807
+ // `${plugin}_${command}` (e.g. Platform_SyncComponent / Platform_RemoveComponent),
808
+ // NOT the aggregate-style `${plugin}_${slice}_${command}` doubling — which both
809
+ // renamed the primary command and overflowed AppSync's subscription cap.
806
810
  let fieldNames =
807
811
  ReventlessCore.Api_Naming.sliceMutationFields(
808
812
  ~plugin="Ordering",
809
813
  ~slice="SyncCatalogProduct",
810
814
  ~commandSchema=dcbMultiCommandSchema->S.castToUnknown,
811
815
  )->Array.map(((f, _)) => f)
812
- expect(fieldNames)->toEqual([
813
- "Ordering_SyncCatalogProduct_SyncNewProduct",
814
- "Ordering_SyncCatalogProduct_ChangeSyncedPrice",
815
- ])
816
+ expect(fieldNames)->toEqual(["Ordering_SyncNewProduct", "Ordering_ChangeSyncedPrice"])
816
817
  })
817
818
 
818
819
  testPromise(
819
- "multi-command slice: both mutations carry their own args, plus both subscriptions",
820
+ "multi-command slice: both mutations carry their own args, plus both subscriptions (each ≤ 50 chars)",
820
821
  async () => {
821
822
  let fieldNames =
822
823
  ReventlessCore.Api_Naming.sliceMutationFields(
@@ -834,12 +835,12 @@ describe("DCB StateChangeSlice — one mutation per command constructor", () =>
834
835
  // Each constructor's mutation carries that constructor's own argument shape.
835
836
  expect(
836
837
  sdl->String.includes(
837
- "Ordering_SyncCatalogProduct_SyncNewProduct(id: ID!, productId: ID!, name: String!, price: Float!)",
838
+ "Ordering_SyncNewProduct(id: ID!, productId: ID!, name: String!, price: Float!)",
838
839
  ),
839
840
  )->toBe(true)
840
841
  expect(
841
842
  sdl->String.includes(
842
- "Ordering_SyncCatalogProduct_ChangeSyncedPrice(id: ID!, productId: ID!, price: Float!)",
843
+ "Ordering_ChangeSyncedPrice(id: ID!, productId: ID!, price: Float!)",
843
844
  ),
844
845
  )->toBe(true)
845
846
 
@@ -849,8 +850,38 @@ describe("DCB StateChangeSlice — one mutation per command constructor", () =>
849
850
  ~eventLogEntries=[],
850
851
  )
851
852
  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)
853
+ expect(subSdl->String.includes("onOrdering_SyncNewProduct"))->toBe(true)
854
+ expect(subSdl->String.includes("onOrdering_ChangeSyncedPrice"))->toBe(true)
855
+
856
+ // Every emitted subscription field name stays within AppSync's 50-char cap.
857
+ subs.subscriptionFields->Array.forEach(sub => {
858
+ // `sub` is the full SDL line ` on<Field>(id: ID): CommandResult\n ...`;
859
+ // extract the leading `on<Field>` token to length-check the field name itself.
860
+ let name =
861
+ sub->String.trim->String.split("(")->Array.getUnsafe(0)
862
+ expect(name->String.length <= 50)->toBe(true)
863
+ })
864
+ },
865
+ )
866
+
867
+ testPromise(
868
+ "regression: a slice whose plugin+command overflow the 50-char cap fails at build, not at AppSync",
869
+ async () => {
870
+ // `on` + `Platform_SyncExtensionWiring_RemoveExtensionWiring` (the old doubled
871
+ // shape) was 52 chars → AppSync rejected the schema push with an opaque 500.
872
+ // A field long enough to overflow must now throw an actionable build-time error.
873
+ let longPlugin = "PlatformIntegrationOrchestration"
874
+ let field = ReventlessCore.Api_Naming.dcbCommandMutationField(
875
+ ~plugin=longPlugin,
876
+ ~command="RemoveExtensionWiring",
877
+ )
878
+ let raised = try {
879
+ let _ = ReventlessCore.Api_Naming.assertSubscriptionNameFits(~fieldName=field)
880
+ false
881
+ } catch {
882
+ | JsExn(_) => true
883
+ }
884
+ expect(raised)->toBe(true)
854
885
  },
855
886
  )
856
887
  })
@@ -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";
@@ -578,29 +579,49 @@ globalThis.describe("DCB StateChangeSlice — one mutation per command construct
578
579
  let fieldNames = Api_Naming$ReventlessCore.sliceMutationFields("Catalog", "AddCategory", dcbSingleCommandSchema).map(param => param[0]);
579
580
  globalThis.expect(fieldNames).toEqual(["Catalog_AddCategory"]);
580
581
  });
581
- globalThis.test("multi-command slice emits one field per constructor", async () => {
582
+ globalThis.test("multi-command slice emits one `${plugin}_${command}` field per constructor", async () => {
582
583
  let fieldNames = Api_Naming$ReventlessCore.sliceMutationFields("Ordering", "SyncCatalogProduct", dcbMultiCommandSchema).map(param => param[0]);
583
584
  globalThis.expect(fieldNames).toEqual([
584
- "Ordering_SyncCatalogProduct_SyncNewProduct",
585
- "Ordering_SyncCatalogProduct_ChangeSyncedPrice"
585
+ "Ordering_SyncNewProduct",
586
+ "Ordering_ChangeSyncedPrice"
586
587
  ]);
587
588
  });
588
- globalThis.test("multi-command slice: both mutations carry their own args, plus both subscriptions", async () => {
589
+ globalThis.test("multi-command slice: both mutations carry their own args, plus both subscriptions (each ≤ 50 chars)", async () => {
589
590
  let fieldNames = Api_Naming$ReventlessCore.sliceMutationFields("Ordering", "SyncCatalogProduct", dcbMultiCommandSchema).map(param => param[0]);
590
591
  let fragment = GraphQL_FragmentGenerator$ReventlessCore.generate([{
591
592
  fieldNames: fieldNames,
592
593
  commandSchema: dcbMultiCommandSchema
593
594
  }], []);
594
595
  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);
596
+ globalThis.expect(sdl.includes("Ordering_SyncNewProduct(id: ID!, productId: ID!, name: String!, price: Float!)")).toBe(true);
597
+ globalThis.expect(sdl.includes("Ordering_ChangeSyncedPrice(id: ID!, productId: ID!, price: Float!)")).toBe(true);
597
598
  let subs = Plugin_SubscriptionSchema$ReventlessCore.generate([{
598
599
  fieldNames: fieldNames,
599
600
  commandSchema: dcbMultiCommandSchema
600
601
  }], []);
601
602
  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);
603
+ globalThis.expect(subSdl.includes("onOrdering_SyncNewProduct")).toBe(true);
604
+ globalThis.expect(subSdl.includes("onOrdering_ChangeSyncedPrice")).toBe(true);
605
+ subs.subscriptionFields.forEach(sub => {
606
+ let name = sub.trim().split("(")[0];
607
+ globalThis.expect(name.length <= 50).toBe(true);
608
+ });
609
+ });
610
+ globalThis.test("regression: a slice whose plugin+command overflow the 50-char cap fails at build, not at AppSync", async () => {
611
+ let field = Api_Naming$ReventlessCore.dcbCommandMutationField("PlatformIntegrationOrchestration", "RemoveExtensionWiring");
612
+ let raised;
613
+ try {
614
+ Api_Naming$ReventlessCore.assertSubscriptionNameFits(field);
615
+ raised = false;
616
+ } catch (raw_exn) {
617
+ let exn = Primitive_exceptions.internalToException(raw_exn);
618
+ if (exn.RE_EXN_ID === "JsExn") {
619
+ raised = true;
620
+ } else {
621
+ throw exn;
622
+ }
623
+ }
624
+ globalThis.expect(raised).toBe(true);
604
625
  });
605
626
  });
606
627