UNPKG

@libsql/core

Version:

libSQL driver for TypeScript and JavaScript

442 lines (441 loc) 19 kB
/** Configuration object for {@link createClient}. */ export interface Config { /** The database URL. * * The client supports `libsql:`, `http:`/`https:`, `ws:`/`wss:` and `file:` URL. For more infomation, * please refer to the project README: * * https://github.com/libsql/libsql-client-ts#supported-urls */ url: string; /** Authentication token for the database. */ authToken?: string; /** Encryption key for the database. */ encryptionKey?: string; /** URL of a remote server to synchronize database with. */ syncUrl?: string; /** Sync interval in seconds. */ syncInterval?: number; /** Enables or disables TLS for `libsql:` URLs. * * By default, `libsql:` URLs use TLS. You can set this option to `false` to disable TLS. */ tls?: boolean; /** How to convert SQLite integers to JavaScript values: * * - `"number"` (default): returns SQLite integers as JavaScript `number`-s (double precision floats). * `number` cannot precisely represent integers larger than 2^53-1 in absolute value, so attempting to read * larger integers will throw a `RangeError`. * - `"bigint"`: returns SQLite integers as JavaScript `bigint`-s (arbitrary precision integers). Bigints can * precisely represent all SQLite integers. * - `"string"`: returns SQLite integers as strings. */ intMode?: IntMode; /** Custom `fetch` function to use for the HTTP client. * * By default, the HTTP client uses `fetch` from the `@libsql/isomorphic-fetch` package, but you can pass * your own function here. The argument to this function will be `Request` from * `@libsql/isomorphic-fetch`, and it must return a promise that resolves to an object that is compatible * with the Web `Response`. */ fetch?: Function; /** Concurrency limit. * * By default, the client performs up to 20 concurrent requests. You can set this option to a higher * number to increase the concurrency limit or set it to 0 to disable concurrency limits completely. */ concurrency?: number | undefined; } /** Representation of integers from database as JavaScript values. See {@link Config.intMode}. */ export type IntMode = "number" | "bigint" | "string"; /** Client object for a remote or local database. * * After you are done with the client, you **should** close it by calling {@link close}. */ export interface Client { /** Execute a single SQL statement. * * Every statement executed with this method is executed in its own logical database connection. If you * want to execute a group of statements in a transaction, use the {@link batch} or the {@link * transaction} methods. * * ```javascript * // execute a statement without arguments * const rs = await client.execute("SELECT * FROM books"); * * // execute a statement with positional arguments * const rs = await client.execute({ * sql: "SELECT * FROM books WHERE author = ?", * args: ["Jane Austen"], * }); * * // execute a statement with named arguments * const rs = await client.execute({ * sql: "SELECT * FROM books WHERE published_at > $year", * args: {year: 1719}, * }); * ``` */ execute(stmt: InStatement): Promise<ResultSet>; /** Execute a batch of SQL statements in a transaction. * * The batch is executed in its own logical database connection and the statements are wrapped in a * transaction. This ensures that the batch is applied atomically: either all or no changes are applied. * * The `mode` parameter selects the transaction mode for the batch; please see {@link TransactionMode} for * details. The default transaction mode is `"deferred"`. * * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is * rolled back and the returned promise is rejected. * * This method provides non-interactive transactions. If you need interactive transactions, please use the * {@link transaction} method. * * ```javascript * const rss = await client.batch([ * // batch statement without arguments * "DELETE FROM books WHERE name LIKE '%Crusoe'", * * // batch statement with positional arguments * { * sql: "INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)", * args: ["First Impressions", "Jane Austen", 1813], * }, * * // batch statement with named arguments * { * sql: "UPDATE books SET name = $new WHERE name = $old", * args: {old: "First Impressions", new: "Pride and Prejudice"}, * }, * ], "write"); * ``` */ batch(stmts: Array<InStatement>, mode?: TransactionMode): Promise<Array<ResultSet>>; /** Execute a batch of SQL statements in a transaction with PRAGMA foreign_keys=off; before and PRAGMA foreign_keys=on; after. * * The batch is executed in its own logical database connection and the statements are wrapped in a * transaction. This ensures that the batch is applied atomically: either all or no changes are applied. * * The transaction mode is `"deferred"`. * * If any of the statements in the batch fails with an error, the batch is aborted, the transaction is * rolled back and the returned promise is rejected. * * ```javascript * const rss = await client.migrate([ * // statement without arguments * "CREATE TABLE test (a INT)", * * // statement with positional arguments * { * sql: "INSERT INTO books (name, author, published_at) VALUES (?, ?, ?)", * args: ["First Impressions", "Jane Austen", 1813], * }, * * // statement with named arguments * { * sql: "UPDATE books SET name = $new WHERE name = $old", * args: {old: "First Impressions", new: "Pride and Prejudice"}, * }, * ]); * ``` */ migrate(stmts: Array<InStatement>): Promise<Array<ResultSet>>; /** Start an interactive transaction. * * Interactive transactions allow you to interleave execution of SQL statements with your application * logic. They can be used if the {@link batch} method is too restrictive, but please note that * interactive transactions have higher latency. * * The `mode` parameter selects the transaction mode for the interactive transaction; please see {@link * TransactionMode} for details. The default transaction mode is `"deferred"`. * * You **must** make sure that the returned {@link Transaction} object is closed, by calling {@link * Transaction.close}, {@link Transaction.commit} or {@link Transaction.rollback}. The best practice is * to call {@link Transaction.close} in a `finally` block, as follows: * * ```javascript * const transaction = client.transaction("write"); * try { * // do some operations with the transaction here * await transaction.execute({ * sql: "INSERT INTO books (name, author) VALUES (?, ?)", * args: ["First Impressions", "Jane Austen"], * }); * await transaction.execute({ * sql: "UPDATE books SET name = ? WHERE name = ?", * args: ["Pride and Prejudice", "First Impressions"], * }); * * // if all went well, commit the transaction * await transaction.commit(); * } finally { * // make sure to close the transaction, even if an exception was thrown * transaction.close(); * } * ``` */ transaction(mode?: TransactionMode): Promise<Transaction>; /** Start an interactive transaction in `"write"` mode. * * Please see {@link transaction} for details. * * @deprecated Please specify the `mode` explicitly. The default `"write"` will be removed in the next * major release. */ transaction(): Promise<Transaction>; /** Execute a sequence of SQL statements separated by semicolons. * * The statements are executed sequentially on a new logical database connection. If a statement fails, * further statements are not executed and this method throws an error. All results from the statements * are ignored. * * We do not wrap the statements in a transaction, but the SQL can contain explicit transaction-control * statements such as `BEGIN` and `COMMIT`. * * This method is intended to be used with existing SQL scripts, such as migrations or small database * dumps. If you want to execute a sequence of statements programmatically, please use {@link batch} * instead. * * ```javascript * await client.executeMultiple(` * CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT NOT NULL, author_id INTEGER NOT NULL); * CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT NOT NULL); * `); * ``` */ executeMultiple(sql: string): Promise<void>; sync(): Promise<Replicated>; /** Close the client and release resources. * * This method closes the client (aborting any operations that are currently in progress) and releases any * resources associated with the client (such as a WebSocket connection). */ close(): void; /** Is the client closed? * * This is set to `true` after a call to {@link close} or if the client encounters an unrecoverable * error. */ closed: boolean; /** Which protocol does the client use? * * - `"http"` if the client connects over HTTP * - `"ws"` if the client connects over WebSockets * - `"file"` if the client works with a local file */ protocol: string; } /** Interactive transaction. * * A transaction groups multiple SQL statements together, so that they are applied atomically: either all * changes are applied, or none are. Other SQL statements on the database (including statements executed on * the same {@link Client} object outside of this transaction) will not see any changes from the transaction * until the transaction is committed by calling {@link commit}. You can also use {@link rollback} to abort * the transaction and roll back the changes. * * You **must** make sure that the {@link Transaction} object is closed, by calling {@link close}, {@link * commit} or {@link rollback}. The best practice is to call {@link close} in a `finally` block, as follows: * * ```javascript * const transaction = client.transaction("write"); * try { * // do some operations with the transaction here * await transaction.execute({ * sql: "INSERT INTO books (name, author) VALUES (?, ?)", * args: ["First Impressions", "Jane Austen"], * }); * await transaction.execute({ * sql: "UPDATE books SET name = ? WHERE name = ?", * args: ["Pride and Prejudice", "First Impressions"], * }); * * // if all went well, commit the transaction * await transaction.commit(); * } finally { * // make sure to close the transaction, even if an exception was thrown * transaction.close(); * } * ``` */ export interface Transaction { /** Execute an SQL statement in this transaction. * * If the statement makes any changes to the database, these changes won't be visible to statements * outside of this transaction until you call {@link rollback}. * * ```javascript * await transaction.execute({ * sql: "INSERT INTO books (name, author) VALUES (?, ?)", * args: ["First Impressions", "Jane Austen"], * }); * ``` */ execute(stmt: InStatement): Promise<ResultSet>; /** Execute a batch of SQL statements in this transaction. * * If any of the statements in the batch fails with an error, further statements are not executed and the * returned promise is rejected with an error, but the transaction is not rolled back. */ batch(stmts: Array<InStatement>): Promise<Array<ResultSet>>; /** Execute a sequence of SQL statements separated by semicolons. * * The statements are executed sequentially in the transaction. If a statement fails, further statements * are not executed and this method throws an error, but the transaction won't be rolled back. All results * from the statements are ignored. * * This method is intended to be used with existing SQL scripts, such as migrations or small database * dumps. If you want to execute statements programmatically, please use {@link batch} instead. */ executeMultiple(sql: string): Promise<void>; /** Roll back any changes from this transaction. * * This method closes the transaction and undoes any changes done by the previous SQL statements on this * transaction. You cannot call this method after calling {@link commit}, though. */ rollback(): Promise<void>; /** Commit changes from this transaction to the database. * * This method closes the transaction and applies all changes done by the previous SQL statement on this * transaction. Once the returned promise is resolved successfully, the database guarantees that the * changes were applied. */ commit(): Promise<void>; /** Close the transaction. * * This method closes the transaction and releases any resources associated with the transaction. If the * transaction is already closed (perhaps by a previous call to {@link commit} or {@link rollback}), then * this method does nothing. * * If the transaction wasn't already committed by calling {@link commit}, the transaction is rolled * back. */ close(): void; /** Is the transaction closed? * * This is set to `true` after a call to {@link close}, {@link commit} or {@link rollback}, or if we * encounter an unrecoverable error. */ closed: boolean; } /** Transaction mode. * * The client supports multiple modes for transactions: * * - `"write"` is a read-write transaction, started with `BEGIN IMMEDIATE`. This transaction mode supports * both read statements (`SELECT`) and write statements (`INSERT`, `UPDATE`, `CREATE TABLE`, etc). The libSQL * server cannot process multiple write transactions concurrently, so if there is another write transaction * already started, our transaction will wait in a queue before it can begin. * * - `"read"` is a read-only transaction, started with `BEGIN TRANSACTION READONLY` (a libSQL extension). This * transaction mode supports only reads (`SELECT`) and will not accept write statements. The libSQL server can * handle multiple read transactions at the same time, so we don't need to wait for other transactions to * complete. A read-only transaction can also be executed on a local replica, so it provides lower latency. * * - `"deferred"` is a transaction started with `BEGIN DEFERRED`, which starts as a read transaction, but the * first write statement will try to upgrade it to a write transaction. However, this upgrade may fail if * there already is a write transaction executing on the server, so you should be ready to handle these * failures. * * If your transaction includes only read statements, `"read"` is always preferred over `"deferred"` or * `"write"`, because `"read"` transactions can be executed more efficiently and don't block other * transactions. * * If your transaction includes both read and write statements, you should be using the `"write"` mode most of * the time. Use the `"deferred"` mode only if you prefer to fail the write transaction instead of waiting for * the previous write transactions to complete. */ export type TransactionMode = "write" | "read" | "deferred"; /** Result of executing an SQL statement. * * ```javascript * const rs = await client.execute("SELECT name, title FROM books"); * console.log(`Found ${rs.rows.length} books`); * for (const row in rs.rows) { * console.log(`Book ${row[0]} by ${row[1]}`); * } * * const rs = await client.execute("DELETE FROM books WHERE author = 'Jane Austen'"); * console.log(`Deleted ${rs.rowsAffected} books`); * ``` */ export interface ResultSet { /** Names of columns. * * Names of columns can be defined using the `AS` keyword in SQL: * * ```sql * SELECT author AS author, COUNT(*) AS count FROM books GROUP BY author * ``` */ columns: Array<string>; /** Types of columns. * * The types are currently shown for types declared in a SQL table. For * column types of function calls, for example, an empty string is * returned. */ columnTypes: Array<string>; /** Rows produced by the statement. */ rows: Array<Row>; /** Number of rows that were affected by an UPDATE, INSERT or DELETE operation. * * This value is not specified for other SQL statements. */ rowsAffected: number; /** ROWID of the last inserted row. * * This value is not specified if the SQL statement was not an INSERT or if the table was not a ROWID * table. */ lastInsertRowid: bigint | undefined; /** Converts the result set to JSON. * * This is used automatically by `JSON.stringify()`, but you can also call it explicitly. */ toJSON(): any; } /** Row returned from an SQL statement. * * The row object can be used as an `Array` or as an object: * * ```javascript * const rs = await client.execute("SELECT name, title FROM books"); * for (const row in rs.rows) { * // Get the value from column `name` * console.log(row.name); * // Get the value from second column (`title`) * console.log(row[1]); * } * ``` */ export interface Row { /** Number of columns in this row. * * All rows in one {@link ResultSet} have the same number and names of columns. */ length: number; /** Columns can be accessed like an array by numeric indexes. */ [index: number]: Value; /** Columns can be accessed like an object by column names. */ [name: string]: Value; } export type Replicated = { frame_no: number; frames_synced: number; } | undefined; export type Value = null | string | number | bigint | ArrayBuffer; export type InValue = Value | boolean | Uint8Array | Date; export type InStatement = { sql: string; args: InArgs; } | string; export type InArgs = Array<InValue> | Record<string, InValue>; /** Error thrown by the client. */ export declare class LibsqlError extends Error { /** Machine-readable error code. */ code: string; /** Raw numeric error code */ rawCode?: number; constructor(message: string, code: string, rawCode?: number, cause?: Error); }