UNPKG

hrana

Version:

A tiny (1034b) runtime-agnostic Hrana HTTP client

216 lines (215 loc) 4.75 kB
async function pipeline(c, input) { let method = "POST"; let headers = new Headers(); let body = null; if (c.token) { headers.set("Authorization", `Bearer ${c.token}`); } if (input != null) { body = JSON.stringify(input); headers.set("Content-Type", "application/json"); } let r = await request(c, "/v3/pipeline", { method, body, headers }); if (!r.ok) throw r; let reply = (await r.json()).results[0]; if (reply.type === "ok") return reply.response.result; let err = new Error(reply.error.message); err.code = reply.error.code; throw err; } function request(c, path, init) { if (c.token) { init ||= {}; init.headers = new Headers(init.headers); init.headers.set("Authorization", `Bearer ${c.token}`); } return (c.fetch || fetch)(c.url + path, init); } /** * Execute a single statement. * * > [!NOTE] * > Throws an `Error` for pipeline statement errors. * > Throws the `Response` for non-2xx status codes (eg, Authorization issues). */ export function execute(config, query) { return pipeline(config, { baton: null, requests: [{ type: "execute", stmt: query }, { type: "close" }] }); } /** * Execute a batch of statements, which will be executed sequentially. * * If the condition of a step is present and evaluates to false, the statement is not executed. * * > [!NOTE] * > Throws an `Error` for pipeline statement errors. * > Throws the `Response` for non-2xx status codes (eg, Authorization issues). */ export function batch(config, steps) { return pipeline(config, { baton: null, requests: [{ type: "batch", batch: { steps } }, { type: "close" }] }); } /** * Execute a transaction. * * > [!NOTE] * > Throws an `Error` for pipeline statement errors. * > Throws the `Response` for non-2xx status codes (eg, Authorization issues). * * @param config The Hrana configuration. * @param type The transaction mode. * @param stmts The statements to execute. */ export async function transaction(config, type, ...stmts) { let i = 0, len = stmts.length; let steps = [{ stmt: { sql: `BEGIN ${type}` } }]; for (; i < len; i++) { steps.push({ stmt: stmts[i], condition: { type: "and", conds: [{ type: "ok", step: i }, { type: "not", cond: { type: "is_autocommit" } }] } }); } len = steps.length; steps.push({ stmt: { sql: "COMMIT" } }); steps.push({ stmt: { sql: "ROLLBACK" }, condition: { type: "not", cond: { type: "ok", step: len } } }); let r = await batch(config, steps); r.step_results = r.step_results.slice(1, len); r.step_errors = r.step_errors.slice(1, len); return r; } /** * Check if the server supports Hrana V3 over HTTP with JSON encoding. * @see https://github.com/tursodatabase/libsql/blob/main/docs/HRANA_3_SPEC.md#check-support-for-version-3-json */ export function supports(config) { return request(config, "/v3").then((r) => r.ok); } /** * Parse all Rows from the statement results. * * @param result The statement results. * @param options The parsing options. */ export function parse(result, options) { let { cols, rows } = result; let i = 0, len = rows.length; let k = 0, klen = cols.length; let row, tmp; let c, v; let output = Array(len); let mode; let tx = {}; if (typeof options === "string") { mode = options; } else if (options) { tx = options; } for (; i < len; i++) { row = {}; tmp = rows[i]; for (k = 0; k < klen; k++) { c = cols[k]; if (c.name) { v = tmp[k]; row[c.name] = decode(v, mode); if (tx[c.name]) { row[c.name] = tx[c.name](row[c.name]); } } } output[i] = row; } return output; } export function decode(raw, mode) { switch (raw.type) { case "null": return null; case "text": return raw.value; case "float": return +raw.value; case "integer": { if (!mode || mode === "number") { return +raw.value; } if (mode === "bigint") { return BigInt(raw.value); } return raw.value; } } raw; let bin = atob(raw.base64); let i = 0, size = bin.length; let bytes = new Uint8Array(size); for (; i < size; i++) { bytes[i] = bin.charCodeAt(i); } return bytes; } /** * Encode a JS value into its Hrana representation. * * @param v The value to encode. */ export function encode(v) { if (v == null) { return { type: "null" }; } switch (typeof v) { case "string": return { type: "text", value: v }; case "number": return { type: "float", value: v }; case "bigint": return { type: "integer", value: "" + v }; case "boolean": return { type: "integer", value: v ? "1" : "0" }; } v; return { type: "blob", base64: btoa( // @ts-expect-error; Uint8Array is ArrayLike<number> String.fromCharCode.apply(null, new Uint8Array(v)) ) }; }