@reventlessdev/reventless-gwt 1.0.0-alpha.74
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 +698 -0
- package/LICENSE +202 -0
- package/bin/reventless-dev.mjs +21 -0
- package/package.json +54 -0
- package/rescript.json +36 -0
- package/src/.gwtignore +10 -0
- package/src/Automation_GWT.res +207 -0
- package/src/Automation_GWT.res.mjs +160 -0
- package/src/Behavior_GWT.res +603 -0
- package/src/Behavior_GWT.res.mjs +426 -0
- package/src/Bind.res +29 -0
- package/src/Bind.res.mjs +22 -0
- package/src/BuildClassifier.res +137 -0
- package/src/BuildClassifier.res.mjs +136 -0
- package/src/Cancellation.res +31 -0
- package/src/Cancellation.res.mjs +45 -0
- package/src/ChildProcess.res +84 -0
- package/src/ChildProcess.res.mjs +92 -0
- package/src/Cli.res +671 -0
- package/src/Cli.res.mjs +754 -0
- package/src/Collector.res +201 -0
- package/src/Collector.res.mjs +215 -0
- package/src/ComponentMeta.res +104 -0
- package/src/ComponentMeta.res.mjs +120 -0
- package/src/ComponentScan.res +103 -0
- package/src/ComponentScan.res.mjs +105 -0
- package/src/Delegate_GWT.res +303 -0
- package/src/Delegate_GWT.res.mjs +372 -0
- package/src/Diff.res +91 -0
- package/src/Diff.res.mjs +98 -0
- package/src/Discovery.res +105 -0
- package/src/Discovery.res.mjs +103 -0
- package/src/DomainDeadCode.res +93 -0
- package/src/DomainDeadCode.res.mjs +69 -0
- package/src/DomainGraph.res +0 -0
- package/src/DomainGraph.res.mjs +0 -0
- package/src/EventMapping_GWT.res +29 -0
- package/src/EventMapping_GWT.res.mjs +44 -0
- package/src/Filter.res +36 -0
- package/src/Filter.res.mjs +40 -0
- package/src/Flow_GWT.res +664 -0
- package/src/Flow_GWT.res.mjs +604 -0
- package/src/FormatterHuman.res +138 -0
- package/src/FormatterHuman.res.mjs +122 -0
- package/src/FormatterJson.res +257 -0
- package/src/FormatterJson.res.mjs +320 -0
- package/src/FormatterJunit.res +93 -0
- package/src/FormatterJunit.res.mjs +90 -0
- package/src/FormatterTap.res +139 -0
- package/src/FormatterTap.res.mjs +149 -0
- package/src/FormatterVsCode.res +362 -0
- package/src/FormatterVsCode.res.mjs +564 -0
- package/src/Hint.res +119 -0
- package/src/Hint.res.mjs +124 -0
- package/src/InboundTranslation_GWT.res +83 -0
- package/src/InboundTranslation_GWT.res.mjs +94 -0
- package/src/JestBind.res +70 -0
- package/src/JestBind.res.mjs +63 -0
- package/src/Loader.res +24 -0
- package/src/Loader.res.mjs +23 -0
- package/src/LocalHost.res +135 -0
- package/src/LocalHost.res.mjs +127 -0
- package/src/Mapping_GWT.res +474 -0
- package/src/Mapping_GWT.res.mjs +322 -0
- package/src/MultiSourceProjection_GWT.res +316 -0
- package/src/MultiSourceProjection_GWT.res.mjs +354 -0
- package/src/OutboundTranslation_GWT.res +238 -0
- package/src/OutboundTranslation_GWT.res.mjs +186 -0
- package/src/Outcome.res +161 -0
- package/src/Outcome.res.mjs +140 -0
- package/src/PackageScan.res +80 -0
- package/src/PackageScan.res.mjs +81 -0
- package/src/PlatformRunner.res +160 -0
- package/src/PlatformRunner.res.mjs +155 -0
- package/src/PlatformScan.res +138 -0
- package/src/PlatformScan.res.mjs +131 -0
- package/src/ProcessManager.res +91 -0
- package/src/ProcessManager.res.mjs +80 -0
- package/src/Projection_GWT.res +312 -0
- package/src/Projection_GWT.res.mjs +332 -0
- package/src/Query_GWT.res +379 -0
- package/src/Query_GWT.res.mjs +367 -0
- package/src/RenderRescript.res +125 -0
- package/src/RenderRescript.res.mjs +141 -0
- package/src/RunnerTypes.res +68 -0
- package/src/RunnerTypes.res.mjs +60 -0
- package/src/SideEffect_GWT.res +157 -0
- package/src/SideEffect_GWT.res.mjs +113 -0
- package/src/StateChange/ExternalAddCategorySlice.res +25 -0
- package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
- package/src/StateChange/WithFixtures.res +22 -0
- package/src/StateChange/WithFixtures.res.mjs +31 -0
- package/src/StateChange/WithFixtures_Behavior.res +17 -0
- package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
- package/src/StateChange/WithManualOpen.res +23 -0
- package/src/StateChange/WithManualOpen.res.mjs +31 -0
- package/src/StateChange/WithManualOpen_Behavior.res +17 -0
- package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
- package/src/StubRuntime.res +53 -0
- package/src/StubRuntime.res.mjs +54 -0
- package/src/TestFixtures.res +16 -0
- package/src/TestFixtures.res.mjs +34 -0
- package/src/Watch.res +92 -0
- package/src/Watch.res.mjs +83 -0
- package/src/WatcherProbe.res +48 -0
- package/src/WatcherProbe.res.mjs +46 -0
- package/tests/AutomationGwtTest.res +71 -0
- package/tests/AutomationGwtTest.res.mjs +189 -0
- package/tests/BuildClassifierTest.res +94 -0
- package/tests/BuildClassifierTest.res.mjs +132 -0
- package/tests/ComponentMetaTest.res +52 -0
- package/tests/ComponentMetaTest.res.mjs +57 -0
- package/tests/DelegateGwtTest.res +487 -0
- package/tests/DelegateGwtTest.res.mjs +891 -0
- package/tests/DiscoveryTest.res +42 -0
- package/tests/DiscoveryTest.res.mjs +43 -0
- package/tests/DomainDeadCodeTest.res +116 -0
- package/tests/DomainDeadCodeTest.res.mjs +155 -0
- package/tests/DomainGraphTest.res +365 -0
- package/tests/DomainGraphTest.res.mjs +413 -0
- package/tests/FlowAggregateGwtTest.res +169 -0
- package/tests/FlowAggregateGwtTest.res.mjs +269 -0
- package/tests/FlowCrossPluginGwtTest.res +340 -0
- package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
- package/tests/FlowGwtTest.res +222 -0
- package/tests/FlowGwtTest.res.mjs +432 -0
- package/tests/FormatterVsCodeMessageTest.res +41 -0
- package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
- package/tests/InboundTranslationGwtTest.res +34 -0
- package/tests/InboundTranslationGwtTest.res.mjs +98 -0
- package/tests/LocalHostIntegration.res +70 -0
- package/tests/LocalHostIntegration.res.mjs +64 -0
- package/tests/LocalHostTest.res +71 -0
- package/tests/LocalHostTest.res.mjs +63 -0
- package/tests/MappingGwtTest.res +348 -0
- package/tests/MappingGwtTest.res.mjs +592 -0
- package/tests/OutboundTranslationGwtTest.res +59 -0
- package/tests/OutboundTranslationGwtTest.res.mjs +138 -0
- package/tests/PlatformRunnerTest.res +66 -0
- package/tests/PlatformRunnerTest.res.mjs +85 -0
- package/tests/PlatformScanTest.res +61 -0
- package/tests/PlatformScanTest.res.mjs +56 -0
- package/tests/QueryGwtTest.res +161 -0
- package/tests/QueryGwtTest.res.mjs +314 -0
- package/tests/QueryResolverGwtTest.res +71 -0
- package/tests/QueryResolverGwtTest.res.mjs +136 -0
- package/tests/RunnerUnitTest.res +219 -0
- package/tests/RunnerUnitTest.res.mjs +244 -0
- package/tests/SideEffect/FakeOrderNotification.res +23 -0
- package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
- package/tests/SideEffect/MockEmail.res +18 -0
- package/tests/SideEffect/MockEmail.res.mjs +47 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
- package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
- package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithFixtures_GWT.res +23 -0
- package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithManualOpen_GWT.res +17 -0
- package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
- package/tests/StateChangeSliceGwtTest.res +160 -0
- package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
- package/tests/StateViewSliceGwtTest.res +64 -0
- package/tests/StateViewSliceGwtTest.res.mjs +161 -0
- package/tests/WatchDebounceTest.res +78 -0
- package/tests/WatchDebounceTest.res.mjs +97 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as FormatterVsCode$ReventlessGwt from "../src/FormatterVsCode.res.mjs";
|
|
4
|
+
|
|
5
|
+
function failingResult(mismatch) {
|
|
6
|
+
return {
|
|
7
|
+
id: "t1",
|
|
8
|
+
name: "rejects when category already exists",
|
|
9
|
+
describePath: [],
|
|
10
|
+
status: "Fail",
|
|
11
|
+
durationMs: 1.0,
|
|
12
|
+
location: undefined,
|
|
13
|
+
slice: undefined,
|
|
14
|
+
mismatch: mismatch,
|
|
15
|
+
skipReason: undefined
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
globalThis.describe("FormatterVsCode.messagePayload", () => {
|
|
20
|
+
globalThis.test("includes the mismatch kind for an EventsMismatch", async () => {
|
|
21
|
+
let m = FormatterVsCode$ReventlessGwt.messagePayload(failingResult({
|
|
22
|
+
TAG: "EventsMismatch",
|
|
23
|
+
expected: [],
|
|
24
|
+
actual: []
|
|
25
|
+
}));
|
|
26
|
+
globalThis.expect(m.kind).toEqual("EventsMismatch");
|
|
27
|
+
});
|
|
28
|
+
globalThis.test("includes the mismatch kind for an ErrorMismatch", async () => {
|
|
29
|
+
let m = FormatterVsCode$ReventlessGwt.messagePayload(failingResult({
|
|
30
|
+
TAG: "ErrorMismatch",
|
|
31
|
+
expected: "CategoryAlreadyExists",
|
|
32
|
+
actual: undefined,
|
|
33
|
+
actualEvents: []
|
|
34
|
+
}));
|
|
35
|
+
globalThis.expect(m.kind).toEqual("ErrorMismatch");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
failingResult,
|
|
41
|
+
}
|
|
42
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Worked example for InboundTranslationSlice_GWT — a payment webhook that
|
|
2
|
+
// translates `status` into either a `ConfirmPayment` command or a
|
|
3
|
+
// translation error for unknown statuses.
|
|
4
|
+
|
|
5
|
+
@@reventless.gwt
|
|
6
|
+
|
|
7
|
+
module PaymentWebhookSlice = {
|
|
8
|
+
let name = "PaymentWebhook"
|
|
9
|
+
|
|
10
|
+
@schema
|
|
11
|
+
type externalInput = {paymentId: string, orderId: string, status: string}
|
|
12
|
+
|
|
13
|
+
@schema
|
|
14
|
+
type command = ConfirmPayment({orderId: string, paymentId: string})
|
|
15
|
+
|
|
16
|
+
let translate = input =>
|
|
17
|
+
switch input.status {
|
|
18
|
+
| "completed" =>
|
|
19
|
+
Ok([(input.orderId, ConfirmPayment({orderId: input.orderId, paymentId: input.paymentId}))])
|
|
20
|
+
| _ => Error("Unknown payment status: " ++ input.status)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("PaymentWebhook InboundTranslationSlice", () => {
|
|
25
|
+
test("completed status emits ConfirmPayment", () =>
|
|
26
|
+
whenInput({paymentId: "p1", orderId: "o1", status: "completed"})
|
|
27
|
+
->thenCommand("o1", ConfirmPayment({orderId: "o1", paymentId: "p1"}))
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
test("unknown status surfaces translate error", () =>
|
|
31
|
+
whenInput({paymentId: "p1", orderId: "o1", status: "garbage"})
|
|
32
|
+
->thenTranslateError("Unknown payment status: garbage")
|
|
33
|
+
)
|
|
34
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as InboundTranslation_GWT$ReventlessGwt from "../src/InboundTranslation_GWT.res.mjs";
|
|
5
|
+
|
|
6
|
+
let externalInputSchema = S.schema(s => ({
|
|
7
|
+
paymentId: s.m(S.string),
|
|
8
|
+
orderId: s.m(S.string),
|
|
9
|
+
status: s.m(S.string)
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
let commandSchema = S.schema(s => ({
|
|
13
|
+
TAG: "ConfirmPayment",
|
|
14
|
+
orderId: s.m(S.string),
|
|
15
|
+
paymentId: s.m(S.string)
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
function translate(input) {
|
|
19
|
+
let match = input.status;
|
|
20
|
+
if (match === "completed") {
|
|
21
|
+
return {
|
|
22
|
+
TAG: "Ok",
|
|
23
|
+
_0: [[
|
|
24
|
+
input.orderId,
|
|
25
|
+
{
|
|
26
|
+
TAG: "ConfirmPayment",
|
|
27
|
+
orderId: input.orderId,
|
|
28
|
+
paymentId: input.paymentId
|
|
29
|
+
}
|
|
30
|
+
]]
|
|
31
|
+
};
|
|
32
|
+
} else {
|
|
33
|
+
return {
|
|
34
|
+
TAG: "Error",
|
|
35
|
+
_0: "Unknown payment status: " + input.status
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function commandAuthorization(param) {
|
|
41
|
+
return "AllowAuthenticated";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let PaymentWebhookSlice = {
|
|
45
|
+
name: "PaymentWebhook",
|
|
46
|
+
externalInputSchema: externalInputSchema,
|
|
47
|
+
commandSchema: commandSchema,
|
|
48
|
+
translate: translate,
|
|
49
|
+
commandAuthorization: commandAuthorization
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let include = InboundTranslation_GWT$ReventlessGwt.Make(PaymentWebhookSlice);
|
|
53
|
+
|
|
54
|
+
let describe = include.describe;
|
|
55
|
+
|
|
56
|
+
let test = include.test;
|
|
57
|
+
|
|
58
|
+
let whenInput = include.whenInput;
|
|
59
|
+
|
|
60
|
+
let thenCommand = include.thenCommand;
|
|
61
|
+
|
|
62
|
+
let thenTranslateError = include.thenTranslateError;
|
|
63
|
+
|
|
64
|
+
describe("PaymentWebhook InboundTranslationSlice", () => {
|
|
65
|
+
test("completed status emits ConfirmPayment", () => thenCommand(whenInput({
|
|
66
|
+
paymentId: "p1",
|
|
67
|
+
orderId: "o1",
|
|
68
|
+
status: "completed"
|
|
69
|
+
}), "o1", {
|
|
70
|
+
TAG: "ConfirmPayment",
|
|
71
|
+
orderId: "o1",
|
|
72
|
+
paymentId: "p1"
|
|
73
|
+
}));
|
|
74
|
+
test("unknown status surfaces translate error", () => thenTranslateError(whenInput({
|
|
75
|
+
paymentId: "p1",
|
|
76
|
+
orderId: "o1",
|
|
77
|
+
status: "garbage"
|
|
78
|
+
}), "Unknown payment status: garbage"));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
let Spec = include.Spec;
|
|
82
|
+
|
|
83
|
+
let thenCommands = include.thenCommands;
|
|
84
|
+
|
|
85
|
+
let thenNoCommand = include.thenNoCommand;
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
PaymentWebhookSlice,
|
|
89
|
+
Spec,
|
|
90
|
+
describe,
|
|
91
|
+
test,
|
|
92
|
+
whenInput,
|
|
93
|
+
thenCommands,
|
|
94
|
+
thenCommand,
|
|
95
|
+
thenNoCommand,
|
|
96
|
+
thenTranslateError,
|
|
97
|
+
}
|
|
98
|
+
/* externalInputSchema Not a pure module */
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Integration test for LocalHost.loadGraph — the cold-load path that instantiates
|
|
2
|
+
// reventless-local's Platform and builds the real example plugins against it.
|
|
3
|
+
//
|
|
4
|
+
// Runs under `node --test` (NOT Jest) on purpose: cold-loading the local platform
|
|
5
|
+
// under Jest's experimental-vm-modules hits the graphql/yoga CJS-in-ESM interop
|
|
6
|
+
// wall documented in tests/SplitApiFixtures.res. Plain node ESM is exactly the
|
|
7
|
+
// runtime the CLI host uses, and where the Phase 4.5 spike proved cold-load works
|
|
8
|
+
// (docs/analysis/reventless-vscode-domain-graph-design.md "Spike result"). It is
|
|
9
|
+
// named without the `Test` suffix so the Jest `testMatch` glob
|
|
10
|
+
// (`tests/**/*Test.res.mjs`) skips it — `pnpm run test:host` runs it via node. The
|
|
11
|
+
// pure helpers (name derivation, discovery) are the Jest test in tests/LocalHostTest.res.
|
|
12
|
+
//
|
|
13
|
+
// Prerequisite: reventless-local + the online-shop-aggregates examples must be
|
|
14
|
+
// compiled (`pnpm run build` at the repo root). Run: `pnpm run test:host`.
|
|
15
|
+
|
|
16
|
+
@module("node:test") external test: (string, unit => promise<unit>) => unit = "test"
|
|
17
|
+
@module("node:assert/strict") external equal: ('a, 'a) => unit = "equal"
|
|
18
|
+
@module("node:assert/strict") external deepEqual: ('a, 'a) => unit = "deepEqual"
|
|
19
|
+
@module("node:assert/strict") external ok: (bool, ~message: string=?) => unit = "ok"
|
|
20
|
+
|
|
21
|
+
@module("node:url") external fileURLToPath: string => string = "fileURLToPath"
|
|
22
|
+
@module("node:path") external dirname: string => string = "dirname"
|
|
23
|
+
@module("node:path") @variadic external join: array<string> => string = "join"
|
|
24
|
+
|
|
25
|
+
let here = dirname(fileURLToPath(%raw(`import.meta.url`)))
|
|
26
|
+
let repoRoot = join([here, "..", "..", ".."]) // reventless-gwt/test → repo root
|
|
27
|
+
let catalogDir = join([repoRoot, "examples", "online-shop-aggregates", "catalog"])
|
|
28
|
+
let orderingDir = join([repoRoot, "examples", "online-shop-aggregates", "ordering"])
|
|
29
|
+
let platformPath = join([repoRoot, "reventless", "reventless-local", "src", "Platform.res.mjs"])
|
|
30
|
+
|
|
31
|
+
let structureFor = (g: LocalHost.graph, name) =>
|
|
32
|
+
g.structures->Array.find(((n, _)) => n == name)->Option.map(((_, s)) => s)
|
|
33
|
+
|
|
34
|
+
test("loadGraph cold-loads structures from the real plugins", async () => {
|
|
35
|
+
let plugins = LocalHost.discover(~packageDirs=[catalogDir, orderingDir])
|
|
36
|
+
let g = await LocalHost.loadGraph(~platformModulePath=platformPath, ~plugins)
|
|
37
|
+
|
|
38
|
+
// structures: resolved plain records, plugin-namespaced event types.
|
|
39
|
+
deepEqual(g.structures->Array.map(((n, _)) => n), ["Catalog", "Ordering"])
|
|
40
|
+
|
|
41
|
+
let catalog = structureFor(g, "Catalog")->Option.getOrThrow
|
|
42
|
+
let category = catalog.aggregates->Array.find((a: Reventless.Plugin.writableDef) => a.name == "Category")->Option.getOrThrow
|
|
43
|
+
ok(category.producedEventTypes->Array.includes("Catalog.Added"))
|
|
44
|
+
|
|
45
|
+
let ordering = structureFor(g, "Ordering")->Option.getOrThrow
|
|
46
|
+
let order = ordering.aggregates->Array.find((a: Reventless.Plugin.writableDef) => a.name == "Order")->Option.getOrThrow
|
|
47
|
+
ok(order.producedEventTypes->Array.includes("Ordering.Placed"))
|
|
48
|
+
|
|
49
|
+
// extensionPoints (producer side): Catalog owns Catalog.Products, fed by the
|
|
50
|
+
// Product aggregate's internal events — so the EP's sourceEventTypes are a
|
|
51
|
+
// subset of the producer's producedEventTypes, the link the event graph draws.
|
|
52
|
+
let catalogEps = catalog.extensionPoints->Option.getOr([])
|
|
53
|
+
let productsEp =
|
|
54
|
+
catalogEps->Array.find((e: Reventless.Plugin.extensionPointDef) => e.name == "Catalog.Products")->Option.getOrThrow
|
|
55
|
+
ok(productsEp.delegateNames->Array.includes("Product"), ~message=productsEp.delegateNames->Array.join(","))
|
|
56
|
+
ok(
|
|
57
|
+
productsEp.sourceEventTypes->Array.includes("Catalog.Added"),
|
|
58
|
+
~message=productsEp.sourceEventTypes->Array.join(","),
|
|
59
|
+
)
|
|
60
|
+
let product = catalog.aggregates->Array.find((a: Reventless.Plugin.writableDef) => a.name == "Product")->Option.getOrThrow
|
|
61
|
+
ok(productsEp.sourceEventTypes->Array.every(e => product.producedEventTypes->Array.includes(e)))
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("loadGraph is cold — a second call in the same process succeeds (cache-busting works)", async () => {
|
|
65
|
+
let plugins = LocalHost.discover(~packageDirs=[catalogDir])
|
|
66
|
+
let g1 = await LocalHost.loadGraph(~platformModulePath=platformPath, ~plugins)
|
|
67
|
+
let g2 = await LocalHost.loadGraph(~platformModulePath=platformPath, ~plugins)
|
|
68
|
+
equal(g1.structures->Array.length, 1)
|
|
69
|
+
equal(g2.structures->Array.length, 1)
|
|
70
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Nodeurl from "node:url";
|
|
4
|
+
import * as Nodepath from "node:path";
|
|
5
|
+
import * as Nodetest from "node:test";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Strict from "node:assert/strict";
|
|
8
|
+
import * as LocalHost$ReventlessGwt from "../src/LocalHost.res.mjs";
|
|
9
|
+
|
|
10
|
+
let here = Nodepath.dirname(Nodeurl.fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
let repoRoot = Nodepath.join(here, "..", "..", "..");
|
|
13
|
+
|
|
14
|
+
let catalogDir = Nodepath.join(repoRoot, "examples", "online-shop-aggregates", "catalog");
|
|
15
|
+
|
|
16
|
+
let orderingDir = Nodepath.join(repoRoot, "examples", "online-shop-aggregates", "ordering");
|
|
17
|
+
|
|
18
|
+
let platformPath = Nodepath.join(repoRoot, "reventless", "reventless-local", "src", "Platform.res.mjs");
|
|
19
|
+
|
|
20
|
+
function structureFor(g, name) {
|
|
21
|
+
return Stdlib_Option.map(g.structures.find(param => param[0] === name), param => param[1]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Nodetest.test("loadGraph cold-loads structures from the real plugins", async () => {
|
|
25
|
+
let plugins = LocalHost$ReventlessGwt.discover([
|
|
26
|
+
catalogDir,
|
|
27
|
+
orderingDir
|
|
28
|
+
]);
|
|
29
|
+
let g = await LocalHost$ReventlessGwt.loadGraph(platformPath, plugins);
|
|
30
|
+
Strict.deepEqual(g.structures.map(param => param[0]), [
|
|
31
|
+
"Catalog",
|
|
32
|
+
"Ordering"
|
|
33
|
+
]);
|
|
34
|
+
let catalog = Stdlib_Option.getOrThrow(structureFor(g, "Catalog"), undefined);
|
|
35
|
+
let category = Stdlib_Option.getOrThrow(catalog.aggregates.find(a => a.name === "Category"), undefined);
|
|
36
|
+
Strict.ok(category.producedEventTypes.includes("Catalog.Added"));
|
|
37
|
+
let ordering = Stdlib_Option.getOrThrow(structureFor(g, "Ordering"), undefined);
|
|
38
|
+
let order = Stdlib_Option.getOrThrow(ordering.aggregates.find(a => a.name === "Order"), undefined);
|
|
39
|
+
Strict.ok(order.producedEventTypes.includes("Ordering.Placed"));
|
|
40
|
+
let catalogEps = Stdlib_Option.getOr(catalog.extensionPoints, []);
|
|
41
|
+
let productsEp = Stdlib_Option.getOrThrow(catalogEps.find(e => e.name === "Catalog.Products"), undefined);
|
|
42
|
+
Strict.ok(productsEp.delegateNames.includes("Product"), productsEp.delegateNames.join(","));
|
|
43
|
+
Strict.ok(productsEp.sourceEventTypes.includes("Catalog.Added"), productsEp.sourceEventTypes.join(","));
|
|
44
|
+
let product = Stdlib_Option.getOrThrow(catalog.aggregates.find(a => a.name === "Product"), undefined);
|
|
45
|
+
Strict.ok(productsEp.sourceEventTypes.every(e => product.producedEventTypes.includes(e)));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
Nodetest.test("loadGraph is cold — a second call in the same process succeeds (cache-busting works)", async () => {
|
|
49
|
+
let plugins = LocalHost$ReventlessGwt.discover([catalogDir]);
|
|
50
|
+
let g1 = await LocalHost$ReventlessGwt.loadGraph(platformPath, plugins);
|
|
51
|
+
let g2 = await LocalHost$ReventlessGwt.loadGraph(platformPath, plugins);
|
|
52
|
+
Strict.equal(g1.structures.length, 1);
|
|
53
|
+
Strict.equal(g2.structures.length, 1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
here,
|
|
58
|
+
repoRoot,
|
|
59
|
+
catalogDir,
|
|
60
|
+
orderingDir,
|
|
61
|
+
platformPath,
|
|
62
|
+
structureFor,
|
|
63
|
+
}
|
|
64
|
+
/* here Not a pure module */
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Unit tests for LocalHost's pure helpers — plugin-name derivation + discovery.
|
|
2
|
+
// These touch only the filesystem (no reventless-local import), so they run under
|
|
3
|
+
// the normal Jest suite. The cold-load integration (`loadGraph`, which instantiates
|
|
4
|
+
// reventless-local's Platform) can't run under Jest's experimental-vm-modules — see
|
|
5
|
+
// tests/SplitApiFixtures.res — and lives in test/LocalHostHostTest.res
|
|
6
|
+
// (`pnpm run test:host`, run via `node --test`).
|
|
7
|
+
//
|
|
8
|
+
// Fixtures are a throwaway temp tree (same pattern as DiscoveryTest) so the test is
|
|
9
|
+
// hermetic and doesn't depend on the example plugins being compiled.
|
|
10
|
+
|
|
11
|
+
open JestGlobals
|
|
12
|
+
|
|
13
|
+
@module("node:os") external tmpdir: unit => string = "tmpdir"
|
|
14
|
+
@module("node:path") external join: (string, string) => string = "join"
|
|
15
|
+
|
|
16
|
+
type mkdirOpts = {recursive: bool}
|
|
17
|
+
@module("node:fs/promises")
|
|
18
|
+
external mkdir: (string, mkdirOpts) => promise<Nullable.t<string>> = "mkdir"
|
|
19
|
+
@module("node:fs/promises") external writeFile: (string, string) => promise<unit> = "writeFile"
|
|
20
|
+
type rmOpts = {recursive: bool, force: bool}
|
|
21
|
+
@module("node:fs/promises") external rm: (string, rmOpts) => promise<unit> = "rm"
|
|
22
|
+
|
|
23
|
+
describe("LocalHost.packageNameToPluginName", () => {
|
|
24
|
+
testPromise("PascalCases scoped / dashed / underscored package names", async () => {
|
|
25
|
+
expect(LocalHost.packageNameToPluginName("@scope/my-catalog"))->toBe("MyCatalog")
|
|
26
|
+
expect(LocalHost.packageNameToPluginName("online-shop"))->toBe("OnlineShop")
|
|
27
|
+
expect(
|
|
28
|
+
LocalHost.packageNameToPluginName("online-shop-aggregates-catalog"),
|
|
29
|
+
)->toBe("OnlineShopAggregatesCatalog")
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe("LocalHost name derivation + discovery", () => {
|
|
34
|
+
testPromise(
|
|
35
|
+
"derivePluginName prefers plugin.json then PascalCase(package.json); discover skips non-plugins",
|
|
36
|
+
async () => {
|
|
37
|
+
let root = join(tmpdir(), "reventless-localhost-test")
|
|
38
|
+
let _ = await rm(root, {recursive: true, force: true})
|
|
39
|
+
|
|
40
|
+
// a: explicit plugin.json name + a compiled composition root.
|
|
41
|
+
let a = join(root, "a")
|
|
42
|
+
let aSrc = join(a, "src")
|
|
43
|
+
let _ = await mkdir(aSrc, {recursive: true})
|
|
44
|
+
let _ = await writeFile(join(a, "package.json"), `{"name":"@x/a-pkg"}`)
|
|
45
|
+
let _ = await writeFile(join(aSrc, "plugin.json"), `{"name":"Catalog"}`)
|
|
46
|
+
let _ = await writeFile(join(aSrc, "Plugin.res.mjs"), "")
|
|
47
|
+
|
|
48
|
+
// b: no plugin.json → name falls back to PascalCase(package.json name).
|
|
49
|
+
let b = join(root, "b")
|
|
50
|
+
let bSrc = join(b, "src")
|
|
51
|
+
let _ = await mkdir(bSrc, {recursive: true})
|
|
52
|
+
let _ = await writeFile(join(b, "package.json"), `{"name":"@scope/my-ordering"}`)
|
|
53
|
+
let _ = await writeFile(join(bSrc, "Plugin.res.mjs"), "")
|
|
54
|
+
|
|
55
|
+
// c: has src/ but no Plugin.res.mjs → discover must skip it.
|
|
56
|
+
let c = join(root, "c")
|
|
57
|
+
let _ = await mkdir(join(c, "src"), {recursive: true})
|
|
58
|
+
|
|
59
|
+
expect(LocalHost.derivePluginName(~pluginSrcDir=aSrc))->toBe("Catalog")
|
|
60
|
+
expect(LocalHost.derivePluginName(~pluginSrcDir=bSrc))->toBe("MyOrdering")
|
|
61
|
+
|
|
62
|
+
let refs = LocalHost.discover(~packageDirs=[a, b, c])
|
|
63
|
+
expect(refs->Array.map((r: LocalHost.pluginRef) => r.name))->toEqual(["Catalog", "MyOrdering"])
|
|
64
|
+
let first: LocalHost.pluginRef = refs->Array.getUnsafe(0)
|
|
65
|
+
expect(first.modulePath->String.endsWith("Plugin.res.mjs"))->toBe(true)
|
|
66
|
+
expect(first.packageDir)->toBe(a)
|
|
67
|
+
|
|
68
|
+
let _ = await rm(root, {recursive: true, force: true})
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Nodeos from "node:os";
|
|
4
|
+
import * as Nodepath from "node:path";
|
|
5
|
+
import * as Promises from "node:fs/promises";
|
|
6
|
+
import * as LocalHost$ReventlessGwt from "../src/LocalHost.res.mjs";
|
|
7
|
+
|
|
8
|
+
globalThis.describe("LocalHost.packageNameToPluginName", () => {
|
|
9
|
+
globalThis.test("PascalCases scoped / dashed / underscored package names", async () => {
|
|
10
|
+
globalThis.expect(LocalHost$ReventlessGwt.packageNameToPluginName("@scope/my-catalog")).toBe("MyCatalog");
|
|
11
|
+
globalThis.expect(LocalHost$ReventlessGwt.packageNameToPluginName("online-shop")).toBe("OnlineShop");
|
|
12
|
+
globalThis.expect(LocalHost$ReventlessGwt.packageNameToPluginName("online-shop-aggregates-catalog")).toBe("OnlineShopAggregatesCatalog");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
globalThis.describe("LocalHost name derivation + discovery", () => {
|
|
17
|
+
globalThis.test("derivePluginName prefers plugin.json then PascalCase(package.json); discover skips non-plugins", async () => {
|
|
18
|
+
let root = Nodepath.join(Nodeos.tmpdir(), "reventless-localhost-test");
|
|
19
|
+
await Promises.rm(root, {
|
|
20
|
+
recursive: true,
|
|
21
|
+
force: true
|
|
22
|
+
});
|
|
23
|
+
let a = Nodepath.join(root, "a");
|
|
24
|
+
let aSrc = Nodepath.join(a, "src");
|
|
25
|
+
await Promises.mkdir(aSrc, {
|
|
26
|
+
recursive: true
|
|
27
|
+
});
|
|
28
|
+
await Promises.writeFile(Nodepath.join(a, "package.json"), `{"name":"@x/a-pkg"}`);
|
|
29
|
+
await Promises.writeFile(Nodepath.join(aSrc, "plugin.json"), `{"name":"Catalog"}`);
|
|
30
|
+
await Promises.writeFile(Nodepath.join(aSrc, "Plugin.res.mjs"), "");
|
|
31
|
+
let b = Nodepath.join(root, "b");
|
|
32
|
+
let bSrc = Nodepath.join(b, "src");
|
|
33
|
+
await Promises.mkdir(bSrc, {
|
|
34
|
+
recursive: true
|
|
35
|
+
});
|
|
36
|
+
await Promises.writeFile(Nodepath.join(b, "package.json"), `{"name":"@scope/my-ordering"}`);
|
|
37
|
+
await Promises.writeFile(Nodepath.join(bSrc, "Plugin.res.mjs"), "");
|
|
38
|
+
let c = Nodepath.join(root, "c");
|
|
39
|
+
await Promises.mkdir(Nodepath.join(c, "src"), {
|
|
40
|
+
recursive: true
|
|
41
|
+
});
|
|
42
|
+
globalThis.expect(LocalHost$ReventlessGwt.derivePluginName(aSrc)).toBe("Catalog");
|
|
43
|
+
globalThis.expect(LocalHost$ReventlessGwt.derivePluginName(bSrc)).toBe("MyOrdering");
|
|
44
|
+
let refs = LocalHost$ReventlessGwt.discover([
|
|
45
|
+
a,
|
|
46
|
+
b,
|
|
47
|
+
c
|
|
48
|
+
]);
|
|
49
|
+
globalThis.expect(refs.map(r => r.name)).toEqual([
|
|
50
|
+
"Catalog",
|
|
51
|
+
"MyOrdering"
|
|
52
|
+
]);
|
|
53
|
+
let first = refs[0];
|
|
54
|
+
globalThis.expect(first.modulePath.endsWith("Plugin.res.mjs")).toBe(true);
|
|
55
|
+
globalThis.expect(first.packageDir).toBe(a);
|
|
56
|
+
await Promises.rm(root, {
|
|
57
|
+
recursive: true,
|
|
58
|
+
force: true
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/* Not a pure module */
|