@juit/pgproxy-client-node 1.0.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/README.md +33 -0
- package/dist/index.cjs +139 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.mjs +103 -0
- package/dist/index.mjs.map +6 -0
- package/package.json +49 -0
- package/src/index.ts +143 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# PostgreSQL Proxy Client (NodeJS Implementation)
|
|
2
|
+
|
|
3
|
+
This package provides a NodeJS specific client for PGProxy Servers.
|
|
4
|
+
|
|
5
|
+
* [Usage with PGClient](#usage-with-pgclient)
|
|
6
|
+
* [Direct Usage](#direct-usage)
|
|
7
|
+
* [PGProxy](https://github.com/juitnow/juit-pgproxy/blob/main/README.md)
|
|
8
|
+
* [Copyright Notice](https://github.com/juitnow/juit-pgproxy/blob/main/NOTICE.md)
|
|
9
|
+
* [License](https://github.com/juitnow/juit-pgproxy/blob/main/NOTICE.md)
|
|
10
|
+
|
|
11
|
+
### Usage with PGClient
|
|
12
|
+
|
|
13
|
+
Simply register the client by importing it, and ensure that the `PGURL`
|
|
14
|
+
environment variable is set to the HTTP/HTTPS url of the server (or specify
|
|
15
|
+
the URL in the constructor):
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import '@juit/pgproxy-client-node'
|
|
19
|
+
import { PGClient } from '@juit/pgproxy-client'
|
|
20
|
+
|
|
21
|
+
const client = new PGClient('https://my-secret@my-pgproxy-server:54321/')
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Direct usage
|
|
25
|
+
|
|
26
|
+
The NodeJS client can be used directly by simply importing the `NodeClient`
|
|
27
|
+
class:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { NodeClient } from '@juit/pgproxy-client-node'
|
|
31
|
+
|
|
32
|
+
const client = new NodeClient('https://my-secret@my-pgproxy-server:54321/')
|
|
33
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
NodeClient: () => NodeClient,
|
|
34
|
+
NodeProvider: () => NodeProvider
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(src_exports);
|
|
37
|
+
var import_node_crypto = require("node:crypto");
|
|
38
|
+
var import_node_http = require("node:http");
|
|
39
|
+
var import_node_https = require("node:https");
|
|
40
|
+
var import_pgproxy_client = require("@juit/pgproxy-client");
|
|
41
|
+
var import_ws = __toESM(require("ws"));
|
|
42
|
+
function getAuthenticationToken(secret) {
|
|
43
|
+
const buffer = (0, import_node_crypto.randomBytes)(48);
|
|
44
|
+
buffer.writeBigInt64LE(BigInt(Date.now()), 0);
|
|
45
|
+
(0, import_node_crypto.createHmac)("sha256", Buffer.from(secret, "utf8")).update(buffer.subarray(0, 16)).digest().copy(buffer, 16);
|
|
46
|
+
return buffer.toString("base64");
|
|
47
|
+
}
|
|
48
|
+
function makeQuery(url, secret) {
|
|
49
|
+
const protocol = url.protocol === "https:" ? import_node_https.request : url.protocol === "http:" ? import_node_http.request : void 0;
|
|
50
|
+
(0, import_pgproxy_client.assert)(protocol, `Unsupported protocol "${url.protocol}"`);
|
|
51
|
+
const href = url.href;
|
|
52
|
+
return function query(query, params) {
|
|
53
|
+
const id = (0, import_node_crypto.randomUUID)();
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const token = getAuthenticationToken(secret);
|
|
56
|
+
const url2 = new URL(href);
|
|
57
|
+
url2.searchParams.set("auth", token);
|
|
58
|
+
const req = protocol(url2, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: { "content-type": "application/json" }
|
|
61
|
+
}, (res) => {
|
|
62
|
+
const buffers = [];
|
|
63
|
+
res.on(
|
|
64
|
+
"error",
|
|
65
|
+
/* coverage ignore next */
|
|
66
|
+
(error) => reject(error)
|
|
67
|
+
);
|
|
68
|
+
res.on("data", (buffer2) => buffers.push(buffer2));
|
|
69
|
+
res.on("end", () => {
|
|
70
|
+
if (res.headers["content-type"] !== "application/json") {
|
|
71
|
+
return reject(new Error(`Invalid response (status=${res.statusCode})`));
|
|
72
|
+
} else {
|
|
73
|
+
const data = Buffer.concat(buffers).toString("utf-8");
|
|
74
|
+
return resolve(data);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
const body = { id, query, params };
|
|
79
|
+
const buffer = JSON.stringify(body);
|
|
80
|
+
req.write(buffer);
|
|
81
|
+
req.end();
|
|
82
|
+
}).then((data) => {
|
|
83
|
+
let payload;
|
|
84
|
+
try {
|
|
85
|
+
payload = JSON.parse(data);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
throw new Error("Unable to parse JSON payload");
|
|
88
|
+
}
|
|
89
|
+
(0, import_pgproxy_client.assert)(payload && typeof payload === "object", "JSON payload is not an object");
|
|
90
|
+
(0, import_pgproxy_client.assert)(payload.id === id, "Invalid/uncorrelated ID in response");
|
|
91
|
+
if (payload.statusCode === 200)
|
|
92
|
+
return payload;
|
|
93
|
+
throw new Error(`${payload.error || /* coverage ignore next */
|
|
94
|
+
"Unknown error"} (${payload.statusCode})`);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
var NodeProvider = class extends import_pgproxy_client.WebSocketProvider {
|
|
99
|
+
constructor(url) {
|
|
100
|
+
super();
|
|
101
|
+
url = new URL(url.href);
|
|
102
|
+
const secret = decodeURIComponent(url.password || url.username);
|
|
103
|
+
(0, import_pgproxy_client.assert)(secret, "No connection secret specified in URL");
|
|
104
|
+
url.password = "";
|
|
105
|
+
url.username = "";
|
|
106
|
+
const baseHttpUrl = new URL(url.href);
|
|
107
|
+
const baseWsUrl = new URL(url.href);
|
|
108
|
+
baseWsUrl.protocol = `ws${baseWsUrl.protocol.slice(4)}`;
|
|
109
|
+
this._getUniqueRequestId = () => (0, import_node_crypto.randomUUID)();
|
|
110
|
+
this._getWebSocket = async () => {
|
|
111
|
+
const token = await getAuthenticationToken(secret);
|
|
112
|
+
const wsUrl = new URL(baseWsUrl.href);
|
|
113
|
+
wsUrl.searchParams.set("auth", token);
|
|
114
|
+
return this._connectWebSocket(new import_ws.default(wsUrl));
|
|
115
|
+
};
|
|
116
|
+
this.query = makeQuery(baseHttpUrl, secret);
|
|
117
|
+
}
|
|
118
|
+
/* ======================================================================== *
|
|
119
|
+
* METHODS FROM CONSTRUCTOR *
|
|
120
|
+
* ======================================================================== */
|
|
121
|
+
query;
|
|
122
|
+
_getWebSocket;
|
|
123
|
+
_getUniqueRequestId;
|
|
124
|
+
};
|
|
125
|
+
var NodeClient = class extends import_pgproxy_client.PGClient {
|
|
126
|
+
constructor(url) {
|
|
127
|
+
url = url || process.env.PGURL;
|
|
128
|
+
(0, import_pgproxy_client.assert)(url, "No URL to connect to (PGURL environment variable missing?)");
|
|
129
|
+
super(new NodeProvider(typeof url === "string" ? new URL(url) : url));
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
(0, import_pgproxy_client.registerProvider)("http", NodeProvider);
|
|
133
|
+
(0, import_pgproxy_client.registerProvider)("https", NodeProvider);
|
|
134
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
135
|
+
0 && (module.exports = {
|
|
136
|
+
NodeClient,
|
|
137
|
+
NodeProvider
|
|
138
|
+
});
|
|
139
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts"],
|
|
4
|
+
"mappings": "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",
|
|
5
|
+
"names": ["https", "http", "url", "buffer", "WebSocket"]
|
|
6
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { PGClient, WebSocketProvider } from '@juit/pgproxy-client';
|
|
3
|
+
import WebSocket from 'ws';
|
|
4
|
+
import type { PGConnectionResult } from '@juit/pgproxy-client';
|
|
5
|
+
export declare class NodeProvider extends WebSocketProvider {
|
|
6
|
+
constructor(url: URL);
|
|
7
|
+
query: (query: string, params: (string | null)[]) => Promise<PGConnectionResult>;
|
|
8
|
+
protected _getWebSocket: () => Promise<WebSocket>;
|
|
9
|
+
protected _getUniqueRequestId: () => string;
|
|
10
|
+
}
|
|
11
|
+
export declare class NodeClient extends PGClient {
|
|
12
|
+
constructor(url?: URL | string);
|
|
13
|
+
}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// index.ts
|
|
2
|
+
import { createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
3
|
+
import { request as http } from "node:http";
|
|
4
|
+
import { request as https } from "node:https";
|
|
5
|
+
import { PGClient, WebSocketProvider, assert, registerProvider } from "@juit/pgproxy-client";
|
|
6
|
+
import WebSocket from "ws";
|
|
7
|
+
function getAuthenticationToken(secret) {
|
|
8
|
+
const buffer = randomBytes(48);
|
|
9
|
+
buffer.writeBigInt64LE(BigInt(Date.now()), 0);
|
|
10
|
+
createHmac("sha256", Buffer.from(secret, "utf8")).update(buffer.subarray(0, 16)).digest().copy(buffer, 16);
|
|
11
|
+
return buffer.toString("base64");
|
|
12
|
+
}
|
|
13
|
+
function makeQuery(url, secret) {
|
|
14
|
+
const protocol = url.protocol === "https:" ? https : url.protocol === "http:" ? http : void 0;
|
|
15
|
+
assert(protocol, `Unsupported protocol "${url.protocol}"`);
|
|
16
|
+
const href = url.href;
|
|
17
|
+
return function query(query, params) {
|
|
18
|
+
const id = randomUUID();
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const token = getAuthenticationToken(secret);
|
|
21
|
+
const url2 = new URL(href);
|
|
22
|
+
url2.searchParams.set("auth", token);
|
|
23
|
+
const req = protocol(url2, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "content-type": "application/json" }
|
|
26
|
+
}, (res) => {
|
|
27
|
+
const buffers = [];
|
|
28
|
+
res.on(
|
|
29
|
+
"error",
|
|
30
|
+
/* coverage ignore next */
|
|
31
|
+
(error) => reject(error)
|
|
32
|
+
);
|
|
33
|
+
res.on("data", (buffer2) => buffers.push(buffer2));
|
|
34
|
+
res.on("end", () => {
|
|
35
|
+
if (res.headers["content-type"] !== "application/json") {
|
|
36
|
+
return reject(new Error(`Invalid response (status=${res.statusCode})`));
|
|
37
|
+
} else {
|
|
38
|
+
const data = Buffer.concat(buffers).toString("utf-8");
|
|
39
|
+
return resolve(data);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
const body = { id, query, params };
|
|
44
|
+
const buffer = JSON.stringify(body);
|
|
45
|
+
req.write(buffer);
|
|
46
|
+
req.end();
|
|
47
|
+
}).then((data) => {
|
|
48
|
+
let payload;
|
|
49
|
+
try {
|
|
50
|
+
payload = JSON.parse(data);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error("Unable to parse JSON payload");
|
|
53
|
+
}
|
|
54
|
+
assert(payload && typeof payload === "object", "JSON payload is not an object");
|
|
55
|
+
assert(payload.id === id, "Invalid/uncorrelated ID in response");
|
|
56
|
+
if (payload.statusCode === 200)
|
|
57
|
+
return payload;
|
|
58
|
+
throw new Error(`${payload.error || /* coverage ignore next */
|
|
59
|
+
"Unknown error"} (${payload.statusCode})`);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
var NodeProvider = class extends WebSocketProvider {
|
|
64
|
+
constructor(url) {
|
|
65
|
+
super();
|
|
66
|
+
url = new URL(url.href);
|
|
67
|
+
const secret = decodeURIComponent(url.password || url.username);
|
|
68
|
+
assert(secret, "No connection secret specified in URL");
|
|
69
|
+
url.password = "";
|
|
70
|
+
url.username = "";
|
|
71
|
+
const baseHttpUrl = new URL(url.href);
|
|
72
|
+
const baseWsUrl = new URL(url.href);
|
|
73
|
+
baseWsUrl.protocol = `ws${baseWsUrl.protocol.slice(4)}`;
|
|
74
|
+
this._getUniqueRequestId = () => randomUUID();
|
|
75
|
+
this._getWebSocket = async () => {
|
|
76
|
+
const token = await getAuthenticationToken(secret);
|
|
77
|
+
const wsUrl = new URL(baseWsUrl.href);
|
|
78
|
+
wsUrl.searchParams.set("auth", token);
|
|
79
|
+
return this._connectWebSocket(new WebSocket(wsUrl));
|
|
80
|
+
};
|
|
81
|
+
this.query = makeQuery(baseHttpUrl, secret);
|
|
82
|
+
}
|
|
83
|
+
/* ======================================================================== *
|
|
84
|
+
* METHODS FROM CONSTRUCTOR *
|
|
85
|
+
* ======================================================================== */
|
|
86
|
+
query;
|
|
87
|
+
_getWebSocket;
|
|
88
|
+
_getUniqueRequestId;
|
|
89
|
+
};
|
|
90
|
+
var NodeClient = class extends PGClient {
|
|
91
|
+
constructor(url) {
|
|
92
|
+
url = url || process.env.PGURL;
|
|
93
|
+
assert(url, "No URL to connect to (PGURL environment variable missing?)");
|
|
94
|
+
super(new NodeProvider(typeof url === "string" ? new URL(url) : url));
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
registerProvider("http", NodeProvider);
|
|
98
|
+
registerProvider("https", NodeProvider);
|
|
99
|
+
export {
|
|
100
|
+
NodeClient,
|
|
101
|
+
NodeProvider
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts"],
|
|
4
|
+
"mappings": "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",
|
|
5
|
+
"names": ["url", "buffer"]
|
|
6
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@juit/pgproxy-client-node",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "./dist/index.cjs",
|
|
5
|
+
"module": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.cjs"
|
|
12
|
+
},
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.mjs"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"author": "Juit Developers <developers@juit.com>",
|
|
20
|
+
"license": "Apache-2.0",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+ssh://git@github.com/juitnow/juit-pgproxy.git"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"database",
|
|
27
|
+
"pg",
|
|
28
|
+
"pool",
|
|
29
|
+
"postgres",
|
|
30
|
+
"proxy"
|
|
31
|
+
],
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/juitnow/juit-pgproxy/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/juitnow/juit-pgproxy#readme",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@juit/pgproxy-client": "1.0.0",
|
|
38
|
+
"@types/ws": "^8.5.10",
|
|
39
|
+
"ws": "^8.15.1"
|
|
40
|
+
},
|
|
41
|
+
"directories": {
|
|
42
|
+
"test": "test"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"*.md",
|
|
46
|
+
"dist/",
|
|
47
|
+
"src/"
|
|
48
|
+
]
|
|
49
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createHmac, randomBytes, randomUUID } from 'node:crypto'
|
|
2
|
+
import { request as http } from 'node:http'
|
|
3
|
+
import { request as https } from 'node:https'
|
|
4
|
+
|
|
5
|
+
import { PGClient, WebSocketProvider, assert, registerProvider } from '@juit/pgproxy-client'
|
|
6
|
+
import WebSocket from 'ws'
|
|
7
|
+
|
|
8
|
+
import type { PGConnectionResult } from '@juit/pgproxy-client'
|
|
9
|
+
import type { Request, Response } from '@juit/pgproxy-server'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/** Create our authentication token */
|
|
13
|
+
function getAuthenticationToken(secret: string): string {
|
|
14
|
+
const buffer = randomBytes(48)
|
|
15
|
+
|
|
16
|
+
buffer.writeBigInt64LE(BigInt(Date.now()), 0)
|
|
17
|
+
|
|
18
|
+
createHmac('sha256', Buffer.from(secret, 'utf8'))
|
|
19
|
+
.update(buffer.subarray(0, 16))
|
|
20
|
+
.digest()
|
|
21
|
+
.copy(buffer, 16)
|
|
22
|
+
|
|
23
|
+
return buffer.toString('base64')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function makeQuery(url: URL, secret: string): (
|
|
27
|
+
query: string,
|
|
28
|
+
params: (string | null)[],
|
|
29
|
+
) => Promise<PGConnectionResult> {
|
|
30
|
+
const protocol =
|
|
31
|
+
url.protocol === 'https:' ? https :
|
|
32
|
+
url.protocol === 'http:' ? http :
|
|
33
|
+
undefined
|
|
34
|
+
assert(protocol, `Unsupported protocol "${url.protocol}"`)
|
|
35
|
+
const href = url.href
|
|
36
|
+
|
|
37
|
+
return function query(
|
|
38
|
+
query: string,
|
|
39
|
+
params: (string | null)[],
|
|
40
|
+
): Promise<PGConnectionResult> {
|
|
41
|
+
const id = randomUUID()
|
|
42
|
+
|
|
43
|
+
return new Promise<string>((resolve, reject) => {
|
|
44
|
+
const token = getAuthenticationToken(secret)
|
|
45
|
+
const url = new URL(href)
|
|
46
|
+
url.searchParams.set('auth', token)
|
|
47
|
+
|
|
48
|
+
const req = protocol(url, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: { 'content-type': 'application/json' },
|
|
51
|
+
}, (res) => {
|
|
52
|
+
const buffers: Buffer[] = []
|
|
53
|
+
res.on('error', /* coverage ignore next */ (error) => reject(error))
|
|
54
|
+
res.on('data', (buffer) => buffers.push(buffer))
|
|
55
|
+
res.on('end', () => {
|
|
56
|
+
if (res.headers['content-type'] !== 'application/json') {
|
|
57
|
+
return reject(new Error(`Invalid response (status=${res.statusCode})`))
|
|
58
|
+
} else {
|
|
59
|
+
const data = Buffer.concat(buffers).toString('utf-8')
|
|
60
|
+
return resolve(data)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const body: Request = { id, query, params }
|
|
66
|
+
const buffer = JSON.stringify(body)
|
|
67
|
+
req.write(buffer)
|
|
68
|
+
req.end()
|
|
69
|
+
}).then((data: string) => {
|
|
70
|
+
let payload: Response
|
|
71
|
+
/* coverage ignore catch */
|
|
72
|
+
try {
|
|
73
|
+
payload = JSON.parse(data)
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw new Error('Unable to parse JSON payload')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Correlate our response to the request */
|
|
79
|
+
assert(payload && (typeof payload === 'object'), 'JSON payload is not an object')
|
|
80
|
+
assert(payload.id === id, 'Invalid/uncorrelated ID in response')
|
|
81
|
+
|
|
82
|
+
/* Analyze the _payload_ status code, is successful, we have a winner! */
|
|
83
|
+
if (payload.statusCode === 200) return payload
|
|
84
|
+
throw new Error(`${payload.error || /* coverage ignore next */ 'Unknown error'} (${payload.statusCode})`)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* ========================================================================== *
|
|
90
|
+
* *
|
|
91
|
+
* ========================================================================== */
|
|
92
|
+
|
|
93
|
+
export class NodeProvider extends WebSocketProvider {
|
|
94
|
+
constructor(url: URL) {
|
|
95
|
+
super()
|
|
96
|
+
|
|
97
|
+
/* Clone the URL and verify it's http/https */
|
|
98
|
+
url = new URL(url.href)
|
|
99
|
+
|
|
100
|
+
/* Extract the secret from the url, we support both "http://secret@host/..."
|
|
101
|
+
* and/or "http://whomever:secret@host/..." formats, discarding username */
|
|
102
|
+
const secret = decodeURIComponent(url.password || url.username)
|
|
103
|
+
assert(secret, 'No connection secret specified in URL')
|
|
104
|
+
url.password = ''
|
|
105
|
+
url.username = ''
|
|
106
|
+
|
|
107
|
+
/* Prepare the URL for http and web sockets */
|
|
108
|
+
const baseHttpUrl = new URL(url.href)
|
|
109
|
+
const baseWsUrl = new URL(url.href)
|
|
110
|
+
baseWsUrl.protocol = `ws${baseWsUrl.protocol.slice(4)}`
|
|
111
|
+
|
|
112
|
+
/* Our methods */
|
|
113
|
+
this._getUniqueRequestId = (): string => randomUUID()
|
|
114
|
+
|
|
115
|
+
this._getWebSocket = async (): Promise<WebSocket> => {
|
|
116
|
+
const token = await getAuthenticationToken(secret)
|
|
117
|
+
const wsUrl = new URL(baseWsUrl.href)
|
|
118
|
+
wsUrl.searchParams.set('auth', token)
|
|
119
|
+
return this._connectWebSocket(new WebSocket(wsUrl))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.query = makeQuery(baseHttpUrl, secret)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ======================================================================== *
|
|
126
|
+
* METHODS FROM CONSTRUCTOR *
|
|
127
|
+
* ======================================================================== */
|
|
128
|
+
|
|
129
|
+
query: (query: string, params: (string | null)[]) => Promise<PGConnectionResult>
|
|
130
|
+
protected _getWebSocket: () => Promise<WebSocket>
|
|
131
|
+
protected _getUniqueRequestId: () => string
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export class NodeClient extends PGClient {
|
|
135
|
+
constructor(url?: URL | string) {
|
|
136
|
+
url = url || process.env.PGURL
|
|
137
|
+
assert(url, 'No URL to connect to (PGURL environment variable missing?)')
|
|
138
|
+
super(new NodeProvider(typeof url === 'string' ? new URL(url) : url))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
registerProvider('http', NodeProvider)
|
|
143
|
+
registerProvider('https', NodeProvider)
|