@reventlessdev/reventless-local 3.0.0-alpha.319 → 3.0.0-alpha.320

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 (46) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/package.json +11 -11
  3. package/src/Platform.res +13 -0
  4. package/src/Platform.res.mjs +14 -0
  5. package/src/adapter/Api/LocalEvents_Server.res +34 -3
  6. package/src/adapter/Api/LocalEvents_Server.res.mjs +31 -3
  7. package/src/adapter/CommandOutcomeLog/CommandOutcomeLogEntry.res +24 -0
  8. package/src/adapter/CommandOutcomeLog/CommandOutcomeLogEntry.res.mjs +28 -0
  9. package/src/adapter/CommandOutcomeLog/CommandOutcomeLogStorage_InMemory.res +26 -0
  10. package/src/adapter/CommandOutcomeLog/CommandOutcomeLogStorage_InMemory.res.mjs +35 -0
  11. package/src/adapter/CommandOutcomeLog/CommandOutcomeLogStorage_Sqlite.res +61 -0
  12. package/src/adapter/CommandOutcomeLog/CommandOutcomeLogStorage_Sqlite.res.mjs +75 -0
  13. package/src/adapter/CommandOutcomeLog/LocalCommandOutcomeLog.res +88 -0
  14. package/src/adapter/CommandOutcomeLog/LocalCommandOutcomeLog.res.mjs +112 -0
  15. package/src/adapter/DomainGraphQL_Server.res +2 -1
  16. package/src/adapter/DomainGraphQL_Server.res.mjs +3 -1
  17. package/src/adapter/LocalFeed.res +16 -2
  18. package/src/adapter/LocalFeed.res.mjs +19 -2
  19. package/src/adapter/LocalIdleExit.res +71 -0
  20. package/src/adapter/LocalIdleExit.res.mjs +96 -0
  21. package/src/adapter/LocalMetrics.res +29 -0
  22. package/src/adapter/LocalMetrics.res.mjs +61 -0
  23. package/src/adapter/LocalPlatformObserve.res +129 -5
  24. package/src/adapter/LocalPlatformObserve.res.mjs +144 -2
  25. package/src/adapter/LocalRequestLog.res +62 -0
  26. package/src/adapter/LocalRequestLog.res.mjs +80 -0
  27. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +11 -3
  28. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +10 -3
  29. package/src/adapter/ViewChangeLog/LocalViewChangeLog.res +95 -0
  30. package/src/adapter/ViewChangeLog/LocalViewChangeLog.res.mjs +116 -0
  31. package/src/adapter/ViewChangeLog/ViewChangeLogEntry.res +33 -0
  32. package/src/adapter/ViewChangeLog/ViewChangeLogEntry.res.mjs +30 -0
  33. package/src/adapter/ViewChangeLog/ViewChangeLogStorage_InMemory.res +28 -0
  34. package/src/adapter/ViewChangeLog/ViewChangeLogStorage_InMemory.res.mjs +40 -0
  35. package/src/adapter/ViewChangeLog/ViewChangeLogStorage_Sqlite.res +76 -0
  36. package/src/adapter/ViewChangeLog/ViewChangeLogStorage_Sqlite.res.mjs +88 -0
  37. package/tests/adapter/GraphQL_SchemaInspectorTest.res +3 -2
  38. package/tests/adapter/GraphQL_SchemaInspectorTest.res.mjs +1 -1
  39. package/tests/adapter/LocalEvents_ServerTest.res +11 -0
  40. package/tests/adapter/LocalEvents_ServerTest.res.mjs +7 -1
  41. package/tests/adapter/LocalIdleExitTest.res +57 -0
  42. package/tests/adapter/LocalIdleExitTest.res.mjs +62 -0
  43. package/tests/adapter/LocalPlatformObserveTest.res +183 -0
  44. package/tests/adapter/LocalPlatformObserveTest.res.mjs +181 -0
  45. package/tests/adapter/LocalRequestLogTest.res +46 -0
  46. package/tests/adapter/LocalRequestLogTest.res.mjs +61 -0
@@ -1,9 +1,11 @@
1
1
  // What the platform API and the platform MCP server offer a tool that watches
