@reventlessdev/reventless-local 3.0.0-alpha.145 → 3.0.0-alpha.147

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.
@@ -16,6 +16,7 @@ let make = (
16
16
  ~resourceNaming as _,
17
17
  ~memorySize as _=?,
18
18
  ~timeout as _=?,
19
+ ~extraEnvVars as _=?,
19
20
  ~opts=?,
20
21
  ): ReventlessCore.SideEffectHandler.component => {
21
22
  let noopEnqueueEvent: ReventlessInfra.EventCollector.enqueueEvent = async (_, _, _) => ()
@@ -5,7 +5,7 @@ import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/co
5
5
  import * as ComponentType$ReventlessCore from "@reventlessdev/reventless-core/src/ComponentType.res.mjs";
6
6
  import * as SideEffectHandler$ReventlessCore from "@reventlessdev/reventless-core/src/components/SideEffectHandler/SideEffectHandler.res.mjs";
7
7
 
8
- function make(name, param, param$1, param$2, param$3, param$4, scheduler, param$5, param$6, param$7, opts) {
8
+ function make(name, param, param$1, param$2, param$3, param$4, scheduler, param$5, param$6, param$7, param$8, opts) {
9
9
  let noopEnqueueEvent = async (param, param$1, param$2) => {};
10
10
  let ops_createSchedule = async schedule => await scheduler.createSchedule([], schedule);
11
11
  let ops_deleteSchedule = async scheduleName => await scheduler.deleteSchedule([], scheduleName);
@@ -99,7 +99,7 @@ let sideEffects = [{
99
99
  execute: execute
100
100
  }];
101
101
 
102
- let seh = SEHBuilder.make("TestSEH", sideEffects, allEventTopics, Pulumi.output({}), undefined, TestFixtures$ReventlessLocal.mockQueryEngine, TestFixtures$ReventlessLocal.mockScheduler, TestFixtures$ReventlessLocal.mockResourceNaming, undefined, undefined, undefined);
102
+ let seh = SEHBuilder.make("TestSEH", sideEffects, allEventTopics, Pulumi.output({}), undefined, TestFixtures$ReventlessLocal.mockQueryEngine, TestFixtures$ReventlessLocal.mockScheduler, TestFixtures$ReventlessLocal.mockResourceNaming, undefined, undefined, undefined, undefined);
103
103
 
104
104
  let testMeta = TestFixtures$ReventlessLocal.makeTestMeta("TestSEHSource");
105
105
 
@@ -0,0 +1,131 @@
1
+ // Behavior GWT for the ApiFragmentRegistry SINGLETON AGGREGATE (the platform API-schema
2
+ // fragment registry — event-sourced-fragment-registries plan). Unlike the retired slice, the
3
+ // aggregate emits a SECOND event ApiSchemaComputed{snapshot} alongside each ApiFragment* fact,
4
+ // carrying the whole consistent per-plugin fragment set after the change (the reactive
5
+ // SideEffect's trigger). RecordApiFragmentPush changes no fragments, so it emits NO snapshot.
6
+ open ReventlessCore
7
+ module P = Reventless.Plugin // P.Domain / P.Platform — avoid shadowing apiSchemaFragment's labels
8
+
9
+ module Test = ReventlessGwt.Behavior_GWT.MakeFromAggregate(ApiFragmentRegistrySpec, ApiFragmentRegistryBehavior)
10
+ open Test
11
+ open ApiFragmentRegistrySpec
12
+
13
+ let fragment1: Reventless.Plugin.apiSchemaFragment = {
14
+ encoded: `{"types":["type Catalog_Product { id: ID! }"],"mutations":[],"queries":[],"subscriptions":[],"subscriptionSources":[]}`,
15
+ protocol: "graphql",
16
+ }
17
+
18
+ let fragment2: Reventless.Plugin.apiSchemaFragment = {
19
+ encoded: `{"types":["type Catalog_Product { id: ID!\\n name: String! }"],"mutations":[],"queries":[],"subscriptions":[],"subscriptionSources":[]}`,
20
+ protocol: "graphql",
21
+ }
22
+
23
+ // A snapshot entry mirrors the behaviour's `snapshotOf` fold (pluginId + the fragment's
24
+ // encoded/protocol + its target).
25
+ let entry = (~pluginId, ~fragment: Reventless.Plugin.apiSchemaFragment, ~apiTarget): fragmentSnapshotEntry => {
26
+ pluginId,
27
+ encoded: fragment.encoded,
28
+ protocol: fragment.protocol,
29
+ apiTarget,
30
+ }
31
+
32
+ describe("ApiFragmentRegistry aggregate", () => {
33
+ test("register on empty state emits ApiFragmentRegistered + ApiSchemaComputed", () =>
34
+ givenEvents([])
35
+ ->whenCmd(RegisterApiFragment({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"}))
36
+ ->thenEvents([
37
+ ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"}),
38
+ ApiSchemaComputed({snapshot: [entry(~pluginId="p1", ~fragment=fragment1, ~apiTarget=P.Domain)]}),
39
+ ])
40
+ )
41
+
42
+ test("re-registering an identical fragment + target is idempotent (no event)", () =>
43
+ givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"})])
44
+ ->whenCmd(RegisterApiFragment({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t1"}))
45
+ ->thenNoEvent
46
+ )
47
+
48
+ test("registering a changed fragment emits ApiFragmentUpdated + ApiSchemaComputed", () =>
49
+ givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"})])
50
+ ->whenCmd(RegisterApiFragment({pluginId: "p1", fragment: fragment2, apiTarget: P.Domain, at: "t1"}))
51
+ ->thenEvents([
52
+ ApiFragmentUpdated({
53
+ pluginId: "p1",
54
+ previousFragment: fragment1,
55
+ newFragment: fragment2,
56
+ apiTarget: P.Domain,
57
+ at: "t1",
58
+ }),
59
+ ApiSchemaComputed({snapshot: [entry(~pluginId="p1", ~fragment=fragment2, ~apiTarget=P.Domain)]}),
60
+ ])
61
+ )
62
+
63
+ test("retargeting a plugin with an identical fragment emits ApiFragmentUpdated (fields move APIs)", () =>
64
+ givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"})])
65
+ ->whenCmd(RegisterApiFragment({pluginId: "p1", fragment: fragment1, apiTarget: P.Platform, at: "t1"}))
66
+ ->thenEvents([
67
+ ApiFragmentUpdated({
68
+ pluginId: "p1",
69
+ previousFragment: fragment1,
70
+ newFragment: fragment1,
71
+ apiTarget: P.Platform,
72
+ at: "t1",
73
+ }),
74
+ ApiSchemaComputed({snapshot: [entry(~pluginId="p1", ~fragment=fragment1, ~apiTarget=P.Platform)]}),
75
+ ])
76
+ )
77
+
78
+ test("registering a second plugin carries BOTH plugins in the snapshot (consistent whole-registry fold)", () =>
79
+ givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"})])
80
+ ->whenCmd(RegisterApiFragment({pluginId: "p2", fragment: fragment2, apiTarget: P.Platform, at: "t1"}))
81
+ ->thenEvents([
82
+ ApiFragmentRegistered({pluginId: "p2", fragment: fragment2, apiTarget: P.Platform, at: "t1"}),
83
+ ApiSchemaComputed({
84
+ snapshot: [
85
+ entry(~pluginId="p1", ~fragment=fragment1, ~apiTarget=P.Domain),
86
+ entry(~pluginId="p2", ~fragment=fragment2, ~apiTarget=P.Platform),
87
+ ],
88
+ }),
89
+ ])
90
+ )
91
+
92
+ test("deregister emits ApiFragmentDeregistered + an empty-registry ApiSchemaComputed", () =>
93
+ givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"})])
94
+ ->whenCmd(DeregisterApiFragment({pluginId: "p1"}))
95
+ ->thenEvents([ApiFragmentDeregistered({pluginId: "p1"}), ApiSchemaComputed({snapshot: []})])
96
+ )
97
+
98
+ test("deregistering an absent fragment is idempotent (no event)", () =>
99
+ givenEvents([])->whenCmd(DeregisterApiFragment({pluginId: "p1"}))->thenNoEvent
100
+ )
101
+
102
+ test("recording a push outcome emits ONLY ApiFragmentPushRecorded (no snapshot, no recompute loop)", () =>
103
+ givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"})])
104
+ ->whenCmd(RecordApiFragmentPush({pluginId: "p1", ok: true, message: "", at: "t1"}))
105
+ ->thenEvent(ApiFragmentPushRecorded({pluginId: "p1", ok: true, message: "", at: "t1"}))
106
+ )
107
+
108
+ test("redelivering an identical push record is idempotent (no event)", () =>
109
+ givenEvents([
110
+ ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"}),
111
+ ApiFragmentPushRecorded({pluginId: "p1", ok: true, message: "", at: "t1"}),
112
+ ])
113
+ ->whenCmd(RecordApiFragmentPush({pluginId: "p1", ok: true, message: "", at: "t1"}))
114
+ ->thenNoEvent
115
+ )
116
+
117
+ test("a later distinct push outcome is recorded again", () =>
118
+ givenEvents([
119
+ ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"}),
120
+ ApiFragmentPushRecorded({pluginId: "p1", ok: true, message: "", at: "t1"}),
121
+ ])
122
+ ->whenCmd(RecordApiFragmentPush({pluginId: "p1", ok: false, message: "stitch failed", at: "t2"}))
123
+ ->thenEvent(ApiFragmentPushRecorded({pluginId: "p1", ok: false, message: "stitch failed", at: "t2"}))
124
+ )
125
+
126
+ test("recording a push for an unregistered plugin is dropped (no event)", () =>
127
+ givenEvents([])
128
+ ->whenCmd(RecordApiFragmentPush({pluginId: "p1", ok: true, message: "", at: "t1"}))
129
+ ->thenNoEvent
130
+ )
131
+ })
@@ -1,19 +1,26 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Behavior_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/Behavior_GWT.res.mjs";
4
- import * as ApiFragmentRegistry$ReventlessCore from "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry.res.mjs";
5
- import * as ApiFragmentRegistry_Behavior$ReventlessCore from "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry_Behavior.res.mjs";
4
+ import * as ApiFragmentRegistrySpec$ReventlessCore from "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/ApiFragmentRegistrySpec.res.mjs";
5
+ import * as ApiFragmentRegistryBehavior$ReventlessCore from "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/ApiFragmentRegistryBehavior.res.mjs";
6
6
 
7
- let Test = Behavior_GWT$ReventlessGwt.Make({
8
- name: ApiFragmentRegistry$ReventlessCore.name,
9
- consumedEventSchema: ApiFragmentRegistry$ReventlessCore.consumedEventSchema,
10
- commandSchema: ApiFragmentRegistry$ReventlessCore.commandSchema,
11
- errorSchema: ApiFragmentRegistry$ReventlessCore.errorSchema,
12
- eventSchema: ApiFragmentRegistry$ReventlessCore.eventSchema
7
+ let Test = Behavior_GWT$ReventlessGwt.MakeFromAggregate({
8
+ name: ApiFragmentRegistrySpec$ReventlessCore.name,
9
+ commandSchema: ApiFragmentRegistrySpec$ReventlessCore.commandSchema,
10
+ eventSchema: ApiFragmentRegistrySpec$ReventlessCore.eventSchema,
11
+ errorSchema: ApiFragmentRegistrySpec$ReventlessCore.errorSchema
13
12
  })({
14
- initialState: undefined,
15
- evolve: ApiFragmentRegistry_Behavior$ReventlessCore.evolve,
16
- decide: ApiFragmentRegistry_Behavior$ReventlessCore.decide
13
+ Spec: {
14
+ name: ApiFragmentRegistrySpec$ReventlessCore.name,
15
+ commandSchema: ApiFragmentRegistrySpec$ReventlessCore.commandSchema,
16
+ eventSchema: ApiFragmentRegistrySpec$ReventlessCore.eventSchema,
17
+ errorSchema: ApiFragmentRegistrySpec$ReventlessCore.errorSchema
18
+ },
19
+ initialState: ApiFragmentRegistryBehavior$ReventlessCore.initialState,
20
+ evolve: ApiFragmentRegistryBehavior$ReventlessCore.evolve,
21
+ decide: ApiFragmentRegistryBehavior$ReventlessCore.decide,
22
+ snapshot: undefined,
23
+ moduleUrl: ApiFragmentRegistryBehavior$ReventlessCore.moduleUrl
17
24
  });
18
25
 
19
26
  let fragment1 = {
@@ -26,20 +33,35 @@ let fragment2 = {
26
33
  protocol: "graphql"
27
34
  };
28
35
 
29
- Test.describe("ApiFragmentRegistry StateChangeSlice", () => {
30
- Test.test("register on empty state emits ApiFragmentRegistered", () => Test.thenEvent(Test.whenCmd(Test.givenEvents([]), {
36
+ function entry(pluginId, fragment, apiTarget) {
37
+ return {
38
+ pluginId: pluginId,
39
+ encoded: fragment.encoded,
40
+ protocol: fragment.protocol,
41
+ apiTarget: apiTarget
42
+ };
43
+ }
44
+
45
+ Test.describe("ApiFragmentRegistry aggregate", () => {
46
+ Test.test("register on empty state emits ApiFragmentRegistered + ApiSchemaComputed", () => Test.thenEvents(Test.whenCmd(Test.givenEvents([]), {
31
47
  TAG: "RegisterApiFragment",
32
48
  pluginId: "p1",
33
49
  fragment: fragment1,
34
50
  apiTarget: "Domain",
35
51
  at: "t0"
36
- }), {
37
- TAG: "ApiFragmentRegistered",
38
- pluginId: "p1",
39
- fragment: fragment1,
40
- apiTarget: "Domain",
41
- at: "t0"
42
- }));
52
+ }), [
53
+ {
54
+ TAG: "ApiFragmentRegistered",
55
+ pluginId: "p1",
56
+ fragment: fragment1,
57
+ apiTarget: "Domain",
58
+ at: "t0"
59
+ },
60
+ {
61
+ TAG: "ApiSchemaComputed",
62
+ snapshot: [entry("p1", fragment1, "Domain")]
63
+ }
64
+ ]));
43
65
  Test.test("re-registering an identical fragment + target is idempotent (no event)", () => Test.thenNoEvent(Test.whenCmd(Test.givenEvents([{
44
66
  TAG: "ApiFragmentRegistered",
45
67
  pluginId: "p1",
@@ -53,7 +75,7 @@ Test.describe("ApiFragmentRegistry StateChangeSlice", () => {
53
75
  apiTarget: "Domain",
54
76
  at: "t1"
55
77
  })));
56
- Test.test("registering a changed fragment emits ApiFragmentUpdated (version supersession)", () => Test.thenEvent(Test.whenCmd(Test.givenEvents([{
78
+ Test.test("registering a changed fragment emits ApiFragmentUpdated + ApiSchemaComputed", () => Test.thenEvents(Test.whenCmd(Test.givenEvents([{
57
79
  TAG: "ApiFragmentRegistered",
58
80
  pluginId: "p1",
59
81
  fragment: fragment1,
@@ -65,15 +87,21 @@ Test.describe("ApiFragmentRegistry StateChangeSlice", () => {
65
87
  fragment: fragment2,
66
88
  apiTarget: "Domain",
67
89
  at: "t1"
68
- }), {
69
- TAG: "ApiFragmentUpdated",
70
- pluginId: "p1",
71
- previousFragment: fragment1,
72
- newFragment: fragment2,
73
- apiTarget: "Domain",
74
- at: "t1"
75
- }));
76
- Test.test("retargeting a plugin with an identical fragment emits ApiFragmentUpdated", () => Test.thenEvent(Test.whenCmd(Test.givenEvents([{
90
+ }), [
91
+ {
92
+ TAG: "ApiFragmentUpdated",
93
+ pluginId: "p1",
94
+ previousFragment: fragment1,
95
+ newFragment: fragment2,
96
+ apiTarget: "Domain",
97
+ at: "t1"
98
+ },
99
+ {
100
+ TAG: "ApiSchemaComputed",
101
+ snapshot: [entry("p1", fragment2, "Domain")]
102
+ }
103
+ ]));
104
+ Test.test("retargeting a plugin with an identical fragment emits ApiFragmentUpdated (fields move APIs)", () => Test.thenEvents(Test.whenCmd(Test.givenEvents([{
77
105
  TAG: "ApiFragmentRegistered",
78
106
  pluginId: "p1",
79
107
  fragment: fragment1,
@@ -85,15 +113,49 @@ Test.describe("ApiFragmentRegistry StateChangeSlice", () => {
85
113
  fragment: fragment1,
86
114
  apiTarget: "Platform",
87
115
  at: "t1"
88
- }), {
89
- TAG: "ApiFragmentUpdated",
90
- pluginId: "p1",
91
- previousFragment: fragment1,
92
- newFragment: fragment1,
116
+ }), [
117
+ {
118
+ TAG: "ApiFragmentUpdated",
119
+ pluginId: "p1",
120
+ previousFragment: fragment1,
121
+ newFragment: fragment1,
122
+ apiTarget: "Platform",
123
+ at: "t1"
124
+ },
125
+ {
126
+ TAG: "ApiSchemaComputed",
127
+ snapshot: [entry("p1", fragment1, "Platform")]
128
+ }
129
+ ]));
130
+ Test.test("registering a second plugin carries BOTH plugins in the snapshot (consistent whole-registry fold)", () => Test.thenEvents(Test.whenCmd(Test.givenEvents([{
131
+ TAG: "ApiFragmentRegistered",
132
+ pluginId: "p1",
133
+ fragment: fragment1,
134
+ apiTarget: "Domain",
135
+ at: "t0"
136
+ }]), {
137
+ TAG: "RegisterApiFragment",
138
+ pluginId: "p2",
139
+ fragment: fragment2,
93
140
  apiTarget: "Platform",
94
141
  at: "t1"
95
- }));
96
- Test.test("deregister emits ApiFragmentDeregistered", () => Test.thenEvent(Test.whenCmd(Test.givenEvents([{
142
+ }), [
143
+ {
144
+ TAG: "ApiFragmentRegistered",
145
+ pluginId: "p2",
146
+ fragment: fragment2,
147
+ apiTarget: "Platform",
148
+ at: "t1"
149
+ },
150
+ {
151
+ TAG: "ApiSchemaComputed",
152
+ snapshot: [
153
+ entry("p1", fragment1, "Domain"),
154
+ entry("p2", fragment2, "Platform")
155
+ ]
156
+ }
157
+ ]));
158
+ Test.test("deregister emits ApiFragmentDeregistered + an empty-registry ApiSchemaComputed", () => Test.thenEvents(Test.whenCmd(Test.givenEvents([{
97
159
  TAG: "ApiFragmentRegistered",
98
160
  pluginId: "p1",
99
161
  fragment: fragment1,
@@ -102,15 +164,21 @@ Test.describe("ApiFragmentRegistry StateChangeSlice", () => {
102
164
  }]), {
103
165
  TAG: "DeregisterApiFragment",
104
166
  pluginId: "p1"
105
- }), {
106
- TAG: "ApiFragmentDeregistered",
107
- pluginId: "p1"
108
- }));
167
+ }), [
168
+ {
169
+ TAG: "ApiFragmentDeregistered",
170
+ pluginId: "p1"
171
+ },
172
+ {
173
+ TAG: "ApiSchemaComputed",
174
+ snapshot: []
175
+ }
176
+ ]));
109
177
  Test.test("deregistering an absent fragment is idempotent (no event)", () => Test.thenNoEvent(Test.whenCmd(Test.givenEvents([]), {
110
178
  TAG: "DeregisterApiFragment",
111
179
  pluginId: "p1"
112
180
  })));
113
- Test.test("recording a push outcome emits ApiFragmentPushRecorded", () => Test.thenEvent(Test.whenCmd(Test.givenEvents([{
181
+ Test.test("recording a push outcome emits ONLY ApiFragmentPushRecorded (no snapshot, no recompute loop)", () => Test.thenEvent(Test.whenCmd(Test.givenEvents([{
114
182
  TAG: "ApiFragmentRegistered",
115
183
  pluginId: "p1",
116
184
  fragment: fragment1,
@@ -188,9 +256,13 @@ Test.describe("ApiFragmentRegistry StateChangeSlice", () => {
188
256
  })));
189
257
  });
190
258
 
259
+ let P;
260
+
191
261
  export {
262
+ P,
192
263
  Test,
193
264
  fragment1,
194
265
  fragment2,
266
+ entry,
195
267
  }
196
268
  /* Test Not a pure module */
@@ -1,12 +1,13 @@
1
- // StateViewSlice projection GWT for the ApiFragments read sidethe current fragment
2
- // per plugin name plus push status, replacing the deploy-schema:* rows as the durable
3
- // stitch source (event-sourced-fragment-registries plan).
1
+ // Projection GWT for the ApiFragments read modelone row per plugin name holding the
2
+ // current fragment + push status, projected off the ApiFragmentRegistry aggregate's events
3
+ // (event-sourced-fragment-registries plan). Keyed by the payload pluginId (not the singleton
4
+ // aggregate id). ApiSchemaComputed is the reactive-push trigger only — it must NOT touch a row.
4
5
  open ReventlessCore
5
6
  module P = Reventless.Plugin // P.Domain / P.Platform — avoid opening apiSchemaFragment's labels
6
7
 
7
- module Test = ReventlessGwt.Projection_GWT.Make(ApiFragments, ApiFragments_Projection)
8
+ module Test = ReventlessGwt.MultiSourceProjection_GWT.Make(ApiFragmentsProjection.ApiFragmentsMapping)
8
9
  open Test
9
- open ApiFragments
10
+ open ApiFragmentRegistrySpec
10
11
 
11
12
  let fragment1: Reventless.Plugin.apiSchemaFragment = {
12
13
  encoded: `{"types":["type Catalog_Product { id: ID! }"],"mutations":[],"queries":[],"subscriptions":[],"subscriptionSources":[]}`,
@@ -18,7 +19,7 @@ let fragment2: Reventless.Plugin.apiSchemaFragment = {
18
19
  protocol: "graphql",
19
20
  }
20
21
 
21
- let registeredState: ApiFragments.state = {
22
+ let registeredState: ApiFragmentsReadModelSpec.state = {
22
23
  pluginId: "p1",
23
24
  encoded: fragment1.encoded,
24
25
  protocol: fragment1.protocol,
@@ -30,13 +31,19 @@ let registeredState: ApiFragments.state = {
30
31
  pushedAt: "",
31
32
  }
32
33
 
33
- describe("ApiFragments StateViewSlice projection", () => {
34
- test("ApiFragmentRegistered creates the row pending (registeredAt = updatedAt = event time)", () =>
34
+ describe("ApiFragments projection", () => {
35
+ test("ApiFragmentRegistered creates the row pending (registeredAt = updatedAt = event `at`)", () =>
35
36
  givenEvents([])
36
37
  ->whenEvent(ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"}))
37
38
  ->thenStateWithId("p1", registeredState)
38
39
  )
39
40
 
41
+ test("ApiSchemaComputed is ignored (no row change)", () =>
42
+ givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"})])
43
+ ->whenEvent(ApiSchemaComputed({snapshot: []}))
44
+ ->thenStateWithId("p1", registeredState)
45
+ )
46
+
40
47
  test("ApiFragmentPushRecorded(ok) marks the row ok with the push time", () =>
41
48
  givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"})])
42
49
  ->whenEvent(ApiFragmentPushRecorded({pluginId: "p1", ok: true, message: "", at: "t1"}))
@@ -45,9 +52,7 @@ describe("ApiFragments StateViewSlice projection", () => {
45
52
 
46
53
  test("ApiFragmentPushRecorded(error) carries the message", () =>
47
54
  givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: P.Domain, at: "t0"})])
48
- ->whenEvent(
49
- ApiFragmentPushRecorded({pluginId: "p1", ok: false, message: "stitch failed", at: "t1"}),
50
- )
55
+ ->whenEvent(ApiFragmentPushRecorded({pluginId: "p1", ok: false, message: "stitch failed", at: "t1"}))
51
56
  ->thenStateWithId(
52
57
  "p1",
53
58
  {...registeredState, pushStatus: "error", pushMessage: "stitch failed", pushedAt: "t1"},
@@ -1,17 +1,9 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as ApiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments.res.mjs";
4
- import * as Projection_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/Projection_GWT.res.mjs";
5
- import * as ApiFragments_Projection$ReventlessCore from "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments_Projection.res.mjs";
3
+ import * as ApiFragmentsProjection$ReventlessCore from "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/ApiFragmentsProjection.res.mjs";
4
+ import * as MultiSourceProjection_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/MultiSourceProjection_GWT.res.mjs";
6
5
 
7
- let Test = Projection_GWT$ReventlessGwt.Make({
8
- name: ApiFragments$ReventlessCore.name,
9
- stateSchema: ApiFragments$ReventlessCore.stateSchema,
10
- consumedEventSchema: ApiFragments$ReventlessCore.consumedEventSchema,
11
- subIdConfig: undefined
12
- })({
13
- project: ApiFragments_Projection$ReventlessCore.project
14
- });
6
+ let Test = MultiSourceProjection_GWT$ReventlessGwt.Make(ApiFragmentsProjection$ReventlessCore.ApiFragmentsMapping);
15
7
 
16
8
  let fragment1 = {
17
9
  encoded: `{"types":["type Catalog_Product { id: ID! }"],"mutations":[],"queries":[],"subscriptions":[],"subscriptionSources":[]}`,
@@ -35,14 +27,24 @@ let registeredState = {
35
27
  pushedAt: ""
36
28
  };
37
29
 
38
- Test.describe("ApiFragments StateViewSlice projection", () => {
39
- Test.test("ApiFragmentRegistered creates the row pending (registeredAt = updatedAt = event time)", undefined, () => Test.thenStateWithId(Test.whenEvent(Test.givenEvents([]), {
30
+ Test.describe("ApiFragments projection", () => {
31
+ Test.test("ApiFragmentRegistered creates the row pending (registeredAt = updatedAt = event `at`)", undefined, () => Test.thenStateWithId(Test.whenEvent(Test.givenEvents([]), {
40
32
  TAG: "ApiFragmentRegistered",
41
33
  pluginId: "p1",
42
34
  fragment: fragment1,
43
35
  apiTarget: "Domain",
44
36
  at: "t0"
45
37
  }), "p1", registeredState));
38
+ Test.test("ApiSchemaComputed is ignored (no row change)", undefined, () => Test.thenStateWithId(Test.whenEvent(Test.givenEvents([{
39
+ TAG: "ApiFragmentRegistered",
40
+ pluginId: "p1",
41
+ fragment: fragment1,
42
+ apiTarget: "Domain",
43
+ at: "t0"
44
+ }]), {
45
+ TAG: "ApiSchemaComputed",
46
+ snapshot: []
47
+ }), "p1", registeredState));
46
48
  Test.test("ApiFragmentPushRecorded(ok) marks the row ok with the push time", undefined, () => Test.thenStateWithId(Test.whenEvent(Test.givenEvents([{
47
49
  TAG: "ApiFragmentRegistered",
48
50
  pluginId: "p1",
@@ -1,103 +0,0 @@
1
- // StateChangeSlice GWT for the ApiFragmentRegistry write side (the platform API-schema
2
- // fragment registry replacing the deploy-schema:* keyspace —
3
- // event-sourced-fragment-registries plan).
4
- open ReventlessCore
5
- open Reventless.Plugin // Domain / Platform constructors of apiTarget
6
-
7
- module Test = ReventlessGwt.Behavior_GWT.Make(ApiFragmentRegistry, ApiFragmentRegistry_Behavior)
8
- open Test
9
- open ApiFragmentRegistry
10
-
11
- let fragment1: Reventless.Plugin.apiSchemaFragment = {
12
- encoded: `{"types":["type Catalog_Product { id: ID! }"],"mutations":[],"queries":[],"subscriptions":[],"subscriptionSources":[]}`,
13
- protocol: "graphql",
14
- }
15
-
16
- let fragment2: Reventless.Plugin.apiSchemaFragment = {
17
- encoded: `{"types":["type Catalog_Product { id: ID!\\n name: String! }"],"mutations":[],"queries":[],"subscriptions":[],"subscriptionSources":[]}`,
18
- protocol: "graphql",
19
- }
20
-
21
- describe("ApiFragmentRegistry StateChangeSlice", () => {
22
- test("register on empty state emits ApiFragmentRegistered", () =>
23
- givenEvents([])
24
- ->whenCmd(RegisterApiFragment({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t0"}))
25
- ->thenEvent(ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t0"}))
26
- )
27
-
28
- test("re-registering an identical fragment + target is idempotent (no event)", () =>
29
- givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t0"})])
30
- ->whenCmd(RegisterApiFragment({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t1"}))
31
- ->thenNoEvent
32
- )
33
-
34
- test("registering a changed fragment emits ApiFragmentUpdated (version supersession)", () =>
35
- givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t0"})])
36
- ->whenCmd(RegisterApiFragment({pluginId: "p1", fragment: fragment2, apiTarget: Domain, at: "t1"}))
37
- ->thenEvent(
38
- ApiFragmentUpdated({
39
- pluginId: "p1",
40
- previousFragment: fragment1,
41
- newFragment: fragment2,
42
- apiTarget: Domain,
43
- at: "t1",
44
- }),
45
- )
46
- )
47
-
48
- test("retargeting a plugin with an identical fragment emits ApiFragmentUpdated", () =>
49
- givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t0"})])
50
- ->whenCmd(RegisterApiFragment({pluginId: "p1", fragment: fragment1, apiTarget: Platform, at: "t1"}))
51
- ->thenEvent(
52
- ApiFragmentUpdated({
53
- pluginId: "p1",
54
- previousFragment: fragment1,
55
- newFragment: fragment1,
56
- apiTarget: Platform,
57
- at: "t1",
58
- }),
59
- )
60
- )
61
-
62
- test("deregister emits ApiFragmentDeregistered", () =>
63
- givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t0"})])
64
- ->whenCmd(DeregisterApiFragment({pluginId: "p1"}))
65
- ->thenEvent(ApiFragmentDeregistered({pluginId: "p1"}))
66
- )
67
-
68
- test("deregistering an absent fragment is idempotent (no event)", () =>
69
- givenEvents([])
70
- ->whenCmd(DeregisterApiFragment({pluginId: "p1"}))
71
- ->thenNoEvent
72
- )
73
-
74
- test("recording a push outcome emits ApiFragmentPushRecorded", () =>
75
- givenEvents([ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t0"})])
76
- ->whenCmd(RecordApiFragmentPush({pluginId: "p1", ok: true, message: "", at: "t1"}))
77
- ->thenEvent(ApiFragmentPushRecorded({pluginId: "p1", ok: true, message: "", at: "t1"}))
78
- )
79
-
80
- test("redelivering an identical push record is idempotent (no event)", () =>
81
- givenEvents([
82
- ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t0"}),
83
- ApiFragmentPushRecorded({pluginId: "p1", ok: true, message: "", at: "t1"}),
84
- ])
85
- ->whenCmd(RecordApiFragmentPush({pluginId: "p1", ok: true, message: "", at: "t1"}))
86
- ->thenNoEvent
87
- )
88
-
89
- test("a later distinct push outcome is recorded again", () =>
90
- givenEvents([
91
- ApiFragmentRegistered({pluginId: "p1", fragment: fragment1, apiTarget: Domain, at: "t0"}),
92
- ApiFragmentPushRecorded({pluginId: "p1", ok: true, message: "", at: "t1"}),
93
- ])
94
- ->whenCmd(RecordApiFragmentPush({pluginId: "p1", ok: false, message: "stitch failed", at: "t2"}))
95
- ->thenEvent(ApiFragmentPushRecorded({pluginId: "p1", ok: false, message: "stitch failed", at: "t2"}))
96
- )
97
-
98
- test("recording a push for an unregistered plugin is dropped (no event)", () =>
99
- givenEvents([])
100
- ->whenCmd(RecordApiFragmentPush({pluginId: "p1", ok: true, message: "", at: "t1"}))
101
- ->thenNoEvent
102
- )
103
- })