@powerhousedao/reactor-api 1.9.4 → 1.10.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/reactor-api",
3
- "version": "1.9.4",
3
+ "version": "1.10.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,20 +13,23 @@
13
13
  "author": "",
14
14
  "license": "AGPL-3.0-only",
15
15
  "devDependencies": {
16
+ "@powerhousedao/analytics-engine-graphql": "^0.2.0",
16
17
  "@types/body-parser": "^1.19.5",
17
18
  "@types/cors": "^2.8.17",
18
19
  "@types/express": "^5.0.0",
19
20
  "@types/pg": "^8.11.10",
20
21
  "esbuild": "^0.24.0",
21
22
  "graphql-tag": "^2.12.6",
22
- "@powerhousedao/scalars": "1.12.0",
23
- "document-model": "2.10.0",
24
- "document-drive": "1.8.4"
23
+ "@powerhousedao/scalars": "1.13.0",
24
+ "document-drive": "1.9.0",
25
+ "document-model": "2.11.0"
25
26
  },
26
27
  "dependencies": {
27
28
  "@apollo/server": "^4.11.0",
28
29
  "@apollo/subgraph": "^2.9.2",
29
30
  "@electric-sql/pglite": "^0.2.12",
31
+ "@powerhousedao/analytics-engine-core": "^0.3.0",
32
+ "@powerhousedao/analytics-engine-knex": "^0.4.0",
30
33
  "body-parser": "^1.20.3",
31
34
  "cors": "^2.8.5",
32
35
  "drizzle-kit": "^0.25.0",
@@ -34,10 +37,12 @@
34
37
  "express": "^4.21.1",
35
38
  "graphql": "^16.9.0",
36
39
  "graphql-request": "^6.1.0",
40
+ "knex": "^3.1.0",
41
+ "knex-pglite": "^0.10.0",
37
42
  "nanoevents": "^9.0.0",
38
43
  "pg": "^8.13.0",
39
44
  "uuid": "^9.0.1",
40
- "document-model-libs": "1.120.3"
45
+ "document-model-libs": "1.121.0"
41
46
  },
