Initial commit: New MoreminiMore website with fresh design

This commit is contained in:
MoreminiMore
2026-04-22 01:59:05 +07:00
commit 76409638cc
14010 changed files with 2052041 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
/// <reference types="node" />
import type { ProtocolVersion, ProtocolEncoding } from "../client.js";
import { Client } from "../client.js";
import { HttpStream } from "./stream.js";
export type Endpoint = {
versionPath: string;
pipelinePath: string;
cursorPath: string | undefined;
version: ProtocolVersion;
encoding: ProtocolEncoding;
};
export declare const checkEndpoints: Array<Endpoint>;
/** A client for the Hrana protocol over HTTP. */
export declare class HttpClient extends Client {
#private;
/** @private */
_endpointPromise: Promise<Endpoint>;
/** @private */
_endpoint: Endpoint | undefined;
/** @private */
constructor(url: URL, jwt: string | undefined, customFetch: unknown | undefined, protocolVersion?: ProtocolVersion);
/** Get the protocol version supported by the server. */
getVersion(): Promise<ProtocolVersion>;
/** @private */
_ensureVersion(minVersion: ProtocolVersion, feature: string): void;
/** Open a {@link HttpStream}, a stream for executing SQL statements. */
openStream(): HttpStream;
/** @private */
_streamClosed(stream: HttpStream): void;
/** Close the client and all its streams. */
close(): void;
/** True if the client is closed. */
get closed(): boolean;
}

View File

