@reventlessdev/reventless-local 3.0.0-alpha.317 → 3.0.0-alpha.318

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 (57) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +10 -10
  3. package/src/LocalPlatformRegistry.res +19 -0
  4. package/src/LocalPlatformRegistry.res.mjs +19 -1
  5. package/src/Platform.res +24 -2
  6. package/src/Platform.res.mjs +22 -2
  7. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +38 -0
  8. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res.mjs +29 -0
  9. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +87 -0
  10. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs +76 -0
  11. package/src/adapter/DcbEventLog/LocalDcbEventLogStorage.res +8 -1
  12. package/src/adapter/DcbEventLog/LocalDcbEventLogStorage.res.mjs +17 -4
  13. package/src/adapter/EventLog/EventLogStorage_InMemory.res +49 -2
  14. package/src/adapter/EventLog/EventLogStorage_InMemory.res.mjs +57 -21
  15. package/src/adapter/EventLog/EventLogStorage_Sqlite.res +85 -0
  16. package/src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs +50 -0
  17. package/src/adapter/EventLog/LocalEventLogStorage.res +3 -0
  18. package/src/adapter/EventLog/LocalEventLogStorage.res.mjs +6 -2
  19. package/src/adapter/LocalBus.res +32 -5
  20. package/src/adapter/LocalBus.res.mjs +27 -7
  21. package/src/adapter/LocalEventLogRead.res +477 -0
  22. package/src/adapter/LocalEventLogRead.res.mjs +507 -0
  23. package/src/adapter/LocalEventTap.res +18 -80
  24. package/src/adapter/LocalEventTap.res.mjs +19 -108
  25. package/src/adapter/LocalFeed.res +433 -0
  26. package/src/adapter/LocalFeed.res.mjs +419 -0
  27. package/src/adapter/LocalLogFeed.res +217 -0
  28. package/src/adapter/LocalLogFeed.res.mjs +295 -0
  29. package/src/adapter/LocalPgEventLogRead.res +85 -0
  30. package/src/adapter/LocalPgEventLogRead.res.mjs +91 -0
  31. package/src/adapter/LocalPlatformObserve.res +456 -0
  32. package/src/adapter/LocalPlatformObserve.res.mjs +505 -0
  33. package/src/adapter/LocalTapLines.res +147 -0
  34. package/src/adapter/LocalTapLines.res.mjs +229 -0
  35. package/src/adapter/PlatformGraphQL_Server.res +36 -3
  36. package/src/adapter/PlatformGraphQL_Server.res.mjs +15 -1
  37. package/src/reexport/CommandTopic.res.mjs +5 -2
  38. package/tests/adapter/GraphQLWsFixtures.mjs +46 -0
  39. package/tests/adapter/LocalEventLogReadTest.res +539 -0
  40. package/tests/adapter/LocalEventLogReadTest.res.mjs +466 -0
  41. package/tests/adapter/LocalFeedTest.res +314 -0
  42. package/tests/adapter/LocalFeedTest.res.mjs +329 -0
  43. package/tests/adapter/LocalPlatformObserveTest.res +165 -0
  44. package/tests/adapter/LocalPlatformObserveTest.res.mjs +183 -0
  45. package/tests/adapter/LocalTapLinesTest.res +182 -0
  46. package/tests/adapter/LocalTapLinesTest.res.mjs +242 -0
  47. package/tests/adapter/PlatformSubscriptionsTest.res +123 -0
  48. package/tests/adapter/PlatformSubscriptionsTest.res.mjs +132 -0
  49. package/tests/components/automationslice/AutomationSliceCallbackTest.res +80 -0
  50. package/tests/components/automationslice/AutomationSliceCallbackTest.res.mjs +81 -0
  51. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +17 -0
  52. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +24 -0
  53. package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res +30 -0
  54. package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res.mjs +28 -0
  55. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +58 -0
  56. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +71 -19
  57. package/tests/components/outboundtranslationslice/OutboundTranslationSlicePiiTest.res.mjs +7 -7
