@reventlessdev/reventless-core 3.0.0-alpha.198 → 3.0.0-alpha.200

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 (31) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/package.json +6 -6
  3. package/src/admin/Platform_AdminApi.res +23 -0
  4. package/src/admin/Platform_AdminApi.res.mjs +12 -0
  5. package/src/components/Task/Task_Builder.res +75 -4
  6. package/src/components/Task/Task_Builder.res.mjs +33 -1
  7. package/src/plugin/component/Plugin_Structure.res +8 -5
  8. package/src/util/StorePrefixCollision.res +96 -0
  9. package/src/util/StorePrefixCollision.res.mjs +55 -0
  10. package/tests/StorePrefixCollisionTest.res +160 -0
  11. package/tests/StorePrefixCollisionTest.res.mjs +174 -0
  12. package/tests/TaskBucketNamingTest.res +97 -0
  13. package/tests/TaskBucketNamingTest.res.mjs +60 -0
  14. package/tests/api/SuryToJsonSchemaTest.res +46 -0
  15. package/tests/api/SuryToJsonSchemaTest.res.mjs +39 -0
  16. package/tests/fixtures/plugin-lifecycle/2026-07-22-versionconnected-no-requiredstores.json +1 -0
  17. package/tests/fixtures/plugin-lifecycle/2026-07-22-versionsuperseded-nested-definitions.json +1 -0
  18. package/tests/fixtures/plugin-lifecycle/2026-07-28-versionconnected-requiredstores-only.json +1 -0
  19. package/tests/fixtures/plugin-lifecycle/2026-07-30-versionconnected-declarations-without-annotation.json +1 -0
  20. package/tests/fixtures/plugin-lifecycle/2026-07-30-versiondisconnected-declarations-without-annotation.json +1 -0
  21. package/tests/fixtures/plugin-lifecycle/README.md +36 -0
  22. package/tests/plugin/PluginDefinitionRequiredScalarsTest.res +42 -0
  23. package/tests/plugin/PluginDefinitionRequiredScalarsTest.res.mjs +45 -0
  24. package/tests/plugin/PluginDefinitionScalars.res +78 -0
  25. package/tests/plugin/PluginDefinitionScalars.res.mjs +110 -0
  26. package/tests/plugin/PluginLifecycleCorpus.res +42 -0
  27. package/tests/plugin/PluginLifecycleCorpus.res.mjs +39 -0
  28. package/tests/plugin/PluginLifecycleCorpusTest.res +50 -0
  29. package/tests/plugin/PluginLifecycleCorpusTest.res.mjs +42 -0
  30. package/tests/plugin/PluginStructureTest.res +3 -3
  31. package/tests/plugin/pluginDefinitionRequiredScalars.txt +103 -0
