UNPKG

planetscale

Version:

A simple client for connecting to PlanetScale.

128 lines (127 loc) 5.94 kB
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(); }