anchordb-relay 0.2.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/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/cjs/cli.d.ts +3 -0
- package/dist/cjs/cli.d.ts.map +1 -0
- package/dist/cjs/cli.js +57 -0
- package/dist/cjs/cli.js.map +1 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +10 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/mongo-bridge.d.ts +65 -0
- package/dist/cjs/mongo-bridge.d.ts.map +1 -0
- package/dist/cjs/mongo-bridge.js +289 -0
- package/dist/cjs/mongo-bridge.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/relay.d.ts +78 -0
- package/dist/cjs/relay.d.ts.map +1 -0
- package/dist/cjs/relay.js +117 -0
- package/dist/cjs/relay.js.map +1 -0
- package/dist/cjs/server.d.ts +30 -0
- package/dist/cjs/server.d.ts.map +1 -0
- package/dist/cjs/server.js +97 -0
- package/dist/cjs/server.js.map +1 -0
- package/dist/esm/cli.d.ts +3 -0
- package/dist/esm/cli.d.ts.map +1 -0
- package/dist/esm/cli.js +55 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mongo-bridge.d.ts +65 -0
- package/dist/esm/mongo-bridge.d.ts.map +1 -0
- package/dist/esm/mongo-bridge.js +252 -0
- package/dist/esm/mongo-bridge.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/relay.d.ts +78 -0
- package/dist/esm/relay.d.ts.map +1 -0
- package/dist/esm/relay.js +113 -0
- package/dist/esm/relay.js.map +1 -0
- package/dist/esm/server.d.ts +30 -0
- package/dist/esm/server.d.ts.map +1 -0
- package/dist/esm/server.js +61 -0
- package/dist/esm/server.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AnchorDB contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# anchordb-relay
|
|
2
|
+
|
|
3
|
+
Development-only relay that connects an app running [AnchorDB](https://www.npmjs.com/package/anchordb)
|
|
4
|
+
to Anchor Lens.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx anchor-relay
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
Anchor Inspector Relay
|
|
12
|
+
──────────────────────
|
|
13
|
+
listening ws://127.0.0.1:9440
|
|
14
|
+
room k3Jf9Xq2mNp4
|
|
15
|
+
agent URL ws://127.0.0.1:9440/relay?room=k3Jf9Xq2mNp4&role=agent
|
|
16
|
+
Lens link anchor-lens://connect?relay=...&room=k3Jf9Xq2mNp4
|
|
17
|
+
MongoDB off — start with --mongo-bridge to sync collections to a real MongoDB
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Why a relay exists
|
|
21
|
+
|
|
22
|
+
React Native and browsers can open a WebSocket **client** but cannot **listen** without a native TCP
|
|
23
|
+
module — which would force an Expo dev build and rule out Expo Go entirely. So both the app and Lens
|
|
24
|
+
dial out to this small Node process, which pairs them by room and copies frames across.
|
|
25
|
+
|
|
26
|
+
It is a dumb pipe on purpose: it never parses the inspector protocol, so the protocol can change
|
|
27
|
+
without the relay ever needing an update.
|
|
28
|
+
|
|
29
|
+
## What it deliberately does not do
|
|
30
|
+
|
|
31
|
+
It **does not authenticate the inspector session.** Pairing is end-to-end between the app and Lens
|
|
32
|
+
(an HMAC over a challenge), so a compromised or malicious relay still cannot issue itself a session.
|
|
33
|
+
The room id only decides who is connected to whom; it is not the security boundary.
|
|
34
|
+
|
|
35
|
+
The QR payload carries the relay address and room — **never the pairing code**, which the app
|
|
36
|
+
displays separately. Photographing the screen is therefore not enough to pair.
|
|
37
|
+
|
|
38
|
+
## The MongoDB bridge
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install mongodb # an optional dependency, installed where the relay runs
|
|
42
|
+
npx anchor-relay --mongo-bridge
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Anchor Lens can move collections between a device and a real MongoDB, but it cannot open the
|
|
46
|
+
connection itself: the driver needs raw TCP and TLS sockets, and React Native has neither. So the
|
|
47
|
+
driver runs here, and Lens drives it over `/bridge`:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
pull: relay reads MongoDB ──Extended JSON──► agent.importCollection()
|
|
51
|
+
push: agent.exportCollection() ──Extended JSON──► relay writes MongoDB
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Both hops carry Extended JSON — the interchange format `anchordb` already cross-validates
|
|
55
|
+
against the official `bson` package — so an ObjectId is still an ObjectId at the far end. Anchor's
|
|
56
|
+
codec encodes on the Anchor side and MongoDB's own codec decodes on the MongoDB side; there is no
|
|
57
|
+
third interpretation anywhere.
|
|
58
|
+
|
|
59
|
+
### Three restrictions, and why
|
|
60
|
+
|
|
61
|
+
- **Off unless you ask for it.** A relay that will dial any MongoDB on request is a confused deputy:
|
|
62
|
+
every process on the machine could use it to reach a database it has no credentials for.
|
|
63
|
+
`--mongo-bridge` is the opt-in.
|
|
64
|
+
- **The room is the token.** A bridge socket must present the room id the relay printed, so knowing
|
|
65
|
+
the port is not enough.
|
|
66
|
+
- **The connection string is never stored and never logged.** It lives in one `MongoClient` for the
|
|
67
|
+
life of one socket and dies with it. Every log line and every error message — including connection
|
|
68
|
+
failures, where a URI is most likely to escape — goes through `redactMongoUri`.
|
|
69
|
+
|
|
70
|
+
`--host 0.0.0.0` together with `--mongo-bridge` is a combination worth thinking twice about.
|
|
71
|
+
|
|
72
|
+
## Safety
|
|
73
|
+
|
|
74
|
+
Binds to `127.0.0.1` by default. A relay reachable from the whole network is a database reachable
|
|
75
|
+
from the whole network, so `--host 0.0.0.0` is a deliberate opt-in and prints a warning.
|
|
76
|
+
|
|
77
|
+
Development only. Never run this in production.
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { InspectorRelay, startRelayServer } from "anchordb-relay";
|
|
83
|
+
|
|
84
|
+
const server = await startRelayServer({ port: 9440 });
|
|
85
|
+
const room = server.relay.createRoom("MyApp");
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`InspectorRelay` is transport-agnostic and has no `ws` dependency, so the routing logic can be
|
|
89
|
+
driven by any socket implementation — or tested without opening a port.
|
|
90
|
+
|
|
91
|
+
## Status
|
|
92
|
+
|
|
93
|
+
**0.2.0.** MIT.
|
|
94
|
+
|
|
95
|
+
**37 tests.** Room routing and isolation, frame limits, and the MongoDB bridge: access control,
|
|
96
|
+
streamed reads, all three write modes, session lifetime, and that a password reaches neither a log
|
|
97
|
+
line nor an error message.
|
|
98
|
+
|
|
99
|
+
The bridge is tested against an injected fake driver — that seam is why `MongoDriver` exists.
|
|
100
|
+
Everything above the driver is exercised for real; **the driver calls themselves have never run
|
|
101
|
+
against a live MongoDB**, because there is none on the machine this was built on.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cjs/cli.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const server_ts_1 = require("./server.js");
|
|
5
|
+
/**
|
|
6
|
+
* `npx anchor-relay`
|
|
7
|
+
*
|
|
8
|
+
* Prints the room and the QR payload a developer scans. Binds to 127.0.0.1 by default: a relay
|
|
9
|
+
* reachable from the whole network is a database reachable from the whole network, so opening it
|
|
10
|
+
* up has to be a deliberate `--host 0.0.0.0`.
|
|
11
|
+
*/
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const readFlag = (name, fallback) => {
|
|
14
|
+
const i = args.indexOf(`--${name}`);
|
|
15
|
+
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
|
16
|
+
};
|
|
17
|
+
const readSwitch = (name) => args.includes(`--${name}`);
|
|
18
|
+
const port = Number(readFlag("port", "9440"));
|
|
19
|
+
const host = readFlag("host", "127.0.0.1");
|
|
20
|
+
const mongoBridge = readSwitch("mongo-bridge");
|
|
21
|
+
// Wrapped in a function rather than using top-level await: this file is emitted as both ESM and
|
|
22
|
+
// CommonJS, and top-level await cannot be expressed in CJS at all.
|
|
23
|
+
async function main() {
|
|
24
|
+
const server = await (0, server_ts_1.startRelayServer)({ port, host, mongoBridge, onLog: (m) => console.log(` ${m}`) });
|
|
25
|
+
const room = server.relay.createRoom("AnchorDB");
|
|
26
|
+
const info = server.relay.connectionInfo(host, port, room);
|
|
27
|
+
console.log("\nAnchor Inspector Relay");
|
|
28
|
+
console.log("──────────────────────");
|
|
29
|
+
console.log(` listening ws://${host}:${port}`);
|
|
30
|
+
console.log(` room ${room}`);
|
|
31
|
+
console.log(` agent URL ws://${host}:${port}/relay?room=${room}&role=agent`);
|
|
32
|
+
console.log(` Lens link ${info.deepLink}`);
|
|
33
|
+
if (mongoBridge) {
|
|
34
|
+
console.log(` MongoDB ws://${host}:${port}/bridge?room=${room}`);
|
|
35
|
+
console.log("\n \x1b[33m! The MongoDB bridge is ON. Anything that can reach this relay and knows the\x1b[0m");
|
|
36
|
+
console.log(" \x1b[33m room can use this machine to reach a database you connect to.\x1b[0m");
|
|
37
|
+
console.log(" Connection strings are never stored or logged — they live only while Lens is connected.");
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
console.log(" MongoDB off — start with --mongo-bridge to sync collections to a real MongoDB");
|
|
41
|
+
}
|
|
42
|
+
if (host !== "127.0.0.1") {
|
|
43
|
+
console.log("\n \x1b[33m! Bound beyond localhost — anyone on this network can reach the relay.\x1b[0m");
|
|
44
|
+
}
|
|
45
|
+
console.log("\n The pairing code is shown by the APP, not here: the QR alone must not be enough to pair.\n");
|
|
46
|
+
const shutdown = async () => {
|
|
47
|
+
await server.close();
|
|
48
|
+
process.exit(0);
|
|
49
|
+
};
|
|
50
|
+
process.on("SIGINT", shutdown);
|
|
51
|
+
process.on("SIGTERM", shutdown);
|
|
52
|
+
}
|
|
53
|
+
main().catch((err) => {
|
|
54
|
+
console.error("anchor-relay failed to start:", err);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { InspectorRelay } from "./relay.js";
|
|
2
|
+
export type { RelayOptions, RelayRole, RelaySocket, RoomInfo } from "./relay.js";
|
|
3
|
+
export { MongoBridge } from "./mongo-bridge.js";
|
|
4
|
+
export type { MongoBridgeOptions, MongoConnection, MongoDriver } from "./mongo-bridge.js";
|
|
5
|
+
export { startRelayServer } from "./server.js";
|
|
6
|
+
export type { RelayServer, RelayServerOptions } from "./server.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startRelayServer = exports.MongoBridge = exports.InspectorRelay = void 0;
|
|
4
|
+
var relay_ts_1 = require("./relay.js");
|
|
5
|
+
Object.defineProperty(exports, "InspectorRelay", { enumerable: true, get: function () { return relay_ts_1.InspectorRelay; } });
|
|
6
|
+
var mongo_bridge_ts_1 = require("./mongo-bridge.js");
|
|
7
|
+
Object.defineProperty(exports, "MongoBridge", { enumerable: true, get: function () { return mongo_bridge_ts_1.MongoBridge; } });
|
|
8
|
+
var server_ts_1 = require("./server.js");
|
|
9
|
+
Object.defineProperty(exports, "startRelayServer", { enumerable: true, get: function () { return server_ts_1.startRelayServer; } });
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,uCAA4C;AAAnC,0GAAA,cAAc,OAAA;AAEvB,qDAAgD;AAAvC,8GAAA,WAAW,OAAA;AAEpB,yCAA+C;AAAtC,6GAAA,gBAAgB,OAAA"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type BridgeWriteMode, type MongoCollectionInfo } from "anchordb";
|
|
2
|
+
import type { RelaySocket } from "./relay.js";
|
|
3
|
+
/**
|
|
4
|
+
* The MongoDB half of the bridge.
|
|
5
|
+
*
|
|
6
|
+
* Anchor Lens cannot open a MongoDB connection — React Native has no TCP sockets — so the driver
|
|
7
|
+
* runs here, in the relay, and Lens drives it. See `bridge.ts` in `anchordb` for the protocol
|
|
8
|
+
* and the reasoning.
|
|
9
|
+
*
|
|
10
|
+
* ## Three deliberate restrictions
|
|
11
|
+
*
|
|
12
|
+
* 1. **Off by default.** A relay that will dial any MongoDB on request is a confused deputy: every
|
|
13
|
+
* local process could use it to reach a database it has no credentials for. It takes
|
|
14
|
+
* `--mongo-bridge` to turn on.
|
|
15
|
+
* 2. **The room is the token.** A bridge socket must present the room id the relay printed, so
|
|
16
|
+
* knowing the port is not enough.
|
|
17
|
+
* 3. **The connection string is never stored and never logged.** It lives in one `MongoClient` for
|
|
18
|
+
* the life of one socket, and every log line and error message goes through `redactMongoUri`.
|
|
19
|
+
*
|
|
20
|
+
* Even so, this is a development tool on a developer's machine. It is not built to be exposed, and
|
|
21
|
+
* `--host 0.0.0.0` plus `--mongo-bridge` is a combination worth thinking twice about.
|
|
22
|
+
*/
|
|
23
|
+
/** The slice of the MongoDB driver this uses, so tests can supply a fake instead of a server. */
|
|
24
|
+
export interface MongoDriver {
|
|
25
|
+
connect(uri: string): Promise<MongoConnection>;
|
|
26
|
+
/** MongoDB's own Extended JSON codec — never a second implementation. */
|
|
27
|
+
stringifyEJSON(value: unknown): string;
|
|
28
|
+
parseEJSON(text: string): unknown;
|
|
29
|
+
}
|
|
30
|
+
export interface MongoConnection {
|
|
31
|
+
serverVersion(): Promise<string>;
|
|
32
|
+
listCollections(database: string): Promise<MongoCollectionInfo[]>;
|
|
33
|
+
find(database: string, collection: string, batchSize: number): AsyncIterable<unknown[]>;
|
|
34
|
+
write(database: string, collection: string, documents: unknown[], mode: BridgeWriteMode): Promise<{
|
|
35
|
+
inserted: number;
|
|
36
|
+
updated: number;
|
|
37
|
+
skipped: number;
|
|
38
|
+
}>;
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export interface MongoBridgeOptions {
|
|
42
|
+
enabled: boolean;
|
|
43
|
+
/** Returns the driver. Defaults to a lazy `import("mongodb")`. */
|
|
44
|
+
driver?: () => Promise<MongoDriver>;
|
|
45
|
+
onLog?: (message: string) => void;
|
|
46
|
+
}
|
|
47
|
+
export declare class MongoBridge {
|
|
48
|
+
private readonly enabled;
|
|
49
|
+
private readonly loadDriver;
|
|
50
|
+
private readonly onLog;
|
|
51
|
+
/** One MongoDB connection per socket, closed with it. */
|
|
52
|
+
private readonly sessions;
|
|
53
|
+
constructor(options: MongoBridgeOptions);
|
|
54
|
+
/** Attach a socket that dialled `/bridge`. */
|
|
55
|
+
join(socket: RelaySocket, room: string | null, validRoom: (room: string) => boolean): void;
|
|
56
|
+
private release;
|
|
57
|
+
private send;
|
|
58
|
+
private handle;
|
|
59
|
+
private connect;
|
|
60
|
+
private session;
|
|
61
|
+
/** Stream a collection out as newline-delimited Extended JSON. */
|
|
62
|
+
private read;
|
|
63
|
+
private write;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=mongo-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mongo-bridge.d.ts","sourceRoot":"","sources":["../../src/mongo-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACzB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,iGAAiG;AACjG,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC/C,yEAAyE;IACzE,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAClE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACxF,KAAK,CACH,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,OAAO,EAAE,EACpB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAKD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;IACpC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAUD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAClD,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6E;gBAE1F,OAAO,EAAE,kBAAkB;IAMvC,8CAA8C;IAC9C,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI;YAmB5E,OAAO;IASrB,OAAO,CAAC,IAAI;YAIE,MAAM;YAsCN,OAAO;IAwCrB,OAAO,CAAC,OAAO;IAMf,kEAAkE;YACpD,IAAI;YA0BJ,KAAK;CA4BpB"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MongoBridge = void 0;
|
|
37
|
+
const anchordb_1 = require("anchordb");
|
|
38
|
+
/** Documents per streamed frame. Large enough to be efficient, small enough to stay under limits. */
|
|
39
|
+
const READ_BATCH = 200;
|
|
40
|
+
class BridgeFailure extends Error {
|
|
41
|
+
constructor(code, message) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.code = code;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
class MongoBridge {
|
|
47
|
+
constructor(options) {
|
|
48
|
+
/** One MongoDB connection per socket, closed with it. */
|
|
49
|
+
this.sessions = new Map();
|
|
50
|
+
this.enabled = options.enabled;
|
|
51
|
+
this.loadDriver = options.driver ?? loadMongoDriver;
|
|
52
|
+
this.onLog = options.onLog ?? (() => undefined);
|
|
53
|
+
}
|
|
54
|
+
/** Attach a socket that dialled `/bridge`. */
|
|
55
|
+
join(socket, room, validRoom) {
|
|
56
|
+
if (!this.enabled) {
|
|
57
|
+
socket.close(4403, "the MongoDB bridge is disabled; start the relay with --mongo-bridge");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!room || !validRoom(room)) {
|
|
61
|
+
socket.close(4404, "a valid room is required to use the bridge");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.onLog("bridge: client joined");
|
|
65
|
+
socket.on("message", (raw) => {
|
|
66
|
+
void this.handle(socket, String(raw));
|
|
67
|
+
});
|
|
68
|
+
socket.on("close", () => {
|
|
69
|
+
void this.release(socket);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async release(socket) {
|
|
73
|
+
const session = this.sessions.get(socket);
|
|
74
|
+
if (!session)
|
|
75
|
+
return;
|
|
76
|
+
this.sessions.delete(socket);
|
|
77
|
+
// The credential dies with the socket — there is nowhere else it is held.
|
|
78
|
+
await session.connection.close().catch(() => undefined);
|
|
79
|
+
this.onLog("bridge: connection closed");
|
|
80
|
+
}
|
|
81
|
+
send(socket, response) {
|
|
82
|
+
socket.send(JSON.stringify(response));
|
|
83
|
+
}
|
|
84
|
+
async handle(socket, raw) {
|
|
85
|
+
let request;
|
|
86
|
+
try {
|
|
87
|
+
request = JSON.parse(raw);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return; // not a bridge frame
|
|
91
|
+
}
|
|
92
|
+
if (!request || typeof request.id !== "string" || typeof request.op !== "string")
|
|
93
|
+
return;
|
|
94
|
+
const base = { v: anchordb_1.BRIDGE_PROTOCOL_VERSION, id: request.id };
|
|
95
|
+
try {
|
|
96
|
+
switch (request.op) {
|
|
97
|
+
case "connect":
|
|
98
|
+
this.send(socket, { ...base, ok: true, result: await this.connect(socket, request) });
|
|
99
|
+
return;
|
|
100
|
+
case "read":
|
|
101
|
+
await this.read(socket, request, base);
|
|
102
|
+
return;
|
|
103
|
+
case "write":
|
|
104
|
+
this.send(socket, { ...base, ok: true, result: await this.write(socket, request) });
|
|
105
|
+
return;
|
|
106
|
+
case "disconnect":
|
|
107
|
+
await this.release(socket);
|
|
108
|
+
this.send(socket, { ...base, ok: true, result: { closed: true } });
|
|
109
|
+
return;
|
|
110
|
+
default:
|
|
111
|
+
throw new BridgeFailure("bad_request", `Unknown bridge operation "${String(request.op)}"`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
const failure = err instanceof BridgeFailure
|
|
116
|
+
? err
|
|
117
|
+
: new BridgeFailure("failed", err.message ?? "The bridge failed.");
|
|
118
|
+
this.onLog(`bridge: ${request.op} failed — ${failure.message}`);
|
|
119
|
+
this.send(socket, { ...base, ok: false, error: { code: failure.code, message: failure.message } });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async connect(socket, request) {
|
|
123
|
+
const uri = String(request.uri ?? "");
|
|
124
|
+
const database = String(request.database ?? "");
|
|
125
|
+
if (!uri || !database) {
|
|
126
|
+
throw new BridgeFailure("bad_request", "A connection string and a database name are required.");
|
|
127
|
+
}
|
|
128
|
+
let driver;
|
|
129
|
+
try {
|
|
130
|
+
driver = await this.loadDriver();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
throw new BridgeFailure("driver_missing", "The mongodb driver is not installed. Run `npm install mongodb` where the relay runs.");
|
|
134
|
+
}
|
|
135
|
+
await this.release(socket); // one connection per socket; a second connect replaces the first
|
|
136
|
+
let connection;
|
|
137
|
+
try {
|
|
138
|
+
connection = await driver.connect(uri);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
// The URI is in scope here and must not reach the message.
|
|
142
|
+
throw new BridgeFailure("connect_failed", `Could not connect to ${(0, anchordb_1.redactMongoUri)(uri)} — ${err.message}`);
|
|
143
|
+
}
|
|
144
|
+
this.sessions.set(socket, { connection, database });
|
|
145
|
+
this.onLog(`bridge: connected to ${(0, anchordb_1.redactMongoUri)(uri)}`);
|
|
146
|
+
return {
|
|
147
|
+
database,
|
|
148
|
+
serverVersion: await connection.serverVersion(),
|
|
149
|
+
collections: await connection.listCollections(database),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
session(socket) {
|
|
153
|
+
const session = this.sessions.get(socket);
|
|
154
|
+
if (!session)
|
|
155
|
+
throw new BridgeFailure("not_connected", "Connect to a MongoDB first.");
|
|
156
|
+
return session;
|
|
157
|
+
}
|
|
158
|
+
/** Stream a collection out as newline-delimited Extended JSON. */
|
|
159
|
+
async read(socket, request, base) {
|
|
160
|
+
const { connection, database } = this.session(socket);
|
|
161
|
+
const collection = String(request.collection ?? "");
|
|
162
|
+
if (!collection)
|
|
163
|
+
throw new BridgeFailure("bad_request", "A collection name is required.");
|
|
164
|
+
const driver = await this.loadDriver();
|
|
165
|
+
let sent = 0;
|
|
166
|
+
// Batched rather than one frame: a large collection would exceed any sane frame limit, and
|
|
167
|
+
// holding the whole thing as a single string on both sides at once is avoidable.
|
|
168
|
+
for await (const batch of connection.find(database, collection, READ_BATCH)) {
|
|
169
|
+
const chunk = batch.map((doc) => driver.stringifyEJSON(doc)).join("\n");
|
|
170
|
+
sent += batch.length;
|
|
171
|
+
this.send(socket, { ...base, ok: true, chunk, done: false, sent });
|
|
172
|
+
}
|
|
173
|
+
this.send(socket, {
|
|
174
|
+
...base,
|
|
175
|
+
ok: true,
|
|
176
|
+
chunk: "",
|
|
177
|
+
done: true,
|
|
178
|
+
sent,
|
|
179
|
+
result: { collection, count: sent },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
async write(socket, request) {
|
|
183
|
+
const { connection, database } = this.session(socket);
|
|
184
|
+
const collection = String(request.collection ?? "");
|
|
185
|
+
if (!collection)
|
|
186
|
+
throw new BridgeFailure("bad_request", "A collection name is required.");
|
|
187
|
+
const mode = (request.mode ?? "insert");
|
|
188
|
+
const driver = await this.loadDriver();
|
|
189
|
+
// Decoded with MongoDB's own Extended JSON codec, so an $oid becomes a real driver ObjectId
|
|
190
|
+
// rather than something that would land in the database as a string.
|
|
191
|
+
const documents = String(request.data ?? "")
|
|
192
|
+
.split("\n")
|
|
193
|
+
.map((line) => line.trim())
|
|
194
|
+
.filter(Boolean)
|
|
195
|
+
.map((line, index) => {
|
|
196
|
+
try {
|
|
197
|
+
return driver.parseEJSON(line);
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
throw new BridgeFailure("bad_request", `Malformed Extended JSON on line ${index + 1}: ${err.message}`);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
if (documents.length === 0)
|
|
204
|
+
return { collection, inserted: 0, updated: 0, skipped: 0 };
|
|
205
|
+
const result = await connection.write(database, collection, documents, mode);
|
|
206
|
+
this.onLog(`bridge: wrote ${documents.length} document(s) to ${collection}`);
|
|
207
|
+
return { collection, ...result };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
exports.MongoBridge = MongoBridge;
|
|
211
|
+
/**
|
|
212
|
+
* Load the real driver.
|
|
213
|
+
*
|
|
214
|
+
* A lazy import so the relay stays usable — and installable — without `mongodb`. It is an optional
|
|
215
|
+
* dependency: most people running `npx anchor-relay` only want the inspector.
|
|
216
|
+
*/
|
|
217
|
+
async function loadMongoDriver() {
|
|
218
|
+
// mongodb is an OPTIONAL dependency: the relay must install and run without it, and most
|
|
219
|
+
// people running the inspector never want the driver. MongoModule above types the slice used
|
|
220
|
+
// here, so absence is a runtime condition rather than a compile error.
|
|
221
|
+
// @ts-ignore -- resolved at runtime; may legitimately not be installed
|
|
222
|
+
const mongo = (await Promise.resolve().then(() => __importStar(require("mongodb"))));
|
|
223
|
+
const EJSON = mongo.EJSON ?? mongo.BSON?.EJSON;
|
|
224
|
+
if (!EJSON)
|
|
225
|
+
throw new Error("This mongodb driver does not expose Extended JSON.");
|
|
226
|
+
return {
|
|
227
|
+
stringifyEJSON: (value) => EJSON.stringify(value, { relaxed: false }),
|
|
228
|
+
parseEJSON: (text) => EJSON.parse(text, { relaxed: false }),
|
|
229
|
+
async connect(uri) {
|
|
230
|
+
const client = new mongo.MongoClient(uri, { serverSelectionTimeoutMS: 10_000 });
|
|
231
|
+
await client.connect();
|
|
232
|
+
return {
|
|
233
|
+
async serverVersion() {
|
|
234
|
+
const info = (await client.db("admin").command({ buildInfo: 1 }));
|
|
235
|
+
return info.version ?? "unknown";
|
|
236
|
+
},
|
|
237
|
+
async listCollections(database) {
|
|
238
|
+
const db = client.db(database);
|
|
239
|
+
const names = await db.listCollections({}, { nameOnly: true }).toArray();
|
|
240
|
+
const out = [];
|
|
241
|
+
for (const { name } of names) {
|
|
242
|
+
// Estimated: instant, where an exact count is a full scan on a large collection.
|
|
243
|
+
out.push({ name, count: await db.collection(name).estimatedDocumentCount() });
|
|
244
|
+
}
|
|
245
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
246
|
+
},
|
|
247
|
+
async *find(database, collection, batchSize) {
|
|
248
|
+
const cursor = client.db(database).collection(collection).find({}, { batchSize });
|
|
249
|
+
let batch = [];
|
|
250
|
+
for await (const doc of cursor) {
|
|
251
|
+
batch.push(doc);
|
|
252
|
+
if (batch.length >= batchSize) {
|
|
253
|
+
yield batch;
|
|
254
|
+
batch = [];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (batch.length > 0)
|
|
258
|
+
yield batch;
|
|
259
|
+
},
|
|
260
|
+
async write(database, collection, documents, mode) {
|
|
261
|
+
const target = client.db(database).collection(collection);
|
|
262
|
+
if (mode === "replace")
|
|
263
|
+
await target.deleteMany({});
|
|
264
|
+
const summary = { inserted: 0, updated: 0, skipped: 0 };
|
|
265
|
+
const withId = documents;
|
|
266
|
+
if (mode === "insert") {
|
|
267
|
+
// $setOnInsert leaves an existing document untouched, which is "skip" without needing a
|
|
268
|
+
// read first or a thrown duplicate-key error to catch.
|
|
269
|
+
const result = await target.bulkWrite(withId.map((doc) => ({
|
|
270
|
+
updateOne: { filter: { _id: doc._id }, update: { $setOnInsert: doc }, upsert: true },
|
|
271
|
+
})), { ordered: false });
|
|
272
|
+
summary.inserted = result.upsertedCount ?? 0;
|
|
273
|
+
summary.skipped = (result.matchedCount ?? 0);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
const result = await target.bulkWrite(withId.map((doc) => ({
|
|
277
|
+
replaceOne: { filter: { _id: doc._id }, replacement: doc, upsert: true },
|
|
278
|
+
})), { ordered: false });
|
|
279
|
+
summary.inserted = result.upsertedCount ?? 0;
|
|
280
|
+
summary.updated = result.modifiedCount ?? 0;
|
|
281
|
+
}
|
|
282
|
+
return summary;
|
|
283
|
+
},
|
|
284
|
+
close: () => client.close(),
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=mongo-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mongo-bridge.js","sourceRoot":"","sources":["../../src/mongo-bridge.ts"],"names":[],"mappings":"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"}
|