planetscale
Version:
A simple client for connecting to PlanetScale.
128 lines (127 loc) • 5.94 kB
JavaScript
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _PlanetScale_instances, _PlanetScale_baseURL, _PlanetScale_headers, _PlanetScale_connection, _PlanetScale_createConnection;
import { Buffer } from "node:buffer";
import { request } from "node:https";
import { webcrypto } from "node:crypto";
import { customAlphabet } from "nanoid";
import { cryptoProvider, Pkcs10CertificateRequestGenerator, } from "@peculiar/x509";
import { createConnection } from "mysql2";
const crypto = webcrypto;
cryptoProvider.set(crypto);
const nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 12);
const nodeBtoa = (str) => Buffer.from(str, "binary").toString("base64");
const base64encode = typeof btoa !== "undefined" ? btoa : nodeBtoa;
export class PlanetScale {
constructor({ branch = "main", tokenName, token, org, db }, connectionOptions = {}) {
_PlanetScale_instances.add(this);
_PlanetScale_baseURL.set(this, void 0);
_PlanetScale_headers.set(this, void 0);
_PlanetScale_connection.set(this, null);
this.branch = branch;
this.tokenName = tokenName;
this.token = token;
this.org = org;
this.db = db;
__classPrivateFieldSet(this, _PlanetScale_baseURL, "https://api.planetscale.com", "f");
__classPrivateFieldSet(this, _PlanetScale_headers, { Authorization: `${tokenName}:${token}` }, "f");
this.connectionOptions = connectionOptions;
}
async getConnection() {
if (!__classPrivateFieldGet(this, _PlanetScale_connection, "f")) {
__classPrivateFieldSet(this, _PlanetScale_connection, await __classPrivateFieldGet(this, _PlanetScale_instances, "m", _PlanetScale_createConnection).call(this), "f");
}
return __classPrivateFieldGet(this, _PlanetScale_connection, "f");
}
}
_PlanetScale_baseURL = new WeakMap(), _PlanetScale_headers = new WeakMap(), _PlanetScale_connection = new WeakMap(), _PlanetScale_instances = new WeakSet(), _PlanetScale_createConnection = async function _PlanetScale_createConnection() {
const alg = {
name: "ECDSA",
namedCurve: "P-256",
hash: "SHA-256",
};
const keyPair = await crypto.subtle.generateKey(alg, true, [
"sign",
"verify",
]);
if (!keyPair.privateKey) {
throw new Error("Failed to generate keypair");
}
const csr = await Pkcs10CertificateRequestGenerator.create({
keys: keyPair,
signingAlgorithm: alg,
});
const fullURL = new URL(`${__classPrivateFieldGet(this, _PlanetScale_baseURL, "f")}/v1/organizations/${this.org}/databases/${this.db}/branches/${this.branch}/certificates`);
const displayName = `pscale-${nanoid()}`;
const { response, body } = await postJSON(fullURL, __classPrivateFieldGet(this, _PlanetScale_headers, "f"), {
csr: csr.toString(),
display_name: displayName,
});
const status = response.statusCode || 0;
if (status < 200 || status > 299) {
throw new Error(`HTTP ${status}`);
}
const addr = body.database_branch.access_host_url;
const exportPrivateKey = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
const exportedAsString = String.fromCharCode.apply(null, Array.from(new Uint8Array(exportPrivateKey)));
const exportedAsBase64 = base64encode(exportedAsString);
const pemExported = `-----BEGIN PRIVATE KEY-----\n${exportedAsBase64}\n-----END PRIVATE KEY-----`;
return createConnection({
...this.connectionOptions,
user: body.id,
host: addr,
database: this.db,
ssl: {
key: pemExported,
cert: body.certificate,
rejectUnauthorized: true,
},
});
};
function postJSON(url, headers, body) {
const json = JSON.stringify(body);
const options = {
hostname: url.host,
port: 443,
path: url.pathname,
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Content-Length": json.length,
"User-Agent": "planetscale/0.2.0",
...headers,
},
};
return new Promise((resolve, reject) => {
const req = request(options, (response) => {
let body = "";
response.on("data", (chunk) => (body += chunk));
response.on("end", () => {
try {
const parsedBody = JSON.parse(body);
resolve({ response, body: parsedBody });
}
catch (e) {
reject(e);
}
});
});
req.on("error", reject);
req.write(json);
req.end();
});
}
export default async function connect(config, connectionOptions = {}) {
const planetscale = new PlanetScale(config, connectionOptions);
return planetscale.getConnection();
}