@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.
Files changed (172) hide show
  1. package/CHANGELOG.md +698 -0
  2. package/LICENSE +202 -0
  3. package/bin/reventless-dev.mjs +21 -0
  4. package/package.json +54 -0
  5. package/rescript.json +36 -0
  6. package/src/.gwtignore +10 -0
  7. package/src/Automation_GWT.res +207 -0
  8. package/src/Automation_GWT.res.mjs +160 -0
  9. package/src/Behavior_GWT.res +603 -0
  10. package/src/Behavior_GWT.res.mjs +426 -0
  11. package/src/Bind.res +29 -0
  12. package/src/Bind.res.mjs +22 -0
  13. package/src/BuildClassifier.res +137 -0
  14. package/src/BuildClassifier.res.mjs +136 -0
  15. package/src/Cancellation.res +31 -0
  16. package/src/Cancellation.res.mjs +45 -0
  17. package/src/ChildProcess.res +84 -0
  18. package/src/ChildProcess.res.mjs +92 -0
  19. package/src/Cli.res +671 -0
  20. package/src/Cli.res.mjs +754 -0
  21. package/src/Collector.res +201 -0
  22. package/src/Collector.res.mjs +215 -0
  23. package/src/ComponentMeta.res +104 -0
  24. package/src/ComponentMeta.res.mjs +120 -0
  25. package/src/ComponentScan.res +103 -0
  26. package/src/ComponentScan.res.mjs +105 -0
  27. package/src/Delegate_GWT.res +303 -0
  28. package/src/Delegate_GWT.res.mjs +372 -0
  29. package/src/Diff.res +91 -0
  30. package/src/Diff.res.mjs +98 -0
  31. package/src/Discovery.res +105 -0
  32. package/src/Discovery.res.mjs +103 -0
  33. package/src/DomainDeadCode.res +93 -0
  34. package/src/DomainDeadCode.res.mjs +69 -0
  35. package/src/DomainGraph.res +0 -0
  36. package/src/DomainGraph.res.mjs +0 -0
  37. package/src/EventMapping_GWT.res +29 -0
  38. package/src/EventMapping_GWT.res.mjs +44 -0
  39. package/src/Filter.res +36 -0
  40. package/src/Filter.res.mjs +40 -0
  41. package/src/Flow_GWT.res +664 -0
  42. package/src/Flow_GWT.res.mjs +604 -0
  43. package/src/FormatterHuman.res +138 -0
  44. package/src/FormatterHuman.res.mjs +122 -0
  45. package/src/FormatterJson.res +257 -0
  46. package/src/FormatterJson.res.mjs +320 -0
  47. package/src/FormatterJunit.res +93 -0
  48. package/src/FormatterJunit.res.mjs +90 -0
  49. package/src/FormatterTap.res +139 -0
  50. package/src/FormatterTap.res.mjs +149 -0
  51. package/src/FormatterVsCode.res +362 -0
  52. package/src/FormatterVsCode.res.mjs +564 -0
  53. package/src/Hint.res +119 -0
  54. package/src/Hint.res.mjs +124 -0
  55. package/src/InboundTranslation_GWT.res +83 -0
  56. package/src/InboundTranslation_GWT.res.mjs +94 -0
  57. package/src/JestBind.res +70 -0
  58. package/src/JestBind.res.mjs +63 -0
  59. package/src/Loader.res +24 -0
  60. package/src/Loader.res.mjs +23 -0
  61. package/src/LocalHost.res +135 -0
  62. package/src/LocalHost.res.mjs +127 -0
  63. package/src/Mapping_GWT.res +474 -0
  64. package/src/Mapping_GWT.res.mjs +322 -0
  65. package/src/MultiSourceProjection_GWT.res +316 -0
  66. package/src/MultiSourceProjection_GWT.res.mjs +354 -0
  67. package/src/OutboundTranslation_GWT.res +238 -0
  68. package/src/OutboundTranslation_GWT.res.mjs +186 -0
  69. package/src/Outcome.res +161 -0
  70. package/src/Outcome.res.mjs +140 -0
  71. package/src/PackageScan.res +80 -0
  72. package/src/PackageScan.res.mjs +81 -0
  73. package/src/PlatformRunner.res +160 -0
  74. package/src/PlatformRunner.res.mjs +155 -0
  75. package/src/PlatformScan.res +138 -0
  76. package/src/PlatformScan.res.mjs +131 -0
  77. package/src/ProcessManager.res +91 -0
  78. package/src/ProcessManager.res.mjs +80 -0
  79. package/src/Projection_GWT.res +312 -0
  80. package/src/Projection_GWT.res.mjs +332 -0
  81. package/src/Query_GWT.res +379 -0
  82. package/src/Query_GWT.res.mjs +367 -0
  83. package/src/RenderRescript.res +125 -0
  84. package/src/RenderRescript.res.mjs +141 -0
  85. package/src/RunnerTypes.res +68 -0
  86. package/src/RunnerTypes.res.mjs +60 -0
  87. package/src/SideEffect_GWT.res +157 -0
  88. package/src/SideEffect_GWT.res.mjs +113 -0
  89. package/src/StateChange/ExternalAddCategorySlice.res +25 -0
  90. package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
  91. package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
  92. package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
  93. package/src/StateChange/WithFixtures.res +22 -0
  94. package/src/StateChange/WithFixtures.res.mjs +31 -0
  95. package/src/StateChange/WithFixtures_Behavior.res +17 -0
  96. package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
  97. package/src/StateChange/WithManualOpen.res +23 -0
  98. package/src/StateChange/WithManualOpen.res.mjs +31 -0
  99. package/src/StateChange/WithManualOpen_Behavior.res +17 -0
  100. package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
  101. package/src/StubRuntime.res +53 -0
  102. package/src/StubRuntime.res.mjs +54 -0
  103. package/src/TestFixtures.res +16 -0
  104. package/src/TestFixtures.res.mjs +34 -0
  105. package/src/Watch.res +92 -0
  106. package/src/Watch.res.mjs +83 -0
  107. package/src/WatcherProbe.res +48 -0
  108. package/src/WatcherProbe.res.mjs +46 -0
  109. package/tests/AutomationGwtTest.res +71 -0
  110. package/tests/AutomationGwtTest.res.mjs +189 -0
  111. package/tests/BuildClassifierTest.res +94 -0
  112. package/tests/BuildClassifierTest.res.mjs +132 -0
  113. package/tests/ComponentMetaTest.res +52 -0
  114. package/tests/ComponentMetaTest.res.mjs +57 -0
  115. package/tests/DelegateGwtTest.res +487 -0
  116. package/tests/DelegateGwtTest.res.mjs +891 -0
  117. package/tests/DiscoveryTest.res +42 -0
  118. package/tests/DiscoveryTest.res.mjs +43 -0
  119. package/tests/DomainDeadCodeTest.res +116 -0
  120. package/tests/DomainDeadCodeTest.res.mjs +155 -0
  121. package/tests/DomainGraphTest.res +365 -0
  122. package/tests/DomainGraphTest.res.mjs +413 -0
  123. package/tests/FlowAggregateGwtTest.res +169 -0
  124. package/tests/FlowAggregateGwtTest.res.mjs +269 -0
  125. package/tests/FlowCrossPluginGwtTest.res +340 -0
  126. package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
  127. package/tests/FlowGwtTest.res +222 -0
  128. package/tests/FlowGwtTest.res.mjs +432 -0
  129. package/tests/FormatterVsCodeMessageTest.res +41 -0
  130. package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
  131. package/tests/InboundTranslationGwtTest.res +34 -0
  132. package/tests/InboundTranslationGwtTest.res.mjs +98 -0
  133. package/tests/LocalHostIntegration.res +70 -0
  134. package/tests/LocalHostIntegration.res.mjs +64 -0
  135. package/tests/LocalHostTest.res +71 -0
  136. package/tests/LocalHostTest.res.mjs +63 -0
  137. package/tests/MappingGwtTest.res +348 -0
  138. package/tests/MappingGwtTest.res.mjs +592 -0
  139. package/tests/OutboundTranslationGwtTest.res +59 -0
  140. package/tests/OutboundTranslationGwtTest.res.mjs +138 -0
  141. package/tests/PlatformRunnerTest.res +66 -0
  142. package/tests/PlatformRunnerTest.res.mjs +85 -0
  143. package/tests/PlatformScanTest.res +61 -0
  144. package/tests/PlatformScanTest.res.mjs +56 -0
  145. package/tests/QueryGwtTest.res +161 -0
  146. package/tests/QueryGwtTest.res.mjs +314 -0
  147. package/tests/QueryResolverGwtTest.res +71 -0
  148. package/tests/QueryResolverGwtTest.res.mjs +136 -0
  149. package/tests/RunnerUnitTest.res +219 -0
  150. package/tests/RunnerUnitTest.res.mjs +244 -0
  151. package/tests/SideEffect/FakeOrderNotification.res +23 -0
  152. package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
  153. package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
  154. package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
  155. package/tests/SideEffect/MockEmail.res +18 -0
  156. package/tests/SideEffect/MockEmail.res.mjs +47 -0
  157. package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
  158. package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
  159. package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
  160. package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
  161. package/tests/StateChange/WithFixtures_GWT.res +23 -0
  162. package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
  163. package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
  164. package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
  165. package/tests/StateChange/WithManualOpen_GWT.res +17 -0
  166. package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
  167. package/tests/StateChangeSliceGwtTest.res +160 -0
  168. package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
  169. package/tests/StateViewSliceGwtTest.res +64 -0
  170. package/tests/StateViewSliceGwtTest.res.mjs +161 -0
  171. package/tests/WatchDebounceTest.res +78 -0
  172. 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 */