@powerhousedao/switchboard 6.0.0-dev.18 → 6.0.0-dev.181
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/Auth.md +45 -27
- package/CHANGELOG.md +1405 -5
- package/README.md +13 -12
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +128 -0
- package/dist/index.mjs.map +1 -0
- package/dist/install-packages.d.mts +1 -0
- package/dist/install-packages.mjs +31 -0
- package/dist/install-packages.mjs.map +1 -0
- package/dist/migrate.d.mts +1 -0
- package/dist/migrate.mjs +55 -0
- package/dist/migrate.mjs.map +1 -0
- package/dist/server-P-6VOlLl.mjs +254 -0
- package/dist/server-P-6VOlLl.mjs.map +1 -0
- package/dist/server.d.mts +76 -0
- package/dist/server.d.mts.map +1 -0
- package/dist/server.mjs +4 -0
- package/dist/utils-DFl0ezBT.mjs +44 -0
- package/dist/utils-DFl0ezBT.mjs.map +1 -0
- package/dist/utils.d.mts +9 -0
- package/dist/utils.d.mts.map +1 -0
- package/dist/utils.mjs +2 -0
- package/package.json +53 -39
- package/test/metrics.test.ts +202 -0
- package/tsconfig.json +11 -9
- package/tsdown.config.ts +16 -0
- package/vitest.config.ts +11 -0
- package/Dockerfile +0 -86
- package/dist/src/clients/redis.d.ts +0 -5
- package/dist/src/clients/redis.d.ts.map +0 -1
- package/dist/src/clients/redis.js +0 -48
- package/dist/src/clients/redis.js.map +0 -1
- package/dist/src/config.d.ts +0 -12
- package/dist/src/config.d.ts.map +0 -1
- package/dist/src/config.js +0 -33
- package/dist/src/config.js.map +0 -1
- package/dist/src/connect-crypto.d.ts +0 -41
- package/dist/src/connect-crypto.d.ts.map +0 -1
- package/dist/src/connect-crypto.js +0 -127
- package/dist/src/connect-crypto.js.map +0 -1
- package/dist/src/feature-flags.d.ts +0 -2
- package/dist/src/feature-flags.d.ts.map +0 -1
- package/dist/src/feature-flags.js +0 -9
- package/dist/src/feature-flags.js.map +0 -1
- package/dist/src/index.d.ts +0 -3
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -21
- package/dist/src/index.js.map +0 -1
- package/dist/src/install-packages.d.ts +0 -2
- package/dist/src/install-packages.d.ts.map +0 -1
- package/dist/src/install-packages.js +0 -36
- package/dist/src/install-packages.js.map +0 -1
- package/dist/src/migrate.d.ts +0 -3
- package/dist/src/migrate.d.ts.map +0 -1
- package/dist/src/migrate.js +0 -65
- package/dist/src/migrate.js.map +0 -1
- package/dist/src/profiler.d.ts +0 -4
- package/dist/src/profiler.d.ts.map +0 -1
- package/dist/src/profiler.js +0 -17
- package/dist/src/profiler.js.map +0 -1
- package/dist/src/server.d.ts +0 -6
- package/dist/src/server.d.ts.map +0 -1
- package/dist/src/server.js +0 -304
- package/dist/src/server.js.map +0 -1
- package/dist/src/types.d.ts +0 -64
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/types.js +0 -2
- package/dist/src/types.js.map +0 -1
- package/dist/src/utils.d.ts +0 -6
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/utils.js +0 -92
- package/dist/src/utils.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/entrypoint.sh +0 -17
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ILogger } from "document-model";
|
|
2
|
+
import { MeterProvider } from "@opentelemetry/api";
|
|
3
|
+
import { IReactorClient } from "@powerhousedao/reactor";
|
|
4
|
+
import { DriveInput } from "@powerhousedao/shared/document-drive";
|
|
5
|
+
import { IRenown } from "@renown/sdk";
|
|
6
|
+
|
|
7
|
+
//#region src/types.d.ts
|
|
8
|
+
type StorageOptions = {
|
|
9
|
+
type: "filesystem" | "memory" | "postgres" | "browser";
|
|
10
|
+
filesystemPath?: string;
|
|
11
|
+
postgresUrl?: string;
|
|
12
|
+
};
|
|
13
|
+
type IdentityOptions = {
|
|
14
|
+
/** Path to the keypair file. Defaults to ~/.ph/keypair.json */keypairPath?: string;
|
|
15
|
+
/**
|
|
16
|
+
* If true, won't start without an existing keypair.
|
|
17
|
+
* Use this to ensure the switchboard only runs with an authenticated identity.
|
|
18
|
+
*/
|
|
19
|
+
requireExisting?: boolean; /** Base url of the Renown instance to use */
|
|
20
|
+
baseUrl?: string; /** If true, unsigned actions will be rejected */
|
|
21
|
+
requireSignatures?: boolean;
|
|
22
|
+
};
|
|
23
|
+
type StartServerOptions = {
|
|
24
|
+
configFile?: string;
|
|
25
|
+
port?: number;
|
|
26
|
+
dev?: boolean;
|
|
27
|
+
dbPath?: string;
|
|
28
|
+
drive?: DriveInput;
|
|
29
|
+
packages?: string[];
|
|
30
|
+
remoteDrives?: string[];
|
|
31
|
+
https?: {
|
|
32
|
+
keyPath: string;
|
|
33
|
+
certPath: string;
|
|
34
|
+
} | boolean | undefined;
|
|
35
|
+
auth?: {
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
guests: string[];
|
|
38
|
+
users: string[];
|
|
39
|
+
admins: string[];
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Identity options for Renown.
|
|
43
|
+
* When configured, the switchboard will load the keypair from `ph login`
|
|
44
|
+
* and can authenticate with remote services on behalf of the user.
|
|
45
|
+
*/
|
|
46
|
+
identity?: IdentityOptions;
|
|
47
|
+
mcp?: boolean;
|
|
48
|
+
processorConfig?: Map<string, unknown>;
|
|
49
|
+
disableLocalPackages?: boolean;
|
|
50
|
+
enableDocumentModelSubgraphs?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* When true, enables dynamic loading of document models from the registry
|
|
53
|
+
* when an unknown document type is encountered during sync.
|
|
54
|
+
* Disabled by default — enable with DYNAMIC_MODEL_LOADING=true env var.
|
|
55
|
+
*/
|
|
56
|
+
dynamicModelLoading?: boolean;
|
|
57
|
+
logger?: ILogger;
|
|
58
|
+
/**
|
|
59
|
+
* OpenTelemetry MeterProvider to register as the global provider before
|
|
60
|
+
* ReactorInstrumentation starts. Must be provided here rather than set
|
|
61
|
+
* externally to guarantee the registration happens before
|
|
62
|
+
* instrumentation.start() reads the global provider via metrics.getMeter().
|
|
63
|
+
*/
|
|
64
|
+
meterProvider?: MeterProvider;
|
|
65
|
+
};
|
|
66
|
+
type SwitchboardReactor = {
|
|
67
|
+
defaultDriveUrl: string | undefined;
|
|
68
|
+
reactor: IReactorClient; /** The Renown instance if identity was initialized */
|
|
69
|
+
renown: IRenown | null;
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/server.d.mts
|
|
73
|
+
declare const startSwitchboard: (options?: StartServerOptions) => Promise<SwitchboardReactor>;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { IdentityOptions, StartServerOptions, StorageOptions, SwitchboardReactor, startSwitchboard };
|
|
76
|
+
//# sourceMappingURL=server.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.mts","names":[],"sources":["../src/types.ts","../src/server.mts"],"mappings":";;;;;;;KAMY,cAAA;EACV,IAAA;EACA,cAAA;EACA,WAAA;AAAA;AAAA,KAGU,eAAA;EALV,+DAOA,WAAA;EALA;;;AAGF;EAOE,eAAA;EAGA,OAAA,WARA;EAWA,iBAAA;AAAA;AAAA,KAGU,kBAAA;EACV,UAAA;EACA,IAAA;EACA,GAAA;EACA,MAAA;EACA,KAAA,GAAQ,UAAA;EACR,QAAA;EACA,YAAA;EACA,KAAA;IAEM,OAAA;IACA,QAAA;EAAA;EAIN,IAAA;IACE,OAAA;IACA,MAAA;IACA,KAAA;IACA,MAAA;EAAA;EAdM;;;;;EAqBR,QAAA,GAAW,eAAA;EACX,GAAA;EACA,eAAA,GAAkB,GAAA;EAClB,oBAAA;EACA,4BAAA;EAXE;;;;;EAiBF,mBAAA;EACA,MAAA,GAAS,OAAA;EAPT;;;;;;EAcA,aAAA,GAAgB,aAAA;AAAA;AAAA,KAGN,kBAAA;EACV,eAAA;EACA,OAAA,EAAS,cAAA,EAEM;EAAf,MAAA,EAAQ,OAAA;AAAA;;;cCkRG,gBAAA,GACX,OAAA,GAAS,kBAAA,KACR,OAAA,CAAQ,kBAAA"}
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { driveCreateDocument, driveCreateState } from "@powerhousedao/shared/document-drive";
|
|
2
|
+
import "@powerhousedao/shared/document-model";
|
|
3
|
+
//#region src/utils.mts
|
|
4
|
+
async function addDefaultDrive(client, drive, serverPort) {
|
|
5
|
+
let driveId = drive.id;
|
|
6
|
+
if (!driveId || driveId.length === 0) driveId = drive.slug;
|
|
7
|
+
if (!driveId || driveId.length === 0) throw new Error("Invalid Drive Id");
|
|
8
|
+
let existingDrive;
|
|
9
|
+
try {
|
|
10
|
+
existingDrive = await client.get(driveId);
|
|
11
|
+
} catch {}
|
|
12
|
+
if (existingDrive) return `http://localhost:${serverPort}/d/${driveId}`;
|
|
13
|
+
const { global } = driveCreateState();
|
|
14
|
+
const document = driveCreateDocument({
|
|
15
|
+
global: {
|
|
16
|
+
...global,
|
|
17
|
+
name: drive.global.name,
|
|
18
|
+
icon: drive.global.icon ?? global.icon
|
|
19
|
+
},
|
|
20
|
+
local: {
|
|
21
|
+
availableOffline: drive.local?.availableOffline ?? false,
|
|
22
|
+
sharingType: drive.local?.sharingType ?? "public",
|
|
23
|
+
listeners: drive.local?.listeners ?? [],
|
|
24
|
+
triggers: drive.local?.triggers ?? []
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
if (drive.id && drive.id.length > 0) document.header.id = drive.id;
|
|
28
|
+
if (drive.slug && drive.slug.length > 0) document.header.slug = drive.slug;
|
|
29
|
+
if (drive.global.name) document.header.name = drive.global.name;
|
|
30
|
+
if (drive.preferredEditor) document.header.meta = { preferredEditor: drive.preferredEditor };
|
|
31
|
+
try {
|
|
32
|
+
await client.create(document);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
if (!(e instanceof Error ? e.message : String(e)).includes("already exists")) throw e;
|
|
35
|
+
}
|
|
36
|
+
return `http://localhost:${serverPort}/d/${driveId}`;
|
|
37
|
+
}
|
|
38
|
+
function isPostgresUrl(url) {
|
|
39
|
+
return url.startsWith("postgresql") || url.startsWith("postgres");
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { isPostgresUrl as n, addDefaultDrive as t };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=utils-DFl0ezBT.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils-DFl0ezBT.mjs","names":[],"sources":["../src/utils.mts"],"sourcesContent":["import type { IReactorClient } from \"@powerhousedao/reactor\";\nimport type { DocumentDriveDocument } from \"@powerhousedao/shared/document-drive\";\nimport {\n driveCreateDocument,\n driveCreateState,\n} from \"@powerhousedao/shared/document-drive\";\nimport type { DriveInput } from \"@powerhousedao/shared/document-drive\";\nimport { generateId } from \"@powerhousedao/shared/document-model\";\n\nexport async function addDefaultDrive(\n client: IReactorClient,\n drive: DriveInput,\n serverPort: number,\n) {\n let driveId = drive.id;\n if (!driveId || driveId.length === 0) {\n driveId = drive.slug;\n }\n\n if (!driveId || driveId.length === 0) {\n throw new Error(\"Invalid Drive Id\");\n }\n\n // check if the drive already exists\n let existingDrive;\n try {\n existingDrive = await client.get(driveId);\n } catch {\n //\n }\n\n // already exists, return the existing drive url\n if (existingDrive) {\n return `http://localhost:${serverPort}/d/${driveId}`;\n }\n\n const { global } = driveCreateState();\n const document = driveCreateDocument({\n global: {\n ...global,\n name: drive.global.name,\n icon: drive.global.icon ?? global.icon,\n },\n local: {\n availableOffline: drive.local?.availableOffline ?? false,\n sharingType: drive.local?.sharingType ?? \"public\",\n listeners: drive.local?.listeners ?? [],\n triggers: drive.local?.triggers ?? [],\n },\n });\n\n if (drive.id && drive.id.length > 0) {\n document.header.id = drive.id;\n }\n if (drive.slug && drive.slug.length > 0) {\n document.header.slug = drive.slug;\n }\n if (drive.global.name) {\n document.header.name = drive.global.name;\n }\n if (drive.preferredEditor) {\n document.header.meta = { preferredEditor: drive.preferredEditor };\n }\n\n try {\n await client.create(document);\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : String(e);\n if (!errorMessage.includes(\"already exists\")) {\n throw e;\n }\n }\n\n return `http://localhost:${serverPort}/d/${driveId}`;\n}\n\nexport function isPostgresUrl(url: string) {\n return url.startsWith(\"postgresql\") || url.startsWith(\"postgres\");\n}\n"],"mappings":";;;AASA,eAAsB,gBACpB,QACA,OACA,YACA;CACA,IAAI,UAAU,MAAM;AACpB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,WAAU,MAAM;AAGlB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,OAAM,IAAI,MAAM,mBAAmB;CAIrC,IAAI;AACJ,KAAI;AACF,kBAAgB,MAAM,OAAO,IAAI,QAAQ;SACnC;AAKR,KAAI,cACF,QAAO,oBAAoB,WAAW,KAAK;CAG7C,MAAM,EAAE,WAAW,kBAAkB;CACrC,MAAM,WAAW,oBAAoB;EACnC,QAAQ;GACN,GAAG;GACH,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO,QAAQ,OAAO;GACnC;EACD,OAAO;GACL,kBAAkB,MAAM,OAAO,oBAAoB;GACnD,aAAa,MAAM,OAAO,eAAe;GACzC,WAAW,MAAM,OAAO,aAAa,EAAE;GACvC,UAAU,MAAM,OAAO,YAAY,EAAE;GACtC;EACF,CAAC;AAEF,KAAI,MAAM,MAAM,MAAM,GAAG,SAAS,EAChC,UAAS,OAAO,KAAK,MAAM;AAE7B,KAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,EACpC,UAAS,OAAO,OAAO,MAAM;AAE/B,KAAI,MAAM,OAAO,KACf,UAAS,OAAO,OAAO,MAAM,OAAO;AAEtC,KAAI,MAAM,gBACR,UAAS,OAAO,OAAO,EAAE,iBAAiB,MAAM,iBAAiB;AAGnE,KAAI;AACF,QAAM,OAAO,OAAO,SAAS;UACtB,GAAG;AAEV,MAAI,EADiB,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,EAC7C,SAAS,iBAAiB,CAC1C,OAAM;;AAIV,QAAO,oBAAoB,WAAW,KAAK;;AAG7C,SAAgB,cAAc,KAAa;AACzC,QAAO,IAAI,WAAW,aAAa,IAAI,IAAI,WAAW,WAAW"}
|
package/dist/utils.d.mts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IReactorClient } from "@powerhousedao/reactor";
|
|
2
|
+
import { DriveInput } from "@powerhousedao/shared/document-drive";
|
|
3
|
+
|
|
4
|
+
//#region src/utils.d.mts
|
|
5
|
+
declare function addDefaultDrive(client: IReactorClient, drive: DriveInput, serverPort: number): Promise<string>;
|
|
6
|
+
declare function isPostgresUrl(url: string): boolean;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { addDefaultDrive, isPostgresUrl };
|
|
9
|
+
//# sourceMappingURL=utils.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.mts"],"mappings":";;;;iBASsB,eAAA,CACpB,MAAA,EAAQ,cAAA,EACR,KAAA,EAAO,UAAA,EACP,UAAA,WAAkB,OAAA;AAAA,iBAgEJ,aAAA,CAAc,GAAA"}
|
package/dist/utils.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/switchboard",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "6.0.0-dev.
|
|
5
|
-
"main": "dist/
|
|
4
|
+
"version": "6.0.0-dev.181",
|
|
5
|
+
"main": "dist/index.mjs",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"types": "./dist/index.d.mts",
|
|
9
|
+
"import": "./dist/index.mjs"
|
|
10
10
|
},
|
|
11
11
|
"./server": {
|
|
12
|
-
"
|
|
13
|
-
"
|
|
12
|
+
"types": "./dist/server.d.mts",
|
|
13
|
+
"import": "./dist/server.mjs"
|
|
14
|
+
},
|
|
15
|
+
"./utils": {
|
|
16
|
+
"types": "./dist/utils.d.mts",
|
|
17
|
+
"import": "./dist/utils.mjs"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=24.0.0"
|
|
22
|
+
},
|
|
16
23
|
"bin": {
|
|
17
|
-
"switchboard": "dist/
|
|
24
|
+
"switchboard": "dist/index.mjs"
|
|
18
25
|
},
|
|
19
26
|
"repository": {
|
|
20
27
|
"type": "git",
|
|
@@ -24,48 +31,55 @@
|
|
|
24
31
|
"license": "ISC",
|
|
25
32
|
"description": "",
|
|
26
33
|
"dependencies": {
|
|
27
|
-
"@electric-sql/pglite": "0.
|
|
28
|
-
"@openfeature/core": "
|
|
29
|
-
"@openfeature/env-var-provider": "
|
|
30
|
-
"@openfeature/server-sdk": "
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
34
|
+
"@electric-sql/pglite": "0.3.15",
|
|
35
|
+
"@openfeature/core": "1.9.1",
|
|
36
|
+
"@openfeature/env-var-provider": "0.3.1",
|
|
37
|
+
"@openfeature/server-sdk": "1.19.0",
|
|
38
|
+
"@opentelemetry/api": "^1.9.0",
|
|
39
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.57.2",
|
|
40
|
+
"@opentelemetry/resources": "^1.29.0",
|
|
41
|
+
"@opentelemetry/sdk-metrics": "^1.29.0",
|
|
33
42
|
"@pyroscope/nodejs": "^0.4.5",
|
|
34
43
|
"@sentry/node": "^9.6.1",
|
|
35
|
-
"body-parser": "^1.20.3",
|
|
36
|
-
"cors": "^2.8.5",
|
|
37
44
|
"dotenv": "^16.4.7",
|
|
38
|
-
"exponential-backoff": "^3.1.1",
|
|
39
45
|
"express": "^4.21.2",
|
|
40
|
-
"
|
|
41
|
-
"kysely": "
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"@powerhousedao/
|
|
46
|
-
"@powerhousedao/reactor": "6.0.0-dev.
|
|
47
|
-
"@
|
|
48
|
-
"@powerhousedao/
|
|
49
|
-
"
|
|
50
|
-
"document-model": "6.0.0-dev.
|
|
46
|
+
"kysely": "0.28.16",
|
|
47
|
+
"kysely-pglite-dialect": "1.2.0",
|
|
48
|
+
"pg": "8.18.0",
|
|
49
|
+
"vite": "8.0.8",
|
|
50
|
+
"@powerhousedao/config": "6.0.0-dev.181",
|
|
51
|
+
"@powerhousedao/reactor": "6.0.0-dev.181",
|
|
52
|
+
"@powerhousedao/opentelemetry-instrumentation-reactor": "6.0.0-dev.181",
|
|
53
|
+
"@powerhousedao/shared": "6.0.0-dev.181",
|
|
54
|
+
"@powerhousedao/vetra": "6.0.0-dev.181",
|
|
55
|
+
"@powerhousedao/reactor-api": "6.0.0-dev.181",
|
|
56
|
+
"document-model": "6.0.0-dev.181",
|
|
57
|
+
"@renown/sdk": "6.0.0-dev.181"
|
|
51
58
|
},
|
|
52
59
|
"devDependencies": {
|
|
53
60
|
"@types/express": "^4.17.25",
|
|
54
|
-
"@types/node": "
|
|
55
|
-
"@types/pg": "
|
|
56
|
-
"
|
|
57
|
-
"
|
|
61
|
+
"@types/node": "25.2.3",
|
|
62
|
+
"@types/pg": "8.16.0",
|
|
63
|
+
"tsdown": "0.21.0",
|
|
64
|
+
"concurrently": "9.2.1",
|
|
65
|
+
"nodemon": "3.1.11",
|
|
66
|
+
"react": "19.2.4",
|
|
67
|
+
"vitest": "4.1.1"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"react": ">=19.0.0"
|
|
58
71
|
},
|
|
59
72
|
"scripts": {
|
|
60
73
|
"tsc": "tsc",
|
|
74
|
+
"test": "vitest run",
|
|
61
75
|
"lint": "eslint",
|
|
62
|
-
"build": "pnpm run install-packages",
|
|
63
|
-
"start": "node dist/
|
|
64
|
-
"start:profile": "mkdir -p .prof && node --cpu-prof --cpu-prof-dir=.prof dist/
|
|
65
|
-
"start:profile:bun": "mkdir -p .prof && bun --cpu-prof --cpu-prof-dir=.prof dist/
|
|
66
|
-
"dev": "concurrently -P 'pnpm -w run tsc --watch' 'nodemon --trace-warnings --watch \"../..\" -e ts,tsx,js,json dist/
|
|
67
|
-
"install-packages": "node dist/
|
|
68
|
-
"migrate": "node dist/
|
|
69
|
-
"migrate:status": "node dist/
|
|
76
|
+
"build": "tsdown && pnpm run install-packages",
|
|
77
|
+
"start": "node dist/index.mjs",
|
|
78
|
+
"start:profile": "mkdir -p .prof && node --cpu-prof --cpu-prof-dir=.prof dist/index.mjs",
|
|
79
|
+
"start:profile:bun": "mkdir -p .prof && bun --cpu-prof --cpu-prof-dir=.prof dist/index.mjs",
|
|
80
|
+
"dev": "concurrently -P 'pnpm -w run tsc --watch' 'nodemon --trace-warnings --watch \"../..\" -e ts,tsx,js,json dist/index.mjs -- {@}' --",
|
|
81
|
+
"install-packages": "node dist/install-packages.mjs",
|
|
82
|
+
"migrate": "node dist/migrate.mjs",
|
|
83
|
+
"migrate:status": "node dist/migrate.mjs status"
|
|
70
84
|
}
|
|
71
85
|
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { MeterProvider } from "@opentelemetry/sdk-metrics";
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createMeterProviderFromEnv } from "../src/metrics.js";
|
|
4
|
+
|
|
5
|
+
// Stub childLogger so tests don't require the full document-drive runtime
|
|
6
|
+
vi.mock("document-drive", () => ({
|
|
7
|
+
childLogger: () => ({
|
|
8
|
+
info: vi.fn(),
|
|
9
|
+
warn: vi.fn(),
|
|
10
|
+
error: vi.fn(),
|
|
11
|
+
}),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const providers: MeterProvider[] = [];
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
// Await shutdown so PeriodicExportingMetricReader timers are cleared
|
|
19
|
+
await Promise.all(providers.map((p) => p.shutdown()));
|
|
20
|
+
providers.length = 0;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function track(provider: MeterProvider | undefined): MeterProvider | undefined {
|
|
24
|
+
if (provider) providers.push(provider);
|
|
25
|
+
return provider;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function trackAsserted(provider: MeterProvider | undefined): MeterProvider {
|
|
29
|
+
expect(provider).toBeInstanceOf(MeterProvider);
|
|
30
|
+
return track(provider) as MeterProvider;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// These helpers access undocumented internal fields of MeterProvider and
|
|
34
|
+
// PeriodicExportingMetricReader. They may break if @opentelemetry/sdk-metrics
|
|
35
|
+
// renames its private state between major versions.
|
|
36
|
+
function getReader(provider: MeterProvider): {
|
|
37
|
+
_exportInterval: number;
|
|
38
|
+
_exportTimeout: number;
|
|
39
|
+
} {
|
|
40
|
+
return (
|
|
41
|
+
provider as unknown as {
|
|
42
|
+
_sharedState: {
|
|
43
|
+
metricCollectors: Array<{
|
|
44
|
+
_metricReader: { _exportInterval: number; _exportTimeout: number };
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
)._sharedState.metricCollectors[0]._metricReader;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getExporterUrl(provider: MeterProvider): string {
|
|
52
|
+
return (
|
|
53
|
+
provider as unknown as {
|
|
54
|
+
_sharedState: {
|
|
55
|
+
metricCollectors: Array<{
|
|
56
|
+
_metricReader: {
|
|
57
|
+
_exporter: {
|
|
58
|
+
_delegate: {
|
|
59
|
+
_transport: {
|
|
60
|
+
_transport: { _parameters: { url: string } };
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
}>;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
)._sharedState.metricCollectors[0]._metricReader._exporter._delegate
|
|
69
|
+
._transport._transport._parameters.url;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getResourceAttributes(
|
|
73
|
+
provider: MeterProvider,
|
|
74
|
+
): Record<string, unknown> {
|
|
75
|
+
return (
|
|
76
|
+
provider as unknown as {
|
|
77
|
+
_sharedState: { resource: { attributes: Record<string, unknown> } };
|
|
78
|
+
}
|
|
79
|
+
)._sharedState.resource.attributes;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("createMeterProviderFromEnv", () => {
|
|
83
|
+
describe("when OTEL_EXPORTER_OTLP_ENDPOINT is not set", () => {
|
|
84
|
+
it("returns undefined", () => {
|
|
85
|
+
expect(createMeterProviderFromEnv({})).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("when OTEL_EXPORTER_OTLP_ENDPOINT is set", () => {
|
|
90
|
+
it("returns a MeterProvider", () => {
|
|
91
|
+
expect(
|
|
92
|
+
track(
|
|
93
|
+
createMeterProviderFromEnv({
|
|
94
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
).toBeInstanceOf(MeterProvider);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("strips trailing slash from endpoint URL without throwing", () => {
|
|
101
|
+
expect(() =>
|
|
102
|
+
track(
|
|
103
|
+
createMeterProviderFromEnv({
|
|
104
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318/",
|
|
105
|
+
}),
|
|
106
|
+
),
|
|
107
|
+
).not.toThrow();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("does not double-append /v1/metrics when endpoint already includes it", () => {
|
|
111
|
+
const provider = trackAsserted(
|
|
112
|
+
createMeterProviderFromEnv({
|
|
113
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318/v1/metrics",
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
expect(getExporterUrl(provider)).toBe("http://localhost:4318/v1/metrics");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("uses 5000ms export interval by default", () => {
|
|
120
|
+
const provider = trackAsserted(
|
|
121
|
+
createMeterProviderFromEnv({
|
|
122
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
expect(getReader(provider)._exportInterval).toBe(5000);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("honours OTEL_METRIC_EXPORT_INTERVAL", () => {
|
|
129
|
+
const provider = trackAsserted(
|
|
130
|
+
createMeterProviderFromEnv({
|
|
131
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
|
|
132
|
+
OTEL_METRIC_EXPORT_INTERVAL: "2000",
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
expect(getReader(provider)._exportInterval).toBe(2000);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("falls back to 5000ms when OTEL_METRIC_EXPORT_INTERVAL is non-numeric", () => {
|
|
139
|
+
const provider = trackAsserted(
|
|
140
|
+
createMeterProviderFromEnv({
|
|
141
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
|
|
142
|
+
OTEL_METRIC_EXPORT_INTERVAL: "abc",
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
expect(getReader(provider)._exportInterval).toBe(5000);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("falls back to 5000ms when OTEL_METRIC_EXPORT_INTERVAL is zero", () => {
|
|
149
|
+
const provider = trackAsserted(
|
|
150
|
+
createMeterProviderFromEnv({
|
|
151
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
|
|
152
|
+
OTEL_METRIC_EXPORT_INTERVAL: "0",
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
expect(getReader(provider)._exportInterval).toBe(5000);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("falls back to 5000ms when OTEL_METRIC_EXPORT_INTERVAL is negative", () => {
|
|
159
|
+
const provider = trackAsserted(
|
|
160
|
+
createMeterProviderFromEnv({
|
|
161
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
|
|
162
|
+
OTEL_METRIC_EXPORT_INTERVAL: "-1000",
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
expect(getReader(provider)._exportInterval).toBe(5000);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("sets exportTimeoutMillis below exportIntervalMillis", () => {
|
|
169
|
+
const provider = trackAsserted(
|
|
170
|
+
createMeterProviderFromEnv({
|
|
171
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
|
|
172
|
+
OTEL_METRIC_EXPORT_INTERVAL: "1000",
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
const reader = getReader(provider);
|
|
176
|
+
expect(reader._exportTimeout).toBeLessThan(reader._exportInterval);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("uses 'switchboard' as service name by default", () => {
|
|
180
|
+
const provider = trackAsserted(
|
|
181
|
+
createMeterProviderFromEnv({
|
|
182
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
expect(getResourceAttributes(provider)["service.name"]).toBe(
|
|
186
|
+
"switchboard",
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("honours OTEL_SERVICE_NAME", () => {
|
|
191
|
+
const provider = trackAsserted(
|
|
192
|
+
createMeterProviderFromEnv({
|
|
193
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
|
|
194
|
+
OTEL_SERVICE_NAME: "my-switchboard",
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
expect(getResourceAttributes(provider)["service.name"]).toBe(
|
|
198
|
+
"my-switchboard",
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -2,22 +2,21 @@
|
|
|
2
2
|
"extends": "../../tsconfig.options.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"outDir": "./dist",
|
|
5
|
-
"lib": [
|
|
6
|
-
"ESNext"
|
|
7
|
-
]
|
|
5
|
+
"lib": ["ESNext"]
|
|
8
6
|
},
|
|
9
|
-
"include": [
|
|
10
|
-
"**/*"
|
|
11
|
-
],
|
|
7
|
+
"include": ["**/*"],
|
|
12
8
|
"references": [
|
|
9
|
+
{
|
|
10
|
+
"path": "../../packages/config"
|
|
11
|
+
},
|
|
13
12
|
{
|
|
14
13
|
"path": "../../packages/document-model"
|
|
15
14
|
},
|
|
16
15
|
{
|
|
17
|
-
"path": "../../packages/
|
|
16
|
+
"path": "../../packages/opentelemetry-instrumentation-reactor"
|
|
18
17
|
},
|
|
19
18
|
{
|
|
20
|
-
"path": "../../packages/
|
|
19
|
+
"path": "../../packages/reactor"
|
|
21
20
|
},
|
|
22
21
|
{
|
|
23
22
|
"path": "../../packages/reactor-api"
|
|
@@ -26,7 +25,10 @@
|
|
|
26
25
|
"path": "../../packages/renown"
|
|
27
26
|
},
|
|
28
27
|
{
|
|
29
|
-
"path": "../../packages/
|
|
28
|
+
"path": "../../packages/shared"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "../../packages/vetra"
|
|
30
32
|
}
|
|
31
33
|
]
|
|
32
34
|
}
|
package/tsdown.config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from "tsdown";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: [
|
|
5
|
+
"src/index.mts",
|
|
6
|
+
"src/server.mts",
|
|
7
|
+
"src/utils.mts",
|
|
8
|
+
"src/install-packages.mts",
|
|
9
|
+
"src/migrate.mts",
|
|
10
|
+
],
|
|
11
|
+
platform: "node",
|
|
12
|
+
outDir: "dist",
|
|
13
|
+
clean: true,
|
|
14
|
+
dts: true,
|
|
15
|
+
sourcemap: true,
|
|
16
|
+
});
|
package/vitest.config.ts
ADDED
package/Dockerfile
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# Build stage
|
|
2
|
-
FROM node:22-alpine AS build
|
|
3
|
-
|
|
4
|
-
WORKDIR /app
|
|
5
|
-
|
|
6
|
-
# Install build dependencies
|
|
7
|
-
RUN apk add --no-cache python3 make g++ git bash \
|
|
8
|
-
&& ln -sf /usr/bin/python3 /usr/bin/python
|
|
9
|
-
|
|
10
|
-
# Setup pnpm
|
|
11
|
-
ENV PNPM_HOME="/pnpm"
|
|
12
|
-
ENV PATH="$PNPM_HOME:$PATH"
|
|
13
|
-
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
14
|
-
|
|
15
|
-
# Configure JSR registry
|
|
16
|
-
RUN pnpm config set @jsr:registry https://npm.jsr.io
|
|
17
|
-
|
|
18
|
-
# Build arguments
|
|
19
|
-
ARG TAG=latest
|
|
20
|
-
ARG PH_PACKAGES=""
|
|
21
|
-
|
|
22
|
-
# Install ph-cmd, prisma, and prettier
|
|
23
|
-
RUN pnpm add -g ph-cmd@$TAG prisma@5.17.0 prettier
|
|
24
|
-
|
|
25
|
-
# Initialize project based on tag
|
|
26
|
-
RUN case "$TAG" in \
|
|
27
|
-
*dev*) ph init project --dev --package-manager pnpm ;; \
|
|
28
|
-
*staging*) ph init project --staging --package-manager pnpm ;; \
|
|
29
|
-
*) ph init project --package-manager pnpm ;; \
|
|
30
|
-
esac
|
|
31
|
-
|
|
32
|
-
WORKDIR /app/project
|
|
33
|
-
|
|
34
|
-
# Install PH packages if provided
|
|
35
|
-
RUN if [ -n "$PH_PACKAGES" ]; then \
|
|
36
|
-
IFS=',' ; for pkg in $PH_PACKAGES; do \
|
|
37
|
-
echo "Installing package: $pkg"; \
|
|
38
|
-
ph install "$pkg"; \
|
|
39
|
-
done; \
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# Regenerate Prisma client for Alpine Linux (linux-musl-openssl-3.0.x)
|
|
43
|
-
# The document-drive package ships with darwin-arm64 binaries, we need to regenerate
|
|
44
|
-
RUN prisma generate --schema node_modules/document-drive/dist/prisma/schema.prisma
|
|
45
|
-
|
|
46
|
-
# Final stage - slim node image
|
|
47
|
-
FROM node:22-alpine
|
|
48
|
-
|
|
49
|
-
WORKDIR /app
|
|
50
|
-
|
|
51
|
-
# Install runtime dependencies (curl for health checks, openssl for Prisma)
|
|
52
|
-
RUN apk add --no-cache curl openssl
|
|
53
|
-
|
|
54
|
-
# Setup pnpm
|
|
55
|
-
ENV PNPM_HOME="/pnpm"
|
|
56
|
-
ENV PATH="$PNPM_HOME:$PATH"
|
|
57
|
-
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
58
|
-
|
|
59
|
-
# Configure JSR registry
|
|
60
|
-
RUN pnpm config set @jsr:registry https://npm.jsr.io
|
|
61
|
-
|
|
62
|
-
# Install ph-cmd and prisma globally (needed at runtime)
|
|
63
|
-
ARG TAG=latest
|
|
64
|
-
RUN pnpm add -g ph-cmd@$TAG prisma@5.17.0
|
|
65
|
-
|
|
66
|
-
# Copy built project from build stage
|
|
67
|
-
COPY --from=build /app/project /app/project
|
|
68
|
-
|
|
69
|
-
WORKDIR /app/project
|
|
70
|
-
|
|
71
|
-
# Copy entrypoint
|
|
72
|
-
COPY entrypoint.sh /app/entrypoint.sh
|
|
73
|
-
RUN chmod +x /app/entrypoint.sh
|
|
74
|
-
|
|
75
|
-
# Environment variables
|
|
76
|
-
ENV NODE_ENV=production
|
|
77
|
-
ENV PORT=3000
|
|
78
|
-
ENV DATABASE_URL=""
|
|
79
|
-
ENV SKIP_DB_MIGRATIONS="false"
|
|
80
|
-
|
|
81
|
-
EXPOSE ${PORT}
|
|
82
|
-
|
|
83
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
|
|
84
|
-
CMD curl -f http://localhost:${PORT}/health || exit 1
|
|
85
|
-
|
|
86
|
-
ENTRYPOINT ["/app/entrypoint.sh"]
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { RedisClientType } from "redis";
|
|
2
|
-
export declare let redisClient: RedisClientType | undefined;
|
|
3
|
-
export declare const initRedis: (url: string) => Promise<RedisClientType | undefined>;
|
|
4
|
-
export declare const closeRedis: () => Promise<void> | undefined;
|
|
5
|
-
//# sourceMappingURL=redis.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../../src/clients/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAG7C,eAAO,IAAI,WAAW,EAAE,eAAe,GAAG,SAAS,CAAC;AACpD,eAAO,MAAM,SAAS,GACpB,KAAK,MAAM,KACV,OAAO,CAAC,eAAe,GAAG,SAAS,CAiCrC,CAAC;AAeF,eAAO,MAAM,UAAU,iCAItB,CAAC"}
|