@powerhousedao/switchboard 2.2.122-dev.0 → 2.3.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@powerhousedao/switchboard",
3
3
  "type": "module",
4
- "version": "2.2.122-dev.0",
4
+ "version": "2.3.0",
5
5
  "main": "dist/src/index.js",
6
6
  "bin": {
7
7
  "switchboard": "dist/src/index.js"
@@ -10,6 +10,7 @@
10
10
  "license": "ISC",
11
11
  "description": "",
12
12
  "dependencies": {
13
+ "@powerhousedao/analytics-engine-core": "^0.4.0",
13
14
  "@powerhousedao/analytics-engine-knex": "^0.4.0",
14
15
  "@pyroscope/nodejs": "^0.4.5",
15
16
  "@sentry/node": "^9.6.1",
@@ -20,11 +21,11 @@
20
21
  "express": "^4.21.2",
21
22
  "graphql": "^16.10.0",
22
23
  "redis": "^4.7.0",
23
- "@powerhousedao/config": "1.27.0-dev.5",
24
- "@powerhousedao/reactor-api": "1.29.18-dev.11",
25
- "@powerhousedao/scalars": "1.33.1-dev.4",
26
- "document-drive": "1.29.9-dev.2",
27
- "document-model": "2.28.1-dev.4"
24
+ "@powerhousedao/config": "1.27.0-dev.9",
25
+ "@powerhousedao/reactor-api": "1.29.23-dev.2",
26
+ "document-model": "2.28.1-dev.9",
27
+ "@powerhousedao/scalars": "1.33.1-dev.7",
28
+ "document-drive": "1.29.11-dev.5"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/express": "^5.0.0",
@@ -1,94 +0,0 @@
1
- // This is your Prisma schema file,
2
- // learn more about it in the docs: https://pris.ly/d/prisma-schema
3
-
4
- generator client {
5
- provider = "prisma-client-js"
6
- }
7
-
8
- datasource db {
9
- provider = "postgresql"
10
- url = env("DATABASE_URL")
11
- }
12
-
13
- model Drive {
14
- id String @id
15
- slug String @unique
16
- driveDocuments DriveDocument[]
17
- }
18
-
19
- model Document {
20
- id String @id
21
- created DateTime @default(now())
22
- lastModified DateTime @default(now())
23
- isDrive Boolean
24
- revision String
25
- name String?
26
- operations Operation[]
27
- initialState String // json object with the scope as keys of the root object
28
- documentType String
29
- meta String?
30
- syncronizationUnits SyncronizationUnit[]
31
- driveDocuments DriveDocument[]
32
- }
33
-
34
- // Model to map the many-to-many relationship between drives and documents
35
- model DriveDocument {
36
- driveId String
37
- documentId String
38
- drive Drive @relation(fields: [driveId], references: [id], onDelete: Cascade)
39
- document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
40
-
41
- @@id([driveId, documentId])
42
- @@index([driveId])
43
- @@index([documentId])
44
- }
45
-
46
- model Operation {
47
- id String @id @default(uuid())
48
- opId String?
49
- driveId String
50
- Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
51
- documentId String
52
- scope String
53
- branch String
54
- index Int
55
- skip Int
56
- hash String
57
- timestamp DateTime
58
- input String
59
- type String
60
- attachments Attachment[]
61
- syncId String?
62
- clipboard Boolean? @default(false)
63
- context Json?
64
- resultingState Bytes?
65
-
66
- SyncronizationUnit SyncronizationUnit? @relation(fields: [syncId, driveId], references: [id, driveId], onDelete: Cascade)
67
-
68
- @@unique([driveId, documentId, scope, branch, index(sort: Asc)], name: "unique_operation")
69
- }
70
-
71
- model SyncronizationUnit {
72
- id String
73
- driveId String
74
- documentId String
75
-
76
- Document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
77
- scope String
78
- branch String
79
- operations Operation[]
80
-
81
- @@id([id, driveId])
82
- }
83
-
84
- model Attachment {
85
- id String @id @default(uuid())
86
- operationId String
87
- Operation Operation @relation(fields: [operationId], references: [id], onDelete: Cascade)
88
-
89
- mimeType String
90
- data String
91
- filename String?
92
- extension String?
93
- hash String
94
- }
@@ -1,51 +0,0 @@
1
- import { createClient, type RedisClientType } from "redis";
2
-
3
- export let redisClient: RedisClientType;
4
- export const initRedis = async () => {
5
- if (!redisClient) {
6
- const url = process.env.REDIS_TLS_URL ?? process.env.REDIS_URL;
7
- if (!url) {
8
- throw new Error("REDIS_TLS_URL is not set");
9
- }
10
-
11
- const socket = url.includes("rediss")
12
- ? {
13
- tls: true,
14
- rejectUnauthorized: false,
15
- }
16
- : undefined;
17
-
18
- redisClient = createClient({
19
- url,
20
- socket,
21
- });
22
-
23
- redisClient.on("error", (err: string) => {
24
- console.log("Redis Client Error", err);
25
- throw new Error("Redis Client Error");
26
- });
27
-
28
- redisClient.connect();
29
- }
30
-
31
- return redisClient;
32
- };
33
-
34
- const timer = setInterval(
35
- async () => {
36
- try {
37
- if (redisClient) {
38
- await redisClient.ping();
39
- }
40
- } catch (err) {
41
- console.error("Ping Interval Error", err);
42
- }
43
- },
44
- 1000 * 60 * 4,
45
- );
46
-
47
- export const closeRedis = () => {
48
- clearInterval(timer);
49
-
50
- redisClient.disconnect();
51
- };
package/src/index.ts DELETED
@@ -1,99 +0,0 @@
1
- #!/usr/bin/env node
2
- import { startAPI } from "@powerhousedao/reactor-api";
3
- import * as Sentry from "@sentry/node";
4
- import { ReactorBuilder, driveDocumentModelModule } from "document-drive";
5
- import RedisCache from "document-drive/cache/redis";
6
- import { PrismaStorageFactory } from "document-drive/storage/prisma";
7
- import {
8
- type DocumentModelModule,
9
- documentModelDocumentModelModule,
10
- } from "document-model";
11
- import dotenv from "dotenv";
12
- import express from "express";
13
- import { initRedis } from "./clients/redis.js";
14
- import { initProfilerFromEnv } from "./profiler.js";
15
- import { PackagesManager } from "./utils/package-manager.js";
16
-
17
- dotenv.config();
18
-
19
- // Create a monolith express app for all subgraphs
20
- const app = express();
21
-
22
- if (process.env.SENTRY_DSN) {
23
- console.log("Initialized Sentry with env:", process.env.SENTRY_ENV);
24
- Sentry.init({
25
- dsn: process.env.SENTRY_DSN,
26
- environment: process.env.SENTRY_ENV,
27
- });
28
-
29
- Sentry.setupExpressErrorHandler(app);
30
- }
31
-
32
- const serverPort = process.env.PORT ? Number(process.env.PORT) : 4001;
33
-
34
- const main = async () => {
35
- if (process.env.PYROSCOPE_SERVER_ADDRESS) {
36
- try {
37
- await initProfilerFromEnv(process.env);
38
- } catch (e) {
39
- Sentry.captureException(e);
40
- console.error("Error starting profiler", e);
41
- }
42
- }
43
-
44
- try {
45
- const packages =
46
- process.env.PH_PACKAGES && process.env.PH_PACKAGES !== ""
47
- ? process.env.PH_PACKAGES.split(",")
48
- : [];
49
- const pkgManager = new PackagesManager({
50
- packages,
51
- });
52
- const { documentModels, subgraphs } = await pkgManager.init();
53
- const redis = await initRedis();
54
- const connectionString = process.env.DATABASE_URL;
55
- if (!connectionString) {
56
- throw new Error("Please set env var DATABASE_URL");
57
- }
58
- const dbUrl =
59
- connectionString.includes("amazonaws") &&
60
- !connectionString.includes("sslmode=no-verify")
61
- ? connectionString + "?sslmode=no-verify"
62
- : connectionString;
63
-
64
- const redisCache = new RedisCache(redis);
65
- const storageFactory = new PrismaStorageFactory(dbUrl, redisCache);
66
- const storage = storageFactory.build();
67
-
68
- const reactor = new ReactorBuilder([
69
- documentModelDocumentModelModule,
70
- driveDocumentModelModule,
71
- ...documentModels,
72
- ] as DocumentModelModule[])
73
- .withStorage(storage)
74
- .withCache(redisCache)
75
- .build();
76
-
77
- // init drive server
78
- await reactor.initialize();
79
-
80
- // Start the API with the reactor and options
81
- await startAPI(reactor, {
82
- express: app,
83
- port: serverPort,
84
- dbPath: dbUrl,
85
- packages,
86
- });
87
-
88
- // start http server
89
- // httpServer.listen({ port: serverPort }, () => {
90
- // console.log(`Subgraph server listening on port ${serverPort}`);
91
- // });
92
- } catch (e) {
93
- Sentry.captureException(e);
94
- console.error("App crashed", e);
95
- throw e;
96
- }
97
- };
98
-
99
- main().catch(console.error);
@@ -1,8 +0,0 @@
1
- import { execSync } from "child_process";
2
-
3
- const pkgs = process.env.PH_PACKAGES?.split(",") || [];
4
- for (const pkg of pkgs) {
5
- if (pkg === "") continue;
6
- console.log(`> Installing ${pkg}`);
7
- execSync(`pnpm add ${pkg}`, { stdio: "inherit" });
8
- }
package/src/profiler.ts DELETED
@@ -1,25 +0,0 @@
1
- import type { PyroscopeConfig } from "@pyroscope/nodejs";
2
-
3
- export async function initProfilerFromEnv(env: typeof process.env) {
4
- const {
5
- PYROSCOPE_SERVER_ADDRESS: serverAddress,
6
- PYROSCOPE_APPLICATION_NAME: appName,
7
- PYROSCOPE_USER: basicAuthUser,
8
- PYROSCOPE_PASSWORD: basicAuthPassword,
9
- } = env;
10
-
11
- const options: PyroscopeConfig = {
12
- serverAddress,
13
- appName,
14
- basicAuthUser,
15
- basicAuthPassword,
16
- };
17
- return initProfiler(options);
18
- }
19
-
20
- export async function initProfiler(options?: PyroscopeConfig) {
21
- console.log("Initializing Pyroscope profiler at:", options?.serverAddress);
22
- const Pyroscope = await import("@pyroscope/nodejs");
23
- Pyroscope.init(options);
24
- Pyroscope.start();
25
- }
@@ -1,65 +0,0 @@
1
- import { type BaseDocumentDriveServer } from "document-drive";
2
- import { parse } from "graphql";
3
-
4
- export const getDocumentModelTypeDefs = (
5
- documentDriveServer: BaseDocumentDriveServer,
6
- typeDefs: string,
7
- ) => {
8
- const documentModels = documentDriveServer.getDocumentModelModules();
9
- let dmSchema = "";
10
- documentModels.forEach(({ documentModel }) => {
11
- dmSchema += `
12
- ${documentModel.specifications
13
- .map((specification) =>
14
- specification.state.global.schema
15
- .replaceAll(" Account ", ` ${documentModel.name}Account `)
16
- .replaceAll(`: Account`, `: ${documentModel.name}Account`)
17
- .replaceAll(`[Account!]!`, `[${documentModel.name}Account!]!`)
18
- .replaceAll("scalar DateTime", "")
19
- .replaceAll(/input (.*?) {[\s\S]*?}/g, ""),
20
- )
21
- .join("\n")};
22
-
23
- ${documentModel.specifications
24
- .map((specification) =>
25
- specification.state.local.schema
26
- .replaceAll(" Account ", ` ${documentModel.name}Account `)
27
- .replaceAll(`: Account`, `: ${documentModel.name}Account`)
28
- .replaceAll(`[Account!]!`, `[${documentModel.name}Account!]!`)
29
- .replaceAll("scalar DateTime", "")
30
- .replaceAll(/input (.*?) {[\s\S]*?}/g, "")
31
- .replaceAll("type AccountSnapshotLocalState", "")
32
- .replaceAll("type BudgetStatementLocalState", "")
33
- .replaceAll("type ScopeFrameworkLocalState", ""),
34
- )
35
- .join("\n")};
36
-
37
- type ${documentModel.name} implements IDocument {
38
- id: ID!
39
- name: String!
40
- documentType: String!
41
- revision: Int!
42
- created: DateTime!
43
- lastModified: DateTime!
44
- ${documentModel.name !== "DocumentModel" ? `state: ${documentModel.name}State!` : ""}
45
- }\n`;
46
- });
47
-
48
- // add the mutation and query types
49
- const schema = `
50
- scalar DateTime
51
- interface IDocument {
52
- name: String!
53
- documentType: String!
54
- revision: Int!
55
- created: DateTime!
56
- lastModified: DateTime!
57
-
58
- }
59
- ${dmSchema}
60
-
61
- ${typeDefs}
62
- `;
63
-
64
- return parse(schema.replaceAll(";", ""));
65
- };
@@ -1,204 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- export const installPackages = async (packages: string[]) => {
3
- for (const packageName of packages) {
4
- execSync(`ph install ${packageName}`);
5
- }
6
- };
7
-
8
- export const readManifest = () => {
9
- const manifest = execSync(`ph manifest`).toString();
10
- return manifest;
11
- };
12
-
13
- import { getConfig } from "@powerhousedao/config/powerhouse";
14
- import { type Subgraph } from "@powerhousedao/reactor-api";
15
- import { type DocumentModelModule } from "document-model";
16
- import EventEmitter from "node:events";
17
- import { type StatWatcher, watchFile } from "node:fs";
18
-
19
- interface IPackagesManager {
20
- onDocumentModelsChange(
21
- handler: (documentModels: DocumentModelModule[]) => void,
22
- ): void;
23
- }
24
-
25
- type IPackagesManagerOptions = { packages: string[] };
26
-
27
- export async function loadDependency(packageName: string, subPath: string) {
28
- const module = (await import(`${packageName}/${subPath}`)) as unknown;
29
- return module;
30
- }
31
-
32
- async function loadPackagesDocumentModels(packages: string[]) {
33
- const loadedPackages = new Map<string, DocumentModelModule[]>();
34
- for (const pkg of packages) {
35
- try {
36
- console.log("> Loading package:", pkg);
37
- const pkgModule = (await loadDependency(pkg, "document-models")) as {
38
- [key: string]: DocumentModelModule;
39
- };
40
- if (pkgModule) {
41
- console.log(` ➜ Loaded Document Models from: ${pkg}`);
42
- loadedPackages.set(pkg, Object.values(pkgModule));
43
- } else {
44
- console.warn(` ➜ No Document Models found: ${pkg}`);
45
- }
46
- } catch (e) {
47
- console.error("Error loading Document Models from", pkg, e);
48
- }
49
- }
50
- return loadedPackages;
51
- }
52
-
53
- async function loadPackagesSubgraphs(packages: string[]) {
54
- const loadedPackages = new Map<string, (typeof Subgraph)[]>();
55
- for (const pkg of packages) {
56
- const pkgModule = (await loadDependency(
57
- pkg,
58
- "subgraphs",
59
- )) as (typeof Subgraph)[];
60
- if (pkgModule) {
61
- console.log(` ➜ Loaded Subgraphs from: ${pkg}`);
62
- loadedPackages.set(pkg, pkgModule);
63
- } else {
64
- console.warn(` ➜ No Subgraphs found: ${pkg}`);
65
- }
66
- }
67
- return loadedPackages;
68
- }
69
-
70
- function getUniqueDocumentModels(
71
- ...documentModels: DocumentModelModule[][]
72
- ): DocumentModelModule[] {
73
- const uniqueModels = new Map<string, DocumentModelModule>();
74
-
75
- for (const models of documentModels) {
76
- for (const model of models) {
77
- uniqueModels.set(model.documentModel.id, model);
78
- }
79
- }
80
-
81
- return Array.from(uniqueModels.values());
82
- }
83
-
84
- function getUniqueSubgraphs(
85
- subgraphs: (typeof Subgraph)[][],
86
- ): (typeof Subgraph)[] {
87
- const uniqueSubgraphs = new Map<string, typeof Subgraph>();
88
- for (const subgraphss of subgraphs) {
89
- const keys = Object.keys(subgraphss);
90
- for (const key of keys) {
91
- uniqueSubgraphs.set(
92
- key,
93
- (
94
- subgraphss as unknown as Record<
95
- string,
96
- Record<string, typeof Subgraph>
97
- >
98
- )[key][key],
99
- );
100
- }
101
- }
102
- return Array.from(uniqueSubgraphs.values());
103
- }
104
-
105
- export class PackagesManager implements IPackagesManager {
106
- private docModelsMap = new Map<string, DocumentModelModule[]>();
107
- private subgraphsMap = new Map<string, (typeof Subgraph)[]>();
108
- private configWatcher: StatWatcher | undefined;
109
- private eventEmitter = new EventEmitter<{
110
- documentModelsChange: DocumentModelModule[][];
111
- subgraphsChange: (typeof Subgraph)[][];
112
- }>();
113
-
114
- constructor(
115
- protected options: IPackagesManagerOptions,
116
- protected onError?: (e: unknown) => void,
117
- ) {
118
- this.eventEmitter.setMaxListeners(0);
119
- }
120
-
121
- public async init() {
122
- return await this.loadPackages(this.options.packages);
123
- }
124
-
125
- private async loadPackages(packages: string[]) {
126
- // install packages
127
- const packagesMap = await loadPackagesDocumentModels(packages);
128
- const subgraphsMap = await loadPackagesSubgraphs(packages);
129
- this.updatePackagesMap(packagesMap);
130
- this.updateSubgraphsMap(subgraphsMap);
131
-
132
- return {
133
- documentModels: getUniqueDocumentModels(
134
- ...Array.from(packagesMap.values()),
135
- ),
136
- subgraphs: getUniqueSubgraphs(Array.from(subgraphsMap.values())),
137
- };
138
- }
139
-
140
- private loadFromConfigFile(configFile: string) {
141
- const config = getConfig(configFile);
142
- const packages = config.packages;
143
-
144
- return this.loadPackages(
145
- packages?.map((pkg) => pkg.packageName) ?? [],
146
- ).catch(this.onError);
147
- }
148
-
149
- private initConfigFile(configFile: string) {
150
- const result = this.loadFromConfigFile(configFile);
151
-
152
- if (!this.configWatcher) {
153
- this.configWatcher = watchFile(
154
- configFile,
155
- { interval: 100 },
156
- (curr, prev) => {
157
- if (curr.mtime === prev.mtime) {
158
- return;
159
- }
160
- void this.loadFromConfigFile(configFile).catch(this.onError);
161
- },
162
- );
163
- }
164
-
165
- return result;
166
- }
167
-
168
- private updatePackagesMap(packagesMap: Map<string, DocumentModelModule[]>) {
169
- const oldPackages = Array.from(this.docModelsMap.keys());
170
- const newPackages = Array.from(packagesMap.keys());
171
- oldPackages
172
- .filter((pkg) => !newPackages.includes(pkg))
173
- .forEach((pkg) => {
174
- console.log("> Removed package:", pkg);
175
- });
176
- this.docModelsMap = packagesMap;
177
- const documentModels = getUniqueDocumentModels(
178
- ...Array.from(packagesMap.values()),
179
- );
180
- this.eventEmitter.emit("documentModelsChange", documentModels);
181
- }
182
-
183
- private updateSubgraphsMap(subgraphsMap: Map<string, (typeof Subgraph)[]>) {
184
- const oldPackages = Array.from(this.subgraphsMap.keys());
185
- const newPackages = Array.from(subgraphsMap.keys());
186
- oldPackages
187
- .filter((pkg) => !newPackages.includes(pkg))
188
- .forEach((pkg) => {
189
- console.log("> Removed Subgraphs from:", pkg);
190
- });
191
- this.subgraphsMap = subgraphsMap;
192
- const subgraphs = getUniqueSubgraphs(Array.from(subgraphsMap.values()));
193
- this.eventEmitter.emit("subgraphsChange", subgraphs);
194
- }
195
-
196
- onDocumentModelsChange(
197
- handler: (documentModels: DocumentModelModule[]) => void,
198
- ): void {
199
- this.eventEmitter.on("documentModelsChange", handler);
200
- }
201
- onSubgraphsChange(handler: (subgraphs: (typeof Subgraph)[]) => void): void {
202
- this.eventEmitter.on("subgraphsChange", handler);
203
- }
204
- }