@@ -0,0 +1,50 @@
1
+ // Frozen-payload compatibility corpus for the Plugin lifecycle aggregate.
2
+ //
3
+ // The Plugin aggregate replays its own event log before every decision, so ONE
4
+ // undecodable stored event freezes Heartbeat / Redetect / Connect for that plugin
5
+ // permanently — and it does so silently, surfacing only as a registration that
6
+ // stops tracking deploys. See docs/analysis/plugin-definition-schema-evolution-wedge.md.
7
+ //
8
+ // The two hand-constructed cases in MessageTest build a `pluginDefinition` from the
9
+ // CURRENT ReScript type and then strip keys. That literal recompiles with every
10
+ // schema change, so it can only test the fields whoever wrote it thought to strip —
11
+ // which is why it did not catch `annotation: string`. These fixtures are frozen JSON
12
+ // captured off a deployed log and are never regenerated; see the corpus README.
13
+ //
14
+ // What turns this suite red is a change no amount of healing can absorb: a field
15
+ // whose TYPE changed under stored data (`float` → an object, say), a `bigint`, or a
16
+ // genuinely corrupt payload. It names the fixture and the sury field path. The fix is
17
+ // to make the change decodable — never to re-cut the fixtures.
18
+ //
19
+ // A newly added required *scalar* does NOT land here: `fillMissingDefaults` invents a
20
+ // value for it, so these still decode. That case is caught by the sibling
21
+ // PluginDefinitionRequiredScalarsTest, at the declaration site.
22
+
23
+ open JestGlobals
24
+
25
+ let entries = PluginLifecycleCorpus.entries
26
+
27
+ // Mirrors EventLog_Operations.decodeEvent: reassemble the stored `{event, data}`
28
+ // pair into a variant and decode it — the exact call replay makes.
29
+ let decodeStored = (entry: PluginLifecycleCorpus.entry) =>
30
+ Message.combineMessage(entry.event, entry.data)->Message.decode(PluginSpec.eventSchema)
31
+
32
+ describe("The stored plugin-lifecycle payload corpus", () => {
33
+ testSync("is not empty (a corpus that loaded nothing would pass vacuously)", () =>
34
+ expect(entries->Array.length > 0)->toBe(true)
35
+ )
36
+
37
+ entries->Array.forEach(entry =>
38
+ testSync(`still decodes: ${entry.name}`, () => {
39
+ // Sury's own exception carries the field path, which is the whole value of
40
+ // this suite's failure message — report it verbatim rather than "it threw".
41
+ let outcome = switch decodeStored(entry) {
42
+ | _ => "decodes"
43
+ | exception S.Error(err) => err.message
44
+ | exception JsExn(e) => `threw: ${JsExn.message(e)->Option.getOr("no message")}`
45
+ | exception _ => "threw a non-JS exception"
46
+ }
47
+ expect(outcome)->toBe("decodes")
48
+ })
49
+ )
50
+ })
@@ -0,0 +1,42 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
7
+ import * as Message$ReventlessCore from "../../src/Message.res.mjs";
8
+ import * as PluginSpec$ReventlessCore from "../../src/plugin/lifecycle/PluginSpec.res.mjs";
9
+ import * as PluginLifecycleCorpus$ReventlessCore from "./PluginLifecycleCorpus.res.mjs";
10
+
11
+ function decodeStored(entry) {
12
+ return Message$ReventlessCore.decode(Message$ReventlessCore.combineMessage(entry.event, entry.data), PluginSpec$ReventlessCore.eventSchema);
13
+ }
14
+
15
+ globalThis.describe("The stored plugin-lifecycle payload corpus", () => {
16
+ globalThis.test("is not empty (a corpus that loaded nothing would pass vacuously)", () => {
17
+ globalThis.expect(PluginLifecycleCorpus$ReventlessCore.entries.length !== 0).toBe(true);
18
+ });
19
+ PluginLifecycleCorpus$ReventlessCore.entries.forEach(entry => {
20
+ globalThis.test(`still decodes: ` + entry.name, () => {
21
+ let outcome;
22
+ try {
23
+ decodeStored(entry);
24
+ outcome = "decodes";
25
+ } catch (raw_err) {
26
+ let err = Primitive_exceptions.internalToException(raw_err);
27
+ outcome = err.RE_EXN_ID === S.$$Error ? err._1.message : (
28
+ err.RE_EXN_ID === "JsExn" ? `threw: ` + Stdlib_Option.getOr(Stdlib_JsExn.message(err._1), "no message") : "threw a non-JS exception"
29
+ );
30
+ }
31
+ globalThis.expect(outcome).toBe("decodes");
32
+ });
33
+ });
34
+ });
35
+
36
+ let entries = PluginLifecycleCorpus$ReventlessCore.entries;
37
+
38
+ export {
39
+ entries,
40
+ decodeStored,
41
+ }
42
+ /* Not a pure module */
@@ -718,13 +718,13 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
718
718
  store: "TestPlugin.documents",
719
719
  component: "AttachInvoice",
720
720
  field: "documentUrl",
721
- annotation: "documents",
721
+ annotation: Some("documents"),
722
722
  },
723
723
  {
724
724
  store: "branding.logos",
725
725
  component: "AttachInvoice",
726
726
  field: "logoUrl",
727
- annotation: "branding.logos",
727
+ annotation: Some("branding.logos"),
728
728
  },
729
729
  ]
730
730
  expect(withStores.requiredStoreDeclarations)->toEqual(Some(expected))
@@ -815,7 +815,7 @@ describe("Plugin_Structure.make — Phase 2 graph fields", () => {
815
815
  store: "TestPlugin.avatars",
816
816
  component: "UploadAvatar",
817
817
  field: "avatarUrl",
818
- annotation: "avatars",
818
+ annotation: Some("avatars"),
819
819
  },
820
820
  ]
821
821
  expect(withOptional.requiredStoreDeclarations)->toEqual(Some(expected))