@@ -0,0 +1,229 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
5
+ import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
6
+ import * as LogRedaction$Reventless from "@reventlessdev/reventless-spec/src/util/LogRedaction.res.mjs";
7
+ import * as ViewChange$ReventlessCore from "@reventlessdev/reventless-core/src/ViewChange.res.mjs";
8
+ import * as LocalLogFeed$ReventlessLocal from "./LocalLogFeed.res.mjs";
9
+ import * as LocalEventTap$ReventlessLocal from "./LocalEventTap.res.mjs";
10
+ import * as CommandTopic_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Helpers.res.mjs";
11
+
12
+ function str(prim) {
13
+ return prim;
14
+ }
15
+
16
+ function now() {
17
+ return new Date().toISOString();
18
+ }
19
+
20
+ function lineOf(fields) {
21
+ return JSON.stringify(Object.fromEntries(fields));
22
+ }
23
+
24
+ function optional(name, value) {
25
+ return Stdlib_Option.mapOr(value, [], v => [[
26
+ name,
27
+ v
28
+ ]]);
29
+ }
30
+
31
+ function compOf(r) {
32
+ return `CommandTopic(` + r.component + `)`;
33
+ }
34
+
35
+ function pluginOf(r) {
36
+ return Logger$ReventlessCore.resolvePlugin(compOf(r), undefined);
37
+ }
38
+
39
+ function outcomeLine(r) {
40
+ let plugin = pluginOf(r);
41
+ let head = [
42
+ [
43
+ "kind",
44
+ "commandOutcome"
45
+ ],
46
+ [
47
+ "component",
48
+ r.component
49
+ ]
50
+ ].concat(optional("plugin", plugin)).concat([[
51
+ "msgId",
52
+ r.reference
53
+ ]]).concat(optional("correlationId", r.correlationId));
54
+ let match = r.outcome;
55
+ let body;
56
+ body = match.TAG === "OutcomeAccepted" ? [[
57
+ "outcome",
58
+ "accepted"
59
+ ]].concat(optional("entityId", match.entityId)).concat([[
60
+ "eventCount",
61
+ match.eventCount
62
+ ]]) : [
63
+ [
64
+ "outcome",
65
+ "rejected"
66
+ ],
67
+ [
68
+ "errorCode",
69
+ match.errorCode
70
+ ],
71
+ [
72
+ "cause",
73
+ CommandTopic_Helpers$ReventlessCore.refusalCauseToGraphQL(match.cause)
74
+ ]
75
+ ];
76
+ return lineOf(head.concat(body).concat([[
77
+ "ts",
78
+ new Date().toISOString()
79
+ ]]));
80
+ }
81
+
82
+ function rejectionRecord(r) {
83
+ let match = r.outcome;
84
+ if (match.TAG === "OutcomeAccepted") {
85
+ return;
86
+ }
87
+ let cause = match.cause;
88
+ let errorDetail = match.errorDetail;
89
+ let level;
90
+ switch (cause) {
91
+ case "InfrastructureFailure" :
92
+ level = "Error";
93
+ break;
94
+ case "DomainRejection" :
95
+ case "AccessRefusal" :
96
+ level = "Warn";
97
+ break;
98
+ }
99
+ let detailJson;
100
+ if (errorDetail === "") {
101
+ detailJson = [];
102
+ } else {
103
+ let parsed;
104
+ try {
105
+ parsed = JSON.parse(errorDetail);
106
+ } catch (exn) {
107
+ parsed = errorDetail;
108
+ }
109
+ detailJson = [[
110
+ "errorDetail",
111
+ Logger$ReventlessCore.truncateDetail(LogRedaction$Reventless.redactJson(parsed))
112
+ ]];
113
+ }
114
+ let annotations = Object.fromEntries([[
115
+ "msgId",
116
+ r.reference
117
+ ]]);
118
+ Stdlib_Option.forEach(r.correlationId, id => {
119
+ annotations["correlationId"] = id;
120
+ });
121
+ return {
122
+ time: new Date().toISOString(),
123
+ level: level,
124
+ message: "command rejected",
125
+ service: Logger$ReventlessCore.serviceName(),
126
+ plugin: pluginOf(r),
127
+ comp: compOf(r),
128
+ annotations: annotations,
129
+ detail: Object.fromEntries([
130
+ [
131
+ "component",
132
+ r.component
133
+ ],
134
+ [
135
+ "errorCode",
136
+ match.errorCode
137
+ ],
138
+ [
139
+ "cause",
140
+ CommandTopic_Helpers$ReventlessCore.refusalCauseToGraphQL(cause)
141
+ ]
142
+ ].concat(detailJson))
143
+ };
144
+ }
145
+
146
+ function onOutcome(r) {
147
+ Stdlib_Option.forEach(rejectionRecord(r), LocalLogFeed$ReventlessLocal.keepProblem);
148
+ if (LocalEventTap$ReventlessLocal.hasReaders()) {
149
+ return LocalEventTap$ReventlessLocal.publish({
150
+ line: outcomeLine(r),
151
+ facets: {
152
+ plugin: pluginOf(r),
153
+ correlationId: r.correlationId
154
+ }
155
+ });
156
+ }
157
+ }
158
+
159
+ function viewChangeLine(c) {
160
+ let side = (name, value) => Stdlib_Option.mapOr(value, [], v => [[
161
+ name,
162
+ Logger$ReventlessCore.truncateDetail(LogRedaction$Reventless.redactJson(v))
163
+ ]]);
164
+ return lineOf([[
165
+ "kind",
166
+ "viewChange"
167
+ ]].concat(ViewChange$ReventlessCore.summaryFields(c)).concat(optional("correlationId", c.correlationId)).concat(side("before", c.before)).concat(side("after", c.after)).concat([[
168
+ "ts",
169
+ new Date().toISOString()
170
+ ]]));
171
+ }
172
+
173
+ function onViewChange(c) {
174
+ if (LocalEventTap$ReventlessLocal.hasReaders()) {
175
+ return LocalEventTap$ReventlessLocal.publish({
176
+ line: viewChangeLine(c),
177
+ facets: {
178
+ plugin: Logger$ReventlessCore.resolvePlugin(`View(` + c.view + `)`, undefined),
179
+ correlationId: c.correlationId
180
+ }
181
+ });
182
+ }
183
+ }
184
+
185
+ let removals = {
186
+ contents: []
187
+ };
188
+
189
+ function install() {
190
+ if (Primitive_object.equal(removals.contents, [])) {
191
+ removals.contents = [
192
+ CommandTopic_Helpers$ReventlessCore.registerCommandOutcome(onOutcome),
193
+ ViewChange$ReventlessCore.register(onViewChange)
194
+ ];
195
+ return;
196
+ }
197
+ }
198
+
199
+ function resetForTests() {
200
+ removals.contents.forEach(remove => remove());
201
+ removals.contents = [];
202
+ }
203
+
204
+ let Logger;
205
+
206
+ let Outcome;
207
+
208
+ let ViewChange;
209
+
210
+ export {
211
+ Logger,
212
+ Outcome,
213
+ ViewChange,
214
+ str,
215
+ now,
216
+ lineOf,
217
+ optional,
218
+ compOf,
219
+ pluginOf,
220
+ outcomeLine,
221
+ rejectionRecord,
222
+ onOutcome,
223
+ viewChangeLine,
224
+ onViewChange,
225
+ removals,
226
+ install,
227
+ resetForTests,
228
+ }
229
+ /* Logger-ReventlessCore Not a pure module */
@@ -8,8 +8,8 @@
8
8
  // Does NOT include Relay-specific features (encodeGlobalId, node resolver)