2
- // this platform: every event of every log newest first (`Platform_EventLog`),
2
+ // this platform: every event of every log newest first (`Platform_EventLog`), every row
3
+ // change and command outcome kept (`Platform_ViewChanges`, `Platform_CommandOutcomes`),
3
4
  // the log level while running (`Platform_LogLevels`, `Platform_SetLogLevel`),
4
5
  // the log feed and the event tap as subscriptions (`Platform_LogAppended`,
5
- // `Platform_EventAppended`), and the MCP resources `platform/logs/recent` and
6
- // `platform/events`.
6
+ // `Platform_EventAppended`), the metrics counted since start (`Platform_Metrics`),
7
+ // and the MCP resources `platform/logs/recent`, `platform/events` and
8
+ // `platform/metrics`.
7
9
  //
8
10
  // Every field here is admin-checked, also when the platform serves one API for
9
11
  // both, where nothing else would check it.
@@ -76,6 +78,19 @@ let logLevelTypes = [
76
78
  }`,
77
79
  ]
78
80
 
81
+ let metricTypes = [
82
+ `type Platform_MetricCount {
83
+ metric: String!
84
+ plugin: String!
85
+ slice: String!
86
+ unit: String!
87
+ count: Int!
88
+ sum: Float!
89
+ lastAt: String!
90
+ perMinute: [Int!]!
91
+ }`,
92
+ ]
93
+
79
94
  let feedTypes = [
80
95
  `input Platform_FeedFilter {
81
96
  plugins: [String!]
@@ -87,12 +102,50 @@ let feedTypes = [
87
102
  ]
88
103
 
89
104
  let eventLogField = "Platform_EventLog"
105
+ let viewChangesField = "Platform_ViewChanges"
106
+ let commandOutcomesField = "Platform_CommandOutcomes"
107
+
108
+ // Every row change kept since the store began, newest first; each node is the tap's
109
+ // `viewChange` line as JSON.
110
+ let viewChangeTypes = [
111
+ `input Platform_ViewChangeFilter {
112
+ correlationId: String
113
+ msgId: String
114
+ view: String
115
+ key: String
116
+ }`,
117
+ `type Platform_ViewChangeEdge {
118
+ node: AWSJSON!
119
+ cursor: String!
120
+ }`,
121
+ `type Platform_ViewChangeConnection {
122
+ edges: [Platform_ViewChangeEdge!]!
123
+ pageInfo: Platform_EventLogPageInfo!
124
+ }`,
125
+ // Every command outcome kept, newest first; each node is the tap's `commandOutcome` line.
126
+ `input Platform_CommandOutcomeFilter {
127
+ correlationId: String
128
+ msgId: String
129
+ }`,
130
+ `type Platform_CommandOutcomeEdge {
131
+ node: AWSJSON!
132
+ cursor: String!
133
+ }`,
134
+ `type Platform_CommandOutcomeConnection {
135
+ edges: [Platform_CommandOutcomeEdge!]!
136
+ pageInfo: Platform_EventLogPageInfo!
137
+ }`,
138
+ ]
90
139
  let logLevelsField = "Platform_LogLevels"
91
140
  let setLogLevelField = "Platform_SetLogLevel"
141
+ let metricsField = "Platform_Metrics"
92
142
 
93
143
  let queryFields = [
94
144
  ` ${eventLogField}(filter: Platform_EventLogFilter, first: Int, after: String): Platform_EventLogConnection!`,
145
+ ` ${viewChangesField}(filter: Platform_ViewChangeFilter, first: Int, after: String): Platform_ViewChangeConnection!`,
146
+ ` ${commandOutcomesField}(filter: Platform_CommandOutcomeFilter, first: Int, after: String): Platform_CommandOutcomeConnection!`,
95
147
  ` ${logLevelsField}: Platform_LogLevels!`,
148
+ ` ${metricsField}(plugins: [String!]): [Platform_MetricCount!]!`,
96
149
  ]
97
150
 
98
151
  let logAppendedField = "Platform_LogAppended"
@@ -145,6 +198,43 @@ let eventLog: GQL.resolverFn = async (_root, args, _ctx) => {
145
198
  pageJson(page)
146
199
  }
147
200
 
201
+ /** A page of kept lines as a connection: each node the tap's line, each cursor its place. */
202
+ let keptPageJson = (page: LocalViewChangeLog.page): JSON.t =>
203
+ Dict.fromArray([
204
+ (
205
+ "edges",
206
+ page.records
207
+ ->Array.map(({item, cursor}) =>
208
+ Dict.fromArray([("node", item), ("cursor", JSON.Encode.string(cursor))])->JSON.Encode.object
209
+ )
210
+ ->JSON.Encode.array,
211
+ ),
212
+ (
213
+ "pageInfo",
214
+ Dict.fromArray([
215
+ ("hasNextPage", JSON.Encode.bool(page.hasNextPage)),
216
+ ("endCursor", page.endCursor->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
217
+ ])->JSON.Encode.object,
218
+ ),
219
+ ])->JSON.Encode.object
220
+
221
+ let firstOf = args => arg(args, "first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
222
+ let afterOf = args => arg(args, "after")->Option.flatMap(JSON.Decode.string)
223
+
224
+ let viewChanges: GQL.resolverFn = async (_root, args, _ctx) =>
225
+ LocalViewChangeLog.read(
226
+ ~filter=ViewChangeLogEntry.filterOfJson(arg(args, "filter")),
227
+ ~first=?firstOf(args),
228
+ ~after=?afterOf(args),
229
+ )->keptPageJson
230
+
231
+ let commandOutcomes: GQL.resolverFn = async (_root, args, _ctx) =>
232
+ LocalCommandOutcomeLog.read(
233
+ ~filter=CommandOutcomeLogEntry.filterOfJson(arg(args, "filter")),
234
+ ~first=?firstOf(args),
235
+ ~after=?afterOf(args),
236
+ )->keptPageJson
237
+
148
238
  let levelOfWire = (s: string): option<Logger.level> =>
149
239
  switch s {
150
240
  | "DEBUG" => Some(Debug)
@@ -309,6 +399,15 @@ let feedField = (feed: LocalFeed.t): GQL.resolverFn =>
309
399
  resolve: payload => payload,
310
400
  })
311
401
 
402
+ /** The metrics counted since the platform started, of the given plugins or of every one:
403
+ per metric and slice, how often, the sum of their values, and the last 60 minutes. */
404
+ let metrics: GQL.resolverFn = async (_root, args, _ctx) =>
405
+ LocalMetrics.read(
406
+ ~plugins=?arg(args, "plugins")
407
+ ->Option.flatMap(JSON.Decode.array)
408
+ ->Option.map(a => a->Array.filterMap(JSON.Decode.string)),
409
+ )
410
+
312
411
  let adminOnly = (resolvers: array<(string, GQL.resolverFn)>): dict<GQL.resolverFn> =>
313
412
  resolvers
314
413
  ->Array.map(((name, resolver)) => (
@@ -320,11 +419,21 @@ let adminOnly = (resolvers: array<(string, GQL.resolverFn)>): dict<GQL.resolverF
320
419
  /** Registers the fields on the platform API. */
321
420
  let registerGraphQL = (server: GQL.t) => {
322
421
  server.registerTypes(
323
- ~sdlTypes=eventLogTypes->Array.concat(logLevelTypes)->Array.concat(feedTypes),
422
+ ~sdlTypes=eventLogTypes
423
+ ->Array.concat(viewChangeTypes)
424
+ ->Array.concat(logLevelTypes)
425
+ ->Array.concat(metricTypes)
426
+ ->Array.concat(feedTypes),
324
427
  )
325
428
  server.registerQueries(
326
429
  ~sdlFields=queryFields,
327
- ~resolvers=adminOnly([(eventLogField, eventLog), (logLevelsField, logLevels)]),
430
+ ~resolvers=adminOnly([
431
+ (eventLogField, eventLog),
432
+ (viewChangesField, viewChanges),
433
+ (commandOutcomesField, commandOutcomes),
434
+ (logLevelsField, logLevels),
435
+ (metricsField, metrics),
436
+ ]),
328
437
  )
329
438
  server.registerMutations(
330
439
  ~sdlFields=mutationFields,
@@ -343,6 +452,7 @@ let registerGraphQL = (server: GQL.t) => {
343
452
 
344
453
  let recentLogsUri = "platform/logs/recent"
345
454
  let eventsUri = "platform/events"
455
+ let metricsUri = "platform/metrics"
346
456
 
347
457
  @val external decodeURIComponent: string => string = "decodeURIComponent"
348
458
 
@@ -431,6 +541,10 @@ let events: MCP_ServerInstance.resourceHandler = async uri => {
431
541
  resourceResult(~uri, pageJson(page))
432
542
  }
433
543
 
544
+ /** `platform/metrics?plugins=a,b`: the metrics counted since the platform started. */
545
+ let metricsResource: MCP_ServerInstance.resourceHandler = async uri =>
546
+ resourceResult(~uri, LocalMetrics.read(~plugins=?listOf(queryOf(uri), "plugins")))
547
+
434
548
  /** Registers the resources on the platform MCP server. */
435
549
  let registerMcp = (server: MCP_ServerInstance.t) => {
436
550
  server.registerResource(
@@ -453,4 +567,14 @@ let registerMcp = (server: MCP_ServerInstance.t) => {
453
567
  },
454
568
  ~handler=events,
455
569
  )
570
+ server.registerResource(
571
+ ~name=metricsUri,
572
+ ~definition={
573
+ uriTemplate: metricsUri,
574
+ name: metricsUri,
575
+ description: "The metrics counted since the platform started, per metric, plugin and slice: count, sum of values, last time, and counts of the last 60 minutes. Query parameter: plugins (narrow).",
576
+ mimeType: "application/json",
577
+ },
578
+ ~handler=metricsResource,
579
+ )
456
580
  }
@@ -8,9 +8,14 @@ import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.
8
8
  import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
9
9
  import * as LocalFeed$ReventlessLocal from "./LocalFeed.res.mjs";
10
10
  import * as LocalLogFeed$ReventlessLocal from "./LocalLogFeed.res.mjs";
11
+ import * as LocalMetrics$ReventlessLocal from "./LocalMetrics.res.mjs";
11
12
  import * as LocalEventTap$ReventlessLocal from "./LocalEventTap.res.mjs";
12
13
  import * as LocalEventLogRead$ReventlessLocal from "./LocalEventLogRead.res.mjs";
14
+ import * as LocalViewChangeLog$ReventlessLocal from "./ViewChangeLog/LocalViewChangeLog.res.mjs";
15
+ import * as ViewChangeLogEntry$ReventlessLocal from "./ViewChangeLog/ViewChangeLogEntry.res.mjs";
13
16
  import * as Auth_GraphqlContext$ReventlessLocal from "./Auth/Auth_GraphqlContext.res.mjs";
17
+ import * as CommandOutcomeLogEntry$ReventlessLocal from "./CommandOutcomeLog/CommandOutcomeLogEntry.res.mjs";
18
+ import * as LocalCommandOutcomeLog$ReventlessLocal from "./CommandOutcomeLog/LocalCommandOutcomeLog.res.mjs";
14
19
 
15
20
  let eventLogTypes = [
16
21
  "scalar AWSJSON",
@@ -75,6 +80,17 @@ let logLevelTypes = [
75
80
  }`
76
81
  ];
77
82
 
83
+ let metricTypes = [`type Platform_MetricCount {
84
+ metric: String!
85
+ plugin: String!
86
+ slice: String!
87
+ unit: String!
88
+ count: Int!
89
+ sum: Float!
90
+ lastAt: String!
91
+ perMinute: [Int!]!
92
+ }`];
93
+
78
94
  let feedTypes = [`input Platform_FeedFilter {
79
95
  plugins: [String!]
80
96
  level: Platform_LogLevel
@@ -85,13 +101,51 @@ let feedTypes = [`input Platform_FeedFilter {
85
101
 
86
102
  let eventLogField = "Platform_EventLog";
87
103
 
104
+ let viewChangesField = "Platform_ViewChanges";
105
+
106
+ let commandOutcomesField = "Platform_CommandOutcomes";
107
+
108
+ let viewChangeTypes = [
109
+ `input Platform_ViewChangeFilter {
110
+ correlationId: String
111
+ msgId: String
112
+ view: String
113
+ key: String
114
+ }`,
115
+ `type Platform_ViewChangeEdge {
116
+ node: AWSJSON!
117
+ cursor: String!
118
+ }`,
119
+ `type Platform_ViewChangeConnection {
120
+ edges: [Platform_ViewChangeEdge!]!
121
+ pageInfo: Platform_EventLogPageInfo!
122
+ }`,
123
+ `input Platform_CommandOutcomeFilter {
124
+ correlationId: String
125
+ msgId: String
126
+ }`,
127
+ `type Platform_CommandOutcomeEdge {
128
+ node: AWSJSON!
129
+ cursor: String!
130
+ }`,
131
+ `type Platform_CommandOutcomeConnection {
132
+ edges: [Platform_CommandOutcomeEdge!]!
133
+ pageInfo: Platform_EventLogPageInfo!
134
+ }`
135
+ ];
136
+
88
137
  let logLevelsField = "Platform_LogLevels";
89
138
 
90
139
  let setLogLevelField = "Platform_SetLogLevel";
91
140
 
141
+ let metricsField = "Platform_Metrics";
142
+
92
143
  let queryFields = [
93
144
  ` ` + eventLogField + `(filter: Platform_EventLogFilter, first: Int, after: String): Platform_EventLogConnection!`,
94
- ` ` + logLevelsField + `: Platform_LogLevels!`
145
+ ` ` + viewChangesField + `(filter: Platform_ViewChangeFilter, first: Int, after: String): Platform_ViewChangeConnection!`,
146
+ ` ` + commandOutcomesField + `(filter: Platform_CommandOutcomeFilter, first: Int, after: String): Platform_CommandOutcomeConnection!`,
147
+ ` ` + logLevelsField + `: Platform_LogLevels!`,
148
+ ` ` + metricsField + `(plugins: [String!]): [Platform_MetricCount!]!`
95
149
  ];
96
150
 
97
151
  let logAppendedField = "Platform_LogAppended";
@@ -144,6 +198,53 @@ async function eventLog(_root, args, _ctx) {
144
198
  return pageJson(await LocalEventLogRead$ReventlessLocal.read(LocalEventLogRead$ReventlessLocal.filterOfJson(arg(args, "filter")), Stdlib_Option.map(Stdlib_Option.flatMap(arg(args, "first"), Stdlib_JSON.Decode.float), prim => prim | 0), Stdlib_Option.flatMap(arg(args, "after"), Stdlib_JSON.Decode.string)));
145
199
  }
146
200
 
201
+ function keptPageJson(page) {
202
+ return Object.fromEntries([
203
+ [
204
+ "edges",
205
+ page.records.map(param => Object.fromEntries([
206
+ [
207
+ "node",
208
+ param.item
209
+ ],
210
+ [
211
+ "cursor",
212
+ param.cursor
213
+ ]
214
+ ]))
215
+ ],
216
+ [
217
+ "pageInfo",
218
+ Object.fromEntries([
219
+ [
220
+ "hasNextPage",
221
+ page.hasNextPage
222
+ ],
223
+ [
224
+ "endCursor",
225
+ Stdlib_Option.mapOr(page.endCursor, null, prim => prim)
226
+ ]
227
+ ])
228
+ ]
229
+ ]);
230
+ }
231
+
232
+ function firstOf(args) {
233
+ return Stdlib_Option.map(Stdlib_Option.flatMap(arg(args, "first"), Stdlib_JSON.Decode.float), prim => prim | 0);
234
+ }
235
+
236
+ function afterOf(args) {
237
+ return Stdlib_Option.flatMap(arg(args, "after"), Stdlib_JSON.Decode.string);
238
+ }
239
+
240
+ async function viewChanges(_root, args, _ctx) {
241
+ return keptPageJson(LocalViewChangeLog$ReventlessLocal.read(ViewChangeLogEntry$ReventlessLocal.filterOfJson(arg(args, "filter")), firstOf(args), afterOf(args)));
242
+ }
243
+
244
+ async function commandOutcomes(_root, args, _ctx) {
245
+ return keptPageJson(LocalCommandOutcomeLog$ReventlessLocal.read(CommandOutcomeLogEntry$ReventlessLocal.filterOfJson(arg(args, "filter")), firstOf(args), afterOf(args)));
246
+ }
247
+
147
248
  function levelOfWire(s) {
148
249
  switch (s) {
149
250
  case "DEBUG" :
@@ -311,6 +412,10 @@ function feedField(feed) {
311
412
  };
312
413
  }
313
414
 
415
+ async function metrics(_root, args, _ctx) {
416
+ return LocalMetrics$ReventlessLocal.read(Stdlib_Option.map(Stdlib_Option.flatMap(arg(args, "plugins"), Stdlib_JSON.Decode.array), a => Stdlib_Array.filterMap(a, Stdlib_JSON.Decode.string)));
417
+ }
418
+
314
419
  function adminOnly(resolvers) {
315
420
  return Object.fromEntries(resolvers.map(param => [
316
421
  param[0],
@@ -319,15 +424,27 @@ function adminOnly(resolvers) {
319
424
  }
320
425
 
321
426
  function registerGraphQL(server) {
322
- server.registerTypes(eventLogTypes.concat(logLevelTypes).concat(feedTypes));
427
+ server.registerTypes(eventLogTypes.concat(viewChangeTypes).concat(logLevelTypes).concat(metricTypes).concat(feedTypes));
323
428
  server.registerQueries(queryFields, adminOnly([
324
429
  [
325
430
  eventLogField,
326
431
  eventLog
327
432
  ],
433
+ [
434
+ viewChangesField,
435
+ viewChanges
436
+ ],
437
+ [
438
+ commandOutcomesField,
439
+ commandOutcomes
440
+ ],
328
441
  [
329
442
  logLevelsField,
330
443
  logLevels
444
+ ],
445
+ [
446
+ metricsField,
447
+ metrics
331
448
  ]
332
449
  ]));
333
450
  server.registerMutations(mutationFields, adminOnly([[
@@ -350,6 +467,8 @@ let recentLogsUri = "platform/logs/recent";
350
467
 
351
468
  let eventsUri = "platform/events";
352
469
 
470
+ let metricsUri = "platform/metrics";
471
+
353
472
  function queryOf(uri) {
354
473
  let params = {};
355
474
  let qs = uri.split("?")[1];
@@ -440,6 +559,10 @@ async function events(uri) {
440
559
  return resourceResult(uri, pageJson(page));
441
560
  }
442
561
 
562
+ async function metricsResource(uri) {
563
+ return resourceResult(uri, LocalMetrics$ReventlessLocal.read(listOf(queryOf(uri), "plugins")));
564
+ }
565
+
443
566
  function registerMcp(server) {
444
567
  server.registerResource(recentLogsUri, {
445
568
  uriTemplate: recentLogsUri,
@@ -453,6 +576,12 @@ function registerMcp(server) {
453
576
  description: "Every event of every log, newest first, one page at a time. Query parameters: plugins, logs, eventTypes, entityId, tagKey, tagValue, msgId, correlationId, causationId, user, timeFrom, timeTo, first, after (the page's endCursor).",
454
577
  mimeType: "application/json"
455
578
  }, events);
579
+ server.registerResource(metricsUri, {
580
+ uriTemplate: metricsUri,
581
+ name: metricsUri,
582
+ description: "The metrics counted since the platform started, per metric, plugin and slice: count, sum of values, last time, and counts of the last 60 minutes. Query parameter: plugins (narrow).",
583
+ mimeType: "application/json"
584
+ }, metricsResource);
456
585
  }
457
586
 
458
587
  let Logger;
@@ -466,10 +595,15 @@ export {
466
595
  GQL,
467
596
  eventLogTypes,
468
597
  logLevelTypes,
598
+ metricTypes,
469
599
  feedTypes,
470
600
  eventLogField,
601
+ viewChangesField,
602
+ commandOutcomesField,
603
+ viewChangeTypes,
471
604
  logLevelsField,
472
605
  setLogLevelField,
606
+ metricsField,
473
607
  queryFields,
474
608
  logAppendedField,
475
609
  eventAppendedField,
@@ -478,6 +612,11 @@ export {
478
612
  arg,
479
613
  pageJson,
480
614
  eventLog,
615
+ keptPageJson,
616
+ firstOf,
617
+ afterOf,
618
+ viewChanges,
619
+ commandOutcomes,
481
620
  levelOfWire,
482
621
  levelsJson,
483
622
  logLevels,
@@ -490,16 +629,19 @@ export {
490
629
  lineToJson,
491
630
  feedSubscription,
492
631
  feedField,
632
+ metrics,
493
633
  adminOnly,
494
634
  registerGraphQL,
495
635
  recentLogsUri,
496
636
  eventsUri,
637
+ metricsUri,
497
638
  queryOf,
498
639
  listOf,
499
640
  intOf,
500
641
  resourceResult,
501
642
  recentLogs,
502
643
  events,
644
+ metricsResource,
503
645
  registerMcp,
504
646
  }
505
647
  /* log Not a pure module */
@@ -0,0 +1,62 @@
1
+ // One log line per GraphQL operation the app's API answers: a query or a subscription at debug, a
2
+ // mutation at info, one that answered with errors at warn, so it shows among the problems.
3
+
4
+ module YG = GraphqlYoga
5
+ module L = ReventlessCore.Logger
6
+
7
+ type line = {level: L.level, message: string, data: JSON.t}
8
+
9
+ let lineOf = (r: YG.requestReport): line => {
10
+ let name = switch (r.operationName, r.fields) {
11
+ | (Some(n), _) => n
12
+ | (None, []) => "(anonymous)"
13
+ | (None, fields) => fields->Array.join(", ")
14
+ }
15
+ let who = r.user->Option.mapOr("", u => ` by ${u}`)
16
+ let took = r.kind == "subscription" ? "" : ` in ${r.ms->Float.toString} ms`
17
+ let message = switch r.errors->Array.get(0) {
18
+ | None => `${r.kind} ${name}${who}${took}`
19
+ | Some(first) =>
20
+ let more = r.errors->Array.length - 1
21
+ `${r.kind} ${name}${who} failed${took}: ${first}${more > 0
22
+ ? ` (and ${more->Int.toString} more)`
23
+ : ""}`
24
+ }
25
+ let level: L.level = if r.errors != [] {
26
+ Warn
27
+ } else if r.kind == "mutation" {
28
+ Info
29
+ } else {
30
+ Debug
31
+ }
32
+ let data = JSON.Encode.object(
33
+ Dict.fromArray(
34
+ [
35
+ ("operation", JSON.Encode.string(r.kind)),
36
+ ("fields", JSON.Encode.array(r.fields->Array.map(JSON.Encode.string))),
37
+ ("ms", JSON.Encode.float(r.ms)),
38
+ ]
39
+ ->Array.concat(
40
+ r.operationName->Option.mapOr([], n => [("operationName", JSON.Encode.string(n))]),
41
+ )
42
+ ->Array.concat(r.user->Option.mapOr([], u => [("user", JSON.Encode.string(u))]))
43
+ ->Array.concat(
44
+ r.errors == []
45
+ ? []
46
+ : [("errors", JSON.Encode.array(r.errors->Array.map(JSON.Encode.string)))],
47
+ ),
48
+ ),
49
+ )
50
+ {level, message, data}
51
+ }
52
+
53
+ /** The plugin that writes the line under the API's component, its fields as the line's detail
54
+ rather than in its words. */
55
+ let plugin = (): YG.plugin =>
56
+ YG.requestLog(r => {
57
+ let {level, message, data} = lineOf(r)
58
+ let comp = "GraphQL:Domain"
59
+ if L.shouldLog(level, ~comp, ()) {
60
+ L.emit(~level, ~comp, ~detail=data, message)
61
+ }
62
+ })
@@ -0,0 +1,80 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as GraphqlYoga from "@reventlessdev/rescript-graphql-yoga/src/GraphqlYoga.res.mjs";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
6
+ import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
7
+
8
+ function lineOf(r) {
9
+ let match = r.operationName;
10
+ let match$1 = r.fields;
11
+ let name = match !== undefined ? match : (
12
+ match$1.length !== 0 ? match$1.join(", ") : "(anonymous)"
13
+ );
14
+ let who = Stdlib_Option.mapOr(r.user, "", u => ` by ` + u);
15
+ let took = r.kind === "subscription" ? "" : ` in ` + r.ms.toString() + ` ms`;
16
+ let first = r.errors[0];
17
+ let message;
18
+ if (first !== undefined) {
19
+ let more = r.errors.length - 1 | 0;
20
+ message = r.kind + ` ` + name + who + ` failed` + took + `: ` + first + (
21
+ more > 0 ? ` (and ` + more.toString() + ` more)` : ""
22
+ );
23
+ } else {
24
+ message = r.kind + ` ` + name + who + took;
25
+ }
26
+ let level = Primitive_object.notequal(r.errors, []) ? "Warn" : (
27
+ r.kind === "mutation" ? "Info" : "Debug"
28
+ );
29
+ let data = Object.fromEntries([
30
+ [
31
+ "operation",
32
+ r.kind
33
+ ],
34
+ [
35
+ "fields",
36
+ r.fields.map(prim => prim)
37
+ ],
38
+ [
39
+ "ms",
40
+ r.ms
41
+ ]
42
+ ].concat(Stdlib_Option.mapOr(r.operationName, [], n => [[
43
+ "operationName",
44
+ n
45
+ ]])).concat(Stdlib_Option.mapOr(r.user, [], u => [[
46
+ "user",
47
+ u
48
+ ]])).concat(Primitive_object.equal(r.errors, []) ? [] : [[
49
+ "errors",
50
+ r.errors.map(prim => prim)
51
+ ]]));
52
+ return {
53
+ level: level,
54
+ message: message,
55
+ data: data
56
+ };
57
+ }
58
+
59
+ function plugin() {
60
+ return GraphqlYoga.requestLog(r => {
61
+ let match = lineOf(r);
62
+ let level = match.level;
63
+ let comp = "GraphQL:Domain";
64
+ if (Logger$ReventlessCore.shouldLog(level, undefined, comp, undefined)) {
65
+ return Logger$ReventlessCore.emit(level, comp, undefined, match.data, undefined, match.message);
66
+ }
67
+ });
68
+ }
69
+
70
+ let YG;
71
+
72
+ let L;
73
+
74
+ export {
75
+ YG,
76
+ L,
77
+ lineOf,
78
+ plugin,
79
+ }
80
+ /* GraphqlYoga Not a pure module */
@@ -1221,9 +1221,17 @@ module Make = (Bus: LocalBus.T) => {
1221
1221
  fieldResolvers->Dict.set(resolvedField, resolver)
1222
1222
  })
1223
1223
 
1224
- if fieldResolvers->Dict.keysToArray->Array.length > 0 {
1225
- server.registerFieldResolvers(~typeName=returnTypeName, ~resolvers=fieldResolvers)
1226
- }
1224
+ // A row's last change is read by an elevated caller only; anyone else reads null.
1225
+ let changeResolver: ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn = async (
1226
+ root,
1227
+ _args,
1228
+ ctx,
1229
+ ) =>
1230
+ extractIdentity(ctx)->Reventless.OwnerScope.resolve->Reventless.OwnerScope.isExempt
1231
+ ? root->sourceField(ReventlessCore.RowChange.key)->Option.getOr(JSON.Encode.null)
1232
+ : JSON.Encode.null
1233
+ fieldResolvers->Dict.set(ReventlessCore.RowChange.key, changeResolver)
1234
+ server.registerFieldResolvers(~typeName=returnTypeName, ~resolvers=fieldResolvers)
1227
1235
 
1228
1236
  // -- Register all fields --------------------------------------------------
1229
1237
  let allSdl =
@@ -16,6 +16,7 @@ import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/compon
16
16
  import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
17
17
  import * as Api_Ids$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/Api_Ids.res.mjs";
18
18
  import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
19
+ import * as RowChange$ReventlessCore from "@reventlessdev/reventless-core/src/RowChange.res.mjs";
19
20
  import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
20
21
  import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
21
22
  import * as SortKey_Filter$ReventlessLocal from "./SortKey_Filter.res.mjs";
@@ -749,9 +750,15 @@ function Make(Bus) {
749
750
  };
750
751
  fieldResolvers[match.resolvedField] = resolver;
751
752
  });
752
- if (Object.keys(fieldResolvers).length !== 0) {
753
- server.registerFieldResolvers(returnTypeName, fieldResolvers);
754
- }
753
+ let changeResolver = async (root, _args, ctx) => {
754
+ if (OwnerScope$Reventless.isExempt(OwnerScope$Reventless.resolve(extractIdentity(ctx), undefined))) {
755
+ return Stdlib_Option.getOr(sourceField(root, RowChange$ReventlessCore.key), null);
756
+ } else {
757
+ return null;
758
+ }
759
+ };
760
+ fieldResolvers[RowChange$ReventlessCore.key] = changeResolver;
761
+ server.registerFieldResolvers(returnTypeName, fieldResolvers);
755
762
  let allSdl = [byIdSdl].concat(byIdsSdl).concat(refsSdl).concat(match[0]).concat(itemsSdl).concat(indexSdlFields);
756
763
  let resolvers = {};
757
764
  resolvers[singleQueryName] = byIdResolver;