42
47
  "scripts": {
43
48
  "build": "tsup",
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from "./server";
2
2
  export * from "./router";
3
3
  export * from "./utils/create-schema";
4
+ export * from "./processors";
5
+ export * from "./types";
@@ -0,0 +1,67 @@
1
+ import { IAnalyticsStore } from "@powerhousedao/analytics-engine-core";
2
+ import { IDocumentDriveServer } from "document-drive";
3
+ import { DocumentDriveDocument } from "document-model-libs/document-drive";
4
+ import { IProcessor, IProcessorManager, ProcessorSetupArgs } from "./types";
5
+ import { ProcessorClass, isProcessorClass } from "./processors";
6
+
7
+ export class ProcessorManager implements IProcessorManager {
8
+ private reactor: IDocumentDriveServer;
9
+ private processors: IProcessor[] = [];
10
+
11
+ constructor(
12
+ driveServer: IDocumentDriveServer,
13
+ private analyticsStore: IAnalyticsStore,
14
+ ) {
15
+ this.reactor = driveServer;
16
+ driveServer.on("driveAdded", this.#onDriveAdded.bind(this));
17
+ }
18
+
19
+ async #onDriveAdded(drive: DocumentDriveDocument) {
20
+ await Promise.all(
21
+ this.processors.map((module) =>
22
+ this.reactor.addInternalListener(
23
+ drive.state.global.id,
24
+ {
25
+ onStrands: (strands) => module.onStrands(strands),
26
+ onDisconnect: () => module.onDisconnect(),
27
+ },
28
+ { ...module.getOptions() },
29
+ ),
30
+ ),
31
+ );
32
+ }
33
+
34
+ async #onProcessorAdded(processor: IProcessor) {
35
+ const drives = await this.reactor.getDrives();
36
+
37
+ const options = processor.getOptions();
38
+ await Promise.all(
39
+ drives.map((drive) =>
40
+ this.reactor.addInternalListener(
41
+ drive,
42
+ {
43
+ onStrands: (strands) => processor.onStrands(strands),
44
+ onDisconnect: () => processor.onDisconnect(),
45
+ },
46
+ options,
47
+ ),
48
+ ),
49
+ );
50
+ }
51
+
52
+ async registerProcessor(module: IProcessor | ProcessorClass) {
53
+ const args: ProcessorSetupArgs = {
54
+ reactor: this.reactor,
55
+ dataSources: {
56
+ analyticsStore: this.analyticsStore,
57
+ },
58
+ };
59
+
60
+ const processor = isProcessorClass(module) ? new module(args) : module;
61
+ processor.onSetup?.(args);
62
+
63
+ await this.#onProcessorAdded(processor);
64
+ this.processors.push(processor);
65
+ return processor;
66
+ }
67
+ }
@@ -0,0 +1,21 @@
1
+ import { IAnalyticsStore } from "@powerhousedao/analytics-engine-core";
2
+ import { Document, OperationScope } from "document-model/document";
3
+ import { Processor } from "./processor";
4
+ import { ProcessorOptions, ProcessorSetupArgs } from "src/types";
5
+
6
+ export abstract class AnalyticsProcessor<
7
+ D extends Document = Document,
8
+ S extends OperationScope = OperationScope,
9
+ > extends Processor<D, S> {
10
+ protected analyticsStore: IAnalyticsStore;
11
+
12
+ constructor(args: ProcessorSetupArgs, options?: ProcessorOptions) {
13
+ super(args, options);
14
+ this.analyticsStore = args.dataSources.analyticsStore;
15
+ }
16
+
17
+ onSetup(args: ProcessorSetupArgs) {
18
+ super.onSetup(args);
19
+ this.analyticsStore = args.dataSources.analyticsStore;
20
+ }
21
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./analytics-processor";
2
+ // export * from "./rwa-analytics-processor";
3
+ export * from "./processor";
@@ -0,0 +1,75 @@
1
+ import {
2
+ IDocumentDriveServer,
3
+ InternalTransmitterUpdate,
4
+ } from "document-drive";
5
+ import { Document, OperationScope } from "document-model/document";
6
+ import { IProcessor, ProcessorOptions, ProcessorSetupArgs } from "src/types";
7
+
8
+ export type ProcessorUpdate<
9
+ D extends Document = Document,
10
+ S extends OperationScope = OperationScope,
11
+ > = InternalTransmitterUpdate<D, S>;
12
+
13
+ export abstract class Processor<
14
+ D extends Document = Document,
15
+ S extends OperationScope = OperationScope,
16
+ > implements IProcessor<D, S>
17
+ {
18
+ protected reactor: IDocumentDriveServer;
19
+ protected processorOptions: ProcessorOptions = {
20
+ listenerId: "processor",
21
+ filter: {
22
+ branch: ["main"],
23
+ documentId: ["*"],
24
+ documentType: ["*"],
25
+ scope: ["global"],
26
+ },
27
+ block: false,
28
+ label: "processor",
29
+ system: true,
30
+ };
31
+
32
+ constructor(args: ProcessorSetupArgs, options?: ProcessorOptions) {
33
+ this.reactor = args.reactor;
34
+ if (options) {
35
+ this.processorOptions = { ...this.processorOptions, ...options };
36
+ }
37
+ }
38
+
39
+ onSetup(args: ProcessorSetupArgs): void {
40
+ this.reactor = args.reactor;
41
+ }
42
+
43
+ abstract onStrands(strands: ProcessorUpdate<D, S>[]): Promise<void>;
44
+
45
+ abstract onDisconnect(): Promise<void>;
46
+
47
+ getOptions() {
48
+ return this.processorOptions;
49
+ }
50
+ }
51
+
52
+ export class BaseProcessor extends Processor {
53
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
54
+ async onStrands(strands: ProcessorUpdate[]): Promise<void> {}
55
+ async onDisconnect(): Promise<void> {}
56
+ }
57
+
58
+ export type ProcessorClass = typeof BaseProcessor;
59
+
60
+ // checks if the provided candidate is a descendant of the Processor class.
61
+ export function isProcessorClass(
62
+ candidate: unknown,
63
+ ): candidate is ProcessorClass {
64
+ if (typeof candidate !== "function") return false;
65
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
66
+ let proto = Object.getPrototypeOf(candidate);
67
+
68
+ while (proto) {
69
+ if (Object.prototype.isPrototypeOf.call(proto, Processor)) return true;
70
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
71
+ proto = Object.getPrototypeOf(proto);
72
+ }
73
+
74
+ return false;
75
+ }
package/src/router.ts CHANGED
@@ -1,31 +1,21 @@
1
1
  import { ApolloServer } from "@apollo/server";
2
2
  import { expressMiddleware } from "@apollo/server/express4";
3
3
  import { ApolloServerPluginInlineTraceDisabled } from "@apollo/server/plugin/disabled";
4
- import { PGlite } from "@electric-sql/pglite";
4
+ import { GraphQLResolverMap } from "@apollo/subgraph/dist/schema-helper";
5
5
  import bodyParser from "body-parser";
6
6
  import cors from "cors";
7
7
  import { IDocumentDriveServer } from "document-drive";
8
- import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
9
- import { PgDatabase } from "drizzle-orm/pg-core";
10
- import { drizzle as drizzlePglite } from "drizzle-orm/pglite";
11
8
  import express, { IRouter, Router } from "express";
12
- import pg from "pg";
13
- import {
14
- InternalListenerManager,
15
- InternalListenerModule,
16
- } from "./internal-listener-manager";
17
- import { driveSubgraph, systemSubgraph } from "./subgraphs";
18
- import { Context, Processor } from "./types";
9
+ import { analyticsSubgraph, driveSubgraph, systemSubgraph } from "./subgraphs";
10
+ import { Context, Subgraph } from "./types";
19
11
  import { createSchema } from "./utils/create-schema";
20
- const { Pool } = pg;
21
12
 
22
13
  export class ReactorRouterManager {
23
- private database: PgDatabase<any, any, any> | undefined;
24
14
  private reactorRouter: IRouter = Router();
25
15
  private contextFields: Record<string, any> = {};
26
16
 
27
17
  // @todo: need to persist somewhere
28
- private registry: Processor[] = [
18
+ private subgraphs: Subgraph[] = [
29
19
  {
30
20
  name: "system",
31
21
  resolvers: systemSubgraph.resolvers,
@@ -36,25 +26,21 @@ export class ReactorRouterManager {
36
26
  resolvers: driveSubgraph.resolvers,
37
27
  typeDefs: driveSubgraph.typeDefs,
38
28
  },
29
+ {
30
+ name: "analytics",
31
+ resolvers: analyticsSubgraph.resolvers as GraphQLResolverMap<Context>,
32
+ typeDefs: analyticsSubgraph.typeDefs,
33
+ },
39
34
  ];
35
+
40
36
  constructor(
41
37
  private readonly path: string,
42
38
  private readonly app: express.Express,
43
- private readonly driveServer: IDocumentDriveServer,
44
- private readonly client: PGlite | typeof Pool = new PGlite(),
45
- private listenerManager: InternalListenerManager = new InternalListenerManager(
46
- driveServer,
47
- ),
39
+ private readonly reactor: IDocumentDriveServer,
48
40
  ) {}
49
41
 
50
42
  async init() {
51
- if (this.client instanceof Pool) {
52
- this.database = drizzlePg(this.client);
53
- } else {
54
- this.database = drizzlePglite(this.client as PGlite);
55
- }
56
- await this.listenerManager.init();
57
- const models = this.driveServer.getDocumentModels();
43
+ const models = this.reactor.getDocumentModels();
58
44
  const driveModel = models.find(
59
45
  (it) => it.documentModel.name === "DocumentDrive",
60
46
  );
@@ -62,7 +48,7 @@ export class ReactorRouterManager {
62
48
  throw new Error("DocumentDrive model required");
63
49
  }
64
50
 
65
- this.driveServer.on("documentModels", () => {
51
+ this.reactor.on("documentModels", () => {
66
52
  this.updateRouter().catch((error: unknown) => console.error(error));
67
53
  });
68
54
 
@@ -74,30 +60,18 @@ export class ReactorRouterManager {
74
60
  }
75
61
 
76
62
  async updateRouter() {
77
- if (!this.database) {
78
- await this.init();
79
- }
80
-
81
63
  const newRouter = Router();
82
64
  newRouter.use(cors());
83
65
  newRouter.use(bodyParser.json());
84
66
  // Run each subgraph on the same http server, but at different paths
85
- for (const subgraph of this.registry) {
86
- if (subgraph.options && subgraph.transmit) {
87
- await this.#registerInternalListener({
88
- name: subgraph.name,
89
- options: subgraph.options,
90
- transmit: subgraph.transmit,
91
- });
92
- }
93
-
67
+ for (const subgraph of this.subgraphs) {
94
68
  const subgraphConfig = this.#getLocalSubgraphConfig(subgraph.name);
95
69
  if (!subgraphConfig) continue;
96
70
  console.log(`Setting up subgraph ${subgraphConfig.name}`);
97
71
  // get schema
98
72
  const schema = createSchema(
99
- this.driveServer,
100
- subgraphConfig.resolvers,
73
+ this.reactor,
74
+ subgraphConfig.resolvers as GraphQLResolverMap<Context>,
101
75
  subgraphConfig.typeDefs,
102
76
  );
103
77
  // create apollo server
@@ -119,8 +93,7 @@ export class ReactorRouterManager {
119
93
  context: ({ req }): Context => ({
120
94
  headers: req.headers,
121
95
  driveId: req.params.drive ?? undefined,
122
- driveServer: this.driveServer,
123
- db: this.database!,
96
+ driveServer: this.reactor,
124
97
  // analyticStore: undefined, // TODO: add analytic store
125
98
  ...this.getAdditionalContextFields(),
126
99
  }),
@@ -132,31 +105,14 @@ export class ReactorRouterManager {
132
105
  console.log("Router updated.");
133
106
  }
134
107
 
135
- async registerProcessor(processor: Processor) {
136
- const schema = createSchema(
137
- this.driveServer,
138
- processor.resolvers,
139
- processor.typeDefs,
140
- );
141
-
142
- this.registry.unshift({
143
- ...processor,
144
- });
145
-
146
- // update router
147
- console.log(`Registering [${processor.name}] processor.`);
148
- await this.updateRouter();
108
+ async registerSubgraph(subgraph: Subgraph) {
109
+ this.subgraphs.unshift(subgraph);
110
+ console.log(`Registered [${subgraph.name}] subgraph.`);
111
+ return Promise.resolve();
149
112
  }
150
113
 
151
114
  #getLocalSubgraphConfig(subgraphName: string) {
152
- return this.registry.find((it) => it.name === subgraphName);
153
- }
154
-
155
- async #registerInternalListener(module: InternalListenerModule) {
156
- if (!this.listenerManager) {
157
- throw new Error("Listener manager not initialized");
158
- }
159
- await this.listenerManager.registerInternalListener(module);
115
+ return this.subgraphs.find((it) => it.name === subgraphName);
160
116
  }
161
117
 
162
118
  getAdditionalContextFields = () => {
package/src/server.ts CHANGED
@@ -1,13 +1,23 @@
1
1
  import { PGlite } from "@electric-sql/pglite";
2
+ import { AnalyticsQueryEngine } from "@powerhousedao/analytics-engine-core";
3
+ import { AnalyticsModel } from "@powerhousedao/analytics-engine-graphql";
4
+ import {
5
+ KnexAnalyticsStore,
6
+ KnexQueryExecutor,
7
+ } from "@powerhousedao/analytics-engine-knex";
2
8
  import { IDocumentDriveServer } from "document-drive";
3
9
  import express, { Express } from "express";
4
- import pg from "pg";
5
- const { Pool } = pg;
10
+ import { Pool } from "pg";
6
11
  import { ReactorRouterManager } from "./router";
12
+ import { getKnexClient } from "./utils/get-knex-client";
13
+ import { ProcessorManager } from "./processor-manager";
14
+ import { API } from "./types";
15
+ import { initialize } from "./subgraphs/analytics";
7
16
 
8
17
  type Options = {
9
18
  express?: Express;
10
19
  port?: number;
20
+ dbConnection: string | undefined;
11
21
  client?: PGlite | typeof Pool | undefined;
12
22
  };
13
23
 
@@ -15,20 +25,29 @@ const DEFAULT_PORT = 4000;
15
25
 
16
26
  export async function startAPI(
17
27
  reactor: IDocumentDriveServer,
18
- options: Options
19
- ) {
28
+ options: Options,
29
+ ): Promise<API> {
20
30
  const port = options.port ?? DEFAULT_PORT;
21
31
  const app = options.express ?? express();
22
32
 
23
- const reactorRouterManager = new ReactorRouterManager(
24
- "/",
25
- app,
26
- reactor,
27
- options.client
28
- );
33
+ const knex = getKnexClient(options.dbConnection ?? "./dev.db");
34
+ await initialize(knex);
35
+
36
+ const analyticsStore = new KnexAnalyticsStore({
37
+ executor: new KnexQueryExecutor(),
38
+ knex,
39
+ });
40
+ const reactorRouterManager = new ReactorRouterManager("/", app, reactor);
41
+ reactorRouterManager.setAdditionalContextFields({
42
+ dataSources: {
43
+ db: {
44
+ Analytics: new AnalyticsModel(new AnalyticsQueryEngine(analyticsStore)),
45
+ },
46
+ },
47
+ });
29
48
  await reactorRouterManager.init();
49
+ const processorManager = new ProcessorManager(reactor, analyticsStore);
30
50
 
31
51
  app.listen(port);
32
-
33
- return { app, reactorRouterManager };
52
+ return { app, reactorRouterManager, processorManager };
34
53
  }
@@ -0,0 +1,55 @@
1
+ import { Knex } from "knex";
2
+
3
+ export async function createSchema(knex: Knex) {
4
+ if (!(await knex.schema.hasTable("AnalyticsDimension"))) {
5
+ await knex.schema.createTable("AnalyticsDimension", (table) => {
6
+ table.increments("id").primary();
7
+ table
8
+ .string("dimension")
9
+ .notNullable()
10
+ .index("analyticsdimension_dimension_index");
11
+ table.string("path").notNullable().index("analyticsdimension_path_index");
12
+ table.string("label").nullable();
13
+ table.string("icon").nullable();
14
+ table.text("description").nullable();
15
+ });
16
+ }
17
+
18
+ if (!(await knex.schema.hasTable("AnalyticsSeries"))) {
19
+ await knex.schema.createTable("AnalyticsSeries", (table) => {
20
+ table.increments("id").primary();
21
+ table
22
+ .string("source")
23
+ .notNullable()
24
+ .index("analyticsseries_source_index");
25
+ table.date("start").notNullable().index("analyticsseries_start_index");
26
+ table.date("end").nullable().index("analyticsseries_end_index");
27
+ table
28
+ .string("metric")
29
+ .notNullable()
30
+ .index("analyticsseries_metric_index");
31
+ table.float("value").notNullable().index("analyticsseries_value_index");
32
+ table.string("unit").nullable().index("analyticsseries_unit_index");
33
+ table.string("fn").notNullable().index("analyticsseries_fn_index");
34
+ table.json("params").nullable();
35
+ });
36
+ }
37
+
38
+ if (!(await knex.schema.hasTable("AnalyticsSeries_AnalyticsDimension"))) {
39
+ await knex.schema.createTable(
40
+ "AnalyticsSeries_AnalyticsDimension",
41
+ (table) => {
42
+ table
43
+ .integer("seriesId")
44
+ .references("AnalyticsSeries.id")
45
+ .onDelete("CASCADE")
46
+ .index("analyticsseries_analyticsdimension_seriesid_index");
47
+ table
48
+ .integer("dimensionId")
49
+ .references("AnalyticsDimension.id")
50
+ .onDelete("CASCADE")
51
+ .index("analyticsseries_analyticsdimension_dimensionid_index");
52
+ },
53
+ );
54
+ }
55
+ }
@@ -0,0 +1,12 @@
1
+ import {
2
+ AnalyticsResolvers as resolvers,
3
+ typedefs as typeDefs,
4
+ } from "@powerhousedao/analytics-engine-graphql";
5
+ import { createSchema } from "./db.js";
6
+ import { Knex } from "knex";
7
+
8
+ export { resolvers, typeDefs };
9
+
10
+ export async function initialize(knex: Knex) {
11
+ await createSchema(knex);
12
+ }
@@ -1,3 +1,4 @@
1
+ import { GraphQLResolverMap } from "@apollo/subgraph/dist/schema-helper";
1
2
  import {
2
3
  generateUUID,
3
4
  ListenerRevision,
@@ -7,19 +8,30 @@ import {
7
8
  import {
8
9
  actions,
9
10
  DocumentDriveAction,
11
+ FileNode,
10
12
  Listener,
11
13
  ListenerFilter,
12
14
  TransmitterType,
13
15
  } from "document-model-libs/document-drive";
16
+ import { Asset } from "document-model-libs/real-world-assets";
14
17
  import { BaseAction, Operation } from "document-model/document";
15
18
  import {
16
19
  DocumentModelInput,
17
20
  DocumentModelState,
18
21
  } from "document-model/document-model";
19
22
  import { Context } from "../types";
20
- import { GraphQLResolverMap } from "@apollo/subgraph/dist/schema-helper";
21
23
 
22
24
  export const resolvers: GraphQLResolverMap<Context> = {
25
+ Asset: {
26
+ __resolveType: (obj: Asset) => {
27
+ return obj.type;
28
+ },
29
+ },
30
+ Node: {
31
+ __resolveType: (obj: FileNode) => {
32
+ return obj.documentType ? "FileNode" : "FolderNode";
33
+ },
34
+ },
23
35
  Query: {
24
36
  drive: async (_: unknown, args: unknown, ctx: Context) => {
25
37
  if (!ctx.driveId) throw new Error("Drive ID is required");
@@ -38,7 +50,7 @@ export const resolvers: GraphQLResolverMap<Context> = {
38
50
  const dms = ctx.driveServer.getDocumentModels();
39
51
  const dm = dms.find(
40
52
  ({ documentModel }: { documentModel: DocumentModelState }) =>
41
- documentModel.id === document.documentType
53
+ documentModel.id === document.documentType,
42
54
  );
43
55
  const globalState = document.state.global;
44
56
  if (!globalState) throw new Error("Document not found");
@@ -63,7 +75,7 @@ export const resolvers: GraphQLResolverMap<Context> = {
63
75
  registerPullResponderListener: async (
64
76
  _: unknown,
65
77
  { filter }: { filter: ListenerFilter },
66
- ctx: Context
78
+ ctx: Context,
67
79
  ) => {
68
80
  if (!ctx.driveId) throw new Error("Drive ID is required");
69
81
  const uuid = generateUUID();
@@ -87,12 +99,12 @@ export const resolvers: GraphQLResolverMap<Context> = {
87
99
 
88
100
  const result = await ctx.driveServer.queueDriveAction(
89
101
  ctx.driveId,
90
- actions.addListener({ listener })
102
+ actions.addListener({ listener }),
91
103
  );
92
104
 
93
105
  if (result.status !== "SUCCESS" && result.error) {
94
106
  throw new Error(
95
- `Listener couldn't be registered: ${result.error.message}`
107
+ `Listener couldn't be registered: ${result.error.message}`,
96
108
  );
97
109
  }
98
110
 
@@ -101,7 +113,7 @@ export const resolvers: GraphQLResolverMap<Context> = {
101
113
  pushUpdates: async (
102
114
  _: unknown,
103
115
  { strands }: { strands: StrandUpdateGraphQL[] },
104
- ctx: Context
116
+ ctx: Context,
105
117
  ) => {
106
118
  if (!ctx.driveId) throw new Error("Drive ID is required");
107
119
  const listenerRevisions: ListenerRevision[] = await Promise.all(
@@ -119,11 +131,11 @@ export const resolvers: GraphQLResolverMap<Context> = {
119
131
  ? ctx.driveServer.queueOperations(
120
132
  s.driveId,
121
133
  s.documentId,
122
- operations
134
+ operations,
123
135
  )
124
136
  : ctx.driveServer.queueDriveOperations(
125
137
  s.driveId,
126
- operations as Operation<DocumentDriveAction | BaseAction>[]
138
+ operations as Operation<DocumentDriveAction | BaseAction>[],
127
139
  ));
128
140
 
129
141
  const scopeOperations = result.document?.operations[s.scope] ?? [];
@@ -148,7 +160,7 @@ export const resolvers: GraphQLResolverMap<Context> = {
148
160
  status: result.status,
149
161
  error: result.error?.message || undefined,
150
162
  };
151
- })
163
+ }),
152
164
  );
153
165
 
154
166
  return listenerRevisions;
@@ -159,7 +171,7 @@ export const resolvers: GraphQLResolverMap<Context> = {
159
171
  listenerId,
160
172
  revisions,
161
173
  }: { listenerId: string; revisions: ListenerRevision[] },
162
- ctx: Context
174
+ ctx: Context,
163
175
  ) => {
164
176
  if (!listenerId || !revisions) return false;
165
177
  if (!ctx.driveId) throw new Error("Drive ID is required");
@@ -176,12 +188,12 @@ export const resolvers: GraphQLResolverMap<Context> = {
176
188
 
177
189
  const transmitter = (await ctx.driveServer.getTransmitter(
178
190
  ctx.driveId,
179
- listenerId
191
+ listenerId,
180
192
  )) as PullResponderTransmitter;
181
193
  const result = await transmitter.processAcknowledge(
182
194
  ctx.driveId ?? "1",
183
195
  listenerId,
184
- validEntries
196
+ validEntries,
185
197
  );
186
198
 
187
199
  return result;
@@ -192,12 +204,12 @@ export const resolvers: GraphQLResolverMap<Context> = {
192
204
  strands: async (
193
205
  _: unknown,
194
206
  { listenerId, since }: { listenerId: string; since: string | undefined },
195
- ctx: Context
207
+ ctx: Context,
196
208
  ) => {
197
209
  if (!ctx.driveId) throw new Error("Drive ID is required");
198
210
  const listener = (await ctx.driveServer.getTransmitter(
199
211
  ctx.driveId,
200
- listenerId
212
+ listenerId,
201
213
  )) as PullResponderTransmitter;
202
214
  const strands = await listener.getStrands({ since });
203
215
  return strands.map((e) => ({
@@ -1,3 +1,4 @@
1
1
  export * as systemSubgraph from "./system";
2
2
  export * as driveSubgraph from "./drive";
3
+ export * as analyticsSubgraph from "./analytics";
3
4
  export * from "./types";