@enbox/local-node 0.0.1

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.
@@ -0,0 +1,98 @@
1
+ import { homedir } from 'node:os';
2
+ import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ export const LOCAL_NODE_PAIRING_SESSIONS_FILENAME = 'local-node-sessions.json';
5
+ const localNodePairingSessionsVersion = 1;
6
+ export class LocalNodePairingSessionFile {
7
+ #filePath;
8
+ constructor(filePath) {
9
+ this.#filePath = filePath ?? join(homedir(), '.enbox', LOCAL_NODE_PAIRING_SESSIONS_FILENAME);
10
+ }
11
+ get path() {
12
+ return this.#filePath;
13
+ }
14
+ async read() {
15
+ let raw;
16
+ try {
17
+ raw = await readFile(this.#filePath, 'utf-8');
18
+ }
19
+ catch (error) {
20
+ if (isMissingFileError(error)) {
21
+ return [];
22
+ }
23
+ throw error;
24
+ }
25
+ let parsed;
26
+ try {
27
+ parsed = JSON.parse(raw);
28
+ }
29
+ catch {
30
+ await this.remove();
31
+ return [];
32
+ }
33
+ if (!isValidPairingSessionsFile(parsed)) {
34
+ await this.remove();
35
+ return [];
36
+ }
37
+ return parsed.sessions.map((session) => ({
38
+ createdAt: session.createdAt,
39
+ origin: session.origin,
40
+ token: session.token,
41
+ }));
42
+ }
43
+ async write(sessions) {
44
+ const browserSessions = sessions.filter((session) => session.origin !== undefined);
45
+ const serialized = {
46
+ sessions: browserSessions,
47
+ version: localNodePairingSessionsVersion,
48
+ };
49
+ const json = JSON.stringify(serialized, null, 2);
50
+ const tempPath = `${this.#filePath}.${process.pid}.tmp`;
51
+ await mkdir(dirname(this.#filePath), { recursive: true });
52
+ await writeFile(tempPath, json, { encoding: 'utf-8', mode: 0o600 });
53
+ await rename(tempPath, this.#filePath);
54
+ await chmod(this.#filePath, 0o600);
55
+ }
56
+ async remove() {
57
+ await rm(this.#filePath, { force: true });
58
+ }
59
+ }
60
+ function isMissingFileError(error) {
61
+ return Boolean(error)
62
+ && typeof error === 'object'
63
+ && error.code === 'ENOENT';
64
+ }
65
+ function isValidPairingSessionsFile(value) {
66
+ if (typeof value !== 'object' || value === null) {
67
+ return false;
68
+ }
69
+ const file = value;
70
+ return file.version === localNodePairingSessionsVersion
71
+ && Array.isArray(file.sessions)
72
+ && file.sessions.every(isValidPairingSession);
73
+ }
74
+ function isValidPairingSession(value) {
75
+ if (typeof value !== 'object' || value === null) {
76
+ return false;
77
+ }
78
+ const session = value;
79
+ const createdAt = session.createdAt;
80
+ return typeof createdAt === 'number'
81
+ && Number.isInteger(createdAt)
82
+ && createdAt > 0
83
+ && typeof session.token === 'string'
84
+ && session.token.length > 0
85
+ && typeof session.origin === 'string'
86
+ && isValidHttpOrigin(session.origin);
87
+ }
88
+ function isValidHttpOrigin(origin) {
89
+ try {
90
+ const url = new URL(origin);
91
+ return (url.protocol === 'http:' || url.protocol === 'https:')
92
+ && url.origin === origin;
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
98
+ //# sourceMappingURL=pairing-session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-session-store.js","sourceRoot":"","sources":["../../src/pairing-session-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ export * from './local-node.js';
2
+ export * from './local-node-config.js';
3
+ export * from './pairing-session-store.js';
4
+ export * from './pairing-broker.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { DwnServerConfig } from '@enbox/dwn-server';
2
+ export type LocalNodeServerConfigOptions = {
3
+ allowedOrigins?: string[];
4
+ baseUrl?: string;
5
+ dataStore?: string;
6
+ deliveryEnabled?: boolean;
7
+ eventBusPluginPath?: string;
8
+ forwardingEnabled?: boolean;
9
+ hostname?: string;
10
+ logLevel?: string;
11
+ maxRecordDataSize?: number;
12
+ messageStore?: string;
13
+ packageJsonPath?: string;
14
+ port: number;
15
+ registrationStoreUrl?: string;
16
+ resumableTaskStore?: string;
17
+ serverName?: string;
18
+ storageUrl?: string;
19
+ termsOfServiceFilePath?: string;
20
+ ttlCacheUrl?: string;
21
+ webSocketSupport?: boolean;
22
+ };
23
+ export declare const defaultLocalNodeHostname = "127.0.0.1";
24
+ export declare const defaultLocalNodeStorageUrl: string;
25
+ export declare function createLocalNodeDwnServerConfig(options: LocalNodeServerConfigOptions): DwnServerConfig;
26
+ //# sourceMappingURL=local-node-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-node-config.d.ts","sourceRoot":"","sources":["../../src/local-node-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAQzD,MAAM,MAAM,4BAA4B,GAAG;IACzC,cAAc,CAAC,EAAG,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAG,MAAM,CAAC;IAClB,SAAS,CAAC,EAAG,MAAM,CAAC;IACpB,eAAe,CAAC,EAAG,OAAO,CAAC;IAC3B,kBAAkB,CAAC,EAAG,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAG,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAG,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAG,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAG,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAG,MAAM,CAAC;IACvB,eAAe,CAAC,EAAG,MAAM,CAAC;IAC1B,IAAI,EAAG,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAG,MAAM,CAAC;IAC/B,kBAAkB,CAAC,EAAG,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAG,MAAM,CAAC;IACrB,UAAU,CAAC,EAAG,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAG,MAAM,CAAC;IACjC,WAAW,CAAC,EAAG,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAG,OAAO,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,wBAAwB,cAAc,CAAC;AACpD,eAAO,MAAM,0BAA0B,QAAkF,CAAC;AAE1H,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,4BAA4B,GAAG,eAAe,CA0BrG"}
@@ -0,0 +1,67 @@
1
+ import type { LocalNodePairingSessionStore } from './pairing-session-store.js';
2
+ import type { LocalNodeServerConfigOptions } from './local-node-config.js';
3
+ import type { PairingBroker } from './pairing-broker.js';
4
+ import type { DwnDiscoveryFile } from '@enbox/agent';
5
+ import type { DwnServerConfig, LocalNodePairingManager, LocalNodePairingRequestView } from '@enbox/dwn-server';
6
+ export type LocalNodeState = 'stopped' | 'running';
7
+ export type LocalNodeDwnServer = {
8
+ dwn?: {
9
+ close(): Promise<void>;
10
+ };
11
+ localNodePairingManager: LocalNodePairingManager;
12
+ revokeLocalNodePairingToken?(token: string): Promise<boolean>;
13
+ start(): Promise<void>;
14
+ stop(): Promise<void>;
15
+ };
16
+ export type LocalNodeStartResult = {
17
+ discoveryFilePath: string;
18
+ endpoint: string;
19
+ localNodeToken: string;
20
+ pid: number;
21
+ port: number;
22
+ };
23
+ export type LocalNodeStatus = {
24
+ discoveryFilePath: string;
25
+ endpoint?: string;
26
+ localNodeToken?: string;
27
+ pendingPairingRequests: LocalNodePairingRequestView[];
28
+ pid: number;
29
+ port?: number;
30
+ state: LocalNodeState;
31
+ webSocketSupport: boolean;
32
+ };
33
+ export type ExistingLocalNode = {
34
+ endpoint: string;
35
+ pid: number;
36
+ };
37
+ export type LocalNodeOptions = Omit<LocalNodeServerConfigOptions, 'port'> & {
38
+ createServer?: (config: DwnServerConfig, pairingManager: LocalNodePairingManager) => LocalNodeDwnServer;
39
+ discoveryFile?: DwnDiscoveryFile;
40
+ fetch?: typeof fetch;
41
+ pairingBroker?: PairingBroker;
42
+ pairingPollIntervalMs?: number;
43
+ pairingManager?: LocalNodePairingManager;
44
+ pairingSessionStore?: LocalNodePairingSessionStore;
45
+ pid?: number;
46
+ portCandidates?: readonly number[];
47
+ };
48
+ export declare class LocalNodeAlreadyRunningError extends Error {
49
+ readonly endpoint: string;
50
+ readonly pid: number;
51
+ constructor(existingNode: ExistingLocalNode);
52
+ }
53
+ export declare class LocalNode {
54
+ #private;
55
+ constructor(options?: LocalNodeOptions);
56
+ get discoveryFilePath(): string;
57
+ get endpoint(): string | undefined;
58
+ get pairingManager(): LocalNodePairingManager;
59
+ get state(): LocalNodeState;
60
+ start(): Promise<LocalNodeStartResult>;
61
+ stop(): Promise<void>;
62
+ findExistingNode(): Promise<ExistingLocalNode | undefined>;
63
+ getStatus(): LocalNodeStatus;
64
+ revokePairingToken(token: string): Promise<boolean>;
65
+ processPendingPairingRequests(): Promise<void>;
66
+ }
67
+ //# sourceMappingURL=local-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-node.d.ts","sourceRoot":"","sources":["../../src/local-node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { LocalNodePairingRequestView } from '@enbox/dwn-server';
2
+ import type { Readable, Writable } from 'node:stream';
3
+ type TtyReadable = Readable & {
4
+ isTTY?: boolean;
5
+ };
6
+ type TtyWritable = Writable & {
7
+ isTTY?: boolean;
8
+ };
9
+ export type PairingDecision = 'approve' | 'deny';
10
+ export interface PairingBroker {
11
+ decidePairingRequest(request: LocalNodePairingRequestView): Promise<PairingDecision>;
12
+ }
13
+ export declare class AllowOriginPairingBroker implements PairingBroker {
14
+ #private;
15
+ constructor(allowedOrigins?: string[], fallback?: PairingBroker);
16
+ decidePairingRequest(request: LocalNodePairingRequestView): Promise<PairingDecision>;
17
+ }
18
+ export declare class TtyPairingBroker implements PairingBroker {
19
+ #private;
20
+ constructor(options?: {
21
+ input?: TtyReadable;
22
+ output?: TtyWritable;
23
+ });
24
+ decidePairingRequest(request: LocalNodePairingRequestView): Promise<PairingDecision>;
25
+ }
26
+ export {};
27
+ //# sourceMappingURL=pairing-broker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-broker.d.ts","sourceRoot":"","sources":["../../src/pairing-broker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAKtD,KAAK,WAAW,GAAG,QAAQ,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAClD,KAAK,WAAW,GAAG,QAAQ,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC5B,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACtF;AAED,qBAAa,wBAAyB,YAAW,aAAa;;gBAIzC,cAAc,GAAE,MAAM,EAAO,EAAE,QAAQ,CAAC,EAAE,aAAa;IAK7D,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,eAAe,CAAC;CAWlG;AAED,qBAAa,gBAAiB,YAAW,aAAa;;gBAIjC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO;IAKjE,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,eAAe,CAAC;CAiBlG"}
@@ -0,0 +1,17 @@
1
+ import type { LocalNodePairingSessionRecord } from '@enbox/dwn-server';
2
+ export declare const LOCAL_NODE_PAIRING_SESSIONS_FILENAME = "local-node-sessions.json";
3
+ export interface LocalNodePairingSessionStore {
4
+ readonly path: string;
5
+ read(): Promise<LocalNodePairingSessionRecord[]>;
6
+ remove(): Promise<void>;
7
+ write(sessions: LocalNodePairingSessionRecord[]): Promise<void>;
8
+ }
9
+ export declare class LocalNodePairingSessionFile implements LocalNodePairingSessionStore {
10
+ #private;
11
+ constructor(filePath?: string);
12
+ get path(): string;
13
+ read(): Promise<LocalNodePairingSessionRecord[]>;
14
+ write(sessions: LocalNodePairingSessionRecord[]): Promise<void>;
15
+ remove(): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=pairing-session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-session-store.d.ts","sourceRoot":"","sources":["../../src/pairing-session-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;AAMvE,eAAO,MAAM,oCAAoC,6BAA6B,CAAC;AAI/E,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,6BAA6B,EAAE,CAAC,CAAC;IACjD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,CAAC,QAAQ,EAAE,6BAA6B,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AAED,qBAAa,2BAA4B,YAAW,4BAA4B;;gBAG3D,QAAQ,CAAC,EAAE,MAAM;IAIpC,IAAW,IAAI,IAAI,MAAM,CAExB;IAEY,IAAI,IAAI,OAAO,CAAC,6BAA6B,EAAE,CAAC;IA+BhD,KAAK,CAAC,QAAQ,EAAE,6BAA6B,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAe/D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@enbox/local-node",
3
+ "version": "0.0.1",
4
+ "description": "Shell-agnostic local DWN node runtime for Enbox",
5
+ "type": "module",
6
+ "main": "./dist/esm/index.js",
7
+ "module": "./dist/esm/index.js",
8
+ "sideEffects": false,
9
+ "types": "./dist/types/index.d.ts",
10
+ "bin": {
11
+ "enbox-local-node": "./dist/esm/cli.js"
12
+ },
13
+ "scripts": {
14
+ "clean": "rm -rf dist",
15
+ "build:esm": "rm -rf dist/esm dist/types && bun tsc -p tsconfig.json",
16
+ "build": "bun run clean && bun run build:esm",
17
+ "lint": "eslint . --max-warnings 0",
18
+ "lint:fix": "eslint . --fix",
19
+ "test:node": "bun test --timeout 10000 .spec.ts",
20
+ "test:node:coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir=coverage .spec.ts"
21
+ },
22
+ "homepage": "https://github.com/enboxorg/enbox/tree/main/packages/local-node#readme",
23
+ "bugs": "https://github.com/enboxorg/enbox/issues",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/enboxorg/enbox.git",
27
+ "directory": "packages/local-node"
28
+ },
29
+ "license": "Apache-2.0",
30
+ "files": [
31
+ "dist",
32
+ "src"
33
+ ],
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/types/index.d.ts",
37
+ "import": "./dist/esm/index.js"
38
+ }
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "engines": {
44
+ "bun": ">=1.3.14"
45
+ },
46
+ "dependencies": {
47
+ "@enbox/agent": "0.8.15",
48
+ "@enbox/dwn-server": "0.1.20"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "22.19.15",
52
+ "@typescript-eslint/eslint-plugin": "8.32.1",
53
+ "@typescript-eslint/parser": "8.32.1",
54
+ "bun-types": "1.3.14",
55
+ "eslint": "9.7.0",
56
+ "typescript": "5.9.3"
57
+ }
58
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { AllowOriginPairingBroker, LocalNode, TtyPairingBroker } from './index.js';
4
+
5
+ type CliOptions = {
6
+ allowedOrigins : string[];
7
+ help : boolean;
8
+ hostname? : string;
9
+ port? : number;
10
+ storageUrl? : string;
11
+ webSocketSupport : boolean;
12
+ };
13
+
14
+ function parseArgs(args: string[]): CliOptions {
15
+ const options: CliOptions = {
16
+ allowedOrigins : [],
17
+ help : false,
18
+ webSocketSupport : true,
19
+ };
20
+
21
+ for (let i = 0; i < args.length; i += 1) {
22
+ const arg = args[i];
23
+ if (arg === '--help' || arg === '-h') {
24
+ options.help = true;
25
+ } else if (arg === '--allow-origin') {
26
+ options.allowedOrigins.push(readValue(args, i, arg));
27
+ i += 1;
28
+ } else if (arg === '--host') {
29
+ options.hostname = readValue(args, i, arg);
30
+ i += 1;
31
+ } else if (arg === '--no-ws') {
32
+ options.webSocketSupport = false;
33
+ } else if (arg === '--port') {
34
+ options.port = parsePort(readValue(args, i, arg));
35
+ i += 1;
36
+ } else if (arg === '--storage-url') {
37
+ options.storageUrl = readValue(args, i, arg);
38
+ i += 1;
39
+ } else {
40
+ throw new Error(`Unknown option: ${arg}`);
41
+ }
42
+ }
43
+
44
+ return options;
45
+ }
46
+
47
+ function readValue(args: string[], index: number, flag: string): string {
48
+ const value = args[index + 1];
49
+ if (value === undefined || value.startsWith('-')) {
50
+ throw new Error(`${flag} requires a value.`);
51
+ }
52
+ return value;
53
+ }
54
+
55
+ function parsePort(value: string): number {
56
+ const port = Number.parseInt(value, 10);
57
+ if (!Number.isInteger(port) || port <= 0 || port > 65_535) {
58
+ throw new Error(`Invalid port: ${value}`);
59
+ }
60
+ return port;
61
+ }
62
+
63
+ function printHelp(): void {
64
+ console.log(`Usage: enbox-local-node [options]
65
+
66
+ Options:
67
+ --allow-origin <origin> Auto-approve a browser origin for dev/CI. Repeatable.
68
+ --host <hostname> Loopback bind hostname. Defaults to 127.0.0.1.
69
+ --port <port> Bind a single port instead of the shared candidate list.
70
+ --storage-url <url> DWN Level/SQL storage URL. Defaults to dwn-server config.
71
+ --no-ws Disable WebSocket support.
72
+ -h, --help Show this help text.
73
+ `);
74
+ }
75
+
76
+ async function run(): Promise<void> {
77
+ const options = parseArgs(process.argv.slice(2));
78
+ if (options.help) {
79
+ printHelp();
80
+ return;
81
+ }
82
+
83
+ const node = new LocalNode({
84
+ allowedOrigins : options.allowedOrigins,
85
+ hostname : options.hostname,
86
+ pairingBroker : new AllowOriginPairingBroker(options.allowedOrigins, new TtyPairingBroker()),
87
+ portCandidates : options.port === undefined ? undefined : [options.port],
88
+ storageUrl : options.storageUrl,
89
+ webSocketSupport : options.webSocketSupport,
90
+ });
91
+
92
+ const result = await node.start();
93
+ console.log(`Enbox local node listening on ${result.endpoint}`);
94
+ console.log(`Discovery file: ${result.discoveryFilePath}`);
95
+
96
+ const shutdown = async (): Promise<void> => {
97
+ await node.stop();
98
+ process.exit(0);
99
+ };
100
+
101
+ process.on('SIGINT', () => {
102
+ void shutdown();
103
+ });
104
+ process.on('SIGTERM', () => {
105
+ void shutdown();
106
+ });
107
+
108
+ await new Promise((): void => {});
109
+ }
110
+
111
+ try {
112
+ await run();
113
+ } catch (error: unknown) {
114
+ console.error(error);
115
+ process.exit(1);
116
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './local-node.js';
2
+ export * from './local-node-config.js';
3
+ export * from './pairing-session-store.js';
4
+ export * from './pairing-broker.js';
@@ -0,0 +1,60 @@
1
+ import type { DwnServerConfig } from '@enbox/dwn-server';
2
+
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+
7
+ import { defaultDwnServerConfig } from '@enbox/dwn-server';
8
+
9
+ export type LocalNodeServerConfigOptions = {
10
+ allowedOrigins? : string[];
11
+ baseUrl? : string;
12
+ dataStore? : string;
13
+ deliveryEnabled? : boolean;
14
+ eventBusPluginPath? : string;
15
+ forwardingEnabled? : boolean;
16
+ hostname? : string;
17
+ logLevel? : string;
18
+ maxRecordDataSize? : number;
19
+ messageStore? : string;
20
+ packageJsonPath? : string;
21
+ port : number;
22
+ registrationStoreUrl? : string;
23
+ resumableTaskStore? : string;
24
+ serverName? : string;
25
+ storageUrl? : string;
26
+ termsOfServiceFilePath? : string;
27
+ ttlCacheUrl? : string;
28
+ webSocketSupport? : boolean;
29
+ };
30
+
31
+ export const defaultLocalNodeHostname = '127.0.0.1';
32
+ export const defaultLocalNodeStorageUrl = pathToFileURL(join(homedir(), '.enbox', 'dwn')).href.replace('file:', 'level:');
33
+
34
+ export function createLocalNodeDwnServerConfig(options: LocalNodeServerConfigOptions): DwnServerConfig {
35
+ const hostname = options.hostname ?? defaultLocalNodeHostname;
36
+ const storageUrl = options.storageUrl ?? process.env.DWN_STORAGE ?? defaultLocalNodeStorageUrl;
37
+
38
+ return {
39
+ ...defaultDwnServerConfig,
40
+ baseUrl : options.baseUrl ?? `http://${hostname}:${options.port}`,
41
+ dataStore : options.dataStore ?? process.env.DWN_STORAGE_DATA ?? storageUrl,
42
+ deliveryEnabled : options.deliveryEnabled ?? true,
43
+ eventBusPluginPath : options.eventBusPluginPath ?? defaultDwnServerConfig.eventBusPluginPath,
44
+ forwardingEnabled : options.forwardingEnabled ?? true,
45
+ hostname,
46
+ localNodeAllowedOrigins : options.allowedOrigins ?? [],
47
+ localNodeProfileEnabled : true,
48
+ logLevel : options.logLevel ?? defaultDwnServerConfig.logLevel,
49
+ maxRecordDataSize : options.maxRecordDataSize ?? 1_073_741_824,
50
+ messageStore : options.messageStore ?? process.env.DWN_STORAGE_MESSAGES ?? storageUrl,
51
+ packageJsonPath : options.packageJsonPath ?? defaultDwnServerConfig.packageJsonPath,
52
+ port : options.port,
53
+ registrationStoreUrl : options.registrationStoreUrl ?? defaultDwnServerConfig.registrationStoreUrl,
54
+ resumableTaskStore : options.resumableTaskStore ?? process.env.DWN_STORAGE_RESUMABLE_TASKS ?? storageUrl,
55
+ serverName : options.serverName ?? defaultDwnServerConfig.serverName,
56
+ termsOfServiceFilePath : options.termsOfServiceFilePath ?? defaultDwnServerConfig.termsOfServiceFilePath,
57
+ ttlCacheUrl : options.ttlCacheUrl ?? defaultDwnServerConfig.ttlCacheUrl,
58
+ webSocketSupport : options.webSocketSupport ?? defaultDwnServerConfig.webSocketSupport,
59
+ };
60
+ }