9
9
  // since admin queries are not Relay-paginated.
10
10
  //
11
- // Authorization: every query AND mutation resolver registered here is
12
- // wrapped with `Auth_GraphqlContext.requireGroup(~group="Admin")` so
11
+ // Authorization: every query, mutation AND subscription resolver registered
12
+ // here is wrapped with `Auth_GraphqlContext.requireGroup(~group="Admin")` so
13
13
  // non-Admin identities are refused before the underlying resolver runs.
14
14
  // Mirrors AppSync's `@aws_cognito_user_pools(cognito_groups: ["Admin"])`
15
15
  // directive that gates Platform_* fields in the AWS adapter.
@@ -46,11 +46,44 @@ let wrapAdmin = (resolvers: dict<ReventlessGraphqlServer.GraphQL_ServerInstance.
46
46
  wrapped
47
47
  }
48
48
 
49
+ // A subscription field's resolver is an object: `subscribe` starts the stream,
50
+ // `resolve` maps each payload. The group check goes on `subscribe`, so a caller
51
+ // without the group is refused before anything is streamed to it.
52
+ type subscriptionResolver = {
53
+ subscribe: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn,
54
+ resolve?: JSON.t => JSON.t,
55
+ }
56
+ external asSubscription: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn => subscriptionResolver =
57
+ "%identity"
58
+ external ofSubscription: subscriptionResolver => ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn =
59
+ "%identity"
60
+
61
+ let wrapAdminSubscriptions = (
62
+ resolvers: dict<ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn>,
63
+ ): dict<ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn> =>
64
+ resolvers
65
+ ->Dict.toArray
66
+ ->Array.map(((k, v)) => {
67
+ let sub = asSubscription(v)
68
+ (
69
+ k,
70
+ ofSubscription({
71
+ ...sub,
72
+ subscribe: Auth_GraphqlContext.requireGroup(
73
+ ~group=Reventless.Role.adminGroup(),
74
+ sub.subscribe,
75
+ ),
76
+ }),
77
+ )
78
+ })
79
+ ->Dict.fromArray
80
+
49
81
  let registerMutations = (~sdlFields, ~resolvers) =>
50
82
  instance.registerMutations(~sdlFields, ~resolvers=wrapAdmin(resolvers))
51
83
  let registerQueries = (~sdlFields, ~resolvers) =>
52
84
  instance.registerQueries(~sdlFields, ~resolvers=wrapAdmin(resolvers))
53
- let registerSubscriptions = instance.registerSubscriptions
85
+ let registerSubscriptions = (~sdlFields, ~resolvers) =>
86
+ instance.registerSubscriptions(~sdlFields, ~resolvers=wrapAdminSubscriptions(resolvers))
54
87
  let registerTypes = instance.registerTypes
