Rindle

API index and search · Build metadata

Source snapshot

packages/sql-client/src/value.ts

Source revision 05d0bf2c2e56 · build details
Source revision: 05d0bf2c2e56.
TypeScript input SHA-256: aabe6cfcc4172b870d5e272142958e9ea8d8784c2aa23133156e5a7ee633318e
Generated 2026-09-04T23:58:25.590Z with TypeScript 6.0.3. Public TypeScript checks and declaration emit passed. Package runtime tests are separate.
1import { protocolError, valueUnsupported } from "./errors.ts";2import type { IntMode, SqlArgs, SqlValue, Statement, WireArgs, WireSqlValue, WireStatement } from "./types.ts";34const I64_MIN = -(1n << 63n);5const I64_MAX = (1n << 63n) - 1n;6const DECIMAL_I64 = /^(?:0|-[1-9][0-9]*|[1-9][0-9]*)$/;78function binaryKind(value: unknown): string | null {9  if (typeof ArrayBuffer !== "undefined") {10    if (value instanceof ArrayBuffer) return "ArrayBuffer";11    if (ArrayBuffer.isView(value)) return value?.constructor?.name ?? "binary view";12  }13  if (typeof Blob !== "undefined" && value instanceof Blob) return "Blob";14  return null;15}1617function assertI64(value: bigint, source: string): void {18  if (value < I64_MIN || value > I64_MAX) {19    throw valueUnsupported(`${source} is outside SQLite's signed 64-bit integer range`);20  }21}2223/** Encode one public SDK value into the frozen v1 tagged JSON representation. */24export function encodeSqlValue(value: SqlValue): WireSqlValue {25  if (value === null || typeof value === "string") return value;26  if (typeof value === "boolean") return { $rindle: "i64", value: value ? "1" : "0" };27  if (typeof value === "bigint") {28    assertI64(value, `integer ${value.toString()}`);29    return { $rindle: "i64", value: value.toString() };30  }31  if (typeof value === "number") {32    if (Number.isNaN(value)) throw valueUnsupported("NaN is not a supported SQLite value");33    if (value === Infinity) return { $rindle: "float", value: "Infinity" };34    if (value === -Infinity) return { $rindle: "float", value: "-Infinity" };35    // JavaScript has one number type, but SQLite distinguishes INTEGER and REAL storage classes.36    // Safe integral numbers carry the exact i64 tag; non-integral and larger numeric values remain37    // REALs. Callers that need a larger integer must use bigint so intent and precision are explicit.38    if (Number.isSafeInteger(value)) {39      return { $rindle: "i64", value: BigInt(value).toString() };40    }41    return value;42  }4344  const kind = binaryKind(value);45  if (kind !== null) throw valueUnsupported(`${kind} bind values are not supported in Rindle SQL v1`);46  throw valueUnsupported(`unsupported bind value of type ${typeof value}`);47}4849function parseWireI64(decimal: unknown): bigint {50  if (typeof decimal !== "string" || !DECIMAL_I64.test(decimal)) {51    throw protocolError("server returned a malformed tagged i64 value");52  }53  let value: bigint;54  try {55    value = BigInt(decimal);56  } catch (cause) {57    throw protocolError("server returned a malformed tagged i64 value", cause);58  }59  if (value < I64_MIN || value > I64_MAX) {60    throw protocolError("server returned an out-of-range tagged i64 value");61  }62  return value;63}6465/** Decode one frozen v1 tagged JSON value using the client's integer policy. */66export function decodeSqlValue(value: unknown, intMode: IntMode = "bigint"): SqlValue {67  if (value === null || typeof value === "string") return value;68  if (typeof value === "number") {69    if (!Number.isFinite(value)) throw protocolError("server returned a non-finite untagged JSON number");70    return value;71  }72  if (typeof value !== "object" || Array.isArray(value)) {73    throw protocolError("server returned a malformed SQL value");74  }7576  const tagged = value as { $rindle?: unknown; value?: unknown };77  if (tagged.$rindle === "float") {78    if (tagged.value === "Infinity") return Infinity;79    if (tagged.value === "-Infinity") return -Infinity;80    throw protocolError("server returned a malformed tagged float value");81  }82  if (tagged.$rindle !== "i64") throw protocolError("server returned an unknown tagged SQL value");8384  const integer = parseWireI64(tagged.value);85  if (intMode === "bigint") return integer;86  if (intMode === "string") return integer.toString();87  const number = Number(integer);88  if (!Number.isSafeInteger(number)) {89    throw valueUnsupported(`integer ${integer.toString()} is outside Number's safe integer range`);90  }91  return number;92}9394export function encodeArgs(args: SqlArgs): WireArgs {95  if (Array.isArray(args)) return args.map((value) => encodeSqlValue(value));96  if (args === null || typeof args !== "object") {97    throw valueUnsupported("statement args must be a positional array or named object");98  }99  return Object.fromEntries(Object.entries(args).map(([name, value]) => [name, encodeSqlValue(value)]));100}101102export function encodeStatement(statement: Statement | string): WireStatement {103  const normalized: Statement = typeof statement === "string" ? { sql: statement } : statement;104  if (normalized === null || typeof normalized !== "object" || typeof normalized.sql !== "string") {105    throw new TypeError("statement must be a SQL string or an object with a string sql field");106  }107  if (normalized.sql.length === 0) throw new TypeError("statement sql must not be empty");108  const wire: WireStatement = { sql: normalized.sql };109  if (normalized.args !== undefined) wire.args = encodeArgs(normalized.args);110  if (normalized.wantRows !== undefined) wire.want_rows = normalized.wantRows;111  return wire;112}113