@libsql/hrana-client
Advanced tools
Comparing version 0.3.9 to 0.3.10
@@ -11,2 +11,3 @@ "use strict"; | ||
#streamState; | ||
#executed; | ||
/** @private */ | ||
@@ -20,2 +21,3 @@ _steps; | ||
this.#streamState = streamState; | ||
this.#executed = false; | ||
this._steps = []; | ||
@@ -30,12 +32,22 @@ this._resultCallbacks = []; | ||
execute() { | ||
if (this.#executed) { | ||
throw new Error("The Batch has already been executed"); | ||
} | ||
this.#executed = true; | ||
return new Promise((doneCallback, errorCallback) => { | ||
const batchState = { | ||
batch: { | ||
const request = { | ||
"type": "batch", | ||
"stream_id": this.#streamState.streamId, | ||
"batch": { | ||
"steps": this._steps, | ||
}, | ||
resultCallbacks: this._resultCallbacks, | ||
doneCallback, | ||
errorCallback, | ||
}; | ||
this.#client._batch(this.#streamState, batchState); | ||
const responseCallback = (response) => { | ||
const result = response["result"]; | ||
for (const callback of this._resultCallbacks) { | ||
callback(result); | ||
} | ||
doneCallback(); | ||
}; | ||
this.#client._sendStreamRequest(this.#streamState, request, { responseCallback, errorCallback }); | ||
}); | ||
@@ -42,0 +54,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Client = void 0; | ||
exports.Client = exports.protocolVersions = void 0; | ||
const isomorphic_ws_1 = require("@libsql/isomorphic-ws"); | ||
@@ -8,8 +8,13 @@ const id_alloc_js_1 = require("./id_alloc.js"); | ||
const result_js_1 = require("./result.js"); | ||
const sql_js_1 = require("./sql.js"); | ||
const stream_js_1 = require("./stream.js"); | ||
exports.protocolVersions = new Map([ | ||
["hrana2", 2], | ||
["hrana1", 1], | ||
]); | ||
/** A client that talks to a SQL server using the Hrana protocol over a WebSocket. */ | ||
class Client { | ||
#socket; | ||
// List of messages that we queue until the socket transitions from the CONNECTING to the OPEN state. | ||
#msgsWaitingToOpen; | ||
// List of callbacks that we queue until the socket transitions from the CONNECTING to the OPEN state. | ||
#openCallbacks; | ||
// Stores the error that caused us to close the client (and the socket). If we are not closed, this is | ||
@@ -20,2 +25,8 @@ // `undefined`. | ||
#recvdHello; | ||
// Protocol version negotiated with the server. It is only available after the socket transitions to the | ||
// OPEN state. | ||
#version; | ||
// Has the `getVersion()` function been called? This is only used to validate that the API is used | ||
// correctly. | ||
#getVersionCalled; | ||
// A map from request id to the responses that we expect to receive from the server. | ||
@@ -27,2 +38,4 @@ #responseMap; | ||
#streamIdAlloc; | ||
// An allocator of SQL text ids. | ||
#sqlIdAlloc; | ||
/** @private */ | ||
@@ -32,8 +45,11 @@ constructor(socket, jwt) { | ||
this.#socket.binaryType = "arraybuffer"; | ||
this.#msgsWaitingToOpen = []; | ||
this.#openCallbacks = []; | ||
this.#closed = undefined; | ||
this.#recvdHello = false; | ||
this.#version = undefined; | ||
this.#getVersionCalled = false; | ||
this.#responseMap = new Map(); | ||
this.#requestIdAlloc = new id_alloc_js_1.IdAlloc(); | ||
this.#streamIdAlloc = new id_alloc_js_1.IdAlloc(); | ||
this.#sqlIdAlloc = new id_alloc_js_1.IdAlloc(); | ||
this.#socket.addEventListener("open", () => this.#onSocketOpen()); | ||
@@ -54,3 +70,5 @@ this.#socket.addEventListener("close", (event) => this.#onSocketClose(event)); | ||
else { | ||
this.#msgsWaitingToOpen.push(msg); | ||
const openCallback = () => this.#sendToSocket(msg); | ||
const errorCallback = (_) => undefined; | ||
this.#openCallbacks.push({ openCallback, errorCallback }); | ||
} | ||
@@ -60,6 +78,16 @@ } | ||
#onSocketOpen() { | ||
for (const msg of this.#msgsWaitingToOpen) { | ||
this.#sendToSocket(msg); | ||
const protocol = this.#socket.protocol; | ||
if (protocol === "") { | ||
this.#version = 1; | ||
} | ||
this.#msgsWaitingToOpen.length = 0; | ||
else { | ||
this.#version = exports.protocolVersions.get(protocol); | ||
if (this.#version === undefined) { | ||
this.#setClosed(new errors_js_1.ProtoError(`Unrecognized WebSocket subprotocol: ${JSON.stringify(protocol)}`)); | ||
} | ||
} | ||
for (const callbacks of this.#openCallbacks) { | ||
callbacks.openCallback(); | ||
} | ||
this.#openCallbacks.length = 0; | ||
} | ||
@@ -69,4 +97,34 @@ #sendToSocket(msg) { | ||
} | ||
/** Get the protocol version negotiated with the server, possibly waiting until the socket is open. */ | ||
getVersion() { | ||
return new Promise((versionCallback, errorCallback) => { | ||
this.#getVersionCalled = true; | ||
if (this.#closed !== undefined) { | ||
errorCallback(this.#closed); | ||
} | ||
else if (this.#version !== undefined) { | ||
versionCallback(this.#version); | ||
} | ||
else { | ||
const openCallback = () => versionCallback(this.#version); | ||
this.#openCallbacks.push({ openCallback, errorCallback }); | ||
} | ||
}); | ||
} | ||
// Make sure that the negotiated version is at least `minVersion`. | ||
/** @private */ | ||
_ensureVersion(minVersion, feature) { | ||
if (this.#version === undefined || !this.#getVersionCalled) { | ||
throw new errors_js_1.ProtocolVersionError(`${feature} is supported only on protocol version ${minVersion} and higher, ` + | ||
"but the version supported by the server is not yet known. Use Client.getVersion() " + | ||
"to wait until the version is available."); | ||
} | ||
else if (this.#version < minVersion) { | ||
throw new errors_js_1.ProtocolVersionError(`${feature} is supported on protocol version ${minVersion} and higher, ` + | ||
`but the server only supports version ${this.#version}`); | ||
} | ||
} | ||
// Send a request to the server and invoke a callback when we get the response. | ||
#sendRequest(request, callbacks) { | ||
/** @private */ | ||
_sendRequest(request, callbacks) { | ||
if (this.#closed !== undefined) { | ||
@@ -100,2 +158,6 @@ callbacks.errorCallback(new errors_js_1.ClosedError("Client is closed", this.#closed)); | ||
this.#closed = error; | ||
for (const callbacks of this.#openCallbacks) { | ||
callbacks.errorCallback(error); | ||
} | ||
this.#openCallbacks.length = 0; | ||
for (const [requestId, responseState] of this.#responseMap.entries()) { | ||
@@ -185,3 +247,3 @@ responseState.errorCallback(error); | ||
}; | ||
this.#sendRequest(request, { responseCallback, errorCallback }); | ||
this._sendRequest(request, { responseCallback, errorCallback }); | ||
return new stream_js_1.Stream(this, streamState); | ||
@@ -203,49 +265,46 @@ } | ||
}; | ||
this.#sendRequest(request, { responseCallback: callback, errorCallback: callback }); | ||
this._sendRequest(request, { responseCallback: callback, errorCallback: callback }); | ||
} | ||
// Execute a statement on a stream and invoke callbacks in `stmtCallbacks` when we get the results (or an | ||
// error). | ||
// Send a stream-specific request to the server and invoke a callback when we get the response. | ||
/** @private */ | ||
_execute(streamState, stmtCallbacks) { | ||
const responseCallback = (response) => { | ||
stmtCallbacks.resultCallback(response["result"]); | ||
}; | ||
const errorCallback = (error) => { | ||
stmtCallbacks.errorCallback(error); | ||
}; | ||
_sendStreamRequest(streamState, request, callbacks) { | ||
if (streamState.closed !== undefined) { | ||
errorCallback(new errors_js_1.ClosedError("Stream was closed", streamState.closed)); | ||
callbacks.errorCallback(new errors_js_1.ClosedError("Stream is closed", streamState.closed)); | ||
return; | ||
} | ||
this._sendRequest(request, callbacks); | ||
} | ||
/** Cache a SQL text on the server. This requires protocol version 2 or higher. */ | ||
storeSql(sql) { | ||
this._ensureVersion(2, "storeSql()"); | ||
const sqlId = this.#sqlIdAlloc.alloc(); | ||
const sqlState = { | ||
sqlId, | ||
closed: undefined, | ||
}; | ||
const responseCallback = () => undefined; | ||
const errorCallback = (e) => this._closeSql(sqlState, e); | ||
const request = { | ||
"type": "execute", | ||
"stream_id": streamState.streamId, | ||
"stmt": stmtCallbacks.stmt, | ||
"type": "store_sql", | ||
"sql_id": sqlId, | ||
"sql": sql, | ||
}; | ||
this.#sendRequest(request, { responseCallback, errorCallback }); | ||
this._sendRequest(request, { responseCallback, errorCallback }); | ||
return new sql_js_1.Sql(this, sqlState); | ||
} | ||
// Execute a batch on a stream and invoke callbacks in `batchCallbacks` when we get the results (or an | ||
// error). | ||
// Make sure that the SQL text is closed. | ||
/** @private */ | ||
_batch(streamState, batchCallbacks) { | ||
const responseCallback = (response) => { | ||
const result = response["result"]; | ||
for (const callback of batchCallbacks.resultCallbacks) { | ||
callback(result); | ||
} | ||
batchCallbacks.doneCallback(); | ||
}; | ||
const errorCallback = (error) => { | ||
batchCallbacks.errorCallback(error); | ||
}; | ||
if (streamState.closed !== undefined) { | ||
errorCallback(new errors_js_1.ClosedError("Stream was closed", streamState.closed)); | ||
_closeSql(sqlState, error) { | ||
if (sqlState.closed !== undefined || this.#closed !== undefined) { | ||
return; | ||
} | ||
sqlState.closed = error; | ||
const callback = () => { | ||
this.#sqlIdAlloc.free(sqlState.sqlId); | ||
}; | ||
const request = { | ||
"type": "batch", | ||
"stream_id": streamState.streamId, | ||
"batch": batchCallbacks.batch, | ||
"type": "close_sql", | ||
"sql_id": sqlState.sqlId, | ||
}; | ||
this.#sendRequest(request, { responseCallback, errorCallback }); | ||
this._sendRequest(request, { responseCallback: callback, errorCallback: callback }); | ||
} | ||
@@ -252,0 +311,0 @@ /** Close the client and the WebSocket. */ |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.LibsqlUrlParseError = exports.WebSocketError = exports.WebSocketUnsupportedError = exports.ClosedError = exports.ResponseError = exports.ProtoError = exports.ClientError = void 0; | ||
exports.ProtocolVersionError = exports.LibsqlUrlParseError = exports.WebSocketError = exports.WebSocketUnsupportedError = exports.ClosedError = exports.ResponseError = exports.ProtoError = exports.ClientError = void 0; | ||
/** Generic error produced by the Hrana client. */ | ||
@@ -73,1 +73,10 @@ class ClientError extends Error { | ||
exports.LibsqlUrlParseError = LibsqlUrlParseError; | ||
/** Error thrown when the protocol version is too low to support a feature. */ | ||
class ProtocolVersionError extends ClientError { | ||
/** @private */ | ||
constructor(message) { | ||
super(message); | ||
this.name = "ProtocolVersionError"; | ||
} | ||
} | ||
exports.ProtocolVersionError = ProtocolVersionError; |
@@ -29,3 +29,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.open = exports.Stream = exports.Stmt = exports.raw = exports.parseLibsqlUrl = exports.BatchCond = exports.BatchStep = exports.Batch = exports.Client = void 0; | ||
exports.open = exports.Stream = exports.Stmt = exports.Sql = exports.raw = exports.parseLibsqlUrl = exports.BatchCond = exports.BatchStep = exports.Batch = exports.Client = void 0; | ||
const isomorphic_ws_1 = require("@libsql/isomorphic-ws"); | ||
@@ -45,2 +45,4 @@ const client_js_1 = require("./client.js"); | ||
exports.raw = __importStar(require("./raw.js")); | ||
var sql_js_1 = require("./sql.js"); | ||
Object.defineProperty(exports, "Sql", { enumerable: true, get: function () { return sql_js_1.Sql; } }); | ||
var stmt_js_1 = require("./stmt.js"); | ||
@@ -55,5 +57,5 @@ Object.defineProperty(exports, "Stmt", { enumerable: true, get: function () { return stmt_js_1.Stmt; } }); | ||
} | ||
const socket = new isomorphic_ws_1.WebSocket(url, ["hrana1"]); | ||
const socket = new isomorphic_ws_1.WebSocket(url, Array.from(client_js_1.protocolVersions.keys())); | ||
return new client_js_1.Client(socket, jwt ?? null); | ||
} | ||
exports.open = open; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.valueFromProto = exports.valueToProto = exports.stmtToProto = exports.errorFromProto = exports.valueResultFromProto = exports.rowResultFromProto = exports.rowsResultFromProto = exports.stmtResultFromProto = void 0; | ||
exports.valueFromProto = exports.valueToProto = exports.stmtToProto = exports.errorFromProto = exports.valueResultFromProto = exports.rowResultFromProto = exports.rowsResultFromProto = exports.stmtResultFromProto = exports.describeResultFromProto = void 0; | ||
var describe_js_1 = require("./describe.js"); | ||
Object.defineProperty(exports, "describeResultFromProto", { enumerable: true, get: function () { return describe_js_1.describeResultFromProto; } }); | ||
var result_js_1 = require("./result.js"); | ||
@@ -5,0 +7,0 @@ Object.defineProperty(exports, "stmtResultFromProto", { enumerable: true, get: function () { return result_js_1.stmtResultFromProto; } }); |
@@ -10,3 +10,4 @@ "use strict"; | ||
lastInsertRowid: result["last_insert_rowid"] ?? undefined, | ||
columnNames: result["cols"].map(col => col.name ?? undefined), | ||
columnNames: result["cols"].map(col => col["name"] ?? undefined), | ||
columnDecltypes: result["cols"].map(col => col["decltype"] ?? undefined), | ||
}; | ||
@@ -13,0 +14,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.stmtToProto = exports.Stmt = void 0; | ||
const sql_js_1 = require("./sql.js"); | ||
const value_js_1 = require("./value.js"); | ||
@@ -8,3 +9,3 @@ /** A statement that can be evaluated by the database. Besides the SQL text, it also contains the positional | ||
class Stmt { | ||
/** The SQL statement string. */ | ||
/** The SQL statement text. */ | ||
sql; | ||
@@ -54,7 +55,7 @@ /** @private */ | ||
function stmtToProto(stmt, wantRows) { | ||
let sql; | ||
let inSql; | ||
let args = []; | ||
let namedArgs = []; | ||
if (stmt instanceof Stmt) { | ||
sql = stmt.sql; | ||
inSql = stmt.sql; | ||
args = stmt._args; | ||
@@ -66,3 +67,3 @@ for (const [name, value] of stmt._namedArgs.entries()) { | ||
else if (Array.isArray(stmt)) { | ||
sql = stmt[0]; | ||
inSql = stmt[0]; | ||
if (Array.isArray(stmt[1])) { | ||
@@ -79,6 +80,13 @@ args = stmt[1].map(value_js_1.valueToProto); | ||
else { | ||
sql = "" + stmt; | ||
inSql = stmt; | ||
} | ||
return { "sql": sql, "args": args, "named_args": namedArgs, "want_rows": wantRows }; | ||
const { sql, sqlId } = (0, sql_js_1.sqlToProto)(inSql); | ||
return { | ||
"sql": sql, | ||
"sql_id": sqlId, | ||
"args": args, | ||
"named_args": namedArgs, | ||
"want_rows": wantRows, | ||
}; | ||
} | ||
exports.stmtToProto = stmtToProto; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Stream = void 0; | ||
const batch_js_1 = require("./batch.js"); | ||
const describe_js_1 = require("./describe.js"); | ||
const errors_js_1 = require("./errors.js"); | ||
const batch_js_1 = require("./batch.js"); | ||
const result_js_1 = require("./result.js"); | ||
const sql_js_1 = require("./sql.js"); | ||
const stmt_js_1 = require("./stmt.js"); | ||
@@ -35,9 +37,12 @@ /** A stream for executing SQL statements (a "database connection"). */ | ||
return new Promise((doneCallback, errorCallback) => { | ||
this.#client._execute(this.#state, { | ||
stmt, | ||
resultCallback(result) { | ||
doneCallback(fromProto(result)); | ||
}, | ||
errorCallback, | ||
}); | ||
const request = { | ||
"type": "execute", | ||
"stream_id": this.#state.streamId, | ||
"stmt": stmt, | ||
}; | ||
const responseCallback = (response) => { | ||
const result = response["result"]; | ||
doneCallback(fromProto(result)); | ||
}; | ||
this.#client._sendStreamRequest(this.#state, request, { responseCallback, errorCallback }); | ||
}); | ||
@@ -49,2 +54,38 @@ } | ||
} | ||
/** Parse and analyze a statement. This requires protocol version 2 or higher. */ | ||
describe(inSql) { | ||
this.#client._ensureVersion(2, "describe()"); | ||
const { sql, sqlId } = (0, sql_js_1.sqlToProto)(inSql); | ||
return new Promise((doneCallback, errorCallback) => { | ||
const request = { | ||
"type": "describe", | ||
"stream_id": this.#state.streamId, | ||
"sql": sql, | ||
"sql_id": sqlId, | ||
}; | ||
const responseCallback = (response) => { | ||
const result = response["result"]; | ||
doneCallback((0, describe_js_1.describeResultFromProto)(result)); | ||
}; | ||
this.#client._sendStreamRequest(this.#state, request, { responseCallback, errorCallback }); | ||
}); | ||
} | ||
/** Execute a sequence of statements separated by semicolons. This requires protocol version 2 or higher. | ||
* */ | ||
sequence(inSql) { | ||
this.#client._ensureVersion(2, "sequence()"); | ||
const { sql, sqlId } = (0, sql_js_1.sqlToProto)(inSql); | ||
return new Promise((doneCallback, errorCallback) => { | ||
const request = { | ||
"type": "sequence", | ||
"stream_id": this.#state.streamId, | ||
"sql": sql, | ||
"sql_id": sqlId, | ||
}; | ||
const responseCallback = (_response) => { | ||
doneCallback(); | ||
}; | ||
this.#client._sendStreamRequest(this.#state, request, { responseCallback, errorCallback }); | ||
}); | ||
} | ||
/** Close the stream. */ | ||
@@ -51,0 +92,0 @@ close() { |
@@ -8,2 +8,3 @@ import { ProtoError } from "./errors.js"; | ||
#streamState; | ||
#executed; | ||
/** @private */ | ||
@@ -17,2 +18,3 @@ _steps; | ||
this.#streamState = streamState; | ||
this.#executed = false; | ||
this._steps = []; | ||
@@ -27,12 +29,22 @@ this._resultCallbacks = []; | ||
execute() { | ||
if (this.#executed) { | ||
throw new Error("The Batch has already been executed"); | ||
} | ||
this.#executed = true; | ||
return new Promise((doneCallback, errorCallback) => { | ||
const batchState = { | ||
batch: { | ||
const request = { | ||
"type": "batch", | ||
"stream_id": this.#streamState.streamId, | ||
"batch": { | ||
"steps": this._steps, | ||
}, | ||
resultCallbacks: this._resultCallbacks, | ||
doneCallback, | ||
errorCallback, | ||
}; | ||
this.#client._batch(this.#streamState, batchState); | ||
const responseCallback = (response) => { | ||
const result = response["result"]; | ||
for (const callback of this._resultCallbacks) { | ||
callback(result); | ||
} | ||
doneCallback(); | ||
}; | ||
this.#client._sendStreamRequest(this.#streamState, request, { responseCallback, errorCallback }); | ||
}); | ||
@@ -39,0 +51,0 @@ } |
/// <reference types="ws" /> | ||
import { WebSocket } from "@libsql/isomorphic-ws"; | ||
import type * as proto from "./proto.js"; | ||
import { Sql } from "./sql.js"; | ||
import { Stream } from "./stream.js"; | ||
export type ProtocolVersion = 1 | 2; | ||
export declare const protocolVersions: Map<string, ProtocolVersion>; | ||
/** A client that talks to a SQL server using the Hrana protocol over a WebSocket. */ | ||
@@ -10,2 +13,8 @@ export declare class Client { | ||
constructor(socket: WebSocket, jwt: string | null); | ||
/** Get the protocol version negotiated with the server, possibly waiting until the socket is open. */ | ||
getVersion(): Promise<ProtocolVersion>; | ||
/** @private */ | ||
_ensureVersion(minVersion: ProtocolVersion, feature: string): void; | ||
/** @private */ | ||
_sendRequest(request: proto.Request, callbacks: ResponseCallbacks): void; | ||
/** Open a {@link Stream}, a stream for executing SQL statements. */ | ||
@@ -16,5 +25,7 @@ openStream(): Stream; | ||
/** @private */ | ||
_execute(streamState: StreamState, stmtCallbacks: StmtCallbacks): void; | ||
_sendStreamRequest(streamState: StreamState, request: proto.Request, callbacks: ResponseCallbacks): void; | ||
/** Cache a SQL text on the server. This requires protocol version 2 or higher. */ | ||
storeSql(sql: string): Sql; | ||
/** @private */ | ||
_batch(streamState: StreamState, batchCallbacks: BatchCallbacks): void; | ||
_closeSql(sqlState: SqlState, error: Error): void; | ||
/** Close the client and the WebSocket. */ | ||
@@ -25,7 +36,10 @@ close(): void; | ||
} | ||
export interface StmtCallbacks { | ||
stmt: proto.Stmt; | ||
resultCallback: (_: proto.StmtResult) => void; | ||
export interface OpenCallbacks { | ||
openCallback: () => void; | ||
errorCallback: (_: Error) => void; | ||
} | ||
export interface ResponseCallbacks { | ||
responseCallback: (_: proto.Response) => void; | ||
errorCallback: (_: Error) => void; | ||
} | ||
export interface StreamState { | ||
@@ -35,7 +49,5 @@ streamId: number; | ||
} | ||
export interface BatchCallbacks { | ||
batch: proto.Batch; | ||
resultCallbacks: Array<(_: proto.BatchResult) => void>; | ||
doneCallback: () => void; | ||
errorCallback: (_: Error) => void; | ||
export interface SqlState { | ||
sqlId: number; | ||
closed: Error | undefined; | ||
} |
import { WebSocket } from "@libsql/isomorphic-ws"; | ||
import { IdAlloc } from "./id_alloc.js"; | ||
import { ClientError, ProtoError, ClosedError, WebSocketError } from "./errors.js"; | ||
import { ClientError, ProtoError, ClosedError, WebSocketError, ProtocolVersionError } from "./errors.js"; | ||
import { errorFromProto } from "./result.js"; | ||
import { Sql } from "./sql.js"; | ||
import { Stream } from "./stream.js"; | ||
export const protocolVersions = new Map([ | ||
["hrana2", 2], | ||
["hrana1", 1], | ||
]); | ||
/** A client that talks to a SQL server using the Hrana protocol over a WebSocket. */ | ||
export class Client { | ||
#socket; | ||
// List of messages that we queue until the socket transitions from the CONNECTING to the OPEN state. | ||
#msgsWaitingToOpen; | ||
// List of callbacks that we queue until the socket transitions from the CONNECTING to the OPEN state. | ||
#openCallbacks; | ||
// Stores the error that caused us to close the client (and the socket). If we are not closed, this is | ||
@@ -16,2 +21,8 @@ // `undefined`. | ||
#recvdHello; | ||
// Protocol version negotiated with the server. It is only available after the socket transitions to the | ||
// OPEN state. | ||
#version; | ||
// Has the `getVersion()` function been called? This is only used to validate that the API is used | ||
// correctly. | ||
#getVersionCalled; | ||
// A map from request id to the responses that we expect to receive from the server. | ||
@@ -23,2 +34,4 @@ #responseMap; | ||
#streamIdAlloc; | ||
// An allocator of SQL text ids. | ||
#sqlIdAlloc; | ||
/** @private */ | ||
@@ -28,8 +41,11 @@ constructor(socket, jwt) { | ||
this.#socket.binaryType = "arraybuffer"; | ||
this.#msgsWaitingToOpen = []; | ||
this.#openCallbacks = []; | ||
this.#closed = undefined; | ||
this.#recvdHello = false; | ||
this.#version = undefined; | ||
this.#getVersionCalled = false; | ||
this.#responseMap = new Map(); | ||
this.#requestIdAlloc = new IdAlloc(); | ||
this.#streamIdAlloc = new IdAlloc(); | ||
this.#sqlIdAlloc = new IdAlloc(); | ||
this.#socket.addEventListener("open", () => this.#onSocketOpen()); | ||
@@ -50,3 +66,5 @@ this.#socket.addEventListener("close", (event) => this.#onSocketClose(event)); | ||
else { | ||
this.#msgsWaitingToOpen.push(msg); | ||
const openCallback = () => this.#sendToSocket(msg); | ||
const errorCallback = (_) => undefined; | ||
this.#openCallbacks.push({ openCallback, errorCallback }); | ||
} | ||
@@ -56,6 +74,16 @@ } | ||
#onSocketOpen() { | ||
for (const msg of this.#msgsWaitingToOpen) { | ||
this.#sendToSocket(msg); | ||
const protocol = this.#socket.protocol; | ||
if (protocol === "") { | ||
this.#version = 1; | ||
} | ||
this.#msgsWaitingToOpen.length = 0; | ||
else { | ||
this.#version = protocolVersions.get(protocol); | ||
if (this.#version === undefined) { | ||
this.#setClosed(new ProtoError(`Unrecognized WebSocket subprotocol: ${JSON.stringify(protocol)}`)); | ||
} | ||
} | ||
for (const callbacks of this.#openCallbacks) { | ||
callbacks.openCallback(); | ||
} | ||
this.#openCallbacks.length = 0; | ||
} | ||
@@ -65,4 +93,34 @@ #sendToSocket(msg) { | ||
} | ||
/** Get the protocol version negotiated with the server, possibly waiting until the socket is open. */ | ||
getVersion() { | ||
return new Promise((versionCallback, errorCallback) => { | ||
this.#getVersionCalled = true; | ||
if (this.#closed !== undefined) { | ||
errorCallback(this.#closed); | ||
} | ||
else if (this.#version !== undefined) { | ||
versionCallback(this.#version); | ||
} | ||
else { | ||
const openCallback = () => versionCallback(this.#version); | ||
this.#openCallbacks.push({ openCallback, errorCallback }); | ||
} | ||
}); | ||
} | ||
// Make sure that the negotiated version is at least `minVersion`. | ||
/** @private */ | ||
_ensureVersion(minVersion, feature) { | ||
if (this.#version === undefined || !this.#getVersionCalled) { | ||
throw new ProtocolVersionError(`${feature} is supported only on protocol version ${minVersion} and higher, ` + | ||
"but the version supported by the server is not yet known. Use Client.getVersion() " + | ||
"to wait until the version is available."); | ||
} | ||
else if (this.#version < minVersion) { | ||
throw new ProtocolVersionError(`${feature} is supported on protocol version ${minVersion} and higher, ` + | ||
`but the server only supports version ${this.#version}`); | ||
} | ||
} | ||
// Send a request to the server and invoke a callback when we get the response. | ||
#sendRequest(request, callbacks) { | ||
/** @private */ | ||
_sendRequest(request, callbacks) { | ||
if (this.#closed !== undefined) { | ||
@@ -96,2 +154,6 @@ callbacks.errorCallback(new ClosedError("Client is closed", this.#closed)); | ||
this.#closed = error; | ||
for (const callbacks of this.#openCallbacks) { | ||
callbacks.errorCallback(error); | ||
} | ||
this.#openCallbacks.length = 0; | ||
for (const [requestId, responseState] of this.#responseMap.entries()) { | ||
@@ -181,3 +243,3 @@ responseState.errorCallback(error); | ||
}; | ||
this.#sendRequest(request, { responseCallback, errorCallback }); | ||
this._sendRequest(request, { responseCallback, errorCallback }); | ||
return new Stream(this, streamState); | ||
@@ -199,49 +261,46 @@ } | ||
}; | ||
this.#sendRequest(request, { responseCallback: callback, errorCallback: callback }); | ||
this._sendRequest(request, { responseCallback: callback, errorCallback: callback }); | ||
} | ||
// Execute a statement on a stream and invoke callbacks in `stmtCallbacks` when we get the results (or an | ||
// error). | ||
// Send a stream-specific request to the server and invoke a callback when we get the response. | ||
/** @private */ | ||
_execute(streamState, stmtCallbacks) { | ||
const responseCallback = (response) => { | ||
stmtCallbacks.resultCallback(response["result"]); | ||
}; | ||
const errorCallback = (error) => { | ||
stmtCallbacks.errorCallback(error); | ||
}; | ||
_sendStreamRequest(streamState, request, callbacks) { | ||
if (streamState.closed !== undefined) { | ||
errorCallback(new ClosedError("Stream was closed", streamState.closed)); | ||
callbacks.errorCallback(new ClosedError("Stream is closed", streamState.closed)); | ||
return; | ||
} | ||
this._sendRequest(request, callbacks); | ||
} | ||
/** Cache a SQL text on the server. This requires protocol version 2 or higher. */ | ||
storeSql(sql) { | ||
this._ensureVersion(2, "storeSql()"); | ||
const sqlId = this.#sqlIdAlloc.alloc(); | ||
const sqlState = { | ||
sqlId, | ||
closed: undefined, | ||
}; | ||
const responseCallback = () => undefined; | ||
const errorCallback = (e) => this._closeSql(sqlState, e); | ||
const request = { | ||
"type": "execute", | ||
"stream_id": streamState.streamId, | ||
"stmt": stmtCallbacks.stmt, | ||
"type": "store_sql", | ||
"sql_id": sqlId, | ||
"sql": sql, | ||
}; | ||
this.#sendRequest(request, { responseCallback, errorCallback }); | ||
this._sendRequest(request, { responseCallback, errorCallback }); | ||
return new Sql(this, sqlState); | ||
} | ||
// Execute a batch on a stream and invoke callbacks in `batchCallbacks` when we get the results (or an | ||
// error). | ||
// Make sure that the SQL text is closed. | ||
/** @private */ | ||
_batch(streamState, batchCallbacks) { | ||
const responseCallback = (response) => { | ||
const result = response["result"]; | ||
for (const callback of batchCallbacks.resultCallbacks) { | ||
callback(result); | ||
} | ||
batchCallbacks.doneCallback(); | ||
}; | ||
const errorCallback = (error) => { | ||
batchCallbacks.errorCallback(error); | ||
}; | ||
if (streamState.closed !== undefined) { | ||
errorCallback(new ClosedError("Stream was closed", streamState.closed)); | ||
_closeSql(sqlState, error) { | ||
if (sqlState.closed !== undefined || this.#closed !== undefined) { | ||
return; | ||
} | ||
sqlState.closed = error; | ||
const callback = () => { | ||
this.#sqlIdAlloc.free(sqlState.sqlId); | ||
}; | ||
const request = { | ||
"type": "batch", | ||
"stream_id": streamState.streamId, | ||
"batch": batchCallbacks.batch, | ||
"type": "close_sql", | ||
"sql_id": sqlState.sqlId, | ||
}; | ||
this.#sendRequest(request, { responseCallback, errorCallback }); | ||
this._sendRequest(request, { responseCallback: callback, errorCallback: callback }); | ||
} | ||
@@ -248,0 +307,0 @@ /** Close the client and the WebSocket. */ |
@@ -40,1 +40,6 @@ import type * as proto from "./proto.js"; | ||
} | ||
/** Error thrown when the protocol version is too low to support a feature. */ | ||
export declare class ProtocolVersionError extends ClientError { | ||
/** @private */ | ||
constructor(message: string); | ||
} |
@@ -63,1 +63,9 @@ /** Generic error produced by the Hrana client. */ | ||
} | ||
/** Error thrown when the protocol version is too low to support a feature. */ | ||
export class ProtocolVersionError extends ClientError { | ||
/** @private */ | ||
constructor(message) { | ||
super(message); | ||
this.name = "ProtocolVersionError"; | ||
} | ||
} |
/// <reference types="node" /> | ||
import { Client } from "./client.js"; | ||
import type * as proto from "./proto.js"; | ||
export type { ProtocolVersion } from "./client.js"; | ||
export { Client } from "./client.js"; | ||
@@ -12,2 +13,4 @@ export * from "./errors.js"; | ||
export type { StmtResult, RowsResult, RowResult, ValueResult, Row } from "./result.js"; | ||
export type { InSql } from "./sql.js"; | ||
export { Sql } from "./sql.js"; | ||
export type { InStmt, InStmtArgs } from "./stmt.js"; | ||
@@ -14,0 +17,0 @@ export { Stmt } from "./stmt.js"; |
import { WebSocket } from "@libsql/isomorphic-ws"; | ||
import { Client } from "./client.js"; | ||
import { Client, protocolVersions } from "./client.js"; | ||
import { WebSocketUnsupportedError } from "./errors.js"; | ||
@@ -10,2 +10,3 @@ export { Client } from "./client.js"; | ||
export * as raw from "./raw.js"; | ||
export { Sql } from "./sql.js"; | ||
export { Stmt } from "./stmt.js"; | ||
@@ -18,4 +19,4 @@ export { Stream } from "./stream.js"; | ||
} | ||
const socket = new WebSocket(url, ["hrana1"]); | ||
const socket = new WebSocket(url, Array.from(protocolVersions.keys())); | ||
return new Client(socket, jwt ?? null); | ||
} |
@@ -34,4 +34,4 @@ export type int32 = number; | ||
}; | ||
export type Request = OpenStreamReq | CloseStreamReq | ExecuteReq | BatchReq; | ||
export type Response = OpenStreamResp | CloseStreamResp | ExecuteResp | BatchResp; | ||
export type Request = OpenStreamReq | CloseStreamReq | ExecuteReq | BatchReq | DescribeReq | SequenceReq | StoreSqlReq | CloseSqlReq; | ||
export type Response = OpenStreamResp | CloseStreamResp | ExecuteResp | BatchResp | DescribeResp | SequenceResp | StoreSqlResp | CloseSqlResp; | ||
export type OpenStreamReq = { | ||
@@ -61,3 +61,4 @@ "type": "open_stream"; | ||
export type Stmt = { | ||
"sql": string; | ||
"sql"?: string | undefined; | ||
"sql_id"?: int32 | undefined; | ||
"args"?: Array<Value>; | ||
@@ -79,2 +80,3 @@ "named_args"?: Array<NamedArg>; | ||
"name": string | null; | ||
"decltype"?: string | null; | ||
}; | ||
@@ -117,2 +119,49 @@ export type BatchReq = { | ||
}; | ||
export type DescribeReq = { | ||
"type": "describe"; | ||
"stream_id": int32; | ||
"sql"?: string | undefined; | ||
"sql_id"?: int32 | undefined; | ||
}; | ||
export type DescribeResp = { | ||
"type": "describe"; | ||
"result": DescribeResult; | ||
}; | ||
export type DescribeResult = { | ||
"params": Array<DescribeParam>; | ||
"cols": Array<DescribeCol>; | ||
"is_explain": boolean; | ||
"is_readonly": boolean; | ||
}; | ||
export type DescribeParam = { | ||
"name": string | null; | ||
}; | ||
export type DescribeCol = { | ||
"name": string; | ||
"decltype": string | null; | ||
}; | ||
export type SequenceReq = { | ||
"type": "sequence"; | ||
"stream_id": int32; | ||
"sql"?: string | null; | ||
"sql_id"?: int32 | null; | ||
}; | ||
export type SequenceResp = { | ||
"type": "sequence"; | ||
}; | ||
export type StoreSqlReq = { | ||
"type": "store_sql"; | ||
"sql_id": int32; | ||
"sql": string; | ||
}; | ||
export type StoreSqlResp = { | ||
"type": "store_sql"; | ||
}; | ||
export type CloseSqlReq = { | ||
"type": "close_sql"; | ||
"sql_id": int32; | ||
}; | ||
export type CloseSqlResp = { | ||
"type": "close_sql"; | ||
}; | ||
export type Value = { | ||
@@ -119,0 +168,0 @@ "type": "null"; |
@@ -0,3 +1,4 @@ | ||
export { describeResultFromProto } from "./describe.js"; | ||
export { stmtResultFromProto, rowsResultFromProto, rowResultFromProto, valueResultFromProto, errorFromProto, } from "./result.js"; | ||
export { stmtToProto } from "./stmt.js"; | ||
export { valueToProto, valueFromProto } from "./value.js"; |
@@ -0,3 +1,4 @@ | ||
export { describeResultFromProto } from "./describe.js"; | ||
export { stmtResultFromProto, rowsResultFromProto, rowResultFromProto, valueResultFromProto, errorFromProto, } from "./result.js"; | ||
export { stmtToProto } from "./stmt.js"; | ||
export { valueToProto, valueFromProto } from "./value.js"; |
@@ -14,2 +14,4 @@ import { ResponseError } from "./errors.js"; | ||
columnNames: Array<string | undefined>; | ||
/** Declared types of columns in the result. */ | ||
columnDecltypes: Array<string | undefined>; | ||
} | ||
@@ -16,0 +18,0 @@ /** An array of rows returned by a database statement. */ |
@@ -7,3 +7,4 @@ import { ResponseError } from "./errors.js"; | ||
lastInsertRowid: result["last_insert_rowid"] ?? undefined, | ||
columnNames: result["cols"].map(col => col.name ?? undefined), | ||
columnNames: result["cols"].map(col => col["name"] ?? undefined), | ||
columnDecltypes: result["cols"].map(col => col["decltype"] ?? undefined), | ||
}; | ||
@@ -10,0 +11,0 @@ } |
import type * as proto from "./proto.js"; | ||
import type { InSql } from "./sql.js"; | ||
import type { InValue } from "./value.js"; | ||
/** A statement that you can send to the database. Statements are represented by the {@link Stmt} class, but | ||
* as a shorthand, you can specify an SQL string without arguments, or a tuple with the SQL string and | ||
* positional or named arguments. | ||
* as a shorthand, you can specify an SQL text without arguments, or a tuple with the SQL text and positional | ||
* or named arguments. | ||
*/ | ||
export type InStmt = Stmt | string | [string, InStmtArgs]; | ||
export type InStmt = Stmt | InSql | [InSql, InStmtArgs]; | ||
/** Arguments for a statement. Either an array that is bound to parameters by position, or an object with | ||
@@ -14,4 +15,4 @@ * values that are bound to parameters by name. */ | ||
export declare class Stmt { | ||
/** The SQL statement string. */ | ||
readonly sql: string; | ||
/** The SQL statement text. */ | ||
sql: InSql; | ||
/** @private */ | ||
@@ -22,3 +23,3 @@ _args: Array<proto.Value>; | ||
/** Initialize the statement with given SQL text. */ | ||
constructor(sql: string); | ||
constructor(sql: InSql); | ||
/** Binds positional parameters from the given `values`. All previous positional bindings are cleared. */ | ||
@@ -25,0 +26,0 @@ bindIndexes(values: Iterable<InValue>): this; |
@@ -0,1 +1,2 @@ | ||
import { sqlToProto } from "./sql.js"; | ||
import { valueToProto, protoNull } from "./value.js"; | ||
@@ -5,3 +6,3 @@ /** A statement that can be evaluated by the database. Besides the SQL text, it also contains the positional | ||
export class Stmt { | ||
/** The SQL statement string. */ | ||
/** The SQL statement text. */ | ||
sql; | ||
@@ -50,7 +51,7 @@ /** @private */ | ||
export function stmtToProto(stmt, wantRows) { | ||
let sql; | ||
let inSql; | ||
let args = []; | ||
let namedArgs = []; | ||
if (stmt instanceof Stmt) { | ||
sql = stmt.sql; | ||
inSql = stmt.sql; | ||
args = stmt._args; | ||
@@ -62,3 +63,3 @@ for (const [name, value] of stmt._namedArgs.entries()) { | ||
else if (Array.isArray(stmt)) { | ||
sql = stmt[0]; | ||
inSql = stmt[0]; | ||
if (Array.isArray(stmt[1])) { | ||
@@ -75,5 +76,12 @@ args = stmt[1].map(valueToProto); | ||
else { | ||
sql = "" + stmt; | ||
inSql = stmt; | ||
} | ||
return { "sql": sql, "args": args, "named_args": namedArgs, "want_rows": wantRows }; | ||
const { sql, sqlId } = sqlToProto(inSql); | ||
return { | ||
"sql": sql, | ||
"sql_id": sqlId, | ||
"args": args, | ||
"named_args": namedArgs, | ||
"want_rows": wantRows, | ||
}; | ||
} |
import type { Client, StreamState } from "./client.js"; | ||
import type { DescribeResult } from "./describe.js"; | ||
import { Batch } from "./batch.js"; | ||
import type { RowsResult, RowResult, ValueResult, StmtResult } from "./result.js"; | ||
import type { InSql } from "./sql.js"; | ||
import type { InStmt } from "./stmt.js"; | ||
@@ -20,2 +22,7 @@ /** A stream for executing SQL statements (a "database connection"). */ | ||
batch(): Batch; | ||
/** Parse and analyze a statement. This requires protocol version 2 or higher. */ | ||
describe(inSql: InSql): Promise<DescribeResult>; | ||
/** Execute a sequence of statements separated by semicolons. This requires protocol version 2 or higher. | ||
* */ | ||
sequence(inSql: InSql): Promise<void>; | ||
/** Close the stream. */ | ||
@@ -22,0 +29,0 @@ close(): void; |
@@ -0,4 +1,6 @@ | ||
import { Batch } from "./batch.js"; | ||
import { describeResultFromProto } from "./describe.js"; | ||
import { ClientError } from "./errors.js"; | ||
import { Batch } from "./batch.js"; | ||
import { stmtResultFromProto, rowsResultFromProto, rowResultFromProto, valueResultFromProto, } from "./result.js"; | ||
import { sqlToProto } from "./sql.js"; | ||
import { stmtToProto } from "./stmt.js"; | ||
@@ -32,9 +34,12 @@ /** A stream for executing SQL statements (a "database connection"). */ | ||
return new Promise((doneCallback, errorCallback) => { | ||
this.#client._execute(this.#state, { | ||
stmt, | ||
resultCallback(result) { | ||
doneCallback(fromProto(result)); | ||
}, | ||
errorCallback, | ||
}); | ||
const request = { | ||
"type": "execute", | ||
"stream_id": this.#state.streamId, | ||
"stmt": stmt, | ||
}; | ||
const responseCallback = (response) => { | ||
const result = response["result"]; | ||
doneCallback(fromProto(result)); | ||
}; | ||
this.#client._sendStreamRequest(this.#state, request, { responseCallback, errorCallback }); | ||
}); | ||
@@ -46,2 +51,38 @@ } | ||
} | ||
/** Parse and analyze a statement. This requires protocol version 2 or higher. */ | ||
describe(inSql) { | ||
this.#client._ensureVersion(2, "describe()"); | ||
const { sql, sqlId } = sqlToProto(inSql); | ||
return new Promise((doneCallback, errorCallback) => { | ||
const request = { | ||
"type": "describe", | ||
"stream_id": this.#state.streamId, | ||
"sql": sql, | ||
"sql_id": sqlId, | ||
}; | ||
const responseCallback = (response) => { | ||
const result = response["result"]; | ||
doneCallback(describeResultFromProto(result)); | ||
}; | ||
this.#client._sendStreamRequest(this.#state, request, { responseCallback, errorCallback }); | ||
}); | ||
} | ||
/** Execute a sequence of statements separated by semicolons. This requires protocol version 2 or higher. | ||
* */ | ||
sequence(inSql) { | ||
this.#client._ensureVersion(2, "sequence()"); | ||
const { sql, sqlId } = sqlToProto(inSql); | ||
return new Promise((doneCallback, errorCallback) => { | ||
const request = { | ||
"type": "sequence", | ||
"stream_id": this.#state.streamId, | ||
"sql": sql, | ||
"sql_id": sqlId, | ||
}; | ||
const responseCallback = (_response) => { | ||
doneCallback(); | ||
}; | ||
this.#client._sendStreamRequest(this.#state, request, { responseCallback, errorCallback }); | ||
}); | ||
} | ||
/** Close the stream. */ | ||
@@ -48,0 +89,0 @@ close() { |
{ | ||
"name": "@libsql/hrana-client", | ||
"version": "0.3.9", | ||
"version": "0.3.10", | ||
"keywords": [ | ||
@@ -5,0 +5,0 @@ "hrana", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
102000
46
2588