55
88
  // Not admin-wrapped: a field resolver is only reached through a parent field
56
89
  // that already met the group check, so wrapping would re-refuse a caller the
@@ -14,6 +14,17 @@ function wrapAdmin(resolvers) {
14
14
  return wrapped;
15
15
  }
16
16
 
17
+ function wrapAdminSubscriptions(resolvers) {
18
+ return Object.fromEntries(Object.entries(resolvers).map(param => {
19
+ let v = param[1];
20
+ let newrecord = {...v};
21
+ return [
22
+ param[0],
23
+ (newrecord.subscribe = Auth_GraphqlContext$ReventlessLocal.requireGroup(Role$Reventless.adminGroup(), v.subscribe), newrecord)
24
+ ];
25
+ }));
26
+ }
27
+
17
28
  function registerMutations(sdlFields, resolvers) {
18
29
  instance.registerMutations(sdlFields, wrapAdmin(resolvers));
19
30
  }
@@ -22,7 +33,9 @@ function registerQueries(sdlFields, resolvers) {
22
33
  instance.registerQueries(sdlFields, wrapAdmin(resolvers));
23
34
  }
24
35
 
25
- let registerSubscriptions = instance.registerSubscriptions;
36
+ function registerSubscriptions(sdlFields, resolvers) {
37
+ instance.registerSubscriptions(sdlFields, wrapAdminSubscriptions(resolvers));
38
+ }
26
39
 
27
40
  let registerTypes = instance.registerTypes;
28
41
 
@@ -69,6 +82,7 @@ let asInterface = {
69
82
  export {
70
83
  instance,
71
84
  wrapAdmin,
85
+ wrapAdminSubscriptions,
72
86
  registerMutations,
73
87
  registerQueries,
74
88
  registerSubscriptions,
@@ -8,7 +8,9 @@ let NotPublishedToChannel = CommandTopic$ReventlessCore.NotPublishedToChannel;
8
8
 
9
9
  let refusalCauseToGraphQL = CommandTopic$ReventlessCore.refusalCauseToGraphQL;
10
10
 
11
- let commandOutcomeHook = CommandTopic$ReventlessCore.commandOutcomeHook;
11
+ let commandOutcomeHooks = CommandTopic$ReventlessCore.commandOutcomeHooks;
12
+
13
+ let nextOutcomeHookId = CommandTopic$ReventlessCore.nextOutcomeHookId;
12
14
 
13
15
  let registerCommandOutcome = CommandTopic$ReventlessCore.registerCommandOutcome;
14
16
 
@@ -50,7 +52,8 @@ export {
50
52
  componentType,
51
53
  NotPublishedToChannel,
52
54
  refusalCauseToGraphQL,
53
- commandOutcomeHook,
55
+ commandOutcomeHooks,
56
+ nextOutcomeHookId,
54
57
  registerCommandOutcome,
55
58
  clearCommandOutcome,
56
59
  fireCommandOutcome,
@@ -0,0 +1,46 @@
1
+ // A graphql-ws client for the subscription tests, here rather than in an
2
+ // `Obj.magic` inside the test, per the repo convention that untyped reflection
3
+ // lives in a companion `.mjs`.
4
+ import { createClient } from 'graphql-ws'
5
+ import WebSocket from 'ws'
6
+
7
+ /** Subscribes, collects values until `count` arrive (or an error or `timeoutMs`),
8
+ calling `onStarted` once the server acknowledged the connection. Resolves
9
+ `{ values, error }`; `error` is the first error message, if any. */
10
+ export const collect = (url, query, connectionParams, count, timeoutMs, onStarted) =>
11
+ new Promise(resolve => {
12
+ const values = []
13
+ let settled = false
14
+ const client = createClient({
15
+ url,
16
+ webSocketImpl: WebSocket,
17
+ connectionParams,
18
+ lazy: false,
19
+ retryAttempts: 0,
20
+ on: { connected: () => onStarted() },
21
+ })
22
+ const finish = error => {
23
+ if (settled) return
24
+ settled = true
25
+ clearTimeout(timer)
26
+ dispose()
27
+ client.dispose()
28
+ resolve({ values, error })
29
+ }
30
+ const timer = setTimeout(() => finish(undefined), timeoutMs)
31
+ const dispose = client.subscribe(
32
+ { query },
33
+ {
34
+ next: msg => {
35
+ if (msg.errors && msg.errors.length > 0) return finish(String(msg.errors[0].message))
36
+ values.push(Object.values(msg.data)[0])
37
+ if (values.length >= count) finish(undefined)
38
+ },
39
+ error: err => {
40
+ const m = Array.isArray(err) ? err[0]?.message : err?.message ?? err?.reason ?? String(err)
41
+ finish(String(m))
42
+ },
43
+ complete: () => finish(undefined),
44
+ },
45
+ )
46
+ })