@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 +14 -0
- package/package.json +8 -8
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +1 -1
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +45 -11
- package/tests/adapter/GraphQL_SchemaInspectorTest.res.mjs +32 -8
- package/tests/adapter/QueryDbListResolverTest.res +1 -0
- package/tests/adapter/QueryDbListResolverTest.res.mjs +1 -0
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.
|
|
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/
|
|
40
|
-
"@reventlessdev/reventless-
|
|
41
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
42
|
-
"@reventlessdev/reventless-
|
|
43
|
-
"@reventlessdev/reventless-
|
|
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.
|
|
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 `
|
|
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 `
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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("
|
|
853
|
-
expect(subSdl->String.includes("
|
|
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
|
-
"
|
|
585
|
-
"
|
|
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("
|
|
596
|
-
globalThis.expect(sdl.includes("
|
|
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("
|
|
603
|
-
globalThis.expect(subSdl.includes("
|
|
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
|
|