@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/CHANGELOG.md +18 -3
- package/dist/index.d.ts +86 -60
- package/dist/index.js +8107 -1402
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
- package/src/index.ts +2 -0
- package/src/processor-manager.ts +67 -0
- package/src/processors/analytics-processor.ts +21 -0
- package/src/processors/index.ts +3 -0
- package/src/processors/processor.ts +75 -0
- package/src/router.ts +22 -66
- package/src/server.ts +31 -12
- package/src/subgraphs/analytics/db.ts +55 -0
- package/src/subgraphs/analytics/index.ts +12 -0
- package/src/subgraphs/drive/resolvers.ts +26 -14
- package/src/subgraphs/index.ts +1 -0
- package/src/types.ts +46 -4
- package/src/utils/create-schema.ts +16 -7
- package/src/utils/get-knex-client.ts +23 -0
- package/test/router.test.ts +27 -31
- package/src/internal-listener-manager.ts +0 -108
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/reactor-api",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
23
|
-
"document-
|
|
24
|
-
"document-
|
|
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.
|
|
45
|
+
"document-model-libs": "1.121.0"
|
|
41
46
|
},
|
|
42
47
|
"scripts": {
|
|
43
48
|
"build": "tsup",
|
package/src/index.ts
CHANGED
|
@@ -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,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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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.
|
|
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
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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) => ({
|
package/src/subgraphs/index.ts
CHANGED