@@ -0,0 +1,103 @@
1
+ # Every bare required scalar reachable from `pluginDefinition`, sorted.
2
+ #
3
+ # This file is a tripwire, not an inventory to keep tidy. Everything reachable from
4
+ # `pluginDefinition` is persisted in the Plugin lifecycle aggregate's event log, and a
5
+ # scalar is the one shape `Message.fillMissingDefaults` cannot derive a value for: adding
6
+ # one means every message stored before it decodes with a value this framework invented
7
+ # (`""` / `0` / `false`), reported at runtime as a warning and nowhere else.
8
+ #
9
+ # So a diff here is a design decision, not a chore. If your new field can be absent, give
10
+ # it the `js_nullable` (`T | null`) shape and this list does not change. If it genuinely
11
+ # must be a required scalar, add the line and accept that older payloads carry a fabricated
12
+ # value for it.
13
+ #
14
+ # Regenerate ONLY after making that call — never to make a red build green.
15
+ # See docs/analysis/plugin-definition-schema-evolution-wedge.md.
16
+ #
17
+ # A `js_nullable` (`T | null`) scalar is NOT listed: absent heals to null, which is the
18
+ # shape the rule asks for. A scalar nested inside a nullable object or array IS listed —
19
+ # when an old payload carries the parent, the new field still has to be invented. That is
20
+ # how requiredStoreDeclarations[].annotation, a required string inside a nullable array,
21
+ # froze a plugin's registration for two days; it is absent from this list because it has
22
+ # since been made `js_nullable`.
23
+ .apiSchemaFragment.encoded: string
24
+ .apiSchemaFragment.protocol: string
25
+ .dcbEventLog.eventTopicArn: string
26
+ .dcbEventLog.name: string
27
+ .eventCollector: string
28
+ .extensionPoints[].commandTopic: string
29
+ .extensionPoints[].eventTopic: string
30
+ .extensionPoints[].name: string
31
+ .extensionProtocols[].commandVersion: string
32
+ .extensionProtocols[].eventVersion: string
33
+ .extensionProtocols[].extensionPointName: string
34
+ .extensions[].dcbSources[]: string
35
+ .extensions[].extensionPointName: string
36
+ .extensions[].name: string
37
+ .id: string
38
+ .name: string
39
+ .structure.aggregates[].commands[].allowedStates[]: string
40
+ .structure.aggregates[].commands[].mutationField: string
41
+ .structure.aggregates[].commands[].name: string
42
+ .structure.aggregates[].commands[].references[].entity: string
43
+ .structure.aggregates[].commands[].references[].fieldName: string
44
+ .structure.aggregates[].commands[].schema: string
45
+ .structure.aggregates[].consumedEventTypes[]: string
46
+ .structure.aggregates[].events[].name: string
47
+ .structure.aggregates[].events[].references[].entity: string
48
+ .structure.aggregates[].events[].references[].fieldName: string
49
+ .structure.aggregates[].events[].schema: string
50
+ .structure.aggregates[].linkedViews[]: string
51
+ .structure.aggregates[].name: string
52
+ .structure.aggregates[].producedEventTypes[]: string
53
+ .structure.automationSlices[].consumedEventTypes[]: string
54
+ .structure.automationSlices[].name: string
55
+ .structure.automationSlices[].producedCommandTypes[]: string
56
+ .structure.automationSlices[].targetName: string
57
+ .structure.extensionPoints[].commandTypes[]: string
58
+ .structure.extensionPoints[].delegateNames[]: string
59
+ .structure.extensionPoints[].name: string
60
+ .structure.extensionPoints[].sourceEventTypes[]: string
61
+ .structure.extensions[].commandTypes[]: string
62
+ .structure.extensions[].delegateNames[]: string
63
+ .structure.extensions[].eventTypes[]: string
64
+ .structure.extensions[].name: string
65
+ .structure.inboundTranslationSlices[].commandTypes[]: string
66
+ .structure.inboundTranslationSlices[].name: string
67
+ .structure.inboundTranslationSlices[].targetName: string
68
+ .structure.outboundTranslationSlices[].consumedEventTypes[]: string
69
+ .structure.outboundTranslationSlices[].inboundCommandTypes[]: string
70
+ .structure.outboundTranslationSlices[].name: string
71
+ .structure.readModels[].consumedEventTypes[]: string
72
+ .structure.readModels[].labelField: string
73
+ .structure.readModels[].linkedWriteSide[]: string
74
+ .structure.readModels[].name: string
75
+ .structure.readModels[].queryField: string
76
+ .structure.readModels[].schema: string
77
+ .structure.readModels[].searchableFields[]: string
78
+ .structure.requiredStoreDeclarations[].component: string
79
+ .structure.requiredStoreDeclarations[].field: string
80
+ .structure.requiredStoreDeclarations[].store: string
81
+ .structure.requiredStores[]: string
82
+ .structure.stateChangeSlices[].commands[].allowedStates[]: string
83
+ .structure.stateChangeSlices[].commands[].mutationField: string
84
+ .structure.stateChangeSlices[].commands[].name: string
85
+ .structure.stateChangeSlices[].commands[].references[].entity: string
86
+ .structure.stateChangeSlices[].commands[].references[].fieldName: string
87
+ .structure.stateChangeSlices[].commands[].schema: string
88
+ .structure.stateChangeSlices[].consumedEventTypes[]: string
89
+ .structure.stateChangeSlices[].events[].name: string
90
+ .structure.stateChangeSlices[].events[].references[].entity: string
91
+ .structure.stateChangeSlices[].events[].references[].fieldName: string
92
+ .structure.stateChangeSlices[].events[].schema: string
93
+ .structure.stateChangeSlices[].linkedViews[]: string
94
+ .structure.stateChangeSlices[].name: string
95
+ .structure.stateChangeSlices[].producedEventTypes[]: string
96
+ .structure.stateViewSlices[].consumedEventTypes[]: string
97
+ .structure.stateViewSlices[].labelField: string
98
+ .structure.stateViewSlices[].linkedWriteSide[]: string
99
+ .structure.stateViewSlices[].name: string
100
+ .structure.stateViewSlices[].queryField: string
101
+ .structure.stateViewSlices[].schema: string
102
+ .structure.stateViewSlices[].searchableFields[]: string
103
+ .version: string