@@ -0,0 +1,124 @@
import { fetch, Request } from "@libsql/isomorphic-fetch";
import { Client } from "../client.js";
import { ClientError, ClosedError, ProtocolVersionError } from "../errors.js";
import { HttpStream } from "./stream.js";
export const checkEndpoints = [
{
versionPath: "v3-protobuf",
pipelinePath: "v3-protobuf/pipeline",
cursorPath: "v3-protobuf/cursor",
version: 3,
encoding: "protobuf",
},
/*
{
versionPath: "v3",
pipelinePath: "v3/pipeline",
cursorPath: "v3/cursor",
version: 3,
encoding: "json",
},
*/
];
const fallbackEndpoint = {
versionPath: "v2",
pipelinePath: "v2/pipeline",
cursorPath: undefined,
version: 2,
encoding: "json",
};
/** A client for the Hrana protocol over HTTP. */
export class HttpClient extends Client {
#url;
#jwt;
#fetch;
#closed;
#streams;
/** @private */
_endpointPromise;
/** @private */
_endpoint;
/** @private */
constructor(url, jwt, customFetch, protocolVersion = 2) {
super();
this.#url = url;
this.#jwt = jwt;
this.#fetch = customFetch ?? fetch;
this.#closed = undefined;
this.#streams = new Set();
if (protocolVersion == 3) {
this._endpointPromise = findEndpoint(this.#fetch, this.#url);
this._endpointPromise.then((endpoint) => this._endpoint = endpoint, (error) => this.#setClosed(error));
}
else {
this._endpointPromise = Promise.resolve(fallbackEndpoint);
this._endpointPromise.then((endpoint) => this._endpoint = endpoint, (error) => this.#setClosed(error));
}
}
/** Get the protocol version supported by the server. */
async getVersion() {
if (this._endpoint !== undefined) {
return this._endpoint.version;
}
return (await this._endpointPromise).version;
}
// Make sure that the negotiated version is at least `minVersion`.
/** @private */
_ensureVersion(minVersion, feature) {
if (minVersion <= fallbackEndpoint.version) {
return;
}
else if (this._endpoint === undefined) {
throw new ProtocolVersionError(`${feature} is supported only on protocol version ${minVersion} and higher, ` +
"but the version supported by the HTTP server is not yet known. " +
"Use Client.getVersion() to wait until the version is available.");
}
else if (this._endpoint.version < minVersion) {
throw new ProtocolVersionError(`${feature} is supported only on protocol version ${minVersion} and higher, ` +
`but the HTTP server only supports version ${this._endpoint.version}.`);
}
}
/** Open a {@link HttpStream}, a stream for executing SQL statements. */
openStream() {
if (this.#closed !== undefined) {
throw new ClosedError("Client is closed", this.#closed);
}
const stream = new HttpStream(this, this.#url, this.#jwt, this.#fetch);
this.#streams.add(stream);
return stream;
}
/** @private */
_streamClosed(stream) {
this.#streams.delete(stream);
}
/** Close the client and all its streams. */
close() {
this.#setClosed(new ClientError("Client was manually closed"));
}
/** True if the client is closed. */
get closed() {
return this.#closed !== undefined;
}
#setClosed(error) {
if (this.#closed !== undefined) {
return;
}
this.#closed = error;
for (const stream of Array.from(this.#streams)) {
stream._setClosed(new ClosedError("Client was closed", error));
}
}
}
async function findEndpoint(customFetch, clientUrl) {
const fetch = customFetch;
for (const endpoint of checkEndpoints) {
const url = new URL(endpoint.versionPath, clientUrl);
const request = new Request(url.toString(), { method: "GET" });
const response = await fetch(request);
await response.arrayBuffer();
if (response.ok) {
return endpoint;
}
}
return fallbackEndpoint;
}

View File

@@ -0,0 +1,19 @@
import type { Response } from "@libsql/isomorphic-fetch";
import type { ProtocolEncoding } from "../client.js";
import { Cursor } from "../cursor.js";
import type * as proto from "./proto.js";
import type { HttpStream } from "./stream.js";
export declare class HttpCursor extends Cursor {
#private;
/** @private */
constructor(stream: HttpStream, encoding: ProtocolEncoding);
open(response: Response): Promise<proto.CursorRespBody>;
/** Fetch the next entry from the cursor. */
next(): Promise<proto.CursorEntry | undefined>;
/** Close the cursor. */
close(): void;
/** @private */
_setClosed(error: Error): void;
/** True if the cursor is closed. */
get closed(): boolean;
}

View File

@@ -0,0 +1,135 @@
import { ByteQueue } from "../byte_queue.js";
import { Cursor } from "../cursor.js";
import * as jsond from "../encoding/json/decode.js";
import * as protobufd from "../encoding/protobuf/decode.js";
import { ClientError, ClosedError, ProtoError, InternalError } from "../errors.js";
import { impossible } from "../util.js";
import { CursorRespBody as json_CursorRespBody } from "./json_decode.js";
import { CursorRespBody as protobuf_CursorRespBody } from "./protobuf_decode.js";
import { CursorEntry as json_CursorEntry } from "../shared/json_decode.js";
import { CursorEntry as protobuf_CursorEntry } from "../shared/protobuf_decode.js";
export class HttpCursor extends Cursor {
#stream;
#encoding;
#reader;
#queue;
#closed;
#done;
/** @private */
constructor(stream, encoding) {
super();
this.#stream = stream;
this.#encoding = encoding;
this.#reader = undefined;
this.#queue = new ByteQueue(16 * 1024);
this.#closed = undefined;
this.#done = false;
}
async open(response) {
if (response.body === null) {
throw new ProtoError("No response body for cursor request");
}
this.#reader = response.body.getReader();
const respBody = await this.#nextItem(json_CursorRespBody, protobuf_CursorRespBody);
if (respBody === undefined) {
throw new ProtoError("Empty response to cursor request");
}
return respBody;
}
/** Fetch the next entry from the cursor. */
next() {
return this.#nextItem(json_CursorEntry, protobuf_CursorEntry);
}
/** Close the cursor. */
close() {
this._setClosed(new ClientError("Cursor was manually closed"));
}
/** @private */
_setClosed(error) {
if (this.#closed !== undefined) {
return;
}
this.#closed = error;
this.#stream._cursorClosed(this);
if (this.#reader !== undefined) {
this.#reader.cancel();
}
}
/** True if the cursor is closed. */
get closed() {
return this.#closed !== undefined;
}
async #nextItem(jsonFun, protobufDef) {
for (;;) {
if (this.#done) {
return undefined;
}
else if (this.#closed !== undefined) {
throw new ClosedError("Cursor is closed", this.#closed);
}
if (this.#encoding === "json") {
const jsonData = this.#parseItemJson();
if (jsonData !== undefined) {
const jsonText = new TextDecoder().decode(jsonData);
const jsonValue = JSON.parse(jsonText);
return jsond.readJsonObject(jsonValue, jsonFun);
}
}
else if (this.#encoding === "protobuf") {
const protobufData = this.#parseItemProtobuf();
if (protobufData !== undefined) {
return protobufd.readProtobufMessage(protobufData, protobufDef);
}
}
else {
throw impossible(this.#encoding, "Impossible encoding");
}
if (this.#reader === undefined) {
throw new InternalError("Attempted to read from HTTP cursor before it was opened");
}
const { value, done } = await this.#reader.read();
if (done && this.#queue.length === 0) {
this.#done = true;
}
else if (done) {
throw new ProtoError("Unexpected end of cursor stream");
}
else {
this.#queue.push(value);
}
}
}
#parseItemJson() {
const data = this.#queue.data();
const newlineByte = 10;
const newlinePos = data.indexOf(newlineByte);
if (newlinePos < 0) {
return undefined;
}
const jsonData = data.slice(0, newlinePos);
this.#queue.shift(newlinePos + 1);
return jsonData;
}
#parseItemProtobuf() {
const data = this.#queue.data();
let varintValue = 0;
let varintLength = 0;
for (;;) {
if (varintLength >= data.byteLength) {
return undefined;
}
const byte = data[varintLength];
varintValue |= (byte & 0x7f) << (7 * varintLength);
varintLength += 1;
if (!(byte & 0x80)) {
break;
}
}
if (data.byteLength < varintLength + varintValue) {
return undefined;
}
const protobufData = data.slice(varintLength, varintLength + varintValue);
this.#queue.shift(varintLength + varintValue);
return protobufData;
}
}

View File

@@ -0,0 +1,4 @@
import * as d from "../encoding/json/decode.js";
import * as proto from "./proto.js";
export declare function PipelineRespBody(obj: d.Obj): proto.PipelineRespBody;
export declare function CursorRespBody(obj: d.Obj): proto.CursorRespBody;

View File

@@ -0,0 +1,62 @@
import { ProtoError } from "../errors.js";
import * as d from "../encoding/json/decode.js";
import { Error, StmtResult, BatchResult, DescribeResult } from "../shared/json_decode.js";
export function PipelineRespBody(obj) {
const baton = d.stringOpt(obj["baton"]);
const baseUrl = d.stringOpt(obj["base_url"]);
const results = d.arrayObjectsMap(obj["results"], StreamResult);
return { baton, baseUrl, results };
}
function StreamResult(obj) {
const type = d.string(obj["type"]);
if (type === "ok") {
const response = StreamResponse(d.object(obj["response"]));
return { type: "ok", response };
}
else if (type === "error") {
const error = Error(d.object(obj["error"]));
return { type: "error", error };
}
else {
throw new ProtoError("Unexpected type of StreamResult");
}
}
function StreamResponse(obj) {
const type = d.string(obj["type"]);
if (type === "close") {
return { type: "close" };
}
else if (type === "execute") {
const result = StmtResult(d.object(obj["result"]));
return { type: "execute", result };
}
else if (type === "batch") {
const result = BatchResult(d.object(obj["result"]));
return { type: "batch", result };
}
else if (type === "sequence") {
return { type: "sequence" };
}
else if (type === "describe") {
const result = DescribeResult(d.object(obj["result"]));
return { type: "describe", result };
}
else if (type === "store_sql") {
return { type: "store_sql" };
}
else if (type === "close_sql") {
return { type: "close_sql" };
}
else if (type === "get_autocommit") {
const isAutocommit = d.boolean(obj["is_autocommit"]);
return { type: "get_autocommit", isAutocommit };
}
else {
throw new ProtoError("Unexpected type of StreamResponse");
}
}
export function CursorRespBody(obj) {
const baton = d.stringOpt(obj["baton"]);
const baseUrl = d.stringOpt(obj["base_url"]);
return { baton, baseUrl };
}

View File

@@ -0,0 +1,4 @@
import * as e from "../encoding/json/encode.js";
import * as proto from "./proto.js";
export declare function PipelineReqBody(w: e.ObjectWriter, msg: proto.PipelineReqBody): void;
export declare function CursorReqBody(w: e.ObjectWriter, msg: proto.CursorReqBody): void;

View File

@@ -0,0 +1,55 @@
import { Stmt, Batch } from "../shared/json_encode.js";
import { impossible } from "../util.js";
export function PipelineReqBody(w, msg) {
if (msg.baton !== undefined) {
w.string("baton", msg.baton);
}
w.arrayObjects("requests", msg.requests, StreamRequest);
}
function StreamRequest(w, msg) {
w.stringRaw("type", msg.type);
if (msg.type === "close") {
// do nothing
}
else if (msg.type === "execute") {
w.object("stmt", msg.stmt, Stmt);
}
else if (msg.type === "batch") {
w.object("batch", msg.batch, Batch);
}
else if (msg.type === "sequence") {
if (msg.sql !== undefined) {
w.string("sql", msg.sql);
}
if (msg.sqlId !== undefined) {
w.number("sql_id", msg.sqlId);
}
}
else if (msg.type === "describe") {
if (msg.sql !== undefined) {
w.string("sql", msg.sql);
}
if (msg.sqlId !== undefined) {
w.number("sql_id", msg.sqlId);
}
}
else if (msg.type === "store_sql") {
w.number("sql_id", msg.sqlId);
w.string("sql", msg.sql);
}
else if (msg.type === "close_sql") {
w.number("sql_id", msg.sqlId);
}
else if (msg.type === "get_autocommit") {
// do nothing
}
else {
throw impossible(msg, "Impossible type of StreamRequest");
}
}
export function CursorReqBody(w, msg) {
if (msg.baton !== undefined) {
w.string("baton", msg.baton);
}
w.object("batch", msg.batch, Batch);
}

View File

@@ -0,0 +1,95 @@
export * from "../shared/proto.js";
import { int32, Error, Stmt, StmtResult, Batch, BatchResult, DescribeResult } from "../shared/proto.js";
export type PipelineReqBody = {
baton: string | undefined;
requests: Array<StreamRequest>;
};
export type PipelineRespBody = {
baton: string | undefined;
baseUrl: string | undefined;
results: Array<StreamResult>;
};
export type StreamResult = {
type: "none";
} | StreamResultOk | StreamResultError;
export type StreamResultOk = {
type: "ok";
response: StreamResponse;
};
export type StreamResultError = {
type: "error";
error: Error;
};
export type CursorReqBody = {
baton: string | undefined;
batch: Batch;
};
export type CursorRespBody = {
baton: string | undefined;
baseUrl: string | undefined;
};
export type StreamRequest = CloseStreamReq | ExecuteStreamReq | BatchStreamReq | SequenceStreamReq | DescribeStreamReq | StoreSqlStreamReq | CloseSqlStreamReq | GetAutocommitStreamReq;
export type StreamResponse = {
type: "none";
} | CloseStreamResp | ExecuteStreamResp | BatchStreamResp | SequenceStreamResp | DescribeStreamResp | StoreSqlStreamResp | CloseSqlStreamResp | GetAutocommitStreamResp;
export type CloseStreamReq = {
type: "close";
};
export type CloseStreamResp = {
type: "close";
};
export type ExecuteStreamReq = {
type: "execute";
stmt: Stmt;
};
export type ExecuteStreamResp = {
type: "execute";
result: StmtResult;
};
export type BatchStreamReq = {
type: "batch";
batch: Batch;
};
export type BatchStreamResp = {
type: "batch";
result: BatchResult;
};
export type SequenceStreamReq = {
type: "sequence";
sql: string | undefined;
sqlId: int32 | undefined;
};
export type SequenceStreamResp = {
type: "sequence";
};
export type DescribeStreamReq = {
type: "describe";
sql: string | undefined;
sqlId: int32 | undefined;
};
export type DescribeStreamResp = {
type: "describe";
result: DescribeResult;
};
export type StoreSqlStreamReq = {
type: "store_sql";
sqlId: int32;
sql: string;
};
export type StoreSqlStreamResp = {
type: "store_sql";
};
export type CloseSqlStreamReq = {
type: "close_sql";
sqlId: int32;
};
export type CloseSqlStreamResp = {
type: "close_sql";
};
export type GetAutocommitStreamReq = {
type: "get_autocommit";
};
export type GetAutocommitStreamResp = {
type: "get_autocommit";
isAutocommit: boolean;
};

View File

@@ -0,0 +1,2 @@
// Types for the structures specific to Hrana over HTTP.
export * from "../shared/proto.js";

View File

@@ -0,0 +1,4 @@
import * as d from "../encoding/protobuf/decode.js";
import * as proto from "./proto.js";
export declare const PipelineRespBody: d.MessageDef<proto.PipelineRespBody>;
export declare const CursorRespBody: d.MessageDef<proto.CursorRespBody>;

View File

@@ -0,0 +1,44 @@
import { Error, StmtResult, BatchResult, DescribeResult } from "../shared/protobuf_decode.js";
export const PipelineRespBody = {
default() { return { baton: undefined, baseUrl: undefined, results: [] }; },
1(r, msg) { msg.baton = r.string(); },
2(r, msg) { msg.baseUrl = r.string(); },
3(r, msg) { msg.results.push(r.message(StreamResult)); },
};
const StreamResult = {
default() { return { type: "none" }; },
1(r) { return { type: "ok", response: r.message(StreamResponse) }; },
2(r) { return { type: "error", error: r.message(Error) }; },
};
const StreamResponse = {
default() { return { type: "none" }; },
1(r) { return { type: "close" }; },
2(r) { return r.message(ExecuteStreamResp); },
3(r) { return r.message(BatchStreamResp); },
4(r) { return { type: "sequence" }; },
5(r) { return r.message(DescribeStreamResp); },
6(r) { return { type: "store_sql" }; },
7(r) { return { type: "close_sql" }; },
8(r) { return r.message(GetAutocommitStreamResp); },
};
const ExecuteStreamResp = {
default() { return { type: "execute", result: StmtResult.default() }; },
1(r, msg) { msg.result = r.message(StmtResult); },
};
const BatchStreamResp = {
default() { return { type: "batch", result: BatchResult.default() }; },
1(r, msg) { msg.result = r.message(BatchResult); },
};
const DescribeStreamResp = {
default() { return { type: "describe", result: DescribeResult.default() }; },
1(r, msg) { msg.result = r.message(DescribeResult); },
};
const GetAutocommitStreamResp = {
default() { return { type: "get_autocommit", isAutocommit: false }; },
1(r, msg) { msg.isAutocommit = r.bool(); },
};
export const CursorRespBody = {
default() { return { baton: undefined, baseUrl: undefined }; },
1(r, msg) { msg.baton = r.string(); },
2(r, msg) { msg.baseUrl = r.string(); },
};

View File

@@ -0,0 +1,4 @@
import * as e from "../encoding/protobuf/encode.js";
import * as proto from "./proto.js";
export declare function PipelineReqBody(w: e.MessageWriter, msg: proto.PipelineReqBody): void;
export declare function CursorReqBody(w: e.MessageWriter, msg: proto.CursorReqBody): void;

View File

@@ -0,0 +1,78 @@
import { Stmt, Batch } from "../shared/protobuf_encode.js";
import { impossible } from "../util.js";
export function PipelineReqBody(w, msg) {
if (msg.baton !== undefined) {
w.string(1, msg.baton);
}
for (const req of msg.requests) {
w.message(2, req, StreamRequest);
}
}
function StreamRequest(w, msg) {
if (msg.type === "close") {
w.message(1, msg, CloseStreamReq);
}
else if (msg.type === "execute") {
w.message(2, msg, ExecuteStreamReq);
}
else if (msg.type === "batch") {
w.message(3, msg, BatchStreamReq);
}
else if (msg.type === "sequence") {
w.message(4, msg, SequenceStreamReq);
}
else if (msg.type === "describe") {
w.message(5, msg, DescribeStreamReq);
}
else if (msg.type === "store_sql") {
w.message(6, msg, StoreSqlStreamReq);
}
else if (msg.type === "close_sql") {
w.message(7, msg, CloseSqlStreamReq);
}
else if (msg.type === "get_autocommit") {
w.message(8, msg, GetAutocommitStreamReq);
}
else {
throw impossible(msg, "Impossible type of StreamRequest");
}
}
function CloseStreamReq(_w, _msg) {
}
function ExecuteStreamReq(w, msg) {
w.message(1, msg.stmt, Stmt);
}
function BatchStreamReq(w, msg) {
w.message(1, msg.batch, Batch);
}
function SequenceStreamReq(w, msg) {
if (msg.sql !== undefined) {
w.string(1, msg.sql);
}
if (msg.sqlId !== undefined) {
w.int32(2, msg.sqlId);
}
}
function DescribeStreamReq(w, msg) {
if (msg.sql !== undefined) {
w.string(1, msg.sql);
}
if (msg.sqlId !== undefined) {
w.int32(2, msg.sqlId);
}
}
function StoreSqlStreamReq(w, msg) {
w.int32(1, msg.sqlId);
w.string(2, msg.sql);
}
function CloseSqlStreamReq(w, msg) {
w.int32(1, msg.sqlId);
}
function GetAutocommitStreamReq(_w, _msg) {
}
export function CursorReqBody(w, msg) {
if (msg.baton !== undefined) {
w.string(1, msg.baton);
}
w.message(2, msg.batch, Batch);
}

View File

@@ -0,0 +1,45 @@
/// <reference types="node" />
import type { fetch } from "@libsql/isomorphic-fetch";
import type { SqlOwner, ProtoSql } from "../sql.js";
import { Sql } from "../sql.js";
import { Stream } from "../stream.js";
import type { HttpClient } from "./client.js";
import { HttpCursor } from "./cursor.js";
import type * as proto from "./proto.js";
export declare class HttpStream extends Stream implements SqlOwner {
#private;
/** @private */
constructor(client: HttpClient, baseUrl: URL, jwt: string | undefined, customFetch: typeof fetch);
/** Get the {@link HttpClient} object that this stream belongs to. */
client(): HttpClient;
/** @private */
_sqlOwner(): SqlOwner;
/** Cache a SQL text on the server. */
storeSql(sql: string): Sql;
/** @private */
_closeSql(sqlId: number): void;
/** @private */
_execute(stmt: proto.Stmt): Promise<proto.StmtResult>;
/** @private */
_batch(batch: proto.Batch): Promise<proto.BatchResult>;
/** @private */
_describe(protoSql: ProtoSql): Promise<proto.DescribeResult>;
/** @private */
_sequence(protoSql: ProtoSql): Promise<void>;
/** Check whether the SQL connection underlying this stream is in autocommit state (i.e., outside of an
* explicit transaction). This requires protocol version 3 or higher.
*/
getAutocommit(): Promise<boolean>;
/** @private */
_openCursor(batch: proto.Batch): Promise<HttpCursor>;
/** @private */
_cursorClosed(cursor: HttpCursor): void;
/** Immediately close the stream. */
close(): void;
/** Gracefully close the stream. */
closeGracefully(): void;
/** True if the stream is closed. */
get closed(): boolean;
/** @private */
_setClosed(error: Error): void;
}

View File

@@ -0,0 +1,363 @@
import { Request, Headers } from "@libsql/isomorphic-fetch";
import { ClientError, HttpServerError, ProtocolVersionError, ProtoError, ClosedError, InternalError, } from "../errors.js";
import { readJsonObject, writeJsonObject, readProtobufMessage, writeProtobufMessage, } from "../encoding/index.js";
import { IdAlloc } from "../id_alloc.js";
import { Queue } from "../queue.js";
import { queueMicrotask } from "../queue_microtask.js";
import { errorFromProto } from "../result.js";
import { Sql } from "../sql.js";
import { Stream } from "../stream.js";
import { impossible } from "../util.js";
import { HttpCursor } from "./cursor.js";
import { PipelineReqBody as json_PipelineReqBody } from "./json_encode.js";
import { PipelineReqBody as protobuf_PipelineReqBody } from "./protobuf_encode.js";
import { CursorReqBody as json_CursorReqBody } from "./json_encode.js";
import { CursorReqBody as protobuf_CursorReqBody } from "./protobuf_encode.js";
import { PipelineRespBody as json_PipelineRespBody } from "./json_decode.js";
import { PipelineRespBody as protobuf_PipelineRespBody } from "./protobuf_decode.js";
export class HttpStream extends Stream {
#client;
#baseUrl;
#jwt;
#fetch;
#baton;
#queue;
#flushing;
#cursor;
#closing;
#closeQueued;
#closed;
#sqlIdAlloc;
/** @private */
constructor(client, baseUrl, jwt, customFetch) {
super(client.intMode);
this.#client = client;
this.#baseUrl = baseUrl.toString();
this.#jwt = jwt;
this.#fetch = customFetch;
this.#baton = undefined;
this.#queue = new Queue();
this.#flushing = false;
this.#closing = false;
this.#closeQueued = false;
this.#closed = undefined;
this.#sqlIdAlloc = new IdAlloc();
}
/** Get the {@link HttpClient} object that this stream belongs to. */
client() {
return this.#client;
}
/** @private */
_sqlOwner() {
return this;
}
/** Cache a SQL text on the server. */
storeSql(sql) {
const sqlId = this.#sqlIdAlloc.alloc();
this.#sendStreamRequest({ type: "store_sql", sqlId, sql }).then(() => undefined, (error) => this._setClosed(error));
return new Sql(this, sqlId);
}
/** @private */
_closeSql(sqlId) {
if (this.#closed !== undefined) {
return;
}
this.#sendStreamRequest({ type: "close_sql", sqlId }).then(() => this.#sqlIdAlloc.free(sqlId), (error) => this._setClosed(error));
}
/** @private */
_execute(stmt) {
return this.#sendStreamRequest({ type: "execute", stmt }).then((response) => {
return response.result;
});
}
/** @private */
_batch(batch) {
return this.#sendStreamRequest({ type: "batch", batch }).then((response) => {
return response.result;
});
}
/** @private */
_describe(protoSql) {
return this.#sendStreamRequest({
type: "describe",
sql: protoSql.sql,
sqlId: protoSql.sqlId
}).then((response) => {
return response.result;
});
}
/** @private */
_sequence(protoSql) {
return this.#sendStreamRequest({
type: "sequence",
sql: protoSql.sql,
sqlId: protoSql.sqlId,
}).then((_response) => {
return undefined;
});
}
/** Check whether the SQL connection underlying this stream is in autocommit state (i.e., outside of an
* explicit transaction). This requires protocol version 3 or higher.
*/
getAutocommit() {
this.#client._ensureVersion(3, "getAutocommit()");
return this.#sendStreamRequest({
type: "get_autocommit",
}).then((response) => {
return response.isAutocommit;
});
}
#sendStreamRequest(request) {
return new Promise((responseCallback, errorCallback) => {
this.#pushToQueue({ type: "pipeline", request, responseCallback, errorCallback });
});
}
/** @private */
_openCursor(batch) {
return new Promise((cursorCallback, errorCallback) => {
this.#pushToQueue({ type: "cursor", batch, cursorCallback, errorCallback });
});
}
/** @private */
_cursorClosed(cursor) {
if (cursor !== this.#cursor) {
throw new InternalError("Cursor was closed, but it was not associated with the stream");
}
this.#cursor = undefined;
queueMicrotask(() => this.#flushQueue());
}
/** Immediately close the stream. */
close() {
this._setClosed(new ClientError("Stream was manually closed"));
}
/** Gracefully close the stream. */
closeGracefully() {
this.#closing = true;
queueMicrotask(() => this.#flushQueue());
}
/** True if the stream is closed. */
get closed() {
return this.#closed !== undefined || this.#closing;
}
/** @private */
_setClosed(error) {
if (this.#closed !== undefined) {
return;
}
this.#closed = error;
if (this.#cursor !== undefined) {
this.#cursor._setClosed(error);
}
this.#client._streamClosed(this);
for (;;) {
const entry = this.#queue.shift();
if (entry !== undefined) {
entry.errorCallback(error);
}
else {
break;
}
}
if ((this.#baton !== undefined || this.#flushing) && !this.#closeQueued) {
this.#queue.push({
type: "pipeline",
request: { type: "close" },
responseCallback: () => undefined,
errorCallback: () => undefined,
});
this.#closeQueued = true;
queueMicrotask(() => this.#flushQueue());
}
}
#pushToQueue(entry) {
if (this.#closed !== undefined) {
throw new ClosedError("Stream is closed", this.#closed);
}
else if (this.#closing) {
throw new ClosedError("Stream is closing", undefined);
}
else {
this.#queue.push(entry);
queueMicrotask(() => this.#flushQueue());
}
}
#flushQueue() {
if (this.#flushing || this.#cursor !== undefined) {
return;
}
if (this.#closing && this.#queue.length === 0) {
this._setClosed(new ClientError("Stream was gracefully closed"));
return;
}
const endpoint = this.#client._endpoint;
if (endpoint === undefined) {
this.#client._endpointPromise.then(() => this.#flushQueue(), (error) => this._setClosed(error));
return;
}
const firstEntry = this.#queue.shift();
if (firstEntry === undefined) {
return;
}
else if (firstEntry.type === "pipeline") {
const pipeline = [firstEntry];
for (;;) {
const entry = this.#queue.first();
if (entry !== undefined && entry.type === "pipeline") {
pipeline.push(entry);
this.#queue.shift();
}
else if (entry === undefined && this.#closing && !this.#closeQueued) {
pipeline.push({
type: "pipeline",
request: { type: "close" },
responseCallback: () => undefined,
errorCallback: () => undefined,
});
this.#closeQueued = true;
break;
}
else {
break;
}
}
this.#flushPipeline(endpoint, pipeline);
}
else if (firstEntry.type === "cursor") {
this.#flushCursor(endpoint, firstEntry);
}
else {
throw impossible(firstEntry, "Impossible type of QueueEntry");
}
}
#flushPipeline(endpoint, pipeline) {
this.#flush(() => this.#createPipelineRequest(pipeline, endpoint), (resp) => decodePipelineResponse(resp, endpoint.encoding), (respBody) => respBody.baton, (respBody) => respBody.baseUrl, (respBody) => handlePipelineResponse(pipeline, respBody), (error) => pipeline.forEach((entry) => entry.errorCallback(error)));
}
#flushCursor(endpoint, entry) {
const cursor = new HttpCursor(this, endpoint.encoding);
this.#cursor = cursor;
this.#flush(() => this.#createCursorRequest(entry, endpoint), (resp) => cursor.open(resp), (respBody) => respBody.baton, (respBody) => respBody.baseUrl, (_respBody) => entry.cursorCallback(cursor), (error) => entry.errorCallback(error));
}
#flush(createRequest, decodeResponse, getBaton, getBaseUrl, handleResponse, handleError) {
let promise;
try {
const request = createRequest();
const fetch = this.#fetch;
promise = fetch(request);
}
catch (error) {
promise = Promise.reject(error);
}
this.#flushing = true;
promise.then((resp) => {
if (!resp.ok) {
return errorFromResponse(resp).then((error) => {
throw error;
});
}
return decodeResponse(resp);
}).then((r) => {
this.#baton = getBaton(r);
this.#baseUrl = getBaseUrl(r) ?? this.#baseUrl;
handleResponse(r);
}).catch((error) => {
this._setClosed(error);
handleError(error);
}).finally(() => {
this.#flushing = false;
this.#flushQueue();
});
}
#createPipelineRequest(pipeline, endpoint) {
return this.#createRequest(new URL(endpoint.pipelinePath, this.#baseUrl), {
baton: this.#baton,
requests: pipeline.map((entry) => entry.request),
}, endpoint.encoding, json_PipelineReqBody, protobuf_PipelineReqBody);
}
#createCursorRequest(entry, endpoint) {
if (endpoint.cursorPath === undefined) {
throw new ProtocolVersionError("Cursors are supported only on protocol version 3 and higher, " +
`but the HTTP server only supports version ${endpoint.version}.`);
}
return this.#createRequest(new URL(endpoint.cursorPath, this.#baseUrl), {
baton: this.#baton,
batch: entry.batch,
}, endpoint.encoding, json_CursorReqBody, protobuf_CursorReqBody);
}
#createRequest(url, reqBody, encoding, jsonFun, protobufFun) {
let bodyData;
let contentType;
if (encoding === "json") {
bodyData = writeJsonObject(reqBody, jsonFun);
contentType = "application/json";
}
else if (encoding === "protobuf") {
bodyData = writeProtobufMessage(reqBody, protobufFun);
contentType = "application/x-protobuf";
}
else {
throw impossible(encoding, "Impossible encoding");
}
const headers = new Headers();
headers.set("content-type", contentType);
if (this.#jwt !== undefined) {
headers.set("authorization", `Bearer ${this.#jwt}`);
}
return new Request(url.toString(), { method: "POST", headers, body: bodyData });
}
}
function handlePipelineResponse(pipeline, respBody) {
if (respBody.results.length !== pipeline.length) {
throw new ProtoError("Server returned unexpected number of pipeline results");
}
for (let i = 0; i < pipeline.length; ++i) {
const result = respBody.results[i];
const entry = pipeline[i];
if (result.type === "ok") {
if (result.response.type !== entry.request.type) {
throw new ProtoError("Received unexpected type of response");
}
entry.responseCallback(result.response);
}
else if (result.type === "error") {
entry.errorCallback(errorFromProto(result.error));
}
else if (result.type === "none") {
throw new ProtoError("Received unrecognized type of StreamResult");
}
else {
throw impossible(result, "Received impossible type of StreamResult");
}
}
}
async function decodePipelineResponse(resp, encoding) {
if (encoding === "json") {
const respJson = await resp.json();
return readJsonObject(respJson, json_PipelineRespBody);
}
if (encoding === "protobuf") {
const respData = await resp.arrayBuffer();
return readProtobufMessage(new Uint8Array(respData), protobuf_PipelineRespBody);
}
await resp.body?.cancel();
throw impossible(encoding, "Impossible encoding");
}
async function errorFromResponse(resp) {
const respType = resp.headers.get("content-type") ?? "text/plain";
let message = `Server returned HTTP status ${resp.status}`;
if (respType === "application/json") {
const respBody = await resp.json();
if ("message" in respBody) {
return errorFromProto(respBody);
}
return new HttpServerError(message, resp.status);
}
if (respType === "text/plain") {
const respBody = (await resp.text()).trim();
if (respBody !== "") {
message += `: ${respBody}`;
}
return new HttpServerError(message, resp.status);
}
await resp.body?.cancel();
return new HttpServerError(message, resp.status);
}